C++入门教程之指针的类型和指针的运算
void *
在上一篇教程中,你可能会有疑问,为什么不用统一的指针类型,而用不同类型后面加*作为指针的类型。而实际上,你可以用void *类型的变量来保存所有的内存地址:
#include int main(void) { int value = 100; void *pointer = &value; std::cout << "变量value的地址:" << &value << std::endl; std::cout << "指针pointer保存的值:" << pointer << std::endl; return 0; }
所有类型的指针都可以不需要转换直接赋值到void *类型的指针,像这样:
int value = 100; int *intpointer = &value; void *pointer = intpointer;
但是当其他类型互转或者void *转换其他类型时,就需要用到类型转换:
int value = 100; void *pointer = &value; // 无类型指针转换到其他指针用static_cast转换, 不转换而直接赋值将会编译报错 int *intpointer = static_cast(pointer); // 其他类型指针互转需要用reinterpret_cast转换, 不转换而直接赋值将会编译报错 long *longpointer = reinterpret_cast(&value);
通过地址修改内存中的数据
我知道你的疑问并没有得到解决:为什么指针有用类型区分。
我们知道,内存是逐个字节划分的,然后为每个字节的内存给一个编号,就是内存地址。以int为例,int一般是4个字节,那么内存地址就是按顺序连续的4个编号,这样为每个字节修改并不方便。为了更加方便地访问内存,只需要指定指针的类型,并且给出内存第1个字节的地址,编译器就会知道你需要修改的是哪些位置的内存。同样,你使用取地址符&获取到int变量的地址,也是这个变量的第1个字节的地址。(第1个字节的地址下面将简称为首地址。)
说了这么多,现在看看怎样通过地址修改内存中的数据。只需要在地址前面加上*,就可以对这个内存中的数据进行操作:
#include int main(void) { int value = 100; std::cout << *(&value) << std::endl; return 0; }
输出结果:
100
上面代码中,先取出变量value的地址&value,然后在地址前面加上*即*(&value),这样就可以使用内存中的数据了,其中*(&value)加()是为了让它先取地址。
也可以将内存地址保存到指针里,然后再通过指针操作内存中的数据,如:
#include int main(void) { int value = 100; int *pointer = &value; // 取出value的首地址赋值给pointer *pointer = 1024; // 在地址前加*就可以对地址代表的内存进行操作 std::cout << value << std::endl; // value的值也随之改变 return 0; }
输出结果:
1024
由于编译器知道*(&value)和*pointer表示的这份内存是int类型(有4个字节),所以它会将从首地址开始的4个字节一起处理。所以如果你不指定地址和指针的类型,编译器就不知道你要操作哪几个字节的内存。也就是使用void *的话,编译器就不知道这份内存有几个字节。
const
现在看看const限制指针。
如果想指针初始化保存一个地址之后就不能再保存其他内存地址,可以这样写:
std::string text1 = "小古银的C++教程"; std::string text2 = "C++的小古银教程"; std::string * const pointer = &text1; // pointer = &text2; // 去掉开头注释将会有编译错误
如果想指针指向的内存中的数据不被改变,可以这样写:
std::string text1 = "小古银的C++教程"; std::string text2 = "C++的小古银教程"; const std::string *pointer = &text1; pointer = &text2; // 可以改变pointer保存的值 // *pointer = "小古银是死肥宅"; // 去掉开头注释将会有编译错误, 因为编译器知道我不是死肥宅
如果既不想改变指针保存的地址,也不想改变指针指向内存中的数据,可以这样写:
std::string text1 = "小古银的C++教程"; std::string text2 = "C++的小古银教程"; const std::string * const pointer = &text1; // pointer = &text2; // 去掉开头注释将会有编译错误 // *pointer = "小古银是死肥宅"; // 去掉开头注释将会有编译错误
指针的运算
指针可以进行加减运算。和普通的数值运算不同,先看一个例子就会明白了:
#include int main(void) { int value = 100; int *pointer = &value; std::cout << "pointer保存的地址:" << pointer << std::endl; std::cout << "pointer保存的地址加1:" << (pointer + 1) << std::endl; return 0; }
当你看到输出结果的时候,你会发现两个地址的差值是4个字节。因为编译器是根据指针的类型来进行加减运算,也因此void *类型的指针是不能进行加减运算的。当然,你不应该对value的下一个地址表示的内存进行操作,因为你不知道这个内存里到底保存里什么,而且你连这个地址指向的地方有没有内存你也不知道。
以int举例,指针的加减运算可以理解为:指针加1,就是下一个int类型变量的首地址;指针加2,就是下下一个int类型变量的首地址;指针减1,就是上一个int类型变量的首地址;指针减2,就是上上一个int类型变量的首地址;以此类推。
指针的运算只能是加法和减法,而且在加法中指针只能和整数相加;在减法中,指针只能减去一个整数,或者两个指针相减。
特殊地pointer = pointer + n;(其中n是一个整数变量)可以简写成pointer += n;,再特殊点,n是1时,可以再简写成++pointer;或者pointer++;。减法同理,除了两个指针相减。
还有一个特殊的用法:*(pointer + n)可以简写成pointer[n],这个在后面连续的内存里经常用到。