指针的定义--应用场景

1 指针的本质

1.1-指针的定义

  1. 如果在程序中定义了一个变量,那么在对程序进行编译时,系统就会给这个变量分配内存单元。在C语言中,指针变量是一种特殊的变量,它用来存放变量地址。指针变量的定义格式如下:基类型 *指针变量名
    另外注意:指针变量加1后,偏移的长度是其基类型的长度,例如int *p; ,整型指针变量p,p+1是偏移sizeof(int)
  2. 按变量地址存取变量值的方式称为直接访问,如printf("%d\n", i); scanf("%d", &i);等。
  3. 将变量i的地址存放到另一个变量中,这一种存取变量值的方式称为间接访问
  4. 指针与指针变量是两个概念,一个变量的地址称为该变量的“指针”。如果有一个变量专门用来存放另一个变量的地址(即指针),那么称它为“指针变量”。

1.2-取地址操作符与取值操作符

  • 取地址操作符为&,也称引用,通过该操作符我们可以获取一个变量的地址值。
  • 取值操作符为*,也称解引用,通过该操作符我们可以得到一个地址对应的数据。
#include <stdio.h>
int main()
{
int i = 10;
int *p; //p就是一个指针变量,p可以用来存储地址,类型是整型指针
p = &i; //初始化赋值
printf("i=%d\n",i); //直接访问
printf("*p=%d\n",*p);//间接访问
}

2 指针的使用场景

指针的使用场景只有两个,即传递和偏移,记住只有在这两种场景下使用指针。

2.1-指针的传递

  • C语言的函数调用均为值传递,所以为了能够在子函数中修改main函数内某个变量的值,可以传递该变量的地址。

2.2-指针的偏移

#include <stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int* p;//对一个指针变量进行取值,得到的类型是其基类型
p = a;
printf("*p=%d\n", *p);
for (int i = 0; i < 5; i++)
{
printf("%d\n", *(p + i));
}
return 0;
}

指针变量加1后,偏移的长度是其基类型的长度,如上述例子中,(p+0)即a[0],(p+1)即a[1],加括号是为了运算符的优先级原因。

2.3-指针与自增、自减运算符

  • 当遇到后增++运算符时,就要分两步来看。第一步是暂时忽略++操作。例如int *p; int j=*p++;,先看j = *p第二步是对p进行++操作。这里注意是对p进行++操作,因为*操作符和++操作符的优先级相同,只有比++优先级更高的操作符才会当成一个整体,比如()[]

2.4-指针与一维数组

  • 当数组名作为实参传递给子函数时,是弱化为指针的。一维数组的数组名中存储的是数组的首地址,所以传递到子函数时,高手形参写的是char *d而不是char d[],因为更符合C语言值传递的原理,并且能少打一个字符。
#include <stdio.h>
//数组名作为实参传递给子函数时,是弱化为指针的
//练习传递与偏移
void change(char *d)
{
*d = 'H';
d[1] = 'E';
*(d + 2) = 'L';
}
int main()
{
char c[10] = "hello";
change(c);
puts(c);
return 0;
}

2.5-指针与动态内存申请

2.5.1-动态内存申请

  • C语言的数组长度固定是因为其定义的整型、浮点型、字符型变量、数组变量都在栈空间中,而栈空间的大小在编译时是确定的,如果使用的空间大小不确定,那么就要使用堆空间,如下所示。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
//动态内存申请,用于使用的空间不确定时,堆空间
int main()
{
int i;
scanf("%d", &i);
char *p;
p = (char*)malloc(i); //注意:赋值的两边类型要保持一致
//char *p="hello"; 等价于把字符串型常量"hello"的首地址赋给p
//char c[10]="hello"; 等价于strcpy(c,"hello");
strcpy(p,"malloc success");
puts(p);
free(p); //堆空间必须手动free
printf("free success\n");
return 0;
}
  • 通过单步调试,在free(p);之前设置断点,可以看到free函数执行之后,p的值并没有改变,仍然是堆空间的这个地址,但是空间中的值被清空了。这时,p指针被称为野指针,为后续代码执行正确,最好加上p = NULL;,把p的值清空。
  • 首先看malloc函数。在执行#include <stdlib.h> void* malloc(size_t size);时,需要给malloc传递的参数是一个整型变量,因为这里的size_t即为int;返回值为void*类型的指针,因为malloc并不知道我们申请的空间用来存放什么类型的数据,所以确定要用来存储什么类型的数据后,都会将void*强制转换为对应的类型。
  • voidvoid*的区别:void表示什么都不返回,void*表示一个空指针,用来存储一个地址,可强制转换为对应类型。
  • 栈空间由系统自动管理,而堆空间的申请和释放需要自行管理,所以在具体例子中需要通过free函数释放堆空间。free函数的头文件及格式为
#include <stdlib.h>
void free(void *ptr);
  • 其传入的参数是void类型的指针,任何指针均可自动转为void*类型指针,所以我们把p传递给free函数时,不需要强制类型转换。
  • p的地址值必须是malloc当时返回的地址值,不能进行偏移,也就是在malloc和free之间不能进行p++等改变变量p的操作,原因是申请一段堆内存空间时,内核帮我们记录的是起始地址和大小,所以释放时内核用对应的首地址进行匹配,匹配不上时,进程就会崩溃。如果需要偏移,那么可以定义两个指针变量解决,赋值然后偏移。

2.5.2-栈空间与堆空间的差异

  • print_stack()子函数中的字符串存放在栈空间中,函数执行结束后,栈空间会被释放,字符数组c的原有空间已被分配给其他函数使用,所以从子函数返回后,再打印就会出现打印乱码。而print_malloc()函数中的字符串存放在堆空间中,并不会因为子函数返回而释放,只有在执行free函数才会释放,否则在进程执行期间一直有效。
posted @   paopaotangzu  阅读(77)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示