数组
1.一维数组
1.1数组名
请看下面这两个声明:
int a;
int b[10];
把变量a称之为标量,因为它是一个单一的值,这个变量的类型是一个整数,我们把变量b称为数组,因为它是一个值得集合。
下标和数组名一起使用,用于标识该集合中某个特定的值,比如b[0]表示数组b的第一个元素,b[5]表示该数组的第六个元素。每个特定值都是一个标量。
在C中,在几乎所有使用数组名的表达式中,数组名的值都是一个指针常量,指向数组第一个元素。它的元素类型取决于数组元素的类型,数组指针的类型是“指向数组元素类型的常量指针”。
注意,数组不等于指针,数组具有确定数量的元素,而指针只是一个标量值,编译器用数组名来记住这些属性,只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量。
数组名是一个指针常量,而不是指针变量,因为指针常量所指向的是内存中数组的起始位置,如果修改这个指针常量,唯一可行的就是移动数组元素的内存位置。但是一旦程序完成链接之后,内存中数组的位置就固定了。
只有在两种情况下数组名不用指针常量表示:
1.当数组名作为sizeof操作符操作数时
2.当数组名作为操作符&的操作数时
sizeof返回整个数组的长度而不是指向数组的指针长度,取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个数组的指针常量的指针。
例:
int a[10];
int b[10];
int *c;
c = &a[0];
表达式&a[0]是一个指向数组第一个元素的指针,但那正是数组名本身的值,所以上面的语句和下面的语句是一样的:
c = a;
注意,下面的语句是非法的:
b = a;
这样看起来好像是把a数组赋值给b数组了,但是这是非法的,必须用循环把数组的元素一个一个复制过去。
考虑下面的语句:
a = c;
这条语句是非法的,c是声明的一个指向整型的指针变量,而a是一个指针常量,常量是不能修改的。
1.2下标引用
在上面那个例子中,下面这个表达式是什么意思:
*(b + 3);
b是一个指向整型的指针,所以3这个值根据整型值的长度进行调整,所以b + 3指向的是数组第一个元素向后移动3个整数长度的位置,然后间接访问这个位置的内容。
注:除优先级外,下标引用和间接访问完全相同
例:
int array[10];
int* ap = array + 2;
指针ap指向array[2],如下图所示:
ap: 指针ap就是array + 2,&array[2]也是它的对等表达式
(*ap):它是数组的第二个元素,array[2],也可以这样写,* (array + 2)
ap[0]: 注意,C的下标引用和间接访问表达式是一样的,所以这个表达式就是*(ap + (0)),所以它的答案和上一个是一样的
ap + 6: ap指向array[2],所以这个表达式指向个数组array的第九个元素,&array[8]
*ap + 6: 间接访问操作符的优先级大于加号的优先级,所以这里先对ap进行解引用得到array[2]的元素,然后再给它加上6
*(ap + 6): 先给ap加6,然后ap指向数组array的第九个元素,然后再解引用,其实这个表达式就是array[8]
ap[6]:和这个表达式等值的是*(ap + 6),就是上一个表达式
&ap :这个表达式合法,是存储指针ap的地址的地址,但是我们无从得知其内容
ap[-1]: 这个表达式其实就是*(ap - 1),ap指向数组array的第三个元素,所以这个表达式的结果是数组array的第二个元素,array[1]
ap[9]:这个表达式对等的表达式是array[11],数组越界了所以这个表达式是非法的,标准表示这类行为是未定义的
下标引用可以作用于任意的指针,而不仅仅是数组名,作用于指针的下标引用的有效性即依赖于该指针当时指向什么内容,也依赖于下标的值。
例子:
2[array];
看起来好像不合法,但是该表达式对等的间接访问表达式是:
*(2 + (array));
注意,这种代码会极大的影响程序的可读性
1.3指针与下标
如果同时可以用指针表达式和下标表达式,应该使用哪一个呢?
看起来好像是指针比较效率高,下标更容易理解,尤其是在多维数组中,但是下标可能会影响效率。
注意:下标绝对不会比指针更有效率,但是指针有时候会比下标更有效率
1.4指针的效率
指针有时候比下标更有效率,前提是它被正确的使用。
指针使用的一些心得:
根据某个固定数目的增量在一个数组中移动时,使用指针变量将比下标产生的效率更高
声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率更高(具体提高的效率幅度依赖于机器)
那些必须在运行时求值的表达式较之Array + SIZE之类的常量表达式往往代价更高
1.5数组和指针
指针和数组是不相等的!
声明一个数组时,编译器将会根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。
声明一个指针变量时,编译器只是为指针本身提供一个内存空间。
1.6作为函数参数的数组名
当一个数组名作为函数参数传递给一个函数时,传递给函数的是该指针常量的一份拷贝,函数如果执行了下标引用,实际上是对这个指针执行间接访问操作。
1.7声明数组参数
把一个数组名传递给函数,正确的函数形参是怎样的?
int strlen(char* string);
int strlen(char string[])'
这两种形式参数在特定的上下文环境中是相同的,但是传递给函数的都是一个指针的拷贝
1.8数组初始化
就像标量变量可以在声明中进行初始化一样,数组也可以这样,例如:
int array[5] = { 10, 20, 30, 40, 50 );
初始化列表给出的值赋给数组的各个元素
在下面两个生命中会发生什么情况
int array[5] = { 1, 2, 3, 4, 5, 6};
int array[5] = { 1, 2, 3, 4};
这两种情况下,初始化值的数目和数组元素的数组并不匹配,第一个声明是错误的,第二个声明却是合法的,它为数组提供了前四个元素的初始值,第五个元素初始化为0;
1.9初始化字符数组
下面两个例子:
char mes[] = { 'h', 'e', 'l', 'l', 'o', 0 };
char mes[] = "hello";
第一种方法十分笨拙,所以初始化一个字符数组应该用第二个方法
注意:
char mes1[] = "hello";
char* mes2 = "hello";
这两个看上去很像,但是其实是不同的,mes1初始化一个字符数组的元素,mes2是一个真正的字符串常量。
2.多维数组
如果某个数组的维数不止一个,它就被称为多维数组,例如下面这个声明:
int matrix[6][10];
创建了一个包含60个元素的矩阵,但是它是6行,每行10个元素,还是10行,每行6个元素呢?
可以将它理解为六个元素的一维数组,但是每个元素都是一个元素为10个的一维数组
2.1数组名
一维数组的数组名是一个指针常量,二维数组也是,一维数组指向数组的第一个元素,上面说过,二维数组可以理解为一维数组,所以二维数组的数组名也是数组的首元素的地址.
2.2初始化
//初始化一个二维数组
int arr[3][5] =
{
{1, 2, 3, 4, 5},
{2, 3, 4, 5, 6}.
{3, 4, 5, 6, 7},
}
多维数组的初始化都是类似的
一些警告:
1.访问多维数组的元素时,误用逗号分隔下标
2.在一个指向未指定长度的数组的hi真上执行指针运算是很危险的
剩下的高级指针话题类似指针数组,数组指针之类的会在后面一一总结,这里只总结一些基本的数组知识。