参考资料:
const
限定符(P53)由于 const
对象在创建后不能修改,所以其必须初始化。
const
对象的常量特征仅在执行改变该变量的操作时才会发生作用。
const
对象默认仅在文件内有效。如果想在多个文件之间共享 const
对象,必须在变量定义前加 extern
关键字。
这部分涉及到多文件的内容,待补充!!!
const
的引用(P54)把引用绑定到 const
对象上,称为常量引用:
const int ci = 1;
const int &r = ci;
const
的引用初始化常量引用时,可以使用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型:
const int &r1 = 1;
double d = 3.14;
const int &r2 = d;
一般的引用要求类型严格一致
为什么常量引用具有上述特殊性呢?实际上,常量引用在绑定时引入了临时量:
const int temp = 1;
const int &r1 = temp;
double d = 3.14;
const int temp = d;
const int &r2 = temp;
需要说明的是,当常量引用满足普通引用的条件时,不会引入临时量。不难看出,常量引用在上述情况中实质上是对临时量的引用。这也解释了普通的引用为什么不能通过临时量来实现跨类型引用,因为我们假如通过普通引用修改绑定对象,我们实质上将会修改临时量,而不会修改原来的变量,这显然与我们的初衷相违背,于是 C++ 将这种行为规定为非法行为。
const
(P56)double pi = 3.14;
// 常量指针
const double *p = π
// 指针常量
double *const p = *pi;
const
(P57)以指针为例,顶层 const
表示指针本身是常量,底层 const
表示指针所指的对象的是常量。
对顶层 const
执行拷贝操作,没有任何限制:
int i = 1;
int *const p1 = &i;
int *p2 = p1; // 正确
对底层 const
执行拷贝操作,则必须保证拷入对象同时具有底层 const
属性:
int i = 1;
const int *const p1 = &i;
int *p2 = p1; // 错误,因为p2不具备底层const属性
const int *p3 = p1; // 正确
constexpr
和常量表达式(P58)常量表达式指值不会改变且在编译过程中能得到结果的表达式。字面值属于常量表达式,用常量表达式初始化的 const
对象也是常量表达式。
一个对象或表达式是否是常量表达式是由其数据类型和初始值共同决定的:
const int max_file = 20; // 是
const int limit = max_file+1; // 是
int staff_size = 27; // 不是
const int sz = get_size(); // 不是,因为编译时无法计算出结果
在一个复杂程序中,我们很难分辨一个初始值是否为常量表达式。C++11 规定,可以通过将变量声明为 constexpr
来使编译器进行常量表达式检查。声明为 constexpr
的变量一定是常量,且必须用常量表达式初始化。
能用 constexpr
修饰的类型称为字面值类型,算术类型、引用和指针属于字面值类型,自定义类、IO 库、string 等类型不属于字面值类型。
用 constexpr
修饰的指针必须是 nullptr
或者具有固定地址的对象(定义在函数外的变量、静态变量等)
constexpr
const int *p1 = nullptr; // p1是常量指针
constexpr int *p2 = nullptr; // p2是指针常量
类型别名和类型等价:
typedef double wages;
typedef wages base, *p; // p是double*的同义词
using wages = double; // 新标准
typedef char *pstring;
const pstring cstr1 = 0; // cstr1是指针常量
const char *cstr2 = 0; // cstr2是常量指针
个人感觉可以理解为:为复合类型起别名后,用这个别名声明变量时,该复合类型就成了这条声明语句的基本类型。
auto
类型说明符(P61)auto
类型说明符让编译器通过初始值推断变量的类型,因此 auto
定义的变量必须初始化。
由于一条声明语句只能有一个基本数据类型,故语句中所有变量的初始基本数据类型必须一样:
auto x1 = 0, *x2 = &x1; // 正确
auto y1 = 0, y2 = 3.14; // 错误
auto
当使用引用变量初始化auto
变量时,编译器以被引用对象的类型作为 auto
的类型:
int i = 0, &r = i;
auto a = r; // a为int型变量
auto
一般会忽略顶层 const
,但底层 const
会保留下来:
const int ci = 0, &cr = ci;
auto b = ci; // b为int型变量,ci的顶层const属性被忽略
auto p = &ci; // p为指向const int的指针
如果我们希望 auto
变量是顶层 const
,需要明确指出:
const auto x = ci; // x为const int
还可以将引用的类型设为 auto
,此时的初始化规则同其他引用的初始化规则。
decltype
类型指示符(P62)decltype
的作用是返回操作数的数据类型,编译器分析表达式的类型但并不计算实际的值。与 auto
不同的是,如果 decltype
使用的表达式是一个变量,则 decltype
会返回该变量的类型(包括顶层 const
和引用):
int i = 0, &r = i;
decltype(r) c; // 错误,c为引用类型,必须初始化
引用变量是被引用对象的代名词,只有在
decltype
处是例外。
decltype
和引用如果 decltype
使用的是表达式而非变量,则 decltype
返回表达式结果对应的类型:
int i = 0;
decltype(i+1) c; // 正确,c为int型变量
特别地,如果表达式的结果为左值,则
decltype
返回引用类型。
如果表达式是解引用操作,则 decltype
将得到引用类型:
int i = 0, *p = &i;
decltype(*p) c; // 错误,c为引用变量
这样的规则是符合逻辑的,因为解引用可以用来操作原变量,所以
decltype
的结果应该是引用类型。
如果 decltype
所使用的表达式是变量名加上一对括号,结果将是引用:
decltype((i)) c; // 错误,c为引用变量
这里涉及到“左值”的概念,待补充
C++ 允许用户以类的形式自定义数据结构。由于类体后面可以紧跟变量名(不推荐这么做),所以类定义后面必须加分号。
C++11 规定,可以为数据成员提供一个类内初始值,在创建对象是,类内初始值将用于初始化数据成员,没有初始值的成员被默认初始化。类内初始值可以放在花括号里、等号右边,不能放在圆括号里。
类定义通常被定义在头文件中,且该头文件的名字应该和类一样。
头文件可能出现多次包含的情况。
预处理器是用来确保头文件多次包含仍能安全工作的常用技术。预处理器在编译之前运行。预处理器看到 #include
标记时会用头文件的内容代替 #include
。
头文件保护符依赖于处理变量。预处理变量(NULL
就是一个预处理变量)有两种状态:已定义和未定义。define
指令把把一个名字定义为预处理变量;#ifdef
在变量已定义时为真,#ifndef
在变量未定义时为真,一旦检查结果为真,则执行到 #endif
为止。
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include
struct Sales_data {\* ... *\};
#endif
如果上述头文件已经被包含,则 ifndef
为假,编译器将忽略 ifndef
到 endif
之间的内容。