一、指针的那些事
说起指针,关于指针的四个方面一定要清楚:
- 指针的类型
- 指针指向的类型
- 指针的值
- 指针本身所占的内存空间
1、指针的类型
<1> int* p; <2> int** p; <3> char* p; <4> double* p[3];
无论上面定义的指针类型你是否认识,有一个判断指针的万能公式----把指针名去掉,剩下的就是是指针的类型
比如int** p; 这个指针自身的类型就是 int** (去掉指针名p).。
2、指针指向的类型
这个和上面的判断方法类似,判断一个指针的指向类型时,把指针名和前面的一个*去掉,剩下的就是指针指向的类型
比如 int** p; 这个指针指向的类型就是int * 。
作用:决定了指针变量所取空间的宽度,决定了指针变量+1时跳过的单位跨度(★★★)
关于上面的作用可能不是很好理解,等到下面讲到指针的运算符会详细的解释上面的作用
3、指针的值
在定义指针会申请一块内存空间在存放这个指针,这个内存空间里面村的就是指针的值,通过指针的值一般都是第一个地址,这个地址都是指针所指向的的地址。定义指针时,仅仅是申请了一块内存空间来存放指针的地址,但是这个内存空间并没有初始化。
4、指针本身所占的内存空间
在定义指针时,肯定要去内存空间中申请一块内存空间来存放这个指针。
一开始发现了一个特别有趣的事:32位的系统中,无论什么类型的指针,都是用4个字节进行存储,64位的系统都是用8个字节进行存储。
#include<iostream> using namespace std; int main() { cout << sizeof(int **) << endl; cout << sizeof(char *) << endl; cout << sizeof(double *) << endl; cout << sizeof(long long int*) << endl; return 0; }
上面的代码在32位的系统中运行,输出的都是4,在64位的系统中运行,输出的都是8。
一开始特别困惑,不同的指针类型,它们怎么可能占用的内存空间一样呢?
最后去网上搜了搜,又查了一些书,最后得到了一个说服自己的理由:
假如是32的系统,那么内存空间的地址编号长度肯定都是一样长的,而指针的值存放的就是一个个的地址,那么不同类型指针占用的内存空间可以是一样大的,因为他们存放的地址长度都是一样的。
总结:一旦遇到了一个指针,自己就应该问问自己?指针的类型是什么?指针指向的类型是什么?指针的值是什么?
二、指针的算术运算
1、关于整形数的存储
首先看一张图片
假如我们定义了一个整型变量,又赋予了一个它一个初值(16进制的11223344).那他在系统中的存储方式如上图所示(采取的是小端存储,至于是大端还是小端存储,这个和操作系统有关,window的系统是小端存储)。
变量a的首地址都是地址1。当使用到变量a是,他就会拿着a的地址去内存中找,从这个地址一次往后找四个字节(找几个节和这个变量是什么类型的有关)。然后根据这四个字节读出来一个整数(至于怎样读的,就不是咱们考虑的,操作系统既然能存进去,它肯定也可以读出来)。
2、关于指针的运算
首先看 代码1:
#include<iostream> using namespace std; int main() { int a = 0x11223344; printf("%#x\n", a);//按16进制数字进行输出 int* p1 = &a; printf("%#x\n", *p1);//按16进制数字进行输出 short* p2 = (short*)&a; printf("%#x\n", *p2);//按16进制数字进行输出 char* p3 = (char*)&a; printf("%#x\n", *p3);//按16进制数字进行输出 return 0; }
代码1运行结果:
根据整数的存储,那么指针的访问如下图所示
下面再看 代码2 :
#include<iostream> using namespace std; int main() { int a = 0x11223344; printf("%#x\n", a);//按16进制数字进行输出 int* p1 = &a; printf("%#x\n", *p1);//按16进制数字进行输出 short* p2 = (short*)&a; p2++; printf("%#x\n", *p2);//按16进制数字进行输出 char* p3 = (char*)&a; p3++; printf("%#x\n", *p3);//按16进制数字进行输出 return 0; }
代码2 运行结果:
经过上面的代码后,指针p2和p3指向的位置都变了,下面通过一张图片看他们移动后的指向
先说指针p1,他的指向是地址1,指针指向的类型是int(4个字节),所以说指针p1的指向地址是地址1,读取的宽度是4个字节,因此*p1的值就是0x11223344(因为采取的小端存储方式,倒着存倒着取)。
再说指针p2,这个指针一开始的指向的地址1,指针指向的类型是short ( 2个字节 ) ,指针读取的宽度2个字节,因此指针p2移动前,*p2读取的数据就是0x3344。采取p2++后,指针p2向后移动一个单位长度(单位长度就是2个字节,这个和指针指向的类型有关)。那么首地址都变成了地址3,读取宽度是2个字节,因此读取的数值就是0x1122
最后说指针p3.这个指针一开始指向的地址1,读取宽度是1,因此*p3读取的数值就是0x44。
p3++后,向后移动一个单位长度(一个字节),读取长度是1,因此 *p3读取的数值就是0x33.
三、指针的安全行问题
1、空指针
在定义指针时,我们一般都要给指针赋初值,如果还没有地址给这个指针赋初值,可以赋给指针一个NULL。
上面是一个特别好的习惯,不过有一点需要注意的就是空指针不能别访问,
原因:NULL其实就是内存空间的0号地址,操作系统中0-255的地址空间都不能被访问,这个地址只能由操作系统进行管理,程序员不可以进行修改的读取。
2、野指针
假设我们现在定义了一个指针,同时由赋给了它一个地址(直接进行指定的常量地址),那这个指针就称为野指针。
#include<iostream> using namespace std; int main() { int* p = &(0x01020304); cout << *p << endl; return 0; }
就像上面那样,直接指定了一个地址赋给了指针,这就是野指针,我们在使用指针时千万不要这样做。
就像你去酒店开房一样,只有你去前台找管理的人,让他给你选择一个空的房间,这样你才能入住,假如少了前面那一步,你可以直接找一个房间进去吗,说不定里面就有人在干啥呢!!!