第二章 变量和基本类型
阅读提示
- 本文中的代码分为三类:
- OK 可编译:可直接作为单文件(或放入 main 函数体)编译通过。
- 片段:只展示语法要点,非完整程序;如需编译,请放入函数体或补齐所需声明/头文件。
- NG 错误示例:为阐释语义而故意写出的编译错误,请勿整块复制编译。
引用
片段
int val = 1024;
int &refVal = val;
const int &refVal5 = val + 1;
double dval = 3.14;
const int &refVal7 = dval;
NG 错误示例(不可编译,演示语义):
// NG 错误示例(不可编译)
int val = 1024;
double dval = 3.14;
int &refVal2 = 10; // 错:引用的初始值必须是对象(左值)
int &refVal3; // 错:引用必须初始化
int &refVal4 = val + 1; // 错:非常量左值引用不能绑定到右值
int &refVal6 = dval; // 错:非常量左值引用不能绑定到临时(由 double 转 int 产生)
指针
-
指针的值应该属于下列4种状态之一:
- 指向一个对象
- 指向紧邻对象所占空间的下一个位置
- 空指针,意味着指针没有指向任何对象
- 无效指针,也就是上述情况之外的其他值
-
注意:对 one-past-the-end 指针(指向“尾后”位置)只能做比较或指针算术(如减法),不可解引用。
-
给指针赋值,就是令它存放一个对象的地址,或者令它为空。
-
通过
*的个数可以区分指针的级别。也就是说,**表示指向指针的指针,***表示指向指针的指针的指针。
*与&运算符
片段
int i = 42;
int &r = i; // &紧随类型名出现,是声明的一部分,r是一个引用。
int *p; // *紧随类型名出现,是声明的一部分,p是一个指针。
p = &i; // &出现在表达式中,是取地址运算符。
*p = i; // *出现在表达式中,是解引用运算符,用于访问/修改p所指向的对象;这里将i的值写入p所指向的对象。
int &r2 = *p; // &是声明的一部分,r2是一个引用。*是解引用运算符,r2被绑定到p所指向的对象i上。
怎么读懂复杂的指针声明
面对复杂的指针或引用声明时,可以使用"从右向左阅读"的方法来理解变量的类型。
基本规则
核心原则:从变量名开始,按照"离变量名最近的符号优先"的原则来理解类型。
阅读顺序:
- 找到变量名
- 看变量名右边的符号(如果有的话)
- 看变量名左边最近的符号
- 继续向左,直到基本类型
示例1:指向指针的引用
int *p = nullptr; // 已有一个指向 int 的指针变量
int *&r = p; // r 是“指向 int 指针”的引用,必须绑定到一个现存的指针对象
分析步骤:
- 找到变量名:
r - 变量名右边没有符号
- 变量名左边最近的符号是
&,所以r是一个引用 - 继续向左看到
*,说明r引用的是一个指针 - 最左边是基本类型
int,说明这个指针指向的是int类型
结论:r 是一个指向 int 指针的引用(reference to a pointer to int)
示例2:指向指针的指针
int **p;
分析步骤:
- 找到变量名:
p - 变量名左边第一个
*,所以p是一个指针 - 继续向左第二个
*,说明p指向的也是一个指针 - 基本类型是
int
结论:p 是一个指向 int 指针的指针(pointer to a pointer to int)
示例3:指针数组
int *arr[10];
分析步骤:
- 找到变量名:
arr - 变量名右边是
[10],说明arr是一个数组,有10个元素 - 向左看到
*,说明数组的每个元素是指针 - 基本类型是
int
结论:arr 是一个包含10个 int 指针的数组(array of 10 pointers to int)
示例4:指向数组的指针
int (*p)[10];
分析步骤:
- 找到变量名:
p p被括号包围,优先处理括号内的内容- 括号内,
p左边是*,所以p是一个指针 - 括号右边是
[10],说明p指向的是一个数组,有10个元素 - 基本类型是
int
结论:p 是一个指向包含10个 int 元素的数组的指针(pointer to an array of 10 ints)
注意:括号 () 改变了运算优先级,[] 和 () 的优先级高于 *
示例5:返回指针的函数指针
int *(*func)(double);
分析步骤:
- 找到变量名:
func func被括号包围,优先处理括号内的内容- 括号内,
func左边是*,所以func是一个指针 - 括号外右边是
(double),说明func指向的是一个函数,该函数接受一个double参数 - 最左边是
int *,说明这个函数返回一个指向 int 的指针
结论:func 是一个函数指针,该函数接受 double 参数并返回指向 int 的指针(pointer to a function that takes a double and returns a pointer to int)
快速记忆口诀
从变量名出发,右左右左读,括号改优先,最后看类型
- 右:先看右边,处理
[](数组)或()(函数) - 左:再看左边,处理
*(指针)或&(引用) - 括号:括号改变优先级,优先处理括号内的内容
- 类型:最后得到基本数据类型
指向指针的引用
指针本身是一个对象,所以可以被引用。 下面来做一个指针,和指针的引用的例子。
- 指针的引用
片段
int i = 1024;
int newI = 2048;
// p是一个int指针,指向i
int *p = &i;
// r是一个对int指针的引用,它是p的别名
int *&r = p;
// 修改r就是修改p。这里让p指向newI
r = &newI;
// 因为p已经指向newI,所以解引用p得到的是newI的值
std::cout << *p << std::endl; // 输出 2048
- 单纯的指针
片段
int i = 1024;
int newI = 2048;
// p是一个int指针,指向i
int *p = &i;
// r是另一个int指针,它的初始值是p的值(即i的地址)
int *r = p; // 这里r和上面的例子不一样,它不是引用,而是指针。
// 修改r,让r指向newI。这个操作不影响p
r = &newI;
// p仍然指向i
std::cout << *p << std::endl; // 输出 1024