此文章是学习《C++ Primer第5版》的笔记与心得。
C++ 重难点学习手册
-
作者:B52
-
日期:2021/07/11 - 2021/08/23
目录
1. cin >> a 作为 while 与 if 语句 condition 的分析
14. initializer_list 类型形参与列表初始化返回值
第Ⅰ部分 C++ 基础
1. cin >> a
作为 while 与 if 语句 condition 的分析
首先请阅读以下这段程序,它的功能是统计每个值连续出现了多少次:
#include <iostream>
using namespace std;
int main()
{
// currVal 是我们正在统计的数;我们将读入的新值存入 val
int currVal = 0, val = 0;
//读取第一个数, 并确保确实有数据可以处理
if (cin >> currVal) {
int cnt = 1; // 保存我们正在处理的当前值的个数
while(cin >> val) { // 读取剩余的数
if (val == currVal) // 如果值相同
++cnt; // 将 cnt 加 1
else{ // 否则,打印前一个值的个数
cout << currVal << " occurs "
<< cnt << " times " << endl;
currVal = val; // 记住新数
cnt = 1; // 重置计数器
}
}// whi1e 循环在这里结束
// 记住打印文件中最后一个值的个数
cout << currVal << " occurs "
<< cnt << " times " << endl;
} // 最外层的 if 语句在这里结束
return 0;
}
《C++ Primer》书中写道:
对于以上程序,
按照书中操作,实际输出却是:
实际上,在输入全部数据后,按下回车只能输出前三条结果,之后无论按多少个回车都不会结束程序并返回第四条结果,针对这个问题,可行的解决方案有两条:1. 输入一个非 int 型数据;2. 输入文件结束符 ^Z (Windows 中为 Ctrl+z
,UNIX 中为 Ctrl+d
)。
-
输入非 int 型数据,
while(cin >> val)
判定为输入错误,条件的布尔值为 0,退出while
循环,打印出最后一个值,结束程序。 -
输入文件结束符 ^Z 原理与上一条类似,如下:
在有的编译器上,以上输入并不能得到正确的输出,必须要通过回车将缓冲区中内容刷到设备中,然后输入 ^Z,才会识别认定为文件终止。
总结:在 while(cin >> a)
语句中,输入的回车 (换行符)只是将输入流中的内容刷到设备中,而不会被识别终止符,需要引入输入错误或者文件结束符。
2. 引用
基本定义:引用不是对象,而是一个已经存在的对象的别名。
主要特征
-
引用必须被初始化,且会绑定其初始值对象 (绑定的是对象而非初始值),一旦被定义即绑定,不能更改对象。
-
被引用类型的初始值一定是与引用类型相同的对象,且一定是左值。
int val = 1.01; // 合法,转换后val被赋值为1 int& r1 = 1.01; // 不合法,引用的初始值必须是左值 int& r2 = val; // 合法 int& r3; // 不合法,引用必须被初始化
-
任何情况 (除了
decltype
) 下使用引用,等同于使用其绑定的对象,即引用是别名。
以上的主要特征针对的是通常情况下的引用,此外还有一种特例——const 的引用
不满足其中的几条特征。
const 的引用(常量引用)
-
允许为常量引用绑定非常量的对象、字面值甚至是一个表达式。
int i = 42; const int &r1 = i; // 合法,允许常量引用绑定到一个int型对象 const int &r2 = 42; // 合法,初始值为一个int型常量 const int &r3 = r1 * 2; // 合法 int &r4 = r1 * 2; // 不合法,引用的初始值必须是左值
常量引用这一特性的具体原理见以下这个案例:
double dval = 3.1415926; const int &r1 = dval;
为了确保 r1 绑定一个整数,编译器将上述代码变成了如下形式:
const int tmp = dval; const int &r1 = tmp;
故实际上首先生成了一个整型常量 tmp,并进行了类型转换,然后将该整型常量的值作为 r1 的初始值。
基于以上原理,我们可以做一组实验来探究常量引用的部分性质:
测试代码如下:
#include <iostream> using namespace std; int main() { int val = 42; const int& r1 = val; const int& r2 = val * 2; const int& r3 = val + 2; cout << r1 << " " << r2 << " " << r3 << endl; val = 0; cout << r1 << " " << r2 << " " << r3 << endl; double dval = 3.1415926; const int& r4 = dval; const int& r5 = dval * 2; const int& r6 = dval + 2; cout << r4 << " " << r5 << " " << r6 << endl; dval = 0; cout << r4 << " " << r5 << " " << r6 << endl; return 0; }
运行结果如下:
通过修改对象
val
和dval
的值,我们可以看到,只有r1
真正绑定的是初始值对象,其他的常量引用均经过了一个中间整型常量tmp
的转换。此外,我们还应注意到,常量引用
r1
绑定了一个int型对象val
,仅仅是指不能通过r1
来改变val
的值,但可以通过其他途径 (比如val++
) 来改变。
3. 切忌混用 int 与 unsigned
特别需要注意的 unsigned
有:string::size_type
#include <iostream>
using namespace std;
int main()
{
int a = -1;
unsigned b = 10000;
if (a < b) cout << "B is bigger." << endl;
else cout << "B is not bigger than a." << endl;
return 0;
}
4. 范围 for (range for
) 语句
用于遍历给定序列的每个元素并对序列的每个值进行某种操作。
// 不改变原字符串
string str("The string is abc123def");
for (auto c : str){
if(isdigit(c))
++c;
cout << c;
}
cout << endl;
cout << str << endl;
// 改变原字符串
string s = "Keep out!";
for (auto& c : s)
c = toupper(c);
5. vector 对象动态增长的限制
vector 是 C++ 的一种特有容器,能够实现对象容量的高效增长。但其对象动态增长也有以下两点限制:
-
不能在
range for
循环中向 vector 对象添加元素; -
任何可能改变 vector 对象容量的操作,比如
push_back
,都会使 vector 的迭代器失效。
对于第一点,range for
的原理是在普通的 for 语句中用迭代器进行遍历,因而该限制可以归为第二点。
对于第二点,则需要了解 string