docs(book): 添加现代 C++教程及相关代码
- 新增现代 C++ 教程的 Preface 章节,包括英文和中文版本 - 添加 C++ Primer 练习代码 - 新增 Learn C++ 教程的 C++ 开发简介章节 - 添加头文件解析文档 - 更新 mkdocs.yml,包含新教程的目录结构 - 修改项目设置,使用 Python 3.10环境
This commit is contained in:
parent
b5160be045
commit
4108f6c42d
32
docs/Cpp-Notes-main/.gitignore
vendored
Normal file
32
docs/Cpp-Notes-main/.gitignore
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
# Prerequisites
|
||||
*.d
|
||||
|
||||
# Compiled Object files
|
||||
*.slo
|
||||
*.lo
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
|
||||
# Compiled Dynamic libraries
|
||||
*.so
|
||||
*.dylib
|
||||
*.dll
|
||||
|
||||
# Fortran module files
|
||||
*.mod
|
||||
*.smod
|
||||
|
||||
# Compiled Static libraries
|
||||
*.lai
|
||||
*.la
|
||||
*.a
|
||||
*.lib
|
||||
|
||||
# Executables
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
476
docs/Cpp-Notes-main/Cpp-Primer-exercise/1.开始.md
Normal file
476
docs/Cpp-Notes-main/Cpp-Primer-exercise/1.开始.md
Normal file
@ -0,0 +1,476 @@
|
||||
---
|
||||
sort: 1
|
||||
---
|
||||
|
||||
# 开始
|
||||
|
||||
## 练习1.1
|
||||
查阅你使用的编译器的文档,确定它所使用的文件名约定。编译并运行第2页的main程序。
|
||||
|
||||
解:
|
||||
- ``g++ 1.1.cpp -o main``
|
||||
- ``./main``
|
||||
|
||||
## 练习1.2
|
||||
改写程序,让它返回-1。返回值-1通常被当做程序错误的标识。重新编译并运行你的程序,观察你的系统如何处理main返回的错误标识。
|
||||
|
||||
解:
|
||||
- 在ubuntu下,使用g++,返回-1,``./main``没有发现任何异常。
|
||||
- ``echo $?``,返回255。
|
||||
|
||||
## 练习1.3
|
||||
编写程序,在标准输出上打印Hello, World。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "Hello,World" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习1.4
|
||||
我们的程序使用加法运算符`+`来将两个数相加。编写程序使用乘法运算符`*`,来打印两个数的积。
|
||||
|
||||
解:
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Enter two numbers" << std::endl;
|
||||
int v1 = 0, v2 = 0;
|
||||
std::cin>> v1 >> v2;
|
||||
std::cout << "The product of " << v1 << " and " << v2 << " is "<< v1 * v2 << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习1.5
|
||||
我们将所有的输出操作放在一条很长的语句中,重写程序,将每个运算对象的打印操作放在一条独立的语句中。
|
||||
|
||||
解:
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "Enter two numbers" << std::endl;
|
||||
int v1 = 0, v2 = 0;
|
||||
std::cin >> v1 >> v2;
|
||||
std::cout << "The product of ";
|
||||
std::cout << v1;
|
||||
std::cout << " and ";
|
||||
std::cout << v2;
|
||||
std::cout << " is ";
|
||||
std::cout << v1 * v2;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习1.6
|
||||
解释下面程序片段是否合法。
|
||||
```cpp
|
||||
std::cout << "The sum of " << v1;
|
||||
<< " and " << v2;
|
||||
<< " is " << v1 + v2 << std::endl;
|
||||
```
|
||||
如果程序是合法的,它的输出是什么?如果程序不合法,原因何在?应该如何修正?
|
||||
|
||||
解:
|
||||
|
||||
程序不合法,有多余的分号,修改如下:
|
||||
```cpp
|
||||
std::cout << "The sum of " << v1
|
||||
<< " and " << v2
|
||||
<< " is " << v1 + v2 << std::endl;
|
||||
```
|
||||
|
||||
## 练习1.7
|
||||
编译一个包含不正确的嵌套注释的程序,观察编译器返回的错误信息。
|
||||
|
||||
解:
|
||||
```cpp
|
||||
/* 正常注释 /* 嵌套注释 */ 正常注释*/
|
||||
```
|
||||
错误信息:
|
||||
```
|
||||
/* 正常注释 /* 嵌套注释 */ 正常注释*/
|
||||
^
|
||||
ch1.cpp:97:37: error: stray ‘\255’ in program
|
||||
ch1.cpp:97:37: error: stray ‘\243’ in program
|
||||
ch1.cpp:97:37: error: stray ‘\345’ in program
|
||||
ch1.cpp:97:37: error: stray ‘\270’ in program
|
||||
ch1.cpp:97:37: error: stray ‘\270’ in program
|
||||
ch1.cpp:97:37: error: stray ‘\346’ in program
|
||||
ch1.cpp:97:37: error: stray ‘\263’ in program
|
||||
ch1.cpp:97:37: error: stray ‘\250’ in program
|
||||
ch1.cpp:97:37: error: stray ‘\351’ in program
|
||||
ch1.cpp:97:37: error: stray ‘\207’ in program
|
||||
ch1.cpp:97:37: error: stray ‘\212’ in program
|
||||
ch1.cpp: In function ‘int main()’:
|
||||
ch1.cpp:97:50: error: expected primary-expression before ‘/’ token
|
||||
/* 正常注释 /* 嵌套注释 */ 正常注释*/
|
||||
^
|
||||
ch1.cpp:98:5: error: expected primary-expression before ‘return’
|
||||
return 0;
|
||||
^
|
||||
```
|
||||
|
||||
## 练习1.8
|
||||
指出下列哪些输出语句是合法的(如果有的话):
|
||||
```cpp
|
||||
std::cout << "/*";
|
||||
std::cout << "*/";
|
||||
std::cout << /* "*/" */;
|
||||
std::cout << /* "*/" /* "/*" */;
|
||||
```
|
||||
预测编译这些语句会产生什么样的结果,实际编译这些语句来验证你的答案(编写一个小程序,每次将上述一条语句作为其主体),改正每个编译错误。
|
||||
|
||||
解:
|
||||
|
||||
只有第三句编译出错,改成如下即可:
|
||||
```cpp
|
||||
std::cout << /* "*/" */";
|
||||
```
|
||||
第四句等价于输出 `" /* "`。
|
||||
|
||||
## 练习1.9
|
||||
|
||||
编写程序,使用`while`循环将50到100整数相加。
|
||||
|
||||
解:
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int i = 50, sum = 0;
|
||||
while (i <= 100) {
|
||||
sum += i;
|
||||
i += 1;
|
||||
}
|
||||
std::cout << "sum is " << sum << std::endl;
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
## 练习1.10
|
||||
除了`++`运算符将运算对象的值增加1之外,还有一个递减运算符`--`实现将值减少1.编写程序与,使用递减运算符在循环中按递减顺序打印出10到0之间的整数。
|
||||
|
||||
解:
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int i = 10;
|
||||
while (i >= 0) {
|
||||
std::cout << i << std::endl;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 练习1.11
|
||||
编写程序,提示用户输入两个整数,打印出这两个整数所指定的范围内的所有整数。
|
||||
|
||||
解:
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int temp = 0;
|
||||
std::cout << "Please enter two numbers"<< std::endl;
|
||||
int v1 = 0, v2 = 0;
|
||||
std::cin >> v1 >> v2;
|
||||
// 若v1 > v2,则调换顺序
|
||||
if (v1 > v2) {
|
||||
temp = v2;
|
||||
v2 = v1;
|
||||
v1 = temp;
|
||||
}
|
||||
|
||||
while (v1 <= v2){
|
||||
std::cout << v1 << std::endl;
|
||||
v1 += 1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 练习1.12
|
||||
|
||||
下面的for循环完成了什么功能?sum的终值是多少?
|
||||
```cpp
|
||||
int sum = 0;
|
||||
for (int i = -100; i <= 100; ++i)
|
||||
sum += i;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
从-100加到100,sum的终值是0。
|
||||
|
||||
## 练习1.13
|
||||
使用for循环重做1.4.1节中的所有练习(练习1.9到1.11)。
|
||||
|
||||
解:
|
||||
|
||||
### 练习1.9
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
// 修改1.9
|
||||
int main() {
|
||||
int sum = 0;
|
||||
for (int i = 50; i <= 100; i++) {
|
||||
sum += i;
|
||||
}
|
||||
std::cout << "sum is " << sum << std::endl;
|
||||
}
|
||||
```
|
||||
|
||||
### 练习1.10
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
for (int i = 10; i >= 0; i--) {
|
||||
std::cout << i << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
### 练习1.11
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int temp = 0;
|
||||
std::cout << "Please enter two numbers"<< std::endl;
|
||||
int v1 = 0, v2 = 0;
|
||||
std::cin >> v1 >> v2;
|
||||
// 若v1 > v2,则调换顺序
|
||||
if (v1 > v2) {
|
||||
temp = v2;
|
||||
v2 = v1;
|
||||
v1 = temp;
|
||||
}
|
||||
|
||||
for (;v1 <= v2;v1++){
|
||||
std::cout << v1 << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 练习1.14
|
||||
对比for循环和while循环,两种形式的优缺点各是什么?
|
||||
|
||||
解:
|
||||
- <https://stackoverflow.com/questions/2950931/for-vs-while-in-c-programming>
|
||||
-
|
||||
```
|
||||
The main difference between the `for`'s and the `while`'s is a matter of pragmatics:
|
||||
we usually use `for` when there is a known number of iterations,
|
||||
and use `while` constructs when the number of iterations in not known in advance.
|
||||
The `while` vs `do ... while` issue is also of pragmatics,
|
||||
the second executes the instructions once at start,
|
||||
and afterwards it behaves just like the simple `while`.
|
||||
```
|
||||
|
||||
|
||||
## 练习1.15
|
||||
编写程序,包含第14页“再探编译”中讨论的常见错误。熟悉编译器生成的错误信息。
|
||||
|
||||
解:
|
||||
|
||||
编译器可以检查出的错误有:
|
||||
- 语法错误
|
||||
- 类型错误
|
||||
- 声明错误
|
||||
|
||||
## 练习1.16
|
||||
编写程序,从cin读取一组数,输出其和。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int sum = 0, value = 0;
|
||||
while(std::cin >> value) {
|
||||
sum += value;
|
||||
}
|
||||
std::cout << "sum is " << sum << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 练习1.17
|
||||
如果输入的所有值都是相等的,本节的程序会输出什么?如果没有重复值,输出又会是怎样的?
|
||||
|
||||
|
||||
## 练习1.18
|
||||
编译并运行本节的程序,给它输入全都相等的值。再次运行程序,输入没有重复的值。
|
||||
|
||||
解:
|
||||

|
||||
|
||||
|
||||

|
||||
|
||||
## 练习1.19
|
||||
修改你为1.4.1节练习1.11(第11页)所编写的程序(打印一个范围内的数),使其能处理用户输入的第一个数比第二个数小的情况。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
int start = 0, end = 0;
|
||||
std::cout << "Please input two num: ";
|
||||
std::cin >> start >> end;
|
||||
if (start <= end) {
|
||||
while (start <= end){
|
||||
std::cout << start << " ";
|
||||
++start;
|
||||
}
|
||||
std::cout << std::endl;
|
||||
}
|
||||
else{
|
||||
std::cout << "start should be smaller than end !!!";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 练习1.20
|
||||
在网站http://www.informit.com/title/032174113 上,第1章的代码目录包含了头文件 Sales_item.h。将它拷贝到你自己的工作目录中。用它编写一个程序,读取一组书籍销售记录,将每条记录打印到标准输出上。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include "Sales_item.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
for (Sales_item item; std::cin >> item; std::cout << item << std::endl);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
命令:
|
||||
```
|
||||
./main < data/add_item
|
||||
```
|
||||
|
||||
输出:
|
||||
```
|
||||
0-201-78345-X 3 60 20
|
||||
0-201-78345-X 2 50 25
|
||||
```
|
||||
|
||||
## 练习1.21
|
||||
编写程序,读取两个 ISBN 相同的 Sales_item 对象,输出他们的和。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include "Sales_item.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
Sales_item item_1;
|
||||
Sales_item item_2;
|
||||
std::cin >> item_1;
|
||||
std::cout << item_1 << std::endl;
|
||||
std::cin >> item_2;
|
||||
std::cout << item_2 << std::endl;
|
||||
std::cout << "sum of sale items: " << item_1 + item_2 << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
命令:
|
||||
```
|
||||
./main < data/add_item
|
||||
```
|
||||
|
||||
输出:
|
||||
```
|
||||
0-201-78345-X 3 60 20
|
||||
0-201-78345-X 2 50 25
|
||||
sum of sale items: 0-201-78345-X 5 110 22
|
||||
```
|
||||
|
||||
## 练习1.22
|
||||
编写程序,读取多个具有相同 ISBN 的销售记录,输出所有记录的和。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include "Sales_item.h"
|
||||
|
||||
int main() {
|
||||
Sales_item sum_item;
|
||||
|
||||
for (Sales_item item; std::cin >> item; std::cout << item << std::endl) {
|
||||
sum_item += item;
|
||||
}
|
||||
|
||||
std::cout << "sum is " << sum_item << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 练习1.23
|
||||
编写程序,读取多条销售记录,并统计每个 ISBN(每本书)有几条销售记录。
|
||||
|
||||
## 练习1.24
|
||||
输入表示多个 ISBN 的多条销售记录来测试上一个程序,每个 ISBN 的记录应该聚在一起。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include "Sales_item.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
Sales_item total;
|
||||
if (std::cin >> total){
|
||||
Sales_item trans;
|
||||
while (std::cin >> trans){
|
||||
if (total.isbn() == trans.isbn()) {
|
||||
total += trans;
|
||||
}
|
||||
else {
|
||||
std::cout << total << std::endl;
|
||||
total = trans;
|
||||
}
|
||||
}
|
||||
std::cout << total << std::endl;
|
||||
}
|
||||
else {
|
||||
std::cerr << "No data?!" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
```
|
||||
## 练习1.25
|
||||
借助网站上的`Sales_item.h`头文件,编译并运行本节给出的书店程序。
|
||||
774
docs/Cpp-Notes-main/Cpp-Primer-exercise/2.变量和基本类型.md
Normal file
774
docs/Cpp-Notes-main/Cpp-Primer-exercise/2.变量和基本类型.md
Normal file
@ -0,0 +1,774 @@
|
||||
---
|
||||
sort: 2
|
||||
---
|
||||
|
||||
# 变量和基本类型
|
||||
|
||||
## 练习2.1
|
||||
类型 int、long、long long 和 short 的区别是什么?无符号类型和带符号类型的区别是什么?float 和 double的区别是什么?
|
||||
|
||||
解:
|
||||
|
||||
在C++中,int、long、long long和short都属于整型,区别是C++规定的尺寸的最小值(即该类型在内存中所占的比特数)不同,其中, short 和 int 至少16位,long 至少32位,long long 至少64位。 C++标准允许不同的编译器赋予这些类型更大的尺寸。某一类型栈的比特数不同,它所能表示的数据范围也不一样。
|
||||
|
||||
大多数整数都能划分为无符号类型和带符号类型,在无符号类型中所有比特都用来存储数值,但只能表示大于等于0的值,带符号类型则可以表示正数、负数或0.
|
||||
|
||||
float 和 double 分别是单精度浮点数和双精度浮点数,区别主要是在内存中所占的比特数不同,以及默认规定的有效位数不同。
|
||||
|
||||
## 练习2.2
|
||||
计算按揭贷款时,对于利率、本金和付款分别应选择何种数据类型?说明你的理由。
|
||||
|
||||
解:
|
||||
|
||||
在实际应用中,利率、本金和付款既有可能是整数,也有可能是普通的实数。因此应该选择一种浮点类型来表示。在三种可供选择的浮点类型float、double和long double中double和float的计算代价比较接近且表示范围更广,long double的计算代价则相对较大,一般情况下没有选择的必要。综合以上分析,选择 double 是比较恰当的。
|
||||
|
||||
## 练习2.3
|
||||
读程序写结果。
|
||||
```cpp
|
||||
unsigned u = 10, u2 = 42;
|
||||
std::cout << u2 - u << std::endl;
|
||||
std::cout << u - u2 << std::endl;
|
||||
int i = 10, i2 = 42;
|
||||
std::cout << i2 - i << std::endl;
|
||||
std::cout << i - i2 << std::endl;
|
||||
std::cout << i - u << std::endl;
|
||||
std::cout << u - i << std::endl;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
输出:
|
||||
```
|
||||
32
|
||||
4294967264
|
||||
32
|
||||
-32
|
||||
0
|
||||
0
|
||||
```
|
||||
|
||||
0
|
||||
u和u2都是无符号整数,因此u2-u得到了正确的结果(42-10=32);u-u2也能正确计算,但是因为直接计算的结果是-32,所以在表示为无符号整数时自动加上模,在作者的编译环境中int占32位,因此加模的结果是 4294967264。
|
||||
|
||||
i和i2 都是带符号整数,因此中间两个式子的结果比较直观,42-10=32,10-42=-32。
|
||||
|
||||
在最后两个式子中,u和i分别是无符号整数和带符号整数,计算时编译器先把带符号数转换为无符号数,幸运的是,i本身是一个正数,因此转换后不会出现异常情况,两个式子的计算结果都是0。
|
||||
|
||||
不过需要提醒读者注意的是,一般情况下请不要在同一个表达式中混合使用无符号类型和带符号类型。因为计算前带符号类型会自动转换成无符号类型,当带符号类型取值为负时就会出现异常结果。
|
||||
|
||||
|
||||
## 练习2.4
|
||||
编写程序检查你的估计是否正确,如果不正确,请仔细研读本节直到弄明白问题所在。
|
||||
|
||||
## 练习2.5
|
||||
指出下述字面值的数据类型并说明每一组内几种字面值的区别:
|
||||
```
|
||||
(a) 'a', L'a', "a", L"a"
|
||||
(b) 10, 10u, 10L, 10uL, 012, 0xC
|
||||
(c) 3.14, 3.14f, 3.14L
|
||||
(d) 10, 10u, 10., 10e-2
|
||||
```
|
||||
|
||||
解:
|
||||
- (a): 字符字面值,宽字符字面值,字符串字面值,宽字符串字面值。
|
||||
- (b): 十进制整型,十进制无符号整型,十进制长整型,十进制无符号长整型, 八进制整型,十六进制整型。
|
||||
- (c): double, float, long double
|
||||
- (d): 十进制整型,十进制无符号整型,double, double
|
||||
|
||||
## 练习2.6
|
||||
下面两组定义是否有区别,如果有,请叙述之:
|
||||
```cpp
|
||||
int month = 9, day = 7;
|
||||
int month = 09, day = 07;
|
||||
```
|
||||
解:
|
||||
|
||||
第一行定义的是十进制的整型,第二行定义的是八进制的整型。但是month变量有误,八进制不能直接写9。
|
||||
|
||||
## 练习2.7
|
||||
下述字面值表示何种含义?它们各自的数据类型是什么?
|
||||
```cpp
|
||||
(a) "Who goes with F\145rgus?\012"
|
||||
(b) 3.14e1L
|
||||
(c) 1024f
|
||||
(d) 3.14L
|
||||
```
|
||||
|
||||
解:
|
||||
- (a)是一个字符串,包含两个转义字符,其中\145表示字符e,\012 表示一个换行符,因此该字符串的输出结果是Who goes with Fergus?
|
||||
- (b)是一个科学计数法表示的扩展精度浮点数,大小为3.14*10=31.4。
|
||||
- (c)试图表示一个单精度浮点数,但是该形式在某些编译器中将报错,因为后缀f直接跟在了整数1024后面;改写成1024f就可以了。
|
||||
- (d)是一个扩展精度浮点数,类型是long double,大小为3.14。
|
||||
|
||||
## 练习2.8
|
||||
请利用转义序列编写一段程序,要求先输出 2M,然后转到新一行。修改程序使其先输出 2,然后输出制表符,再输出 M,最后转到新一行。
|
||||
|
||||
解:
|
||||
```cpp
|
||||
#include <iostream>
|
||||
int main()
|
||||
{
|
||||
std::cout << 2 << "\115\012";
|
||||
std::cout << 2 << "\t\115\012";
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习2.9
|
||||
解释下列定义的含义,对于非法的定义,请说明错在何处并将其改正。
|
||||
|
||||
- (a) std::cin >> int input_value;
|
||||
- (b) int i = { 3.14 };
|
||||
- (c) double salary = wage = 9999.99;
|
||||
- (d) int i = 3.14;
|
||||
|
||||
解:
|
||||
|
||||
(a): 应该先定义再使用。
|
||||
```cpp
|
||||
int input_value = 0;
|
||||
std::cin >> input_value;
|
||||
```
|
||||
|
||||
(b): 用列表初始化内置类型的变量时,如果存在丢失信息的风险,则编译器将报错。
|
||||
```cpp
|
||||
double i = { 3.14 };
|
||||
```
|
||||
|
||||
(c): 声明多个变量时应该用逗号将变量名隔开,不能用赋值运算符连接。
|
||||
```cpp
|
||||
double salary,wage;
|
||||
double salary = wage = 9999.99;
|
||||
```
|
||||
|
||||
(d): 不报错,但是小数部分会被截断。
|
||||
```cpp
|
||||
double i = 3.14;
|
||||
```
|
||||
|
||||
## 练习2.10
|
||||
下列变量的初值分别是什么?
|
||||
```cpp
|
||||
std::string global_str;
|
||||
int global_int;
|
||||
int main()
|
||||
{
|
||||
int local_int;
|
||||
std::string local_str;
|
||||
}
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
`global_str`和`global_int`是全局变量,所以初值分别为空字符串和0。
|
||||
`local_int`是局部变量并且没有初始化,它的初值是未定义的。
|
||||
`local_str` 是 `string` 类的对象,它的值由类确定,为空字符串。
|
||||
|
||||
## 练习2.11
|
||||
指出下面的语句是声明还是定义:
|
||||
|
||||
- (a) extern int ix = 1024;
|
||||
- (b) int iy;
|
||||
- (c) extern int iz;
|
||||
|
||||
解:
|
||||
声明与定义的关系是:声明使得名字为程序所知,而定义负责创建与名字关联的实体。
|
||||
(a): 定义变量ix
|
||||
(b): 声明并定义变量iy
|
||||
(c): 声明变量iz
|
||||
|
||||
## 练习2.12
|
||||
请指出下面的名字中哪些是非法的?
|
||||
|
||||
- (a) int double = 3.14;
|
||||
- (b) int _;
|
||||
- (c) int catch-22;
|
||||
- (d) int 1_or_2 = 1;
|
||||
- (e) double Double = 3.14;
|
||||
|
||||
解:
|
||||
|
||||
(a)是非法的,因为double是C++关键字,代表一种数据类型,不能作为变量的名字。
|
||||
(c)是非法的,在标识符中只能出现字母、数字和下画线,不能出现符号-,如果改成“int catch 22;”就是合法的了。
|
||||
(d)是非法的,因为标识符必须以字母或下画线开头,不能以数字开头。(b)和(e)是合法的命名。
|
||||
|
||||
## 练习2.13
|
||||
下面程序中`j`的值是多少?
|
||||
|
||||
```cpp
|
||||
int i = 42;
|
||||
int main()
|
||||
{
|
||||
int i = 100;
|
||||
int j = i;
|
||||
}
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
`j`的值是100,局部变量`i`覆盖了全局变量`i`。
|
||||
|
||||
## 练习2.14
|
||||
下面的程序合法吗?如果合法,它将输出什么?
|
||||
```cpp
|
||||
int i = 100, sum = 0;
|
||||
for (int i = 0; i != 10; ++i)
|
||||
sum += i;
|
||||
std::cout << i << " " << sum << std::endl;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
合法。输出是 100 45 。
|
||||
|
||||
该程序存在嵌套的作用域,其中for循环之外是外层作用域,for循环内部是内层作用域。首先在外层作用域中定义了i和sum,但是在for循环内部i被重新定义了,因此for循环实际上是从i=0循环到了i=9,内层作用域中没有重新定义 sum,因此sum的初始值是0并在此基础上依次累加。最后一句输出语句位于外层作用域中,此时在for循环内部重新定义的i已经失效,因此实际输出的仍然是外层作用域的i,值为100;而sum经由循环累加,值变为了 45。
|
||||
|
||||
## 练习2.15
|
||||
下面的哪个定义是不合法的?为什么?
|
||||
|
||||
- (a) int ival = 1.01;
|
||||
- (b) int &rval1 = 1.01;
|
||||
- (c) int &rval2 = ival;
|
||||
- (d) int &rval3;
|
||||
|
||||
解:
|
||||
|
||||
(b)和(d)不合法,(b)引用必须绑定在对象上,(d)引用必须初始化。
|
||||
|
||||
## 练习2.16
|
||||
考察下面的所有赋值然后回答:哪些赋值是不合法的?为什么?哪些赋值是合法的?它们执行了哪些操作?
|
||||
|
||||
```cpp
|
||||
int i = 0, &r1 = i;
|
||||
double d = 0, &r2 = d;
|
||||
```
|
||||
- (a) r2 = 3.14159;
|
||||
- (b) r2 = r1;
|
||||
- (c) i = r2;
|
||||
- (d) r1 = d;
|
||||
|
||||
解:
|
||||
|
||||
- (a): 合法。给 d 赋值为 3.14159。
|
||||
- (b): 合法。会执行自动转换(int->double)。
|
||||
- (c): 合法。会发生小数截取。
|
||||
- (d): 合法。会发生小数截取。
|
||||
|
||||
## 练习2.17
|
||||
执行下面的代码段将输出什么结果?
|
||||
```cpp
|
||||
int i, &ri = i;
|
||||
i = 5; ri = 10;
|
||||
std::cout << i << " " << ri << std::endl;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
程序的输出结果是10 10。
|
||||
|
||||
引用不是对象,它只是为已经存在的对象起了另外一个名字,因此ri实际上是 i的别名。在上述程序中,首先将i赋值为5,然后把这个值更新为10。因为ri是i的引用,所以它们的输出结果是一样的。
|
||||
|
||||
## 练习2.18
|
||||
编写代码分别改变指针的值以及指针所指对象的值。
|
||||
|
||||
解:
|
||||
```cpp
|
||||
int main() {
|
||||
int i = 1, j = 2;
|
||||
int *p = &i;
|
||||
std::cout << p << " " << *p << std::endl;
|
||||
p = &j; //更改指针的值,令p指向另一个整数对象j
|
||||
std::cout << p << " " << *p << std::endl;
|
||||
*p = 10; // 显式改变指针p指向的内容
|
||||
std::cout << p << " " << *p << std::endl;
|
||||
j = 20; // 通过改变变量j的值
|
||||
std::cout << p << " " << *p << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
输出结果:
|
||||
|
||||
```cpp
|
||||
0x61fe14 1
|
||||
0x61fe10 2
|
||||
0x61fe10 10
|
||||
0x61fe10 20
|
||||
```
|
||||
|
||||
## 练习2.19
|
||||
说明指针和引用的主要区别
|
||||
|
||||
解:
|
||||
|
||||
指针“指向”内存中的某个对象,而引用“绑定到”内存中的某个对象,他们都实现了对于娶她对象的间接访问,二者的区别主要有两方面:
|
||||
1. 指针本身就是一个对象,允许对指针复制和拷贝,而且在指针的生命周期内它可以指向几个不同的对象;引用不是一个对象,无法令引用重新绑定到另外一个对象。
|
||||
2. 指针无须在定义时赋初值,和其他内置类型一样,在块作用域内定义的指针如果没有被初始化,也将拥有一个不确定的值;引用则必须在定义时赋初值。
|
||||
|
||||
## 练习2.20
|
||||
请叙述下面这段代码的作用。
|
||||
|
||||
```cpp
|
||||
int i = 42;
|
||||
int *p1 = &i;
|
||||
*p1 = *p1 * *p1;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
这段代码首先定义了一个整数变量 `i` 并设其初值为 `42`;接着定义了一个整型指针 `p1`,令其指向变量 `i`;最后取出 `p1` 所指的当前值,计算平方后重新赋给 `p1` 所指的变量 `i`。
|
||||
|
||||
第二行的 `*` 表示声明一个指针,第三行的 `*` 表示解引用运算,即取出指针 `p1` 所指对象的值。
|
||||
|
||||
|
||||
## 练习2.21
|
||||
请解释下述定义。在这些定义中有非法的吗?如果有,为什么?
|
||||
|
||||
`int i = 0;`
|
||||
- (a) double* dp = &i;
|
||||
- (b) int *ip = i;
|
||||
- (c) int *p = &i;
|
||||
|
||||
解:
|
||||
|
||||
- (a): 非法。不能将一个指向 `double` 的指针指向 `int` 。
|
||||
- (b): 非法。不能将 `int` 变量赋给指针。
|
||||
- (c): 合法。
|
||||
|
||||
## 练习2.22
|
||||
假设 p 是一个 int 型指针,请说明下述代码的含义。
|
||||
|
||||
```cpp
|
||||
if (p) // ...
|
||||
if (*p) // ...
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
指针 p 作为 if 语句的条件时,实际检验的是指针本身的值,即指针所指的地址值。如果指针指向一个真实存在的变量,则其值必不为 0,此时条件为真;如果指针没有指向任何对象或者是无效指针,则对 p 的使用将引发不可预计的结果。
|
||||
|
||||
解引用运算符 `*p` 作为 if 语句的条件时,实际检验的是指针所指的对象内容,在上面的示例中是指针p所指的int值。如果该 int 值为0,则条件为假;否则。如果该 int 值不为0,对应条件为真。
|
||||
|
||||
|
||||
## 练习2.23
|
||||
给定指针 p,你能知道它是否指向了一个合法的对象吗?如果能,叙述判断的思路;如果不能,也请说明原因。
|
||||
|
||||
解:
|
||||
|
||||
在 C++ 程序中,应该尽量初始化所有指针,并且尽可能等定义了对象之后再定义指向它的指针。如果实在不清楚指针应该指向何处,就把它初始化为 nullptr 或者 0,这样程序就能检测并知道它有没有指向一个具体的对象了。其中,nullptr 是C++11新标准刚刚引入的一个特殊字面值,它可以转换成任意其他的指针类型。在此前提下,判断p是否指向合法的对象,只需把 p 作为 if 语句的条件即可,如果 p 的值是 nullptr,则条件为假;反之,条件为真。
|
||||
如果不注意初始化所有指针而贸然判断指针的值,则有可能引发不可预知的结果。一种处理的办法是把if(p)置于try结构中,当程序块顺利执行时,表示p指向了合法的对象:当程序块出错跳转到catch语句时,表示p没有指向合法的对象。
|
||||
|
||||
## 练习2.24
|
||||
在下面这段代码中为什么 p 合法而 lp 非法?
|
||||
|
||||
```cpp
|
||||
int i = 42;
|
||||
void *p = &i;
|
||||
long *lp = &i;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
`void *`是一种特殊的指针类型,可用于存放任意对象的地址。
|
||||
而其他指针类型必须要与所指对象严格匹配。
|
||||
|
||||
## 练习2.25
|
||||
说明下列变量的类型和值。
|
||||
```cpp
|
||||
(a) int* ip, i, &r = i;
|
||||
(b) int i, *ip = 0;
|
||||
(c) int* ip, ip2;
|
||||
```
|
||||
|
||||
解:
|
||||
- (a): ip 是一个指向 int 的指针, i 是一个 int, r 是 i 的引用。
|
||||
- (b): i 是 int , ip 是一个空指针。
|
||||
- (c): ip 是一个指向 int 的指针, ip2 是一个 int。
|
||||
|
||||
## 练习2.26
|
||||
下面哪些语句是合法的?如果不合法,请说明为什么?
|
||||
|
||||
解:
|
||||
```cpp
|
||||
const int buf; // 不合法, const 对象必须初始化
|
||||
int cnt = 0; // 合法
|
||||
const int sz = cnt; // 合法
|
||||
++cnt; ++sz; // 不合法, const 对象不能被改变
|
||||
```
|
||||
|
||||
## 练习2.27
|
||||
下面的哪些初始化是合法的?请说明原因。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
int i = -1, &r = 0; // 不合法, 非常用引用 r 不能引用字面值常量 0
|
||||
int *const p2 = &i2; // 合法,常量指针
|
||||
const int i = -1, &r = 0; // 合法, r 是一个常量引用,可以绑定到字面值常量 0
|
||||
const int *const p3 = &i2; // 合法
|
||||
const int *p1 = &i2; // 合法
|
||||
const int &const r2; // 不合法, r2 是引用,引用本身不是对象,不能让引用恒定不变
|
||||
const int i2 = i, &r = i; // 合法,i2是一个常量,r是一个常量引用
|
||||
```
|
||||
|
||||
## 练习2.28
|
||||
说明下面的这些定义是什么意思,挑出其中不合法的。
|
||||
|
||||
解:
|
||||
```cpp
|
||||
int i, *const cp; // 不合法, const 指针必须初始化
|
||||
int *p1, *const p2; // 不合法, const 指针必须初始化
|
||||
const int ic, &r = ic; // 不合法, const int 必须初始化
|
||||
const int *const p3; // 不合法, const 指针必须初始化
|
||||
const int *p; // 合法. 一个指针,指向 const int
|
||||
```
|
||||
|
||||
## 练习2.29
|
||||
假设已有上一个练习中定义的那些变量,则下面的哪些语句是合法的?请说明原因。
|
||||
|
||||
解:
|
||||
```cpp
|
||||
i = ic; // 合法, 常量赋值给普通变量
|
||||
p1 = p3; // 不合法, p3 是const指针不能赋值给普通指针
|
||||
p1 = ⁣ // 不合法, 普通指针不能指向常量
|
||||
p3 = ⁣ // 不合法, p3 是一个常量指针,不能被赋值
|
||||
p2 = p1; // 不合法, p2 是一个常量指针,不能被赋值
|
||||
ic = *p3; // 不合法, ic是常量,不能被赋值
|
||||
```
|
||||
|
||||
## 练习2.30
|
||||
对于下面的这些语句,请说明对象被声明成了顶层const还是底层const?
|
||||
|
||||
```cpp
|
||||
const int v2 = 0; int v1 = v2;
|
||||
int *p1 = &v1, &r1 = v1;
|
||||
const int *p2 = &v2, *const p3 = &i, &r2 = v2;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
v2 和 p3 是顶层const,分别表示一个整型常量和一个整型常量指针;p2 和 r2 是底层const,分别表示他们所指的对象是常量。
|
||||
|
||||
## 练习2.31
|
||||
假设已有上一个练习中所做的那些声明,则下面的哪些语句是合法的?请说明顶层const和底层const在每个例子中有何体现。
|
||||
|
||||
解:
|
||||
|
||||
|
||||
```cpp
|
||||
r1 = v2;
|
||||
p1 = p2;
|
||||
p2 = p1;
|
||||
p1 = p3;
|
||||
p2 = p3;
|
||||
```
|
||||
|
||||
在执行拷贝操作时,顶层const和底层const区别明显。其中,顶层 const不受影响,这是因为拷贝操作并不会改变被拷贝对象的值。底层const的限制则不容忽视,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行。
|
||||
|
||||
r1=v2;是合法的,r1是一个非常量引用,v2是一个常量(顶层const),把v2的值拷贝给r1不会对v2有任何影响。
|
||||
|
||||
pl=p2;是非法的,p1是普通指针,指向的对象可以是任意值,p2是指向常量的指针(底层const),令p1指向p2所指的内容,有可能错误地改变常量的值。
|
||||
|
||||
p2=p1;是合法的,与上一条语句相反,p2可以指向一个非常量,只不过我们不会通过p2更改它所指的值。
|
||||
|
||||
pl=p3;是非法的,p3包含底层const定义(p3所指的对象是常量),不能把 p3的值赋给普通指针。
|
||||
|
||||
p2=p3;是合法的,p2和p3包含相同的底层const,p3的顶层const则可以忽略不计。
|
||||
|
||||
|
||||
## 练习2.32
|
||||
下面的代码是否合法?如果非法,请设法将其修改正确。
|
||||
```cpp
|
||||
int null = 0, *p = null;
|
||||
```
|
||||
解:
|
||||
|
||||
上述代码是非法的,null是一个int 变量,p是一个int指针,二者不能直 接绑定。仅从语法角度来说,可以将代码修修改为:
|
||||
`int null=0,*p=&null;`
|
||||
|
||||
显然,这种改法与代码的原意不一定相符。另一种改法是使用nullptr: `int null=0,*p=nullptri`
|
||||
|
||||
## 练习2.33
|
||||
利用本节定义的变量,判断下列语句的运行结果。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
a=42; // a 是 int
|
||||
b=42; // b 是一个 int,(ci的顶层const在拷贝时被忽略掉了)
|
||||
c=42; // c 也是一个int
|
||||
d=42; // d 是一个 int *,所以语句非法
|
||||
e=42; // e 是一个 const int *, 所以语句非法
|
||||
g=42; // g 是一个 const int 的引用,引用都是底层const,所以不能被赋值
|
||||
```
|
||||
【出题思路】
|
||||
|
||||
本题旨在考查auto说明符与复合类型、常量混合使用时的各种情形。首先,使用引用其实是使用引用的对象,所以当引用被用作初始值时,真正参与初始化的其实是引用对象的值,编译器以引用对象的类型作为auto的推断类型。其次,auto一般会忽略掉顶层const,同时保留底层const。
|
||||
|
||||
【解答】
|
||||
前3条赋值语句是合法的,原因如下:
|
||||
r是i的别名,而i是一个整数,所以a的类型推断结果是一个整数;ci是一个整型常量,在类型推断时顶层const被忽略掉了,所以b是一个整数;cr是ci的别名,而ci是一个整型常量,所以c的类型推断结果是一个整数。因为a、b、c都是整数,所以为其赋值 42 是合法的。
|
||||
后3条赋值语句是非法的,原因如下:
|
||||
i是一个整数,&i是i的地址,所以d的类型推断结果是一个整型指针;ci是一个整型常量,&ci是一个整型常量的地址,所以e的类型推断结果是一个指向整型常量的指针:ci是一个整型常量,所以a的类型推断结果是一个整型常量引用。因为d和e都是指针,所以不能直接用字面值常量为其赋值;g绑定到了整型常量,所以不能修改它的值。
|
||||
|
||||
|
||||
## 练习2.34
|
||||
基于上一个练习中的变量和语句编写一段程序,输出赋值前后变量的内容,你刚才的推断正确吗?如果不对,请反复研读本节的示例直到你明白错在何处为止。
|
||||
|
||||
## 练习2.35
|
||||
判断下列定义推断出的类型是什么,然后编写程序进行验证。
|
||||
|
||||
```cpp
|
||||
const int i = 42;
|
||||
auto j = i; const auto &k = i; auto *p = &i;
|
||||
const auto j2 = i, &k2 = i;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
j 是 int,k 是 const int的引用,p 是const int *,j2 是const int,k2 是 const int 的引用。
|
||||
|
||||
## 练习2.36
|
||||
关于下面的代码,请指出每一个变量的类型以及程序结束时它们各自的值。
|
||||
|
||||
```cpp
|
||||
int a = 3, b = 4;
|
||||
decltype(a) c = a;
|
||||
decltype((b)) d = a;
|
||||
++c;
|
||||
++d;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
在本题的程序中,初始情况下a的值是3、b的值是4。decltype(a)c=ai使用的是一个不加括号的变量,因此c的的类型就是a的类型,即该语句等同于`int c=a;`,此时c是一个新整型变量,值为 3.
|
||||
`decltype((b)) d=a;`使用的是一个
|
||||
加了括号的变量,因此d的类型是引用, 即该语句等同于`int &d=a;`,此时d是变量a的别名。
|
||||
执行++c;++d;时,变量c的值自增为4,因为d是a的别名,所以d自增1
|
||||
意味着a的值变成了4。当程序结束时, a、b、c、d的值都是 4。
|
||||
|
||||
## 练习2.37
|
||||
赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。根据这一特点,请指出下面的代码中每一个变量的类型和值。
|
||||
|
||||
```cpp
|
||||
int a = 3, b = 4;
|
||||
decltype(a) c = a;
|
||||
decltype(a = b) d = a;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
c 是 int 类型,值为 3。d 是 int& 类型,绑定到 a。
|
||||
|
||||
## 练习2.38
|
||||
说明由decltype 指定类型和由auto指定类型有何区别。请举一个例子,decltype指定的类型与auto指定的类型一样;再举一个例子,decltype指定的类型与auto指定的类型不一样。
|
||||
|
||||
解:
|
||||
|
||||
decltype 处理顶层const和引用的方式与 auto不同,decltype会将顶层const和引用保留起来。
|
||||
|
||||
auto和decltype的区别主要有三个方面:
|
||||
|
||||
第一,auto类型说明符用编译器计算变量的初始值来推断其类型,而decltype虽然也让编译器分析表达式并得到它的类型,但是不实际计算表达式的值。
|
||||
|
||||
第二,编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初女治化规则。例如,auto一般会忽略掉顶层 const,而把底层const保留下来。与之相目反,decltype会保留变量的顶层const。
|
||||
|
||||
第三,与auto不同,decltype的结果类型与表达式形式密切相关,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同。如果decltype使用的是一个不加括号的变量,则得到的结果就就是该变量的类型;如果给变量加上了一层或多层括号,则编译器将推断得到引用类型。
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
|
||||
int main() {
|
||||
int a = 1;
|
||||
auto c1 = a;
|
||||
decltype(a) c2 = a;
|
||||
decltype((a)) c3 = a;
|
||||
|
||||
const int d = 3;
|
||||
auto f1 = d;
|
||||
decltype(d) f2 = d;
|
||||
|
||||
std::cout << typeid(c1).name() << std::endl;
|
||||
std::cout << typeid(c2).name() << std::endl;
|
||||
std::cout << typeid(c3).name() << std::endl;
|
||||
std::cout << typeid(f1).name() << std::endl;
|
||||
std::cout << typeid(f2).name() << std::endl;
|
||||
|
||||
c1++;
|
||||
c2++;
|
||||
c3++;
|
||||
f1++;
|
||||
// f2++; f2是整型变量,不能执行自增操作
|
||||
|
||||
std::cout << c1 << " " << c2 << " "<< c3 << " "<< f1 << " "<< f2 << " " << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 练习2.39
|
||||
编译下面的程序观察其运行结果,注意,如果忘记写类定义体后面的分号会发生什么情况?记录下相关的信息,以后可能会有用。
|
||||
```cpp
|
||||
struct Foo { /* 此处为空 */ } // 注意:没有分号
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}。
|
||||
```
|
||||
解:
|
||||
|
||||
提示应输入分号。
|
||||
|
||||
## 练习2.40
|
||||
根据自己的理解写出 Sales_data 类,最好与书中的例子有所区别。
|
||||
|
||||
```cpp
|
||||
struct Sale_data
|
||||
{
|
||||
std::string bookNo;
|
||||
std::string bookName;
|
||||
unsigned units_sold = 0;
|
||||
double revenue = 0.0;
|
||||
double price = 0.0;
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
## 练习2.41
|
||||
使用你自己的Sale_data类重写1.5.1节(第20页)、1.5.2节(第21页)和1.6节(第22页)的练习。眼下先把Sales_data类的定义和main函数放在一个文件里。
|
||||
|
||||
```cpp
|
||||
// 1.5.1
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
struct Sale_data
|
||||
{
|
||||
std::string bookNo;
|
||||
unsigned units_sold = 0;
|
||||
double revenue = 0.0;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
Sale_data book;
|
||||
double price;
|
||||
std::cin >> book.bookNo >> book.units_sold >> price;
|
||||
book.revenue = book.units_sold * price;
|
||||
std::cout << book.bookNo << " " << book.units_sold << " " << book.revenue << " " << price;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 1.5.2
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
struct Sale_data
|
||||
{
|
||||
std::string bookNo;
|
||||
unsigned units_sold = 0;
|
||||
double revenue = 0.0;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
Sale_data book1, book2;
|
||||
double price1, price2;
|
||||
std::cin >> book1.bookNo >> book1.units_sold >> price1;
|
||||
std::cin >> book2.bookNo >> book2.units_sold >> price2;
|
||||
book1.revenue = book1.units_sold * price1;
|
||||
book2.revenue = book2.units_sold * price2;
|
||||
|
||||
if (book1.bookNo == book2.bookNo)
|
||||
{
|
||||
unsigned totalCnt = book1.units_sold + book2.units_sold;
|
||||
double totalRevenue = book1.revenue + book2.revenue;
|
||||
std::cout << book1.bookNo << " " << totalCnt << " " << totalRevenue << " ";
|
||||
if (totalCnt != 0)
|
||||
std::cout << totalRevenue / totalCnt << std::endl;
|
||||
else
|
||||
std::cout << "(no sales)" << std::endl;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "Data must refer to same ISBN" << std::endl;
|
||||
return -1; // indicate failure
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
```cpp
|
||||
// 1.6
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
struct Sale_data
|
||||
{
|
||||
std::string bookNo;
|
||||
unsigned units_sold = 0;
|
||||
double revenue = 0.0;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
Sale_data total;
|
||||
double totalPrice;
|
||||
if (std::cin >> total.bookNo >> total.units_sold >> totalPrice)
|
||||
{
|
||||
total.revenue = total.units_sold * totalPrice;
|
||||
|
||||
Sale_data trans;
|
||||
double transPrice;
|
||||
while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice)
|
||||
{
|
||||
trans.revenue = trans.units_sold * transPrice;
|
||||
|
||||
if (total.bookNo == trans.bookNo)
|
||||
{
|
||||
total.units_sold += trans.units_sold;
|
||||
total.revenue += trans.revenue;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << " ";
|
||||
if (total.units_sold != 0)
|
||||
std::cout << total.revenue / total.units_sold << std::endl;
|
||||
else
|
||||
std::cout << "(no sales)" << std::endl;
|
||||
|
||||
total.bookNo = trans.bookNo;
|
||||
total.units_sold = trans.units_sold;
|
||||
total.revenue = trans.revenue;
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << " ";
|
||||
if (total.units_sold != 0)
|
||||
std::cout << total.revenue / total.units_sold << std::endl;
|
||||
else
|
||||
std::cout << "(no sales)" << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cerr << "No data?!" << std::endl;
|
||||
return -1; // indicate failure
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 练习2.42
|
||||
根据你自己的理解重写一个Sales_data.h头文件,并以此为基础重做2.6.2节(第67页)的练习。
|
||||
|
||||
|
||||
1360
docs/Cpp-Notes-main/Cpp-Primer-exercise/3.字符串、向量和数组.md
Normal file
1360
docs/Cpp-Notes-main/Cpp-Primer-exercise/3.字符串、向量和数组.md
Normal file
File diff suppressed because it is too large
Load Diff
637
docs/Cpp-Notes-main/Cpp-Primer-exercise/4.表达式.md
Normal file
637
docs/Cpp-Notes-main/Cpp-Primer-exercise/4.表达式.md
Normal file
@ -0,0 +1,637 @@
|
||||
---
|
||||
sort: 4
|
||||
---
|
||||
|
||||
|
||||
# 表达式
|
||||
|
||||
## 练习4.1
|
||||
|
||||
表达式`5 + 10 * 20 / 2`的求值结果是多少?
|
||||
|
||||
解:
|
||||
|
||||
等价于`5 + ((10 * 20) / 2) = 105`
|
||||
|
||||
## 练习4.2
|
||||
|
||||
根据4.12节中的表,在下述表达式的合理位置添加括号,使得添加括号后运算对象的组合顺序与添加括号前一致。
|
||||
(a) `*vec.begin()`
|
||||
(b) `*vec.begin() + 1`
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
*(vec.begin())
|
||||
(*(vec.begin())) + 1
|
||||
```
|
||||
|
||||
## 练习4.3
|
||||
|
||||
C++语言没有明确规定大多数二元运算符的求值顺序,给编译器优化留下了余地。这种策略实际上是在代码生成效率和程序潜在缺陷之间进行了权衡,你认为这可以接受吗?请说出你的理由。
|
||||
|
||||
解:
|
||||
|
||||
正如题目所说,C++只规定了非常少的二元运算符(逻辑与运算符、逻辑或运算符、逗号运算符)的求值顺序,其他绝大多数二元运算符的求值顺序并没有明确规定。这样做提高了代码生成的效率,但是可能引发潜在的缺陷。
|
||||
|
||||
关键是缺陷的风险有多大?我们知道,对于没有指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将会引发错误并产生未定义的行为;而如果运算对象彼此无关,它们既不会改变同一对象的状态也不执行IO任务,则函数的调用顺序不受限制。
|
||||
|
||||
就作者的观点而言,这样的做法在一定程度上是可以接受的,前提是在编写程序时注意以下两点:一是拿不准的时候最好用括号来强制让表达式的组合关系符合程序逻辑的要求;二是一旦改变了某个运算对象的值,在表达式的其地方就不要再使用这个运算对象了。
|
||||
|
||||
## 练习4.4
|
||||
在下面的表达式中添加括号,说明其求值过程及最终结果。编写程序编译该(不加括号的)表达式并输出结果验证之前的推断。
|
||||
|
||||
`12 / 3 * 4 + 5 * 15 + 24 % 4 / 2`
|
||||
|
||||
解:
|
||||
|
||||
`((12 / 3) * 4) + (5 * 15) + ((24 % 4) / 2) = 16 + 75 + 0 = 91`
|
||||
|
||||
## 练习4.5
|
||||
|
||||
写出下列表达式的求值结果。
|
||||
```cpp
|
||||
-30 * 3 + 21 / 5 // -90+4 = -86
|
||||
-30 + 3 * 21 / 5 // -30+63/5 = -30+12 = -18
|
||||
30 / 3 * 21 % 5 // 10*21%5 = 210%5 = 0
|
||||
-30 / 3 * 21 % 4 // -10*21%4 = -210%4 = -2
|
||||
```
|
||||
|
||||
## 练习4.6
|
||||
|
||||
写出一条表达式用于确定一个整数是奇数还是偶数。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
if (i % 2 == 0) /* ... */
|
||||
```
|
||||
或者
|
||||
```cpp
|
||||
if (i & 0x1) /* ... */
|
||||
```
|
||||
|
||||
## 练习4.7
|
||||
|
||||
溢出是何含义?写出三条将导致溢出的表达式。
|
||||
|
||||
解:
|
||||
|
||||
当计算的结果超出该类型所能表示的范围时就会产生溢出。
|
||||
|
||||
```cpp
|
||||
short svalue = 32767; ++svalue; // -32768
|
||||
unsigned uivalue = 0; --uivalue; // 4294967295
|
||||
unsigned short usvalue = 65535; ++usvalue; // 0
|
||||
```
|
||||
|
||||
## 练习4.8
|
||||
|
||||
说明在逻辑与、逻辑或及相等性运算符中运算对象的求值顺序。
|
||||
|
||||
解:
|
||||
|
||||
对于逻辑与运算符来说,当且仅当两两个运算对象都为真时结果为真;对于逻辑或运算符来说,只要两个运算对象中的1个为真结果就为真。
|
||||
|
||||
逻辑与运算符和逻辑或运算符都是先先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表过达式的结果时才会计算右侧运算对象的值。这种策略就是短路求值。其策略是:对于干逻辑与运算符来说,当且仅当左侧运算对象为真时才计算右侧运算对象;对于逻转辑或运算符来说,当且仅当左侧运算对象为假时才计算右侧运算对象。
|
||||
|
||||
## 练习4.9
|
||||
|
||||
解释在下面的`if`语句中条件部分的判断过程。
|
||||
```cpp
|
||||
const char *cp = "Hello World";
|
||||
if (cp && *cp)
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
cp是指向字符串的指针,因此上式的条件部分含义是首先检查指针cp是否有效。如果cp为空指针或无效指针,则条件不满足。如果cp有效,即cp指向了内存中的某个有效地址,继续解引用指针cp并检查cp所指的对象是否为空字符'\0',如果cp所指的对象不是空字符则条件满足; 否则不满足。
|
||||
|
||||
在本例中,显然初始状态下cp指向了字符串的首字符,是有效的;同时当前cp所指的对象是字符'H,不是空字符,所以 if的条件部分为真。
|
||||
|
||||
## 练习4.10
|
||||
|
||||
为`while`循环写一个条件,使其从标准输入中读取整数,遇到`42`时停止。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
int i;
|
||||
while(cin >> i && i != 42)
|
||||
```
|
||||
|
||||
## 练习4.11
|
||||
|
||||
书写一条表达式用于测试4个值a、b、c、d的关系,确保a大于b、b大于c、c大于d。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
a>b && b>c && c>d
|
||||
```
|
||||
|
||||
## 练习4.12
|
||||
|
||||
假设`i`、`j`和`k`是三个整数,说明表达式`i != j < k`的含义。
|
||||
|
||||
解:
|
||||
|
||||
这个表达式等于`i != (j < k)`。首先得到`j < k`的结果为`true`或`false`,转换为整数值是`1`或`0`,然后判断`i`不等于`1`或`0` ,最终的结果为`bool`值。
|
||||
|
||||
## 练习4.13
|
||||
|
||||
在下述语句中,当赋值完成后 i 和 d 的值分别是多少?
|
||||
|
||||
```cpp
|
||||
int i; double d;
|
||||
d = i = 3.5; // i = 3, d = 3.0
|
||||
i = d = 3.5; // d = 3.5, i = 3
|
||||
```
|
||||
|
||||
## 练习4.14
|
||||
|
||||
执行下述 if 语句后将发生什么情况?
|
||||
|
||||
```cpp
|
||||
if (42 = i) // 编译错误。赋值运算符左侧必须是一个可修改的左值。而字面值是右值。
|
||||
if (i = 42) // 所有非0整数转换成布尔值时都对应true,该条件恒为真。
|
||||
```
|
||||
|
||||
## 练习4.15
|
||||
|
||||
下面的赋值是非法的,为什么?应该如何修改?
|
||||
|
||||
```cpp
|
||||
double dval; int ival; int *pi;
|
||||
dval = ival = pi = 0;
|
||||
```
|
||||
|
||||
解:
|
||||
该赋值语句是非法的,虽然连续赋值的的形式本身并没有错,但是参与赋值的几个变量类型不同。其中,dval是双精度浮点数,ival是整数,pi是整型指针。
|
||||
|
||||
自右向左分析赋值操作的含义,pi=0天表示pi是一个空指针,接下来ival=pi试图把整型指针的值赋给整数,这是不符合合语法规范的操作,无法编译通过。稍作调整,就可以把上述程序改为合法。
|
||||
|
||||
```cpp
|
||||
dval = ival = 0;
|
||||
pi = 0;
|
||||
```
|
||||
|
||||
## 练习4.16
|
||||
|
||||
尽管下面的语句合法,但它们实际执行的行为可能和预期并不一样,为什么?应该如何修改?
|
||||
|
||||
```cpp
|
||||
if (p = getPtr() != 0)
|
||||
if (i = 1024)
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
(a)的原意是把getPtr()得到的指针赋值给p,然后判断p是否是一个空指针,但上述表达式的实际执行结果与之相距甚这元。因为赋值运算符的优先级低于不相等运算符,所以真正的表达式求值过程是先判到断getPtr()的返回值是否为空指针,如果是则p=0,否则p=1,最后以p的值作为if语句的条件。要想符合原意,应该修改为:
|
||||
|
||||
```cpp
|
||||
if ((p=getPtr()) != 0)
|
||||
if (i == 1024)
|
||||
```
|
||||
|
||||
## 练习4.17
|
||||
|
||||
说明前置递增运算符和后置递增运算符的区别。
|
||||
|
||||
解:
|
||||
|
||||
递增和递减运算符有两种形式:前置版本和后置版本。前置版本首先将运算对象加1(或减 1),然后把改变后的对象作为求值结果。后置版本也将运算对象加(或减1),但是求值结果是运算对象改变之前那个值的副本。这两种运算符必须作用于左值运算对象。前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回。
|
||||
|
||||
我们的建议是,除非必须,否则不用递增(递减)运算符的后置版本。前置版本的递增运算符避免了不必要的工作,它把值加 1 后直接返回改变了的运算对象。与之相比,后置版本需要将原始值存储下来以便于返回这个未修改的内容。如果我们不需要修改之前的值,那么后置版本的操作就是一种浪费。
|
||||
|
||||
对于整数和指针类型来说,编译器可能对这种额外的工作进行了一定的优化;
|
||||
但是对于相对复杂的迭代器类型来说,这种额外的工作就消耗巨大了。建议养成使用前置版本的习惯,这样不仅不需要担心性能问题,而且更重要的是写出的代码会更符合编程人员的初衷。
|
||||
|
||||
## 练习4.18
|
||||
|
||||
如果132页那个输出`vector`对象元素的`while`循环使用前置递增运算符,将得到什么结果?
|
||||
|
||||
解:
|
||||
|
||||
【出题思路】
|
||||
|
||||
前置递增运算符先将运算对象加1, 然后把改变后的对象作为求值结果;后置递增运算符也将运算对象加1,但是求值结果是运算对象改变之前那个值的副本。
|
||||
|
||||
简言之,如果在一条表达式中出现了 递增运算符,则其计算规律是:++在前, 先加1,后参与运算;++在后,先参与运 算,后加1。
|
||||
|
||||
【解答】
|
||||
|
||||
基于上述分析,本题不应该把whil e循环的后置递增运算符改为前置递增运算符。如果这样做了,会产生两个错误结果:一是无法输出vector对象的第一个元素;二是当所有元素都不为负时,移动至到最后一个元素的地方,程序试图继续向前移动迭代器并解引用一个根本不存在的元素。
|
||||
|
||||
## 练习4.19
|
||||
|
||||
假设`ptr`的类型是指向`int`的指针、`vec`的类型是`vector`、`ival`的类型是`int`,说明下面的表达式是何含义?如果有表达式不正确,为什么?应该如何修改?
|
||||
|
||||
```cpp
|
||||
(a) ptr != 0 && *ptr++
|
||||
(b) ival++ && ival
|
||||
(c) vec[ival++] <= vec[ival]
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
- (a)的含义是先判定指针ptr是否为空, 如果不为空,继续判断指针ptr所指的整数是否为非0数。如果非0,则该表达式的们最终求值结果为真;否则为假。最后把指针ptr向后移动一位。该表达式从语法上分析是合法的,但是最后的指针移位操作不一定有意义。如果ptr所指的是整型数组中的某个元素,则ptr可以按照预期移动到下一个元素。如果ptr所指的只是一个独立的整数变量,则移动指针操作将产生未定义的结果。
|
||||
- (b)的含义是先检查`ival`的值是否非0,如果非0继续检查`(ival+1)`的值是否非0。只有当两个值都是非0值时,表达式的求值结果为真;否则为假。在4.1.3 节中我们学习到,如果二元运算符的两个运算对象涉及同一个对象并改变对象的值,则这是一种不好的程序写法,应该改写。所以按照程序的原意,本式应该改写成`ival&&(ival +1)`。
|
||||
- (c)的含义是比较`vec[ivall和vec[ival+1]`的大小,如果前者较小则求值结果为真,否则为假。与(b)式一样,本式也出现了二元运算符的两个运算对象涉及同一个对象并改变对象值的情况,应该改写为`vec[ivall <=vec[ival +11`。
|
||||
|
||||
## 练习4.20
|
||||
|
||||
假设`iter`的类型是`vector::iterator`, 说明下面的表达式是否合法。如果合法,表达式的含义是什么?如果不合法,错在何处?
|
||||
|
||||
```cpp
|
||||
(a) *iter++;
|
||||
(b) (*iter)++;
|
||||
(c) *iter.empty();
|
||||
(d) iter->empty();
|
||||
(e) ++*iter;
|
||||
(f) iter++->empty();
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
- (a)是合法的,后置递增运算符的优先级高于解引用运算符,其含义是解引用当前迭代器所处位置的对象内容,然后把迭代器的位置向后移动一位。
|
||||
- (b)是非法的,解引用iter得到vector对象当前的元素,结果是一个string,显然string没有后置递增操作。
|
||||
- (c)是非法的,解引用运算符的优先级低于点运算符,所以该式先计算 iter.empty(),而迭代器并没有定义empty函数,所以无法通过编译。
|
||||
- (d)是合法的,iter->empty(();等价于(*iter).empty();。解引用迭代器得到迭代器当前所指的元素,结果是一个string,显然字符串可以判断是否为空, empty函数在此处有效。
|
||||
- (e)是非法的,该式先解引用iter,得到迭代器当前所指的元素,结果是一个 string,显然string没有后置递增操作。
|
||||
- (f)是合法的,iter++->empty();等价于(*iter++).empty();。含义是解引用迭代器当前位置的对象内容,得到一个字符串,判断该字符串是否为空,然后把迭代器向后移动一位。
|
||||
|
||||
## 练习4.21
|
||||
|
||||
编写一段程序,使用条件运算符从`vector`中找到哪些元素的值是奇数,然后将这些奇数值翻倍。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
vector<int> vInt;
|
||||
const int sz = 10;
|
||||
srand((unsigned ) time(NULL));
|
||||
|
||||
cout <<"数组的初始值为" << endl;
|
||||
|
||||
for(int i = 0; i != sz; ++i) {
|
||||
vInt.push_back(rand() % 100);
|
||||
cout << vInt[i] << " ";
|
||||
}
|
||||
cout <<endl;
|
||||
|
||||
for(auto &val : vInt) {
|
||||
val = (val % 2 == 0) ? val : val * 2;
|
||||
}
|
||||
cout <<"最终数组为" << endl;
|
||||
|
||||
for(auto it = vInt.cbegin(); it != vInt.cend(); ++it) {
|
||||
cout << *it << " ";
|
||||
}
|
||||
cout <<endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 练习4.22
|
||||
|
||||
本节的示例程序将成绩划分为`high pass`、`pass` 和 `fail` 三种,扩展该程序使其进一步将 60 分到 75 分之间的成绩设定为`low pass`。要求程序包含两个版本:一个版本只使用条件运算符;另一个版本使用1个或多个`if`语句。哪个版本的程序更容易理解呢?为什么?
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
|
||||
string finalGrade;
|
||||
int grade;
|
||||
|
||||
cout <<"请输入成绩" << endl;
|
||||
|
||||
while (cin >> grade && grade >=0 && grade <=100) {
|
||||
finalGrade = (grade >90) ? "high pass": (grade > 75)? "pass": (grade > 60) ? "low pass": "fail";
|
||||
}
|
||||
cout << "等级为" << finalGrade << endl;
|
||||
|
||||
// -------------------------
|
||||
if (grade > 90) cout << "high pass";
|
||||
else if (grade < 60) cout << "fail";
|
||||
else if (grade < 75) cout << "low pass";
|
||||
else cout << "pass";
|
||||
cout << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
第二个版本容易理解。当条件运算符嵌套层数变多之后,代码的可读性急剧下降。而`if else`的逻辑很清晰。
|
||||
|
||||
## 练习4.23
|
||||
|
||||
因为运算符的优先级问题,下面这条表达式无法通过编译。根据4.12节中的表指出它的问题在哪里?应该如何修改?
|
||||
|
||||
```cpp
|
||||
string s = "word";
|
||||
string pl = s + s[s.size() - 1] == 's' ? "" : "s" ;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
加法运算符的优先级高于条件运算符。因此要改为:
|
||||
|
||||
```cpp
|
||||
string pl = s + (s[s.size() - 1] == 's' ? "" : "s") ;
|
||||
```
|
||||
|
||||
## 练习4.24
|
||||
|
||||
本节的示例程序将成绩划分为`high pass`、`pass`、和`fail`三种,它的依据是条件运算符满足右结合律。假如条件运算符满足的是左结合律,求值的过程将是怎样的?
|
||||
|
||||
解:
|
||||
|
||||
如果条件运算符满足的是左结合律。那么
|
||||
|
||||
`finalgrade = (grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass";`
|
||||
等同于
|
||||
`finalgrade = ((grade > 90) ? "high pass" : (grade < 60)) ? "fail" : "pass";`
|
||||
假如此时 `grade > 90` ,第一个条件表达式的结果是 `"high pass"` ,而字符串字面值的类型是 `const char *`,非空所以为真。因此第二个条件表达式的结果是 `"fail"`。这样就出现了自相矛盾的逻辑。
|
||||
|
||||
## 练习4.25
|
||||
|
||||
如果一台机器上`int`占32位、`char`占8位,用的是`Latin-1`字符集,其中字符`'q'` 的二进制形式是`01110001`,那么表达式`~'q' << 6`的值是什么?
|
||||
|
||||
解:
|
||||
|
||||
首先将`char`类型提升为`int`类型,即`00000000 00000000 00000000 01110001`,然后取反,再左移6位,结果是-7296。
|
||||
|
||||
## 练习4.26
|
||||
|
||||
在本节关于测验成绩的例子中,如果使用`unsigned int` 作为`quiz1` 的类型会发生什么情况?
|
||||
|
||||
解:
|
||||
|
||||
在有的机器上,`unsigned int` 类型可能只有 16 位,因此结果是未定义的。
|
||||
|
||||
## 练习4.27
|
||||
|
||||
下列表达式的结果是什么?
|
||||
```cpp
|
||||
unsigned long ul1 = 3, ul2 = 7;
|
||||
(a) ul1 & ul2
|
||||
(b) ul1 | ul2
|
||||
(c) ul1 && ul2
|
||||
(d) ul1 || ul2
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
- (a) 3
|
||||
- (b) 7
|
||||
- (c) true
|
||||
- (d) ture
|
||||
|
||||
## 练习4.28
|
||||
|
||||
编写一段程序,输出每一种内置类型所占空间的大小。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
cout << "bool:\t\t" << sizeof(bool) << " bytes" << endl << endl;
|
||||
|
||||
cout << "char:\t\t" << sizeof(char) << " bytes" << endl;
|
||||
cout << "wchar_t:\t" << sizeof(wchar_t) << " bytes" << endl;
|
||||
cout << "char16_t:\t" << sizeof(char16_t) << " bytes" << endl;
|
||||
cout << "char32_t:\t" << sizeof(char32_t) << " bytes" << endl << endl;
|
||||
|
||||
cout << "short:\t\t" << sizeof(short) << " bytes" << endl;
|
||||
cout << "int:\t\t" << sizeof(int) << " bytes" << endl;
|
||||
cout << "long:\t\t" << sizeof(long) << " bytes" << endl;
|
||||
cout << "long long:\t" << sizeof(long long) << " bytes" << endl << endl;
|
||||
|
||||
cout << "float:\t\t" << sizeof(float) << " bytes" << endl;
|
||||
cout << "double:\t\t" << sizeof(double) << " bytes" << endl;
|
||||
cout << "long double:\t" << sizeof(long double) << " bytes" << endl << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
输出:
|
||||
|
||||
```
|
||||
bool: 1 bytes
|
||||
|
||||
char: 1 bytes
|
||||
wchar_t: 4 bytes
|
||||
char16_t: 2 bytes
|
||||
char32_t: 4 bytes
|
||||
|
||||
short: 2 bytes
|
||||
int: 4 bytes
|
||||
long: 8 bytes
|
||||
long long: 8 bytes
|
||||
|
||||
float: 4 bytes
|
||||
double: 8 bytes
|
||||
long double: 16 bytes
|
||||
```
|
||||
|
||||
## 练习4.29
|
||||
|
||||
推断下面代码的输出结果并说明理由。实际运行这段程序,结果和你想象的一样吗?如不一样,为什么?
|
||||
|
||||
```cpp
|
||||
int x[10]; int *p = x;
|
||||
cout << sizeof(x)/sizeof(*x) << endl;
|
||||
cout << sizeof(p)/sizeof(*p) << endl;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
sizeof(x)的运算对象x是数组的名字,求值结果是整个数组所占空间的大小,等价于对数组中所有的元素各执行一次sizeof运算并对所得结果求和。读者尤其需要注意,sizeof运算符不会把数组转换成指针来处理。在本例中,x是一个int数组且包含10个元素,所以sizeof(x)的求值结果是10个int值所占的内存空间总和。
|
||||
sizeof(*x)的运算对象*x是一条解引用表达式,此处的x既是数组的名称,也表示指向数组首元素的指针,解引用该指针得到指针所指的内容,在本例中是一个int.所以sizeof(*x)在这里等价于sizeof(int),即int所占的内存空间。
|
||||
sizeof(x)/sizeof(*x)可以理解为数组x所占的全部空间除以其中一个元素所占的空间,得到的结果应该是数组x的元素总数。实际上,因为C++的内置数组并没有定义成员函数size0),所以通常无法直接得到数组的容量。本题所示的方法是计算得到数组容量的一种常规方法。
|
||||
sizeof(p)的运算对象p是一个指针,求值结果是指针所占的空间大小。
|
||||
sizeof(*p)的运算对象*p是指针p所指的对象,即int变量x,所以求值结果是int值所占的空间大小。
|
||||
在作者的编译环境中,int占4字节,指针也占4字节,所以本题程序的输出结果是:
|
||||
10
|
||||
1
|
||||
|
||||
## 练习4.30
|
||||
|
||||
根据4.12节中的表,在下述表达式的适当位置加上括号,使得加上括号之后的表达式的含义与原来的含义相同。
|
||||
|
||||
```cpp
|
||||
(a) sizeof x + y
|
||||
(b) sizeof p->mem[i]
|
||||
(c) sizeof a < b
|
||||
(d) sizeof f()
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
(a)的含义是先求变量x所占空间的大小,然后与变量y的值相加;因为sizeof运算符的优先级高于加法运算符的优先级,所以如果想求表达式x+y所占的内存空间,应该改为`sizeof(x+y)`。
|
||||
|
||||
(b)的含义是先定位到指针p所指的对象,然后求该对象中名为mem的数组成员第i个元素的尺寸。因为成员选择运算符的优先级高于sizeof的优先级,所以本例无须添加括号。
|
||||
|
||||
(c)的含义是先求变量a在内存中所占空间的大小,再把求得的值与变量b的值比较。因为sizeof运算符的优先级高于关系运算符的优先级,所以如果想求表达式`a<b`所占的内存空间,应该改为`sizeof(a<b)`。
|
||||
|
||||
(d)的含义是求函数f()返回值所占内存空间的大小,因为函数调用运算符的优先级高于sizeof的优先级,所以本例无须添加括号。
|
||||
|
||||
## 练习4.31
|
||||
|
||||
本节的程序使用了前置版本的递增运算符和递减运算符,解释为什么要用前置版本而不用后置版本。要想使用后置版本的递增递减运算符需要做哪些改动?使用后置版本重写本节的程序。
|
||||
|
||||
解:
|
||||
|
||||
在4.5节(132页)已经说过了,除非必须,否则不用递增递减运算符的后置版本。在这里要使用后者版本的递增递减运算符不需要任何改动。
|
||||
|
||||
## 练习4.32
|
||||
解释下面这个循环的含义。
|
||||
|
||||
```cpp
|
||||
constexpr int size = 5;
|
||||
int ia[size] = { 1, 2, 3, 4, 5 };
|
||||
for (int *ptr = ia, ix = 0;
|
||||
ix != size && ptr != ia+size;
|
||||
++ix, ++ptr) { /* ... */ }
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
首先定义一个常量表达式size,它的的值是5;接着以size作为维度创建一个整型数组ia,5个元素分别是1~5。
|
||||
|
||||
for语句头包括三部分:第一部分定义整型指针指向数组ia的首元素,并且定义了一个整数ix,赋给它初值0;第二部了分判断循环终止的条件,当ix没有达到 size同时指针ptr没有指向数组最后一个元素的下一位置时,执行循环体;第三部分令变量ix和指针ptr分别执行递增操作。
|
||||
|
||||
## 练习4.33
|
||||
|
||||
根据4.12节中的表说明下面这条表达式的含义。
|
||||
|
||||
```cpp
|
||||
someValue ? ++x, ++y : --x, --y
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
逗号表达式的优先级是最低的。因此这条表达式也等于:
|
||||
```cpp
|
||||
(someValue ? ++x, ++y : --x), --y
|
||||
```
|
||||
如果`someValue`的值为真,`x` 和 `y` 的值都自增并返回 `y` 值,然后丢弃`y`值,`y`递减并返回`y`值。如果`someValue`的值为假,`x` 递减并返回`x` 值,然后丢弃`x`值,`y`递减并返回`y`值。
|
||||
|
||||
|
||||
|
||||
## 练习4.34
|
||||
|
||||
根据本节给出的变量定义,说明在下面的表达式中将发生什么样的类型转换:
|
||||
|
||||
```cpp
|
||||
(a) if (fval)
|
||||
(b) dval = fval + ival;
|
||||
(c) dval + ival * cval;
|
||||
```
|
||||
|
||||
需要注意每种运算符遵循的是左结合律还是右结合律。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
(a) fval 转换为 `bool` 类型,非0值转换为 `true`,0转换成 `false`
|
||||
(b) ival 转换为 `float` ,相加的结果转换为 `double`
|
||||
(c) cval 转换为 `int` ,然后相乘的结果转换为 `double`
|
||||
```
|
||||
|
||||
## 练习4.35
|
||||
|
||||
假设有如下的定义:
|
||||
|
||||
```cpp
|
||||
char cval;
|
||||
int ival;
|
||||
unsigned int ui;
|
||||
float fval;
|
||||
double dval;
|
||||
```
|
||||
|
||||
请回答在下面的表达式中发生了隐式类型转换吗?如果有,指出来。
|
||||
|
||||
```cpp
|
||||
(a) cval = 'a' + 3;
|
||||
(b) fval = ui - ival * 1.0;
|
||||
(c) dval = ui * fval;
|
||||
(d) cval = ival + fval + dval;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
- (a) `'a'` 转换为 `int` ,然后与 `3` 相加的结果转换为 `char`
|
||||
- (b) `ival` 转换为 `double`,`ui` 转换为 `double`,结果转换为 `float`
|
||||
- (c) `ui` 转换为 `float`,结果转换为 `double`
|
||||
- (d) `ival` 转换为 `float`,与`fval`相加后的结果转换为 `double`,最后的结果转换为`char`
|
||||
|
||||
## 练习4.36
|
||||
|
||||
假设 `i` 是`int`类型,`d` 是`double`类型,书写表达式 `i*=d` 使其执行整数类型的乘法而非浮点类型的乘法。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
i *= static_cast<int>(d);
|
||||
```
|
||||
|
||||
## 练习4.37
|
||||
|
||||
练习4.37
|
||||
用命名的强制类型转换改写下列旧式的转换语句。
|
||||
|
||||
```cpp
|
||||
int i; double d; const string *ps; char *pc; void *pv;
|
||||
(a) pv = (void*)ps;
|
||||
(b) i = int(*pc);
|
||||
(c) pv = &d;
|
||||
(d) pc = (char*)pv;
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
(a) pv = static_cast<void*>(const_cast<string*>(ps));
|
||||
(b) i = static_cast<int>(*pc);
|
||||
(c) pv = static_cast<void*>(&d);
|
||||
(d) pc = static_cast<char*>(pv);
|
||||
```
|
||||
|
||||
## 练习4.38
|
||||
|
||||
说明下面这条表达式的含义。
|
||||
|
||||
```cpp
|
||||
double slope = static_cast<double>(j/i);
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
将`j/i`的结果值转换为`double`,然后赋值给`slope`。
|
||||
|
||||
854
docs/Cpp-Notes-main/Cpp-Primer-exercise/5.语句.md
Normal file
854
docs/Cpp-Notes-main/Cpp-Primer-exercise/5.语句.md
Normal file
@ -0,0 +1,854 @@
|
||||
---
|
||||
sort: 5
|
||||
---
|
||||
|
||||
# 语句
|
||||
|
||||
## 练习5.1
|
||||
|
||||
什么是空语句?什么时候会用到空语句?
|
||||
|
||||
解:
|
||||
|
||||
空语句是最简单的语句,空语句由一个单独的分号构成。如果在程序的某个地方,语法上需要一条语句但是逻辑上不需要,此时应该使用空语句,空语句什么也不做。
|
||||
|
||||
一种常见的情况是,当循环的全部工作在条件部分就可以完成时,我们通常会用到空语句。使用空语句时最好加上注释,从而令代码的阅读者知道这条语句是有意省略内容的。
|
||||
|
||||
```cpp
|
||||
while (cin >> s && s != sought)
|
||||
;
|
||||
```
|
||||
|
||||
## 练习5.2
|
||||
|
||||
什么是块?什么时候会用到块?
|
||||
|
||||
解:
|
||||
|
||||
块是指用花括号括起来的语句和声明的序列,也称为复合语句。一个块就是一个作用域,在块中引入的名字只能在块内部以及嵌套在块中的子块里访问。如果在
|
||||
|
||||
程序的某个地方,语法上需要一条语句, 但是逻辑上需要多条语句,此时应该使用 块。块不需要以分号结束。
|
||||
|
||||
例如,循环体必须是一条语句,但是是我们通常需要在循环体内做很多事情,此时就应该把多条语句用花括号括起来,从而把语句序列转变成块。
|
||||
|
||||
```cpp
|
||||
{
|
||||
// ...
|
||||
}
|
||||
```
|
||||
如果在程序的某个地方,语法上需要一条语句,而逻辑上需要多条语句,此时应该使用块
|
||||
```cpp
|
||||
while (val <= 10) {
|
||||
sum += val;
|
||||
++val;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.3
|
||||
使用逗号运算符重写1.4.1节的`while`循环,使它不再需要块,观察改写之后的代码可读性提高了还是降低了。
|
||||
|
||||
```cpp
|
||||
while (val <= 10)
|
||||
sum += val, ++val;
|
||||
```
|
||||
代码的可读性反而降低了。
|
||||
|
||||
## 练习5.4
|
||||
|
||||
说明下列例子的含义,如果存在问题,试着修改它。
|
||||
|
||||
```cpp
|
||||
(a) while (string::iterator iter != s.end()) { /* . . . */ }
|
||||
|
||||
(b) while (bool status = find(word)) { /* . . . */ }
|
||||
if (!status) { /* . . . */ }
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
- (a) 这个循环试图用迭代器遍历`string`,但是变量的定义应该放在循环的外面,目前每次循环都会重新定义一个变量,明显是错误的。
|
||||
- (b) 这个循环的`while`和`if`是两个独立的语句,`if`语句中无法访问`status`变量,正确的做法是应该将status定义在`while`之前。
|
||||
|
||||
## 练习5.5
|
||||
写一段自己的程序,使用`if else`语句实现把数字转换为字母成绩的要求。
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
int g;
|
||||
|
||||
cout <<"请输入成绩:"<<endl;
|
||||
|
||||
cin >> g;
|
||||
|
||||
if (g < 0 || g > 100) {
|
||||
cout << "该成绩不合法" <<endl;
|
||||
return -1;
|
||||
}
|
||||
if(g == 100) {
|
||||
cout << "等级成绩是:" << "A++" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(g < 60) {
|
||||
cout << "等级成绩是:" << "F" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i = g /10;
|
||||
int j = g % 10;
|
||||
string score, level, finalGrade;
|
||||
|
||||
if(i == 9)
|
||||
score = "A";
|
||||
else if (i == 8)
|
||||
score = "B";
|
||||
else if (i == 7)
|
||||
score = "C";
|
||||
else
|
||||
score = "D";
|
||||
|
||||
if (j > 7) {
|
||||
level = "+";
|
||||
}else if (j < 3) {
|
||||
level = "-";
|
||||
}else {
|
||||
level = "";
|
||||
}
|
||||
finalGrade = score + level;
|
||||
|
||||
cout << "等级成绩是:" << finalGrade << endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.6
|
||||
|
||||
改写上一题的程序,使用条件运算符代替`if else`语句。
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
int g;
|
||||
|
||||
cout <<"请输入成绩:"<<endl;
|
||||
|
||||
cin >> g;
|
||||
|
||||
if (g < 0 || g > 100) {
|
||||
cout << "该成绩不合法" <<endl;
|
||||
return -1;
|
||||
}
|
||||
if(g == 100) {
|
||||
cout << "等级成绩是:" << "A++" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(g < 60) {
|
||||
cout << "等级成绩是:" << "F" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i = g /10;
|
||||
int j = g % 10;
|
||||
string score, level, finalGrade;
|
||||
|
||||
score = (i == 9) ? "A" :(i == 8) ? "B": (i == 7)? "C":"D";
|
||||
level = (j > 7) ? "+" : (j < 3)? "-": "";
|
||||
|
||||
finalGrade = score + level;
|
||||
|
||||
cout << "等级成绩是:" << finalGrade << endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.7
|
||||
改写下列代码段中的错误。
|
||||
|
||||
```cpp
|
||||
(a) if (ival1 != ival2)
|
||||
ival1 = ival2
|
||||
else
|
||||
ival1 = ival2 = 0;
|
||||
(b) if (ival < minval)
|
||||
minval = ival;
|
||||
occurs = 1;
|
||||
(c) if (int ival = get_value())
|
||||
cout << "ival = " << ival << endl;
|
||||
if (!ival)
|
||||
cout << "ival = 0\n";
|
||||
(d) if (ival = 0)
|
||||
ival = get_value();
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
- (a) `ival1 = ival2` 后面少了分号。
|
||||
- (b) 应该用花括号括起来。
|
||||
- (c) ival是定义在if语句中的变量,应定义在两个if语句的外部。
|
||||
- (d) `if (ival = 0)` 应该改为 `if (ival == 0)`。
|
||||
|
||||
## 练习5.8
|
||||
什么是“悬垂else”?C++语言是如何处理else子句的?
|
||||
|
||||
解:
|
||||
|
||||
用来描述在嵌套的`if else`语句中,如果`if`比`else`多时如何处理的问题。C++使用的方法是`else`匹配最近没有配对的`if`。
|
||||
|
||||
## 练习5.9
|
||||
编写一段程序,使用一系列`if`语句统计从`cin`读入的文本中有多少元音字母。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
|
||||
char ch;
|
||||
cout << "请输入一段文本" << endl;
|
||||
while (cin >> ch)
|
||||
{
|
||||
if (ch == 'a') ++aCnt;
|
||||
else if (ch == 'e') ++eCnt;
|
||||
else if (ch == 'i') ++iCnt;
|
||||
else if (ch == 'o') ++oCnt;
|
||||
else if (ch == 'u') ++uCnt;
|
||||
}
|
||||
cout << "Number of vowel a: \t" << aCnt << '\n'
|
||||
<< "Number of vowel e: \t" << eCnt << '\n'
|
||||
<< "Number of vowel i: \t" << iCnt << '\n'
|
||||
<< "Number of vowel o: \t" << oCnt << '\n'
|
||||
<< "Number of vowel u: \t" << uCnt << '\n'
|
||||
<< "Total number: \t" << aCnt + eCnt + iCnt + oCnt +uCnt<< endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.10
|
||||
我们之前实现的统计元音字母的程序存在一个问题:如果元音字母以大写形式出现,不会被统计在内。编写一段程序,既统计元音字母的小写形式,也统计元音字母的大写形式,也就是说,新程序遇到'a'和'A'都应该递增`aCnt`的值,以此类推。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
|
||||
char ch;
|
||||
cout << "请输入一段文本" << endl;
|
||||
while (cin >> ch)
|
||||
{
|
||||
switch (ch) {
|
||||
case 'a':
|
||||
case 'A':
|
||||
++aCnt;
|
||||
break;
|
||||
case 'e':
|
||||
case 'E':
|
||||
++eCnt;
|
||||
break;
|
||||
case 'I':
|
||||
case 'i':
|
||||
++iCnt;
|
||||
break;
|
||||
case 'o':
|
||||
case 'O':
|
||||
++oCnt;
|
||||
break;
|
||||
case 'u':
|
||||
case 'U':
|
||||
++uCnt;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
cout << "Number of vowel a: \t" << aCnt << '\n'
|
||||
<< "Number of vowel e: \t" << eCnt << '\n'
|
||||
<< "Number of vowel i: \t" << iCnt << '\n'
|
||||
<< "Number of vowel o: \t" << oCnt << '\n'
|
||||
<< "Number of vowel u: \t" << uCnt << '\n'
|
||||
<< "Total number: \t" << aCnt + eCnt + iCnt + oCnt +uCnt<< endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.11
|
||||
修改统计元音字母的程序,使其也能统计空格、制表符、和换行符的数量。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
unsigned int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
|
||||
unsigned int spaceCnt = 0, tabCnt = 0,newlineCnt = 0;
|
||||
char ch;
|
||||
cout << "请输入一段文本" << endl;
|
||||
while (cin >> ch)
|
||||
{
|
||||
switch (ch) {
|
||||
case 'a':
|
||||
case 'A':
|
||||
++aCnt;
|
||||
break;
|
||||
case 'e':
|
||||
case 'E':
|
||||
++eCnt;
|
||||
break;
|
||||
case 'I':
|
||||
case 'i':
|
||||
++iCnt;
|
||||
break;
|
||||
case 'o':
|
||||
case 'O':
|
||||
++oCnt;
|
||||
break;
|
||||
case 'u':
|
||||
case 'U':
|
||||
++uCnt;
|
||||
break;
|
||||
case ' ':
|
||||
++spaceCnt;
|
||||
break;
|
||||
case '\t':
|
||||
++tabCnt;
|
||||
break;
|
||||
case '\n':
|
||||
++newlineCnt;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
cout << "Number of vowel a: \t" << aCnt << '\n'
|
||||
<< "Number of vowel e: \t" << eCnt << '\n'
|
||||
<< "Number of vowel i: \t" << iCnt << '\n'
|
||||
<< "Number of vowel o: \t" << oCnt << '\n'
|
||||
<< "Number of vowel u: \t" << uCnt << '\n'
|
||||
<< "Number of space: \t" << spaceCnt << '\n'
|
||||
<< "Number of tab: \t" << tabCnt << '\n'
|
||||
<< "Number of newline: \t" << newlineCnt << '\n'
|
||||
<< "Total number: \t" << aCnt + eCnt + iCnt + oCnt +uCnt + spaceCnt + tabCnt + newlineCnt<< endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 练习5.12
|
||||
修改统计元音字母的程序,使其能统计含以下两个字符的字符序列的数量:`ff`、`fl`和`fi`。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
unsigned int flCnt = 0, ffCnt = 0, fiCnt = 0;
|
||||
char ch, prech= '\0';
|
||||
cout << "请输入一段文本" << endl;
|
||||
while (cin >> ch)
|
||||
{
|
||||
bool flag = true;
|
||||
if (prech == 'f') {
|
||||
switch (ch) {
|
||||
case 'f':
|
||||
++ffCnt;
|
||||
flag = false;
|
||||
break;
|
||||
case 'l':
|
||||
++flCnt;
|
||||
break;
|
||||
case 'i':
|
||||
++flCnt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
prech = '\0';
|
||||
}else {
|
||||
prech = ch;}
|
||||
}
|
||||
cout << "Number of ff : \t" << ffCnt << '\n'
|
||||
<< "Number of fi \t" << fiCnt << '\n'
|
||||
<< "Number of fl: \t" << flCnt << '\n'
|
||||
<< endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## 练习5.13
|
||||
下面显示的每个程序都含有一个常见的编码错误,指出错误在哪里,然后修改它们。
|
||||
```cpp
|
||||
(a) unsigned aCnt = 0, eCnt = 0, iouCnt = 0;
|
||||
char ch = next_text();
|
||||
switch (ch) {
|
||||
case 'a': aCnt++;
|
||||
case 'e': eCnt++;
|
||||
default: iouCnt++;
|
||||
}
|
||||
(b) unsigned index = some_value();
|
||||
switch (index) {
|
||||
case 1:
|
||||
int ix = get_value();
|
||||
ivec[ ix ] = index;
|
||||
break;
|
||||
default:
|
||||
ix = ivec.size()-1;
|
||||
ivec[ ix ] = index;
|
||||
}
|
||||
(c) unsigned evenCnt = 0, oddCnt = 0;
|
||||
int digit = get_num() % 10;
|
||||
switch (digit) {
|
||||
case 1, 3, 5, 7, 9:
|
||||
oddcnt++;
|
||||
break;
|
||||
case 2, 4, 6, 8, 10:
|
||||
evencnt++;
|
||||
break;
|
||||
}
|
||||
(d) unsigned ival=512, jval=1024, kval=4096;
|
||||
unsigned bufsize;
|
||||
unsigned swt = get_bufCnt();
|
||||
switch(swt) {
|
||||
case ival:
|
||||
bufsize = ival * sizeof(int);
|
||||
break;
|
||||
case jval:
|
||||
bufsize = jval * sizeof(int);
|
||||
break;
|
||||
case kval:
|
||||
bufsize = kval * sizeof(int);
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
(a) 少了`break`语句。应该为:
|
||||
```cpp
|
||||
unsigned aCnt = 0, eCnt = 0, iouCnt = 0;
|
||||
char ch = next_text();
|
||||
switch (ch) {
|
||||
case 'a': aCnt++; break;
|
||||
case 'e': eCnt++; break;
|
||||
default: iouCnt++; break;
|
||||
}
|
||||
```
|
||||
|
||||
(b) 在`default`分支当中,`ix`未定义。应该在外部定义`ix`。
|
||||
```cpp
|
||||
unsigned index = some_value();
|
||||
int ix;
|
||||
switch (index) {
|
||||
case 1:
|
||||
ix = get_value();
|
||||
ivec[ ix ] = index;
|
||||
break;
|
||||
default:
|
||||
ix = static_cast<int>(ivec.size())-1;
|
||||
ivec[ ix ] = index;
|
||||
}
|
||||
```
|
||||
|
||||
(c) `case`后面应该用冒号而不是逗号。
|
||||
```cpp
|
||||
unsigned evenCnt = 0, oddCnt = 0;
|
||||
int digit = get_num() % 10;
|
||||
switch (digit) {
|
||||
case 1: case 3: case 5: case 7: case 9:
|
||||
oddcnt++;
|
||||
break;
|
||||
case 2: case 4: case 6: case 8: case 0:
|
||||
evencnt++;
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
(d) `case`标签必须是整型常量表达式。
|
||||
```cpp
|
||||
const unsigned ival=512, jval=1024, kval=4096;
|
||||
unsigned bufsize;
|
||||
unsigned swt = get_bufCnt();
|
||||
switch(swt) {
|
||||
case ival:
|
||||
bufsize = ival * sizeof(int);
|
||||
break;
|
||||
case jval:
|
||||
bufsize = jval * sizeof(int);
|
||||
break;
|
||||
case kval:
|
||||
bufsize = kval * sizeof(int);
|
||||
break;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.14
|
||||
编写一段程序,从标准输入中读取若干`string`对象并查找连续重复出现的单词,所谓连续重复出现的意思是:一个单词后面紧跟着这个单词本身。要求记录连续重复出现的最大次数以及对应的单词。如果这样的单词存在,输出重复出现的最大次数;如果不存在,输出一条信息说明任何单词都没有连续出现过。
|
||||
例如:如果输入是:
|
||||
```
|
||||
how now now now brown cow cow
|
||||
```
|
||||
那么输出应该表明单词now连续出现了3次。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
##include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
string currStr, preStr = " ", maxStr;
|
||||
|
||||
int currCnt = 1, maxCnt = 0;
|
||||
|
||||
|
||||
while (cin >> currStr) {
|
||||
//
|
||||
if (currStr == preStr) {
|
||||
++currCnt;
|
||||
if (currCnt > maxCnt) {
|
||||
maxCnt = currCnt;
|
||||
maxStr = currStr;
|
||||
}
|
||||
}else {
|
||||
currCnt = 1;
|
||||
}
|
||||
preStr = currStr;
|
||||
}
|
||||
if (maxCnt >1) {
|
||||
cout << "出现最多的字符串是 : " << maxStr << ", 次数是:" << maxCnt << endl;
|
||||
}else {
|
||||
cout << "每个字符串都出现了一次" << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.15
|
||||
说明下列循环的含义并改正其中的错误。
|
||||
|
||||
```cpp
|
||||
(a) for (int ix = 0; ix != sz; ++ix) { /* ... */ }
|
||||
if (ix != sz)
|
||||
// . . .
|
||||
(b) int ix;
|
||||
for (ix != sz; ++ix) { /* ... */ }
|
||||
(c) for (int ix = 0; ix != sz; ++ix, ++sz) { /*...*/ }
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
应该改为下面这样:
|
||||
|
||||
```cpp
|
||||
(a) int ix;
|
||||
for (ix = 0; ix != sz; ++ix) { /* ... */ }
|
||||
if (ix != sz)
|
||||
// . . .
|
||||
(b) int ix;
|
||||
for (; ix != sz; ++ix) { /* ... */ }
|
||||
(c) for (int ix = 0; ix != sz; ++ix) { /*...*/ }
|
||||
```
|
||||
|
||||
## 练习5.16
|
||||
|
||||
`while`循环特别适用于那种条件不变、反复执行操作的情况,例如,当未达到文件末尾时不断读取下一个值。
|
||||
`for`循环更像是在按步骤迭代,它的索引值在某个范围内一次变化。根据每种循环的习惯各自编写一段程序,然后分别用另一种循环改写。
|
||||
如果只能使用一种循环,你倾向于哪种?为什么?
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
int i;
|
||||
while ( cin >> i )
|
||||
// ...
|
||||
|
||||
|
||||
for (int i = 0; cin >> i;)
|
||||
// ...
|
||||
|
||||
|
||||
for (int i = 0; i != size; ++i)
|
||||
// ...
|
||||
|
||||
|
||||
int i = 0;
|
||||
while (i != size)
|
||||
{
|
||||
// ...
|
||||
++i;
|
||||
}
|
||||
```
|
||||
如果只能用一种循环,我会更倾向使用`while`,因为`while`显得简洁,代码可读性强。
|
||||
|
||||
## 练习5.17
|
||||
|
||||
假设有两个包含整数的`vector`对象,编写一段程序,检验其中一个`vector`对象是否是另一个的前缀。
|
||||
为了实现这一目标,对于两个不等长的`vector`对象,只需挑出长度较短的那个,把它的所有元素和另一个`vector`对象比较即可。
|
||||
例如,如果两个`vector`对象的元素分别是0、1、1、2 和 0、1、1、2、3、5、8,则程序的返回结果为真。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using std::cout; using std::vector;
|
||||
|
||||
bool is_prefix(vector<int> const& lhs, vector<int> const& rhs)
|
||||
{
|
||||
if(lhs.size() > rhs.size())
|
||||
return is_prefix(rhs, lhs);
|
||||
for(unsigned i = 0; i != lhs.size(); ++i)
|
||||
if(lhs[i] != rhs[i]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
vector<int> l{ 0, 1, 1, 2 };
|
||||
vector<int> r{ 0, 1, 1, 2, 3, 5, 8 };
|
||||
cout << (is_prefix(r, l) ? "yes\n" : "no\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.18
|
||||
说明下列循环的含义并改正其中的错误。
|
||||
|
||||
```cpp
|
||||
(a) do { // 应该添加花括号
|
||||
int v1, v2;
|
||||
cout << "Please enter two numbers to sum:" ;
|
||||
if (cin >> v1 >> v2)
|
||||
cout << "Sum is: " << v1 + v2 << endl;
|
||||
}while (cin);
|
||||
(b) int ival;
|
||||
do {
|
||||
// . . .
|
||||
} while (ival = get_response()); // 应该将ival 定义在循环外
|
||||
(c) int ival = get_response();
|
||||
do {
|
||||
ival = get_response();
|
||||
} while (ival); // 应该将ival 定义在循环外
|
||||
```
|
||||
|
||||
## 练习5.19
|
||||
编写一段程序,使用`do while`循环重复地执行下述任务:
|
||||
首先提示用户输入两个`string`对象,然后挑出较短的那个并输出它。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using std::cout; using std::cin; using std::endl; using std::string;
|
||||
|
||||
int main()
|
||||
{
|
||||
string rsp;
|
||||
do {
|
||||
cout << "Input two strings: ";
|
||||
string str1, str2;
|
||||
cin >> str1 >> str2;
|
||||
cout << (str1 <= str2 ? str1 : str2)
|
||||
<< " is less than the other. " << "\n\n"
|
||||
<< "More? Enter yes or no: ";
|
||||
cin >> rsp;
|
||||
} while (!rsp.empty() && tolower(rsp[0]) == 'y');
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.20
|
||||
编写一段程序,从标准输入中读取`string`对象的序列直到连续出现两个相同的单词或者所有的单词都读完为止。
|
||||
使用`while`循环一次读取一个单词,当一个单词连续出现两次时使用`break`语句终止循环。
|
||||
输出连续重复出现的单词,或者输出一个消息说明没有任何单词是连续重复出现的。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
using std::cout; using std::cin; using std::endl; using std::string;
|
||||
|
||||
int main()
|
||||
{
|
||||
string read, tmp;
|
||||
while (cin >> read)
|
||||
if (read == tmp) break; else tmp = read;
|
||||
|
||||
if (cin.eof()) cout << "no word was repeated." << endl; //eof(end of file)判断输入是否结束,或者文件结束符,等同于 CTRL+Z
|
||||
else cout << read << " occurs twice in succession." << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.21
|
||||
修改5.5.1节练习题的程序,使其找到的重复单词必须以大写字母开头。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
using std::cin; using std::cout; using std::endl;
|
||||
#include <string>
|
||||
using std::string;
|
||||
|
||||
int main()
|
||||
{
|
||||
string curr, prev;
|
||||
bool no_twice = true;
|
||||
while (cin >> curr)
|
||||
{
|
||||
if (isupper(curr[0]) && prev == curr)
|
||||
{
|
||||
cout << curr << ": occurs twice in succession." << endl;
|
||||
no_twice = false;
|
||||
break;
|
||||
}
|
||||
prev = curr;
|
||||
}
|
||||
|
||||
if (no_twice)
|
||||
cout << "no word was repeated." << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.22
|
||||
本节的最后一个例子跳回到`begin`,其实使用循环能更好的完成该任务,重写这段代码,注意不再使用`goto`语句。
|
||||
|
||||
```cpp
|
||||
// 向后跳过一个带初始化的变量定义是合法的
|
||||
begin:
|
||||
int sz = get_size();
|
||||
if (sz <= 0) {
|
||||
goto begin;
|
||||
}
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
用 for 循环修改的话就是这样
|
||||
```cpp
|
||||
for (int sz = get_size(); sz <=0; sz = get_size())
|
||||
;
|
||||
```
|
||||
|
||||
## 练习5.23
|
||||
编写一段程序,从标准输入读取两个整数,输出第一个数除以第二个数的结果。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
using std::cin;
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
|
||||
int main()
|
||||
{
|
||||
int i, j;
|
||||
cin >> i >> j;
|
||||
cout << i / j << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.24
|
||||
修改你的程序,使得当第二个数是0时抛出异常。先不要设定`catch`子句,运行程序并真的为除数输入0,看看会发生什么?
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int i, j;
|
||||
std::cin >> i >> j;
|
||||
if (j == 0)
|
||||
throw std::runtime_error("divisor is 0");
|
||||
std::cout << i / j << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习5.25
|
||||
修改上一题的程序,使用`try`语句块去捕获异常。`catch`子句应该为用户输出一条提示信息,询问其是否输入新数并重新执行`try`语句块的内容。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
using std::cin; using std::cout; using std::endl; using std::runtime_error;
|
||||
|
||||
int main(void)
|
||||
{
|
||||
for (int i, j; cout << "Input two integers:\n", cin >> i >> j; )
|
||||
{
|
||||
try
|
||||
{
|
||||
if (j == 0)
|
||||
throw runtime_error("divisor is 0");
|
||||
cout << i / j << endl;
|
||||
}
|
||||
catch (runtime_error err)
|
||||
{
|
||||
cout << err.what() << "\nTry again? Enter y or n" << endl;
|
||||
char c;
|
||||
cin >> c;
|
||||
if (!cin || c == 'n')
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
1063
docs/Cpp-Notes-main/Cpp-Primer-exercise/6.函数.md
Normal file
1063
docs/Cpp-Notes-main/Cpp-Primer-exercise/6.函数.md
Normal file
File diff suppressed because it is too large
Load Diff
1370
docs/Cpp-Notes-main/Cpp-Primer-exercise/7.类.md
Normal file
1370
docs/Cpp-Notes-main/Cpp-Primer-exercise/7.类.md
Normal file
File diff suppressed because it is too large
Load Diff
418
docs/Cpp-Notes-main/Cpp-Primer-exercise/8.IO库.md
Normal file
418
docs/Cpp-Notes-main/Cpp-Primer-exercise/8.IO库.md
Normal file
@ -0,0 +1,418 @@
|
||||
---
|
||||
sort: 8
|
||||
---
|
||||
|
||||
# IO库
|
||||
|
||||
## 练习8.1
|
||||
> 编写函数,接受一个`istream&`参数,返回值类型也是`istream&`。此函数须从给定流中读取数据,直至遇到文件结束标识时停止。它将读取的数据打印在标准输出上。完成这些操作后,在返回流之前,对流进行复位,使其处于有效状态。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
std::istream& func(std::istream &is)
|
||||
{
|
||||
std::string buf;
|
||||
while (is >> buf)
|
||||
std::cout << buf << std::endl;
|
||||
is.clear();
|
||||
return is;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习8.2
|
||||
> 测试函数,调用参数为`cin`。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
using std::istream;
|
||||
|
||||
istream& func(istream &is)
|
||||
{
|
||||
std::string buf;
|
||||
while (is >> buf)
|
||||
std::cout << buf << std::endl;
|
||||
is.clear();
|
||||
return is;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
istream& is = func(std::cin);
|
||||
std::cout << is.rdstate() << std::endl;
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习8.3
|
||||
> 什么情况下,下面的`while`循环会终止?
|
||||
|
||||
```cpp
|
||||
while (cin >> i) /* ... */
|
||||
```
|
||||
|
||||
解:
|
||||
|
||||
遇到文件结束符、或者IO流错误,或读入了无效数据。
|
||||
|
||||
## 练习8.4
|
||||
> 编写函数,以读模式打开一个文件,将其内容读入到一个`string`的`vector`中,将每一行作为一个独立的元素存于`vector`中。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
void ReadFileToVec(const string& fileName, vector<string>& vec)
|
||||
{
|
||||
ifstream ifs(fileName);
|
||||
if (ifs)
|
||||
{
|
||||
string buf;
|
||||
while (getline(ifs, buf))
|
||||
vec.push_back(buf);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 练习8.5
|
||||
> 重写上面的程序,将每个单词作为一个独立的元素进行存储。
|
||||
解:
|
||||
|
||||
```cpp
|
||||
void ReadFileToVec(const string& fileName, vector<string>& vec)
|
||||
{
|
||||
ifstream ifs(fileName);
|
||||
if (ifs)
|
||||
{
|
||||
string buf;
|
||||
while (ifs >> buf)
|
||||
vec.push_back(buf);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 练习8.6
|
||||
> 重写7.1.1节的书店程序,从一个文件中读取交易记录。将文件名作为一个参数传递给`main`。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "../ch07/ex7_26.h"
|
||||
using std::ifstream; using std::cout; using std::endl; using std::cerr;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
ifstream input(argv[1]);
|
||||
|
||||
Sales_data total;
|
||||
if (read(input, total))
|
||||
{
|
||||
Sales_data trans;
|
||||
while (read(input, trans))
|
||||
{
|
||||
if (total.isbn() == trans.isbn())
|
||||
total.combine(trans);
|
||||
else
|
||||
{
|
||||
print(cout, total) << endl;
|
||||
total = trans;
|
||||
}
|
||||
}
|
||||
print(cout, total) << endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
cerr << "No data?!" << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习8.7
|
||||
> 修改上一节的书店程序,将结果保存到一个文件中。将输出文件名作为第二个参数传递给`main`函数。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "../ch07/ex7_26.h"
|
||||
using std::ifstream; using std::ofstream; using std::endl; using std::cerr;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
ifstream input(argv[1]);
|
||||
ofstream output(argv[2]);
|
||||
|
||||
Sales_data total;
|
||||
if (read(input, total))
|
||||
{
|
||||
Sales_data trans;
|
||||
while (read(input, trans))
|
||||
{
|
||||
if (total.isbn() == trans.isbn())
|
||||
total.combine(trans);
|
||||
else
|
||||
{
|
||||
print(output, total) << endl;
|
||||
total = trans;
|
||||
}
|
||||
}
|
||||
print(output, total) << endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
cerr << "No data?!" << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习8.8
|
||||
> 修改上一题的程序,将结果追加到给定的文件末尾。对同一个输出文件,运行程序至少两次,检验数据是否得以保留。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
|
||||
#include "../ch07/ex7_26.h"
|
||||
using std::ifstream; using std::ofstream; using std::endl; using std::cerr;
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
ifstream input(argv[1]);
|
||||
ofstream output(argv[2], ofstream::app);
|
||||
|
||||
Sales_data total;
|
||||
if (read(input, total))
|
||||
{
|
||||
Sales_data trans;
|
||||
while (read(input, trans))
|
||||
{
|
||||
if (total.isbn() == trans.isbn())
|
||||
total.combine(trans);
|
||||
else
|
||||
{
|
||||
print(output, total) << endl;
|
||||
total = trans;
|
||||
}
|
||||
}
|
||||
print(output, total) << endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
cerr << "No data?!" << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 练习8.9
|
||||
> 使用你为8.1.2节第一个练习所编写的函数打印一个`istringstream`对象的内容。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
using std::istream;
|
||||
|
||||
istream& func(istream &is)
|
||||
{
|
||||
std::string buf;
|
||||
while (is >> buf)
|
||||
std::cout << buf << std::endl;
|
||||
is.clear();
|
||||
return is;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
std::istringstream iss("hello");
|
||||
func(iss);
|
||||
return 0;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 练习8.10
|
||||
> 编写程序,将来自一个文件中的行保存在一个`vector`中。然后使用一个`istringstream`从`vector`读取数据元素,每次读取一个单词。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
using std::vector; using std::string; using std::ifstream; using std::istringstream; using std::cout; using std::endl; using std::cerr;
|
||||
|
||||
int main()
|
||||
{
|
||||
ifstream ifs("../data/book.txt");
|
||||
if (!ifs)
|
||||
{
|
||||
cerr << "No data?" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
vector<string> vecLine;
|
||||
string line;
|
||||
while (getline(ifs, line))
|
||||
vecLine.push_back(line);
|
||||
|
||||
for (auto &s : vecLine)
|
||||
{
|
||||
istringstream iss(s);
|
||||
string word;
|
||||
while (iss >> word)
|
||||
cout << word << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习8.11
|
||||
> 本节的程序在外层`while`循环中定义了`istringstream`对象。如果`record`对象定义在循环之外,你需要对程序进行怎样的修改?重写程序,将`record`的定义移到`while`循环之外,验证你设想的修改方法是否正确。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
using std::vector; using std::string; using std::cin; using std::istringstream;
|
||||
|
||||
struct PersonInfo {
|
||||
string name;
|
||||
vector<string> phones;
|
||||
};
|
||||
|
||||
int main()
|
||||
{
|
||||
string line, word;
|
||||
vector<PersonInfo> people;
|
||||
istringstream record;
|
||||
while (getline(cin, line))
|
||||
{
|
||||
PersonInfo info;
|
||||
record.clear();
|
||||
record.str(line);
|
||||
record >> info.name;
|
||||
while (record >> word)
|
||||
info.phones.push_back(word);
|
||||
people.push_back(info);
|
||||
}
|
||||
|
||||
for (auto &p : people)
|
||||
{
|
||||
std::cout << p.name << " ";
|
||||
for (auto &s : p.phones)
|
||||
std::cout << s << " ";
|
||||
std::cout << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习8.12
|
||||
> 我们为什么没有在`PersonInfo`中使用类内初始化?
|
||||
|
||||
解:
|
||||
|
||||
因为这里只需要聚合类就够了,所以没有必要在`PersionInfo`中使用类内初始化。
|
||||
|
||||
## 练习8.13
|
||||
> 重写本节的电话号码程序,从一个命名文件而非`cin`读取数据。
|
||||
|
||||
解:
|
||||
|
||||
```cpp
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using std::vector; using std::string; using std::cin; using std::istringstream;
|
||||
using std::ostringstream; using std::ifstream; using std::cerr; using std::cout; using std::endl;
|
||||
using std::isdigit;
|
||||
|
||||
struct PersonInfo {
|
||||
string name;
|
||||
vector<string> phones;
|
||||
};
|
||||
|
||||
bool valid(const string& str)
|
||||
{
|
||||
return isdigit(str[0]);
|
||||
}
|
||||
|
||||
string format(const string& str)
|
||||
{
|
||||
return str.substr(0,3) + "-" + str.substr(3,3) + "-" + str.substr(6);
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
ifstream ifs("../data/phonenumbers.txt");
|
||||
if (!ifs)
|
||||
{
|
||||
cerr << "no phone numbers?" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
string line, word;
|
||||
vector<PersonInfo> people;
|
||||
istringstream record;
|
||||
while (getline(ifs, line))
|
||||
{
|
||||
PersonInfo info;
|
||||
record.clear();
|
||||
record.str(line);
|
||||
record >> info.name;
|
||||
while (record >> word)
|
||||
info.phones.push_back(word);
|
||||
people.push_back(info);
|
||||
}
|
||||
|
||||
for (const auto &entry : people)
|
||||
{
|
||||
ostringstream formatted, badNums;
|
||||
for (const auto &nums : entry.phones)
|
||||
if (!valid(nums)) badNums << " " << nums;
|
||||
else formatted << " " << format(nums);
|
||||
if (badNums.str().empty())
|
||||
cout << entry.name << " " << formatted.str() << endl;
|
||||
else
|
||||
cerr << "input error: " << entry.name
|
||||
<< " invalid number(s) " << badNums.str() << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
## 练习8.14
|
||||
> 我们为什么将`entry`和`nums`定义为`const auto&`?
|
||||
|
||||
解:
|
||||
|
||||
它们都是类类型,因此使用引用避免拷贝。
|
||||
在循环当中不会改变它们的值,因此用`const`。
|
||||
1148
docs/Cpp-Notes-main/Cpp-Primer-exercise/9.顺序容器.md
Normal file
1148
docs/Cpp-Notes-main/Cpp-Primer-exercise/9.顺序容器.md
Normal file
File diff suppressed because it is too large
Load Diff
9
docs/Cpp-Notes-main/Cpp-Primer-exercise/README.md
Normal file
9
docs/Cpp-Notes-main/Cpp-Primer-exercise/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
sort: 2
|
||||
---
|
||||
|
||||
# C++ Primer 第5版练习题解答
|
||||
|
||||
{% include list.liquid %}
|
||||
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
int main() {
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int i = 10;
|
||||
while (i >= 0) {
|
||||
std::cout << i << std::endl;
|
||||
i--;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int temp = 0;
|
||||
std::cout << "Please enter two numbers"<< std::endl;
|
||||
int v1 = 0, v2 = 0;
|
||||
std::cin >> v1 >> v2;
|
||||
// 若v1 > v2,则调换顺序
|
||||
if (v1 > v2) {
|
||||
temp = v2;
|
||||
v2 = v1;
|
||||
v1 = temp;
|
||||
}
|
||||
|
||||
while (v1 <= v2){
|
||||
std::cout << v1 << std::endl;
|
||||
v1 += 1;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int sum = 0;
|
||||
for (int i = -100; i <= 100; ++i) {
|
||||
sum += i;
|
||||
}
|
||||
std::cout << "sum is " << sum << std::endl;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
#include <iostream>
|
||||
// 修改1.9
|
||||
int main() {
|
||||
int sum = 0;
|
||||
for (int i = 50; i <= 100; i++) {
|
||||
sum += i;
|
||||
}
|
||||
std::cout << "sum is " << sum << std::endl;
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
for (int i = 10; i >= 0; i--) {
|
||||
std::cout << i << std::endl;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int temp = 0;
|
||||
std::cout << "Please enter two numbers"<< std::endl;
|
||||
int v1 = 0, v2 = 0;
|
||||
std::cin >> v1 >> v2;
|
||||
// 若v1 > v2,则调换顺序
|
||||
if (v1 > v2) {
|
||||
temp = v2;
|
||||
v2 = v1;
|
||||
v1 = temp;
|
||||
}
|
||||
|
||||
for (;v1 <= v2;v1++){
|
||||
std::cout << v1 << std::endl;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int sum = 0, value = 0;
|
||||
while(std::cin >> value) {
|
||||
sum += value;
|
||||
}
|
||||
std::cout << "sum is " << sum << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
int currVal = 0, val = 0;
|
||||
|
||||
if (std::cin >> currVal)
|
||||
{
|
||||
int cnt = 1;
|
||||
while (std::cin >> val)
|
||||
{
|
||||
if (val == currVal)
|
||||
{
|
||||
++cnt;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << currVal << " occurs " << cnt << " times" << std::endl;
|
||||
currVal = val;
|
||||
cnt = 1;
|
||||
}
|
||||
}
|
||||
std::cout << currVal << " occurs " << cnt << " times" << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int temp = 0;
|
||||
std::cout << "Please enter two numbers"<< std::endl;
|
||||
int v1 = 0, v2 = 0;
|
||||
std::cin >> v1 >> v2;
|
||||
// 若v1 > v2,则调换顺序
|
||||
if (v1 > v2) {
|
||||
temp = v2;
|
||||
v2 = v1;
|
||||
v1 = temp;
|
||||
}
|
||||
|
||||
while (v1 <= v2){
|
||||
std::cout << v1 << std::endl;
|
||||
v1 += 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
return -1;
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
#include <iostream>
|
||||
#include "Sales_item.h"
|
||||
|
||||
int main() {
|
||||
|
||||
for(Sales_item item;std::cin >> item; std::cout << item << std::endl);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
#include <iostream>
|
||||
#include "Sales_item.h"
|
||||
|
||||
int main() {
|
||||
Sales_item item1;
|
||||
Sales_item item2;
|
||||
|
||||
std::cin >> item1;
|
||||
std::cin >> item2;
|
||||
|
||||
if (item1.isbn() == item2.isbn()) {
|
||||
std::cout << item1 + item2 << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
#include <iostream>
|
||||
#include "Sales_item.h"
|
||||
|
||||
int main() {
|
||||
Sales_item sum_item;
|
||||
|
||||
for (Sales_item item; std::cin >> item; std::cout << item << std::endl) {
|
||||
sum_item += item;
|
||||
}
|
||||
|
||||
std::cout << "sum is " << sum_item << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
#include <iostream>
|
||||
#include "Sales_item.h"
|
||||
|
||||
int main() {
|
||||
Sales_item item1, item2;
|
||||
int num = 1;
|
||||
std::cout << "请输入销售记录" << std::endl;
|
||||
|
||||
if (std::cin >> item1) {
|
||||
while (std::cin >> item2) {
|
||||
if (compareIsbn(item1, item2)) { //相同
|
||||
num++;
|
||||
}
|
||||
else {
|
||||
std::cout << item1.isbn() << "共有 " << num << "条销售记录" << std::endl;
|
||||
item1 = item2;
|
||||
num = 1;
|
||||
}
|
||||
}
|
||||
std::cout << item1.isbn() << "共有 " << num << "条销售记录" << std::endl;
|
||||
}
|
||||
else {
|
||||
std::cerr << "没有数据" <<std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout<< "Hello, World" <<std::endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Enter two numbers" << std::endl;
|
||||
int v1 = 0, v2 = 0;
|
||||
std::cin>> v1 >> v2;
|
||||
std::cout << "The product of " << v1 << " and " << v2 << " is "<< v1 * v2 << std::endl;
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
#include <iostream>
|
||||
|
||||
int main()
|
||||
{
|
||||
std::cout << "Enter two numbers" << std::endl;
|
||||
int v1 = 0, v2 = 0;
|
||||
std::cin >> v1 >> v2;
|
||||
std::cout << "The product of ";
|
||||
std::cout << v1;
|
||||
std::cout << " and ";
|
||||
std::cout << v2;
|
||||
std::cout << " is ";
|
||||
std::cout << v1 * v2;
|
||||
std::cout << std::endl;
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "Enter two numbers" << std::endl;
|
||||
int v1 = 0, v2 = 0;
|
||||
std::cin>> v1 >> v2;
|
||||
std::cout << "The sum of " << v1
|
||||
<< " and " << v2
|
||||
<< " is "<< v1 + v2 << std::endl;
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
/* 正常注释 /* 嵌套注释 */ 正常注释*/
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
std::cout << "/*";
|
||||
std::cout << "*/";
|
||||
std::cout << /* "*/ " */";
|
||||
std::cout << /* "*/" /* "/*" */;
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
#include <iostream>
|
||||
|
||||
int main() {
|
||||
int i = 50, sum = 0;
|
||||
while (i <= 100) {
|
||||
sum += i;
|
||||
i += 1;
|
||||
}
|
||||
std::cout << "sum is " << sum << std::endl;
|
||||
}
|
||||
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* This file contains code from "C++ Primer, Fifth Edition", by Stanley B.
|
||||
* Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the
|
||||
* copyright and warranty notices given in that book:
|
||||
*
|
||||
* "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo."
|
||||
*
|
||||
*
|
||||
* "The authors and publisher have taken care in the preparation of this book,
|
||||
* but make no expressed or implied warranty of any kind and assume no
|
||||
* responsibility for errors or omissions. No liability is assumed for
|
||||
* incidental or consequential damages in connection with or arising out of the
|
||||
* use of the information or programs contained herein."
|
||||
*
|
||||
* Permission is granted for this code to be used for educational purposes in
|
||||
* association with the book, given proper citation if and when posted or
|
||||
* reproduced.Any commercial use of this code requires the explicit written
|
||||
* permission of the publisher, Addison-Wesley Professional, a division of
|
||||
* Pearson Education, Inc. Send your request for permission, stating clearly
|
||||
* what code you would like to use, and in what specific way, to the following
|
||||
* address:
|
||||
*
|
||||
* Pearson Education, Inc.
|
||||
* Rights and Permissions Department
|
||||
* One Lake Street
|
||||
* Upper Saddle River, NJ 07458
|
||||
* Fax: (201) 236-3290
|
||||
*/
|
||||
|
||||
/* This file defines the Sales_item class used in chapter 1.
|
||||
* The code used in this file will be explained in
|
||||
* Chapter 7 (Classes) and Chapter 14 (Overloaded Operators)
|
||||
* Readers shouldn't try to understand the code in this file
|
||||
* until they have read those chapters.
|
||||
*/
|
||||
|
||||
#ifndef SALESITEM_H
|
||||
// we're here only if SALESITEM_H has not yet been defined
|
||||
#define SALESITEM_H
|
||||
|
||||
#include "Version_test.h"
|
||||
|
||||
// Definition of Sales_item class and related functions goes here
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
class Sales_item {
|
||||
// these declarations are explained section 7.2.1, p. 270
|
||||
// and in chapter 14, pages 557, 558, 561
|
||||
friend std::istream& operator>>(std::istream&, Sales_item&);
|
||||
friend std::ostream& operator<<(std::ostream&, const Sales_item&);
|
||||
friend bool operator<(const Sales_item&, const Sales_item&);
|
||||
friend bool
|
||||
operator==(const Sales_item&, const Sales_item&);
|
||||
public:
|
||||
// constructors are explained in section 7.1.4, pages 262 - 265
|
||||
// default constructor needed to initialize members of built-in type
|
||||
#if defined(IN_CLASS_INITS) && defined(DEFAULT_FCNS)
|
||||
Sales_item() = default;
|
||||
#else
|
||||
Sales_item(): units_sold(0), revenue(0.0) { }
|
||||
#endif
|
||||
Sales_item(const std::string &book):
|
||||
bookNo(book), units_sold(0), revenue(0.0) { }
|
||||
Sales_item(std::istream &is) { is >> *this; }
|
||||
public:
|
||||
// operations on Sales_item objects
|
||||
// member binary operator: left-hand operand bound to implicit this pointer
|
||||
Sales_item& operator+=(const Sales_item&);
|
||||
|
||||
// operations on Sales_item objects
|
||||
std::string isbn() const { return bookNo; }
|
||||
double avg_price() const;
|
||||
// private members as before
|
||||
private:
|
||||
std::string bookNo; // implicitly initialized to the empty string
|
||||
#ifdef IN_CLASS_INITS
|
||||
unsigned units_sold = 0; // explicitly initialized
|
||||
double revenue = 0.0;
|
||||
#else
|
||||
unsigned units_sold;
|
||||
double revenue;
|
||||
#endif
|
||||
};
|
||||
|
||||
// used in chapter 10
|
||||
inline
|
||||
bool compareIsbn(const Sales_item &lhs, const Sales_item &rhs)
|
||||
{ return lhs.isbn() == rhs.isbn(); }
|
||||
|
||||
// nonmember binary operator: must declare a parameter for each operand
|
||||
Sales_item operator+(const Sales_item&, const Sales_item&);
|
||||
|
||||
inline bool
|
||||
operator==(const Sales_item &lhs, const Sales_item &rhs)
|
||||
{
|
||||
// must be made a friend of Sales_item
|
||||
return lhs.units_sold == rhs.units_sold &&
|
||||
lhs.revenue == rhs.revenue &&
|
||||
lhs.isbn() == rhs.isbn();
|
||||
}
|
||||
|
||||
inline bool
|
||||
operator!=(const Sales_item &lhs, const Sales_item &rhs)
|
||||
{
|
||||
return !(lhs == rhs); // != defined in terms of operator==
|
||||
}
|
||||
|
||||
// assumes that both objects refer to the same ISBN
|
||||
Sales_item& Sales_item::operator+=(const Sales_item& rhs)
|
||||
{
|
||||
units_sold += rhs.units_sold;
|
||||
revenue += rhs.revenue;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// assumes that both objects refer to the same ISBN
|
||||
Sales_item
|
||||
operator+(const Sales_item& lhs, const Sales_item& rhs)
|
||||
{
|
||||
Sales_item ret(lhs); // copy (|lhs|) into a local object that we'll return
|
||||
ret += rhs; // add in the contents of (|rhs|)
|
||||
return ret; // return (|ret|) by value
|
||||
}
|
||||
|
||||
std::istream&
|
||||
operator>>(std::istream& in, Sales_item& s)
|
||||
{
|
||||
double price;
|
||||
in >> s.bookNo >> s.units_sold >> price;
|
||||
// check that the inputs succeeded
|
||||
if (in)
|
||||
s.revenue = s.units_sold * price;
|
||||
else
|
||||
s = Sales_item(); // input failed: reset object to default state
|
||||
return in;
|
||||
}
|
||||
|
||||
std::ostream&
|
||||
operator<<(std::ostream& out, const Sales_item& s)
|
||||
{
|
||||
out << s.isbn() << " " << s.units_sold << " "
|
||||
<< s.revenue << " " << s.avg_price();
|
||||
return out;
|
||||
}
|
||||
|
||||
double Sales_item::avg_price() const
|
||||
{
|
||||
if (units_sold)
|
||||
return revenue/units_sold;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* This file contains code from "C++ Primer, Fifth Edition", by Stanley B.
|
||||
* Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the
|
||||
* copyright and warranty notices given in that book:
|
||||
*
|
||||
* "Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo."
|
||||
*
|
||||
*
|
||||
* "The authors and publisher have taken care in the preparation of this book,
|
||||
* but make no expressed or implied warranty of any kind and assume no
|
||||
* responsibility for errors or omissions. No liability is assumed for
|
||||
* incidental or consequential damages in connection with or arising out of the
|
||||
* use of the information or programs contained herein."
|
||||
*
|
||||
* Permission is granted for this code to be used for educational purposes in
|
||||
* association with the book, given proper citation if and when posted or
|
||||
* reproduced. Any commercial use of this code requires the explicit written
|
||||
* permission of the publisher, Addison-Wesley Professional, a division of
|
||||
* Pearson Education, Inc. Send your request for permission, stating clearly
|
||||
* what code you would like to use, and in what specific way, to the following
|
||||
* address:
|
||||
*
|
||||
* Pearson Education, Inc.
|
||||
* Rights and Permissions Department
|
||||
* One Lake Street
|
||||
* Upper Saddle River, NJ 07458
|
||||
* Fax: (201) 236-3290
|
||||
*/
|
||||
|
||||
#ifndef VERSION_TEST_H
|
||||
#define VERSION_TEST_H
|
||||
|
||||
/* As of the first printing of C++ Primer, 5th Edition (July 2012),
|
||||
* the Microsoft Complier did not yet support a number of C++ 11 features.
|
||||
*
|
||||
* The code we distribute contains both normal C++ code and
|
||||
* workarounds for missing features. We use a series of CPP variables to
|
||||
* determine whether a given features is implemented in a given release
|
||||
* of the MS compiler. The base version we used to test the code in the book
|
||||
* is Compiler Version 17.00.50522.1 for x86.
|
||||
*
|
||||
* When new releases are available we will update this file which will
|
||||
* #define the features implmented in that release.
|
||||
*/
|
||||
|
||||
#if _MSC_FULL_VER == 170050522 || _MSC_FULL_VER == 170050727
|
||||
// base version, future releases will #define those features as they are
|
||||
// implemented by Microsoft
|
||||
|
||||
/* Code in this delivery use the following variables to control compilation
|
||||
|
||||
Variable tests C++ 11 Feature
|
||||
CONSTEXPR_VARS constexpr variables
|
||||
CONSTEXPR_FCNS constexpr functions
|
||||
CONSTEXPR_CTORS constexpr constructors and other member functions
|
||||
DEFAULT_FCNS = default
|
||||
DELETED_FCNS = delete
|
||||
FUNC_CPP __func__ local static
|
||||
FUNCTION_PTRMEM function template with pointer to member function
|
||||
IN_CLASS_INITS in class initializers
|
||||
INITIALIZER_LIST library initializer_list<T> template
|
||||
LIST_INIT list initialization of ordinary variables
|
||||
LROUND lround function in cmath
|
||||
NOEXCEPT noexcept specifier and noexcept operator
|
||||
SIZEOF_MEMBER sizeof class_name::member_name
|
||||
TEMPLATE_FCN_DEFAULT_ARGS default template arguments for function templates
|
||||
TYPE_ALIAS_DECLS type alias declarations
|
||||
UNION_CLASS_MEMS unions members that have constructors or copy control
|
||||
VARIADICS variadic templates
|
||||
*/
|
||||
#endif // ends compiler version check
|
||||
|
||||
#ifndef LROUND
|
||||
inline long lround(double d)
|
||||
{
|
||||
return (d >= 0) ? long(d + 0.5) : long(d - 0.5);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // ends header guard
|
||||
@ -0,0 +1,15 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
int main() {
|
||||
int i = 1, j = 2;
|
||||
int *p = &i;
|
||||
std::cout << p << " " << *p << std::endl;
|
||||
p = &j; //更改指针的值,令p指向另一个整数对象j
|
||||
std::cout << p << " " << *p << std::endl;
|
||||
*p = 10; // 显式改变指针p指向的内容
|
||||
std::cout << p << " " << *p << std::endl;
|
||||
j = 20; // 通过改变变量j的值
|
||||
std::cout << p << " " << *p << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
int main() {
|
||||
int i = 0, &r = i;
|
||||
auto a = r;
|
||||
const int ci = i, &cr = ci;
|
||||
auto b = ci;
|
||||
auto c = cr;
|
||||
auto d = &i;
|
||||
auto e = &ci;
|
||||
auto &g = ci;
|
||||
|
||||
std::cout << a << " "<< b << " " << c << " " << d << " " << e << " " << g << " " << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
int main() {
|
||||
const int i = 42;
|
||||
auto j = i;
|
||||
const auto &k = i;
|
||||
auto *p = &i;
|
||||
const auto j2 = i, &k2 = i;
|
||||
|
||||
std::cout << typeid(i).name() << std::endl;
|
||||
std::cout << typeid(j).name() << std::endl;
|
||||
std::cout << typeid(k).name() << std::endl;
|
||||
std::cout << typeid(p).name() << std::endl;
|
||||
std::cout << typeid(j2).name() << std::endl;
|
||||
std::cout << typeid(k2).name() << std::endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
int main() {
|
||||
int a = 1;
|
||||
auto c1 = a;
|
||||
decltype(a) c2 = a;
|
||||
decltype((a)) c3 = a;
|
||||
|
||||
const int d = 3;
|
||||
auto f1 = d;
|
||||
decltype(d) f2 = d;
|
||||
|
||||
std::cout << typeid(c1).name() << std::endl;
|
||||
std::cout << typeid(c2).name() << std::endl;
|
||||
std::cout << typeid(c3).name() << std::endl;
|
||||
std::cout << typeid(f1).name() << std::endl;
|
||||
std::cout << typeid(f2).name() << std::endl;
|
||||
|
||||
c1++;
|
||||
c2++;
|
||||
c3++;
|
||||
f1++;
|
||||
// f2++; f2是整型变量,不能执行自增操作
|
||||
|
||||
std::cout << c1 << " " << c2 << " "<< c3 << " "<< f1 << " "<< f2 << " " << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,14 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
int main() {
|
||||
unsigned u = 10, u2 = 42;
|
||||
std::cout << u2 - u << std::endl;
|
||||
std::cout << u - u2 << std::endl;
|
||||
int i = 10, i2 = 42;
|
||||
std::cout << i2 - i << std::endl;
|
||||
std::cout << i - i2 << std::endl;
|
||||
std::cout << i - u << std::endl;
|
||||
std::cout << u - i << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
int main() {
|
||||
int a = 1;
|
||||
auto c1 = a;
|
||||
decltype(a) c2 = a;
|
||||
decltype((a)) c3 = a;
|
||||
|
||||
const int d = 3;
|
||||
auto f1 = d;
|
||||
decltype(d) f2 = d;
|
||||
|
||||
std::cout << typeid(c1).name() << std::endl;
|
||||
std::cout << typeid(c2).name() << std::endl;
|
||||
std::cout << typeid(c3).name() << std::endl;
|
||||
std::cout << typeid(f1).name() << std::endl;
|
||||
std::cout << typeid(f2).name() << std::endl;
|
||||
|
||||
c1++;
|
||||
c2++;
|
||||
c3++;
|
||||
f1++;
|
||||
// f2++; f2是整型变量,不能执行自增操作
|
||||
|
||||
std::cout << c1 << " " << c2 << " "<< c3 << " "<< f1 << " "<< f2 << " " << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
#include <iostream>
|
||||
#include "Sales_data.h"
|
||||
|
||||
int main() {
|
||||
Sales_data item1, item2;
|
||||
int num = 1;
|
||||
std::cout << "请输入销售记录" << std::endl;
|
||||
|
||||
if (std::cin >> item1) {
|
||||
while (std::cin >> item2) {
|
||||
if (compareIsbn(item1, item2)) { //相同
|
||||
num++;
|
||||
}
|
||||
else {
|
||||
std::cout << item1.isbn() << "共有 " << num << "条销售记录" << std::endl;
|
||||
item1 = item2;
|
||||
num = 1;
|
||||
}
|
||||
}
|
||||
std::cout << item1.isbn() << "共有 " << num << "条销售记录" << std::endl;
|
||||
}
|
||||
else {
|
||||
std::cerr << "没有数据" <<std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -0,0 +1,14 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
int main() {
|
||||
unsigned u = 10, u2 = 42;
|
||||
std::cout << u2 - u << std::endl;
|
||||
std::cout << u - u2 << std::endl;
|
||||
int i = 10, i2 = 42;
|
||||
std::cout << i2 - i << std::endl;
|
||||
std::cout << i - i2 << std::endl;
|
||||
std::cout << i - u << std::endl;
|
||||
std::cout << u - i << std::endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
#include <iostream>
|
||||
|
||||
|
||||
int main() {
|
||||
std::cout << "2\x4d\012";
|
||||
std::cout <<"2\tM\n";
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,72 @@
|
||||
//
|
||||
// Created by ChenYL on 2022/5/14.
|
||||
//
|
||||
|
||||
#ifndef PAT_SALES_DATA_H
|
||||
#define PAT_SALES_DATA_H
|
||||
# include <iostream>
|
||||
# include <string>
|
||||
|
||||
class Sales_data {
|
||||
friend std::istream& operator >> (std::istream&, Sales_data&);
|
||||
friend std::ostream& operator >> (std::ostream&, const Sales_data&);
|
||||
friend bool operator < (const Sales_data&, const Sales_data&);
|
||||
friend bool operator == (const Sales_data&, const Sales_data&);
|
||||
|
||||
public:
|
||||
Sales_data() = default;
|
||||
Sales_data(const std::string &book):bookNo(book) { }
|
||||
Sales_data(std::istream &is) {is >> *this;}
|
||||
|
||||
public:
|
||||
Sales_data& operator += (const Sales_data&);
|
||||
std::string isbn() const { return bookNo;}
|
||||
|
||||
private:
|
||||
std::string bookNo; //书籍编号
|
||||
unsigned units_sold = 0; //销售量
|
||||
double selling_price = 0.0; //原始价格
|
||||
double sale_price =0.0; //市售价格
|
||||
double discount = 0.0; //折扣
|
||||
};
|
||||
|
||||
inline bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) {
|
||||
return lhs.isbn() == rhs.isbn();
|
||||
}
|
||||
|
||||
inline bool operator !=(const Sales_data &lhs, const Sales_data &rhs) {
|
||||
return !(lhs == rhs); //基于运算符==给出!=的定义
|
||||
}
|
||||
|
||||
Sales_data& Sales_data::operator+=(const Sales_data& rhs) {
|
||||
units_sold += rhs.units_sold;
|
||||
sale_price = (rhs.sale_price * rhs.units_sold + sale_price * units_sold) / (rhs.units_sold + units_sold);
|
||||
if (selling_price != 0)
|
||||
discount = sale_price / selling_price;
|
||||
return *this;
|
||||
}
|
||||
|
||||
Sales_data operator + (const Sales_data &lhs, const Sales_data &rhs)
|
||||
{
|
||||
Sales_data ret(lhs);//把hs的内容拷贝到临时变量ret中,这种做法便于运算
|
||||
ret += rhs; //把rhs的内容加入其中
|
||||
return ret; //返回ret
|
||||
}
|
||||
|
||||
std::istream& operator >> (std::istream& in, Sales_data& s) {
|
||||
in >> s.bookNo >> s.units_sold >> s.selling_price >> s.sale_price;
|
||||
if (in && s.selling_price != 0)
|
||||
s.discount = s.sale_price / s.selling_price;
|
||||
else
|
||||
s = Sales_data();//输入错误,重置输入的数据
|
||||
return in;
|
||||
}
|
||||
|
||||
std::ostream& operator << (std::ostream& out,const Sales_data& s)
|
||||
{
|
||||
out << s.isbn();
|
||||
// out << s.isbn() <<" " << s.units_sold << " "<< s.selling_price <<""<< s.sale_price <<""<< s.discount;
|
||||
return out;
|
||||
}
|
||||
|
||||
#endif //PAT_SALES_DATA_H
|
||||
@ -0,0 +1,11 @@
|
||||
#include <iostream>
|
||||
using std::cout;
|
||||
using std::endl;
|
||||
int main() {
|
||||
int i = 50, sum = 0;
|
||||
while (i <= 100) {
|
||||
sum += i;
|
||||
i += 1;
|
||||
}
|
||||
cout << "sum is " << sum << endl;
|
||||
}
|
||||
@ -0,0 +1,16 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
string s;
|
||||
cout << "请输入一个字符串" <<endl;
|
||||
getline(cin, s);
|
||||
for (auto c : s) {
|
||||
if (!ispunct(c))
|
||||
cout << c
|
||||
}
|
||||
cout << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<int> v;
|
||||
int i;
|
||||
char cont = 'y';
|
||||
cout << "请输入数字" << endl;
|
||||
while(cin >> i) {
|
||||
v.push_back(i);
|
||||
cout << "是否继续(y or n)" << endl;
|
||||
cin >> cont;
|
||||
if (cont == 'y' || cont == 'Y'){
|
||||
cout << "请输入下一个整数" << endl;
|
||||
}else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto &m : v)
|
||||
cout << "输入的整数为" <<m << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<string> v;
|
||||
string i;
|
||||
char cont = 'y';
|
||||
cout << "请输入字符串" << endl;
|
||||
while(cin >> i) {
|
||||
v.push_back(i);
|
||||
cout << "是否继续(y or n)" << endl;
|
||||
cin >> cont;
|
||||
if (cont == 'y' || cont == 'Y'){
|
||||
cout << "请输入下一个字符串" << endl;
|
||||
}else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto &m : v)
|
||||
cout << m << " ";
|
||||
cout << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
vector<int> v1; // size:0, no values.
|
||||
vector<int> v2(10); // size:10, value:0
|
||||
vector<int> v3(10, 42); // size:10, value:42
|
||||
vector<int> v4{ 10 }; // size:1, value:10
|
||||
vector<int> v5{ 10, 42 }; // size:2, value:10, 42
|
||||
vector<string> v6{ 10 }; // size:10, value:""
|
||||
vector<string> v7{ 10, "hi" }; // size:10, value:"hi"
|
||||
|
||||
cout << "v1 size :" << v1.size() << endl;
|
||||
cout << "v2 size :" << v2.size() << endl;
|
||||
cout << "v3 size :" << v3.size() << endl;
|
||||
cout << "v4 size :" << v4.size() << endl;
|
||||
cout << "v5 size :" << v5.size() << endl;
|
||||
cout << "v6 size :" << v6.size() << endl;
|
||||
cout << "v7 size :" << v7.size() << endl;
|
||||
|
||||
cout << "v1 content: ";
|
||||
for (auto i : v1)
|
||||
{
|
||||
cout << i << " , ";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
cout << "v2 content: ";
|
||||
for (auto i : v2)
|
||||
{
|
||||
cout << i << " , ";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
cout << "v3 content: ";
|
||||
for (auto i : v3)
|
||||
{
|
||||
cout << i << " , ";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
cout << "v4 content: ";
|
||||
for (auto i : v4)
|
||||
{
|
||||
cout << i << " , ";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
cout << "v5 content: ";
|
||||
for (auto i : v5)
|
||||
{
|
||||
cout << i << " , ";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
cout << "v6 content: ";
|
||||
for (auto i : v6)
|
||||
{
|
||||
cout << i << " , ";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
cout << "v7 content: ";
|
||||
for (auto i : v7)
|
||||
{
|
||||
cout << i << " , ";
|
||||
}
|
||||
cout << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<string> v;
|
||||
string i;
|
||||
char cont = 'y';
|
||||
cout << "请输入字符串" << endl;
|
||||
while (cin >> i) {
|
||||
v.push_back(i);
|
||||
cout << "是否继续(y or n)" << endl;
|
||||
cin >> cont;
|
||||
if (cont == 'y' || cont == 'Y') {
|
||||
cout << "请输入下一个字符串" << endl;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (auto &m: v) {
|
||||
for (auto &c: m){
|
||||
c = toupper(c);
|
||||
}
|
||||
cout << m << " ";
|
||||
cout << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
// string line;
|
||||
// cout << "请输入字符串" <<endl;
|
||||
// while (getline(cin, line)) { // 读入一整行
|
||||
// cout << line << endl;
|
||||
// }
|
||||
string word;
|
||||
while (cin >> word) { // 读入一个词
|
||||
cout << word << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,37 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<int> v;
|
||||
int w;
|
||||
cout << "请输入数字" << endl;
|
||||
while (cin >> w) {
|
||||
v.push_back(w);
|
||||
}
|
||||
if (v.size() == 0) {
|
||||
cout << "没有任何元素" <<endl;
|
||||
return -1;
|
||||
}
|
||||
cout << "相邻一组数的和为:" <<endl;
|
||||
for(decltype((v.size())) i = 0; i < v.size() - 1; i += 2)
|
||||
cout << v[i] + v[i + 1] << " ";
|
||||
|
||||
if (v.size() % 2 != 0)
|
||||
cout << v[v.size() - 1];
|
||||
cout <<endl;
|
||||
|
||||
cout << "首尾两项数的和为:" <<endl;
|
||||
decltype(v.size()) i = 0, j = v.size() - 1;
|
||||
while (i < j) {
|
||||
cout << v[i] +v[j] << " ";
|
||||
++i,--j;
|
||||
}
|
||||
if (v.size() % 2 != 0)
|
||||
cout << v[v.size() / 2];
|
||||
cout <<endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,85 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
vector<int> v1; // size:0, no values.
|
||||
vector<int> v2(10); // size:10, value:0
|
||||
vector<int> v3(10, 42); // size:10, value:42
|
||||
vector<int> v4{ 10 }; // size:1, value:10
|
||||
vector<int> v5{ 10, 42 }; // size:2, value:10, 42
|
||||
vector<string> v6{ 10 }; // size:10, value:""
|
||||
vector<string> v7{ 10, "hi" }; // size:10, value:"hi"
|
||||
|
||||
cout << "v1 size :" << v1.size() << endl;
|
||||
cout << "v2 size :" << v2.size() << endl;
|
||||
cout << "v3 size :" << v3.size() << endl;
|
||||
cout << "v4 size :" << v4.size() << endl;
|
||||
cout << "v5 size :" << v5.size() << endl;
|
||||
cout << "v6 size :" << v6.size() << endl;
|
||||
cout << "v7 size :" << v7.size() << endl;
|
||||
|
||||
cout << "v1 content: ";
|
||||
if (v1.cbegin() != v1.cend()) {
|
||||
for (auto it = v1.cbegin(); it != v1.cend(); it++) {
|
||||
cout << *it << " , ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
cout << "v2 content: ";
|
||||
if (v2.cbegin() != v2.cend()) {
|
||||
for (auto it = v2.cbegin(); it != v2.cend(); it++) {
|
||||
cout << *it << " , ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
cout << "v3 content: ";
|
||||
if (v3.cbegin()!= v3.cend()) {
|
||||
for (auto it = v3.cbegin(); it != v3.cend(); it++){
|
||||
cout << *it << " , ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
|
||||
|
||||
cout << "v4 content: ";
|
||||
if (v4.cbegin() != v4.cend()) {
|
||||
for(auto it = v4.cbegin(); it != v4.cend(); it++) {
|
||||
cout << *it << ",";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
cout << "v5 content: ";
|
||||
if (v5.cbegin() != v5.cend()) {
|
||||
for(auto it = v5.cbegin(); it != v5.cend(); it++) {
|
||||
cout << *it << ",";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
|
||||
cout << "v6 content: ";
|
||||
if (v6.cbegin() != v6.cend()) {
|
||||
for(auto it = v6.cbegin(); it != v6.cend(); it++) {
|
||||
cout << *it << ",";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
|
||||
cout << "v7 content: ";
|
||||
if (v7.cbegin() != v7.cend()) {
|
||||
for(auto it = v7.cbegin(); it != v7.cend(); it++) {
|
||||
cout << *it << ",";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,23 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<string> v;
|
||||
string w;
|
||||
cout << "请输入一段话" << endl;
|
||||
|
||||
while (getline(cin, w)) { //利用getline读取一句话,直接回车产生一个空串,表示段落结束
|
||||
v.push_back(w);
|
||||
}
|
||||
for(auto it = v.begin(); it != v.end() && !it -> empty();it++) {
|
||||
for (auto it2 = it->begin(); it2 != it->end(); it2++){
|
||||
*it2 = toupper(*it2);
|
||||
}
|
||||
cout << *it << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
|
||||
vector<int> words;
|
||||
for (int i = 0; i < 10; i++) {
|
||||
words.push_back(rand() % 1000);
|
||||
}
|
||||
cout << "随机生成的数字是:" <<endl;
|
||||
for(auto it = words.begin(); it != words.end() && !words.empty(); it++) {
|
||||
cout << *it << " ";
|
||||
}
|
||||
cout <<endl;
|
||||
|
||||
cout << "翻倍后的数字是:" <<endl;
|
||||
|
||||
for(auto it = words.begin(); it != words.end() && !words.empty(); it++) {
|
||||
*it = *it * 2;
|
||||
cout << *it << " ";
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<int> v;
|
||||
int w;
|
||||
cout << "请输入数字" << endl;
|
||||
while (cin >> w) {
|
||||
v.push_back(w);
|
||||
}
|
||||
if (v.cbegin() == v.cend()) {
|
||||
cout << "没有任何元素" <<endl;
|
||||
return -1;
|
||||
}
|
||||
cout << "首尾两项的和为:" <<endl;
|
||||
auto begin = v.begin();
|
||||
auto end = v.end();
|
||||
for(auto it = begin; it != begin + (end - begin) / 2; it ++){
|
||||
cout << (*it + *(begin + (end - it) - 1))<< " ";
|
||||
|
||||
if (v.size() % 2 != 0)
|
||||
cout << *(begin + (end - it + 1)/ 2);
|
||||
cout <<endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
vector<unsigned> v(11);
|
||||
auto it = v.begin();
|
||||
int i;
|
||||
cout << "请输入一组成绩"<<endl;
|
||||
while(cin >> i) {
|
||||
if (i < 101) {
|
||||
++*(it + i / 10);
|
||||
}
|
||||
}
|
||||
cout << "各分数段的人数分布式" << endl;
|
||||
for (it = v.begin(); it!= v.end();it++) {
|
||||
cout << *it << " ";
|
||||
}
|
||||
cout << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
const int sz = 10;
|
||||
int num[sz];
|
||||
for (int i = 0; i < sz; i++) {
|
||||
num[i] = i;
|
||||
}
|
||||
|
||||
for (auto val : num) {
|
||||
cout << val << " ";
|
||||
}
|
||||
cout << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,38 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
const int sz = 10;
|
||||
vector<int> num3;
|
||||
vector<int> num4;
|
||||
int num1[sz], num2[sz];
|
||||
for (int i = 0; i < sz; i++) {
|
||||
num1[i] = i;
|
||||
}
|
||||
for (int i = 0; i < sz; i++) {
|
||||
num2[i] = num1[i];
|
||||
}
|
||||
cout << "num2中的值为" << endl;
|
||||
for (auto val : num2) {
|
||||
cout << val << " ";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
for (int i = 0; i < sz; i++) {
|
||||
num3.push_back(i);
|
||||
}
|
||||
for (int i = 0; i < sz; i++) {
|
||||
num4.push_back(num3[i]);
|
||||
}
|
||||
cout << "num4中的值为" << endl;
|
||||
for (auto val : num4) {
|
||||
cout << val << " ";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using std::vector;
|
||||
|
||||
int main() {
|
||||
const int sz = 10;
|
||||
int nums[sz];
|
||||
for (int i = 0; i < sz; i++) {
|
||||
nums[i] = i;
|
||||
}
|
||||
|
||||
|
||||
for (auto ptr = nums; ptr != nums + sz; ++ptr) *ptr = 0;
|
||||
// int *p = begin(nums);
|
||||
// while (p != end(nums)) {
|
||||
// *p = 0;
|
||||
// p++;
|
||||
// }
|
||||
|
||||
for (auto num : nums) {
|
||||
cout << num << " ";
|
||||
}
|
||||
cout << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,42 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <iterator>
|
||||
|
||||
using std::begin; using std::end; using std::cout; using std::endl; using std::vector;
|
||||
|
||||
// pb point to begin of the array, pe point to end of the array.
|
||||
bool compare(int* const pb1, int* const pe1, int* const pb2, int* const pe2)
|
||||
{
|
||||
if ((pe1 - pb1) != (pe2 - pb2)) // have different size.
|
||||
return false;
|
||||
else
|
||||
{
|
||||
for (int* i = pb1, *j = pb2; (i != pe1) && (j != pe2); ++i, ++j)
|
||||
if (*i != *j) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
int arr1[3] = { 0, 1, 2 };
|
||||
int arr2[3] = { 0, 2, 4 };
|
||||
|
||||
if (compare(begin(arr1), end(arr1), begin(arr2), end(arr2)))
|
||||
cout << "The two arrays are equal." << endl;
|
||||
else
|
||||
cout << "The two arrays are not equal." << endl;
|
||||
|
||||
cout << "==========" << endl;
|
||||
|
||||
vector<int> vec1 = { 0, 1, 2 };
|
||||
vector<int> vec2 = { 0, 1, 2 };
|
||||
|
||||
if (vec1 == vec2)
|
||||
cout << "The two vectors are equal." << endl;
|
||||
else
|
||||
cout << "The two vectors are not equal." << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
string str1, str2;
|
||||
cout << "请输入两个字符串" <<endl;
|
||||
cin >> str1 >> str2;
|
||||
|
||||
if (str1 > str2)
|
||||
cout << "第一个字符串大于第二个字符串" << endl;
|
||||
else if (str1 < str2)
|
||||
cout << "第一个字符串小于第二个字符串" << endl;
|
||||
else
|
||||
cout << "第一个字符串等于第二个字符串" << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
char str1[80], str2[80];
|
||||
|
||||
cout << "请输入两个字符串" <<endl;
|
||||
cin >> str1 >> str2;
|
||||
auto result = strcmp(str1, str2);
|
||||
switch(result) {
|
||||
case 1:
|
||||
cout << "第一个字符串大于第二个字符串" << endl;
|
||||
break;
|
||||
case -1:
|
||||
cout << "第一个字符串小于第二个字符串" << endl;
|
||||
break;
|
||||
case 0 :
|
||||
cout << "第一个字符串等于第二个字符串" << endl;
|
||||
break;
|
||||
default:
|
||||
cout << "未定义的结果" << endl;
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
string str1, str2;
|
||||
cout << "请输入两个字符串" << endl;
|
||||
cin >> str1;
|
||||
cin >> str2;
|
||||
|
||||
if (str1 == str2) {
|
||||
cout << "两个字符相等" << endl;
|
||||
}else if (str1 > str2) {
|
||||
cout << str1 << endl;
|
||||
}else {
|
||||
cout << str2 <<endl;
|
||||
}
|
||||
auto len1 = str1.size();
|
||||
auto len2 = str2.size();
|
||||
|
||||
if (len1 == len2) {
|
||||
cout << str1 << "和" << str2 << "的长度是" << len2 << endl;
|
||||
}else if (len1 > len2)
|
||||
cout << str1 << "比" << str2 << "的长度多" << len1 - len2 << endl;
|
||||
else
|
||||
cout << str1 << "比" << str2 << "的长度少" << len2 - len1 << endl;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,21 @@
|
||||
#include <iostream>
|
||||
#include <cstring>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
char str1[] = "Welcome to";
|
||||
char str2[] = "C++";
|
||||
|
||||
char result[strlen(str1) + strlen(str2) - 1];
|
||||
|
||||
strcpy(result, str1);
|
||||
strcat(result, str2);
|
||||
|
||||
cout << "第一个字符为" << str1 <<endl;
|
||||
cout << "第二个字符为" << str2 <<endl;
|
||||
cout << "拼接后的字符为" << result <<endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,19 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
int nums[] = {1, 2, 3, 4} ;
|
||||
|
||||
vector<int> words(begin(nums), end(nums));
|
||||
|
||||
for (auto val : words) {
|
||||
cout << val << " ";
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
|
||||
const int sz = 10;
|
||||
vector<int> words;
|
||||
int nums[10];
|
||||
|
||||
for (int i = 0; i < sz; i++) {
|
||||
words.push_back(i);
|
||||
}
|
||||
|
||||
auto it = words.cbegin();
|
||||
for (auto & val : nums) {
|
||||
val = *it;
|
||||
cout << val << " ";
|
||||
it++;
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
// 范围for语句
|
||||
int ia[2][2] = {1, 2, 3, 4};
|
||||
for (int (&row)[2] : ia){
|
||||
for(int &val : row) {
|
||||
cout << val << " ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
// 普通for语句,下标形式
|
||||
for (int i = 0; i != 2; i++){
|
||||
for(int j = 0; j != 2; j++) {
|
||||
cout << ia[i][j] << " ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
// 普通for语句,指针形式
|
||||
for (int (*p)[2] = ia; p != ia + 2; p++){
|
||||
for(int *q = *p; q != *p + 2; q++) {
|
||||
cout << *q << " ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using int_array = int[2];
|
||||
|
||||
int main()
|
||||
{
|
||||
// 范围for语句
|
||||
int ia[2][2] = {1, 2, 3, 4};
|
||||
for (int_array &row : ia){
|
||||
for(int &val : row) {
|
||||
cout << val << " ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
// 普通for语句,下标形式
|
||||
for (int i = 0; i != 2; i++){
|
||||
for(int j = 0; j != 2; j++) {
|
||||
cout << ia[i][j] << " ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
// 普通for语句,指针形式
|
||||
for (int_array *p = ia; p != ia + 2; p++){
|
||||
for(int *q = *p; q != *p + 2; q++) {
|
||||
cout << *q << " ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
using namespace std;
|
||||
using int_array = int[2];
|
||||
|
||||
int main()
|
||||
{
|
||||
// 范围for语句
|
||||
int ia[2][2] = {1, 2, 3, 4};
|
||||
for (auto &row : ia){
|
||||
for(auto &val : row) {
|
||||
cout << val << " ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
// 普通for语句,下标形式
|
||||
for (auto i = 0; i != 2; i++){
|
||||
for(auto j = 0; j != 2; j++) {
|
||||
cout << ia[i][j] << " ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
// 普通for语句,指针形式
|
||||
for (auto *p = ia; p != ia + 2; p++){
|
||||
for(auto *q = *p; q != *p + 2; q++) {
|
||||
cout << *q << " ";
|
||||
}
|
||||
cout << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
char cont = 'y';
|
||||
string s, result;
|
||||
cout << "请输入字符串" << endl;
|
||||
while(cin >> s) {
|
||||
if (!result.size())
|
||||
result += s;
|
||||
else
|
||||
result = result + " " + s;
|
||||
cout << "是否继续(y or n)" << endl;
|
||||
cin >> cont;
|
||||
if (cont == 'y' || cont == 'Y'){
|
||||
cout << "请输入下一个字符串" << endl;
|
||||
}else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
cout << "拼接后的字符为" <<result << endl;
|
||||
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
string s;
|
||||
cout << "请输入一个字符串" <<endl;
|
||||
getline(cin, s);
|
||||
for (auto &c : s) {
|
||||
c = 'X';
|
||||
}
|
||||
cout << s << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
string s;
|
||||
cout << "请输入一个字符串" <<endl;
|
||||
getline(cin, s);
|
||||
for (char &c : s) {
|
||||
c = 'X';
|
||||
}
|
||||
cout << s << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,17 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
// while
|
||||
int main() {
|
||||
string s;
|
||||
cout << "请输入一个字符串" <<endl;
|
||||
getline(cin, s);
|
||||
int i = 0;
|
||||
while (s[i] !='\0') {
|
||||
s[i] = 'X';
|
||||
++i;
|
||||
}
|
||||
cout << s << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
string s;
|
||||
cout << "请输入一个字符串" <<endl;
|
||||
getline(cin, s);
|
||||
for (unsigned int i = 0; i < s.size(); i++) {
|
||||
s[i] = 'X';
|
||||
}
|
||||
cout << s << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,31 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
vector<int> vInt;
|
||||
const int sz = 10;
|
||||
srand((unsigned ) time(NULL));
|
||||
|
||||
cout <<"数组的初始值为" << endl;
|
||||
|
||||
for(int i = 0; i != sz; ++i) {
|
||||
vInt.push_back(rand() % 100);
|
||||
cout << vInt[i] << " ";
|
||||
}
|
||||
cout <<endl;
|
||||
|
||||
for(auto &val : vInt) {
|
||||
val = (val % 2 == 0) ? val : val * 2;
|
||||
}
|
||||
cout <<"最终数组为" << endl;
|
||||
|
||||
for(auto it = vInt.cbegin(); it != vInt.cend(); ++it) {
|
||||
cout << *it << " ";
|
||||
}
|
||||
cout <<endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <ctime>
|
||||
#include <cstdlib>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main() {
|
||||
|
||||
string finalGrade;
|
||||
int grade;
|
||||
|
||||
cout <<"请输入成绩" << endl;
|
||||
|
||||
while (cin >> grade && grade >=0 && grade <=100) {
|
||||
finalGrade = (grade >90) ? "high pass": (grade > 75)? "pass": (grade > 60) ? "low pass": "fail";
|
||||
}
|
||||
cout << "等级为" << finalGrade << endl;
|
||||
|
||||
// -------------------------
|
||||
if (grade > 90) cout << "high pass";
|
||||
else if (grade < 60) cout << "fail";
|
||||
else if (grade < 75) cout << "low pass";
|
||||
else cout << "pass";
|
||||
cout << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,24 @@
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
cout << "bool:\t\t" << sizeof(bool) << " bytes" << endl << endl;
|
||||
|
||||
cout << "char:\t\t" << sizeof(char) << " bytes" << endl;
|
||||
cout << "wchar_t:\t" << sizeof(wchar_t) << " bytes" << endl;
|
||||
cout << "char16_t:\t" << sizeof(char16_t) << " bytes" << endl;
|
||||
cout << "char32_t:\t" << sizeof(char32_t) << " bytes" << endl << endl;
|
||||
|
||||
cout << "short:\t\t" << sizeof(short) << " bytes" << endl;
|
||||
cout << "int:\t\t" << sizeof(int) << " bytes" << endl;
|
||||
cout << "long:\t\t" << sizeof(long) << " bytes" << endl;
|
||||
cout << "long long:\t" << sizeof(long long) << " bytes" << endl << endl;
|
||||
|
||||
cout << "float:\t\t" << sizeof(float) << " bytes" << endl;
|
||||
cout << "double:\t\t" << sizeof(double) << " bytes" << endl;
|
||||
cout << "long double:\t" << sizeof(long double) << " bytes" << endl << endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
|
||||
char ch;
|
||||
cout << "请输入一段文本" << endl;
|
||||
while (cin >> ch)
|
||||
{
|
||||
if (ch == 'a') ++aCnt;
|
||||
else if (ch == 'e') ++eCnt;
|
||||
else if (ch == 'i') ++iCnt;
|
||||
else if (ch == 'o') ++oCnt;
|
||||
else if (ch == 'u') ++uCnt;
|
||||
}
|
||||
cout << "Number of vowel a: \t" << aCnt << '\n'
|
||||
<< "Number of vowel e: \t" << eCnt << '\n'
|
||||
<< "Number of vowel i: \t" << iCnt << '\n'
|
||||
<< "Number of vowel o: \t" << oCnt << '\n'
|
||||
<< "Number of vowel u: \t" << uCnt << '\n'
|
||||
<< "Total number: \t" << aCnt + eCnt + iCnt + oCnt +uCnt<< endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
unsigned int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
|
||||
unsigned int spaceCnt = 0, tabCnt = 0,newlineCnt = 0;
|
||||
char ch;
|
||||
cout << "请输入一段文本" << endl;
|
||||
while (cin >> ch)
|
||||
{
|
||||
switch (ch) {
|
||||
case 'a':
|
||||
case 'A':
|
||||
++aCnt;
|
||||
break;
|
||||
case 'e':
|
||||
case 'E':
|
||||
++eCnt;
|
||||
break;
|
||||
case 'I':
|
||||
case 'i':
|
||||
++iCnt;
|
||||
break;
|
||||
case 'o':
|
||||
case 'O':
|
||||
++oCnt;
|
||||
break;
|
||||
case 'u':
|
||||
case 'U':
|
||||
++uCnt;
|
||||
break;
|
||||
case ' ':
|
||||
++spaceCnt;
|
||||
break;
|
||||
case '\t':
|
||||
++tabCnt;
|
||||
break;
|
||||
case '\n':
|
||||
++newlineCnt;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
cout << "Number of vowel a: \t" << aCnt << '\n'
|
||||
<< "Number of vowel e: \t" << eCnt << '\n'
|
||||
<< "Number of vowel i: \t" << iCnt << '\n'
|
||||
<< "Number of vowel o: \t" << oCnt << '\n'
|
||||
<< "Number of vowel u: \t" << uCnt << '\n'
|
||||
<< "Number of space: \t" << spaceCnt << '\n'
|
||||
<< "Number of tab: \t" << tabCnt << '\n'
|
||||
<< "Number of newline: \t" << newlineCnt << '\n'
|
||||
<< "Total number: \t" << aCnt + eCnt + iCnt + oCnt +uCnt + spaceCnt + tabCnt + newlineCnt<< endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
unsigned int flCnt = 0, ffCnt = 0, fiCnt = 0;
|
||||
char ch, prech= '\0';
|
||||
cout << "请输入一段文本" << endl;
|
||||
while (cin >> ch)
|
||||
{
|
||||
bool flag = true;
|
||||
if (prech == 'f') {
|
||||
switch (ch) {
|
||||
case 'f':
|
||||
++ffCnt;
|
||||
flag = false;
|
||||
break;
|
||||
case 'l':
|
||||
++flCnt;
|
||||
break;
|
||||
case 'i':
|
||||
++flCnt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!flag) {
|
||||
prech = '\0';
|
||||
}else {
|
||||
prech = ch;}
|
||||
}
|
||||
cout << "Number of ff : \t" << ffCnt << '\n'
|
||||
<< "Number of fi \t" << fiCnt << '\n'
|
||||
<< "Number of fl: \t" << flCnt << '\n'
|
||||
<< endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,32 @@
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
string currStr, preStr = " ", maxStr;
|
||||
|
||||
int currCnt = 1, maxCnt = 0;
|
||||
|
||||
|
||||
while (cin >> currStr) {
|
||||
//
|
||||
if (currStr == preStr) {
|
||||
++currCnt;
|
||||
if (currCnt > maxCnt) {
|
||||
maxCnt = currCnt;
|
||||
maxStr = currStr;
|
||||
}
|
||||
}else {
|
||||
currCnt = 1;
|
||||
}
|
||||
preStr = currStr;
|
||||
}
|
||||
if (maxCnt >1) {
|
||||
cout << "出现最多的字符串是 : " << maxStr << ", 次数是:" << maxCnt << endl;
|
||||
}else {
|
||||
cout << "每个字符串都出现了一次" << endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
int g;
|
||||
|
||||
cout <<"请输入成绩:"<<endl;
|
||||
|
||||
cin >> g;
|
||||
|
||||
if (g < 0 || g > 100) {
|
||||
cout << "该成绩不合法" <<endl;
|
||||
return -1;
|
||||
}
|
||||
if(g == 100) {
|
||||
cout << "等级成绩是:" << "A++" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(g < 60) {
|
||||
cout << "等级成绩是:" << "F" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i = g /10;
|
||||
int j = g % 10;
|
||||
string score, level, finalGrade;
|
||||
|
||||
if(i == 9)
|
||||
score = "A";
|
||||
else if (i == 8)
|
||||
score = "B";
|
||||
else if (i == 7)
|
||||
score = "C";
|
||||
else
|
||||
score = "D";
|
||||
|
||||
if (j > 7) {
|
||||
level = "+";
|
||||
}else if (j < 3) {
|
||||
level = "-";
|
||||
}else {
|
||||
level = "";
|
||||
}
|
||||
finalGrade = score + level;
|
||||
|
||||
cout << "等级成绩是:" << finalGrade << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
using namespace std;
|
||||
|
||||
int main()
|
||||
{
|
||||
|
||||
int g;
|
||||
|
||||
cout <<"请输入成绩:"<<endl;
|
||||
|
||||
cin >> g;
|
||||
|
||||
if (g < 0 || g > 100) {
|
||||
cout << "该成绩不合法" <<endl;
|
||||
return -1;
|
||||
}
|
||||
if(g == 100) {
|
||||
cout << "等级成绩是:" << "A++" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if(g < 60) {
|
||||
cout << "等级成绩是:" << "F" << endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int i = g /10;
|
||||
int j = g % 10;
|
||||
string score, level, finalGrade;
|
||||
|
||||
score = (i == 9) ? "A" :(i == 8) ? "B": (i == 7)? "C":"D";
|
||||
level = (j > 7) ? "+" : (j < 3)? "-": "";
|
||||
|
||||
finalGrade = score + level;
|
||||
|
||||
cout << "等级成绩是:" << finalGrade << endl;
|
||||
return 0;
|
||||
}
|
||||
@ -0,0 +1,27 @@
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
unsigned aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;
|
||||
char ch;
|
||||
cout << "请输入一段文本" << endl;
|
||||
while (cin >> ch)
|
||||
{
|
||||
if (ch == 'a') ++aCnt;
|
||||
else if (ch == 'e') ++eCnt;
|
||||
else if (ch == 'i') ++iCnt;
|
||||
else if (ch == 'o') ++oCnt;
|
||||
else if (ch == 'u') ++uCnt;
|
||||
}
|
||||
cout << "Number of vowel a: \t" << aCnt << '\n'
|
||||
<< "Number of vowel e: \t" << eCnt << '\n'
|
||||
<< "Number of vowel i: \t" << iCnt << '\n'
|
||||
<< "Number of vowel o: \t" << oCnt << '\n'
|
||||
<< "Number of vowel u: \t" << uCnt << '\n'
|
||||
<< "Total number: \t" << aCnt + eCnt + iCnt + oCnt +uCnt<< endl;
|
||||
|
||||
return 0;
|
||||
}
|
||||
149
docs/Cpp-Notes-main/Cpp-Primer/1.开始.md
Normal file
149
docs/Cpp-Notes-main/Cpp-Primer/1.开始.md
Normal file
@ -0,0 +1,149 @@
|
||||
---
|
||||
sort: 1
|
||||
---
|
||||
|
||||
# 开始
|
||||
|
||||
## 编写简单的C++程序
|
||||
|
||||
一个函数的定义包含四部分:返回类型、函数名、一个括号包围的形参列表(允许为空)以及函数体。
|
||||
|
||||
## 熟悉编译器
|
||||
|
||||
**g++**:
|
||||
|
||||
- 编译:`g++ 1.1.cpp -o main`
|
||||
- 运行:`./main`
|
||||
- 查看运行状态:`echo $?`
|
||||
- 编译多个文件:`g++ ch2.cpp Sales_item.cc -o main`
|
||||
|
||||
输入 `g++ --help`,查看编译器选项:
|
||||
|
||||
```
|
||||
Usage: g++ [options] file...
|
||||
Options:
|
||||
-pass-exit-codes Exit with highest error code from a phase
|
||||
--help Display this information
|
||||
--target-help Display target specific command line options
|
||||
--help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...]
|
||||
Display specific types of command line options
|
||||
(Use '-v --help' to display command line options of sub-processes)
|
||||
--version Display compiler version information
|
||||
-dumpspecs Display all of the built in spec strings
|
||||
-dumpversion Display the version of the compiler
|
||||
-dumpmachine Display the compiler's target processor
|
||||
-print-search-dirs Display the directories in the compiler's search path
|
||||
-print-libgcc-file-name Display the name of the compiler's companion library
|
||||
-print-file-name=<lib> Display the full path to library <lib>
|
||||
-print-prog-name=<prog> Display the full path to compiler component <prog>
|
||||
-print-multiarch Display the target's normalized GNU triplet, used as
|
||||
a component in the library path
|
||||
-print-multi-directory Display the root directory for versions of libgcc
|
||||
-print-multi-lib Display the mapping between command line options and
|
||||
multiple library search directories
|
||||
-print-multi-os-directory Display the relative path to OS libraries
|
||||
-print-sysroot Display the target libraries directory
|
||||
-print-sysroot-headers-suffix Display the sysroot suffix used to find headers
|
||||
-Wa,<options> Pass comma-separated <options> on to the assembler
|
||||
-Wp,<options> Pass comma-separated <options> on to the preprocessor
|
||||
-Wl,<options> Pass comma-separated <options> on to the linker
|
||||
-Xassembler <arg> Pass <arg> on to the assembler
|
||||
-Xpreprocessor <arg> Pass <arg> on to the preprocessor
|
||||
-Xlinker <arg> Pass <arg> on to the linker
|
||||
-save-temps Do not delete intermediate files
|
||||
-save-temps=<arg> Do not delete intermediate files
|
||||
-no-canonical-prefixes Do not canonicalize paths when building relative
|
||||
prefixes to other gcc components
|
||||
-pipe Use pipes rather than intermediate files
|
||||
-time Time the execution of each subprocess
|
||||
-specs=<file> Override built-in specs with the contents of <file>
|
||||
-std=<standard> Assume that the input sources are for <standard>
|
||||
--sysroot=<directory> Use <directory> as the root directory for headers
|
||||
and libraries
|
||||
-B <directory> Add <directory> to the compiler's search paths
|
||||
-v Display the programs invoked by the compiler
|
||||
-### Like -v but options quoted and commands not executed
|
||||
-E Preprocess only; do not compile, assemble or link
|
||||
-S Compile only; do not assemble or link
|
||||
-c Compile and assemble, but do not link
|
||||
-o <file> Place the output into <file>
|
||||
-pie Create a position independent executable
|
||||
-shared Create a shared library
|
||||
-x <language> Specify the language of the following input files
|
||||
Permissible languages include: c c++ assembler none
|
||||
'none' means revert to the default behavior of
|
||||
guessing the language based on the file's extension
|
||||
|
||||
```
|
||||
|
||||
输入 `g++ -v --help`可以看到更完整的指令。
|
||||
例如还有些常用的:
|
||||
```
|
||||
-h FILENAME, -soname FILENAME: Set internal name of shared library
|
||||
-I PROGRAM, --dynamic-linker PROGRAM: Set PROGRAM as the dynamic linker to use
|
||||
-l LIBNAME, --library LIBNAME: Search for library LIBNAME
|
||||
-L DIRECTORY, --library-path DIRECTORY: Add DIRECTORY to library search path
|
||||
```
|
||||
|
||||
**获得程序状态**:
|
||||
|
||||
- windows: ``echo %ERRORLEVEL%``
|
||||
- UNIX: ``echo $?``
|
||||
|
||||
## IO
|
||||
|
||||
- ```#include <iostream>```
|
||||
- ```std::cout << "hello"```
|
||||
- ```std::cin >> v1```
|
||||
|
||||
记住`>>`和`<<`返回的结果都是左操作数,也就是输入流和输出流本身。
|
||||
|
||||
**endl**:这是一个被称为**操纵符**(manipulator)的特殊值,效果是结束当前行,并将设备关联的缓冲区(buffer)中的内容刷到设备中。
|
||||
|
||||
UNIX和Mac下键盘输入文件结束符:`ctrl+d`,Windows下:`ctrl+z`
|
||||
|
||||
**头文件**:类的类型一般存储在头文件中,标准库的头文件使用`<>`,非标准库的头文件使用`""`。申明写在`.h`文件,定义实现写在`.cpp`文件。
|
||||
|
||||
**避免多次包含同一头文件**:
|
||||
|
||||
```cpp
|
||||
#ifndef SALESITEM_H
|
||||
#define SALESITEM_H
|
||||
// Definition of Sales_itemclass and related functions goes here
|
||||
#endif
|
||||
```
|
||||
|
||||
**成员函数(类方法)**:使用`.`调用。
|
||||
|
||||
**命名空间(namespace)**:使用作用域运算符`::`调用。
|
||||
|
||||
## 注释
|
||||
|
||||
- 单行注释: `//`
|
||||
- 多行注释: `/**/`。编译器将`/*`和`*/`之间的内容都作为注释内容忽略。注意不能嵌套。
|
||||
```cpp
|
||||
#define SALESITEM_H
|
||||
/*
|
||||
* 多行注释格式
|
||||
* 每一行加一个*
|
||||
*/
|
||||
```
|
||||
|
||||
## while语句
|
||||
|
||||
循环执行,(直到条件(condition)为假。
|
||||
|
||||
## for语句
|
||||
|
||||
循环头由三部分组成:
|
||||
|
||||
- 一个初始化语句(init-statement)
|
||||
- 一个循环条件(condition)
|
||||
- 一个表达式(expression)
|
||||
|
||||
## 使用文件重定向
|
||||
|
||||
``./main <infile >outfile``
|
||||
|
||||
|
||||
|
||||
249
docs/Cpp-Notes-main/Cpp-Primer/2.变量和基本类型.md
Normal file
249
docs/Cpp-Notes-main/Cpp-Primer/2.变量和基本类型.md
Normal file
@ -0,0 +1,249 @@
|
||||
---
|
||||
sort: 2
|
||||
---
|
||||
|
||||
# 变量和基本类型
|
||||
|
||||
### 基本内置类型
|
||||
|
||||
**基本算数类型**:
|
||||
|
||||
| 类型 | 含义 | 最小尺寸 |
|
||||
| ------------- | -------------- | ------------------------------ |
|
||||
| `bool` | 布尔类型 | 8bits |
|
||||
| `char` | 字符 | 8bits |
|
||||
| `wchar_t` | 宽字符 | 16bits |
|
||||
| `char16_t` | Unicode字符 | 16bits |
|
||||
| `char32_t` | Unicode字符 | 32bits |
|
||||
| `short` | 短整型 | 16bits |
|
||||
| `int` | 整型 | 16bits (在32位机器中是32bits) |
|
||||
| `long` | 长整型 | 32bits |
|
||||
| `long long` | 长整型 | 64bits (是在C++11中新定义的) |
|
||||
| `float` | 单精度浮点数 | 6位有效数字 |
|
||||
| `double` | 双精度浮点数 | 10位有效数字 |
|
||||
| `long double` | 扩展精度浮点数 | 10位有效数字 |
|
||||
|
||||
|
||||
### 如何选择类型
|
||||
|
||||
- 1.当明确知晓数值不可能是负数时,选用无符号类型;
|
||||
- 2.使用`int`执行整数运算。一般`long`的大小和`int`一样,而`short`常常显得太小。除非超过了`int`的范围,选择`long long`。
|
||||
- 3.算术表达式中不要使用`char`或`bool`。
|
||||
- 4.浮点运算选用`double`。
|
||||
|
||||
### 类型转换
|
||||
|
||||
- 非布尔型赋给布尔型,初始值为0则结果为false,否则为true。
|
||||
- 布尔型赋给非布尔型,初始值为false结果为0,初始值为true结果为1。
|
||||
|
||||
### 字面值常量
|
||||
|
||||
- 一个形如`42`的值被称作**字面值常量**(literal)。
|
||||
- 整型和浮点型字面值。
|
||||
- 字符和字符串字面值。
|
||||
- 使用空格连接,继承自C。
|
||||
- 字符字面值:单引号, `'a'`
|
||||
- 字符串字面值:双引号, `"Hello World""`
|
||||
- 分多行书写字符串。
|
||||
```
|
||||
std:cout<<"wow, a really, really long string"
|
||||
"literal that spans two lines" <<std::endl;
|
||||
```
|
||||
- 转义序列。`\n`、`\t`等。
|
||||
- 布尔字面值。`true`,`false`。
|
||||
- 指针字面值。`nullptr`
|
||||
|
||||
> 字符串型实际上时常量字符构成的数组,结尾处以`'\0'`结束,所以字符串类型实际上长度比内容多1。
|
||||
|
||||
## 变量
|
||||
|
||||
**变量**提供一个**具名**的、可供程序操作的存储空间。 `C++`中**变量**和**对象**一般可以互换使用。
|
||||
|
||||
### 变量定义(define)
|
||||
|
||||
- **定义形式**:类型说明符(type specifier) + 一个或多个变量名组成的列表。如`int sum = 0, value, units_sold = 0;`
|
||||
- **初始化**(initialize):对象在创建时获得了一个特定的值。
|
||||
- **初始化不是赋值!**:
|
||||
- 初始化 = 创建变量 + 赋予初始值
|
||||
- 赋值 = 擦除对象的当前值 + 用新值代替
|
||||
- **列表初始化**:使用花括号`{}`,如`int units_sold{0};`
|
||||
- 默认初始化:定义时没有指定初始值会被默认初始化;**在函数体内部的内置类型变量将不会被初始化**。
|
||||
- 建议初始化每一个内置类型的变量。
|
||||
|
||||
### 变量的**声明**(declaration) vs **定义**(define)
|
||||
- 为了支持分离式编译,`C++`将声明和定义区分开。**声明**使得名字为程序所知。**定义**负责创建与名字关联的实体。
|
||||
- **extern**:只是说明变量定义在其他地方。
|
||||
- 只声明而不定义: 在变量名前添加关键字 `extern`,如`extern int i;`。但如果包含了初始值,就变成了定义:`extern double pi = 3.14;`
|
||||
- 变量只能被定义一次,但是可以多次声明。定义只出现在一个文件中,其他文件使用该变量时需要对其声明。
|
||||
- 名字的**作用域**(namescope)`{}`
|
||||
- **第一次使用变量时再定义它**。
|
||||
- 嵌套的作用域
|
||||
- 同时存在全局和局部变量时,已定义局部变量的作用域中可用`::reused`显式访问全局变量reused。
|
||||
- **但是用到全局变量时,尽量不适用重名的局部变量。**
|
||||
|
||||
#### 变量命名规范
|
||||
1. 需体现实际意义
|
||||
2. 变量名用小写字母
|
||||
3. 自定义类名用大写字母开头:Sales_item
|
||||
4. 标识符由多个单词组成,中间须有明确区分:student_loan或studentLoan,不要用studentloan。
|
||||
|
||||
## 左值和右值
|
||||
|
||||
- **左值**(l-value)**可以**出现在赋值语句的左边或者右边,比如变量;
|
||||
- **右值**(r-value)**只能**出现在赋值语句的右边,比如常量。
|
||||
|
||||
|
||||
## 复合类型
|
||||
|
||||
### 引用
|
||||
|
||||
> 一般说的引用是指的左值引用
|
||||
- **引用**:引用是一个对象的别名,引用类型引用(refer to)另外一种类型。如`int &refVal = val;`。
|
||||
- 引用必须初始化。
|
||||
- 引用和它的初始值是**绑定bind**在一起的,而**不是拷贝**。一旦定义就不能更改绑定为其他的对象
|
||||
|
||||
### 指针
|
||||
|
||||
> int *p; //**指向int型对象**的指针
|
||||
|
||||
- 是一种 `"指向(point to)"`另外一种类型的复合类型。
|
||||
|
||||
- **定义**指针类型: `int *ip1;`,**从右向左读有助于阅读**,`ip1`是指向`int`类型的指针。
|
||||
|
||||
- 指针存放某个对象的**地址**。
|
||||
|
||||
- 获取对象的地址: `int i=42; int *p = &i;`。 `&`是**取地址符**。
|
||||
|
||||
- 指针的类型与所指向的对象类型必须一致(均为同一类型int、double等)
|
||||
|
||||
- 指针的值的四种状态:
|
||||
- 1.指向一个对象;
|
||||
- 2.指向紧邻对象的下一个位置;
|
||||
- 3.空指针;
|
||||
- 4.无效指针。
|
||||
- >**对无效指针的操作均会引发错误,第二种和第三种虽为有效的,但理论上是不被允许的**
|
||||
|
||||
- 指针访问对象: `cout << *p;`输出p指针所指对象的数据, `*`是**解引用符**。
|
||||
- 像 `&` 和 `*`这样的符号,既能用作表达式里的运算符,也能作为声明的一部分出现,符号的上下文决定了符号的意义。
|
||||
```cpp
|
||||
int i = 42;
|
||||
int &r = i; // & 紧随类型名出现,因此是声明的一部分,r是一个引用
|
||||
int *p; // * 紧随类型名出现,因此是声明的一部分,p是一个指针
|
||||
p = &i; // & 出现在表达式中,是一个取地址符
|
||||
*p = i; // * 出现在表达式中,是一个解引用符
|
||||
int &r2 = *p; // &是声明的一部分,*是一个解引用符
|
||||
```
|
||||
|
||||
|
||||
|
||||
- 空指针不指向任何对象。使用`int *p=nullptr;`来使用空指针。
|
||||
|
||||
- > 指针和引用的区别:引用本身并非一个对象,引用定义后就不能绑定到其他的对象了;指针并没有此限制,相当于变量一样使用。
|
||||
|
||||
- > 赋值语句永远改变的是**左侧**的对象。
|
||||
|
||||
- `void*`指针可以存放**任意**对象的地址。因无类型,仅操作内存空间,对所存对象无法访问。
|
||||
|
||||
- 其他指针类型必须要与所指对象**严格匹配**。
|
||||
|
||||
- 两个指针相减的类型是`ptrdiff_t`。
|
||||
|
||||
- 建议:初始化所有指针。
|
||||
|
||||
- `int* p1, p2;//*是对p1的修饰,所以p2还是int型`
|
||||
|
||||
## const限定符
|
||||
|
||||
- 动机:希望定义一些不能被改变值的变量。
|
||||
|
||||
### 初始化和const
|
||||
- `const` 对象**必须初始化**,且**不能被改变**。
|
||||
- `const` 变量默认不能被其他文件访问,非要访问,必须在指定 `const` 定义之前加 `extern` 。要想在多个文件中使用const变量共享,定义和声明都加 `extern` 关键字即可。
|
||||
|
||||
### const的引用
|
||||
|
||||
- **reference to const**(对常量的引用):指向const对象的引用,如 `const int ival=1; const int &refVal = ival;`,可以读取但不能修改`refVal`。
|
||||
- **临时量**(temporary)对象:当编译器需要一个空间来暂存表达式的求值结果时,临时创建的一个未命名的对象。
|
||||
- 对临时量的引用是非法行为。
|
||||
|
||||
### 指针和const
|
||||
|
||||
- **pointer to const**(指向常量的指针):不能用于改变其所指对象的值, 如 `const double pi = 3.14; const double *cptr = π`。
|
||||
- **const pointer**:指针本身是常量,也就是说指针固定指向该对象,(存放在指针中的地址不变,地址所对应的那个对象值可以修改)如 `int i = 0; int *const ptr = &i;`
|
||||
|
||||
### 顶层const
|
||||
|
||||
- `顶层const`:指针本身是个常量。
|
||||
- `底层const`:指针指向的对象是个常量。拷贝时严格要求相同的底层const资格。
|
||||
|
||||
### `constexpr`和常量表达式(▲可选)
|
||||
|
||||
- 常量表达式:指值不会改变,且在编译过程中就能得到计算结果的表达式。
|
||||
- `C++11`新标准规定,允许将变量声明为`constexpr`类型以便由编译器来验证变量的值是否是一个常量的表达式。
|
||||
|
||||
## 处理类型
|
||||
|
||||
### 类型别名
|
||||
|
||||
- 传统别名:使用**typedef**来定义类型的同义词。 `typedef double wages;`
|
||||
- 新标准别名:别名声明(alias declaration): `using SI = Sales_item;`(C++11)
|
||||
|
||||
```cpp
|
||||
// 对于复合类型(指针等)不能代回原式来进行理解
|
||||
typedef char *pstring; // pstring是char*的别名
|
||||
const pstring cstr = 0; // 指向char的常量指针
|
||||
// 如改写为const char *cstr = 0;不正确,为指向const char的指针
|
||||
|
||||
// 辅助理解(可代回后加括号)
|
||||
// const pstring cstr = 0;代回后const (char *) cstr = 0;
|
||||
// const char *cstr = 0;即为(const char *) cstr = 0;
|
||||
```
|
||||
|
||||
### auto类型说明符 c++11
|
||||
|
||||
- **auto**类型说明符:让编译器**自动推断类型**。
|
||||
- 一条声明语句只能有一个数据类型,所以一个auto声明多个变量时只能相同的变量类型(包括复杂类型&和*)。`auto sz = 0, pi =3.14//错误`
|
||||
- `int i = 0, &r = i; auto a = r;` 推断`a`的类型是`int`。
|
||||
- 会忽略`顶层const`。
|
||||
- `const int ci = 1; const auto f = ci;`推断类型是`int`,如果希望是顶层const需要自己加`const`
|
||||
|
||||
### decltype类型指示符
|
||||
|
||||
- 从表达式的类型推断出要定义的变量的类型。
|
||||
- **decltype**:选择并返回操作数的**数据类型**。
|
||||
- `decltype(f()) sum = x;` 推断`sum`的类型是函数`f`的返回类型。
|
||||
- 不会忽略`顶层const`。
|
||||
- 如果对变量加括号,编译器会将其认为是一个表达式,如int i-->(i),则decltype((i))得到结果为int&引用。
|
||||
- 赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。
|
||||
- decltype((variable))(双层括号)的结果永远是引用,而decltype(variable)的结果只有当 **variable** 本身是一个引用时才是引用
|
||||
|
||||
## 自定义数据结构
|
||||
|
||||
### struct
|
||||
|
||||
> 尽量不要把类定义和对象定义放在一起。如`struct Student{} xiaoming,xiaofang;`
|
||||
- 类可以以关键字`struct`开始,紧跟类名和类体。
|
||||
- 类数据成员:类体定义类的成员。
|
||||
- `C++11`:可以为类数据成员提供一个**类内初始值**(in-class initializer)。
|
||||
|
||||
### 编写自己的头文件
|
||||
|
||||
- 头文件通常包含哪些只能被定义一次的实体:类、`const`和`constexpr`变量。
|
||||
|
||||
预处理器概述:
|
||||
|
||||
- **预处理器**(preprocessor):确保头文件多次包含仍能安全工作。
|
||||
- 当预处理器看到`#include`标记时,会用指定的头文件内容代替`#include`
|
||||
- **头文件保护符**(header guard):头文件保护符依赖于预处理变量的状态:已定义和未定义。
|
||||
- `#indef`已定义时为真
|
||||
- `#inndef`未定义时为真
|
||||
- 头文件保护符的名称需要唯一,且保持全部大写。养成良好习惯,不论是否该头文件被包含,要加保护符。
|
||||
|
||||
```cpp
|
||||
#ifndef SALES_DATA_H //SALES_DATA_H未定义时为真
|
||||
#define SALES_DATA_H
|
||||
strct Sale_data{
|
||||
...
|
||||
}
|
||||
#endif
|
||||
```
|
||||
242
docs/Cpp-Notes-main/Cpp-Primer/3.字符串、向量和数组.md
Normal file
242
docs/Cpp-Notes-main/Cpp-Primer/3.字符串、向量和数组.md
Normal file
@ -0,0 +1,242 @@
|
||||
---
|
||||
sort: 3
|
||||
---
|
||||
|
||||
# 字符串、向量和数组
|
||||
|
||||
## using声明
|
||||
- 使用某个命名空间:例如 `using std::cin`表示使用命名空间`std`中的名字`cin`。
|
||||
- 头文件中不应该包含`using`声明。这样使用了该头文件的源码也会使用这个声明,会带来风险。
|
||||
|
||||
## string
|
||||
- 标准库类型`string`表示可变长的字符序列。
|
||||
- `#include <string>`,然后 `using std::string;`
|
||||
- **string对象**:注意,不同于字符串字面值。
|
||||
|
||||
### 定义和初始化string对象
|
||||
|
||||
初始化`string`对象的方式:
|
||||
|
||||
| 方式 | 解释 |
|
||||
| --------------------- | ------------------------------------------------------- |
|
||||
| `string s1` | 默认初始化,`s1`是个空字符串 |
|
||||
| `string s2(s1)` | `s2`是`s1`的副本 |
|
||||
| `string s2 = s1` | 等价于`s2(s1)`,`s2`是`s1`的副本 |
|
||||
| `string s3("value")` | `s3`是字面值“value”的副本,除了字面值最后的那个空字符外 |
|
||||
| `string s3 = "value"` | 等价于`s3("value")`,`s3`是字面值"value"的副本 |
|
||||
| `string s4(n, 'c')` | 把`s4`初始化为由连续`n`个字符`c`组成的串 |
|
||||
|
||||
- 拷贝初始化(copy initialization):使用等号`=`将一个已有的对象拷贝到正在创建的对象。
|
||||
- 直接初始化(direct initialization):通过括号给对象赋值。
|
||||
|
||||
### string对象上的操作
|
||||
|
||||
`string`的操作:
|
||||
|
||||
| 操作 | 解释 |
|
||||
| -------------------- | ------------------------------------------------------------------------------------------ |
|
||||
| `os << s` | 将`s`写到输出流`os`当中,返回`os` |
|
||||
| `is >> s` | 从`is`中读取字符串赋给`s`,字符串以空白分割,返回`is` |
|
||||
| `getline(is, s)` | 从`is`中读取一行赋给`s`,返回`is` |
|
||||
| `s.empty()` | `s`为空返回`true`,否则返回`false` |
|
||||
| `s.size()` | 返回`s`中字符的个数 |
|
||||
| `s[n]` | 返回`s`中第`n`个字符的引用,位置`n`从0计起 |
|
||||
| `s1+s2` | 返回`s1`和`s2`连接后的结果 |
|
||||
| `s1=s2` | 用`s2`的副本代替`s1`中原来的字符 |
|
||||
| `s1==s2` | 如果`s1`和`s2`中所含的字符完全一样,则它们相等;`string`对象的相等性判断对字母的大小写敏感 |
|
||||
| `s1!=s2` | 同上 |
|
||||
| `<`, `<=`, `>`, `>=` | 利用字符在字典中的顺序进行比较,且对字母的大小写敏感(对第一个不相同的位置进行比较) |
|
||||
|
||||
- string io:
|
||||
- 执行读操作`>>`:忽略掉开头的空白(包括空格、换行符和制表符),直到遇到下一处空白为止。
|
||||
- `getline`:读取一整行,**包括空白符**。
|
||||
- `s.size()`返回的时`string::size_type`类型,记住是一个**无符号**类型的值,不要和`int`混用
|
||||
- `s1+s2`使用时,保证至少一侧是string类型。`string s1 = "hello" + "world" // 错误,两侧均为字符串字面值`
|
||||
- **字符串字面值和string是不同的类型。**
|
||||
|
||||
### 处理string对象中的字符
|
||||
|
||||
- **ctype.h vs. cctype**:C++修改了c的标准库,名称为去掉`.h`,前面加`c`。
|
||||
> 如c++版本为`cctype`,c版本为`ctype.h`
|
||||
- **尽量使用c++版本的头文件**,即`cctype`
|
||||
|
||||
`cctype`头文件中定义了一组标准函数:
|
||||
|
||||
| 函数 | 解释 |
|
||||
| ------------- | ------------------------------------------------------------------------- |
|
||||
| `isalnum(c)` | 当`c`是字母或数字时为真 |
|
||||
| `isalpha(c)` | 当`c`是字母时为真 |
|
||||
| `iscntrl(c)` | 当`c`是控制字符时为真 |
|
||||
| `isdigit(c)` | 当`c`是数字时为真 |
|
||||
| `isgraph(c)` | 当`c`不是空格但可以打印时为真 |
|
||||
| `islower(c)` | 当`c`是小写字母时为真 |
|
||||
| `isprint(c)` | 当`c`是可打印字符时为真 |
|
||||
| `ispunct(c)` | 当`c`是标点符号时为真 |
|
||||
| `isspace(c)` | 当`c`是空白时为真(空格、横向制表符、纵向制表符、回车符、换行符、进纸符) |
|
||||
| `isupper(c)` | 当`c`是大写字母时为真 |
|
||||
| `isxdigit(c)` | 当`c`是十六进制数字时为真 |
|
||||
| `tolower(c)` | 当`c`是大写字母,输出对应的小写字母;否则原样输出`c` |
|
||||
| `toupper(c)` | 当`c`是小写字母,输出对应的大写字母;否则原样输出`c` |
|
||||
|
||||
- 遍历字符串:使用**范围for**(range for)语句: `for (auto c: str)`,或者 `for (auto &c: str)`使用引用直接改变字符串中的字符。 (C++11)
|
||||
- `str[x]`,[]输入参数为`string::size_type`类型,给出`int`整型也会自动转化为该类型
|
||||
|
||||
## vector
|
||||
- vector是一个**容器**,也是一个类模板;
|
||||
- `#include <vector>` 然后 `using std::vector;`
|
||||
- 容器:包含其他对象。
|
||||
- 类模板:本身不是类,但可以**实例化instantiation**出一个类。 `vector`是一个模板, `vector<int>`是一个类型。
|
||||
- 通过将类型放在类模板名称后面的**尖括号**中来指定**类型**,如`vector<int> ivec`。
|
||||
|
||||
### 定义和初始化vector对象
|
||||
|
||||
初始化`vector`对象的方法
|
||||
|
||||
| 方法 | 解释 |
|
||||
| --------------------------- | ------------------------------------------------------------- |
|
||||
| `vector<T> v1` | `v1`是一个空`vector`,它潜在的元素是`T`类型的,执行默认初始化 |
|
||||
| `vector<T> v2(v1)` | `v2`中包含有`v1`所有元素的副本 |
|
||||
| `vector<T> v2 = v1` | 等价于`v2(v1)`,`v2`中包含`v1`所有元素的副本 |
|
||||
| `vector<T> v3(n, val)` | `v3`包含了n个重复的元素,每个元素的值都是`val` |
|
||||
| `vector<T> v4(n)` | `v4`包含了n个重复地执行了值初始化的对象 |
|
||||
| `vector<T> v5{a, b, c...}` | `v5`包含了初始值个数的元素,每个元素被赋予相应的初始值 |
|
||||
| `vector<T> v5={a, b, c...}` | 等价于`v5{a, b, c...}` |
|
||||
|
||||
- 列表初始化: `vector<string> v{"a", "an", "the"};` (C++11)
|
||||
|
||||
### 向vector对象中添加元素
|
||||
|
||||
- `v.push_back(e)` 在尾部增加元素。
|
||||
|
||||
### 其他vector操作
|
||||
|
||||
`vector`支持的操作:
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ------------------ | ---------------------------------------------------------------- |
|
||||
| `v.emtpy()` | 如果`v`不含有任何元素,返回真;否则返回假 |
|
||||
| `v.size()` | 返回`v`中元素的个数 |
|
||||
| `v.push_back(t)` | 向`v`的尾端添加一个值为`t`的元素 |
|
||||
| `v[n]` | 返回`v`中第`n`个位置上元素的**引用** |
|
||||
| `v1 = v2` | 用`v2`中的元素拷贝替换`v1`中的元素 |
|
||||
| `v1 = {a,b,c...}` | 用列表中元素的拷贝替换`v1`中的元素 |
|
||||
| `v1 == v2` | `v1`和`v2`相等当且仅当它们的元素数量相同且对应位置的元素值都相同 |
|
||||
| `v1 != v2` | 同上 |
|
||||
| `<`,`<=`,`>`, `>=` | 以字典顺序进行比较 |
|
||||
|
||||
- 范围`for`语句内不应该改变其遍历序列的大小。
|
||||
- `vector`对象(以及`string`对象)的下标运算符,只能对确知已存在的元素执行下标操作,不能用于添加元素。
|
||||
- 两个`vector`对象相等当且仅当他们所含的元素个数相同,而且对应位置的元素也相同。
|
||||
|
||||
## 迭代器iterator
|
||||
|
||||
- 所有标准库容器都可以使用迭代器。
|
||||
- 类似于指针类型,迭代器也提供了对对象的间接访问。
|
||||
|
||||
### 使用迭代器
|
||||
|
||||
- `vector<int>::iterator iter`。
|
||||
- `auto b = v.begin();`返回指向第一个元素的迭代器。
|
||||
- `auto e = v.end();`返回指向最后一个元素的下一个(哨兵,尾后,one past the end)的迭代器(off the end)。
|
||||
- 如果容器为空, `begin()`和 `end()`返回的是同一个迭代器,都是尾后迭代器。
|
||||
- 使用解引用符`*`访问迭代器指向的元素。
|
||||
- 养成使用迭代器和`!=`的习惯(泛型编程)。
|
||||
- **容器**:可以包含其他对象;但所有的对象必须类型相同。
|
||||
- **迭代器(iterator)**:每种标准容器都有自己的迭代器。`C++`倾向于用迭代器而不是下标遍历元素。
|
||||
- **const_iterator**:只能读取容器内元素不能改变。
|
||||
- **箭头运算符**: 解引用 + 成员访问,`it->mem`等价于 `(*it).mem`
|
||||
- **谨记**:但凡是使用了**迭代器**的循环体,都**不要**向迭代器所属的容器**添加元素**。
|
||||
|
||||
标准容器迭代器的运算符:
|
||||
|
||||
| 运算符 | 解释 |
|
||||
| ---------------- | -------------------------------------- |
|
||||
| `*iter` | 返回迭代器`iter`所指向的**元素的引用** |
|
||||
| `iter->mem` | 等价于`(*iter).mem` |
|
||||
| `++iter` | 令`iter`指示容器中的下一个元素 |
|
||||
| `--iter` | 令`iter`指示容器中的上一个元素 |
|
||||
| `iter1 == iter2` | 判断两个迭代器是否相等 |
|
||||
|
||||
### 迭代器运算
|
||||
|
||||
`vector`和`string`迭代器支持的运算:
|
||||
|
||||
| 运算符 | 解释 |
|
||||
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `iter + n` | 迭代器加上一个整数值仍得到一个迭代器,迭代器指示的新位置和原来相比向前移动了若干个元素。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置。 |
|
||||
| `iter - n` | 迭代器减去一个证书仍得到一个迭代器,迭代器指示的新位置比原来向后移动了若干个元素。结果迭代器或者指向容器内的一个元素,或者指示容器尾元素的下一位置。 |
|
||||
| `iter1 += n` | 迭代器加法的复合赋值语句,将`iter1`加n的结果赋给`iter1` |
|
||||
| `iter1 -= n` | 迭代器减法的复合赋值语句,将`iter2`减n的加过赋给`iter1` |
|
||||
| `iter1 - iter2` | 两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动差值个元素后得到左侧的迭代器。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置。 |
|
||||
| `>`、`>=`、`<`、`<=` | 迭代器的关系运算符,如果某迭代器 |
|
||||
|
||||
- **difference_type**:保证足够大以存储任何两个迭代器对象间的距离,可正可负。
|
||||
|
||||
## 数组
|
||||
|
||||
- 相当于vector的低级版,**长度固定**。
|
||||
|
||||
### 定义和初始化内置数组
|
||||
|
||||
- 初始化:`char input_buffer[buffer_size];`,长度必须是const表达式,或者不写,让编译器自己推断。
|
||||
- 定义数组必须指定数组的类型,不允许用`auto`关键字由初始值的列表推断类型,但在遍历时允许使用`auto`。
|
||||
- 不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。
|
||||
- 不允许使用一个数组为另一个内置类型的数组赋值,也不允许使用`vector`对象初始化数组,相反地,允许使用数组来初始化`vector`对象。
|
||||
- 不存在引用的数组。
|
||||
- 理解数组声明的含义,最好的板房是从数组的名字开始按照由内向外的顺序阅读。
|
||||
|
||||
### 访问数组元素
|
||||
|
||||
- 数组下标的类型:`size_t` 。
|
||||
- 字符数组的特殊性:结尾处有一个空字符,如 `char a[] = "hello";` 。
|
||||
- 用数组初始化 `vector`: `int a[] = {1,2,3,4,5}; vector<int> v(begin(a), end(a));` 。
|
||||
|
||||
### 数组和指针
|
||||
|
||||
- 使用数组时,编译器一般会把它转换成指针。
|
||||
- 标准库类型限定使用的下标必须是无符号类型,而内置的下标可以处理负值。
|
||||
- **指针访问数组**:在表达式中使用数组名时,名字会自动转换成指向数组的第一个元素的指针。
|
||||
|
||||
## C风格字符串
|
||||
|
||||
- 从C继承来的字符串。
|
||||
- 用空字符结束(`\0`)。
|
||||
- 对大多数应用来说,使用标准库 `string`比使用C风格字符串更安全、更高效。
|
||||
- 获取 `string` 中的 `cstring` : `const char *str = s.c_str();` 。
|
||||
|
||||
C标准库String函数,定义在`<cstring>` 中:
|
||||
|
||||
| 函数 | 介绍 |
|
||||
| ---------------- | --------------------------------------------------------------------------------------------------- |
|
||||
| `strlen(p)` | 返回`p`的长度,空字符不计算在内 |
|
||||
| `strcmp(p1, p2)` | 比较`p1`和`p2`的相等性。如果`p1==p2`,返回0;如果`p1>p2`,返回一个正值;如果`p1<p2`,返回一个负值。 |
|
||||
| `strcat(p1, p2)` | 将`p2`附加到`p1`之后,返回`p1` |
|
||||
| `strcpy(p1, p2)` | 将`p2`拷贝给`p1`,返回`p1` |
|
||||
|
||||
**尽量使用vector和迭代器,少用内置数组和指针;应尽量使用string,避免使用C风格的基于数组的字符串**
|
||||
|
||||
## 多维数组
|
||||
|
||||
- **多维数组的初始化**:
|
||||
|
||||
- 使用范围for语句时,除了最内层的循环外,其他所有循环的控制变量都应该是**引用**类型。
|
||||
|
||||
## 指针vs引用
|
||||
|
||||
- 引用总是指向某个对象,定义引用时没有初始化是错的。
|
||||
- 给引用赋值,修改的是该引用所关联的对象的值,而不是让引用和另一个对象相关联。
|
||||
|
||||
## 指向指针的指针
|
||||
|
||||
- 定义: `int **ppi = π`
|
||||
- 解引用:`**ppi`
|
||||
|
||||
## 动态数组
|
||||
|
||||
- 使用 `new`和 `delete`表达和c中`malloc`和`free`类似的功能,即在堆(自由存储区)中分配存储空间。
|
||||
- 定义: `int *pia = new int[10];` 10可以被一个变量替代。
|
||||
- 释放: `delete [] pia;`,注意不要忘记`[]`。
|
||||
|
||||
|
||||
|
||||
|
||||
207
docs/Cpp-Notes-main/Cpp-Primer/4.表达式.md
Normal file
207
docs/Cpp-Notes-main/Cpp-Primer/4.表达式.md
Normal file
@ -0,0 +1,207 @@
|
||||
---
|
||||
sort: 4
|
||||
---
|
||||
|
||||
# 表达式
|
||||
|
||||
## 表达式基础
|
||||
|
||||
- **运算对象转换**:小整数类型会被提升为较大的整数类型
|
||||
- **重载运算符**:当运算符作用在类类型的运算对象时,用户可以自行定义其含义。
|
||||
- **左值和右值**:
|
||||
- C中原意:左值**可以**在表达式左边,右值不能。
|
||||
- `C++`:当一个对象被用作**右值**的时候,用的是对象的**值**(内容);
|
||||
- 被用做**左值**时,用的是对象的**身份**(在内存中的位置)。
|
||||
- **求值顺序**:`int i = f1() + f2()`
|
||||
- 先计算`f1() + f2()`,再计算`int i = f1() + f2()`。但是f1和f2的计算**先后不确定**
|
||||
- 但是,如果f1、f2都对同一对象进行了修改,因为顺序不确定,所以会编译出错,显示未定义
|
||||
- 有4种运算符明确规定了运算对象的求值顺序。
|
||||
- 逻辑与`&&`
|
||||
- 逻辑或`||`
|
||||
- 条件运算符`?:`
|
||||
- 逗号运算符`,`
|
||||
|
||||
|
||||
## 算术运算符
|
||||
|
||||
- **溢出**:当计算的结果超出该类型所能表示的范围时就会产生溢出。
|
||||
- **bool类型不应该参与计算**
|
||||
```cpp
|
||||
bool b=true;
|
||||
bool b2=-b; //仍然为true
|
||||
//b为true,提升为对应int=1,-b=-1
|
||||
//b2=-1≠0,所以b2仍未true
|
||||
```
|
||||
- 取余运算m%n,结果符号与m相同
|
||||
|
||||
## 逻辑运算符
|
||||
|
||||
- **短路求值**:逻辑与运算符和逻辑或运算符都是先求左侧运算对象的值再求右侧运算对象的值,当且仅当左侧运算对象无法确定表达式的结果时才会计算右侧运算对象的值。**先左再右**
|
||||
- 对于逻辑与运算符来说,当且仅当左侧运算对象为真时才对右侧运算对象求值。
|
||||
- 对于逻辑或运算符来说,当且仅当左侧运算对象为假时才对右侧运算对象求值。
|
||||
- 小技巧,声明为引用类型可以避免对元素的拷贝,如下,如string特别大时可以节省大量时间。
|
||||
|
||||
```cpp
|
||||
vector<string> text;
|
||||
for(const auto &s: text){
|
||||
cout<<s;
|
||||
}
|
||||
```
|
||||
## 相等性测试
|
||||
|
||||
- 测试一个算法对象或指针对象的真值,最直接的方法就是将其作为if语句的条件;
|
||||
- ```if(val) {} // 如果val是任意的非0值,条件为真```
|
||||
- ```if(!val) {} // 如果val是0,条件为真```
|
||||
|
||||
## 赋值运算符
|
||||
|
||||
- 赋值运算的**返回结果时它的左侧运算对象**,且是一个左值。类型也就是左侧对象的类型。
|
||||
- 如果赋值运算的左右侧运算对象类型不同,则右侧运算对象将转换成左侧运算对象的类型。
|
||||
- 赋值运算符满足**右结合律**,这点和其他二元运算符不一样。 `ival = jval = 0;`等价于`ival = (jval = 0);`
|
||||
- 赋值运算优先级比较低,使用其当条件时应该加括号。
|
||||
- 复合赋值运算符,**复合运算符只求值一次**,普通运算符求值两次。
|
||||
任意复合运算符op等价于`a = a op b;`
|
||||
|
||||
## 递增递减运算符
|
||||
|
||||
* 前置版本`j = ++i`,先加一后赋值
|
||||
* 后置版本`j = i++`,先赋值后加一
|
||||
|
||||
**优先使用前置**版本,后置多一步储存原始值。(除非需要变化前的值)
|
||||
|
||||
### 混用解引用和递增运算符
|
||||
|
||||
`*iter++`等价于`*(iter++)`,递增优先级较高
|
||||
|
||||
```c++
|
||||
auto iter = vi.begin();
|
||||
while (iter!=vi.end()&&*iter>=0)
|
||||
cout<<*iter++<<endl; // 输出当前值,指针向前移1
|
||||
```
|
||||
|
||||
> **简介是一种美德**,追求简洁能降低程序出错可能性
|
||||
|
||||
## 成员访问运算符
|
||||
|
||||
`ptr->mem`等价于`(*ptr).mem`
|
||||
|
||||
注意`.`运算符优先级大于`*`,所以记得加括号
|
||||
|
||||
## 条件运算符
|
||||
|
||||
- 条件运算符(`?:`)允许我们把简单的`if-else`逻辑嵌入到单个表达式中去,按照如下形式:`cond? expr1: expr2`
|
||||
|
||||
- 可以嵌套使用,**右结合律**,从右向左顺序组合
|
||||
|
||||
- ```c++
|
||||
finalgrade = (grade > 90) ? "high pass"
|
||||
: (grade < 60) ? "fail" : "pass";
|
||||
//等价于
|
||||
finalgrade = (grade > 90) ? "high pass"
|
||||
: ((grade < 60) ? "fail" : "pass");
|
||||
```
|
||||
|
||||
- 输出表达式使用条件运算符记得加括号,条件运算符优先级太低。
|
||||
|
||||
## 位运算符
|
||||
|
||||
用于检查和设置二进制位的功能。
|
||||
|
||||
- 位运算符是作用于**整数类型**的运算对象。
|
||||
- 二进制位向左移(`<<`)或者向右移(`>>`),移出边界外的位就被舍弃掉了。
|
||||
- 位取反(`~`)(逐位求反)、与(`&`)、或(`|`)、异或(`^`)
|
||||
|
||||
有符号数负值可能移位后变号,所以强烈建议**位运算符仅用于无符号数**。
|
||||
|
||||
应用:
|
||||
|
||||
```c++
|
||||
unsigned long quiz1 = 0; // 每一位代表一个学生是否通过考试
|
||||
1UL << 12; // 代表第12个学生通过
|
||||
quiz1 |= (1UL << 12); // 将第12个学生置为已通过
|
||||
quiz1 &= ~(1UL << 12); // 将第12个学生修改为未通过
|
||||
bool stu12 = quiz1 & (1UL << 12); // 判断第12个学生是否通过
|
||||
```
|
||||
|
||||
> 位运算符使用较少,但是重载cout、cin大家都用过
|
||||
|
||||
位运算符满足左结合律,优先级介于中间,使用时尽量加括号。
|
||||
|
||||
## sizeof运算符
|
||||
|
||||
- 返回一条表达式或一个类型名字所占的**字节数**。
|
||||
- 返回的类型是 `size_t`的常量表达式。
|
||||
- `sizeof`并不实际计算其运算对象的值。
|
||||
- 两种形式:
|
||||
1. `sizeof (type)`,给出类型名
|
||||
2. `sizeof expr`,给出表达式
|
||||
- 可用sizeof返回数组的大小
|
||||
|
||||
- sizeof运算符的结果部分地依赖于其作用的类型:
|
||||
- 对char或者类型为char的表达式执行sizeof运算,结果得1。
|
||||
- 对引用类型执行sizeof运算得到被被引用对象所占空间的大小。
|
||||
- 对指针执行sizeof运算得到指针大本身所占空间的大小。
|
||||
- 对解引用指针执行sizeof运算得至到指针指向的对象所占空间的大小,指针不需有效。
|
||||
- 对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有的元素各执行一次sizeof运算并将所得结果求和。注意,sizeof运算不会把数组转换成指针来处理。
|
||||
- 对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。
|
||||
|
||||
因为执行sizeof运算能得到整个数组的大小,所以可以用数组的大小除以单个元素的大小得到数组中元素的个数:
|
||||
|
||||
```c++
|
||||
int ia[10];
|
||||
// sizeof(ia)返回整个数组所占空间的大小
|
||||
// sizeof(ia)/sizeof(*ia)返回数组的大小
|
||||
constexpr size_t sz = sizeof(ia)/sizeof(*ia);
|
||||
int arr[sz];
|
||||
```
|
||||
|
||||
## 逗号运算符
|
||||
|
||||
从左向右依次求值。
|
||||
|
||||
左侧求值结果丢弃,逗号运算符**结果是右侧表达式**的值。
|
||||
|
||||
## 类型转换
|
||||
|
||||
### 隐式类型转换
|
||||
|
||||
> 设计为尽可能避免损失精度,即转换为更精细类型。
|
||||
|
||||
- 比 `int`类型小的整数值先提升为较大的整数类型。
|
||||
- 条件中,非布尔转换成布尔。
|
||||
- 初始化中,初始值转换成变量的类型。
|
||||
- 算术运算或者关系运算的运算对象有多种类型,要转换成同一种类型。
|
||||
- 函数调用时也会有转换。
|
||||
|
||||
#### 算术转换
|
||||
|
||||
##### 整型提升
|
||||
|
||||
* 常见的char、bool、short能存在int就会转换成int,否则提升为`unsigned int`
|
||||
* `wchar_t,char16_t,char32_t`提升为整型中`int,long,long long ……`最小的,且能容纳原类型所有可能值的类型。
|
||||
|
||||
#### 其他转换
|
||||
|
||||
> p143
|
||||
|
||||
### 显式类型转换(尽量避免)
|
||||
|
||||
- **static_cast**:任何明确定义的类型转换,只要不包含底层const,都可以使用。 `double slope = static_cast<double>(j);`
|
||||
|
||||
- **dynamic_cast**:支持运行时类型识别。
|
||||
|
||||
- **const_cast**:只能改变运算对象的底层const,一般可用于去除const性质。 `const char *pc; char *p = const_cast<char*>(pc)`
|
||||
|
||||
> 只有其可以改变常量属性
|
||||
|
||||
- **reinterpret_cast**:通常为运算对象的位模式提供低层次上的重新解释。
|
||||
|
||||
#### 旧式强制类型转换
|
||||
|
||||
`type expr`
|
||||
|
||||
## 运算符优先级表
|
||||
|
||||

|
||||
|
||||

|
||||
32
docs/Cpp-Notes-main/Cpp-Primer/5.语句.md
Normal file
32
docs/Cpp-Notes-main/Cpp-Primer/5.语句.md
Normal file
@ -0,0 +1,32 @@
|
||||
---
|
||||
sort: 5
|
||||
---
|
||||
|
||||
# 语句
|
||||
|
||||
## 简单语句
|
||||
|
||||
- **表达式语句**:一个表达式末尾加上分号,就变成了表达式语句。
|
||||
- **空语句**:只有一个单独的分号。
|
||||
- **复合语句(块)**:用花括号 `{}`包裹起来的语句和声明的序列。一个块就是一个作用域。
|
||||
|
||||
## 条件语句
|
||||
|
||||
- **悬垂else**(dangling else):用来描述在嵌套的`if else`语句中,如果`if`比`else`多时如何处理的问题。C++使用的方法是`else`匹配最近没有配对的`if`。
|
||||
|
||||
## 迭代语句
|
||||
|
||||
- **while**:当不确定到底要迭代多少次时,使用 `while`循环比较合适,比如读取输入的内容。
|
||||
- **for**: `for`语句可以省略掉 `init-statement`, `condition`和 `expression`的任何一个;**甚至全部**。
|
||||
- **范围for**: `for (declaration: expression) statement`
|
||||
|
||||
## 跳转语句
|
||||
|
||||
- **break**:`break`语句负责终止离它最近的`while`、`do while`、`for`或者`switch`语句,并从这些语句之后的第一条语句开始继续执行。
|
||||
- **continue**:终止最近的循环中的当前迭代并立即开始下一次迭代。只能在`while`、`do while`、`for`循环的内部。
|
||||
|
||||
## try语句块和异常处理
|
||||
|
||||
- **throw表达式**:异常检测部分使用 `throw`表达式来表示它遇到了无法处理的问题。我们说 `throw`引发 `raise`了异常。
|
||||
- **try语句块**:以 `try`关键词开始,以一个或多个 `catch`字句结束。 `try`语句块中的代码抛出的异常通常会被某个 `catch`捕获并处理。 `catch`子句也被称为**异常处理代码**。
|
||||
- **异常类**:用于在 `throw`表达式和相关的 `catch`子句之间传递异常的具体信息。
|
||||
184
docs/Cpp-Notes-main/Cpp-Primer/6.函数.md
Normal file
184
docs/Cpp-Notes-main/Cpp-Primer/6.函数.md
Normal file
@ -0,0 +1,184 @@
|
||||
---
|
||||
sort: 6
|
||||
---
|
||||
|
||||
# 函数
|
||||
|
||||
## 函数基础
|
||||
|
||||
- **函数定义**:包括返回类型、函数名字和0个或者多个**形参**(parameter)组成的列表和函数体。
|
||||
- **调用运算符**:调用运算符的形式是一对圆括号 `()`,作用于一个表达式,该表达式是函数或者指向函数的指针。
|
||||
- 圆括号内是用逗号隔开的**实参**(argument)列表。
|
||||
- 函数调用过程:
|
||||
- 1.主调函数(calling function)的执行被中断。
|
||||
- 2.被调函数(called function)开始执行。
|
||||
- **形参和实参**:形参和实参的**个数**和**类型**必须匹配上。
|
||||
- **返回类型**: `void`表示函数不返回任何值。函数的返回类型不能是数组类型或者函数类型,但可以是指向数组或者函数的指针。
|
||||
|
||||
|
||||
|
||||
### 局部对象
|
||||
|
||||
- **名字**:名字的作用于是程序文本的一部分,名字在其中可见。
|
||||
- **生命周期**:对象的生命周期是程序执行过程中该对象存在的一段时间。
|
||||
- **局部变量**(local variable):形参和函数体内部定义的变量统称为局部变量。它对函数而言是局部的,对函数外部而言是**隐藏**的。
|
||||
- **自动对象**:只存在于块执行期间的对象。当块的执行结束后,它的值就变成**未定义**的了。
|
||||
- **局部静态对象**: `static`类型的局部变量,生命周期贯穿函数调用前后。
|
||||
|
||||
### 函数声明
|
||||
|
||||
- **函数声明**:函数的声明和定义唯一的区别是声明无需函数体,用一个分号替代。函数声明主要用于描述函数的接口,也称**函数原型**。
|
||||
- **在头文件中进行函数声明**:建议变量在头文件中声明;在源文件中定义。
|
||||
- **分离编译**: `CC a.cc b.cc`直接编译生成可执行文件;`CC -c a.cc b.cc`编译生成对象代码`a.o b.o`; `CC a.o b.o`编译生成可执行文件。
|
||||
|
||||
## 参数传递
|
||||
|
||||
- 形参初始化的机理和变量初始化一样。
|
||||
- **引用传递**(passed by reference):又称传引用调用(called by reference),指**形参是引用类型**,引用形参是它对应的实参的别名。
|
||||
- **值传递**(passed by value):又称传值调用(called by value),指实参的值是通过**拷贝**传递给形参。
|
||||
|
||||
### 传值参数
|
||||
|
||||
- 当初始化一个非引用类型的变量时,初始值被拷贝给变量。
|
||||
- 函数对形参做的所有操作都不会影响实参。
|
||||
- **指针形参**:常用在C中,`C++`建议使用引用类型的形参代替指针。
|
||||
|
||||
### 传引用参数
|
||||
|
||||
- 通过使用引用形参,允许函数改变一个或多个实参的值。
|
||||
- 引用形参直接关联到绑定的对象,而非对象的副本。
|
||||
- 使用引用形参可以用于**返回额外的信息**。
|
||||
- 经常用引用形参来避免不必要的复制。
|
||||
- `void swap(int &v1, int &v2)`
|
||||
- 如果无需改变引用形参的值,最好将其声明为常量引用。
|
||||
|
||||
### const形参和实参
|
||||
|
||||
- 形参的顶层`const`被忽略。`void func(const int i);`调用时既可以传入`const int`也可以传入`int`。
|
||||
- 我们可以使用非常量初始化一个底层`const`对象,但是反过来不行。
|
||||
- 在函数中,不能改变实参的**局部副本**。
|
||||
- 尽量使用常量引用。
|
||||
|
||||
### 数组形参
|
||||
|
||||
- 当我们为函数传递一个数组时,实际上传递的是指向数组首元素的指针。
|
||||
- 要注意数组的实际长度,不能越界。
|
||||
|
||||
### main处理命令行选项
|
||||
|
||||
- `int main(int argc, char *argv[]){...}`
|
||||
- 第一个形参代表参数的个数;第二个形参是参数C风格字符串数组。
|
||||
|
||||
### 可变形参
|
||||
|
||||
`initializer_list`提供的操作(`C++11`):
|
||||
|
||||
| 操作 | 解释 |
|
||||
|-----|-----|
|
||||
| `initializer_list<T> lst;` | 默认初始化;`T`类型元素的空列表 |
|
||||
| `initializer_list<T> lst{a,b,c...};` | `lst`的元素数量和初始值一样多;`lst`的元素是对应初始值的副本;列表中的元素是`const`。 |
|
||||
| `lst2(lst)` | 拷贝或赋值一个`initializer_list`对象不会拷贝列表中的元素;拷贝后,原始列表和副本共享元素。 |
|
||||
| `lst2 = lst` | 同上 |
|
||||
| `lst.size()` | 列表中的元素数量 |
|
||||
| `lst.begin()` | 返回指向`lst`中首元素的指针 |
|
||||
| `lst.end()` | 返回指向`lst`中微元素下一位置的指针 |
|
||||
|
||||
`initializer_list`使用demo:
|
||||
|
||||
```cpp
|
||||
void err_msg(ErrCode e, initializer_list<string> il){
|
||||
cout << e.msg << endl;
|
||||
for (auto bed = il.begin(); beg != il.end(); ++ beg)
|
||||
cout << *beg << " ";
|
||||
cout << endl;
|
||||
}
|
||||
|
||||
err_msg(ErrCode(0), {"functionX", "okay});
|
||||
```
|
||||
|
||||
- 所有实参类型相同,可以使用 `initializer_list`的标准库类型。
|
||||
- 实参类型不同,可以使用`可变参数模板`。
|
||||
- 省略形参符: `...`,便于`C++`访问某些C代码,这些C代码使用了 `varargs`的C标准功能。
|
||||
|
||||
## 返回类型和return语句
|
||||
|
||||
### 无返回值函数
|
||||
|
||||
没有返回值的 `return`语句只能用在返回类型是 `void`的函数中,返回 `void`的函数不要求非得有 `return`语句。
|
||||
|
||||
### 有返回值函数
|
||||
|
||||
- `return`语句的返回值的类型必须和函数的返回类型相同,或者能够**隐式地**转换成函数的返回类型。
|
||||
- 值的返回:返回的值用于初始化调用点的一个**临时量**,该临时量就是函数调用的结果。
|
||||
- **不要返回局部对象的引用或指针**。
|
||||
- **引用返回左值**:函数的返回类型决定函数调用是否是左值。调用一个返回引用的函数得到左值;其他返回类型得到右值。
|
||||
- **列表初始化返回值**:函数可以返回花括号包围的值的列表。(`C++11`)
|
||||
- **主函数main的返回值**:如果结尾没有`return`,编译器将隐式地插入一条返回0的`return`语句。返回0代表执行成功。
|
||||
|
||||
### 返回数组指针
|
||||
|
||||
- `Type (*function (parameter_list))[dimension]`
|
||||
- 使用类型别名: `typedef int arrT[10];` 或者 `using arrT = int[10;]`,然后 `arrT* func() {...}`
|
||||
- 使用 `decltype`: `decltype(odd) *arrPtr(int i) {...}`
|
||||
- **尾置返回类型**: 在形参列表后面以一个`->`开始:`auto func(int i) -> int(*)[10]`(`C++11`)
|
||||
|
||||
## 函数重载
|
||||
|
||||
- **重载**:如果同一作用域内几个函数名字相同但形参列表不同,我们称之为重载(overload)函数。
|
||||
- `main`函数不能重载。
|
||||
- **重载和const形参**:
|
||||
- 一个有顶层const的形参和没有它的函数无法区分。 `Record lookup(Phone* const)`和 `Record lookup(Phone*)`无法区分。
|
||||
- 相反,是否有某个底层const形参可以区分。 `Record lookup(Account*)`和 `Record lookup(const Account*)`可以区分。
|
||||
- **重载和作用域**:若在内层作用域中声明名字,它将隐藏外层作用域中声明的同名实体,在不同的作用域中无法重载函数名。
|
||||
|
||||
## 特殊用途语言特性
|
||||
|
||||
### 默认实参
|
||||
|
||||
- `string screen(sz ht = 24, sz wid = 80, char backgrnd = ' ');`
|
||||
- 一旦某个形参被赋予了默认值,那么它之后的形参都必须要有默认值。
|
||||
|
||||
### 内联(inline)函数
|
||||
|
||||
- 普通函数的缺点:调用函数比求解等价表达式要慢得多。
|
||||
- `inline`函数可以避免函数调用的开销,可以让编译器在编译时**内联地展开**该函数。
|
||||
- `inline`函数应该在头文件中定义。
|
||||
|
||||
### constexpr函数
|
||||
|
||||
- 指能用于常量表达式的函数。
|
||||
- `constexpr int new_sz() {return 42;}`
|
||||
- 函数的返回类型及所有形参类型都要是字面值类型。
|
||||
- `constexpr`函数应该在头文件中定义。
|
||||
|
||||
### 调试帮助
|
||||
|
||||
- `assert`预处理宏(preprocessor macro):`assert(expr);`
|
||||
|
||||
开关调试状态:
|
||||
|
||||
`CC -D NDEBUG main.c`可以定义这个变量`NDEBUG`。
|
||||
|
||||
```cpp
|
||||
void print(){
|
||||
#ifndef NDEBUG
|
||||
cerr << __func__ << "..." << endl;
|
||||
#endif
|
||||
}
|
||||
```
|
||||
|
||||
## 函数匹配
|
||||
|
||||
- 重载函数匹配的**三个步骤**:1.候选函数;2.可行函数;3.寻找最佳匹配。
|
||||
- **候选函数**:选定本次调用对应的重载函数集,集合中的函数称为候选函数(candidate function)。
|
||||
- **可行函数**:考察本次调用提供的实参,选出可以被这组实参调用的函数,新选出的函数称为可行函数(viable function)。
|
||||
- **寻找最佳匹配**:基本思想:实参类型和形参类型越接近,它们匹配地越好。
|
||||
|
||||
## 函数指针
|
||||
|
||||
- **函数指针**:是指向函数的指针。
|
||||
- `bool (*pf)(const string &, const string &);` 注:两端的括号不可少。
|
||||
- **函数指针形参**:
|
||||
- 形参中使用函数定义或者函数指针定义效果一样。
|
||||
- 使用类型别名或者`decltype`。
|
||||
- **返回指向函数的指针**:1.类型别名;2.尾置返回类型。
|
||||
753
docs/Cpp-Notes-main/Cpp-Primer/7.类.md
Normal file
753
docs/Cpp-Notes-main/Cpp-Primer/7.类.md
Normal file
@ -0,0 +1,753 @@
|
||||
---
|
||||
sort: 7
|
||||
---
|
||||
|
||||
# 类
|
||||
|
||||
类的基本思想是数据抽象(data abstraction)和封装(encapsulation)。数据抽象是一种依赖于接口(interface)和实现(implementation)分离的编程及设计技术。类的接口包括用户所能执行的操作;类的实现包括类的数据成员、负责接口实现的函数体以及其他私有函数。
|
||||
|
||||
## 定义抽象数据类型(Defining Abstract Data Types)
|
||||
|
||||
### 设计Sales_data类(Designing the Sales_data Class)
|
||||
|
||||
类的用户是程序员,而非应用程序的最终使用者。
|
||||
|
||||
### 定义改进的Sales_data类(Defining the Revised Sales_data Class)
|
||||
|
||||
成员函数(member function)的声明必须在类的内部,定义则既可以在类的内部也可以在类的外部。定义在类内部的函数是隐式的内联函数。
|
||||
|
||||
```c++
|
||||
struct Sales_data
|
||||
{
|
||||
// new members: operations on Sales_data objects
|
||||
std::string isbn() const { return bookNo; }
|
||||
Sales_data& combine(const Sales_data&);
|
||||
double avg_price() const;
|
||||
|
||||
// data members
|
||||
std::string bookNo;
|
||||
unsigned units_sold = 0;
|
||||
double revenue = 0.0;
|
||||
};
|
||||
```
|
||||
|
||||
成员函数通过一个名为`this`的隐式额外参数来访问调用它的对象。`this`参数是一个常量指针,被初始化为调用该函数的对象地址。在函数体内可以显式使用`this`指针。
|
||||
|
||||
```c++
|
||||
total.isbn()
|
||||
// pseudo-code illustration of how a call to a member function is translated
|
||||
Sales_data::isbn(&total)
|
||||
|
||||
std::string isbn() const { return this->bookNo; }
|
||||
std::string isbn() const { return bookNo; }
|
||||
```
|
||||
|
||||
默认情况下,`this`的类型是指向类类型非常量版本的常量指针。`this`也遵循初始化规则,所以默认不能把`this`绑定到一个常量对象上,即不能在常量对象上调用普通的成员函数。
|
||||
|
||||
C++允许在成员函数的参数列表后面添加关键字`const`,表示`this`是一个指向常量的指针。使用关键字`const`的成员函数被称作常量成员函数(const member function)。
|
||||
|
||||
```c++
|
||||
// pseudo-code illustration of how the implicit this pointer is used
|
||||
// this code is illegal: we may not explicitly define the this pointer ourselves
|
||||
// note that this is a pointer to const because isbn is a const member
|
||||
std::string Sales_data::isbn(const Sales_data *const this)
|
||||
{
|
||||
return this->isbn;
|
||||
}
|
||||
```
|
||||
|
||||
常量对象和指向常量对象的引用或指针都只能调用常量成员函数。
|
||||
|
||||
类本身就是一个作用域,成员函数的定义嵌套在类的作用域之内。编译器处理类时,会先编译成员声明,再编译成员函数体(如果有的话),因此成员函数可以随意使用类的其他成员而无须在意这些成员的出现顺序。
|
||||
|
||||
在类的外部定义成员函数时,成员函数的定义必须与它的声明相匹配。如果成员函数被声明为常量成员函数,那么它的定义也必须在参数列表后面指定`const`属性。同时,类外部定义的成员名字必须包含它所属的类名。
|
||||
|
||||
```c++
|
||||
double Sales_data::avg_price() const
|
||||
{
|
||||
if (units_sold)
|
||||
return revenue / units_sold;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
可以定义返回`this`对象的成员函数。
|
||||
|
||||
```c++
|
||||
Sales_data& Sales_data::combine(const Sales_data &rhs)
|
||||
{
|
||||
units_sold += rhs.units_sold; // add the members of rhs into
|
||||
revenue += rhs.revenue; // the members of 'this' object
|
||||
return *this; // return the object on which the function was called
|
||||
}
|
||||
```
|
||||
|
||||
### 定义类相关的非成员函数(Defining Nonmember Class-Related Functions)
|
||||
|
||||
类的作者通常会定义一些辅助函数,尽管这些函数从概念上来说属于类接口的组成部分,但实际上它们并不属于类本身。
|
||||
|
||||
```c++
|
||||
// input transactions contain ISBN, number of copies sold, and sales price
|
||||
istream &read(istream &is, Sales_data &item)
|
||||
{
|
||||
double price = 0;
|
||||
is >> item.bookNo >> item.units_sold >> price;
|
||||
item.revenue = price * item.units_sold;
|
||||
return is;
|
||||
}
|
||||
|
||||
ostream &print(ostream &os, const Sales_data &item)
|
||||
{
|
||||
os << item.isbn() << " " << item.units_sold << " "
|
||||
<< item.revenue << " " << item.avg_price();
|
||||
return os;
|
||||
}
|
||||
```
|
||||
|
||||
如果非成员函数是类接口的组成部分,则这些函数的声明应该与类放在同一个头文件中。
|
||||
|
||||
一般来说,执行输出任务的函数应该尽量减少对格式的控制。
|
||||
|
||||
### 构造函数(Constructors)
|
||||
|
||||
类通过一个或几个特殊的成员函数来控制其对象的初始化操作,这些函数被称作构造函数。只要类的对象被创建,就会执行构造函数。
|
||||
|
||||
构造函数的名字和类名相同,没有返回类型,且不能被声明为`const`函数。构造函数在`const`对象的构造过程中可以向其写值。
|
||||
|
||||
```c++
|
||||
struct Sales_data
|
||||
{
|
||||
// constructors added
|
||||
Sales_data() = default;
|
||||
Sales_data(const std::string &s): bookNo(s) { }
|
||||
Sales_data(const std::string &s, unsigned n, double p):
|
||||
bookNo(s), units_sold(n), revenue(p*n) { }
|
||||
Sales_data(std::istream &);
|
||||
// other members as before
|
||||
};
|
||||
```
|
||||
|
||||
类通过默认构造函数(default constructor)来控制默认初始化过程,默认构造函数无须任何实参。
|
||||
|
||||
如果类没有显式地定义构造函数,则编译器会为类隐式地定义一个默认构造函数,该构造函数也被称为合成的默认构造函数(synthesized default constructor)。对于大多数类来说,合成的默认构造函数初始化数据成员的规则如下:
|
||||
|
||||
- 如果存在类内初始值,则用它来初始化成员。
|
||||
|
||||
- 否则默认初始化该成员。
|
||||
|
||||
某些类不能依赖于合成的默认构造函数。
|
||||
|
||||
- 只有当类没有声明任何构造函数时,编译器才会自动生成默认构造函数。一旦类定义了其他构造函数,那么除非再显式地定义一个默认的构造函数,否则类将没有默认构造函数。
|
||||
|
||||
- 如果类包含内置类型或者复合类型的成员,则只有当这些成员全部存在类内初始值时,这个类才适合使用合成的默认构造函数。否则用户在创建类的对象时就可能得到未定义的值。
|
||||
|
||||
- 编译器不能为某些类合成默认构造函数。例如类中包含一个其他类类型的成员,且该类型没有默认构造函数,那么编译器将无法初始化该成员。
|
||||
|
||||
在C++11中,如果类需要默认的函数行为,可以通过在参数列表后面添加`=default`来要求编译器生成构造函数。其中`=default`既可以和函数声明一起出现在类的内部,也可以作为定义出现在类的外部。和其他函数一样,如果`=default`在类的内部,则默认构造函数是内联的。
|
||||
|
||||
```c++
|
||||
Sales_data() = default;
|
||||
```
|
||||
构造函数初始值列表(constructor initializer list)负责为新创建对象的一个或几个数据成员赋初始值。形式是每个成员名字后面紧跟括号括起来的(或者在花括号内的)成员初始值,不同成员的初始值通过逗号分隔。
|
||||
|
||||
```c++
|
||||
Sales_data(const std::string &s): bookNo(s) { }
|
||||
Sales_data(const std::string &s, unsigned n, double p):
|
||||
bookNo(s), units_sold(n), revenue(p*n) { }
|
||||
```
|
||||
当某个数据成员被构造函数初始值列表忽略时,它会以与合成默认构造函数相同的方式隐式初始化。
|
||||
|
||||
```c++
|
||||
// has the same behavior as the original constructor defined above
|
||||
Sales_data(const std::string &s):
|
||||
bookNo(s), units_sold(0), revenue(0) { }
|
||||
```
|
||||
|
||||
构造函数不应该轻易覆盖掉类内初始值,除非新值与原值不同。如果编译器不支持类内初始值,则所有构造函数都应该显式初始化每个内置类型的成员。
|
||||
|
||||
### 拷贝、赋值和析构(Copy、Assignment,and Destruction)
|
||||
|
||||
编译器能合成拷贝、赋值和析构函数,但是对于某些类来说合成的版本无法正常工作。特别是当类需要分配类对象之外的资源时,合成的版本通常会失效。
|
||||
|
||||
## 访问控制与封装(Access Control and Encapsulation)
|
||||
|
||||
使用访问说明符(access specifier)可以加强类的封装性:
|
||||
|
||||
- 定义在`public`说明符之后的成员在整个程序内都可以被访问。`public`成员定义类的接口。
|
||||
|
||||
- 定义在`private`说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问。`private`部分封装了类的实现细节。
|
||||
|
||||
```c++
|
||||
class Sales_data
|
||||
{
|
||||
public: // access specifier added
|
||||
Sales_data() = default;
|
||||
Sales_data(const std::string &s, unsigned n, double p):
|
||||
bookNo(s), units_sold(n), revenue(p*n) { }
|
||||
Sales_data(const std::string &s): bookNo(s) { }
|
||||
Sales_data(std::istream&);
|
||||
std::string isbn() const { return bookNo; }
|
||||
Sales_data &combine(const Sales_data&);
|
||||
|
||||
private: // access specifier added
|
||||
double avg_price() const { return units_sold ? revenue/units_sold : 0; }
|
||||
std::string bookNo;
|
||||
unsigned units_sold = 0;
|
||||
double revenue = 0.0;
|
||||
};
|
||||
```
|
||||
|
||||
一个类可以包含零或多个访问说明符,每个访问说明符指定了接下来的成员的访问级别,其有效范围到出现下一个访问说明符或类的结尾处为止。
|
||||
|
||||
使用关键字`struct`定义类时,定义在第一个访问说明符之前的成员是`public`的;而使用关键字`class`时,这些成员是`private`的。二者唯一的区别就是默认访问权限不同。
|
||||
|
||||
### 友元(Friends)
|
||||
|
||||
类可以允许其他类或函数访问它的非公有成员,方法是使用关键字`friend`将其他类或函数声明为它的友元。
|
||||
|
||||
```C++
|
||||
class Sales_data
|
||||
{
|
||||
// friend declarations for nonmember Sales_data operations added
|
||||
friend Sales_data add(const Sales_data&, const Sales_data&);
|
||||
friend std::istream &read(std::istream&, Sales_data&);
|
||||
friend std::ostream &print(std::ostream&, const Sales_data&);
|
||||
|
||||
// other members and access specifiers as before
|
||||
public:
|
||||
Sales_data() = default;
|
||||
Sales_data(const std::string &s, unsigned n, double p):
|
||||
bookNo(s), units_sold(n), revenue(p*n) { }
|
||||
Sales_data(const std::string &s): bookNo(s) { }
|
||||
Sales_data(std::istream&);
|
||||
std::string isbn() const { return bookNo; }
|
||||
Sales_data &combine(const Sales_data&);
|
||||
|
||||
private:
|
||||
std::string bookNo;
|
||||
unsigned units_sold = 0;
|
||||
double revenue = 0.0;
|
||||
};
|
||||
|
||||
// declarations for nonmember parts of the Sales_data interface
|
||||
Sales_data add(const Sales_data&, const Sales_data&);
|
||||
std::istream &read(std::istream&, Sales_data&);
|
||||
std::ostream &print(std::ostream&, const Sales_data&);
|
||||
```
|
||||
|
||||
友元声明只能出现在类定义的内部,具体位置不限。友元不是类的成员,也不受它所在区域访问级别的约束。
|
||||
|
||||
通常情况下,最好在类定义开始或结束前的位置集中声明友元。
|
||||
|
||||
封装的好处:
|
||||
|
||||
- 确保用户代码不会无意间破坏封装对象的状态。
|
||||
|
||||
- 被封装的类的具体实现细节可以随时改变,而无须调整用户级别的代码。
|
||||
|
||||
友元声明仅仅指定了访问权限,而并非一个通常意义上的函数声明。如果希望类的用户能调用某个友元函数,就必须在友元声明之外再专门对函数进行一次声明(部分编译器没有该限制)。
|
||||
|
||||
为了使友元对类的用户可见,通常会把友元的声明(类的外部)与类本身放在同一个头文件中。
|
||||
|
||||
## 类的其他特性(Additional Class Features)
|
||||
|
||||
### 类成员再探(Class Members Revisited)
|
||||
|
||||
由类定义的类型名字和其他成员一样存在访问限制,可以是`public`或`private`中的一种。
|
||||
|
||||
```c++
|
||||
class Screen
|
||||
{
|
||||
public:
|
||||
// alternative way to declare a type member using a type alias
|
||||
using pos = std::string::size_type;
|
||||
// other members as before
|
||||
};
|
||||
```
|
||||
|
||||
与普通成员不同,用来定义类型的成员必须先定义后使用。类型成员通常位于类起始处。
|
||||
|
||||
定义在类内部的成员函数是自动内联的。
|
||||
|
||||
如果需要显式声明内联成员函数,建议只在类外部定义的位置说明`inline`。
|
||||
|
||||
`inline`成员函数该与类定义在同一个头文件中。
|
||||
|
||||
使用关键字`mutable`可以声明可变数据成员(mutable data member)。可变数据成员永远不会是`const`的,即使它在`const`对象内。因此`const`成员函数可以修改可变成员的值。
|
||||
|
||||
```c++
|
||||
class Screen
|
||||
{
|
||||
public:
|
||||
void some_member() const;
|
||||
private:
|
||||
mutable size_t access_ctr; // may change even in a const object
|
||||
// other members as before
|
||||
};
|
||||
|
||||
void Screen::some_member() const
|
||||
{
|
||||
++access_ctr; // keep a count of the calls to any member function
|
||||
// whatever other work this member needs to do
|
||||
}
|
||||
```
|
||||
|
||||
提供类内初始值时,必须使用`=`或花括号形式。
|
||||
|
||||
### 返回\*this的成员函数(Functions That Return \*this)
|
||||
|
||||
`const`成员函数如果以引用形式返回`*this`,则返回类型是常量引用。
|
||||
|
||||
通过区分成员函数是否为`const`的,可以对其进行重载。在常量对象上只能调用`const`版本的函数;在非常量对象上,尽管两个版本都能调用,但会选择非常量版本。
|
||||
|
||||
```c++
|
||||
class Screen
|
||||
{
|
||||
public:
|
||||
// display overloaded on whether the object is const or not
|
||||
Screen &display(std::ostream &os)
|
||||
{ do_display(os); return *this; }
|
||||
const Screen &display(std::ostream &os) const
|
||||
{ do_display(os); return *this; }
|
||||
|
||||
private:
|
||||
// function to do the work of displaying a Screen
|
||||
void do_display(std::ostream &os) const
|
||||
{ os << contents; }
|
||||
// other members as before
|
||||
};
|
||||
|
||||
Screen myScreen(5,3);
|
||||
const Screen blank(5, 3);
|
||||
myScreen.set('#').display(cout); // calls non const version
|
||||
blank.display(cout); // calls const version
|
||||
```
|
||||
|
||||
### 类类型(Class Types)
|
||||
|
||||
每个类定义了唯一的类型。即使两个类的成员列表完全一致,它们也是不同的类型。
|
||||
|
||||
可以仅仅声明一个类而暂时不定义它。这种声明被称作前向声明(forward declaration),用于引入类的名字。在类声明之后定义之前都是一个不完全类型(incomplete type)。
|
||||
|
||||
```c++
|
||||
class Screen; // declaration of the Screen class
|
||||
```
|
||||
|
||||
可以定义指向不完全类型的指针或引用,也可以声明(不能定义)以不完全类型作为参数或返回类型的函数。
|
||||
|
||||
只有当类全部完成后才算被定义,所以一个类的成员类型不能是该类本身。但是一旦类的名字出现,就可以被认为是声明过了,因此类可以包含指向它自身类型的引用或指针。
|
||||
|
||||
```c++
|
||||
class Link_screen
|
||||
{
|
||||
Screen window;
|
||||
Link_screen *next;
|
||||
Link_screen *prev;
|
||||
};
|
||||
```
|
||||
|
||||
### 友元再探(Friendship Revisited)
|
||||
|
||||
除了普通函数,类还可以把其他类或其他类的成员函数声明为友元。友元类的成员函数可以访问此类包括非公有成员在内的所有成员。
|
||||
|
||||
```c++
|
||||
class Screen
|
||||
{
|
||||
// Window_mgr members can access the private parts of class Screen
|
||||
friend class Window_mgr;
|
||||
// ... rest of the Screen class
|
||||
};
|
||||
```
|
||||
|
||||
友元函数可以直接定义在类的内部,这种函数是隐式内联的。但是必须在类外部提供相应声明令函数可见。
|
||||
|
||||
```c++
|
||||
struct X
|
||||
{
|
||||
friend void f() { /* friend function can be defined in the class body */ }
|
||||
X() { f(); } // error: no declaration for f
|
||||
void g();
|
||||
void h();
|
||||
};
|
||||
|
||||
void X::g() { return f(); } // error: f hasn't been declared
|
||||
void f(); // declares the function defined inside X
|
||||
void X::h() { return f(); } // ok: declaration for f is now in scope
|
||||
```
|
||||
|
||||
友元关系不存在传递性。
|
||||
|
||||
把其他类的成员函数声明为友元时,必须明确指定该函数所属的类名。
|
||||
|
||||
```c++
|
||||
class Screen
|
||||
{
|
||||
// Window_mgr::clear must have been declared before class Screen
|
||||
friend void Window_mgr::clear(ScreenIndex);
|
||||
// ... rest of the Screen class
|
||||
};
|
||||
```
|
||||
|
||||
如果类想把一组重载函数声明为友元,需要对这组函数中的每一个分别声明。
|
||||
|
||||
## 类的作用域(Class Scope)
|
||||
|
||||
当成员函数定义在类外时,返回类型中使用的名字位于类的作用域之外,此时返回类型必须指明它是哪个类的成员。
|
||||
|
||||
```c++
|
||||
class Window_mgr
|
||||
{
|
||||
public:
|
||||
// add a Screen to the window and returns its index
|
||||
ScreenIndex addScreen(const Screen&);
|
||||
// other members as before
|
||||
};
|
||||
|
||||
// return type is seen before we're in the scope of Window_mgr
|
||||
Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s)
|
||||
{
|
||||
screens.push_back(s);
|
||||
return screens.size() - 1;
|
||||
}
|
||||
```
|
||||
|
||||
### 名字查找与作用域(Name Lookup and Class Scope)
|
||||
|
||||
成员函数体直到整个类可见后才会被处理,因此它能使用类中定义的任何名字。
|
||||
|
||||
声明中使用的名字,包括返回类型或参数列表,都必须确保使用前可见。
|
||||
|
||||
如果类的成员使用了外层作用域的某个名字,而该名字表示一种类型,则类不能在之后重新定义该名字。
|
||||
|
||||
```c++
|
||||
typedef double Money;
|
||||
class Account
|
||||
{
|
||||
public:
|
||||
Money balance() { return bal; } // uses Money from the outer scop
|
||||
private:
|
||||
typedef double Money; // error: cannot redefine Money
|
||||
Money bal;
|
||||
// ...
|
||||
};
|
||||
```
|
||||
|
||||
类型名定义通常出现在类起始处,这样能确保所有使用该类型的成员都位于类型名定义之后。
|
||||
|
||||
成员函数中名字的解析顺序:
|
||||
|
||||
- 在成员函数内查找该名字的声明,只有在函数使用之前出现的声明才会被考虑。
|
||||
|
||||
- 如果在成员函数内没有找到,则会在类内继续查找,这时会考虑类的所有成员。
|
||||
|
||||
- 如果类内也没有找到,会在成员函数定义之前的作用域查找。
|
||||
|
||||
```c++
|
||||
// it is generally a bad idea to use the same name for a parameter and a member
|
||||
int height; // defines a name subsequently used inside Screen
|
||||
class Screen
|
||||
{
|
||||
public:
|
||||
typedef std::string::size_type pos;
|
||||
void dummy_fcn(pos height)
|
||||
{
|
||||
cursor = width * height; // which height? the parameter
|
||||
}
|
||||
|
||||
private:
|
||||
pos cursor = 0;
|
||||
pos height = 0, width = 0;
|
||||
};
|
||||
```
|
||||
|
||||
可以通过作用域运算符`::`或显式`this`指针来强制访问被隐藏的类成员。
|
||||
|
||||
```c++
|
||||
// bad practice: names local to member functions shouldn't hide member names
|
||||
void Screen::dummy_fcn(pos height)
|
||||
{
|
||||
cursor = width * this->height; // member height
|
||||
// alternative way to indicate the member
|
||||
cursor = width * Screen::height; // member height
|
||||
}
|
||||
|
||||
// good practice: don't use a member name for a parameter or other local variable
|
||||
void Screen::dummy_fcn(pos ht)
|
||||
{
|
||||
cursor = width * height; // member height
|
||||
}
|
||||
```
|
||||
|
||||
## 构造函数再探(Constructors Revisited)
|
||||
|
||||
### 构造函数初始值列表(Constructor Initializer List)
|
||||
|
||||
如果没有在构造函数初始值列表中显式初始化成员,该成员会在构造函数体之前执行默认初始化。
|
||||
|
||||
如果成员是`const`、引用,或者是某种未定义默认构造函数的类类型,必须在初始值列表中将其初始化。
|
||||
|
||||
```c++
|
||||
class ConstRef
|
||||
{
|
||||
public:
|
||||
ConstRef(int ii);
|
||||
private:
|
||||
int i;
|
||||
const int ci;
|
||||
int &ri;
|
||||
};
|
||||
|
||||
// ok: explicitly initialize reference and const members
|
||||
ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(i) { }
|
||||
```
|
||||
|
||||
最好令构造函数初始值的顺序与成员声明的顺序一致,并且尽量避免使用某些成员初始化其他成员。
|
||||
|
||||
如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数。
|
||||
|
||||
### 委托构造函数(Delegating Constructors)
|
||||
|
||||
C++11扩展了构造函数初始值功能,可以定义委托构造函数。委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程。
|
||||
|
||||
```c++
|
||||
class Sales_data
|
||||
{
|
||||
public:
|
||||
// defines the default constructor as well as one that takes a string argument
|
||||
Sales_data(std::string s = ""): bookNo(s) { }
|
||||
// remaining constructors unchanged
|
||||
Sales_data(std::string s, unsigned cnt, double rev):
|
||||
bookNo(s), units_sold(cnt), revenue(rev*cnt) { }
|
||||
Sales_data(std::istream &is) { read(is, *this); }
|
||||
// remaining members as before
|
||||
}
|
||||
```
|
||||
|
||||
### 默认构造函数的作用(The Role of the Default Constructor)
|
||||
|
||||
当对象被默认初始化或值初始化时会自动执行默认构造函数。
|
||||
|
||||
默认初始化的发生情况:
|
||||
|
||||
- 在块作用域内不使用初始值定义非静态变量或数组。
|
||||
|
||||
- 类本身含有类类型的成员且使用合成默认构造函数。
|
||||
|
||||
- 类类型的成员没有在构造函数初始值列表中显式初始化。
|
||||
|
||||
值初始化的发生情况:
|
||||
|
||||
- 数组初始化时提供的初始值数量少于数组大小。
|
||||
|
||||
- 不使用初始值定义局部静态变量。
|
||||
|
||||
- 通过`T()`形式(`T`为类型)的表达式显式地请求值初始化。
|
||||
|
||||
类必须包含一个默认构造函数。
|
||||
|
||||
如果想定义一个使用默认构造函数进行初始化的对象,应该去掉对象名后的空括号对。
|
||||
|
||||
```c++
|
||||
Sales_data obj(); // oops! declares a function, not an object
|
||||
Sales_data obj2; // ok: obj2 is an object, not a function
|
||||
```
|
||||
|
||||
### 隐式的类类型转换(Implicit Class-Type Conversions)
|
||||
|
||||
如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制。这种构造函数被称为转换构造函数(converting constructor)。
|
||||
|
||||
```c++
|
||||
string null_book = "9-999-99999-9";
|
||||
// constructs a temporary Sales_data object
|
||||
// with units_sold and revenue equal to 0 and bookNo equal to null_book
|
||||
item.combine(null_book);
|
||||
```
|
||||
|
||||
编译器只会自动执行一步类型转换。
|
||||
|
||||
```c++
|
||||
// error: requires two user-defined conversions:
|
||||
// (1) convert "9-999-99999-9" to string
|
||||
// (2) convert that (temporary) string to Sales_data
|
||||
item.combine("9-999-99999-9");
|
||||
// ok: explicit conversion to string, implicit conversion to Sales_data
|
||||
item.combine(string("9-999-99999-9"));
|
||||
// ok: implicit conversion to string, explicit conversion to Sales_data
|
||||
item.combine(Sales_data("9-999-99999-9"));
|
||||
```
|
||||
|
||||
在要求隐式转换的程序上下文中,可以通过将构造函数声明为`explicit`的加以阻止。
|
||||
|
||||
```c++
|
||||
class Sales_data
|
||||
{
|
||||
public:
|
||||
Sales_data() = default;
|
||||
Sales_data(const std::string &s, unsigned n, double p):
|
||||
bookNo(s), units_sold(n), revenue(p*n) { }
|
||||
explicit Sales_data(const std::string &s): bookNo(s) { }
|
||||
explicit Sales_data(std::istream&);
|
||||
// remaining members as before
|
||||
};
|
||||
```
|
||||
|
||||
`explicit`关键字只对接受一个实参的构造函数有效。
|
||||
|
||||
只能在类内声明构造函数时使用`explicit`关键字,在类外定义时不能重复。
|
||||
|
||||
执行拷贝初始化时(使用`=`)会发生隐式转换,所以`explicit`构造函数只能用于直接初始化。
|
||||
|
||||
```c++
|
||||
Sales_data item1 (null_book); // ok: direct initialization
|
||||
// error: cannot use the copy form of initialization with an explicit constructor
|
||||
Sales_data item2 = null_book;
|
||||
```
|
||||
|
||||
可以使用`explicit`构造函数显式地强制转换类型。
|
||||
|
||||
```c++
|
||||
// ok: the argument is an explicitly constructed Sales_data object
|
||||
item.combine(Sales_data(null_book));
|
||||
// ok: static_cast can use an explicit constructor
|
||||
item.combine(static_cast<Sales_data>(cin));
|
||||
```
|
||||
|
||||
### 聚合类(Aggregate Classes)
|
||||
|
||||
聚合类满足如下条件:
|
||||
|
||||
- 所有成员都是`public`的。
|
||||
|
||||
- 没有定义任何构造函数。
|
||||
|
||||
- 没有类内初始值。
|
||||
|
||||
- 没有基类。
|
||||
|
||||
- 没有虚函数。
|
||||
|
||||
```c++
|
||||
struct Data
|
||||
{
|
||||
int ival;
|
||||
string s;
|
||||
};
|
||||
```
|
||||
|
||||
可以使用一个用花括号包围的成员初始值列表初始化聚合类的数据成员。初始值顺序必须与声明顺序一致。如果初始值列表中的元素个数少于类的成员个数,则靠后的成员被值初始化。
|
||||
|
||||
```c++
|
||||
// val1.ival = 0; val1.s = string("Anna")
|
||||
Data val1 = { 0, "Anna" };
|
||||
```
|
||||
|
||||
### 字面值常量类(Literal Classes)
|
||||
|
||||
数据成员都是字面值类型的聚合类是字面值常量类。或者一个类不是聚合类,但符合下列条件,则也是字面值常量类:
|
||||
|
||||
- 数据成员都是字面值类型。
|
||||
|
||||
- 类至少含有一个`constexpr`构造函数。
|
||||
|
||||
- 如果数据成员含有类内初始值,则内置类型成员的初始值必须是常量表达式。如果成员属于类类型,则初始值必须使用成员自己的`constexpr`构造函数。
|
||||
|
||||
- 类必须使用析构函数的默认定义。
|
||||
|
||||
`constexpr`构造函数用于生成`constexpr`对象以及`constexpr`函数的参数或返回类型。
|
||||
|
||||
`constexpr`构造函数必须初始化所有数据成员,初始值使用`constexpr`构造函数或常量表达式。
|
||||
|
||||
## 类的静态成员(static Class Members)
|
||||
|
||||
使用关键字`static`可以声明类的静态成员。静态成员存在于任何对象之外,对象中不包含与静态成员相关的数据。
|
||||
|
||||
```c++
|
||||
class Account
|
||||
{
|
||||
public:
|
||||
void calculate() { amount += amount * interestRate; }
|
||||
static double rate() { return interestRate; }
|
||||
static void rate(double);
|
||||
|
||||
private:
|
||||
std::string owner;
|
||||
double amount;
|
||||
static double interestRate;
|
||||
static double initRate();
|
||||
};
|
||||
```
|
||||
|
||||
由于静态成员不与任何对象绑定,因此静态成员函数不能声明为`const`的,也不能在静态成员函数内使用`this`指针。
|
||||
|
||||
用户代码可以使用作用域运算符访问静态成员,也可以通过类对象、引用或指针访问。类的成员函数可以直接访问静态成员。
|
||||
|
||||
```c++
|
||||
double r;
|
||||
r = Account::rate(); // access a static member using the scope operator
|
||||
|
||||
Account ac1;
|
||||
Account *ac2 = &ac1;
|
||||
// equivalent ways to call the static member rate function
|
||||
r = ac1.rate(); // through an Account object or reference
|
||||
r = ac2->rate(); // through a pointer to an Account object
|
||||
|
||||
class Account
|
||||
{
|
||||
public:
|
||||
void calculate() { amount += amount * interestRate; }
|
||||
private:
|
||||
static double interestRate;
|
||||
// remaining members as before
|
||||
};
|
||||
```
|
||||
|
||||
在类外部定义静态成员时,不能重复`static`关键字,其只能用于类内部的声明语句。
|
||||
|
||||
由于静态数据成员不属于类的任何一个对象,因此它们并不是在创建类对象时被定义的。通常情况下,不应该在类内部初始化静态成员。而必须在类外部定义并初始化每个静态成员。一个静态成员只能被定义一次。一旦它被定义,就会一直存在于程序的整个生命周期中。
|
||||
|
||||
```c++
|
||||
// define and initialize a static class member
|
||||
double Account::interestRate = initRate();
|
||||
```
|
||||
|
||||
建议把静态数据成员的定义与其他非内联函数的定义放在同一个源文件中,这样可以确保对象只被定义一次。
|
||||
|
||||
尽管在通常情况下,不应该在类内部初始化静态成员。但是可以为静态成员提供`const`整数类型的类内初始值,不过要求静态成员必须是字面值常量类型的`constexpr`。初始值必须是常量表达式。
|
||||
|
||||
```c++
|
||||
class Account
|
||||
{
|
||||
public:
|
||||
static double rate() { return interestRate; }
|
||||
static void rate(double);
|
||||
private:
|
||||
static constexpr int period = 30; // period is a constant
|
||||
double daily_tbl[period];
|
||||
};
|
||||
```
|
||||
|
||||
静态数据成员的类型可以是它所属的类类型。
|
||||
|
||||
```c++
|
||||
class Bar
|
||||
{
|
||||
static Bar mem1; // ok: static member can have incomplete type
|
||||
Bar *mem2; // ok: pointer member can have incomplete type
|
||||
Bar mem3; // error: data members must have complete type
|
||||
}
|
||||
```
|
||||
|
||||
可以使用静态成员作为函数的默认实参。
|
||||
|
||||
```c++
|
||||
class Screen
|
||||
{
|
||||
public:
|
||||
// bkground refers to the static member
|
||||
// declared later in the class definition
|
||||
Screen& clear(char = bkground);
|
||||
private:
|
||||
static const char bkground;
|
||||
};
|
||||
```
|
||||
111
docs/Cpp-Notes-main/Cpp-Primer/8.IO库.md
Normal file
111
docs/Cpp-Notes-main/Cpp-Primer/8.IO库.md
Normal file
@ -0,0 +1,111 @@
|
||||
---
|
||||
sort: 8
|
||||
---
|
||||
|
||||
# IO库
|
||||
|
||||
## 前面章节已经在用的IO库设施
|
||||
|
||||
- **istream**:输入流类型,提供输入操作。
|
||||
- **ostream**:输出流类型,提供输出操作
|
||||
- **cin**:一个`istream`对象,从标准输入读取数据。
|
||||
- **cout**:一个`ostream`对象,向标准输出写入数据。
|
||||
- **cerr**:一个`ostream`对象,向标准错误写入消息。
|
||||
- **>>运算符**:用来从一个`istream`对象中读取输入数据。
|
||||
- **<<运算符**:用来向一个`ostream`对象中写入输出数据。
|
||||
- **getline函数**:从一个给定的`istream`对象中读取一行数据,存入到一个给定的`string`对象中。
|
||||
|
||||
## IO类
|
||||
|
||||
### 标准库定义的IO类型
|
||||
|
||||
- `iostream`头文件:从标准流中读写数据,`istream`、`ostream`等。
|
||||
- `fstream`头文件:从文件中读写数据,`ifstream`、`ofstream`等。
|
||||
- `sstream`头文件:从字符串中读写数据,`istringstream`、`ostringstream`
|
||||
|
||||
### IO对象不可复制或赋值
|
||||
|
||||
- 1.IO对象不能存在容器里.
|
||||
- 2.形参和返回类型也不能是流类型。
|
||||
- 3.形参和返回类型一般是流的**引用**。
|
||||
- 4.读写一个IO对象会改变其状态,因此传递和返回的引用不能是`const`的。
|
||||
|
||||
### 条件状态
|
||||
|
||||
| 状态 | 解释 |
|
||||
| ------------------- | ------------------------------------------------------------- |
|
||||
| `strm:iostate` | 是一种机器无关的**类型**,提供了表达条件状态的完整功能 |
|
||||
| `strm:badbit` | 用来指出流已经崩溃 |
|
||||
| `strm:failbit` | 用来指出一个IO操作失败了 |
|
||||
| `strm:eofbit` | 用来指出流到达了文件结束 |
|
||||
| `strm:goodbit` | 用来指出流未处于错误状态,此值保证为零 |
|
||||
| `s.eof()` | 若流`s`的`eofbit`置位,则返回`true` |
|
||||
| `s.fail()` | 若流`s`的`failbit`置位,则返回`true` |
|
||||
| `s.bad()` | 若流`s`的`badbit`置位,则返回`true` |
|
||||
| `s.good()` | 若流`s`处于有效状态,则返回`true` |
|
||||
| `s.clear()` | 将流`s`中所有条件状态位复位,将流的状态设置成有效,返回`void` |
|
||||
| `s.clear(flags)` | 将流`s`中指定的条件状态位复位,返回`void` |
|
||||
| `s.setstate(flags)` | 根据给定的标志位,将流`s`中对应的条件状态位置位,返回`void` |
|
||||
| `s.rdstate()` | 返回流`s`的当前条件状态,返回值类型为`strm::iostate` |
|
||||
|
||||
上表中,`strm`是一种IO类型,(如`istream`), `s`是一个流对象。
|
||||
|
||||
### 管理输出缓冲
|
||||
|
||||
- 每个输出流都管理一个缓冲区,执行输出的代码,文本串可能立即打印出来,也可能被操作系统保存在缓冲区内,随后再打印。
|
||||
- 刷新缓冲区,可以使用如下IO操纵符:
|
||||
- `endl`:输出一个换行符并刷新缓冲区。
|
||||
- `flush`:刷新流,单不添加任何字符。
|
||||
- `ends`:在缓冲区插入空字符`null`,然后刷新。
|
||||
- `unitbuf`:告诉流接下来每次操作之后都要进行一次`flush`操作。
|
||||
- `nounitbuf`:回到正常的缓冲方式。
|
||||
|
||||
## 文件输入输出
|
||||
|
||||
- 头文件`fstream`定义了三个类型来支持文件IO:
|
||||
- `ifstream`从一个给定文件读取数据。
|
||||
- `ofstream`向一个给定文件写入数据。
|
||||
- `fstream`可以读写给定文件。
|
||||
- **文件流**:需要读写文件时,必须定义自己的文件流对象,并绑定在需要的文件上。
|
||||
|
||||
### fstream特有的操作
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ------------------------- | ------------------------------------------------------------------------ |
|
||||
| `fstream fstrm;` | 创建一个未绑定的文件流。 |
|
||||
| `fstream fstrm(s);` | 创建一个文件流,并打开名为`s`的文件,`s`可以是`string`也可以是`char`指针 |
|
||||
| `fstream fstrm(s, mode);` | 与前一个构造函数类似,但按指定`mode`打开文件 |
|
||||
| `fstrm.open(s)` | 打开名为`s`的文件,并和`fstrm`绑定 |
|
||||
| `fstrm.close()` | 关闭和`fstrm`绑定的文件 |
|
||||
| `fstrm.is_open()` | 返回一个`bool`值,指出与`fstrm`关联的文件是否成功打开且尚未关闭 |
|
||||
|
||||
上表中,`fstream`是头文件`fstream`中定义的一个类型,`fstrm`是一个文件流对象。
|
||||
|
||||
### 文件模式
|
||||
|
||||
| 文件模式 | 解释 |
|
||||
| -------- | ---------------------------- |
|
||||
| `in` | 以读的方式打开 |
|
||||
| `out` | 以写的方式打开 |
|
||||
| `app` | 每次写操作前均定位到文件末尾 |
|
||||
| `ate` | 打开文件后立即定位到文件末尾 |
|
||||
| `trunc` | 截断文件 |
|
||||
| `binary` | 以二进制方式进行IO操作。 |
|
||||
|
||||
## string流
|
||||
|
||||
- 头文件`sstream`定义了三个类型来支持内存IO:
|
||||
- `istringstream`从`string`读取数据。
|
||||
- `ostringstream`向`string`写入数据。
|
||||
- `stringstream`可以读写给定`string`。
|
||||
|
||||
### stringstream特有的操作
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ----------------- | ---------------------------------- |
|
||||
| `sstream strm` | 定义一个未绑定的`stringstream`对象 |
|
||||
| `sstream strm(s)` | 用`s`初始化对象 |
|
||||
| `strm.str()` | 返回`strm`所保存的`string`的拷贝 |
|
||||
| `strm.str(s)` | 将`s`拷贝到`strm`中,返回`void` |
|
||||
|
||||
上表中`sstream`是头文件`sstream`中任意一个类型。`s`是一个`string`。
|
||||
345
docs/Cpp-Notes-main/Cpp-Primer/9.顺序容器.md
Normal file
345
docs/Cpp-Notes-main/Cpp-Primer/9.顺序容器.md
Normal file
@ -0,0 +1,345 @@
|
||||
---
|
||||
sort: 9
|
||||
---
|
||||
|
||||
# 顺序容器
|
||||
|
||||
## 顺序容器概述
|
||||
|
||||
- **顺序容器**(sequential container):为程序员提供了控制元素存储和访问顺序的能力。这种顺序不依赖于元素的值,而是与元素加入容器时的位置相对应。
|
||||
|
||||
### 顺序容器类型
|
||||
|
||||
| 容器类型 | 介绍 |
|
||||
| -------------- | ----------------------------------------------------------------------------- |
|
||||
| `vector` | 可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。 |
|
||||
| `deque` | 双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。 |
|
||||
| `list` | 双向链表。只支持双向顺序访问。在`list`中任何位置进行插入/删除操作速度都很快。 |
|
||||
| `forward_list` | 单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快。 |
|
||||
| `array` | 固定大小数组。支持快速随机访问。不能添加或者删除元素。 |
|
||||
| `string` | 与`vector`相似的容器,但专门用于保存字符。随机访问块。在尾部插入/删除速度快。 |
|
||||
|
||||
- 除了固定大小的`array`外,其他容器都提供高效、灵活的内存管理。
|
||||
- `forward_list`和`array`是新C++标准增加的类型。
|
||||
- 通常使用`vector`是最好的选择,除非你有很好的理由选择其他容器。
|
||||
- 新标准库的容器比旧版的快得多。
|
||||
|
||||
## 容器操作
|
||||
|
||||
### 类型
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ----------------- | -------------------------------------------------- |
|
||||
| `iterator` | 此容器类型的迭代器类型 |
|
||||
| `const_iterator` | 可以读取元素但不能修改元素的迭代器类型 |
|
||||
| `size_type` | 无符号整数类型,足够保存此种容器类型最大可能的大小 |
|
||||
| `difference_type` | 带符号整数类型,足够保存两个迭代器之间的距离 |
|
||||
| `value_type` | 元素类型 |
|
||||
| `reference` | 元素的左值类型;和`value_type &`含义相同 |
|
||||
| `const_reference` | 元素的`const`左值类型,即`const value_type &` |
|
||||
|
||||
### 构造函数
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ------------------------- | ---------------------------------------------------------------------- |
|
||||
| `C c;` | 默认构造函数,构造空容器 |
|
||||
| `C c1(c2);`或`C c1 = c2;` | 构造`c2`的拷贝`c1` |
|
||||
| `C c(b, e)` | 构造`c`,将迭代器`b`和`e`指定范围内的所有元素拷贝到`c` |
|
||||
| `C c(a, b, c...)` | 列表初始化`c` |
|
||||
| `C c(n)` | 只支持顺序容器,且不包括`array`,包含`n`个元素,这些元素进行了值初始化 |
|
||||
| `C c(n, t)` | 包含`n`个初始值为`t`的元素 |
|
||||
|
||||
- 只有顺序容器的构造函数才接受大小参数,关联容器并不支持。
|
||||
- `array`具有固定大小。
|
||||
- 和其他容器不同,默认构造的`array`是非空的。
|
||||
- 直接复制:将一个容器复制给另一个容器时,类型必须匹配:容器类型和元素类型都必须相同。
|
||||
- 使用迭代器复制:不要求容器类型相同,容器内的元素类型也可以不同。
|
||||
|
||||
### 赋值和`swap`
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ------------------- | ------------------------------------------------------------------------------ |
|
||||
| `c1 = c2;` | 将`c1`中的元素替换成`c2`中的元素 |
|
||||
| `c1 = {a, b, c...}` | 将`c1`中的元素替换成列表中的元素(不适用于`array`) |
|
||||
| `c1.swap(c2)` | 交换`c1`和`c2`的元素 |
|
||||
| `swap(c1, c2)` | 等价于`c1.swap(c2)` |
|
||||
| `c.assign(b, e)` | 将`c`中的元素替换成迭代器`b`和`e`表示范围中的元素,`b`和`e`不能指向`c`中的元素 |
|
||||
| `c.assign(il)` | 将`c`中的元素替换成初始化列表`il`中的元素 |
|
||||
| `c.assign(n, r)` | 将`c`中的元素替换为`n`个值是`t`的元素 |
|
||||
|
||||
- 使用非成员版本的`swap`是一个好习惯。
|
||||
- `assign`操作不适用于关联容器和`array`
|
||||
|
||||
### 大小
|
||||
|
||||
| 操作 | 解释 |
|
||||
| -------------- | ---------------------------------------------- |
|
||||
| `c.size()` | `c`中元素的数目(不支持`forward_list`) |
|
||||
| `c.max_size()` | `c`中可保存的最大元素数目 |
|
||||
| `c.empty()` | 若`c`中存储了元素,返回`false`,否则返回`true` |
|
||||
|
||||
### 添加元素
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ----------------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `c.push_back(t)` | 在`c`尾部创建一个值为`t`的元素,返回`void` |
|
||||
| `c.emplace_back(args)` | 同上 |
|
||||
| `c.push_front(t)` | 在`c`头部创建一个值为`t`的元素,返回`void` |
|
||||
| `c.emplace_front(args)` | 同上 |
|
||||
| `c.insert(p, t)` | 在迭代器`p`指向的元素之前创建一个值是`t`的元素,返回指向新元素的迭代器 |
|
||||
| `c.emplace(p, args)` | 同上 |
|
||||
| `c.insert(p, n, t)` | 在迭代器`p`指向的元素之前插入`n`个值为`t`的元素,返回指向第一个新元素的迭代器;如果`n`是0,则返回`p` |
|
||||
| `c.insert(p, b, e)` | 将迭代器`b`和`e`范围内的元素,插入到`p`指向的元素之前;如果范围为空,则返回`p` |
|
||||
| `c.insert(p, il)` | `il`是一个花括号包围中的元素值列表,将其插入到`p`指向的元素之前;如果`il`是空,则返回`p` |
|
||||
|
||||
- 因为这些操作会改变大小,因此不适用于`array`。
|
||||
- `forward_list`有自己专有版本的`insert`和`emplace`。
|
||||
- `forward_list`不支持`push_back`和`emplace_back`。
|
||||
- 当我们用一个对象去初始化容器或者将对象插入到容器时,实际上放入的是对象的拷贝。
|
||||
- `emplace`开头的函数是新标准引入的,这些操作是构造而不是拷贝元素。
|
||||
- 传递给`emplace`的参数必须和元素类型的构造函数相匹配。
|
||||
|
||||
### 访问元素
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ----------- | ---------------------------------------------------------------------------------------- |
|
||||
| `c.back()` | 返回`c`中尾元素的引用。若`c`为空,函数行为未定义 |
|
||||
| `c.front()` | 返回`c`中头元素的引用。若`c`为空,函数行为未定义 |
|
||||
| `c[n]` | 返回`c`中下标是`n`的元素的引用,`n`时候一个无符号证书。若`n>=c.size()`,则函数行为未定义 |
|
||||
| `c.at(n)` | 返回下标为`n`的元素引用。如果下标越界,则抛出`out_of_range`异常 |
|
||||
|
||||
- 访问成员函数返回的是引用。
|
||||
- `at`和下标操作只适用于`string`、`vector`、`deque`、`array`。
|
||||
- `back`不适用于`forward_list`。
|
||||
- 如果希望下标是合法的,可以使用`at`函数。
|
||||
|
||||
### 删除元素
|
||||
|
||||
| 操作 | 解释 |
|
||||
| --------------- | ------------------------------------------------------------------------------------------------------------------- |
|
||||
| `c.pop_back()` | 删除`c`中尾元素,若`c`为空,则函数行为未定义。函数返回`void` |
|
||||
| `c.pop_front()` | 删除`c`中首元素,若`c`为空,则函数行为未定义。函数返回`void` |
|
||||
| `c.erase(p)` | 删除迭代器`p`指向的元素,返回一个指向被删除元素之后的元素的迭代器,若`p`本身是尾后迭代器,则函数行为未定义 |
|
||||
| `c.erase(b, e)` | 删除迭代器`b`和`e`范围内的元素,返回指向最后一个被删元素之后元素的迭代器,若`e`本身就是尾后迭代器,则返回尾后迭代器 |
|
||||
| `c.clear()` | 删除`c`中所有元素,返回`void` |
|
||||
|
||||
- 会改变容器大小,不适用于`array`。
|
||||
- `forward_list`有特殊版本的`erase`
|
||||
- `forward_list`不支持`pop_back`
|
||||
- `vector`和`string`不支持`pop_front`
|
||||
|
||||
### 特殊的forwad_list操作
|
||||
|
||||
- 链表在删除元素时需要修改前置节点的内容,双向链表会前驱的指针,但是单向链表没有保存,因此需要增加获取前置节点的方法。
|
||||
- `forward_list`定义了`before_begin`,即首前(off-the-begining)迭代器,允许我们再在首元素之前添加或删除元素。
|
||||
|
||||
| 操作 | 解释 |
|
||||
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `lst.before_begin()` | 返回指向链表首元素之前不存在的元素的迭代器,此迭代器不能解引用。 |
|
||||
| `lst.cbefore_begin()` | 同上,但是返回的是常量迭代器。 |
|
||||
| `lst.insert_after(p, t)` | 在迭代器`p`之后插入元素。`t`是一个对象 |
|
||||
| `lst.insert_after(p, n, t)` | 在迭代器`p`之后插入元素。`t`是一个对象,`n`是数量。若`n`是0则函数行为未定义 |
|
||||
| `lst.insert_after(p, b, e)` | 在迭代器`p`之后插入元素。由迭代器`b`和`e`指定范围。 |
|
||||
| `lst.insert_after(p, il)` | 在迭代器`p`之后插入元素。由`il`指定初始化列表。 |
|
||||
| `emplace_after(p, args)` | 使用`args`在`p`之后的位置,创建一个元素,返回一个指向这个新元素的迭代器。若`p`为尾后迭代器,则函数行为未定义。 |
|
||||
| `lst.erase_after(p)` | 删除`p`指向位置之后的元素,返回一个指向被删元素之后的元素的迭代器,若`p`指向`lst`的尾元素或者是一个尾后迭代器,则函数行为未定义。 |
|
||||
| `lst.erase_after(b, e)` | 类似上面,删除对象换成从`b`到`e`指定的范围。 |
|
||||
|
||||
### 改变容器大小
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ---------------- | ---------------------------------------------------------------------------------------------------- |
|
||||
| `c.resize(n)` | 调整`c`的大小为`n`个元素,若`n<c.size()`,则多出的元素被丢弃。若必须添加新元素,对新元素进行值初始化 |
|
||||
| `c.resize(n, t)` | 调整`c`的大小为`n`个元素,任何新添加的元素都初始化为值`t` |
|
||||
|
||||
### 获取迭代器
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ------------------------ | ------------------------------------------- |
|
||||
| `c.begin()`, `c.end()` | 返回指向`c`的首元素和尾元素之后位置的迭代器 |
|
||||
| `c.cbegin()`, `c.cend()` | 返回`const_iterator` |
|
||||
|
||||
- 以`c`开头的版本是C++11新标准引入的
|
||||
- 当不需要写访问时,应该使用`cbegin`和`cend`。
|
||||
|
||||
### 反向容器的额外成员
|
||||
|
||||
| 操作 | 解释 |
|
||||
| -------------------------- | ------------------------------------------- |
|
||||
| `reverse_iterator` | 按逆序寻址元素的迭代器 |
|
||||
| `const_reverse_iterator` | 不能修改元素的逆序迭代器 |
|
||||
| `c.rbegin()`, `c.rend()` | 返回指向`c`的尾元素和首元素之前位置的迭代器 |
|
||||
| `c.crbegin()`, `c.crend()` | 返回`const_reverse_iterator` |
|
||||
|
||||
- 不支持`forward_list`
|
||||
|
||||
### 迭代器
|
||||
|
||||
- 迭代器范围:`begin`到`end`,即第一个元素到最后一个元素的后面一个位置。
|
||||
- 左闭合区间:`[begin, end)`
|
||||
- 左闭合范围蕴含的编程设定:
|
||||
- 如果`begin`和`end`相等,则范围为空。
|
||||
- 如果二者不等,则范围至少包含一个元素,且`begin`指向该范围中的第一个元素。
|
||||
- 可以对`begin`递增若干次,使得`begin == end`。
|
||||
|
||||
### 容器操作可能使迭代器失效
|
||||
|
||||
- 在向容器添加元素后:
|
||||
- 如果容器是`vector`或`string`,且存储空间被重新分配,则指向容器的迭代器、指针、引用都会失效。
|
||||
- 对于`deque`,插入到除首尾位置之外的任何位置都会导致指向容器的迭代器、指针、引用失效。如果在首尾位置添加元素,迭代器会失效,但指向存在元素的引用和指针不会失效。
|
||||
- 对于`list`和`forward_list`,指向容器的迭代器、指针和引用依然有效。
|
||||
- 在从一个容器中删除元素后:
|
||||
- 对于`list`和`forward_list`,指向容器其他位置的迭代器、引用和指针仍然有效。
|
||||
- 对于`deque`,如果在首尾之外的任何位置删除元素,那么指向被删除元素外其他元素的迭代器、指针、引用都会失效;如果是删除`deque`的尾元素,则尾后迭代器会失效,但其他不受影响;如果删除的是`deque`的头元素,这些也不会受影响。
|
||||
- 对于`vector`和`string`,指向被删元素之前的迭代器、引用、指针仍然有效。
|
||||
- 注意:当我们删除元素时,尾后迭代器总是会失效。
|
||||
- 注意:使用失效的迭代器、指针、引用是严重的运行时错误!
|
||||
- 建议:将要求迭代器必须保持有效的程序片段最小化。
|
||||
- 建议:不要保存`end`返回的迭代器。
|
||||
|
||||
### 容器内元素的类型约束
|
||||
|
||||
- 元素类型必须支持赋值运算;
|
||||
- 元素类型的对象必须可以复制。
|
||||
- 除了输入输出标准库类型外,其他所有标准库类型都是有效的容器元素类型。
|
||||
|
||||
## vector对象是如何增长的
|
||||
|
||||
`vector`和`string`在内存中是连续保存的,如果原先分配的内存位置已经使用完,则需要重新分配新空间,将已有元素从就位置移动到新空间中,然后添加新元素。
|
||||
|
||||
### 管理容量的成员函数
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ------------------- | --------------------------------------------- |
|
||||
| `c.shrink_to_fit()` | 将`capacity()`减少到和`size()`相同大小 |
|
||||
| `c.capacity()` | 不重新分配内存空间的话,`c`可以保存多少个元素 |
|
||||
| `c.reverse(n)` | 分配至少能容纳`n`个元素的内存空间 |
|
||||
|
||||
- `shrink_to_fit`只适用于`vector`、`string`和`deque`
|
||||
- `capacity`和`reverse`只适用于`vector`和`string`。
|
||||
|
||||
## 额外的string操作
|
||||
|
||||
### 构造string的其他方法
|
||||
|
||||
| 操作 | 解释 |
|
||||
| -------------------------- | -------------------------------------------------------------------------------------------- |
|
||||
| `string s(cp, n)` | `s`是`cp`指向的数组中前`n`个字符的拷贝,此数组 |
|
||||
| `string s(s2, pos2)` | `s`是`string s2`从下标`pos2`开始的字符的拷贝。若`pos2 > s2.size()`,则构造函数的行为未定义。 |
|
||||
| `string s(s2, pos2, len2)` | `s`是`string s2`从下标`pos2`开始的`len2`个字符的拷贝。 |
|
||||
|
||||
- `n`,`len2`,`pos2`都是无符号值。
|
||||
|
||||
### substr操作
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `s.substr(pos, n)` | 返回一个`string`,包含`s`中从`pos`开始的`n`个字符的拷贝。`pos`的默认值是0,`n`的默认值是`s.size() - pos`,即拷贝从`pos`开始的所有字符。 |
|
||||
|
||||
### 改变string的其他方法
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `s.insert(pos, args)` | 在`pos`之前插入`args`指定的字符。`pos`可以使是下标或者迭代器。接受下标的版本返回指向`s`的引用;接受迭代器的版本返回指向第一个插入字符的迭代器。 |
|
||||
| `s.erase(pos, len)` | 删除从`pos`开始的`len`个字符,如果`len`被省略,则删除后面所有字符,返回指向`s`的引用。 |
|
||||
| `s.assign(args)` | 将`s`中的字符替换成`args`指定的字符。返回一个指向`s`的引用。 |
|
||||
| `s.append(args)` | 将`args`指定的字符追加到`s`,返回一个指向`s`的引用。 |
|
||||
| `s.replace(range, args)` | 删除`s`中范围`range`中的字符,替换成`args`指定的字符。返回一个指向`s`的引用。 |
|
||||
|
||||
### string搜索操作
|
||||
|
||||
- `string`类提供了6个不同的搜索函数,每个函数都有4个重载版本。
|
||||
- 每个搜索操作都返回一个`string::size_type`值,表示匹配发生位置的下标。如果搜索失败则返回一个名为`string::npos`的`static`成员(类型是`string::size_type`,初始化值是-1,也就是`string`最大的可能大小)。
|
||||
|
||||
| 搜索操作 | 解释 |
|
||||
| --------------------------- | ------------------------------------------------- |
|
||||
| `s.find(args)` | 查找`s`中`args`第一次出现的位置 |
|
||||
| `s.rfind(args)` | 查找`s`中`args`最后一次出现的位置 |
|
||||
| `s.find_first_of(args)` | 在`s`中查找`args`中任何一个字符第一次出现的位置 |
|
||||
| `s.find_last_of(args)` | 在`s`中查找`args`中任何一个字符最后一次出现的位置 |
|
||||
| `s.find_first_not_of(args)` | 在`s`中查找第一个不在`args`中的字符 |
|
||||
| `s.find_first_not_of(args)` | 在`s`中查找最后一个不在`args`中的字符 |
|
||||
|
||||
args必须是一下的形式之一:
|
||||
|
||||
| `args`形式 | 解释 |
|
||||
| ------------ | ----------------------------------------------------------------------------- |
|
||||
| `c, pos` | 从`s`中位置`pos`开始查找字符`c`。`pos`默认是0 |
|
||||
| `s2, pos` | 从`s`中位置`pos`开始查找字符串`s`。`pos`默认是0 |
|
||||
| `cp, pos` | 从`s`中位置`pos`开始查找指针`cp`指向的以空字符结尾的C风格字符串。`pos`默认是0 |
|
||||
| `cp, pos, n` | 从`s`中位置`pos`开始查找指针`cp`指向的前`n`个字符。`pos`和`n`无默认值。 |
|
||||
|
||||
### s.compare的几种参数形式
|
||||
|
||||
逻辑类似于C标准库的`strcmp`函数,根据`s`是等于、大于还是小于参数指定的字符串,`s.compare`返回0、正数或负数。
|
||||
|
||||
| 参数形式 | 解释 |
|
||||
| ------------------------ | ----------------------------------------------------------------- |
|
||||
| `s2` | 比较`s`和`s2` |
|
||||
| `pos1, n1, s2` | 比较`s`从`pos1`开始的`n1`个字符和`s2` |
|
||||
| `pos1, n1, s2, pos2, n2` | 比较`s`从`pos1`开始的`n1`个字符和`s2` |
|
||||
| `cp` | 比较`s`和`cp`指向的以空字符结尾的字符数组 |
|
||||
| `pos1, n1, cp` | 比较`s`从`pos1`开始的`n1`个字符和`cp`指向的以空字符结尾的字符数组 |
|
||||
| `pos1, n1, cp, n2` | 比较`s`从`pos1`开始的`n1`个字符和`cp`指向的地址开始`n2`个字符 |
|
||||
|
||||
### string和数值转换
|
||||
|
||||
| 转换 | 解释 |
|
||||
| ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `to_string(val)` | 一组重载函数,返回数值`val`的`string`表示。`val`可以使任何算术类型。对每个浮点类型和`int`或更大的整型,都有相应版本的`to_string()`。和往常一样,小整型会被提升。 |
|
||||
| `stoi(s, p, b)` | 返回`s`起始子串(表示整数内容)的数值,`p`是`s`中第一个非数值字符的下标,默认是0,`b`是转换所用的基数。返回`int` |
|
||||
| `stol(s, p, b)` | 返回`long` |
|
||||
| `stoul(s, p, b)` | 返回`unsigned long` |
|
||||
| `stoll(s, p, b)` | 返回`long long` |
|
||||
| `stoull(s, p, b)` | 返回`unsigned long long` |
|
||||
| `stof(s, p)` | 返回`s`起始子串(表示浮点数内容)的数值,`p`是`s`中第一个非数值字符的下标,默认是0。返回`float` |
|
||||
| `stod(s, p)` | 返回`double` |
|
||||
| `stold(s, p)` | 返回`long double` |
|
||||
|
||||
## 容器适配器(adapter)
|
||||
|
||||
- 适配器是使一事物的行为类似于另一事物的行为的一种机制,例如`stack`可以使任何一种顺序容器以栈的方式工作。
|
||||
- 初始化 `deque<int> deq; stack<int> stk(deq);` 从`deq`拷贝元素到`stk`。
|
||||
- 创建适配器时,指定一个顺序容器,可以覆盖默认的基础容器: `stack<string, vector<string> > str_stk;`。
|
||||
|
||||
### 适配器的通用操作和类型
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ---------------- | ------------------------------------------------------------------------------------------------- |
|
||||
| `size_type` | 一种类型,须以保存当前类型的最大对象的大小 |
|
||||
| `value_type` | 元素类型 |
|
||||
| `container_type` | 实现适配器的底层容器类型 |
|
||||
| `A a;` | 创建一个名为`a`的空适配器 |
|
||||
| `A a(c)` | 创建一个名为`a`的适配器,带有容器`c`的一个拷贝 |
|
||||
| 关系运算符 | 每个适配器都支持所有关系运算符:`==`、`!=`、`<`、 `<=`、`>`、`>=`这些运算符返回底层容器的比较结果 |
|
||||
| `a.empty()` | 若`a`包含任何元素,返回`false`;否则返回`true` |
|
||||
| `a.size()` | 返回`a`中的元素数目 |
|
||||
| `swap(a, b)` | 交换`a`和`b`的内容,`a`和`b`必须有相同类型,包括底层容器类型也必须相同 |
|
||||
| `a.swap(b)` | 同上 |
|
||||
|
||||
### stack
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ----------------- | -------------------------------------------------------- |
|
||||
| `s.pop()` | 删除栈顶元素,不返回。 |
|
||||
| `s.push(item)` | 创建一个新元素,压入栈顶,该元素通过拷贝或移动`item`而来 |
|
||||
| `s.emplace(args)` | 同上,但元素由`args`来构造。 |
|
||||
| `s.top()` | 返回栈顶元素,不删除。 |
|
||||
|
||||
- 定义在`stack`头文件中。
|
||||
- `stack`默认基于`deque`实现,也可以在`list`或`vector`之上实现。
|
||||
|
||||
### queue和priority_queue
|
||||
|
||||
| 操作 | 解释 |
|
||||
| ----------------- | ----------------------------------------- |
|
||||
| `q.pop()` | 删除队首元素,但不返回。 |
|
||||
| `q.front()` | 返回队首元素的值,不删除。 |
|
||||
| `q.back()` | 返回队尾元素的值,不删除。只适用于`queue` |
|
||||
| `q.top()` | 返回具有最高优先级的元素值,不删除。 |
|
||||
| `q.push(item)` | 在队尾压入一个新元素。 |
|
||||
| `q.emplace(args)` | |
|
||||
|
||||
- 定义在`queue`头文件中。
|
||||
- `queue`默认基于`deque`实现,`priority_queue`默认基于`vector`实现。
|
||||
- `queue`可以在`list`或`vector`之上实现,`priority_queue`也可以用`deque`实现。
|
||||
57
docs/Cpp-Notes-main/Cpp-Primer/README.md
Normal file
57
docs/Cpp-Notes-main/Cpp-Primer/README.md
Normal file
@ -0,0 +1,57 @@
|
||||
---
|
||||
sort: 1
|
||||
---
|
||||
|
||||
# C++ Primer 第5版学习笔记
|
||||
|
||||

|
||||
|
||||
## 目录
|
||||
|
||||
[第1章 开始](./1.开始.md)
|
||||
|
||||
### 第I部分 C++基础
|
||||
|
||||
|
||||
[第2章 变量和基本类型](./2.变量和基本类型.md)
|
||||
|
||||
[第3章 字符串、向量和数组](./3.字符串、向量和数组.md)
|
||||
|
||||
[第4章 表达式](./4.表达式.md)
|
||||
|
||||
[第5章 语句](./5.语句.md)
|
||||
|
||||
[第6章 函数](./6.函数.md)
|
||||
|
||||
[第7章 类](./7.类.md)
|
||||
|
||||
|
||||
### 第II部分 C++标准库
|
||||
|
||||
[第8章 IO库](./8.IO库.md)
|
||||
|
||||
[第9章 顺序容器](./9.顺序容器.md)
|
||||
|
||||
[第10章 泛型算法](./10.泛型算法.md)
|
||||
|
||||
[第11章 关联容器](./11.关联容器.md)
|
||||
|
||||
[第12章 动态内存](./12.动态内存.md)
|
||||
|
||||
### 第III部分 类设计者的工具
|
||||
|
||||
[第13章 拷贝控制](./13.拷贝控制.md)
|
||||
|
||||
[第14章 操作重载与类型转换](./14.操作重载与类型转换.md)
|
||||
|
||||
[第15章 面向对象程序设计](./15.面向对象程序设计.md)
|
||||
|
||||
[第16章 模板与泛型编程](./16.模板与泛型编程.md)
|
||||
|
||||
### 第IV部分 高级主题
|
||||
|
||||
[第17章 标准库特殊设施](./17.标准库特殊设施.md)
|
||||
|
||||
[第18章 用于大型程序的工具](./18.用于大型程序的工具.md)
|
||||
|
||||
[第19章 特殊工具与技术](./19.特殊工具与技术.md)
|
||||
59
docs/Cpp-Notes-main/Design-Patterns/1.设计模式简介.md
Normal file
59
docs/Cpp-Notes-main/Design-Patterns/1.设计模式简介.md
Normal file
@ -0,0 +1,59 @@
|
||||
---
|
||||
sort: 1
|
||||
---
|
||||
|
||||
# C++设计模式
|
||||
|
||||
## 目标
|
||||
- 理解松耦合设计思想
|
||||
- 掌握面向对象设计原则
|
||||
- 掌握重构技法改善设计
|
||||
- 掌握GOF 核心设计模式
|
||||
|
||||
## 从面向对象谈起
|
||||
|
||||
### 底层思维:向下,如何把握机器底层从微观理解对象构造
|
||||
|
||||
- 语言构造
|
||||
- 编译转换
|
||||
- 内存模型
|
||||
- 运行时机制
|
||||
|
||||
### 抽象思维:向上,如何将我们的周围世界抽象为程序代码
|
||||
|
||||
- 面向对象
|
||||
- 组件封装
|
||||
- 设计模式
|
||||
- 架构模式
|
||||
|
||||
## 深入理解面向对象
|
||||
|
||||
### 向下:
|
||||
深入理解三大面向对象机制
|
||||
|
||||
- 封装,隐藏内部实现
|
||||
- 继承,复用现有代码
|
||||
- 多态,改写对象行为
|
||||
|
||||
### 向上:
|
||||
深刻把握面向对象机制所带来的抽象意义,理解如何使用
|
||||
这些机制来表达现实世界,掌握什么是“好的面向对象设计”
|
||||
|
||||
## 如何解决复杂性?
|
||||
|
||||
### 分解
|
||||
|
||||
人们面对复杂性有一个常见的做法:即分而治之,将大问题分
|
||||
解为多个小问题,将复杂问题分解为多个简单问题。
|
||||
|
||||
### 抽象
|
||||
|
||||
更高层次来讲,人们处理复杂性有一个通用的技术,即抽象。
|
||||
由于不能掌握全部的复杂对象,我们选择忽视它的非本质细节,
|
||||
而去处理泛化和理想化了的对象模型。
|
||||
|
||||
## 软件设计的目标
|
||||
|
||||
什么是好的软件设计?软件设计的金科玉律:
|
||||
|
||||
**复用!**
|
||||
66
docs/Cpp-Notes-main/Design-Patterns/2.面向对象设计原则.md
Normal file
66
docs/Cpp-Notes-main/Design-Patterns/2.面向对象设计原则.md
Normal file
@ -0,0 +1,66 @@
|
||||
---
|
||||
sort: 2
|
||||
---
|
||||
|
||||
# 面向对象设计原则
|
||||
|
||||
## 面向对象设计,为什么?
|
||||
|
||||
变化是复用的天敌!
|
||||
|
||||
面向对象设计最大的优势在于:
|
||||
|
||||
**抵御变化!**
|
||||
|
||||
## 重新认识面向对象
|
||||
|
||||
- 理解隔离变化
|
||||
- 从宏观层面来看,面向对象的构建方式更能适应软件的变化,能将变化所带来的影响减为最小
|
||||
- 各司其职
|
||||
- 从微观层面来看,面向对象的方式更强调各个类的“责任”
|
||||
- 由于需求变化导致的新增类型不应该影响原来类型的实现——是所谓各负其责
|
||||
- 对象是什么?
|
||||
- 从语言实现层面来看,对象封装了代码和数据。
|
||||
- 从规格层面讲,对象是一系列可被使用的公共接口。
|
||||
- 从概念层面讲,对象是某种拥有责任的抽象。
|
||||
|
||||
## 面向对象设计原则
|
||||
|
||||
- 依赖倒置原则(DIP)
|
||||
- 高层模块(稳定)不应该依赖于低层模块(变化),二者都应该依赖于抽象(稳定) 。
|
||||
- 抽象(稳定)不应该依赖于实现细节(变化) ,实现细节应该依赖于抽象(稳定)。
|
||||
- 开放封闭原则(OCP)
|
||||
- 对扩展开放,对更改封闭。
|
||||
- 类模块应该是可扩展的,但是不可修改
|
||||
- 单一职责原则(SRP)
|
||||
- 一个类应该仅有一个引起它变化的原因。
|
||||
- 变化的方向隐含着类的责任。
|
||||
- Liskov 替换原则(LSP)
|
||||
- 子类必须能够替换它们的基类(IS-A)
|
||||
- 继承表达类型抽象。
|
||||
- 接口隔离原则(ISP)
|
||||
- 不应该强迫客户程序依赖它们不用的方法。
|
||||
- 接口应该小而完备。
|
||||
- 优先使用对象组合,而不是类继承
|
||||
- 类继承通常为“白箱复用”,对象组合通常为“黑箱复用”。
|
||||
- 继承在某种程度上破坏了封装性,子类父类耦合度高。
|
||||
- 而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。
|
||||
- 封装变化点
|
||||
- 使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。
|
||||
- 针对接口编程,而不是针对实现编程
|
||||
- 不将变量类型声明为某个特定的具体类,而是声明为某个接口。
|
||||
- 客户程序无需获知对象的具体类型,只需要知道对象所具有的接口。
|
||||
- 减少系统中各部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案。
|
||||
|
||||
## 面向接口设计
|
||||
|
||||
产业强盛的标志:**接口标准化!**
|
||||
|
||||
## 将设计原则提升为设计经验
|
||||
|
||||
- 设计习语 Design Idioms
|
||||
- Design Idioms 描述与特定编程语言相关的低层模式,技巧,惯用法。
|
||||
- 设计模式 Design Patterns
|
||||
- Design Patterns主要描述的是“类与相互通信的对象之间的组织关系,包括它们的角色、职责、协作方式等方面。
|
||||
- 架构模式 Architectural Patterns
|
||||
- Architectural Patterns描述系统中与基本结构组织关系密切的高层模式,包括子系统划分,职责,以及如何组织它们之间关系的规则。
|
||||
9
docs/Cpp-Notes-main/Design-Patterns/README.md
Normal file
9
docs/Cpp-Notes-main/Design-Patterns/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
---
|
||||
sort: 8
|
||||
---
|
||||
|
||||
# C++设计模式
|
||||
|
||||
{% include list.liquid %}
|
||||
|
||||
|
||||
109
docs/Cpp-Notes-main/Effective-Modern-C++/README.md
Normal file
109
docs/Cpp-Notes-main/Effective-Modern-C++/README.md
Normal file
@ -0,0 +1,109 @@
|
||||
---
|
||||
sort: 3
|
||||
---
|
||||
|
||||
# Effective Modern C++ 笔记
|
||||
|
||||

|
||||
|
||||
## 目录
|
||||
|
||||
### 第一章 型别推导
|
||||
|
||||
[条款1:理解模板型别推导](./item1.md)
|
||||
|
||||
[条款2:理解auto型别推导](./item2.md)
|
||||
|
||||
[条款3:理解decltype](./item3.md)
|
||||
|
||||
[条款4:掌握查看型别推导结果的方法](./item4.md)
|
||||
|
||||
|
||||
### 第二章 auto
|
||||
|
||||
[条款5:优先选用auto,而非显式型别声明](./item5.md)
|
||||
|
||||
[条款6:当auto推导的型别不符合要求时,使用带显式型别的初始化物习惯用法](./item6.md)
|
||||
|
||||
|
||||
### 第三章 转向现代C++
|
||||
|
||||
[条款7:在创建对象时注意区分()和{}](./item7.md)
|
||||
|
||||
[条款8:优先选用nullptr,而非0或NULL](./item8.md)
|
||||
|
||||
[条款9:优先选用别名声明,而非typedef](./item9.md)
|
||||
|
||||
[条款10:优先选用限定作用域的枚举型别,而非不限作用域的枚举型别](./item10.md)
|
||||
|
||||
[条款11:优先选用删除函数,而非private未定义函数](./item11.md)
|
||||
|
||||
[条款12:为意在改写的函数添加override声明](./item12.md)
|
||||
|
||||
[条款13:优先选用const_iterator,而非iterator](./item13.md)
|
||||
|
||||
[条款14:只要函数不会发射异常,就为其加上noexcept声明](./item14.md)
|
||||
|
||||
[条款15:只要有可能使用constexpr,就使用它](./item15.md)
|
||||
|
||||
[条款16:保证const成员函数的线程安全性](./item16.md)
|
||||
|
||||
[条款17:理解特种成员函数的生成机制](./item17.md)
|
||||
|
||||
### 第四章 智能指针
|
||||
|
||||
[条款18:使用std::unique_ptr管理具备专属所有权的资源](./item18.md)
|
||||
|
||||
[条款19:使用std::shared_ptr管理具备共享所有权的资源](./item19.md)
|
||||
|
||||
[条款20:对于类似std::shared_ptr但有可能空悬的指针使用std::weak_ptr](./item20.md)
|
||||
|
||||
[条款21:优先选用std::make_unique和std::make_shared,而非直接使用new](./item21.md)
|
||||
|
||||
[条款22:使用Pimpl习惯用法时,将特殊成员函数的定义放在实现文件中](./item22.md)
|
||||
### 第五章 右值引用、移动语义和完美转发
|
||||
|
||||
[条款23:理解std::move和std::forward](./item23.md)
|
||||
|
||||
[条款24:区分万能引用和右值引用](./item24.md)
|
||||
|
||||
[条款25:针对右值引用实施std::move,针对万能引用实施std::forward](./item25.md)
|
||||
|
||||
[条款26:避免依万能引用型别进行重载](./item26.md)
|
||||
|
||||
[条款27:熟悉依万能引用型别进行重载的替代方案](./item27.md)
|
||||
|
||||
[条款28:理解引用折叠](./item28.md)
|
||||
|
||||
[条款29:假定移动操作不存在、成本高、未使用](./item29.md)
|
||||
|
||||
[条款30:熟悉完美转发的失败情形](./item30.md)
|
||||
|
||||
### 第六章 lambda表达式
|
||||
|
||||
[条款31:避免默认捕获模式](./item31.md)
|
||||
|
||||
[条款32:使用初始化捕获将对象移入闭包](./item32.md)
|
||||
|
||||
[条款33:对auto&&型别的形参使用decltype,以std::forward之](./item33.md)
|
||||
|
||||
[条款34:优先选用lambda式,而非std::bind](./item34.md)
|
||||
|
||||
### 第七章 并发API
|
||||
|
||||
[35:优先选用基于任务而非基于线程的程序设计](./item35.md)
|
||||
|
||||
[36:如果异步是必要的,则指定std::lauch::async](./item36.md)
|
||||
|
||||
[37:使std::thread型别对象在所有路径皆不可联结](./item37.md)
|
||||
|
||||
[38:对变化多端的线程句柄析构函数行为保持关注](./item38.md)
|
||||
|
||||
[39:考虑针对一次性事件通信使用以void为模板性别实参的期值](./item39.md)
|
||||
|
||||
[40:对并发使用std::atomic,对特种内存使用volatile](./item40.md)
|
||||
### 第8章 微调
|
||||
|
||||
[41:针对可复制的形参,在移动成本低并且一定会被复制的前提下,考虑将其按值传递](./item41.md)
|
||||
|
||||
[42:考虑置入而非插入](./item42.md)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user