代码改变世界

C++数组剖析

2011-06-03 14:42  x_feng  阅读(315)  评论(0)    收藏  举报

(发现自己像记流水账一样,感觉条例不太清晰,也没有大牛的深入,或许这就是想写博的原因,发现不足和差距,在学习中延伸更多的东西)

C++Primer曰:与vector类型相似,数组也可以保持某种类型的一组对象,它们的区别在于:数组长度固定。数组一经创建,就不允许增加新的元素。

C++Primer曰:现代C++程序应尽量使用vector。设计良好的程序只有在强调速度时才在类的内部使用数组

C++Primer曰:与vector相比,数组的缺陷在于:数组长度固定,程序员无法知道一个给定数组的长度,无size操作,无push_back动态添加元素。

C++Primer曰:与使用vector类型的程序相比,依赖于内置数组的程序更容易出错而且难于调试。

……看到这里,如果你依然觉得数组还可用,那么就继续往下看,听我白唬一会。

1.数组定义

类型 数组名[常量表达式]。比如:int a[n]。这里最关键的就是数组的维数,这个维数必须是常量表达式,那么此常量表达式包含哪些呢?

/*有必要说一下什么是常量表达式:编译器在编译时就能够计算出结果的整型表达式*/

*整型字面值常量,如:int a[3],3

*用常量表达式初始化的整型const对象,如:const int n = 4, char h[n]。

*枚举常量。说道enum这里赘述一下:如enum obj{shape = 1, sphere, cylinder},枚举成员是常量。可以为一个或多个枚举成员提供初始值,用来初始化枚举成员的值必须是一个常量表达式。定义数组:int a[sphere],即为int a[2]。

const int size = 10;
const int hsize = get_size();
int nsize = 10;
char cbuf_array[size];//ok
char hbuf_array[hsize];//error
char nbuf_array[nsize];//error, nsize不是const类型,这也是最容易错的一种,好在编译器不通过,到那时就能发现问题。

2.数组初始化

显式初始化数组元素:

int ia[3] = {0, 1, 2};
int ia[3] = {1};//数组所有元素都是1
int ia[ ]  = {0,1,2};//编译器会确定数组长度为3
int ia[5] = {1, 2, 3};与 int ia[5] =   {1, 2, 3, 0, 0};等价

字符数组有点不同:

char ch[] = {‘a’, 'b’, 'c’};与char ch2[] = “abc”;完全不同ch的维数为3,而ch2的维数为4,使用一组字符字面值初始化字符数组时,一定要记得添加结束字符串的空字符。char ch2[] = “abc”;自动在末尾添加了一个空字符。
char ch3[3] = “abc”;//错误,空间不足

/*题外话:你也可以测试一下sizeof,strlen:

char ch1[] = {‘a’, 'b’, 'c’};
char ch2[] = “abc”;
sizeof(ch2) = 4;//ok,数组的实际大小为4,末尾自动添加了一个空字符
strlen(ch2) = 3;//ok,为什么是3,而不是4呢?strlen返回当前字符串的长度,不包括结尾空字符
sizeof(ch1) = 3;//ok
strlen(ch1) = ??//这里是多少?看似正确的写法,其实很错!win32,vc6.0下为7,vs2010下为24,很奇怪的两个数字吧?

**注意:strlen(const char * _Str);1,只能char *做参数。2,char *参数必须要求是指向一串:以“\0”为结尾的字符串,就像这样:char ch2[] = “abc”;。3,返回值,不包括“\0”的长度。

strlen 原型:extern int strlen(char *s); 
    用法:#include <string.h> 
    功能:计算字符串s的(unsigned int型)长度 
    说明:返回s的长度,不包括结束符NULL。

*/

当然数组不允许数组直接复制和赋值
int ia[] = {0,2,3};
int ia2[](ia);//error
int ia2[] = ia;//error

3.数组操作

int ia[] = {0,2,3};

很明显,数组不能越界。数组名不是指针而是常量,但是它的值是整个数组的首地址,如ia++就是错误的。但是你可以这样写ia + 1就好啦。

4.数组与指针

首先明确一点:数组名不是指针。现在来区分一下数组指针和指针数组。

(1)指针数组:一个数组里存放的都是同一个类型的指针。定义一个指针数组:int *p[3];

指针数组内容:比如:char* a[3]; a[0],a[1],a[2]每一个都一个类型为char*的指针。

指针数组名的含义:我们来看看a到底什么样的!如下:char* a[5];

a[0] = “abc”;
a[1] = “def”;
a[2] = “ghi”;
a[3] = "jkl";
a[4] = "mnu";
sizeof(a) = 12;//为什么
sizeof(*a) = 4;//为什么
sizeof(*(*a)) = 1;//为什么,我们来看看它们在内存中的值

无标题

我们来分析一下char*a[5]:

由于[]的优先级比*的优先级高,所以a[5]先结合,这说明a是一个数组(不是指针),a为数组名,那么前面的char*则是这个数组存储的类型,就如同char p[5]一样,p是数组名,数组的类型为了char。而char* a[5]则是把char换成了char*,数组的类型不一样了。大家都知道sizeof(p) = 5,这是因为数组p有5个char元素。那么显然:char* a[5],数组a有也有5个元素,元素的类型为char*,是指针,因此大小为20。

现在来分析一下*a和**a:

在而char* a[5]中,a为数组名,*a表示我们取整个数组的第一个元素,而这个元素的类型是什么呢?当然是char*类型,是一个指针,那么这个指针指向什么呢?指向一串字符串。**a表示取*a指向字符串的第一个元素,因此上图中**a = ‘a’,单字符的大小为1,是第一个元素。

现在分析一下a+1:

上图中a的值0x0038f6a0,a+1的值为:0x0038f6a4,a+1比a的值大4,即在a的基础上向后偏移4。刚说过,a是数组,a+1当然是指向a[1]这里,因为a里面存储的是指针,指针大小为4。

(2)数组指针:int (*a)[5],定义一个指向一维数组的指针。a是指针,它指向一个int的数组,数组大小为5。

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

a = p;

sizeof(a) = 4;//a为指针

sizeof(*a) = 20;//为什么?由定义:int (*a)[5],定义一个指向一维数组的指针,a指向的是一个大小为5的数组,那么*a就相当于这个数组的数组名,显然这个数组的大小为4*5。

sizeof(**a) = 4;//第一个元素类型的大小,int

那么来看一看a+1是什么?

由于a指向一个数组,因此a所指向的空间的大小为这个数组的大小,因此,a+1时,a要向后偏移整个数组的大小,这里a+1其实就是指向p的第二行,即&p[1][0]。

下面来看一道有趣的题:

int a[5] = { 1, 2, 3, 4, 5 }; 
int *ptr = (int*)(&a + 1); 
printf(“%d %d\n”, *(a + 1), *(ptr – 1)); 

这道题考的其实是指向数组的指针,&a是一个隐式的指向int [5]数组的指针,它和int* ptr是不一样的,如果真要定义这个指针,应该是int (*ptoa)[5]。所以ptoa每一次加一操作都相当于跨越int a[5]的内存步长(也就是5个int长度),也就是说&a + 1其实就是指向了a[5]这个位置,实际上内存里面这个位置是非法的,但是对ptr的强制转换导致了后面ptr-1的内存步长改为了1个int长度,所以ptr-1实际指向了a[4]。至于*(a+1)没什么好说的,值就是2