C++ Primer Fifth Edition notes(3)
- 1C++ Primer Fifth Edition notes(1)
- 2C++ Primer Fifth Edition notes(2)
- 3C++ Primer Fifth Edition notes(3)本文
- 4C++ Primer Fifth Edition notes(4)

2.2 变量
1 变量的基本结构
变量(variable) 提供可由名称访问的存储空间。
例如:
int value = 10;,既可以说value是一个int变量,也可以说是一个int对象。
结构:
-
类型 变量名;
-
类型 变量名 = 初始值;
-
类型 变量1 = 初始值, 变量2, 变量3 = 初始值;
变量初始化:
-
int units_sold = 0; -
int units_sold = {0};(列表初始化) -
int units_sold{0};(列表初始化) -
int units_sold(0);
花括号可以发现数据丢失:{}会阻止可能丢失信息的窄化转换,在较大的工程中,这比悄悄得到错误数值更安全。
如:
long double ld = 3.1415926536;int a = ld // √ a = 3int a{ld}; // 错误int b = {ld}; // 错误
2 变量的默认初始化
函数外的内置类型变量:定义在任何函数之外的内置类型变量,会被初始化为 0 / false。
函数内的内置类型变量:如果没有提供初始值,就不会被初始化,值未定义。
类类型变量:由类自身决定,例如std::string 类规定默认初始化会创建一个空字符串。
3 变量声明和定义
变量声明:使名字为程序所知,不创建对象,也不分配存储空间,如extern int i;。
变量定义:使名字为程序所知,创建对象,并分配存储空间,如int j;或extern double pi = 3.1416;(带初始化器)。
为什么同一个变量可以声明多次,却只能定义一次?
跨文件共享同一变量时,只在一个文件中定义对象并分配存储空间,在其他文件中使用 extern 声明该对象;同一变量可以有多条一致的声明,但整个程序只能有一个定义。
4 标识符可以包含哪些字符?
| 名称 | 是否适合作为标识符 | 原因 |
|---|---|---|
value | 是 | 普通字母 |
value2 | 是 | 数字不在开头 |
student_count | 是 | 合法使用下划线 |
2value | 否 | 以数字开头 |
class | 否 | C++ 关键字 |
user__count | 不应使用 | 包含连续两个下划线 |
_Value | 不应使用 | 下划线后接大写字母 |
5 局部同名变量为什么会隐藏外层变量?
一个作用域可以包含另一个作用域,这种关系称为嵌套作用域。
内层作用域通常可以使用外层作用域中的变量名。
例如:
#include <iostream>int reused = 42;int main(){int unique = 0;std::cout << reused << " " << unique << std::endl;int reused = 0;std::cout << reused << " " << unique << std::endl;std::cout << ::reused << " " << unique << std::endl;return 0;}输出:
Terminal window 42 00 042 0
-
如果内层重新定义同名对象,外层名字会被隐藏。
-
空的作用域限定符
::name可以显式访问同名全局对象。
| 代码位置 | reused 表示的对象 | 输出 |
|---|---|---|
定义局部 reused 之前 | 全局 reused | 42 |
定义局部 reused 之后 | 局部 reused | 0 |
使用 ::reused | 全局 reused | 42 |
2.3 复合类型
6 什么是复合类型?
- 复合类型以其他类型为基础定义,必须建立在另一种类型之上。
例如:引用和指针,不能只说引用或指针,还需要说明它与什么类型有关。
- 一条声明由基本类型和声明符组成,声明符不仅包含名字,还可能通过
&、*等符号决定每个名字最终具有的完整类型。
| 声明 | 基本类型 | 声明符 | 对象的完整类型 |
|---|---|---|---|
int value; | int | value | int |
int &ref = value; | int | &ref | int 的引用 |
int *ptr = &value; | int | *ptr | 指向 int 的指针 |
7 什么是引用?
- 引用不是一个独立对象,而是为一个已经存在的对象提供另一个名字。
例如:
int ival = 1024;int &refVal = ival;
ival与&refVal指向同一个对象,而区别于普通变量int value = ival;创建两个对象。
- 引用定义时必须绑定对象,绑定后不能改绑。
例如:
int ival = 1024;int &refVal = ival;int value = 5;int &refVal = value;表示把
value的值赋给&refVal绑定的对象,即赋给ival,而&refVal仍旧绑定ival。例如:
int &refVal = 10;
10是整数字面值,并非对象,因此会编译错误。例如:
int first = 10;int second = 20;int &ref = first + second;
first + second产生一个计算结果,不是一个可以被普通引用长期作为别名的对象,因此会编译错误。存在例外:
const int &ref = 10;
const引用具有更宽松的绑定规则,因此编译通过。
- 对引用进行读取、赋值和运算时,实际操作的是引用绑定的对象。
例如:
int value = 10;int &ref = value;ref += 5;等价于:
value += 5;
- 引用绑定时通常要求类型完全一致。
例如:
double dval = 3.14;int &refVal = dval;
refVal要求绑定一个int对象,但dval是double对象,因此会编译错误。
8 什么是指针?
- 指针本身是存放地址的变量对象,因此可以改变指向,可以被复制和赋值,不必在定义时初始化。
例如:
double dval;double *pd = &dval;double *pd2 = pd;这会复制指针保存的地址,两个指针可能指向同一个对象,但仍然是两个独立的指针对象。
| 对比 | 引用 | 指针 |
|---|---|---|
| 是否为对象 | 否 | 是 |
| 定义时是否必须初始化 | 是 | 语法上不必须 |
| 能否改变绑定或指向 | 不能 | 可以 |
| 能否表示“不指向对象” | 普通引用不能 | 可以使用 nullptr |
| 保存的内容 | 是原对象的别名 | 对象的地址 |
- 指针使用取地址运算符
&保存对象的地址,而非对象的值。
例如:
int ival = 42;int *p = &ival;std::cout << *p; // 输出 42同一个
&在不同位置含义不同,在声明符中表示引用,在指针初始化中表示取地址运算符。同一个
p在不同位置含义不同,在声明符中p表示保存地址,而*p表示根据这个地址访问对象,得到对象的值。同一个
*在不同语句含义不同,int *p中的*声明p是指针;*p中的*是解引用运算符,表示访问指针所指的对象。
- 空指针不指向任何对象,指针一般需要初始化为空指针。
int *p1 = nullptr; // 常用int *p2 = 0;
// 使用 NULL 需要包含 <cstdlib>int *p3 = NULL;9 如何修改指针?
- 修改指针变量自身的值
例如:
int i = 42;int *pi = 0;int *pi2 = &i;int *pi3; // 此时 pi3 尚未初始化pi3 = pi2; // pi3 和 pi2 现在都指向 ipi2 = 0; // pi2 不再指向对象
pi3 = pi2:读取pi2变量内存中的值(即i的地址),将其拷贝写入pi3变量的内存中。结果:
pi3和pi2两个指针变量内部存储的地址相同,均指向i。随后的
pi2 = 0;仅将pi2自身的内存值修改为空指针,不影响pi3,更不影响i所在的内存空间。
- 修改指针指向的值
C++ 中,赋值操作修改的永远是左操作数(左值)。
例如:
pi = &i; // 左边是 pi:改变 pi 保存的地址*pi = 0; // 左边是 *pi:把 pi 所指的 i 改为 0
pi = &i:左值是pi(指针变量本身);修改pi的内存,写入i的地址。
*pi = 0:左值是*pi(解引用表达式);*p表示根据这个地址访问对象,再定位到该地址对应的内存空间(即i),将其值修改为0,pi自身存储的地址不变。
10 如何使用指针?
if判断指针是否为空
例如:
int ival = 1024;int *pi = 0;int *pi2 = &ival;if (pi)// 条件为假if (pi2)// 条件为真
pi是空指针,因此if (pi)的条件为假。
pi2保存了ival的地址,因此if (pi2)的条件为真。
未经初始化等无效指针不能用于条件判断,读取未初始化指针的值是未定义行为,对 C++ 智能指针来说,std::unique_ptr、std::shared_ptr 放进 if 通常判断它是否持有对象。
==和!=比较两个指针
例如:
int ival = 1024;int *pi = 0;int *pi2 = &ival;int *pi3 = pi2;pi == pi2; // falsepi2 == pi3; // truepi2 != pi3; // false
使用未初始化指针进行比较,行为未定义。
void*指针能保存任意对象类型的地址
基本规则:
| 操作 | 是否允许 | 说明 |
|---|---|---|
int* -> void* | 可以 | C/C++ 都可隐式转换 |
void* -> int* | C 可以隐式;C++ 必须显式转换 | C++ 用static_cast<int*>(vp) |
*vp | 不行 | 不能解引用void*,因为void不是完整类型 |
vp++/vp + 1 | 标准不允许 | 不知道元素大小,不能做指针算术 |
const void* | 可以 | 指向 const 对象,不能通过它修改 |
函数指针转void* | 不保证可移植 | 标准不保证,尽量避免 |
例如:
int a = 42;void *vp = &a; // int* -> void* 可以隐式int *ip = static_cast<int*>(vp); // C++ 必须显式转回
int *ip = vp错误。
不能直接通过它读取或修改对象,例如 *vp 不合法。原因是编译器不知道vp指向的对象是什么类型,也就无法确定该如何访问它。
*只属于它所在的声明符
例如:
int* p1, p2;:基本类型仍是int,*只修饰p1,所以p1是指向int的指针,p2则是int对象。如果两个变量都要是指针,就要分别写出
*,如int *p1, *p2;
**指向指针的指针与*&指向指针的引用
C风格接口常用**:
例如:
void alloc(int** out) {*out = new int(42);}int main() {int* p = nullptr;alloc(&p); // 传 p 的地址// 现在 p 指向 42}调用时,
&p是p的地址,类型是int**,即out存p的地址,out = &p;
*out类型是int*,即*out存p的值,即p所指向的变量的地址,*out = p;
**out类型是int,即**out存p所指向的变量的值,**out = *p;C 没有引用,所以只能用
int**。
C++常用*&:
例如:
void alloc(int*& out) {out = new int(42);}int main() {int* p = nullptr;alloc(p); // 直接传 p// 现在 p 指向 42}调用时,令
&out = p,类型是int*,out是p的别名。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!













