C和指针(学习笔记)_第8章 数组

1.一维数组

       1)数组名:在C中,在几乎所有使用数组名的表达式中,数组名的值是一个指针常量,也就是数组第1个元素的地址。它的类型取决于数组元素的类型:如果它们是int类型,那么数组名的类型就是“指向int的常量指针”;如果它们是其它类型,那么数组的类型就是“指向其它类型的常量指针”。请不要根据这个事实得出数组和指针是相同的结论,数组具有一些和指针完全不同的特征,例如:数组具有确定数量的元素,而指针只是一个标量值。编译器用数组名来记住这些属性,只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量。

      指针常量所指向的是内存中数组的起始位置,如果修改这个指针常量,唯一可行的操作就是把整个数组移动到内存的其它位置。只有在两种场合下:数组名并不用指针常量表示——就是当数组名作为sizeof操作符或单目操作符&的操作数时。

      2)下标引用

int array[10];
int *ap=array+2;

*ap      //等价array[2],也可这样写*(array+2)
ap[0]    //等价*(ap+0),也就是array[2]
ap+6     //等价array+8或&arry[8]
*ap+6    //这有两种操作符,执行顺序决定方式,等价array[2]+6
ap[6]    //等价array[8]
*(ap+6)  //等价array[8]
&ap      //这个表达式是完全合法的,但此时并没有对等的涉及array的表达式,因为你无法预测编译器会把ap放在相对于array的什么位置
ap[-1]   //负值下标!,等价array[1]
ap[9]    //表达式看上去正常,但表达array[11],数组越界,表达式非法,但编译器很少能够检测到此类错误

       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这个值必须与整型的长度相乘,然后再与指针相加

这里存在一个重大的区别:循环每次执行时,执行乘法运算的都是两个相同的数(1和4)。结果,这个乘法只在编译
时执行一次,程序现在包含了一条指令,把4与指针相加。
这例子说明指针比下标更有效率的场合——当你在数组中1次1步(或某个固定的数字)地移动时,与固定数组相乘的
运算在编译时完成,所以在运行时所需的指令就少一些,在绝大多数机器上,程序将会更小些、更快一些。

现在考虑下面两段代码:
a=get_value();                 a=get_value();
array[a]=0;                    *(array+a)=0;
两边的语句所产生的代码并无区别,a可能是任何值,在运行时方知,所以两种方案都需要乘法指令,用于对a的值
进行调整,这个例子说明了指针和下标的效率完全相同的场合。

       4)指针的效率:指针有时比下标更有效率,前提是它们被正确地使用

一些试验:a)改用指针方案;b)重新使用计数器;c)寄存器指针变量;d)消除计数器;

从这些试验可以学到:

       (1)当你根据某个固定数目的增量在一个数组中移动时,使用指针变量将比使用下标产生效率更高的代码,当这个增量是1并且机器具有地址自动增量模型时,这点表现得更为突出;

       (2)声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率更高(具体提高幅度取决于你所使用的机器);

       (3)如果你可以通过测试一些已经初始化并经过调整的内容来判断循环是否终止,那么你就不需要使用一个单独的计数器;

      (4)那些必须在运行时求值的表达式较之诸如&array[SIZE]或array+SIZE这样的常量表达式往往代价更高;

      5)数组和指针:int a[5];  int *b;

      声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置,声明一个指针变量时,编译器只为指针本身保留内存空间。

      6)作为函数参数的数组名

//把第2个参数中的字符串复制到第1个参数指定的缓存区
void strcpy(char *buffer,char const *string){
    //重复复制字符,直到遇见NUL字节
    while((*buffer++=*string++)!='\0')
        ;
}

      7)声明数组参数

//下面两个函数原型是相当的
int strlen(char *string);
int strlen(char string[])

      8)初始化:静态初始化和自动初始化

      数组初始化方式类似于标量变量的初始化方式——也就是取决于它们的存储类型,存储于静态内存的数组只初始化一次,也就是在程序开始执行之前。

      9)不完整的初始化

int vector[5]={1,2,3,4,5,6};
int vector[5]={1,2,3,4};

//第1个超出数组限定,是错误的,第2个声明合法,它为前4个元素赋了值,最后一个元素则会初始化为0

       10)自动计算数组长度

int vector[]={1,2,3,4,5} //编译器根据容纳元素数计算长度

       11)字符数组的初始化

char message1[]="Hello";
char *message2="Hello";
这两个初始化看上去很像,但它们具有不同的含义。前者初始化一个字符数组的元素,而后者是一个真正的字符串常量。

  2.多维数组

