八、保护数组中的数据
编写一个处理基本类型(如, int)的函数时,要选择是传递int类型的值还是传递指向int的指针。通常都是直接传递数值,只有程序需要在函数中改变该数值时,才会传递指针。对于数组别无选择,必须传递指针,因为这样做效率高。如果一个函数按值传递数组,则必须分配足够的空间来储存原数组的副本,然后把原数组所有的数据拷贝至新的数组中。如果把数组的地址传递给函数,让函数直接处理原数组则效率要高。
传递地址会导致一些问题。C通常都按值传递数据,因为这样做可以保证数据的完整性。如果函数使用的是原始数据的副本,就不会意外修改原始数据。但是,处理数组的函数通常都需要使用原始数据,因此这样的函数可以修改原数组。
对形参使用const
在K&R C的年代,避免类似错误的唯一方法是提高警惕。ANSI C提供了一种预防手段。如果函数的意图不是修改数组中的数据内容,那么在函数原型和函数定义中声明形式参数时应使用关键字 const。例如,sum ( )函数的原型和定义如下:
以上代码中的const告诉编译器,该函数不能修改ar指向的数组中的内容。如果在函数中不小心使用类似ar [ i]++的表达式,编译器会捕获这个错误,并生成一条错误信息。
这里一定要理解,**这样使用const并不是要求原数组是常量,而是该函数在处理数组时将其视为常量,不可更改。这样使用const可以保护数组的数据不被修改,就像按值传递可以保护基本数据类型的原始值不被改变一样。**一般而言,如果编写的函数需要修改数组,在声明数组形参时则不使用const:如果编写的函数不用修改数组,那么在声明数组形参时最好使用const。
九、指针和多维数组的关系
书上写的也很详细
b站有个视频写的也挺好,分享出来
https://www.bilibili.com/video/BV1MJ411D7EX
下面这个是我做的笔记(下次一定把字写好)
此时,C和C[0]的值都是二维数组的首地址
实例
#include <stdio.h> int main(void) { int zippo[4][2] = {{2, 4}, {6, 8}, {1, 3}, {5, 7}}; //zippon 拉链 printf("zippo = %p,zippo + 1 = %p\n", zippo, zippo + 1); //zippo和zippo[0]都是二维数组的首地址 printf("zippo[0] = %p, zippo[0]+1 = %p\n", zippo[0], zippo[0] + 1); //zippo+1是指向第二个一维数组,zippo[0]+1是第一个一维数组的第二个元素 printf("*zippo = %p,*zippo+1 = %p\n", *zippo, *zippo + 1); printf("zippo[0][0] = %d\n", zippo[0][0]); printf("*zippo[0] = %d\n", *zippo[0]); printf("**zippo = %d \n", **zippo); printf("zippon[2][1] = %d\n", zippo[2][1]); printf("*(*(zippo+2)+1) = %d\n", *(*(zippo + 2) + 1)); return 0; } 输出的结果: PS D:\Code\C\指针> cd "d:\Code\C\指针\" ; if ($?) { gcc 指针Demo06.c -o 指针Demo06 } ; if ($?) { .\指针Demo06 } zippo = 000000000061FE00,zippo + 1 = 000000000061FE08 zippo[0] = 000000000061FE00, zippo[0]+1 = 000000000061FE04 *zippo = 000000000061FE00,*zippo+1 = 000000000061FE04 zippo[0][0] = 2 *zippo[0] = 2 **zippo = 2 zippon[2][1] = 3 *(*(zippo+2)+1) = 3
解析
其他系统显示的地址值和地址形式可能不同,但是地址之间的关系与以上输出相同。该输出显示了二维数组zippo的地址和一维数组zippo[0]的地址相同。它们的地址都是各自数组首元素的地址,因而与&zippo [0] [0]的值也相同。
尽管如此,它们也有差别。在我们的系统中, int是4字节。前面讨论过,zippo[0]指向一个4字节的数据对象。zippo[0]加1,其值加4。
数组名zippo 是一个内含2个int类型值的数组的地址,所以zippo指向一个8字节的数据对象。因此,zippo 加1,它所指向的地址加8字节。
该程序演示了zippo[0]和zippo完全相同,实际上确实如此。然后,对二维数组名解引用两次,得到储存在数组中的值。使用两个间接运算符(*)或者使用两对方括号([])都能获得该值(还可以使用一个和一对[ ],但是我们暂不讨论这么多情况)。
要特别注意,与 zippo[2] [1]等价的指针表示法是 ((zippo+2) + 1)。看上去比较复杂,应最好能理解。下面列出了理解该表达式的思路:
zippo 二维数组首元素的地址(每个元素都是内含两个int类型元素的一维数组)
zippo + 2 二维数组的第三个一维数组的地址
*(zippo + 2)二维数组的第三个一维数组的地址首元素(一个int类型的值)地址
*(zippo + 2)+ 1 二维数组的第3个元素(即一维数组)的第2个元素(也是一个int类型的值)地址
*(*(zippo + 2)+ 1) 二维数组的第3个元素(即一维数组)的第2个元素的值,即zippo[2][1]
以上分析并不是为了说明用指针表示法(* (*(zippo+2) + 1))代替数组表示法(zippo[2][1]),而是提示读者,如果程序恰巧使用一个指向二维数组的指针,而且要通过该指针获取值时,最好用简单的数组表示法,而不是指针表示法。
十、总结
数组是一组数据类型相同的元素。数组元素按顺序储存在内存中,通过整数下标(或索引)可以访问各元素。在c中,数组首元素的下标是0,所以对于内含n 个元素的数组,其最后一个元素的下标是n-1。作为程序员,要确保使用有效的数组下标,因为编译器和运行的程序都不会检查下标的有效性。
把数组名解释为该数组首元素的地址。换言之,数组名与指向该数组首元素的指针等价。概括地说,数组和指针的关系十分密切。如果ar是一个数组,那么表达式ar[i]和 (ar+i)等价。*
二维数组即是数组的数组。例如,下面声明了一个二维数组:
double sales [5] [12] ;
该数组名为sales,有5个元素(一维数组),每个元素都是一个内含12个double类型值的数组。第1个一维数组是sales [0],第2个一维数组是sales [1],以此类推,每个元素都是内含12个double类型值的数组。使用第2个下标可以访问这些一维数组中的特定元素。例如,sales [2] [5]是slaes[2]的第6个元素,而sales [ 2]是sales的第3个元素。
十一、复习题