《C和指针》笔记(八)

本文详细阐述了C语言中数组与指针的密切关系,包括一维数组的指针表示、数组名的特殊性、指针与下标的区别以及在函数参数中的传递方式。讨论了数组初始化、多维数组、指向数组的指针以及在效率方面的考虑。强调了在编程中,可读性和效率之间的平衡以及正确使用数组和指针的重要性。
摘要由CSDN通过智能技术生成

(八)数组

一维数组

数组名的值是一个指针常量,也就是数组第1个元素的地址。

只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量。

注意这个值是指针常量,而不是指针变量。

只有在两种场合下,数组名并不用指针常量来表示——就是当数组名作为sizeof操作符或单目操作符&的操作数时。sizeof返回整个数组的长度,而不是指向数组的指针的长度。取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值的指针。

    int a[2] = {1, 2};
    int b = sizeof(a);
    cout << b << endl;
    cout << &a[0] << endl;
    cout << &a << endl;
    cout << a << endl;

&a[0]、&a、a三个值的结果是一样的。

-----------------------------------

int array[10];
int *ap = array + 2;
  • ap[0]:C的下标引用和间接访问表达式是一样的。在这种情况下,对等的表达式是*(ap+(0)),除去0和括号,其结果与*ap相等。
  • &ap:这个表达式是完全合法的,但它不是数组名,它表示一个指向ap的指针。

-----------------------------------

2[array] => *(2+(array))

    cout << array[2] << endl;
    cout << 2[array] << endl;

上面输出的值是相同的。这个诡异技巧之所以可行,缘于C实现下标的方法。对编译器来说,这两种形式并无差别。但是绝不应该编写2[array],会大大影响程序的可读性。

指针与下标

如果可以互换地使用指针表达式和下标表达式,那么应该使用哪一个?
下标更容易理解,尤其是在多维数组中。所以在可读性方面,下标有一定的优势。但在另一方面,这个选择可能会影响运行时效率。下标绝不会比指针更有效率,但指针有时会比下标更有效率。

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;

循环每次执行时,执行乘法运算的都是两个相同的数(1和4),结果,这个乘法只在编译时执行一次——程序现在包含了一条指令,把4与指针相加。程序在运行时并不执行乘法运算。

这个例子说明了指针比下标更有效率的场合——当你在数组中1次1步地移动时,与固定数字相乘的运算在编译时完成,所以在运行时所需的指令就少一些。

指针的效率

不要为了效率上的细微差别而牺牲可读性,这点非常重要。

数组和指针

int a[5];
int *b;

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

在上述声明之后,表达式a是完全合法的,但表达式b却是非法的。*b将访问内存中某个不确定的位置,或者导致程序终止。另一方面,表达式b++可以通过编译,但a++却不行,因为a的值是个常量。

作为函数参数的数组名

void strcpy(char *buffer, char const *string)
{
    while((*buffer++ = *string++) != '\0')
        ;
}

while语句中的*string++表达式取得string所指向的那个字符,并且产生一个副作用就是修改string,使它指向下一个字符。用这种方式修改形参并不会影响调用程序的实参,因为只有传递给函数的那份拷贝进行了修改。

形参被声明为一个指向const字符的指针。对于一个并不打算修改这些字符的函数而言,预先把它声明为常量有三个好处:
第一:这是良好的文档习惯,有些人希望仅观察该函数的原型就能发现该数据不会被修改,而不必阅读完整的函数定义。
第二:编译器可以捕捉到任何试图修改该数据的意外错误。
第三:这类声明允许向函数传递const参数。

声明数组参数

把一个数组名参数传递给函数,实际上传递的是一个指针。

int strlen(char *string);
int strlen(char string[]);

在当前这个上下文环境中,这两个声明是相等的,但是如果它们出现在别处就可能完全不同。

使用指针声明更加准确。

函数并不为数组参数分配内存空间,形参只是一个指针,它指向的是已经在其他地方分配好内存的空间。

数组形参可以与任何长度的数组匹配,这种实现方式使函数无法知道数组的长度。如果函数需要知道数组的长度,它必须作为一个显式的参数传递给函数。

初始化

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

静态初始化:存储于静态内存的数组只初始化一次,也就是在程序开始执行之前。如果数组未被初始化,数组元素的初始值将会自动设置为零。

自动变量初始化:对于自动变量而言,执行流每次进入它们所在的代码块时,这类变量每次所处的内存位置可能并不相同。在程序开始之前,编译器没有办法对这些位置进行初始化。所以,自动变量在缺省情况下是未初始化的。

因此这里就需要权衡利弊。当数组的初始化局部于一个函数时,你应该仔细考虑一下,在程序的执行流每次进入该函数时每次都对数组进行重新初始化是不是值得。如果答案是否定的,就把数组声明为static,这样数组的初始化只需在程序开始前执行一次。

字符数组的初始化

字符数组初始化:char message[] = {‘H’,‘e’,‘l’,‘l’,‘o’,0};
语言标准提供了一种快速方法用于初始化字符数组:
char message[] = “Hello”;

如何分辨字符串常量和这种初始化列表快速记法:
当用于初始化一个字符数组时,它就是一个初始化列表。在其他任何地方,他都表示一个字符串常量。

char message1[] = “Hello”;
char *message2 = “Hello”;
前者初始化一个字符数组的元素,而后者则是一个真正的字符串常量。

多维数组

int matrix[3][10];
matrix这个名字的值是一个指向它第1个元素的指针,所以matrix是一个指向一个包含10个整型元素的数组的指针。

所以 *(matrix + 1)事实上标识了一个包含10个整型元素的子数组。

*(matrix + 1) + 5
这整个表达式的结果是一个指针,它指向的位置比原先那个表达式所指向的位置向后移动了5个整型元素。

((matrix + 1) + 5)
它访问的是那个整型元素。

指向数组的指针

int vector[10], *vp = vector;
int matrix[3][10], *mp = matrix;

以上的声明中 *mp = matrix 是不合法的。因为matrix并不是一个指向整型的指针,而是一个指向整型数组的指针。

应该改为
int (*p)[10] = matrix;

int (*p)[10];
这个声明了一个指向整型数组的指针。

我们假定它是一个表达式并对它求值。
下标引用的优先级高于间接访问,但由于括号的存在,首先执行的还是间接访问。所以,p是一个指针。
接下来执行的是下标引用,所以p指向某种类型的数组。
当我们对p执行间接访问操作时,我们得到的是个数组,对该数组进行下标引用操作得到的是一个整型值,所以p是一个指向整型数组的指针。

作为函数参数的多维数组名的传递方式:

void func2(int (*mat)[10]);
void func2(int mat[][10]);

这里的关键在于编译器必须知道第2个及以后各维的长度才能对各下标进行求值,因此在原型中必须声明这些维的长度。

void func2(int **mat);

这个例子把mat声明为一个指向整型指针的指针,它和指向整型数组的指针并不是一回事。

指针数组

int *api[10];
char const *keyword[] = {
    "do",
    "for",
    "if",
    "register",
    "return",
    "switch",
    "while",
    NULL
};

我们在表的末尾增加一个NULL指针。这个NULL指针使函数在搜索这个表时能够检测到表的结束,而无需预先知道表的长度,如下所示:

char const **kwp;
for(kwp = keyword; *kwp != NULL; kwp++)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值