回顾
将复习 C++ Primer 从开始到 2.4节(不包括)前的内容。
注意,该文档仅仅简单提及要点,真正的复习需要仔细看书,并将遗漏的知识点不足。
弱类型
C++ 是弱类型的语言,弱类型即允许隐式类型转换
在 C++ 中,我们允许这样的代码出现
double d = 0.0;
int i = d; // 发生隐式类型转换
该代码将一个 double
赋给 int
,此处发生 double
到 int
的自动转换。
与弱类型相反,在强类型的编程语言中,任何隐式转换都不允许发生,因此不允许两个不同类型的变量相互赋值。
请仔细阅读 C++ Primer 第 33 页开头部分(不同类型隐式转换的过程)。
有无符号类型的隐式转换
有符号类型与无符号类型同时出现的表达式中,有符号类型隐式转换为无符号类型。
auto i = -1 + unsigned(0);
// 结果是 unsigned int 这个类型能表示的最大值
初始化
拷贝初始化
-
拷贝初始化:
double i = 0.0
-
直接初始化:
double i(0.0)
- 列表初始化:
double i{0.0}
当存在信息丢失风险的时候,列表初始化会报错!
int i = 0.0; // ok
int i1(0.0); // ok
int i2{0.0}; // wrong
若一个变量没有初始化,例如 int i;
,那它的值是未定义的,这种情况比较危险,因此推荐每次定义变量都初始化。
Scope Rules(作用域规则)
两类作用域
-
全局作用域
-
块作用域
全局变量都是处在全局作用域中。
作用域嵌套规则
- 内层作用域有权利访问外层作用域中的变量
- 内存作用域中的同名变量,在名称查找中覆盖外层中的变量
使用作用域运算符 ::
可以强制指定其它作用域中的名字。
#include <iostream>
int a = 0;
int main() {
int a = 1;
std::cout << a << ' ' << ::a << std::endl;
// Prints "1 0"
}
复合类型
引用
引用定义了一个别名。
int a = 0;
int &ra = a;
这里的 ra
仅仅是 a
的别名。可以理解为,任何使用 ra
的情况都等价于使用 a
的情况(不完全准确)。因此,引用本身是不占用内存空间的,即引用类型不是对象。
只能绑定对象
一个引用只能绑定对象,即只能绑定到占用内存空间的变量、值。
int a = 0;
int &ra = a; // ok
int &rx = 0; // wrong, 0 不是对象
int &ra1 = ra; // ok, 这里的 ra 只是 a 的别名
引用不是对象
再次强调,引用不是对象。一个指针需要指向一个对象,即一个内存空间。而由于引用不是对象,所以指针不能指向一个引用。
int &*p = ra; // wrong
ra
沿用之前定义。这段代码中,p
是个指向引用的指针,这是不允许的。
指针
本身是对象,指向一个对象。
指针的概念无需赘述,在此介绍一些特殊指针。
空指针
nullptr
是空指针字面值常量,任何类型的指针都可用它赋值来置空。
0
、NULL
也可以当作空指针使用,但不推荐。
int *p = nullptr; // recommended
p = 0; // not recommended
// 需要先 #include <cstdlib>
p = NULL; // not recommended
注意
将 int
赋值给指针是错误的,即使它恰好为 0。
int zero = 0;
int *p = zero; // wrong
初始化
指针不初始化会指向某个随机的地址,那个地址上的数据或许根本不存在,因此建议指针必须初始化。
Void * 指针
该指针可以赋为任意类型的指针。
int *p = nullptr;
double *p1 = nullptr;
void *any = p;
any = p1;
这样做可以擦除类型,但不建议这样使用。
理解复合类型的声明
int* p, i; // p 是指针,i 是 int。
int *p1, **pp; // p1 是指针,pp 是指向指针的指针
从内向外理解类型
int *&r = ...; // “指针”的引用
int **&r1 = ...; // “指向指针的指针”的引用