说起指针,不少人认为指针是C/C++语言中尽量避免使用的东西,乃至把它当做潘多拉魔盒。但是作为一种比C# JAVA要低级一点的语言,其优势也就在于能够使用指针。指针为何会有很多人希望不用它是因为它容易出错,而一旦出错,要么指向不可用的内存直接崩溃,要么将把别人的一些正确的东西弄错了,搞出莫名其妙的结果。
其实指针并不可怕,要理解指针,首先要内存说起。就不扯到高端的64位处理器,拿我们用得最频繁的32位系统说起吧。指针的值的范围是多少呢?在32位系统里,sizeof(void*)其实长度刚好是4,也就是说如果把它强转成一个long对象,那么刚好匹配的值与long相同,也就是0~4294967294,刚好就是4G,而32位内存寻址最大值也是4G。事无凑巧,指针其实就是一个值对应的内存寻址中的地址。当我们定义一个变量 不管是局部变量还是 new一个出来,都会有其地址,也就是可以用一个指针的值来保存它,指针也可以通过取值符号*来拿到其对应的值。
这个时候,问题来了,指针类型可是多种多样的,不仅仅只有一种指针,比如 void* int* float*等,这个其实和取值计算有关,只有告诉编译器你的指针是什么类型,编译才能够算出你要取的值究竟有多大该怎么表示,最终找到内存地址,把内存地址中的数据,转换成已知类型传递给程序。并且指针的运算符号也是基于此,比如:
int* i = new int;printf("%u\n", i);
++i;
printf("%u\n", i);
--i;
delete i;
输出结果是:
3362432
3362436
而不是
3362432
3362433
指针的值 多了4 而不是1 ,而4刚好是== sizeof(int)
只有代码:
char* i = new char;
printf("%u\n", i);
++i;
printf("%u\n", i);
--i;
delete i;
输出结果才是:
4149496
4149497
因为1 == sizeof(char)
因此我们可以通过类型强转 把指针类型转换成不同类型,来达到灵活挪动内存位数,也可以依靠这个来做到遍历二进制数据。
指针另外一个让人头疼的,无疑就是 指向指针的指针,比如 int** pp;
指向指针的指针也叫双重指针,其存的值就是一个指针对象的地址。要知道当我们定义 int* p = NULL;的时候,这个指针p其实也有自己的地址,以为指针其实也是一种数据类型,与float int等一样,都是变量,也就一样拥有地址和值。不过指针的值就是另外一个值的地址而已。
这种东西主要用途是在于 从函数中传递指针
比如我们希望写一个函数,从中可以拿到我们分配好的一块内存的首地址,但是返回值已经被其他功能占用,这个时候需要通过从传递的参数中把值返回回来。
我们也知道 函数中传递参数其实是会把参数进行值的拷贝
比如
void func(int a){ a = 3;} 如果代码这么写
int i = 2; func(i); 这个时候执行完结果i依旧是2,因为func(i)执行的时候 a会被当做一个局部变量一样执行 a = i 的逻辑,然后再执行a = 3
这个时候a变成什么样与i无关。 因此 要改变i 得写成 void func(int* a){ *a = 3;} 这个时候 执行func(&i),可以把i变成3。
同理,当要传递一个指针出来的时候 我们可以用类似的方式 void func(int** ptr){ ... ; *ptr = pArray;} 来获得pArray[0]的地址到外面
int* p;
func(&p);
//p == pArray
指针还一个易错的,就是指针值拷贝,其实就是地址复制。这种操作其实是很危险的。
我们C++代码,有一个原则,谁创建的谁销毁。而这种值拷贝会直接破坏这个原则。
比如我int* p = new int[3];
int* q = p;
这个时候,p如果执行了delete[] p; 如果q没有得到通知清除为0,那么就会变成一个野指针,指向不可引用的内存。如果取其值将造成内存越界崩溃。
而更可怕的是,内存越界并不一定会崩溃,当后面其他对象被创建的时候,如果刚好又把这个地址变成可用的,那么q就指向了另外一个未知的地址,而你调用它对它进行操作,可能带来的结果不是崩溃,而是诱发其他地方出现崩溃或者数据异常。这个就非常难查。
所以,指针是个好东西,但是一定要用好,废品往往是被用废的好东西。