目录
1.初始C指针
几个重要的概念:
指针的加减
&与*
二级指针
2.指针与数组
指针数组
数组指针变量
一维数组与二维数组传参的本质
编辑编辑 编辑
3.指针与函数
函数指针数组
4.指针与结构体
5.野指针以及常见的内存管理错误
常见的内存错误:
6.结语
今天咱们讲C语言的指针,都说指针难,确实,这部分对新手来说确实不好理解,那么下面跟着博主一起来学习学习吧。
1.初始C指针
先来看一个代码图片
怎么样,这六个能否彻底搞清楚呢?如何可以的话,那么说明你的指针学的还是可以的,如果搞不清楚,咱们就一起来探讨一下。先抛出几个问题?什么是指针?指针与数组的关系是什么?指针与函数的关系是什么?指针与函数,数组之间的关系又是什么?
几个重要的概念:
博主在观看c语言指针相关的书籍的时候,发现有的书籍并没有把这几个概念描述的很清楚,但这几个概念要是不清楚,很影响学指针的。
1.指针的类型:指针变量的声明类型,例如 int*
、char*
、void*
等。
一般来说,去掉*剩下的就是指针类型。
int **p;//指针类型为int**
int(*p)[3];//指针类型为int(*)[3]
int*(*p)[3];//指针类型为int*(*)[3]
由于指针可对指针指向的内存区里的数据进行修改,所以说二级指针,可以修改一级指针!
2.指针所指向的类型:指针指向的内存区域中存储的数据类型。
一般只需要将指针名称,以及指针左边的*去掉即可。
int **p;//指针所指向的类型为int*;
int(*p)[3];//指针所指向的类型int()[3]
int*(*p)[3];/指针所指向的类型int*()[3]
3.指针的值:就是指针所指向的那块内存的起始地址 (是一个地址)
要注意的是,一般为指针指向的内存块就是从内存的首地址开始,往后sizeof(指针所指向的类型)个字节的内存区域,以后,我们说一个指针的值是hh,就相当于说该指针指向了以hh 为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。
4.指针的大小:这个大小取决于编译器,x86环境下是4字节,x64环境下是8字节。
指针的加减
1.指针加减整数
由此可见,指针的+1,这个1的大小为sizeof(指针所指向的类型)个字节的大小,例如上面第一个图片,由于ptr这个指针所指向的类型是int,所以说,ptr+1就是相当于在原来的基础上加了4个字节。所以说,相当于一开始的ptr指向的是内存空间0地址的位置,后面指向了内存空间第4个字节的位置。
2.指针减指针
指针也是可以减指针的,当然这里需要注意的是如果是高地址减低地址,为正。反之为负。
3.指针的比较:指针也是可以比较大小的
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int sz = sizeof(arr)/sizeof(arr[0]);
while(p < arr + sz) // 指针的大小比较
{
printf("%d ", *p);
p++;
}
&与*
&是“取地址”的意思,而*是解引用操作符,相当于&的逆运用
int a=10;
int *p=&a;
printf("%d",*p);
这里就是把a的地址拿出来(&)锁到p这个指针变量中,且p指向的是int类型的a,而*相当于钥匙,打开房门,拿出a的地址的钥匙,所以*p,就是拿出了a的地址,相当于*p就是a了。
二级指针
把变量的地址拿出来,放到一个指针中,这个指针叫做一级指针a,存放的是变量的地址,但是一级指针a也有地址的,把一级指针a的地址拿出来,再放到一个指针b 中,这个指针b就是二级指针。同理,也有三级指针,四级指针·,不过这些都用的少了。
int a =10;
int *p=&a;//一级指针
int **pp=&p;//二级指针
2.指针与数组
下面开始咱们的正题:咱们知道,对于一个数组,数组名是数组首元素的地址
实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。sizeof(a)就是整个数组的大小,就是12字节,sizeof(&a),&a,是整个数组的地址,而地址的大小,取决于编译器的编译环境,上面有讲解。
指针数组
什么叫做数组指针呢?就是存储指针的数组,它是一个数组,这个数组里面存储了指针
例如:int *p[3];就是p是数组名,p这个数组中存了三个int类型的指针。看这类可以通过符号的优先级来看,首先从指针名字开始,指针名字先与[]结合,就是数组,后再与*结合,为数组中存储的是指针,最后再是int,说明数组中的指针为int类型的。
数组指针变量
这是一个指针变量,例如:int(*p)[3]就是p先与*结合,所以这是一个指针,后再与[]结合,这是一个数组,是指针指向的数组,且指针存储的数组是3个int类型元素的数组,指针指向的是整个数组的地址。
那么数组指针变量如何初始化呢?
int arr[10]={1,2,3,4};
int (*p)[10]=&arr;//数组指针即可这样初始化,这样p这个指针指向的是arr这个数组的地址。
一维数组与二维数组传参的本质
图一是一维数组传参,数组传参的本质就是:一维数组的参数可以写成arr[],arr[3],但是编译器在识别的时候,会自动把他们退化为指针,即int*arr。
图二是二维数组传参的本质:你看着像不像数组指针?没错,就是它,没想到吧,数组指针变量还可以用做二维数组的形参。如何理解呢?实参是arr(二维数组的数组名),咱们知道数组名是数组首元素的地址。那么二维数组的首元素的地址就是整个第一行的地址,而整个第一行有5个元素,所以说,像不像一个指向数组的指针变量,这个数组中存储的是元素。这个就是数组指针。
1 | 2 | 3 | 4 | 5 |
2 | 3 | 4 | 5 | 6 |
3 | 4 | 5 | 6 | 7 |
但在内存中,二维数组是连续存放的,并不是这样的表格形状。
再来看一个知识点:*(*(p+i)+j)
剖析来看,p+i,p指的是整一行的地址,所以说p+i是横着移动i行,移动完之后再解引用,得到的是第i行第一个元素的地址,那么之后再加j,指的是,该行第j个元素的地址,再解引用就可以得出,二维数组第i行j列的元素,与p[i][j]一样的。同理:一维数组也是这样的,p[i]=*(p+i).\
3.指针与函数
看完了指针与数组,再来看指针与函数,先来介绍一个函数指针:
上图中,p为函数指针变量,(因为前面有*,代表p是一个指针变量)。(int x,int y)代表的是函数指针指向的函数的参数,而int是函数指针指向的函数的返回值。经过这样一剖析,是不是就清楚多了。而函数指针的初始化,与数组指针的初始化差不多,这里需要注意的是函数名与&函数名均可以作为函数的地址。所以可见上面的代码示例。
函数指针数组
那么有没有一个数组,里面存储的是函数指针呢?有的兄弟有的,下面请看:
int(*p[3])(int x,int y);
parr1 先和 [] 结合,说明parr1是数组,数组的内容是什么呢? 是 i nt (*)()类型的函数指针。
4.指针与结构体
观看上面两个代码图片可知,第一张图片是结构体定义了一个变量ss,咱们可以通过"ss."访问成员变量。也可以通过结构体定义的指针变量去访问结构体成员变量。如图二。
5.野指针以及常见的内存管理错误
什么叫做野孩子?就是不受管教的孩子,那么我们需要制定出一套规则去约束野孩子,这样他就不会乱闯祸了。野狗也是一样,找一条链子把它拴起来,这样它就不会乱咬人了。那么野指针,就需要使用NULL来约束它,为了避免野指针,建议每次定义指针之后,将指针置为空。
对于malloc,realloc,calloc等动态开辟出的空间,就需要手动置为空,即free()。
常见的内存错误:
1.指针没有指向一块合法的内存。
2.为指针分配的内存太小。
3.内存分配成功了,但是没有初始化。
4.内存越界。
5.内存泄露。
6.内存已经被释放了,但仍通过指针来访问内存:
[1].free(p)后,继续通过p来访问指针,这时候p是野指针。
[2].在函数内部定义了一个数组,却用return语句返回指向该数组的指针。这个地方函数栈帧已经回收了,指针指向的那块内存空间没有了,你还怎么访问?解决办法就是弄明白栈上的变量的生命周期。
6.结语
世上无完人,也无完事。所以说,C语言成也指针,败也指针。这个野指针,一定一定要注意,还有内存问题,否则以后会吃大亏。
以上内容仅我个人理解,如果不对,请指出,谢谢!
本篇完..................