- 数组、指针和函数
- 0、前言
- 1、数组的内存中的存储
- 2、指针
- 2.1、指针和指针变量
- 2.2、操作符
- 2.3、指针操作
- 2.3.1、指针和整数相加减
- 2.3.2、指针递增
- 2.3.3、指针求差
- 2.3.4、& 和 * 同时使用
- 2.4、指针类型与地址
- 3、函数
- 3.1、全局变量和局部变量
- 3.2、堆空间和栈空间的差异
- 4、函数、数组和指针
之前大一的时候,学习C语言,学到数组的时候感觉还行,可以接受。但是学到指针和函数后,绕来绕去的,就开始懵圈了。
但是,指针的重要性不言而喻,检验C语言学得好不好,就看“指针和内存管理”了。
大一下之后呢,就一直学Java,虽说原理都是相近的,但是毕竟C的语法还是比较细的,而且最近因为一些原因呢,要用C语言实现算法,所以抽空补补了C的基础和语法,特此记录一下。
1、数组的内存中的存储那些简单的数组定义、初始化等比较简单就不说明,我们重点来看数组在内存中的存储。
在此,我们先来补充一下内存地址原理,其实这个知识点是《计算机组成原理》的内容,在此简单提一下,顺带补充一句,基础很重要,小伙伴们要重视基础哦!
假设我们的机子32位,则会使用4字节来存储地址,例如”0x0011223344”;若为64位,则用8字节来存储地址,例如“0x1122334455667788”。
以32位机为例:
接着,我们先写一段很简单的程序,然后我们再来看,VS2022调试中的内存映像。
#includeint main() { int arr[5] = { 11,22,33,44,55 }; for (int i = 0; i < 5; i++) { printf("%dn", arr[i]); } return 0; }
内存映像:
通过内存映像我们可以很清楚的看到,例如:
- 一个int类型确实占4字节
- 数组在内存中是顺序存放的,大小根据元素的类型和个数决定
- 数组名就是数组的首地址,也就是数组第一个元素的地址
2、指针 2.1、指针和指针变量
指针:一个变量的地址称为该变量的指针,也就是说,指针就是变量的地址。
指针变量:用来存放为“地址”的变量。
int num = 18; int *point = #
假设32位机,num存放的地址为“0x 11 22 33 44”。
我们就说,变量num的地址(指针)是“0x 11 22 33 44”;指针变量point的值是“0x 11 22 33 44”。
我们都知道int类型的变量num占4个字节(32位机),那么指针变量point占多大的空间呢?答若为32位机,寻址范围为32位即4字节;若为64位机,则寻址范围为64位即8字节。
2.2、操作符
-
取地址操作符为“&”,也称“引用”,通过该操作符我们可以获取一个变量的地址值;
-
取值操作符为“*”,也称“解引用”,通过该操作符我们可以得到一个地址对应的数据。
#includeint main() { int num = 15; int* point = # printf("num: %dn", num); //若编译器不支持%p,可以用%u、%lu来代替 printf("&num: %pn",&num); printf("point: %pn",point); printf("*point: %dn",*point); return 0; }
2.3、指针操作 2.3.1、指针和整数相加减
我们以加法为例,剑法同理。
#includeint main() { int arr[5] = { 1,2,3,4,5 }; int* point = arr; printf("%pn", point); printf("%pn", point+2); printf("%dn", *point); printf("%dn", *(point+2)); return 0; }
0x 00 DD FD 00 -- 0x 00 DD FD 08,一共是相差8个字节,即两个int类型变量所占的空间 用"+"运算符把指针和整数相加(相减),整数都会和指针所指向类型的大小相乘(以字节为单位),再将结果与初始地址相加。 即,0x00DDFD00 + 2 * 4Byte = 00DDFD08 point + 2后的值就是arr[2]的地址,即(point + 2)与&arr[2]等价2.3.2、指针递增
#includeint main() { int arr[5] = { 1,2,3,4,5 }; int* point = &arr[2]; printf("%pn", point); printf("%dn", *point); printf("%pn", ++point); printf("%dn", *point); return 0; }
0x 00 F8 FE 78 --- 0x 00 F8 FE 7C,一共相差4个字节 一开始point指向arr[2],其值为"00F8FE78";而后point递增后指向arr[3],其值为"00F8FE7C" 所以,point++相当于把point的值加上4,即 加上(1 * 4 Byte)2.3.3、指针求差
#includeint main() { int arr[5] = { 1,2,3,4,5 }; int* point01 = &arr[2]; int* point02 = &arr[5]; printf("%pn", point01); printf("%pn", point02); printf("%dn", point02 - point01); return 0; }
0x 00 6F FB 38 --- 0x 00 6F FB 44,一共相差12个字节 point02 - point01 得3,意思是两个指针所指向得两个元素之间相差3个int,即12个字节2.3.4、& 和 * 同时使用
//若有此语句 int a = 18; int* point = &a;
&*point 和 *&a 的含义和区别
“&”和“*”两个运算符的优先级别相同,但要按自右向左的方向结合。
因此,&* point 与 &a 相同,都表示变量a的地址,也就是point。
而*&a,首先进行&a运算,得到a的地址,再进行*运算。*&a和*point的作用是一样的,它们都等价于变量a,即*&a 与a等价。
2.4、指针类型与地址
地址应该和指针类型兼容,也就是说,我们不能把int型变量的地址赋值给指向double类型的指针,只能将int型变量的地址赋值给指向int类型的指针变量中。
用代码表示如下:
int num01 = 18; double num02 = 3.14; int* point01 = &num01; //正确,歪瑞good double* point02 = &num01; //毫无意义而且会出错 double* poinr03 = &num02; //正确,歪瑞good
我们在开发过程中,应该避免出现这种问题。
但是,如果硬要这么做的话,又有什么影响呢?
其实是可以赋值的,但是会出现数据截断等错误,而且不能进行指针操作,由于篇幅所限,详情请看《C语言_地址与指针类型不兼容造成的影响》。
3、函数 3.1、全局变量和局部变量
#includeint i = 18; // 全局变量 void print(int a) { printf("print -- &a = %pn", &a); printf("print -- a = %dn", a); } int main() { printf("main -- &i = %pn", &i); printf("main -- i = %dn", i); print(i); int i = 5; //局部变量 printf("main -- &i = %pn", &i); printf("main -- i = %dn", i); print(i); return 0; }
全局变量i存储在数据段,所以main函数和my_print函数都是可见的。
全局变量不会因为某个函数执行结束而消失,在整个进程的执行过程中始终有效,因此开发中应尽量避免使用全局变量!
我们在函数内定义的变量都称为局部变量,局部变量存储在自己的函数对应的栈空间内,函数执行结束后,函数内的局部变量所分配的空间将会得到释放。
如果局部变量与全局变量重名,那么将采取就近原则,即实际获取和修改的值是局部变量的值。
3.2、堆空间和栈空间的差异
我们用一个例子来看一看栈空间和堆空间的差异。
#includechar* print_stack() { char c[17] = "I am print_stack"; puts(c);//能正常打印 return c; } char* print_malloc() { //malloc是在堆空间中开辟一块内存 char* p = (char*)malloc(30); strcpy(p, "I am print_malloc"); puts(p); return p; } int main() { char* p; p = print_stack();//栈空间会随着函数的执行结束而释放,相当于p=c puts(p);//打印不出来 p = print_malloc();//堆空间不会随子函数的结束而释放,必须自己free puts(p); free(p); return 0; }
4、函数、数组和指针
假设我们有一个需求,是要对数组进行求和,我们可以怎样定义函数呢?
#include#define NUM 10 int getSum01(int arr[], int num); int getSum02(int* arr, int num); int getSum03(int* start, int* end); int main() { int arr[NUM] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int* point = arr; int result01 = getSum01(arr, NUM); int result02 = getSum02(point, NUM); int result03 = getSum03(point, point + NUM); printf("result01 = %dn", result01); printf("result02 = %dn", result02); printf("result03 = %dn", result03); return 0; } int getSum01(int arr[], int num) { printf("getSum01:arr = %pn", arr); int result = 0; for (int i = 0; i < num; i++) { result += arr[i]; } return result; } int getSum02(int* arr, int num) { printf("getSum02:arr = %pn", arr); int result = 0; for (int i = 0; i < num; i++) { result += *(arr + i); } return result; } int getSum03(int* start, int* end) { printf("getSum03:start = %pn", start); printf("getSum03:end = %pn", end); int result = 0; while (start < end) { result += *start; start++; } return result; }
int getSum01(int arr[], int num); int getSum02(int* arr, int num); int getSum03(int* start, int* end);
从输出的结果可以出,以上三个函数是等价的!
之前,我们也说过,数组名就是数组首元素地址。
无论是将函数形参定义成“数组”亦或是“指针”,我们传入的“数组名”亦或是“指针”,其实在底层最后传的都是“指针”,也就是“地址”,对应到本例就是“数组首元素的地址”。
这样也就能明白,为什么我们定义函数时需要两个形式参数,即“数组地址”和“数组大小”。
因为在函数实现中,我们并不知道数组的长度,盲目运算可能会造成空指针异常,因此我们要手动地传入“数组长度”。当然,我们也可以传入“数组首地址”和“数组尾地址”,其实原理都是一样的。
emmm,大概就唠这么多吧,最近发现基础真的重要!!!
注:如有错误,敬请指正!!!