int a[6][10];
int b[3][6][10];

       1)存储顺序:在C中,多维数组的元素存储顺序按照最右边的下标率先变换的原则,称为行主序

       2)数组名:一维数组名的值是一个指针常量,它的类型是“指向元素类型的指针”,它所指向数组的第1个元素,多维数组也差不多如此,唯一的区别是多维数组第1维的元素实际上另一个数组。

       3)下标

int a[3][10];

a[1]      //指向包含10个整型元素的数组的指针
*(a+1)    //与a[1]等价
*(a+1)+5  //指向元素a[1][5]
*(*(a+1)+5) //与*(a+1)+5等价

       4)指向数组指针

int a[10],*va=a;
int b[3][10],*vb=b;
//第1个合法,第2个非法,应该如此声明:
int (*p)[10];

       5)作为函数参数的多维数组

       作为函数参数的多维数组名的传递方式和一维数组名相同——实际传递的是一个指向数组第1个元素的指针。但是,两者之间的区别在于,多维数组的每个参数本身是另外一个数组。编译器需要知道它的维数,以便为函数形参的下标表达式进行求值。

       6)初始化:多维数组的初始化有两种方式:a)长长的初始列表;b)数组列表;

int a[2][2];
//方法1初始化:
int a[2][2]={1,2,3,4};
//方法2初始化:
int a[2][2]={
    {1,2},
    {3,4}
};

      7)数组长度计算:在多维数组中,只有第1维才能根据初始化列表缺省地提供,剩下几维必须显式地写出来

3.指针数组

       下标引用的优先级高于间接访问。sizeof常用于对数组中的元素进行自动计数。

4.总结

       在绝大多数表达式中,数组名的值是指向数组第1个元素的指针,这个规则只有两个例外:a)sizeof返回整个数组所占用的字节而不是一个指针所占用的字节;b)单目操作符&返回一个指向数组的指针,而不是一个指向数组第1个元素的指针的指针。

      除了优先级不同外,下标表达式array[value]和间接访问表达式*(array+(value))是一样的。因此下标不仅可以用于数组名,也可以用于表达式中,不过这样一来,编译器就很难检查下标的有效性。指针表达式可能比下标表达式效率更高,但下标表达式绝不可能比指针表达式效率更高。但是以牺牲程序的可维护性为代价获得程序的运行效率的提高可不是个好主意。

      指针和数组并不相等,数组的属性和指针的属性大相径庭,当声明一个数组时,它同时也分配了一些内存空间,用于容纳数组元素,但是当我们声明一个指针时,它只分配了用于容纳指针本身的空间。

      当数组名作为函数参数传递时,实际传递给函数的是一个指向数组第1个元素的指针。函数所接收到的参数实际是原参数的一份拷贝,所有函数可以对其进行操纵而不会影响实际参数。但是对指针参数执行间接访问操作允许函数修改原先的数组元素。数组形参既可以声明为数组,也可以声明为指针。这两种声明形式只有当它们作为函数的形参时才是相等的。

      数组也可以用初始化列表进行初始化,初始值列表就是由一对花括号包围的一组值。静态变量(包括数值)在程序载入到内存时得到初始值。自动变量(包括数组)每次当执行流进入它们声明所在的代码块时都要使用隐式的赋值语句重新进行初始化。

      多维数组实际上是一维数组的一种特型,就是它的每个元素本身也是一个数组。多维数组中的元素根据行主序进行存储,也就是最右边的下标率先变化。多维数组名的值是一个指向它第1个元素指针,也就是一个指向数组的指针。

       字符串的列表可以以矩阵的形式存储,也可以以指向字符串常量的指针数组形式存储。在矩阵中,每行必须与最长字符串的长度一样长,但它不需要任何指针。指针数组本身要占用空间,但每个指针所指向的字符串所占用的内存空间就是字符串本身的长度。

5.警告的总结

       1)当访问多维数组的元素时,误用逗号分隔下标;

       2)在一个指向未指定长度的数组得指针上执行指针运算;

6.编程提示的总结

        1)一开始就编写良好的代码显示比依赖编译器来修正劣质代码更好;

        2)源代码的可读性几乎总是比程序的运行时效率更为重要;

        3)只要有可能,函数的指针形参都应该声明为const。

        4)在有些环境中,使用register关键字提高程序的运行时效率;

        5)在多维数组的初始化列表中使用完整的多层花括号能提高可读性;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值