• 数组


    数组时一种类似于标准库类型 vector 的数据结构,但是再性能和灵活的权衡上又与 vector 有所不同。与 vector 相似的地方是,数组也是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在位置访问。与 vector 不同的地方是,数组的大小确定后不能变,不能随意向数组中添加元素。因为数组的大小固定,因此对某些特殊的应用来说程序运行时的性能较好,但是相应的也损失了一些灵活性。

    定义和初始化内置数组

     1 unsigned cnt = 42;//不是常量表达式
     2 
     3 constexpr unsigned sz = 42;//常量表达式
     4 
     5 int a[10];//含有10整数的数组
     6 
     7 int *b[sz];//含有42个整型指针的数组
     8 
     9 string c[cnt];//错误,cnt不是常量表达式
    10 
    11 string d[get_size()];//当constexpr时正确,否则错误

    默认情况下,数组的元素被默认初始化。定义在函数体外时默认值为0,在函数体内时默认值为随机值(未定义的值)。

    定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值的列表推断类型。另外和 vector 一样,数组的元素应该为对象,因此不存在引用的数组。

    显示初始化元素:

     1 const unsigned sz = 3;
     2 
     3 int a[sz] = {0, 1, 2};//含有3个元素的数组,元素值分别为0, 1, 2
     4 
     5 int b[] = {0, 1, 2};//含有3个元素的数组,元素值分别为0, 1, 2
     6 
     7 int c[5] = {0, 1, 2};//等价于 c[5] = {0, 1, 2, 0, 0}
     8 
     9 string d[3] = {"hello", "world"};//等价于d[3] = {"hello", "world", ""}
    10 
    11 int e[2] = {0, 1, 2};//错误,初始值过多

    可以发现,当初始值的数目多于数组大小时,会编译错误,当初始值的数目小于数组大小时,初始值会赋给靠前的元素,剩下的元素被初始化为默认值(一般为0)。

    字符数组:

    字符数组有一种额外的初始化形式,可以用字符串字面值对此类数组初始化。当使用这种方式时,一定要注意字符串字面值的结尾处还有一个空字符,这个空字符也会像字符串的其他字符一样拷贝到字符数组中去。

    1 char a[] = {'c', '+', '+'};//列表初始化,没有空字符
    2 
    3 char b[] = {'c', '+', '+', ''};//列表初始化,含有显示的空字符
    4 
    5 char c[] = {"c++"};//用字符串字面值拷贝初始化,会自动添加表示字符串结束的空字符
    6 
    7 char d[3] = {"c++"};//错误,没有空间存储空字符

    不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值,这一点也是区别于 vector 的

    1 int a[] = {1, 2, 3};
    2 
    3 int b[] = a;//错误,不能用一个数组初始化另一个数组
    4 
    5 b = a;//错误,不能把一个数组直接赋值给另一个数组

    和 vector 一样,数组能存放大多数类型的对象。如:可以定义一个存放指针的数组。又因为数组本身就是对象,所以允许定义数组的指针及数组的引用。

     1 int x[10];
     2 
     3 int *a[10];//a是含有10个整型指针的数组
     4 
     5 int &b[10] = /*?*/;//错误,不存在引用的数组(引用不是对象)
     6 
     7 int (*c)[10] = &x;//c指向一个含有10个整型数的数组
     8 
     9 int (&d)[10] = x;//d绑定一个含有10个整型的数组
    10 
    11 int *(&e)[10] = a;//e是数组的引用,该数组含有10个指针

    理解复杂的声明语句可以由变量名开始由内往外,从右往左阅读声明语句

    数组和指针:

    在c++语言中,指针和数组联系非常紧密。使用数组的时候编译器一般会将数组转化为指针。

    通常情况下,使用取地址符来获取指向某个对象的指针,取地址符可用于任何对象,对数组使用下标运算符得到该数组指定位置的元素。因此像其他对象一样,对数组的元素使用取地址符能得到该元素的指针:

    1 string nums[] = {"one", "two", "three"};
    2 
    3 string *p = &nums[0];//p指向nums数组的第一个元素
    4 
    5 string *q = nums;//等价于q = nums[0];

    在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针。

    1  int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    2 
    3 auto b(a);//b是一个整型指针,指向a的第一个元素
    4 
    5 b = 2014;//错误,不能给指针赋一个int类型的值

    尽管 a 是由 10 个整型数构成的数组,但当使用 a 作为初始值时,编译器实际执行的初始化过程类似于

    auto b(&a[0]);//显然 b 的类型是 int*

    另外还需要注意的是,当使用 decltype 关键字时上述转换不会发生,decltype(a) 返回的类型是由 10 个整型数组成的数组:

    1 int cnt = 1024, *p = &cnt;
    2 
    3 int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    4 
    5 decltype(a) b = {1, 2, 3, 4, 5, 6};//b是一个包含 10 个整型数的数组
    6 
    7 cout << b[0] << endl;//输出 b 数组的第一个元素1
    8 
    9 b = p;//错误,不能用整型指针给数组赋值

    指针也是迭代器

     1 int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
     2 
     3 int *s = a, *e = &a[10];//s指向a的第一个元素,e为a的尾后指针
     4 
     5 for(int *indx = a; indx != e; indx++){//用指针遍历数组
     6 
     7     cout << (*indx) << endl;
     8 
     9 }
    10 
    11 int *x = begin(a), *y = end(a);//x指向a的第一个元素,y为a的尾后指针
    12 
    13 //begin返回a的首元素指针,end返回a的尾后指针,这两个元素定义在iterator头文件中
    14 
    15 while(x != y){
    16 
    17     cout << (*x) << endl;
    18 
    19     x++;
    20 
    21 }

    下标和指针

     1 int a = [1, 2, 3, 4, 5];
     2 
     3 int *p(a);//此时a和p具有相同的地址,所以 a[1, 2, 3, 4] 和 p[1, 2, 3, 4] 地址也相同
     4 
     5 cout << p[2] << endl;//p[2]等价于a[2],输出3
     6 
     7 p[2] += 1;//给&p[2]位置的对象加一,所以此时 a[2] 的值为 4
     8 
     9 int cnt = *(p + 2);//等价于cnt = a[2]
    10 
    11 p += 2;
    12 
    13 int k = p[-2];//等价于k = a[0]

    使用数组初始化 vector 对象

    1 int a[] = {1, 2, 3, 4, 5};
    2 
    3 vector<int> v(begin(a), end(a));//整个数组 a 的元素作为 v 的初始值
    4 
    5 vector<int> vt(a + 1, a + 3);//将数组元素 a[1],a[2] 作为 vt 的初始值

    多维数组:

    在 c++ 中多维数组就是数组的数组。

    多维数组的初始化:

     1 int a[3][2] = {
     2 
     3     {1, 2},//内层在嵌套花括号对应a的三个数组元素
     4 
     5     {3, 4},
     6 
     7     {5, 6}
     8 
     9 };
    10 
    11 int a[3][2] = {
    12 
    13     {1, 2},//也可以给部分元素嵌花括号
    14 
    15     3, 4,
    16 
    17     5, 6
    18 
    19 };
    20 
    21 int a[3][2] = {
    22 
    23     1, 2,//也可以不嵌花括号
    24 
    25     3, 4,
    26 
    27     5, 6
    28 
    29 };
    30 
    31 int a[3][2] = {
    32 
    33     1, 2, 3//没加花括号时初始化是从前往后依次进行的,不足的自动初始化为0
    34 
    35 };
    36 
    37 int a[3][2] = {
    38 
    39     {1},//也可以只初始化某些行的部分元素,剩下的元素自动初始化0
    40 
    41     {3},
    42 
    43     {5, 6}
    44 
    45 };
    46 
    47 int a[3][2] = {
    48 
    49     1, 2//值初始化了第一行,其余的自动初始化为0
    50 
    51 };

    多维数组的下标引用:

    如果表达式含有的下标运算符数量和数组的维度一样多,该表达式的结果将是给定类型的元素;反之,如果表达式含有的下标运算符数量比数组的维度少,则表达式的结果将是给定索引处的一个内层数组:

     1 int a[3][2] = {
     2 
     3     {1, 2},//内层在嵌套花括号对应a的三个数组元素
     4 
     5     {3, 4},
     6 
     7     {5, 6}
     8 };
     9 
    10 int (&row)[2] = a[2];//将row绑定到 a 的第三个 2 元素数组上
    11 
    12 cout << row[0] << " " << row[1] << endl;//输出5 6

    使用范围 for 循环处理多维数组

     1 int a[3][2] = {0, 1, 2, 3, 4, 5};
     2 
     3 for(auto &row : a){//如果row不加引用声明,数组会自动转化为指针,即row会是 int* 类型,显然是错误的
     4 
     5     for(auto &col : row){//如果col不加引用声明,col会是 int 类型,原数组的元素不会被修改
     6 
     7         col += 1;
     8 
     9     }
    10 
    11 }

    还有需要注意的是:

    对于多维数组,如果解引用次数少于数组维数的话得到的是指针,而且将数组映射到空间的话,同一位置对应的内存地址是相同的,与解引用次数无关。如上图所示:a[3][3][3],可以当作一个 3*3*3 的正方体,a 即(0,0,0)位置,在得到其中元素对象之前,解引用0次,1次,2次得到的地址是相同的。对于更高维的数组也同样如此。不过这些地址本身值是一样的,但在其他方面还是有所不同的:

    解引用次数不同的指针的移动方向是不同的,如上图中的二维数组所示,没有解引用得到的指针 q 执行自增运算后是向下移动的,而解引用一次得到的指针 q_ 执行自增运算后是向又移动的。对于高维情况也是如此。

  • 相关阅读:
    javascript 字符串截取
    HTML5 转
    Javascript try catch finally
    C#之json字符串转xml字符串
    AspNetCore Json序列化设置
    类加载(对象创建)顺序
    线性表,线性表和链表的区别
    Implement int sqrt(int x).
    Add Binary
    Roman to Integer(将罗马数字转成整数)
  • 原文地址:https://www.cnblogs.com/geloutingyu/p/7900402.html
Copyright © 2020-2023  润新知