目录
前言
指针无疑是学习C语言时的重难点,很多人对指针的学习都很头疼,我也差不多,所以我现在想在学完指针全部知识点的前提下,系统地将其梳理一遍,来加深我对指针的了解和掌握。以下则是我对我所学指针全部知识点的梳理。
一、内存是什么?
要想了解指针,就必须知道内存是什么,因为有内存方才会有指针(下面会提)。按我的理解,内存其实就是电脑程序运行前临时存放程序及其相关文档的一块空间,可以将其想像成一栋楼,然后这一整块空间又由许多以字节为单位的小空间组成,这些小空间就相当于组成这楼的各个小房间。
二、指针
1.什么是指针
内存单元的编号==指针==地址
打个比方,比如我刚开学的时候,我被分配到学校一栋楼里某层的某间宿舍,但是我刚来并不知道在是哪个宿舍,这时你师姐告诉你宿舍的门牌号,叫D5511,这时你就能根据这个门牌号去找到你的宿舍了,那么这里这个宿舍门牌号就是地址,有了地址,我们就能非常容易得找到自己的宿舍(内存中的空间)。在内存中,为了方便计算机快速访问内存中的某块空间,通常会给内存中的每块空间进行编址,其实就是给每块空间编一个门牌号。
2.指针变量
&操作符
&是取地址操作符,某些时候,要是想访问内存某块空间的内容,我们可以将其地址给取出来,这时就需要用到&取地址操作符了。
指针变量的意义
指针变量是用来存放地址的变量,存放在指针变量里的值都会被认为是地址。
这里我们可以用类比的方法来理解指针变量,比如 整形数据存放在整形变量里,浮点型数据存放在浮点型变量里,那么地址(指针)就得存放在指针变量里。
int a = 10;
int * p = &a;
对 int * p 进行拆解介绍,首先 p 是一个变量,在 p 前面加上 * 号 就表明 p 是一个指针变量,那么int 则是说明这个指针变量里的地址指向的是一个 int 类型的数据。
* 操作符
用一个指针变量将一个地址存放起来,是为了后面能通过这个地址找到这个地址指向的空间,对其内容进行访问。
int main()
{
int a = 10;
int * p = &a;
*p = 0;
return 0;
}
第五行的 *p 意思是指通过p中存放的地址找到其指向的空间, *p 就是a 变量了,然后0赋给a,那么a的内容就被修改了。那么有人就会问,为什么要通过这种方法改变a的值,直接将0赋给a不是更简单明了吗,其实了,通过指针访问修改让我们在写代码的时候多了一种途径或方法,这让我们在以后写代码的时候会更加灵活。
指针变量的大小
是变量就会有大小,如int形变量占四个字节,char类型变量占一个字节。那么指针变量占多少个字节空间。在32位的计算机上,32位说明有32根地址总线,每根地址总线的状态仅有0或1两种,那么我们就把32根地址总线组成的32位0或1序列作为一个地址,既然是32位,那么就需要4个字节来存储(每个字节八位),因此在32位系统下,指针变量的大小为4个字节。同理,64位系统下,指针变量的大小为8个字节。
结论:32位平台下,指针变量的大小为4个字节,64位平台下,指针变量大小为8个字节。
3.指针类型的意义
int a = 10;
int * p =&a;
上面讲到 int 指的是地址指向的数据的类型,那么如果是 char 类型的数据呢,其指针变量的类型又该怎么写,很简单,通过类比我们就知道 char 类型的数据的地址的指针变量的写法应该为 char * p,用来接收字符数据的地址。
char ch = 'w';
char * p = &ch;
指针的解引用
对不同指针类型的同一变量进行解引用,其能访问的空间权限是不一样的。比如 对 int 类型的p指针变量进行解引用,其能访问p变量的4个字节,那么对 char 类型的指针变量解引用,其访问的空间则为一个字节。
如果不理解的话,自己调试这两个代码打开内存窗口看看对比一下,就能理解指针变量类型的意义了,因篇幅原因,这里就补赘述了。
指针+-整数
不同类型的指针加减整数其实差异在其跳过多少个字节然后指向另一个空间。
比如 上图a的地址是0062FC84(32位系统下),那么p加上一的话,其指向的地址就变为0062FC88,相较之前的地址高出4个字节。
void * 类型指针
这是一种特殊的指针类型,其特点是可以接收存储任意类型的指针,但是呢,不能直接进行指针的解引用和+-整数操作,需要在此之前进行类型的强制转换后方可操作。
指针运算
指针的基本运算有三种:
- 指针+- 指针
- 指针 - 指针
- 指针的关系运算
第一种我们刚才在上面已经讲过,那么第二种指针减指针得到的结果是两个指针之间的元素个数。
运行这个代码得到的结果为3,即为指针之间的元素个数。
4.const修饰指针
我们知道 const 修饰变量,以至变量的值不可更改,但是通过变量的地址可以避开这一语法错误,从而更改变量的值。
int main()
{
const int a = 10;
a = 0; // 不可这样更改变量值
int *p = &a;
*p = 0; // 可以这样更改
}
为了避免变量值通过指针更改,因此可以用 const 来修饰指针变量。
const 修饰指针变量有两种用法,即位置的不同,分别为:
int const * p = &a;
int * const p = &a;
第一种表示 p 地址指向的内容不可修改,但指针变量本身的值可修改。
第二种则表示 p 指针指向的内容是可以修改的,但指针变量本身的值不可修改。
5.野指针
野指针就是指 指针指向的位置是未知的(随机的、不正确的、没有限制的)。
导致野指针的原因:
- 指针未初始化
- 指针越界访问
- 指针指向的空间释放
规避野指针的方法:
- 指针初始化
- 小心指针越界访问
- 指针变量不在使用时及时置为NULL,指针使用之前检查其有效性
- 不要返回局部变量的地址
6.assert断言
assert(表达式)用于当表达式满足指定条件时,返回非零值,程序继续执行,如果不满足条件时,返回0值,assert就会报错。运用assert断言时需要包含<assert.h>头文件,如果想关闭assert断言,则只需在头文件前加上 #define NDEBUG。
7.数组名的理解
运行这段代码看其结果,就能知道数组名其实是数组首元素的地址,也就是数组第一个元素的地址。
但是有两个例外:
- sizeof(arr)
- &arr
这两个表达式里的数组名是整个数组,不是数组首元素的地址。第一个计算的是整个数组的大小,第二个取出的是整个数组的地址。
数组传参的本质
既然说数组名是数组首元素的地址,那么在函数传参中,传递的数组名是否传的是数组首元素的地址,答案是 是的。本质上数组传参传的就是数组首元素的地址。
8.二级指针
指针变量也是一个变量,既然是变量那么就有地址,那么指针变量的地址放在哪里?
答案是二级指针
int a = 10;
int * p = &a;
int* *pp= &p;
*pp 找到的其实是 p,**pp找到的则是a。
9.指针数组
指针数组是指针还是数组,看名字就能猜到指针数组其实是数组,是一个存放指针的数组,数组中的每个元素都是地址。
int arr1[] = {1,2,3};
int arr2[] = {2,3,4};
int arr3[] = {3,4,5};
int * p[] = {arr1,arr2,arr3};
如上面的整形指针数组,里面存放的都是整形指针。
10.字符指针变量
char ch ='w';
char * p = &ch;
有一种指针类型为字符指针,类比前面的整形指针,字符指针就是指针指向的是字符型数据。
char * p = "abcde";
上面这种写法 其实是把字符串首字符的地址传给了指针变量p。
11.数组指针变量
数组指针变量是数组还是指针变量?
答案是指针变量。
整形指针变量存放的是整形变量的地址。
浮点型指针变量存放的是浮点型变量的地址。
那么数组指针变量则存放应该是数组的地址,能够指向数组的地址。
形式为 :int (*p) [10]
其中*p表明这是一个指针变量,[10]说明指针指向的数组有10个元素,int 说明数组每个元素为 int型。
12.函数指针变量
通过类比,我们可以得知函数指针变量就是存放函数地址的变量,目的是未来能通过这个地址来调用函数的。
函数名其实就是函数的地址,当然也可以通过&地址符来获得函数的地址。
形式为:int(*p)(int ,int )
上面括号里的int int 指的是指针指向的函数的参数类型和个数,前面的int则说明函数的返回类型。
这是函数指针的简单用法,大家可以试试,然后理解它。
函数指针数组:
类比整形指针数组,函数指针数组其实是一个数组,其数组中存放的是函数的地址。
形式为:int (*p[3])()
p[3]表示这是一个数组,存放的是 int(*)()类型的函数指针。
总结
指针内容的知识点比较多,也比较难理解,但是这么梳理下来,你会在脑海里建立指针的知识框架,对系统掌握指针会有很大帮助,日后通过指针就可以更高效便捷的解决问题了。