文章目录
- 第一节:指针的本质
- 1. 指针的定义
- 2. 取地址操作符与取值操作符
- 第二节:指针的传递
- 1. 指针的传递
- 第三节:指针的偏移
- 1. 指针的偏移
- 2. 指针与一维数组
- 第四节:动态指针与内存申请
- 1. 指针与动态内存申请
- 2. 堆空间和栈空间的差异
第一节:指针的本质
1. 指针的定义
直接访问:如果在程序中定义了一个变量,那么在对程序进行编译时,系统就会给这个变量分配内存单元,按变量地址存取变量值的方式称为“直接访问”,如:printf(“%d”,i);scanf(“%d”,&i);等。
间接访问:将变量i的地址存放到另一个变量中,在C语言中,指针变量是一种特殊的变量,它用来存放变量地址。
指针变量的定义格式:
基类型 *指针变量名;
例如:int *i_pointer;
指针与指针变量是两个概念,一个变量的地址称为该变量的“指针”。
如图:2000是地址,即变量i的指针。i_pointer存放的是2000,那么i_pointer称为指针变量。
64位应用程序指针寻址范围是8个字节。32位应用程序寻址范围是4个字节。
2. 取地址操作符与取值操作符
取地址操作符为&,也称引用。通过该操作符我们可以获取一个变量的地址值;取值操作符为*,也称解引用。通过该操作符我们可以得到一个地址对应的数据。
指针变量的初始化一定是某个变量的地址。
#include <stdio.h>
// &符号是取地址,指针变量的初始化一定是某个变量取地址
int main() {
int i=5;
int *p=&i; // 指针变量初始化
printf("i=%d\n",i); // 直接访问
printf("*p=%d\n",*p); // 直接访问
return 0;
}
F:\Computer\Project\practice\6\6.1-pointer\cmake-build-debug\6_1_pointer.exe
i=5
*p=5
进程已结束,退出代码为 0
注意点:
-
指针变量前面的* 表示该变量的类型为指针变量。指针变量名为p,而不是*p。
-
在定义指针变量时必须指定其类型,指针变量类型和变量类型要保持一致。例如:int i;那么指针变量应为int *p,都是int类型。
-
&和*两个运算符的优先级别相同,但要按自右向左的方向结合。
-
如果要同时定义多个指针变量,需要使用:int *a,*b,*c…
第二节:指针的传递
指针的使用场景通常只有两种,传递和偏移。
1. 指针的传递
#include <stdio.h>
void change(int j){
j=5;
}
int main() {
int i=10;
printf("before change i=%d\n",i);
change(i);
printf("after change i=%d\n",i);
return 0;
}
F:\Computer\Project\practice\6\6.2-transmit\cmake-build-debug\6_2_transmit.exe
before change i=10
after change i=10
进程已结束,退出代码为 0
分析:i=10经过了change函数,值应当变为5才对,为何没有改变?
当main函数开始执行时,系统会为main函数开辟函数栈空间,当走到int i时,main函数的栈空间就会为变量i分配4个字节大小的空间,调用change函数时,系统会为change函数重新分配新的函数栈空间,并为形参j分配4个字节大小的空间,所以i和j对应空间地址是不同的。在调用change(i)时,实际上是将i的值赋给了j。我们把这种效果称为值传递(C语言的函数调用均为值传递)。因此,当我们在change函数的函数栈空间内修改变量j值后,change函数执行结束,其栈空间会释放,j就不存在了,i的地址和它不同,并没有被修改。
我们将以上程序做一些修改:
#include <stdio.h>
void change(int *j){
*j=5; // 注意这里不是j=5,而是*j=5
}
int main() {
int i=10;
printf("before change i=%d\n",i);
change(&i); // 传递变量i的地址
printf("after change i=%d\n",i);
return 0;
}
F:\Computer\Project\practice\6\6.2-transmit\cmake-build-debug\6_2_transmit.exe
before change i=10
after change i=5
进程已结束,退出代码为 0
很明显,当我们把i的地址传递给chang函数,那么它取到的值就是i的实际地址对应的值。当然修改的就是i的值了。
注意:*j=5才是对i的修改,如果写成j=5,那只是对j的修改。因为j和i对应的内存空间不同,只有*j才能取到i的空间地址。
第三节:指针的偏移
1. 指针的偏移
指针的另一种场景就是对其进行加减。在实际中,我们把对指针的加减称为指针的偏移,加就是向后偏移,减就是向前偏移。
#include <stdio.h>
#define N 5
int main() {
int a[N]={1,2,3,4,5};
int *p=a; // 保证等号两边的数值类型一致。一维数组本身存放的就是首地址,所以没有必要写&取地址
int i;
for(i=0;i<N;i++){ // 正序输出
printf("%3d",*(p+i));
}
printf("\n------------------\n");
p=&a[N-1]; // 让P指向最后一个元素,注意有&
for(i=0;i<N;i++){ // 逆序输出
printf("%3d",*(p-i));
}
printf("\n");
return 0;
}
F:\Computer\Project\practice\6\6.3-deviation\cmake-build-debug\6_3_deviation.exe
1 2 3 4 5
------------------
5 4 3 2 1
进程已结束,退出代码为 0
说明:数组本身就是存放的就是指针,不需要加&,但是里面的具体数据需要通过&取值。
同时,偏移的长度是其基类型的长度,也就是偏移sizeof(int),这样通过*(p+1)就可以得到元素a[1]的值。如下图:
2. 指针与一维数组
分析:为什么一维数组在函数调用进行传递时,它的长度子函数无法知道呢?
#include <stdio.h>
void change(char *d){ // 形参实际上接收到的是一个地址(数组的首地址)
*d='H'; // 指针法
d[1]='E'; // 下标法
*(d+2)='L'; // 指针法
}
int main() {
char c[10]="hello";
puts(c);
change(c); // 数组名作为参数传递给子函数时,是弱化为指针的
puts(c);
return 0;
}
F:\Computer\Project\practice\6\6.3-pointer_arr\cmake-build-debug\6_3_pointer_arr.exe
hello
HELlo
进程已结束,退出代码为 0
解析:因为一维数组名中存储的是数组的首地址。所以子函数change中其实传入了一个地址。定义一个指针变量时,指针变量类型要和数组类型保持一致,通过取值操作,就可以将"h"改为"H",这种方法称为指针法。获取数组元素时,也可以通过取下标的方式来获取数组元素并修改,这种方法称为下标法。
第四节:动态指针与内存申请
1. 指针与动态内存申请
平时我们定义的整型、浮点型、字符型变量、数组变量都是在栈空间中,而栈空间的大小在编译时确定的,不会改变。如果使用的空间大小不确定,就要使用堆空间。
#include <stdio.h>
#include <stdlib.h> // malloc需要的头文件
#include <string.h>
int main() {
int i;
char *p;
char q[50]; // 也可以写成char *q
scanf("%d",&i);
p=(char*) malloc(i); // 使用malloc动态申请堆空间,注意强制类型转换。堆空间刚申请下来是void类型,需要转换
strcpy(p,"malloc success");
strcpy(q,p);
puts(p);
free(p); // free时必须使用malloc申请时返回的指针值,同时不能进行任何偏移
puts(p); // 可以看到释放完后是乱码
puts(q);
return 0;
}
F:\Computer\Project\practice\6\6.4-apply\cmake-build-debug\6_4_apply.exe
20
malloc success
鄅
malloc success
进程已结束,退出代码为 0
说明:在执行#include <stdlib.h> void *malloc(size_t size)时,需要给malloc传递的参数是一个整型变量。
当确定了用来存储什么类型的数据后,需要将void*强制转换为对应的类型。
同时需要注意指针本身的大小,和其指向空间的大小是两回事。
如图所示,定义的整型变量i,指针变量p均在main函数的栈空间中,通过malloc申请的空间会返回一个堆空间的首地址,我们把首地址存入变量p,知道了首地址,就可以通过strcpy函数往对应的空间存储字符数据。
堆的效率要比栈低得多。
栈空间由系统自动管理,而堆空间的申请和释放需要自行管理。所以需要通过free函数释放堆空间,free函数的头文件及格式为:
#include <stdlib.h>
void free(void *ptr);
其传入的参数为void类型指针,任何指针均可自动转换为void*类型指针,所以p传递给free函数时,不需要强制类型转换。p的地址值必须是malloc当时返回的地址值,不能进行偏移等操作。
2. 堆空间和栈空间的差异
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char* print_stack(){
char c[17]="I am print_stack";
puts(c);
return c;
}
char* print_malloc(){
char *p;
p=(char*)malloc(20);
strcpy(p,"I am print_malloc");
puts(p);
return p;
}
int main() {
char *p;
p=print_stack();
printf("p=%s\n",p); // 打印出来了null,因为栈空间被释放了
p=print_malloc();
print_malloc("p=%s\n",p); // 打印出了实际的数据,因为堆空间不会被释放,除非进程结束
return 0;
}
F:\Computer\Project\practice\6\6.4-heap_stack\cmake-build-debug\6_4_heap_stack.exe
I am print_stack
p=(null)
I am print_malloc
I am print_malloc
进程已结束,退出代码为
解析:函数执行结束后,栈空间会被释放,而堆空间不会被释放,除非进行free操作。