数组

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

 

定义和初始化数组

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

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

constexptr unsigned sz=42;  //常量表达式

int arr[10];//含有10个整数的数组

int *parr[sz];  //含义42个整型指针的数组

string bad[cnt]; //错误:cnt不是常量表达式(但是在我的编译器上运行是对的)

string strs[get_size()];  //当get_size是constexptr时正确;否则错误

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

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

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

 

不允许拷贝和赋值

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

int a[]={0,1,2};  //含有3个整数的数组

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

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

 

理解复杂的数组声明

和vector一样,数组能存放大多数类型的对象。例如,可以定义一个存放指针的数组。又因为数组本身就是对象,所以允许定义数组的指针及数组的引用。在这几种情况中,定义存放指针的数组比较简单和直接,但是定义数组的指针或数组的引用就稍微复杂一点了:

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

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

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

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

默认情况下,类型修饰符从右向左依次绑定。对应ptrs来说,从右向左理解其含义比较简单:首先知道我们定义的是一个大小为10的数组,它的名字是ptrs,然后知道数组这存放的值指向int的指针。

但是对于parray来说,从右向左理解就不太合理了,因为数组的维度是紧跟着被声明的名字的,所以就数组而言, 由内向外阅读要比从右向左好多了。由内向外的顺序可帮助我们更好地理解parray的含义:首先是圆括号括起来的部分,*parray意味着parray是个指针,接下来观察右边,可知道parray是个指向大小为10的数组的指针,最后观察左边,知道数组这的元素是int。这样最终的含义就明白无误了,parray是一个指针,它指向一个int数组,数组这包含10个元素。同理,(&arrRef)表示arrRef是一个引用,它引用的对象是一个大小为10的数组,数组中元素的类型是int。

当然,対修饰符的数量并没有特殊限制:

int *(&arry)[10]=ptrs;  //arry是数组的引用,该数组含有10个指针

安装由内向外的顺序阅读上述语句,首先知道arry是一个引用,然后观察右边知道,arry引用的对象是一个大小为10的数组,最后观察左边知道,数组的元素类型是指向int的指针。这样,arry就是一个含有10个int型指针的数组的引用。

 

指针和数组

在一些情况下数组的操作实际上是指针的操作,这一结论有很多隐含的意思。其中一层意思是当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组。

int ia[]={0,1,2,3,4,5,6,7,8,9};  //ia是一个含有10个整数的数组

auto ia2(ia);  //ia2 是一个整型指针,指向ia的第一个元素

ia2=42;  //错误:ia2是一个指针,不能用int值给指针赋值

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

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

必须指出的是,当使用decltype关键字时上述转换不会发生,decltype(ia)返回的类型是由10个整数构成的数组:

//ia3是一个含有10个整数的数组

decltype(ia) ia3={0,1,2,3,4,5,6,7,8,9};

ia3=p;//错误:不能用整型指针给数组赋值

ia3[4]=i;  //正确:把i的值赋给ia3的一个元素

 

标准库函数begin和end

为了让指针的使用更简单、更安全,C++11新标准引入了两个名为begin和end的函数。这两个函数与容器中的两个同名成员功能类似,不过数组毕竟不是类类型,因此这两个函数不是成员函数。正确的使用形式是将数组作为它们的参数:

int ia[]={0,1,2,3,4,5,6,7,8,9}; //ia是一个含有10个整数的数组

int *beg=begin(ia);   //指向ia首元素的指针

int *last=end(ia);   //指向arr尾元素的下一位置的指针

begin函数返回指向ia首元素的指针,end函数返回指向ia尾元素下一位置的指针,这两个函数定义在iterator头文件中。

 

下标和指针

只要指针指向的是数组中的元素(或者数组中尾元素的下一位置),都可以执行下标运算:

int *p=&ia[2];  

int j=p[1];

int k=p[-2]; //p[-2]是ia[0]表示的那个元素

虽然标准库类型string和vector也能执行下标运算,但是数组和他们相比还是有所不同。标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求,上面的最后一个例子很好地说明了这一点。内置的下标运算符可以处理负值,当然,结果地址必须指向原来的指针所指同一数组中的元素。

 

内置的下标运算符所用的索引值不是无符号类型,这一点与vector和string不一样。

posted @ 2014-08-03 20:48  Jessica程序猿  阅读(613)  评论(0编辑  收藏  举报