C 和 C++ 中的指针
数据、指令和内存
在冯·诺依曼体系中,程序的数据和指令,是存放在同一空间中的。具体到 C 和 C++ 语言来说,对应数据的那部分内存,存储的自然是数据;对应函数的那部分内存,存储的就是指令。
因此,对于内存中存储的内容来说,有两个关键要素:
- 它在哪里(内存地址是多少);
- 它具有哪些属性、能做哪些事情(它的类型是什么)。
指针是对内存区域的抽象
C 和 C++ 中的指针变量中存放着目标对象的内存地址,而与指针相符合的类型,则说明了相应内存区域中的内容具有哪些属性,以及能做什么事情。也就是说,在内存空间某块区域中的内容,如果有一个描述这块内存区域的指针存在,我们就能找到它(地址的作用),并且合理地使用它(类型的作用)。
定义和使用指针
指针的定义
在 C 和 C++ 中定义指针变量是很简单的,和定义普通的变量基本是一样的。所有的区别,仅在于我们需要在变量名称前使用解引用符号 *
来标记这是一个指针。
int *ip1, *ip2; // ip1 和 ip2 都是指向 int 类型变量的指针变量
double d, *dp; // d 是 double 类型变量,dp 是指向 double 类型变量的指针变量
在上述定义中,我们看到,ip1
, ip2
, dp
是三个指针,因为在定义它们时,在变量名前用 *
号标记他们是指针;而d
是一个普通的double
类型变量。同时,我们注意到,ip1
和 ip2
在定义之时,就确定了他们是指向int
类型的变量。这意味着,被ip1
和ip2
指向的内存,在使用ip1
和ip2
进行访问的时候,将被当做是int
类型的对象来对待。同理,dp
指向的内存,在使用dp
进行访问的时候,将被当做是double
类型的对象来对待。
回顾一下,我们在本节中提到,内存空间中的内容有两个关键要素:地址和类型。在上述定义过程中,我们通过类型与解引用符号*
相结合,已经确定了类型。如果要正确使用指针,我们还应该让指针记录一个地址。
获取对象的地址
上面说到,我们应该在定义指针之后,记录一个地址。在 C 和 C++ 中,我们需要使用取地址符号&
来获取对象的地址。
int val = 42;
int *p = &val; // &val 返回变量 val 的地址,记录在指向 int 类型变量的指针里
(绝大多数情况下,)指针的类型和对象的类型需要严格匹配。例如,你不能用一个指向int
类型的指针变量,保存一个double
类型的对象的地址。
double dval = 0.0;
double *pd1 = &dval;// 正确:pd1 保存 double 类型变量 dval 的地址
double *pd2 = pd1; // 正确:pd1 是 double 类型的指针,可以赋值初始化同样类型的 pd2
int *pi1 = &dval; // 错误:不能用指向 int 类型变量的指针保存 double 类型变量的地址
int *pi2 = pd1; // 错误:pd1 是 double 类型的指针,不能将其赋值给 int 类型的指针
访问指针指向的对象
在下例中,指针p
记录了变量val
的地址。因此,我们可以通过解引用指针p
来访问变量val
。
int val = 42;
int *p = &val; // &val 返回变量 val 的地址,记录在指向 int 类型变量的指针里
cout << *p << endl; // 通过指针 p 访问变量 val,输出 val 的值:42
*p = 360; // 通过指针 p 改变变量 val 的值
cout << *p << endl; // 通过指针 p 访问变量 val,输出 val 的值:360
cout << val << endl;// 输出 val 的值:360