函数、数组、指针与指针操作
学完这两节,真切感受到为什么所有人都说,C语言学的好不好的检验标准是指针和内存管理了。接触底层,大部分时间都是去跟内存打交道,也是为学习数据结构和算法铺路的重要步骤。
函数原型和指针
书本以用函数去计算整形数组的总和举例:
#include <stdio.h>
int sum(int *, int ); //函数原型可以不用明确形参名字,但必须指定形参类型,这样写比较简洁,也不失代码可读性
int main(void){
int arr[3] = {1,2,3};
int answer;
answer = sum(arr,3);
printf("Sum is %d.\n",answer);
return 0;
}
其中,sum()
函数的定义为:(新知识点,*(ai+i)
与ai[i]
是等价的,但为了代码可读性,我更愿意选择使用前者。)
int sum(int *ai,int n){
int i;
int total = 0;
for (i = 0;i < n; ++i){
total += ai[i]; //注意,*(ai+i)与ai[i]在编译器眼里是一样的
}
return total;
}
这个例子充分展示了函数中指针来处理数组的方法。数组是最简单的数据结构,每个元素的内存地址紧挨着一起,一个接一个,所以可以用指针轻而易举地访问每个元素的值。
值得注意的是,对于数组得最后一个元素后面的那个地址,如果指针指向了这里,C语言中是允许这种情况存在的,此时它仍然是有效的指针。
int main() {
int arr[2] = {1, 2};
int *ptr = arr;
ptr += 2;
if (ptr)
printf("%p\n", ptr);
return 0;
}
输出结果为一个正常的地址:
007FFE24
函数处理数组得表示法
函数在接受数组并处理数组时,会有两种表示法,分别是形如ai[2]
的数组表示法和形如*(ai+2)
的指针表示法,上面也说了,*(ai+i)
与ai[i]
是等价的,但是在编译过程中,指针表示法会有更好的效率,所以建议使用指针表示法!
指针操作
学到这里,该对C语言中的指针的操作作一个总结和延伸了。现在感觉,指针真的很有趣,能够亲手去操纵内存,比Python来的实在,让我有种知根知底的感觉。
指针赋值
通过指针=&变量名
来给指针赋值,指针是存储地址的变量。
解引用
通过*
运算符来获取指针所储存的地址上储存的值。
指针与整数相加相减
前面说到,指针与整数相加相减,得到的值依然是内存地址。而这个加法减法的本质是:与指针相加相减的整数先与指针的数据类型所对应的字节大小相乘,然后把这个相乘的结果与指针相加相减,也就得到了新的地址。
指针的递增与递减
指针的递增递减遵从指针与整数的加法减法的思路,但要注意的是,不论指针所存储的内存地址如何变化,指针本身的地址是不变的,因为指针本身也是变量,变量、常量都有计算机分配给它的内存大小和内存地址。
指针求差
指针和指针之间是可以求差的,前提是这两个指针是相同的数据类型的指针(否则会报错)。指针与指针相减,得到的是一个整数,这个整数就是两个指针存储的内存地址之间相差的字节数除以数据类型的字节大小。
指针的运算小结
指针 +/- 整数 = 新的指针;
指针 - 指针 = 整数。
切勿使用野指针
指针如果没有被初始化,被称为野指针,他可能指向内存中任意一个位置。野指针是不能使用的,一旦使用,野指针会造成各种不可思议的危险。这算是学编程的都知道的了。
那么这就是告诉我:指针一定要先初始化,哪怕先初始化为NULL
也行。
总结
指针和内存管理,也许真的够我学一辈子QAQ。