CHARLORÉ / AQUATIC MEMORY

Charloré

C++ Primer Fifth Edition notes(3)

2641 字
13 分钟
C++ Primer Fifth Edition notes(3)

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 = 3
int 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 0
0 0
42 0
  1. 如果内层重新定义同名对象,外层名字会被隐藏。

  2. 空的作用域限定符 ::name 可以显式访问同名全局对象。

代码位置reused 表示的对象输出
定义局部 reused 之前全局 reused42
定义局部 reused 之后局部 reused0
使用 ::reused全局 reused42

2.3 复合类型#

6 什么是复合类型?#

  1. 复合类型以其他类型为基础定义,必须建立在另一种类型之上。

例如:引用和指针,不能只说引用或指针,还需要说明它与什么类型有关。

  1. 一条声明由基本类型和声明符组成,声明符不仅包含名字,还可能通过 &、* 等符号决定每个名字最终具有的完整类型。
声明基本类型声明符对象的完整类型
int value;intvalueint
int &ref = value;int&refint 的引用
int *ptr = &value;int*ptr指向 int 的指针

7 什么是引用?#

  1. 引用不是一个独立对象,而是为一个已经存在的对象提供另一个名字。

例如:

int ival = 1024;
int &refVal = ival;

ival与&refVal指向同一个对象,而区别于普通变量int value = ival;创建两个对象。

  1. 引用定义时必须绑定对象,绑定后不能改绑。

例如:

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 引用具有更宽松的绑定规则,因此编译通过。

  1. 对引用进行读取、赋值和运算时,实际操作的是引用绑定的对象。

例如:

int value = 10;
int &ref = value;
ref += 5;

等价于:value += 5;

  1. 引用绑定时通常要求类型完全一致。

例如:

double dval = 3.14;
int &refVal = dval;

refVal 要求绑定一个 int 对象,但 dval 是 double 对象,因此会编译错误。

8 什么是指针?#

  1. 指针本身是存放地址的变量对象,因此可以改变指向,可以被复制和赋值,不必在定义时初始化。

例如:

double dval;
double *pd = &dval;
double *pd2 = pd;

这会复制指针保存的地址,两个指针可能指向同一个对象,但仍然是两个独立的指针对象。

对比引用指针
是否为对象否是
定义时是否必须初始化是语法上不必须
能否改变绑定或指向不能可以
能否表示“不指向对象”普通引用不能可以使用 nullptr
保存的内容是原对象的别名对象的地址
  1. 指针使用取地址运算符 &保存对象的地址,而非对象的值。

例如:

int ival = 42;
int *p = &ival;
std::cout << *p; // 输出 42

同一个 & 在不同位置含义不同,在声明符中表示引用,在指针初始化中表示取地址运算符。

同一个p在不同位置含义不同,在声明符中p表示保存地址,而 *p 表示根据这个地址访问对象,得到对象的值。

同一个*在不同语句含义不同,int *p 中的 * 声明 p 是指针;*p 中的 * 是解引用运算符,表示访问指针所指的对象。

  1. 空指针不指向任何对象,指针一般需要初始化为空指针。
int *p1 = nullptr; // 常用
int *p2 = 0;
// 使用 NULL 需要包含 <cstdlib>
int *p3 = NULL;

9 如何修改指针?#

  1. 修改指针变量自身的值

例如:

int i = 42;
int *pi = 0;
int *pi2 = &i;
int *pi3; // 此时 pi3 尚未初始化
pi3 = pi2; // pi3 和 pi2 现在都指向 i
pi2 = 0; // pi2 不再指向对象

pi3 = pi2:读取 pi2 变量内存中的值(即 i 的地址),将其拷贝写入 pi3 变量的内存中。

结果:pi3 和 pi2 两个指针变量内部存储的地址相同,均指向 i。

随后的 pi2 = 0; 仅将 pi2 自身的内存值修改为空指针,不影响 pi3,更不影响 i 所在的内存空间。

  1. 修改指针指向的值

C++ 中,赋值操作修改的永远是左操作数(左值)。

例如:

pi = &i; // 左边是 pi:改变 pi 保存的地址
*pi = 0; // 左边是 *pi:把 pi 所指的 i 改为 0

pi = &i:左值是 pi(指针变量本身);修改 pi 的内存,写入 i 的地址。

*pi = 0:左值是 *pi(解引用表达式); *p 表示根据这个地址访问对象,再定位到该地址对应的内存空间(即 i),将其值修改为 0,pi 自身存储的地址不变。

10 如何使用指针?#

  1. 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 通常判断它是否持有对象。

  1. == 和 != 比较两个指针

例如:

int ival = 1024;
int *pi = 0;
int *pi2 = &ival;
int *pi3 = pi2;
pi == pi2; // false
pi2 == pi3; // true
pi2 != pi3; // false

使用未初始化指针进行比较,行为未定义。

  1. 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指向的对象是什么类型,也就无法确定该如何访问它。

  1. * 只属于它所在的声明符

例如:

int* p1, p2;:基本类型仍是 int, * 只修饰p1,所以 p1 是指向 int 的指针,p2则是 int 对象。

如果两个变量都要是指针,就要分别写出 *,如int *p1, *p2;

  1. ** 指向指针的指针与*&指向指针的引用

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 的别名。

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

C++ Primer Fifth Edition notes(3)
https://charlore.cn/posts/c++-primer-fifth-edition-notes-3/
作者
路人蛃
发布于
2026-09-27
许可协议
CC BY-NC-SA 4.0

评论区