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
实现赋值的功能。比如下面的代码,完全可以不使用赋值运算符,但实现了赋值的目的。
#includestatic 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_mem
和alloc_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语言不像java
,python
那样使用起来方便,因为这些语言都有垃圾回收器,不用使用者处理关于内存的问题。这才是C语言难学的根本。
结论
指针是存储内存地址的数据类型,C语言可以根据内存地址操作内存。
指针和数组不相同,指针存储的是内存的起始地址,不包括内存的大小;数组既有起始地址,也有大小。
通过函数传递指针,除了指针参数本身,还要带上指针指向内存的大小。
指针难学并非指针本身,而是内存管理难处理
感谢各位的阅读,以上就是"C语言的指针详细介绍"的内容了,经过本文的学习后,相信大家对C语言的指针详细介绍这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!