千家信息网

C语言的指针详细介绍

发表于:2024-11-18 作者:千家信息网编辑
千家信息网最后更新 2024年11月18日,这篇文章主要讲解了"C语言的指针详细介绍",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"C语言的指针详细介绍"吧!C语言的指针C语言最臭名昭著的是就是其
千家信息网最后更新 2024年11月18日C语言的指针详细介绍

这篇文章主要讲解了"C语言的指针详细介绍",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"C语言的指针详细介绍"吧!

C语言的指针

C语言最臭名昭著的是就是其指针的使用,也是C语言的精华所在。很多人在学习C语言时,吐槽指针特别难以学习和使用。今天我们来探讨一下C语言指针的问题。

指针的引入

C语言一个特点就是一切皆内存,定义任何类型的变量,都要分配内存。在函数里定义变量,需要在栈内存上非配;定义全局变量,需要在静态内存区分配内存,就连定义函数,也要在静态区分配内存(这就是所谓的代码段存储区域);不固定大小的内存,需要在动态区分配内存。

有了内存,就需要操作内存,也就是读取或者写入内存数据。C语言的最简单的内存操作就是通过赋值操作符(=)实现。比如下面的代码,赋值给变量a,实际就是改变a变量占用的内存的数据; 甚至是用户自定义的结构体变量c,也可以直接使用赋值运算符将b占用的内存块的数据拷贝到c占用的内存块,来完成内存的读写操作。之

int a;a = 6;strcut a_t{   int m_a;   char m_b;   shor m_c;};struct a_t b;a.m_a = 1;a.m_b = 2;a.m_c = 3;struct a_t c;//使用赋值运算符,将b里面所有成员的值赋给c里面的成员,即完成内存拷贝操作。c = b;

所以可以这样操作,是因为C语言的变量包含内存的地址(通过&取)和内存大小(通过sizeof取),知道这两个信息,即使没有赋值运算符,也可以使用memcpy实现赋值的功能。比如下面的代码,完全可以不使用赋值运算符,但实现了赋值的目的。

#include static int g_test = 0;int main(){    int a = 1;    //这里我们使用memcpy操作内存,实现赋值的操作    memcpy(&g_test, &a, sizeof(a));        return 0;}

但是这里有一个问题,即使C语言的函数参数传递只有值传递,就是说所有的变量通过参数传递,都会产生一份拷贝,唯一的办法就是传递变量的地址,这就产生的了用一个专门的类型来存储变量的内存地址的需求,指针应运而生了,这样做有下面的好处:

  • 用一个专门的类型保存内存地址,比用现有类型(int, unsigned int)更方便,因为指针确定了内存地址的大小。

  • 引入语法,为了方便操作内存:像上面的操作一个语句就可以解决了int *addr = &g_test; *addr = 1;这个可能还不太明显,但是到了结构体有很多成员变量,我们就要自己算每个成员的偏移量了,而指针可以直接找到每个成员的地址, 如指针的->符号操作,也可以使用[]进行内存的偏移操作。

二级指针

C语言引入了指针,又衍生出个二级指针的概念,直接弄晕了一部分新生,其实二级指针只是语法的体现,本质上也是指针,是用来存储内存地址的,这个本质没有变化。比如:

int a = 0;//p1存储a的地址int *p1 = &a;//p2存储p1的地址int **p2 = &p;

C语言的所有变量都是内存,都可以取地址,指针变量也不例外。不管是几级指针,都可以当做一级指针使用, 只要自己知道当前变量的意义然后处理好就可以了,比如下面的代码:

int alloc_mem(char *p, int size){    char *tmp_p = (char *)malloc(size);    //*p存储的是`p1`或者`p2`变量的内存地址,用memcpy直接将分配的内存地址付给到p指向的内存    memcpy(p, &tmp_p, sizeof(char *));        return 0;}int alloc_mem1(char **p, int size){    *p = (char *)malloc(size);            return 0;}int main(){    char *p1 = NULL;    char *p2 = NULL;        //利用c语言的强转    alloc_mem((char *)&p1, 2);        //利用二级指针    alloc_mem1(&p2, 2);}

上面的alloc_memalloc_mem1效果是一样的,只不过alloc_mem1使用的二级指针的概念。

指针与数组

指针与数组有者本质的区别,指针用来存储一块内存的地址,严格的来说是一块内存的起始地址,至于这块内存有多大,是不知道的,必须使用者显式的指定。比如memcpy这个函数,原型如下:

void *memcpy(void *dst, const void *src, size_t n);

dst指针指的是目标内存的起始地址,但是其指向的内存到底有多大,必须由参数n来给出,负责函数无法知道是否溢出了。而数组本身就是一块内存,包括数组的起始地址大小,比如我们定义一个数组:

//该数组的起始地址为&arr,大小为sizeof(arr)int arr[32];

arr数组的地址为&a,数组的大小为sizeof(arr)。这里就产生一个问题,就是如何通过函数参数传递数组,C语言是不支持传递数组的,它会默认把数组转换为数组的起始地址的指针。所以C语言函数传递数组,需要额外带上数组的大小,以防止内存溢出的问题。实际上,只要传递指针参数给函数,都要带上这个指针指向内存的大小。

指针难学?

大部分人都认为指针难学,其实不在指针本身,而是指针指向的内容--内存。C语言要保存数据,可以在栈上分配,这部分内存的作用域仅限于函数内部;可以在静态区分配内存,这个区域的内存作用域是全局的,但仅限于确定大小的内存;可以在动态区分配内存,这部分的内存作用域是全局的,支持可变大小的内存。C语言程序存储数据的大部分内存都是在动态区分配的。这就产生了内存管理问题,包括单不限于:

  • 内存的分配

  • 内存的释放

  • 内存大小的检查

  • 内存分配失败的处理

  • 边界的处理:字符总是以0结尾

  • 读越界的处理:在有效内存之外读数据

  • 写越界的处理:在有效内存之外写数据

由于以上问题的存在,导致C语言不像javapython那样使用起来方便,因为这些语言都有垃圾回收器,不用使用者处理关于内存的问题。这才是C语言难学的根本。

结论
  • 指针是存储内存地址的数据类型,C语言可以根据内存地址操作内存。

  • 指针和数组不相同,指针存储的是内存的起始地址不包括内存的大小;数组既有起始地址,也有大小。

  • 通过函数传递指针,除了指针参数本身,还要带上指针指向内存的大小

  • 指针难学并非指针本身,而是内存管理难处理

感谢各位的阅读,以上就是"C语言的指针详细介绍"的内容了,经过本文的学习后,相信大家对C语言的指针详细介绍这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

0