C/C++数组详解(一维和二维)

数组这东西,说说都懂,但是似乎并没有完全吃透,导致很多地方有疑惑。所以再梳理一遍。

数组定义

数组是存放类型相同的对象的容器,这些对象本身没有名字,需要通过其所在位置访问。

从定义中可以看出,数组存放的是对象且类型相同。所以不存在引用的对象(引用不是对象)

数组的局限性:数组的大小确定不变,不能随意向数组中增加元素。如果不确定元素的确切个数,使用vector。

定义和初始化内置数组

数组的声明形如a[d],其中a是数组的名字,d是数组的维度。维度说明数组中的元素的个数,因此必须大于0。数组中元素的个数也属于数组类型的一部分,编译的时候维度应该也是已知的。也就是说,维度必须是一个常量表达式:

unsigned int cnt = 42;   //不是常量表达式

int arr[10];     //含有10个整数的数组  // 10是字面值常量

const int sz = 42;      //常量表达式 

constexpr int sz = 42; //C++11,常量

const int sz = get_size();  //不是常量表达式,sz的值只有在运行时才会获取

int arr['a'];//‘a’是字符字面值常量,

默认情况下,数组的元素被默认初始化

和内置类型的变量一样,如果在函数内部定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值。

定义数组的时候必须指定数组的类型,不允许使用auto关键字由初始化的列表推断类型。

显式初始化数组

可以对数组的元素进行列表初始化,此时可以忽略数组的维度。如果在声明时候没有指定数组的维度,编译器可以根据初始值的数量计算并推测出来;相反,如果指明了维度,那么初始值的总数量不应该超过指定的大小。如果维度比提供的初始值数量大,则用提供的初始值初始化靠前的元素,剩下的元素被初始化成默认值。(如int中的0,string中的“”)

constexpr int sz = 3;

int ia1[sz] = {0,1,2};

int a2[] = {0,1,2};

int a3[5] = {1,2,3};   //等价于a3[5]={1,2,3,0,0}

srting a4[3] = {"hi","bye"};  //等价于a4[3]={"hi","byr",""}

int a[2]  = {1,2,3};   //错误,初始值过多

字符数组的特殊性

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

char a1[] = {'C', '+', '+'};   //列表初始化,没有空字符

char a2[] = {'C', '+', '+', '\0'}; 

char a2[] = "C++";   //自动添加表示字符串结束的空字符

const char a4[6] = "Daniel";   //错误:没有空间可以存放空字符 

注意:有时候在Linux和Windows下不太一样,Windows要求严格(出错直接报错,而Linux并不会,内存管理机制不一样)

注意:特别是在拷贝的时候,注意结尾处有没有加上'\0'

不允许拷贝和赋值

不能将数组的内容拷贝给其他数组作为初始值,也不能用数组为其他数组赋值:

int a[] = {0,1,2};

int a2[] = a;  //错误:不允许使用一个数组初始化另一个数组

a2 = a;    //错误:不能把一个数组直接赋值给另一个数组

理解复杂的数组声明

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

 

①:int *ptrs[10];          //ptrs是含有10个整型指针的数组

默认情况下,类型修饰符从右向左依次绑定,所以ptrs首先是一个大小为10的数组,它的名字是ptrs,然后数组中存放的是指向int的指针。

②:int &refs[10]  = /* ? */;     //错误:不错在引用的数组

因为数组存放的是对象,而引用不是对象。

拓展:这里还要注意一点:引用在定义的时候必须初始化

例1:int &ref; //错误

例2:class A{

public:

A::A();

~A::A();

int &ref;   //正确

};

解释:引用在定义的时候必须初始化,而什么叫定义,凡是有内存占用行为的就是定义,否则就是声明。

③:int (*Parray)[10] = &arr;    //Parray指向一个含有10个整数的数组

其实这里很好理解,就是按照运算符优先级理解,首先()与[]优先级一样,所以按照结合,先执行()再[],()括号里表明Parray是一个指针,然后在执行右边的[],所以得知Parray是一个指向大小为10的数组的指针,然后左边的int表明数组类型为int。

注意:等号右边是对整个数组取地址

知识点:数组名在什么时候退化为指针

以下情况均不能将数组名s视为指针:

(1) sizeof(s)
(2) &s;
(3) 用数组对数组的引用进行初始化时

④:int (&arrRef)[10] = arr;      //arrRef引用一个含有10个整数的数组

这个与③一样。只不过arrRef是大小10,类型为int的数组的引用。因为这里是对数组的引用,所以右边arr理解为数组而不是首元素地址。

多维数组

严格来说,C++语言中没有多维数组,通常说的多维数组其实是数组的数组。

int a[2][3];   //大小为2的数组,每个元素是含有3个整数的数组

int b[10][20][30] = {0};   //大小为10的数组,每个元素是含有20个数组的数组,然后这些数组是含有30个整数的数组

对于二维数组来说,常把第一个维度称作行,第二个维度称作列。

多维数组的初始化

int ia[3][4] = {                //三个元素,每个元素都是大小为4的数组

        {0,1,2,3},              // 第一行的初始值

        {4,5,6,7},              // 第二行的初始值

        {8,9,10,11}           // 第三行的初始值

};

也可以是:

int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};

也可以显示初始化每行的首元素:

int ia[3][4] = {{ 0 },{ 4 },{ 8 }};  //其他未列出的元素执行默认值初始化,但是若省去花括号就不一样了:

int ia[3][4] = {0,4,8};  //这里显示初始化第一行前3个元素,后面的元素默认初始化

理解如下声明:

int ia[3][4] = { 0 };

int  (&row)[4]  = ia[1];    //把row绑定到ia的第二个4元素数组上

解释:首先二维数组和一维数组一样,数组名也是指向数组首元素的指针。

但是:这里一定要注意二维数组的元素其实是数组,

而上面说过数组引用初始化时,数组不褪化为指针,所以ia[1]代表第二个元素,也就是一个大小为4的数组。

猜你喜欢

转载自blog.csdn.net/songsong2017/article/details/83625004