第三章 字符串、向量和数组

3.1 using声明

  • 可以对单个名字进行独立的using声明,比如using std::cin
  • 头文件中不应该包含using声明。

3.2 string

初始化 string 对象的方式

3.3 vector

初始化 vector 对象的方法

  • 列表初始化: vector<string> v{"a", "an", "the"}; (C++11)
  • 拷贝初始化:=
  • 直接初始化:()
  • 数组初始化:vector<int> v(arr.begin(), arr.end());
    vector 支持的操作
  • 范围for语句内不应该改变其遍历序列的大小。
  • vector对象(以及string对象)的下标运算符,只能对确知已存在的元素执行下标操作,不能用于添加元素。

3.4 迭代器iterator

使用迭代器

  • 养成使用迭代器和!=的习惯(泛型编程),循环判断中少使用<(因为所有标准库迭代器都定义了==!=,但是只有很少一部分定义了<)。
  • 但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。

3.5 数组

定义和初始化内置数组

  • 初始化:列表初始化
    • 字符数组可以用字符串字面值进行初始化,结尾\0也拷贝到字符数组中
  • 数组不允许直接拷贝或赋值给另一个数组。
    • 想复制可以使用memcpy(void *dst, void *src, size_t size)
  • 复杂的数组声明,指向数组的指针绑定到数组的引用
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
        int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; // arr是int[10]类型
    
        int (*pa)[10] = &arr; // pa是指向arr(int[10]类型)的指针,右边当然要取地址(虽然arr==&arr)
        for(int i = 0; i < 10; ++i)
            cout<<*( (*pa) +i)<<" "; // *pa是数组指针解引用,即得到arr(即为数组名);剩下部分即为通常的指针访问数组
            // cout<< (*pa)[i] << " ";  // 或者这样访问元素
        cout<<endl;
    
        int (&ra)[10] = arr; // ra是绑定到arr(int[10]类型)的引用,右边即为对象
        for(int i = 0; i < 10; ++i)
            cout<<*(ra + i) << " "; 
            // cout<< ra[i] <<" ";
        cout<<endl;
    
        int m[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
    
        int (*p)[5] = m; // 左边是指向int[5]类型的指针,右边是&m[0](即为m)
        for(int i = 0; i < 3; ++i)
            for(int j = 0; j < 5; ++j)
                cout<< (*(p + i)) [j] << " "; // p+i表示指向哪一个int[5]类型,解引用即为者5个int数组的数组名
                // 等价写法:p[i][j], *(p[i] + j), *(*(p+i) + j)
    
        int (*pp[10])[5]; 
        // 首先pp与[10]结合,表明这是一个数组
        // 剩下的部分就是数组元素的类型:int (*)[5]
        // 即每个数组元素是一个指针,指向int[5]类型
    
    各类指针的详细介绍

数组与指针

  • 很多情况下,编译器会将数组名自动替换为指向数组首元素的指针(相当于顶层const指针)
  • 数组的类型推断
    • 当auto变量基于数组名进行类型推断时,得到的是对应类型的指针
    • 当使用decltype进行类型推断时,返回数组类型
      1
      2
      3
      4
      
      int arr[] = {1,2,3};
      auto ptr(arr);  //ptr是int*类型
      decltype(arr) arr2 = {4,5}; // 相当于arr2的类型是int[3]
      for(auto i: arr2) cout<<i<<" ";  // 输出是4 5 0而非4 5 3
      
  • 数组名不是指针
    • 证据一:sizeof(数组名) = 整个数组所占内存大小,如果数组名是指针,则为4或8
    • 证据二:对数组名取地址得到的是整个数组的地址,如果数组名是指针,则对数组名取地址是另外一个地址(指针的地址)
      • arr(数组首个元素的地址)与&arr(数组地址)在值上是相等的,但是意义不同 image.png image.png
  • 函数传参和接受参数时,传递的都是指针,即使形参是int arr[]这样表示整个数组(可能情形是,外面传入的实参是数组名,但是形参实例化时自动转换为指针)
    • 否则需要进行数组的拷贝,效率低
    • int arr[x]中即使带数组容量,也会被忽略,x随便取都无所谓
  • 参考

C风格字符串

  • C风格字符串:以空字符\0结束的char数组
  • 相互使用:
    • 可以直接将char字符数组赋值给string字符串
    • 字符串到字符数组:const char *arr = str.c_str();
      • str, arr使用的是同一块数据
  • C 风格字符串的函数strlen, strcmp, strcat, strcpy
    • 传入参数必须是字符数组,注意列表初始化字符数组时必须最后带\0
  • 字符串与字符数组:
    • 字符串:使用字符指针指向只读数据段的一片区域
    • 字符数组:放在栈中或者数据段
    • 参考

3.6多维数组

  • 多维数组阅读顺序由内向外
  • 多维数组的初始化
    1
    2
    3
    4
    5
    
    int ia[2][4] = { {0, 1, 2, 3}, {4, 5, 6, 7} };
    int ib[2][4] = {0, 1, 2, 3, 4, 5, 6, 7};
    // 初始化部分元素
    int ic[2][4] = {{ 0 }, { 4 }}; // 初始化第一列
    int id[2][4] = {0, 3, 6, 9}; // 初始化第一行
    
  • 使用range-for语句时,除了最内层的循环外,外层循环的控制变量都应该是引用类型。
    • 否则编译器自动将数组名转换为指针,无法使用range-for语法
  • 遍历:int ia[2][4] = { {0, 1, 2, 3}, {4, 5, 6, 7} };
    • range-for:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      // row为int[4]类型,如果row不是引用类型(不加&),编译器会自动将row从int[4]类型转变为指针,无法遍历指针
      for(auto &row: ia)
          for(auto item: row)
              cout<<item<<" ";
      
      // 不使用auto,写出变量类型:
      for(int (&row)[4]: ia) // 理解山相当于:int[4] &row
          for(int item: row)
              cout<<item<<" ";
      
    • 使用i,j下标进行访问
    • 使用指针访问
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      
      for(auto p = ia; p != ia+2; ++p)
          for(auto q = *p; q!= *p+4; ++q)
              cout<<*q<<" ";
      
      // 不使用auto:
      for(int (*p)[4] = ia; p != ia+2; ++p)
          for(int *q = *p; q != *p+4; ++q)
              cout<<*q<<" ";
      
      // 使用类型别名
      using arr = int[4];
      // typedef int arr[4]; // 看起来比较奇怪
      for(arr *p = ia; p != ia+2; ++p)
          for(int *q = *p; q != *p+4; ++q)
              cout<<*q<<" ";
      

其他

const、指针和引用、多维数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
int m[3][4] ={
    {0, 1, 2, 3},   
    {4, 5, 6, 7},   
    {8, 9, 10, 11}  
};
int (&r_m)[4] = m[0]; // r_m是一个绑定到int[4]数组的引用,
// int* (&rr_m)[3] = m; // rr_m是一个绑定到(int*)[3]数组的引用,即绑定到大小为3的指针数组;但是m[0], m[1], m[2]各自都是一个int[4]数组的引用,m的类型是int[3][4],类型都不匹配
int (&rr_m)[3][4] = m; // rr_m是一个绑定到int[3][4]的引用

int (*p)[4] = m; // p是一个指向int[4]数组的指针(即每个元素是int[4]数组,即m)
int* pp[3] = {m[0], m[1], m[2]}; // m[0], m[1], m[2]每个是一维数组的数组名,只是编译器将数组名转换为指针,{m[0], m[1], m[2]}就变成了一个指针数组
int* (&r)[3] = pp; 
// int* (&r)[3] = {m[0], m[1], m[2]}; // 报错,因为{m[0], m[1], m[2]}是右值,尝试将右值赋值给引用
  • 也进行顶层const和底层const的区分

动态数组

  • 使用 newdelete表达和c中mallocfree类似的功能,即在堆中分配存储空间。
  • 定义: int *pia = new int[n];
  • 释放: delete [] pia;,注意不要忘记[]