C和指针读书笔记(第八章)

本文详细探讨了C语言中指针与数组的关系,包括数组名在sizeof和&操作下的特殊行为、指针的效率优势、指针与数组的区别、数组初始化规则、多维数组的存储顺序和初始化、指针作为函数参数的行为,以及const修饰指针的用法。总结了数组名在多数情况下视为指向首元素的指针,但在sizeof和作为&操作数时有所不同,并强调了函数形参指针声明为const的重要性。
摘要由CSDN通过智能技术生成

1. 数组名不用指针常量表示的两种情况:

  • 数组名作为sizeof的操作数:sizeof返回数组的长度,而不是指向数组的指针的长度。
  • 数组名作为操作符&的操作数:取一个数组名的地址所产生的是一个指向数组的指针,而不是指向某个指针常量值的指针

2. 指针的效率:
首先声明(适用于接下来的所有函数):

#define SIZE 50;
int x[SIZE];
int y[SIZE];
int i;
int *p1, *p2;

示例1:函数的下标版本

void try1()
{
    for(i = 0; i < SIZE; i++)
        x[i] = y[i];
}

示例2:指针

void try2()
{
    for(p1 = x, p2 = y; p1 - x < SIZE; ){
        *p1++ = *p2++;
    }
}

示例3:重新使用计数器

void try3()
{
    for(i = 0, p1 = x, p2 = y; i < SIZE; i++){
        *p1++ = *p2++;
    }
}

示例4:寄存器指针变量

void try4()
{
    register int *p1, *p2;
    register int i;

    for(i = 0, p1 = x, p2 = y; i < SIZE; i++){
        *p1++ = *p2++;
    }
}

示例5:消除计数器

void try5()
{
    register int *p1, *p2;

    for(p1 = x, p2 = y; p1 < &x[SIZE]){
        *p1++ = *p2++;
    }
}

结论:

  1. 当根据某个固定数目的增量在一个数组中移动时,使用指针变量将比使用下标产生效率跟高的代码,当增量为1且机器具有地址自动增量模型时,这点尤为突出。
  2. 声明为寄存器变量的指针通常比位于静态内存和堆栈中的指针效率更高。
  3. 如果可以通过测试一些已经初始化并经过调整的内容来判断循环是否应该终止,那么不需要采用单独的计数器。
  4. 那些必须在运行时求值的表达式较之诸如&array[SIZE]或array+SIZE这样的变量表达式往往代价更高。

P145-149


3. 指针和数组并不相等。

  • 声明一个数组时,编译器将根据声明所指定的元素数量为数组保留内存空间,然后再创建数组名,它的值是一个常量,指向这段空间的起始位置。
  • 声明一个指针变量时,编译器只为指针本身保留内存空间,并不为任何整型值分配内存空间。而且指针变量并未被初始化为指向任何现有的内存空间,如果是个自动变量,甚至不会被初始化

4. 数组初始化的方式类似于标量变量的初始化方式——也就是取决于他们的存储类型。

  • 存储于静态内存(声明为static)的数组只初始化一次,也就是在程序开始执行之前。如果数组未被初始化,数组元素的初始值将被设置为零。
  • 自动变量位于运行时堆栈中,执行流每次进入它们所在的代码块时,这类变量每次所处的变量位置可能不同,因此在缺省情况下可能未初始化。故每当执行流进入自动变量声明所在的作用域时,变量就被隐式的赋值语句初始化。

5. 多维数组元素存储顺序按照最右边的下标率先变化的原则,称为行主序。

#include<stdio.h>

int main(void){

    int mat[3][3]={1, 2, 3, 4, 5, 6, 7, 8, 9};  //二维数组
    int* mp;  //指针

    mp = &mat[1][2];  //初始化

    printf("first is: %d\n", *mp);  //6
    printf("second is: %d\n", *++mp);  //7
    printf("third is: %d\n", *++mp);  //8
    return 0;
}

6. 多维数组的数组名也是指向第一个元素,只不过多维数组第一维的元素实际上是另一个数组。

#include<stdio.h>

int main(void){

    int mat[3][4]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
    //mat这个名字的值是一个指向它第1个元素的指针
    //所以mat是一个指向一个包含10个整型元素的数组的指针

    printf("mat[2][3] is: %d\n", mat[2][3]);  //12
    //第三行第四列

    //二维数组类似于两个一维数组的叠加
    printf("mat points on: %d\n", *mat);  //6356704
    printf("mat points on: %x\n", *mat);  //60fee0
    //二维数组第一个元素里的数组的首地址
    printf("mat+1 points on: %d\n", *(mat+1));  //6356720
    printf("mat+1 points on: %d\n", *(mat+1));  //60fef0
    //二维数组第二个元素里的数组的首地址
    //二者相差16(6356720-6356704=16),为四个int型的长度

    printf("mat[0][0] points on: %d\n", **mat);  //1
    //间接操作两次,取第一个元素里的数组的首地址,再间接操作首地址取数
    printf("mat[0][2] points on: %d\n", *(*mat+2));  //3
    printf("mat[1][2] points on: %d\n", *(*(mat+1)+2));  //7
    printf("mat[1][2] points on: %d\n", *(mat[1]+2));  //7
    //mat[1]和*(mat+1)等价
    printf("mat[1][2] points on: %d\n", mat[1][2]);  //7
    return 0;
}

