《C与指针》读书笔记
1.一维数组
1.1 数组名
在C中,在几乎所有使用数组名的表达式中,数组名的值是一个指针常量,也就是数组第一个元素的地址。
但是数组和指针并不是相同的。数组具有确定数量的元素,而指针只是一个标量值。编译器用数组名来记住这些属性。只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量。
只有在两个场合下,数组名并不用指针常量来表示——就是当数组名作为操作符sizeof和操作符&的操作数时。sizeof返回整数数组的长度,而不是指向数组的指针的长度。另外,取一个数组名的地址所产生的是一个指向数组的指针,而不是指向某个指针常量的指针。
1.2 下标引用
有下面声明:
int b[10];
*(b + 3)表达什么意思?
首先,b的值是一个指向整形的指针,所以3这个值是根据整形值的长度进行调整。加法运算的结果是另一个指向整型的指针。它所指向的是数组第一个元素向后移3个整数长度的位置。然后,间接访问操作访问这个新位置。
实际上,下边引用的执行过程与上边的原理是完全相同的。除了优先级别不同之外,下标引用和间接访问完全相同。下面表达式是等同的
array[subscript]
*(array + (subscript))
1.3 指针与下标
指针表达式和下标表达式是等价的,但是下标表达式不会比指针表达式更有效率。只不过下标表达式比较好理解。
考虑下面代码:
int array[10],a;
for(a = 0;a < 10; a += 1)
array[a] = 0;
为了对下标表达式求值,编译器在程序中插入指令,取得a的值,并把它与数组的长度(也就是4)相乘。这个乘法需要花费一定的时间和空间。
下面使用指针表达式:
int array[10],*ap;
for(ap = array;ap < array + 10;ap++)
*ap = 0;
现在这个乘法出现在for语句的调整部分。1这个值必须与整型的长度相乘,然后再与指针相加。但这里存在一个重大区别:循环每次执行时,执行乘法运算都是两个相同的数。结果,这个乘法只在编译时执行一次。程序在运行时并不执行乘法。
这两种表达式也有效率完全一样的场合。
a = get_value(); a = get_value();
array[a] = 0; *(array + a) = 0;
由于a可能是任何值,所以在运行时,两种都需要乘法指令。
1.4 指针的效率
有下面声明:
#define SIZE 50
int x[SIZE];
int y[SIZE];
int i;
int *p1,*p2;
下面是下标版本
void try1()
{
for(i = 0; i < SIZE; i++)
x[i] = y[i];
}
下面是指针版本
void try2()
{
for(p1 = x,p2 = y; p1 - x < SIZE)
*p1++ = *p2++;
}
上面两种效率差不多。
针对指针版本增加计数器
void try3()
{
for(i = 0,p1 = x,p2 = y; i < SIZE;i++)
*p1++ = *p2++;
}
效率稍微高一点。
针对上面的版本对指针使用寄存器变量。
void try4()
{
register int *p1,*p2;
register int i;
for(i = 0,p1 = x,p2 = y; i < SIZE;i++)
*p1++ = *p2++;
}
效率再高一点。
再消除计数器
void try4()
{
register int *p1,*p2;
for(p1 = x,p2 = y; p1 < &x[SIZE];)
*p1++ = *p2++;
}
这个版本是代码最紧凑效率最高的。
1.5 数组与指针
数组与指针并不是相等的。考虑下面声明:
int a[5];
int *b;
声明数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。声明一个指针变量时,编译器只为指针本身保留内存空间,它并不为任何整型值分配内存空间。而且,指针变量并未被初始化为指向任何现有的内存空间,如果它是一个自动变量,它甚至不会被初始化。
因此,上述声明之后,表达式*a是完全合法的,但表达式*b就是非法的。另外表达式b++可以通过编译,但a++不行,因为a是个常量。
1.6 作为函数参数的数组名
当一个数组名作为参数传递给一个函数时,会发生什么情况?由于数组名的值其实就是指向数组的第一个元素的指针,所以此时传递给函数的是该指针的一份拷贝。
1.7 声明数组参数
可以像下面一样声明:
int strlen(char *string);
int strlen(char string[]);
两种声明都一样,但是第一种声明说明了数组的本质。
1.8 初始化
int vector[5] = {1,2,3,4,5};
1.9 不完整的初始化
int vector[5] = {1,2,3,4};
缺少的部分补上0。
1.10 自动计算数组长度
int int vector[] = {1,2,3,4,5};
1.11 字符数组的初始化
char message[] = {'H','e','l','l','o',0};
char message[] = "Hello";
字符数组与字符串常量的区别
char message1[] = "Hello";
char* message2 = "Hello";
它们的区别下面两种图可以说明。
2. 多维数组
2.1 存储顺序
考虑下面这个数组
int array[3];
它在内存中如下图。
int array[3][6];
它在内存中如下图。
在C中,多维数组的元素存储顺序按照最右边的下标率先变化的原则,称为行主序。
2.2 数组名
int matrix[3][10];
创建了matrix,它可以看作是一个一位数组,包含三个元素,只是每个元素恰好是包含10个元素的数组。
matrix这个名字的值是一个指向它第一个元素的指针,所以matrix是一个指向一个包含10个整型元素的数组的指针。
2.3 下标
int matrix[3][10];
表达式matrix[1][5]
访问下面这个元素:
matrix,它的值如下:
matrix + 1,它的值如下:
*(matrix + 1),它的值如下:
*(matrix + 1) + 5
*(*(matrix + 1) + 5)
2.4 指向数组的指针
int matrix[3][10];
int (*p)[10] = matrix;
把 int (*p)[10]假定是个表达式并对它进行求值。先是间接引用,p是个指针,但它指向什么呢?
接下来是下标引用,所以p指向某种类型的数组。而数组的每个元素都是整型。
2.5 作为函数参数的多维数组
可以使用下面两种形式的一种:
void func2(int (*mat)[10]);
void func2(int mat[][10]);
这里的关键是编译器必须知道第二个及以后各维的长度才能对各下标进行求值。
2.6 初始化
int matrix[2][3] = {11,12,13,21,22,23};
int matrix[2][3] = {
{11,12,13},
{21,22,23}
}
2.7 数组长度自动计算
int tow_dim[][5] = {
{00,01,02},
{10,11},
{20,21,22,23}
};
3 指针数组
考虑下面声明:
int *api[10];
假定它是一个表达式,并对它进行求值。下标引用优先于间接引用。所以api是某种类型的数组。在取得一个数组元素之后,随即执行的是间接访问操作,结果是一个整型值。
所以api是个数组,它的元素类型是指向整型的指针。