Chapter 1
- 包含标准库的头文件时,使用尖括号(<>)包围文件名。对于不属于标准库的头文件,则使用双引号(“”)包围
文件重定向
在测试程序时,可以在控制台(windows 下的cmd)使用文件重定向,将infile中内容当作输入,输出结果到outfile中。这两个文件都处于当前目录
$ test.exe <infile >outfile
- 点运算符(.)左侧运算对象必须是一个类类型的对象,右侧运算对象必须是该类型的一个成员名
Chapter2
- 算术类型尺寸:long long >= long >= int >= short
类型使用经验准则:
- 当知道数值不可能为负,使用无符号类型
- 使用 int 执行整数运算。在实际应用中,short 常常显得太小而 long 一般和 int 有一样的尺寸。如果数值超过了int的表示范围,选用 long long。
- 在算术表达式中不要使用 char 或 bool,只有存放字符或布尔值时才使用他们。因为类型 char 在一些机器上是有符号的,而在另一些机器上又是无符号的,进行运算特别容易出问题。如果需要使用一个不大的整数,那么明确指定它的类型是 signed char 或者 unsigned char。
- 执行浮点数运算选用 double 。因为 float 通常精度不够且双精度浮点数和单精度浮点数的计算代价相差无几。事实上,对于某些机器来说,双精度运算甚至比单精度还要快。long double提供的精度一般是没必要的,况且带来的运行时消耗也不容忽视。
类型转换:
- 非布尔类型的算术值赋给布尔类型,初始值为 0 则为 false,否则为 true。
- 布尔值赋给非布尔类型时,初始值为 false 则结果为 0,初始值为 true 则结果为1
- 浮点数赋给整数类型,进行近似处理,结果值仅保留浮点数中的小数点之前的部分
- 赋给无符号类型一个超过它表示范围的值,结果是初始值对无符号类型表示数值总数取模后的余数。例如,8 比特大小的 unsigned char 可以表示 0 至 255 区间的值,如果赋一个区间以外的值,则实际结果是对 256 取模后的余数。因此将 -1 赋给 8 比特大小的 unsigned char 所得到的结果是 255
- 赋给带符号类型一个超出表示范围的值时,结果时未定义的(undefined)。可能崩溃或继续运行或生成错乱的数据
建议:
- 应避免依赖于实现环境的行为。如果把 int 的尺寸当作确定不变的已知值,那么这样的程序就称作不可移植的(nonportable)
注意:
- 当程序的某处使用了一种算术类型的值而其实所需的另一种类型的值时,编译器会执行类型转换。
- 当一个算术表达式中既有无符号数又有 int 值时,那个 int 值就会变成无符号数。所以切勿混用带符号类型和无符号类型
字面值:
一个形如42的值被称作字面值常量(literal)。每个字面值常量都对应一种数据类型
默认情况下:
- 十进制的字面值是符号数
- 八进制和十六进制字面值可能是带符号也可能是无符号的。
- 十进制字面值是 int 、long 和 long long 中尺寸最小的那个,前提是当前的值在该类型范围内
- 八进制和十六进制字面值的类型是能容纳其数值的 int 、unsigned int 、 long 、 unsigned long 、long long 和 unsigned long long 中尺寸最小者。
- short 没有对应字面值
- 浮点型字面值是 double
- 由单引号括起来的一个字符称为 char 型字面值
- 双引号括起来的零个或多个字符构成字符串型字面值,实际为由常量字符构成的数组,编译器会在每个字符串结尾处添加一个空字符(‘\0’)
- 如果两个字符串字面值位置紧邻且仅由空格、缩进和换行符分隔,则实际是一个整体
转义序列
广义转义序列的形式为 \x 后紧跟 1 个或多个十六进制数字,或者 \ 后紧跟1个、2个或3个八进制数字。如果超过三个则只有前三个数字构成转义序列。
指定字面值的类型
通过添加前缀和后缀可以改变字面值的默认类型,建议尽量使用大写 L, 因为小写的 l 和 1 有时候很难分清
变量
变量提供一个具名的、可供程序操作的存储空间。
初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。
int units_sold{0};
花括号初始化被称为列表初始化,如果使用该初始化且初始值存在丢失信息的风险,编译器将报错。
如果定义变量没有指定初值,则变量被默认初始化,此时变量被赋予“默认值”
- 如果内置类型的变量未被显示初始化,值由定义的位置决定。
- 定义在函数体外的变量被初始化为 0
- 定义在函数体内的内置类型变量将不被初始化,值是未定义的
- 每个类各自决定其初始化对象的方法,且是否允许不经初始化就定义对象也由类自己决定。绝大多数类支持无须显示初始化而定义对象,这种类会提供一个合适的默认值。
建议初始化每一个内置类型的变量。
C++语言支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。声明使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义负责创建与名字关联的实体。
- 如果想声明一个变量而非定义它,就在变量名前添加关键字 extern,且不要显示地初始化变量。如果在函数体内部试图初始化一个由 extern关键字标记的变量将引发错误。
extern int i;
- 变量只能被定义一次,但可以被多次声明。
名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束
- 定义所有花括号之外的名字拥有全局作用域
复合类型
一条声明语句由一个基本数据类型和紧随其后的一个声明符列表组成。每个声明符命名了一个变量并指定该变量为与基本数据类型有关的某种类型。
引用为对象起了另外了一个名字,引用类型引用另外一种类型。(引用必须被初始化)
- 定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。且该绑定不会改变。
- 引用不是对象,所以不能定义引用的引用
- 引用的类型要与绑定的对象严格匹配,且只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定
指针式指向另外一种类型的复合类型
和引用不同:
- 指针本身是个对象,允许赋值和拷贝,且在指针的生命周期内可以先后指向几个不同的对象
- 指针无需在定义时赋值,且不被初始化的话,也会有个不确定的值
获取对象地址
获取某个对象的地址使用取址符(&)
int *p = &ival;
-
引用不是对象,没有实际地址,所以不能定义指向引用的指针
指针值应该应属下列四个状态之一:
-
1、指向一个对象
-
2、指向紧邻对象所占空间的下一个位置
-
3、空指针,意味着指针没有指向任何对象
-
4、无效指针,即除上述情况外的值
如果指针指向了一个对象(前提),则允许使用解引用符(*) 来访问对象
*p = 0;
cout << *p;
空指针不指向任何对象,在试图使用一个指针之前可以先检查是否为空
生成空指针:
int *p = nullptr; // C++11新标准
int *p2 = 0; // 注意不是int变量,而是字面值 0
// 先#include cstdlib
int *p3 = NULL; // 预处理变量,由预处理器管理,在编译前预处理器会将变量替换为实际值 0
void* 是一种特殊的指针类型,可存放任意对象的地址,但不能操作所指对象,因为不确定所指对象到底是什么类型。
复合类型的声明
由基本类型和修饰符完成定义,其中
int* p1, p2; // 只有p1为int型指针,p2为int型变量
因此建议写法
int *p1, p2;
指向指针的指针
int ival = 1024;
int *pi = &ival; // pi指向一个 int型的数
int **ppi = π //ppi指向一个 int型的指针
指向指针的引用
int i = 42;
int *p; // p是一个 int型指针
int *&r = p; // r是一个对指针 p的引用,从右到左读修饰符,&--引用、*--对指针引用、int--指针类型为int
r = &i; // r引用了指针,因此给 r赋值即是令 P指向 i
*r = 0; // 解引用 r得到 i,即将 i的值改为 0
const 限定符
const 对象一旦创建后就不能再改变,所以 const 对象必须初始化
- 编译器在编译过程中将用到 const 定义变量的地方都换成初始化的值,如果程序包含多个文件,则每个用到了 const 对象的文件都必须访问得到它的初始值。要做到这点就必须在每个用到变量的文件都由对该变量的定义。为了避免对同一变量的定义,const 对象被设定仅在文件内有效。当多个文件中出现同名 const 变量,等同于不同文件中分别定义了独立的变量。
某些时候 const 变量的初始值不是常量表达式且需要在文件间共享。我们不希望编译器为每个文件分别生成独立的变量,而希望该 const 变量只在一个文件中定义,且多个文件中声明并使用。
解决方法是对 const 变量不管是声明还是定义都添加 extern 关键字,这样只需定义一次即可:
// file+1.cc 定义并初始化一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
// file_1.h 头文件
extern const int bufSize; // 与file_1.cc 中定义的bufSize是同一个,extern 指明并非本文文件独有变量,定义在别处出现
const 的引用
对 const 的对象的引用成为对常量引用 (reference to const)。和普通引用不同,对常量引用不能被用作修改它所绑定的对象
const int ci = 1024;
const int &r1 = ci; // 引用及其对象都是常量
int &r2 = ci; // 错误:试图让一个非常量引用指向一个常量对象
初始化和对 const 的引用
- 在初始化常量引用时允许用任意表达式作为初始值,只要表达式的结构可以转换成引用的类型即可
double dval = 3.14;
const int &ri = dval;
编译器会将上述操作变成如下
const int temp = dval; // 由双精度浮点数生成一个临时的整型常量
const int &ri = temp; // 让ri 绑定这个临时量
临时量对象是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。
上述操作引用绑定了一个临时量而非 dval,和引用原意相悖,因此 c++ 把这种行为归为非法。
对 const 的引用可能引用一个并非 const 的对象
常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是常量不作限定。
int i = 42;
int &r1 = i;
const int &r2 = i;
r1 = 0; // r1 非常量,可修改值
r2 = 0; // 错误:r2 是常量引用,无法修改
指向常量的指针
**指向常量的指针(pointer to const)**不能用于改变其所指对象的指。要存放常量对象的地址,只能使用指向常量的指针。
// 允许令一个指向常量的指针指向一个非常量对象
double dval = 3.14;
const double *cptr;
cptr = &dval; // 可以,但不能同通过cptr改变
const指针
指针本身是对象,因此可以把指针本身定位常量。**常量指针(const pointer)**必须初始化,且一旦初始化完成,值同样不可改变。
int errNumb = 0;
int *const curErr = &errNumb; // 一直指向errNumb
const int *const pip = &errNumb // 一直指向errNumb且不能修改errNumb值
顶层const
顶层const表示对象是个常量,底层const则与指针和引用等复合类型的基本类型部分有关。
顶层const 的声明不限制变量进行拷贝,但是底层const会限制变量进行拷贝。
constexpr 和常量表达式
常量表达式(const expression)是指值不会改变并且在编译过程就能得到计算结果的表达式(字面值也是)。
c++11规定允许将变量声明为 constexpr 类型以由编译器来验证变量的值是否是一个常量表达式。
字面值类型
声明 constexpr 时用到的类型有限制,即字面值类型(literal type)。
- 字面值类型:算术类型、引用和指针等
- 非字面值类型:自定义类、IO库、string 类
指针和 constexpr
在 constexpr 声明中如果定义一个指针,限定符仅对指针有效,而对指针所指对象无关
constexpr int *np = nullptr; // np 是一个指向整数的常量指针,其值为空
处理类型
类型别名
**类型别名(type alias)**是一个名字,是某种类型的同义词。
定义类型别名:
- 使用关键字 typedef
typedef double wages; // wages 是 double 的同义词
typedef wages base, *p; // base 是 double 的同义词, p 是 double* 的同义词
- 别名声明(alias declaration)
using SI = Sales_item; // SI 是 Sales_item 的同义词
指针、常量和类型别名
注意不要错误地将类型别名替换成它原本的样子来理解句子含义
typedef char *pstring;
const pstring cstr = 0; // cstr 是指向 char 的常量指针,不能将其替换成 const char *cstr,因为替换后意思是指向指向 const char 的指针
const pstring *ps; // ps 是一个指针,对象是指向 char 的常量指针
auto 类型说明符
c++11 引入 auto 类型说明符,能让编译器替我们去分析表达式所属的类型。auto 定义的变量必须有初始值。
复合类型、常量和 auto
auto 一般会忽略掉顶层 const 而底层 const 会保留下来
int i = 0, &r = i;
auto a = r; // a 是一个整数
const int ci = i, &cr = ci;
auto b = ci; // b 是一个整数(会忽略顶层 const 特性)
auto c = cr; // c 是一个整数(cr 是 ci 的别名, ci 本身是一个顶层 const )
auto d = &i; // d 是一个整型指针
auto e = &ci; // e 是一个指向整数常量的指针(对常量对象取地址是一种底层 const )
const auto f = ci; // f 是 const int
auto &g = ci; // g 是一个整型常量引用
auto &h = 42; // 错误:不能为非常量引用绑定字面值
const auto &j = 42; // 正确:可以为常量引用绑定字面值
设置一个类型为 atuo 的引用时,初始值中的顶层常量属性仍然保留
若在一条语句中定义多个 auto 变量,初始值必须是同一个类型
auto k = ci, &l = i; // auto 可当作 int
auto &m = ci, *p = &ci; // m 是对整型常量的引用,p 是指向整型常量的指针, auto 可当作 const int
auto &n = i, *p2 = &ci; // 错误:i 是 int 而 &ci 是 const int
decltype 类型指示符
该类型说明符选择并返回操作数的数据类型。在这个过程中,编译器分析表达式并得到它的类型,而不实际计算表达式的值
decltype(f()) sum = x; // sum 的类型就是函数 f 的返回类型
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x 的类型是 const int
decltype(cj) y = x; // y 的类型是 const int&, y 绑定到变量 x
decltype(cj) z; // 错误: z 是一个引用,必须初始化
引用只有在 decltype 处会被当作引用,其他时候都被当作其引用对象的同义词
int i = 42, *p = &i, &r = i;
decltype(r+0) b; // 正确:加法结果是 int
decltype(*p) c; // 错误: c 是 int &,必须初始化。表达式是解引用操作,得到引用类型
// decltype 的表达式如果加上了括号的变量,结果是引用
decltype((i)) d; // 错误:d是 int &,必须初始化
decltype(i) e; // 正确:e 是一个 int