7. 指向数组的指针

#include<stdio.h>

int main(void){

    int mat1[7]={1, 2, 3, 4, 5, 6, 7};
    int mat2[3][7]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21};

    int *p1 = mat1;
    //int *p2 = mat2;  //非法:mat2并不是指向整型的指针,而是一个指向整型数组的指针


    int (*p)[7] = mat2;  //p指向某种类型的数组
    //初始化使p指向mat2的第1int *pi1 = &mat2[0][0];  //指向mat2的第一个整型元素mat2[0][0]
    int *pi2 = mat2[0];  //指向mat2的第一个整型元素mat2[0][0]
    int **pi3 = mat2;  //非法:pi3是指向整型的指针的指针,mat2是指向整型数组的指针

    printf("*p1: %d\n", *p1);  //1
    printf("*p1 NEXT: %d\n", *++p1);  //2
    printf("(*p)[7]: %d\n", (*p)[7]);  //8
    printf("(*p)[0]: %d\n", (*p)[0]);  //1
    printf("(*p)[7] NEXT1: %d\n", ++(*p)[7]);  //9
    printf("(*p)[7] NEXT2: %d\n", (*++p)[7]);  //15
    printf("(*p)[7] NEXT2 NEXT: %d\n", ++(*p)[7]);  //16

    printf("*pi1: %d\n", *pi1);  //1
    printf("*pi1 NEXT: %d\n", *++pi1);  //2

    printf("*pi2: %d\n", *pi2);  //1
    printf("*pi2 NEXT: %d\n", *++pi2);  //2
    return 0;
}

8. 作为函数参数的多维数组:作为函数参数的多维数组名和一维数组名相同,实际传递的是个指向数组第1个元素的指针.不同之处在于多维数组的每个元素本身是另外一个数组,编译器需要知道它的维数。

//一维数组
int vec[10];
func(vec);
//函数的原型可以是以下两种
void func(int v*);
void func(int v[]);
//二维数组
int mat[3][10];
func2(mat);
//函数的原型可以是以下两种
void func2(int (*m)[10]);
void func2(int m[][10]);

//这个例子把m声明为指向整型指针的指针,它和指向整形数组的指针并不是一回事
void func2(int **m);

9. 多维数组的初始化可以一个一个按顺序的赋初值,也可以通过大括号套大括号的方式进行。P160-161


10. 多维数组中,只有第1维才能根据初始化列表缺省的提供,其余的几个维必须显式地写出。(这样编译器就可以推断每个子数组维度的长度)


11. const char*, char const*, char*const的区别问题
助记方法: 把一个声明从右向左读。

char * const cp; ( * 读成 pointer to )
cp is a const pointer to char

const char * p;
p is a pointer to const char;

char const * p;
同上因为C++里面没有const*的运算符,所以const只能修饰属于前面的类型char,因此char const * p等价于const char *p。
C++标准规定,const关键字放在类型或变量名之前等价的。
参考网页:http://blog.csdn.net/tianziczj/article/details/5984308


12. 指针数组

int *api[10];  //api是一个数组,元素类型是指向整型的指针

下标引用的优先级高于间接访问,所以先执行下标引用,因此api是某种类型的数组(包含10个元素),在取得一个元素后,随即执行间接访问操作,这个表达式不再有其他操作符,所以结果是一个整型值。


13. 总结:

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

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

  • 数组也乐意用初始值列表进行初始化,初始值列表就是由一对花括号包围的一组值。静态变量(包括数组)在程序载入到内存时才得到初始值。自动变量(包括数组)每次当执行流进入它们声明所在的代码块时都要使用隐式的赋值语句重新进行初始化。如果初始值列表包含的值的个数少于数组的元素的个数,数组的最后几个元素就用缺省值进行初始化。如果一个被初始化的数组的长度在声明未给出,编译器将使这个数组的长度设置为刚好能容纳初始值列表中所有值的长度。

  • 多维数组实际上是一维数组的一种特型,就是它的每个元素本身也是一个数组。多维数组中的元素根据主序进行存储,也就是最右边的下标率先变化。多维数组名的值是一个指向它第一个元素的指针,也就是一个指向数组的指针。对该指针进行运算将根据它所指向数组的长度对操作数进行调整。多维数组的下标引用也是指针表达式。当一个多维数组名作为参数传递给一个函数时,它所对应的函数形参的声明必须显式指明第2维(和之后所有维)的长度。在多维数组的初始值列表中,只有第1维的长度会被自动计算出来。

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


14. 函数的形参指针尽量声明为const


2016.10.2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值