[
  {
    "path": ".gitignore",
    "content": "site/\nnohup.out\n.vscode/"
  },
  {
    "path": "auto_refresh_mkdocs_site.sh",
    "content": "#! /bin/bash\n\nwhile true; do\n\tsleep 3\n\n\tif git pull origin master | grep '^Updating' ; then\n\n\t\techo \"[$(date)] mkdocs gh-deploy\"\n\t\tmkdocs gh-deploy\n\n\tfi\ndone\n"
  },
  {
    "path": "codes/CProgramming/README.md",
    "content": "# C语言程序设计：现代方法\n\n学习环境：CentOS 7, VS Code & MinGW & Git-Bash\n\n第25章 测试多字符与宽字符采取的环境是 CentOS7 ，在 Windows 下测试会出现问题\n\n---\n\n## 练习题\n\n### 第2章 C语言基本概念\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch02_C语言基本概念/ex_01.c \"./ch02_C语言基本概念/ex_01.c\")|[练习2](./ch02_C语言基本概念/ex_02.c \"./ch02_C语言基本概念/ex_02.c\")|[练习3](./ch02_C语言基本概念/ex_03.c \"./ch02_C语言基本概念/ex_03.c\")|[练习4](./ch02_C语言基本概念/ex_04.c \"./ch02_C语言基本概念/ex_04.c\")|[练习5](./ch02_C语言基本概念/ex_05.c \"./ch02_C语言基本概念/ex_05.c\")|[练习6](./ch02_C语言基本概念/ex_06.c \"./ch02_C语言基本概念/ex_06.c\")|\n|[练习7](./ch02_C语言基本概念/ex_07.c \"./ch02_C语言基本概念/ex_07.c\")|[练习8](./ch02_C语言基本概念/ex_08.c \"./ch02_C语言基本概念/ex_08.c\")|[练习9](./ch02_C语言基本概念/ex_09.md \"./ch02_C语言基本概念/ex_09.md\")|[练习10](./ch02_C语言基本概念/ex_10.c \"./ch02_C语言基本概念/ex_10.c\")|[练习11](./ch02_C语言基本概念/ex_11.md \"./ch02_C语言基本概念/ex_11.md\")|[练习12](./ch02_C语言基本概念/ex_12.md \"./ch02_C语言基本概念/ex_12.md\")|\n|[练习13](./ch02_C语言基本概念/ex_13.md \"./ch02_C语言基本概念/ex_13.md\")|\n\n### 第3章 格式化的输入输出\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch03_格式化的输入输出/ex_01.c \"./ch03_格式化的输入输出/ex_01.c\")|[练习2](./ch03_格式化的输入输出/ex_02.c \"./ch03_格式化的输入输出/ex_02.c\")|[练习3](./ch03_格式化的输入输出/ex_03.c \"./ch03_格式化的输入输出/ex_03.c\")|[练习4](./ch03_格式化的输入输出/ex_04.c \"./ch03_格式化的输入输出/ex_04.c\")|[练习5](./ch03_格式化的输入输出/ex_05.c \"./ch03_格式化的输入输出/ex_05.c\")|[练习6](./ch03_格式化的输入输出/ex_06.c \"./ch03_格式化的输入输出/ex_06.c\")|\n|[练习7](./ch03_格式化的输入输出/ex_07.c \"./ch03_格式化的输入输出/ex_07.c\")|[练习8](./ch03_格式化的输入输出/ex_08.c \"./ch03_格式化的输入输出/ex_08.c\")|\n\n### 第4章 表达式\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch04_表达式/ex_01.c \"./ch04_表达式/ex_01.c\")|[练习2](./ch04_表达式/ex_02.c \"./ch04_表达式/ex_02.c\")|[练习3](./ch04_表达式/ex_03.c \"./ch04_表达式/ex_03.c\")|[练习4](./ch04_表达式/ex_04.c \"./ch04_表达式/ex_04.c\")|[练习5](./ch04_表达式/ex_05.c \"./ch04_表达式/ex_05.c\")|[练习6](./ch04_表达式/ex_06.c \"./ch04_表达式/ex_06.c\")|\n|[练习7](./ch04_表达式/ex_07.c \"./ch04_表达式/ex_07.c\")|[练习8](./ch04_表达式/ex_08.c \"./ch04_表达式/ex_08.c\")|[练习9](./ch04_表达式/ex_09.md \"./ch04_表达式/ex_09.md\")|[练习10](./ch04_表达式/ex_10.c \"./ch04_表达式/ex_10.c\")|[练习11](./ch04_表达式/ex_11.md \"./ch04_表达式/ex_11.md\")|\n\n### 第5章 选择语句\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch05_选择语句/ex_01.c \"./ch05_选择语句/ex_01.c\")|[练习2](./ch05_选择语句/ex_02.c \"./ch05_选择语句/ex_02.c\")|[练习3](./ch05_选择语句/ex_03.c \"./ch05_选择语句/ex_03.c\")|[练习4](./ch05_选择语句/ex_04.c \"./ch05_选择语句/ex_04.c\")|[练习5](./ch05_选择语句/ex_05.c \"./ch05_选择语句/ex_05.c\")|[练习6](./ch05_选择语句/ex_06.c \"./ch05_选择语句/ex_06.c\")|\n|[练习7](./ch05_选择语句/ex_07.c \"./ch05_选择语句/ex_07.c\")|[练习8](./ch05_选择语句/ex_08.c \"./ch05_选择语句/ex_08.c\")|[练习9](./ch05_选择语句/ex_09.c \"./ch05_选择语句/ex_09.c\")|[练习10](./ch05_选择语句/ex_10.c \"./ch05_选择语句/ex_10.c\")|[练习11](./ch05_选择语句/ex_11.c \"./ch05_选择语句/ex_11.c\")|[练习12](./ch05_选择语句/ex_12.c \"./ch05_选择语句/ex_12.c\")|\n|[练习13](./ch05_选择语句/ex_13.c \"./ch05_选择语句/ex_13.c\")|[练习14](./ch05_选择语句/ex_14.c \"./ch05_选择语句/ex_14.c\")|[练习15](./ch05_选择语句/ex_15.c \"./ch05_选择语句/ex_15.c\")|[练习16](./ch05_选择语句/ex_16.c \"./ch05_选择语句/ex_16.c\")|\n\n### 第6章 循环\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch06_循环/ex_01.c \"./ch06_循环/ex_01.c\")|[练习2](./ch06_循环/ex_02.c \"./ch06_循环/ex_02.c\")|[练习3](./ch06_循环/ex_03.c \"./ch06_循环/ex_03.c\")|[练习4](./ch06_循环/ex_04.c \"./ch06_循环/ex_04.c\")|[练习5](./ch06_循环/ex_05.c \"./ch06_循环/ex_05.c\")|[练习6](./ch06_循环/ex_06.c \"./ch06_循环/ex_06.c\")|\n|[练习7](./ch06_循环/ex_07.c \"./ch06_循环/ex_07.c\")|[练习8](./ch06_循环/ex_08.c \"./ch06_循环/ex_08.c\")|[练习9](./ch06_循环/ex_09.c \"./ch06_循环/ex_09.c\")|[练习10](./ch06_循环/ex_10.md \"./ch06_循环/ex_10.md\")|[练习11](./ch06_循环/ex_11.md \"./ch06_循环/ex_11.md\")|[练习12](./ch06_循环/ex_12.c \"./ch06_循环/ex_12.c\")|\n|[练习13](./ch06_循环/ex_13.c \"./ch06_循环/ex_13.c\")|[练习14](./ch06_循环/ex_14.c \"./ch06_循环/ex_14.c\")|[练习15](./ch06_循环/ex_15.c \"./ch06_循环/ex_15.c\")|[练习16](./ch06_循环/ex_16.md \"./ch06_循环/ex_16.md\")|\n\n### 第7章 基本类型\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch07_基本类型/ex_01.c \"./ch07_基本类型/ex_01.c\")|[练习2](./ch07_基本类型/ex_02.c \"./ch07_基本类型/ex_02.c\")|[练习3](./ch07_基本类型/ex_03.c \"./ch07_基本类型/ex_03.c\")|[练习4](./ch07_基本类型/ex_04.c \"./ch07_基本类型/ex_04.c\")|[练习5](./ch07_基本类型/ex_05.c \"./ch07_基本类型/ex_05.c\")|[练习6](./ch07_基本类型/ex_06.c \"./ch07_基本类型/ex_06.c\")|\n|[练习7](./ch07_基本类型/ex_07.md \"./ch07_基本类型/ex_07.md\")|[练习8](./ch07_基本类型/ex_08.c \"./ch07_基本类型/ex_08.c\")|[练习9](./ch07_基本类型/ex_09.c \"./ch07_基本类型/ex_09.c\")|[练习10](./ch07_基本类型/ex_10.c \"./ch07_基本类型/ex_10.c\")|[练习11](./ch07_基本类型/ex_11.c \"./ch07_基本类型/ex_11.c\")|[练习12](./ch07_基本类型/ex_12.c \"./ch07_基本类型/ex_12.c\")|\n|[练习13](./ch07_基本类型/ex_13.md \"./ch07_基本类型/ex_13.md\")|[练习14](./ch07_基本类型/ex_15.md \"./ch07_基本类型/ex_15.md\")|[练习15](./ch07_基本类型/ex_16.md \"./ch07_基本类型/ex_16.md\")|[练习16](./ch07_基本类型/ex_17.c \"./ch07_基本类型/ex_17.c\")|[练习17](./ch07_基本类型/ex_18.md \"./ch07_基本类型/ex_18.md\")|[练习18](./ch07_基本类型/ex_19.c \"./ch07_基本类型/ex_19.c\")|\n\n### 第8章 数组\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch08_数组/ex_01.c \"./ch08_数组/ex_01.c\")|[练习2](./ch08_数组/ex_02.c \"./ch08_数组/ex_02.c\")|[练习3](./ch08_数组/ex_03.c \"./ch08_数组/ex_03.c\")|[练习4](./ch08_数组/ex_04.md \"./ch08_数组/ex_04.md\")|[练习5](./ch08_数组/ex_05.c \"./ch08_数组/ex_05.c\")|[练习6](./ch08_数组/ex_06.c \"./ch08_数组/ex_06.c\")|\n|[练习7](./ch08_数组/ex_07.c \"./ch08_数组/ex_07.c\")|[练习8](./ch08_数组/ex_08.md \"./ch08_数组/ex_08.md\")|[练习9](./ch08_数组/ex_09.c \"./ch08_数组/ex_09.c\")|[练习10](./ch08_数组/ex_10.c \"./ch08_数组/ex_10.c\")|[练习11](./ch08_数组/ex_11.c \"./ch08_数组/ex_11.c\")|[练习12](./ch08_数组/ex_12.c \"./ch08_数组/ex_12.c\")|\n|[练习13](./ch08_数组/ex_13.c \"./ch08_数组/ex_13.c\")|\n\n### 第9章 函数\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch09_函数/ex_01.c \"./ch09_函数/ex_01.c\")|[练习2](./ch09_函数/ex_02.c \"./ch09_函数/ex_02.c\")|[练习3](./ch09_函数/ex_03.c \"./ch09_函数/ex_03.c\")|[练习4](./ch09_函数/ex_04.c \"./ch09_函数/ex_04.c\")|[练习5](./ch09_函数/ex_05.c \"./ch09_函数/ex_05.c\")|[练习6](./ch09_函数/ex_06.c \"./ch09_函数/ex_06.c\")|\n|[练习7](./ch09_函数/ex_07.c \"./ch09_函数/ex_07.c\")|[练习8](./ch09_函数/ex_08.md \"./ch09_函数/ex_08.md\")|[练习9](./ch09_函数/ex_09.c \"./ch09_函数/ex_09.c\")|[练习10](./ch09_函数/ex_10.c \"./ch09_函数/ex_10.c\")|[练习11](./ch09_函数/ex_11.md \"./ch09_函数/ex_11.md\")|[练习12](./ch09_函数/ex_12.c \"./ch09_函数/ex_12.c\")|\n|[练习13](./ch09_函数/ex_13.c \"./ch09_函数/ex_13.c\")|[练习14](./ch09_函数/ex_14.c \"./ch09_函数/ex_14.c\")|[练习15](./ch09_函数/ex_15.c \"./ch09_函数/ex_15.c\")|[练习16](./ch09_函数/ex_16.c \"./ch09_函数/ex_16.c\")|[练习17](./ch09_函数/ex_17.c \"./ch09_函数/ex_17.c\")|\n\n### 第10章 程序结构\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch10_程序结构/ex_01.c \"./ch10_程序结构/ex_01.c\")|[练习2](./ch10_程序结构/ex_02.md \"./ch10_程序结构/ex_02.md\")|[练习3](./ch10_程序结构/ex_03.c \"./ch10_程序结构/ex_03.c\")|[练习4](./ch10_程序结构/ex_04.c \"./ch10_程序结构/ex_04.c\")|[练习5](./ch10_程序结构/ex_05.c \"./ch10_程序结构/ex_05.c\")|[练习6](./ch10_程序结构/ex_06.c \"./ch10_程序结构/ex_06.c\")|\n|[练习7](./ch10_程序结构/ex_07.c \"./ch10_程序结构/ex_07.c\")|\n\n### 第11章 指针\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch11_指针/ex_01.md \"./ch11_指针/ex_01.md\")|[练习2](./ch11_指针/ex_02.md \"./ch11_指针/ex_02.md\")|[练习3](./ch11_指针/ex_03.c \"./ch11_指针/ex_03.c\")|[练习4](./ch11_指针/ex_04.c \"./ch11_指针/ex_04.c\")|[练习5](./ch11_指针/ex_05.c \"./ch11_指针/ex_05.c\")|[练习6](./ch11_指针/ex_06.c \"./ch11_指针/ex_06.c\")|\n\n### 第12章 指针和数组\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch12_指针和数组/ex_01.c \"./ch12_指针和数组/ex_01.c\")|[练习2](./ch12_指针和数组/ex_02.md \"./ch12_指针和数组/ex_02.md\")|[练习3](./ch12_指针和数组/ex_03.c \"./ch12_指针和数组/ex_03.c\")|[练习4](./ch12_指针和数组/ex_04.c \"./ch12_指针和数组/ex_04.c\")|[练习5](./ch12_指针和数组/ex_05.c \"./ch12_指针和数组/ex_05.c\")|[练习6](./ch12_指针和数组/ex_06.c \"./ch12_指针和数组/ex_06.c\")|\n|[练习7](./ch12_指针和数组/ex_07.md \"./ch12_指针和数组/ex_07.md\")|[练习8](./ch12_指针和数组/ex_08.md \"./ch12_指针和数组/ex_08.md\")|[练习9](./ch12_指针和数组/ex_09.md \"./ch12_指针和数组/ex_09.md\")|[练习10](./ch12_指针和数组/ex_10.c \"./ch12_指针和数组/ex_10.c\")|[练习11](./ch12_指针和数组/ex_11.c \"./ch12_指针和数组/ex_11.c\")|[练习12](./ch12_指针和数组/ex_12.c \"./ch12_指针和数组/ex_12.c\")|\n|[练习13](./ch12_指针和数组/ex_13.c \"./ch12_指针和数组/ex_13.c\")|[练习14](./ch12_指针和数组/ex_14.c \"./ch12_指针和数组/ex_14.c\")|[练习15](./ch12_指针和数组/ex_15.c \"./ch12_指针和数组/ex_15.c\")|\n\n### 第13章 字符串\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch13_字符串/ex_01.md \"./ch13_字符串/ex_01.md\")|[练习2](./ch13_字符串/ex_02.md \"./ch13_字符串/ex_02.md\")|[练习3](./ch13_字符串/ex_03.c \"./ch13_字符串/ex_03.c\")|[练习4](./ch13_字符串/ex_04.c \"./ch13_字符串/ex_04.c\")|[练习5](./ch13_字符串/ex_05.c \"./ch13_字符串/ex_05.c\")|[练习6](./ch13_字符串/ex_06.c \"./ch13_字符串/ex_06.c\")|\n|[练习7](./ch13_字符串/ex_07.c \"./ch13_字符串/ex_07.c\")|[练习8](./ch13_字符串/ex_08.c \"./ch13_字符串/ex_08.c\")|[练习9](./ch13_字符串/ex_09.md \"./ch13_字符串/ex_09.md\")|[练习10](./ch13_字符串/ex_10.c \"./ch13_字符串/ex_10.c\")|[练习11](./ch13_字符串/ex_11.c \"./ch13_字符串/ex_11.c\")|[练习12](./ch13_字符串/ex_12.md \"./ch13_字符串/ex_12.md\")|\n|[练习13](./ch13_字符串/ex_13.c \"./ch13_字符串/ex_13.c\")|[练习14](./ch13_字符串/ex_14.c \"./ch13_字符串/ex_14.c\")|[练习15](./ch13_字符串/ex_15.c \"./ch13_字符串/ex_15.c\")|[练习16](./ch13_字符串/ex_16.c \"./ch13_字符串/ex_16.c\")|[练习17](./ch13_字符串/ex_17.c \"./ch13_字符串/ex_17.c\")|[练习18](./ch13_字符串/ex_18.md \"./ch13_字符串/ex_18.md\")|\n|[练习19](./ch13_字符串/ex_19.c \"./ch13_字符串/ex_19.c\")|[练习20](./ch13_字符串/ex_20.c \"./ch13_字符串/ex_20.c\")|\n\n### 第14章 预处理器\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch14_预处理器/ex_01.c \"./ch14_预处理器/ex_01.c\")|[练习2](./ch14_预处理器/ex_02.c \"./ch14_预处理器/ex_02.c\")|[练习3](./ch14_预处理器/ex_03.c \"./ch14_预处理器/ex_03.c\")|[练习4](./ch14_预处理器/ex_04.c \"./ch14_预处理器/ex_04.c\")|[练习5](./ch14_预处理器/ex_05.c \"./ch14_预处理器/ex_05.c\")|[练习6](./ch14_预处理器/ex_06.c \"./ch14_预处理器/ex_06.c\")|\n|[练习7](./ch14_预处理器/ex_07.c \"./ch14_预处理器/ex_07.c\")|[练习8](./ch14_预处理器/ex_08.c \"./ch14_预处理器/ex_08.c\")|[练习9](./ch14_预处理器/ex_09.c \"./ch14_预处理器/ex_09.c\")|[练习10](./ch14_预处理器/ex_10.c \"./ch14_预处理器/ex_10.c\")|[练习11](./ch14_预处理器/ex_11.c \"./ch14_预处理器/ex_11.c\")|[练习12](./ch14_预处理器/ex_12.c \"./ch14_预处理器/ex_12.c\")|\n\n### 第15章 编写大规模程序\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch15_编写大规模程序/ex_01.md \"./ch15_编写大规模程序/ex_01.md\")|[练习2](./ch15_编写大规模程序/ex_02.md \"./ch15_编写大规模程序/ex_02.md\")|[练习3](./ch15_编写大规模程序/ex_03.md \"./ch15_编写大规模程序/ex_03.md\")|[练习4](./ch15_编写大规模程序/ex_04.md \"./ch15_编写大规模程序/ex_04.md\")|[练习5](./ch15_编写大规模程序/ex_05.md \"./ch15_编写大规模程序/ex_05.md\")|[练习6](./ch15_编写大规模程序/ex_06.md \"./ch15_编写大规模程序/ex_06.md\")|\n|[练习7](./ch15_编写大规模程序/ex_07.md \"./ch15_编写大规模程序/ex_07.md\")|[练习8](./ch15_编写大规模程序/ex_08.md \"./ch15_编写大规模程序/ex_08.md\")|[练习9](./ch15_编写大规模程序/ex_09.md \"./ch15_编写大规模程序/ex_09.md\")|\n\n### 第16章 结构_联合_枚举\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch16_结构_联合_枚举/ex_01.c \"./ch16_结构_联合_枚举/ex_01.c\")|[练习2](./ch16_结构_联合_枚举/ex_02.c \"./ch16_结构_联合_枚举/ex_02.c\")|[练习3](./ch16_结构_联合_枚举/ex_04.md \"./ch16_结构_联合_枚举/ex_04.md\")|[练习4](./ch16_结构_联合_枚举/ex_05.c \"./ch16_结构_联合_枚举/ex_05.c\")|[练习5](./ch16_结构_联合_枚举/ex_06.c \"./ch16_结构_联合_枚举/ex_06.c\")|[练习6](./ch16_结构_联合_枚举/ex_07.md \"./ch16_结构_联合_枚举/ex_07.md\")|\n|[练习7](./ch16_结构_联合_枚举/ex_08.md \"./ch16_结构_联合_枚举/ex_08.md\")|[练习8](./ch16_结构_联合_枚举/ex_09.md \"./ch16_结构_联合_枚举/ex_09.md\")|[练习9](./ch16_结构_联合_枚举/ex_10.c \"./ch16_结构_联合_枚举/ex_10.c\")|[练习10](./ch16_结构_联合_枚举/ex_11.c \"./ch16_结构_联合_枚举/ex_11.c\")|[练习11](./ch16_结构_联合_枚举/ex_12.c \"./ch16_结构_联合_枚举/ex_12.c\")|[练习12](./ch16_结构_联合_枚举/ex_13.md \"./ch16_结构_联合_枚举/ex_13.md\")|\n|[练习13](./ch16_结构_联合_枚举/ex_14.c \"./ch16_结构_联合_枚举/ex_14.c\")|[练习14](./ch16_结构_联合_枚举/ex_15.md \"./ch16_结构_联合_枚举/ex_15.md\")|[练习15](./ch16_结构_联合_枚举/ex_16.md \"./ch16_结构_联合_枚举/ex_16.md\")|[练习16](./ch16_结构_联合_枚举/ex_17.c \"./ch16_结构_联合_枚举/ex_17.c\")|\n\n### 第17章 指针的高级应用\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch17_指针的高级应用/ex_01.c \"./ch17_指针的高级应用/ex_01.c\")|[练习2](./ch17_指针的高级应用/ex_02.c \"./ch17_指针的高级应用/ex_02.c\")|[练习3](./ch17_指针的高级应用/ex_03.c \"./ch17_指针的高级应用/ex_03.c\")|[练习4](./ch17_指针的高级应用/ex_04.md \"./ch17_指针的高级应用/ex_04.md\")|[练习5](./ch17_指针的高级应用/ex_05.c \"./ch17_指针的高级应用/ex_05.c\")|[练习6](./ch17_指针的高级应用/ex_06.c \"./ch17_指针的高级应用/ex_06.c\")|\n|[练习7](./ch17_指针的高级应用/ex_07.md \"./ch17_指针的高级应用/ex_07.md\")|[练习8](./ch17_指针的高级应用/ex_08.md \"./ch17_指针的高级应用/ex_08.md\")|[练习9](./ch17_指针的高级应用/ex_09.md \"./ch17_指针的高级应用/ex_09.md\")|[练习10](./ch17_指针的高级应用/ex_10.c \"./ch17_指针的高级应用/ex_10.c\")|[练习11](./ch17_指针的高级应用/ex_11.md \"./ch17_指针的高级应用/ex_11.md\")|[练习12](./ch17_指针的高级应用/ex_12.c \"./ch17_指针的高级应用/ex_12.c\")|\n|[练习13](./ch17_指针的高级应用/ex_13.c \"./ch17_指针的高级应用/ex_13.c\")|[练习14](./ch17_指针的高级应用/ex_14.c \"./ch17_指针的高级应用/ex_14.c\")|[练习15](./ch17_指针的高级应用/ex_15.md \"./ch17_指针的高级应用/ex_15.md\")|[练习16](./ch17_指针的高级应用/ex_16.md \"./ch17_指针的高级应用/ex_16.md\")|[练习17](./ch17_指针的高级应用/ex_17.c \"./ch17_指针的高级应用/ex_17.c\")|\n\n### 第18章 声明\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch18_声明/ex_01.md \"./ch18_声明/ex_01.md\")|[练习2](./ch18_声明/ex_02.md \"./ch18_声明/ex_02.md\")|[练习3](./ch18_声明/ex_03.md \"./ch18_声明/ex_03.md\")|[练习4](./ch18_声明/ex_04.c \"./ch18_声明/ex_04.c\")|[练习5](./ch18_声明/ex_05.md \"./ch18_声明/ex_05.md\")|[练习6](./ch18_声明/ex_06.c \"./ch18_声明/ex_06.c\")|\n|[练习7](./ch18_声明/ex_07.c \"./ch18_声明/ex_07.c\")|[练习8](./ch18_声明/ex_08.c \"./ch18_声明/ex_08.c\")|[练习9](./ch18_声明/ex_09.c \"./ch18_声明/ex_09.c\")|[练习10](./ch18_声明/ex_10.c \"./ch18_声明/ex_10.c\")|[练习11](./ch18_声明/ex_11.c \"./ch18_声明/ex_11.c\")|[练习12](./ch18_声明/ex_12.md \"./ch18_声明/ex_12.md\")|\n\n### 第19章 程序设计\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch19_程序设计/ex_01.c \"./ch19_程序设计/ex_01.c\")|[练习2](./ch19_程序设计/ex_02.md \"./ch19_程序设计/ex_02.md\")|[练习3](./ch19_程序设计/ex_03.md \"./ch19_程序设计/ex_03.md\")|[练习4](./ch19_程序设计/ex_04.md \"./ch19_程序设计/ex_04.md\")|[练习5](./ch19_程序设计/ex_05.md \"./ch19_程序设计/ex_05.md\")|[练习6](./ch19_程序设计/ex_06.md \"./ch19_程序设计/ex_06.md\")|\n|[练习7](./ch19_程序设计/ex_07.cpp \"./ch19_程序设计/ex_07.cpp\")|[练习8](./ch19_程序设计/ex_08.md \"./ch19_程序设计/ex_08.md\")|[练习9](./ch19_程序设计/ex_09.md \"./ch19_程序设计/ex_09.md\")|[练习10](./ch19_程序设计/ex_10.md \"./ch19_程序设计/ex_10.md\")|\n\n### 第20章 低级程序设计\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch20_低级程序设计/ex_01.c \"./ch20_低级程序设计/ex_01.c\")|[练习2](./ch20_低级程序设计/ex_02.c \"./ch20_低级程序设计/ex_02.c\")|[练习3](./ch20_低级程序设计/ex_03.c \"./ch20_低级程序设计/ex_03.c\")|[练习4](./ch20_低级程序设计/ex_04.c \"./ch20_低级程序设计/ex_04.c\")|[练习5](./ch20_低级程序设计/ex_05.c \"./ch20_低级程序设计/ex_05.c\")|[练习6](./ch20_低级程序设计/ex_06.c \"./ch20_低级程序设计/ex_06.c\")|\n|[练习7](./ch20_低级程序设计/ex_07.c \"./ch20_低级程序设计/ex_07.c\")|[练习8](./ch20_低级程序设计/ex_08.c \"./ch20_低级程序设计/ex_08.c\")|[练习9](./ch20_低级程序设计/ex_09.c \"./ch20_低级程序设计/ex_09.c\")|[练习10](./ch20_低级程序设计/ex_10.c \"./ch20_低级程序设计/ex_10.c\")|\n\n### 第21章 标准库\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch21_标准库/ex_01.md \"./ch21_标准库/ex_01.md\")|[练习2](./ch21_标准库/ex_02.md \"./ch21_标准库/ex_02.md\")|[练习3](./ch21_标准库/ex_03.c \"./ch21_标准库/ex_03.c\")|[练习4](./ch21_标准库/ex_04.md \"./ch21_标准库/ex_04.md\")|[练习5](./ch21_标准库/ex_05.c \"./ch21_标准库/ex_05.c\")|\n\n### 第22章 输入_输出\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch22_输入_输出/ex_01.md \"./ch22_输入_输出/ex_01.md\")|[练习2](./ch22_输入_输出/ex_02.md \"./ch22_输入_输出/ex_02.md\")|[练习3](./ch22_输入_输出/ex_03.c \"./ch22_输入_输出/ex_03.c\")|[练习4](./ch22_输入_输出/ex_04.c \"./ch22_输入_输出/ex_04.c\")|[练习5](./ch22_输入_输出/ex_05.c \"./ch22_输入_输出/ex_05.c\")|[练习6](./ch22_输入_输出/ex_06.c \"./ch22_输入_输出/ex_06.c\")|\n|[练习7](./ch22_输入_输出/ex_07.c \"./ch22_输入_输出/ex_07.c\")|[练习8](./ch22_输入_输出/ex_08.c \"./ch22_输入_输出/ex_08.c\")|[练习9](./ch22_输入_输出/ex_09.c \"./ch22_输入_输出/ex_09.c\")|[练习10](./ch22_输入_输出/ex_10.md \"./ch22_输入_输出/ex_10.md\")|[练习11](./ch22_输入_输出/ex_11.md \"./ch22_输入_输出/ex_11.md\")|[练习12](./ch22_输入_输出/ex_12.c \"./ch22_输入_输出/ex_12.c\")|\n|[练习13](./ch22_输入_输出/ex_13.c \"./ch22_输入_输出/ex_13.c\")|[练习14](./ch22_输入_输出/ex_14.c \"./ch22_输入_输出/ex_14.c\")|[练习15](./ch22_输入_输出/ex_15.c \"./ch22_输入_输出/ex_15.c\")|[练习16](./ch22_输入_输出/ex_16.c \"./ch22_输入_输出/ex_16.c\")|[练习17](./ch22_输入_输出/ex_17.c \"./ch22_输入_输出/ex_17.c\")|[练习18](./ch22_输入_输出/ex_18.c \"./ch22_输入_输出/ex_18.c\")|\n|[练习19](./ch22_输入_输出/ex_19.md \"./ch22_输入_输出/ex_19.md\")|[练习20](./ch22_输入_输出/ex_20.c \"./ch22_输入_输出/ex_20.c\")|[练习21](./ch22_输入_输出/ex_21.md \"./ch22_输入_输出/ex_21.md\")|[练习22](./ch22_输入_输出/ex_22.c \"./ch22_输入_输出/ex_22.c\")|[练习23](./ch22_输入_输出/ex_23.c \"./ch22_输入_输出/ex_23.c\")|\n\n### 第23章 库对数值和字符数据的支持\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch23_库对数值和字符数据的支持/ex_01.c \"./ch23_库对数值和字符数据的支持/ex_01.c\")|[练习2](./ch23_库对数值和字符数据的支持/ex_02.c \"./ch23_库对数值和字符数据的支持/ex_02.c\")|[练习3](./ch23_库对数值和字符数据的支持/ex_03.c \"./ch23_库对数值和字符数据的支持/ex_03.c\")|[练习4](./ch23_库对数值和字符数据的支持/ex_04.c \"./ch23_库对数值和字符数据的支持/ex_04.c\")|[练习5](./ch23_库对数值和字符数据的支持/ex_05.c \"./ch23_库对数值和字符数据的支持/ex_05.c\")|[练习6](./ch23_库对数值和字符数据的支持/ex_06.c \"./ch23_库对数值和字符数据的支持/ex_06.c\")|\n|[练习7](./ch23_库对数值和字符数据的支持/ex_07.c \"./ch23_库对数值和字符数据的支持/ex_07.c\")|[练习8](./ch23_库对数值和字符数据的支持/ex_08.c \"./ch23_库对数值和字符数据的支持/ex_08.c\")|[练习9](./ch23_库对数值和字符数据的支持/ex_09.md \"./ch23_库对数值和字符数据的支持/ex_09.md\")|[练习10](./ch23_库对数值和字符数据的支持/ex_10.c \"./ch23_库对数值和字符数据的支持/ex_10.c\")|[练习11](./ch23_库对数值和字符数据的支持/ex_11.c \"./ch23_库对数值和字符数据的支持/ex_11.c\")|[练习12](./ch23_库对数值和字符数据的支持/ex_12.c \"./ch23_库对数值和字符数据的支持/ex_12.c\")|\n|[练习13](./ch23_库对数值和字符数据的支持/ex_13.c \"./ch23_库对数值和字符数据的支持/ex_13.c\")|\n\n### 第24章 错误处理\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch24_错误处理/ex_01.md \"./ch24_错误处理/ex_01.md\")|[练习2](./ch24_错误处理/ex_02.c \"./ch24_错误处理/ex_02.c\")|[练习3](./ch24_错误处理/ex_03.c \"./ch24_错误处理/ex_03.c\")|[练习4](./ch24_错误处理/ex_04.md \"./ch24_错误处理/ex_04.md\")|\n\n### 第25章 国际化特性\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch25_国际化特性/ex_01.c \"./ch25_国际化特性/ex_01.c\")|[练习2](./ch25_国际化特性/ex_02.c \"./ch25_国际化特性/ex_02.c\")|[练习3](./ch25_国际化特性/ex_03.c \"./ch25_国际化特性/ex_03.c\")|[练习4](./ch25_国际化特性/ex_04.md \"./ch25_国际化特性/ex_04.md\")|\n\n### 第26章 其他库函数\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1](./ch26_其他库函数/ex_01.c \"./ch26_其他库函数/ex_01.c\")|[练习2](./ch26_其他库函数/ex_02.c \"./ch26_其他库函数/ex_02.c\")|[练习3](./ch26_其他库函数/ex_03.c \"./ch26_其他库函数/ex_03.c\")|[练习4](./ch26_其他库函数/ex_04.c \"./ch26_其他库函数/ex_04.c\")|[练习5](./ch26_其他库函数/ex_05.c \"./ch26_其他库函数/ex_05.c\")|[练习6](./ch26_其他库函数/ex_06.c \"./ch26_其他库函数/ex_06.c\")|\n|[练习7](./ch26_其他库函数/ex_07.c \"./ch26_其他库函数/ex_07.c\")|[练习8](./ch26_其他库函数/ex_08.c \"./ch26_其他库函数/ex_08.c\")|[练习9](./ch26_其他库函数/ex_09.c \"./ch26_其他库函数/ex_09.c\")|[练习10](./ch26_其他库函数/ex_10.c \"./ch26_其他库函数/ex_10.c\")|[练习11](./ch26_其他库函数/ex_11.c \"./ch26_其他库函数/ex_11.c\")|[练习12](./ch26_其他库函数/ex_12.c \"./ch26_其他库函数/ex_12.c\")|\n|[练习13](./ch26_其他库函数/ex_13.c \"./ch26_其他库函数/ex_13.c\")|[练习14](./ch26_其他库函数/ex_14.c \"./ch26_其他库函数/ex_14.c\")|\n\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/celsius.c",
    "content": "/* Converts a Fahrenheit temperature to Celsius */\n\n#include <stdio.h>\n\n#define FREEZING_PT 32.0f\n#define SCALE_FACTOR (5.0f / 9.0f)\n\nint main()\n{\n\tfloat fahrenheit, celsius;\n\n\tprintf(\"Enter Fahrenheit temperature: \");\n\tscanf(\"%f\", &fahrenheit);\n\n\tcelsius = (fahrenheit - FREEZING_PT) * SCALE_FACTOR;\n\n\tprintf(\"Celsius equivalent: %.1f\\n\", celsius);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/dweight.c",
    "content": "/* Computes the dimensional weight of a 12 * 10 * 8 box */\n\n#include <stdio.h>\n\nint main()\n{\n\tint length, width, height, volume, weight;\n\n\tlength = 12;\n\twidth = 10;\n\theight = 8;\n\n\tvolume = length * width * height;\n\n\tweight = (volume + 165) / 166;\n\n\tprintf(\"Dimensions: %dx%dx%d\\n\", length, width, height);\n\tprintf(\"Volume (cubic inches): %d\\n\", volume);\n\tprintf(\"Dimensional weight (pounds): %d\\n\", weight);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/dweight2.c",
    "content": "/* Computes the dimensional weight of a 12 * 10 * 8 box */\n\n#include <stdio.h>\n\nint main()\n{\n\tint length, width, height, volume, weight;\n\n\tprintf(\"Enter length of box: \");\n\tscanf(\"%d\", &length);\n\tprintf(\"Enter width of box: \");\n\tscanf(\"%d\", &width);\n\tprintf(\"Enter height of box: \");\n\tscanf(\"%d\", &height);\n\n\tvolume = length * width * height;\n\n\tweight = (volume + 165) / 166;\n\n\tprintf(\"Volume (cubic inches): %d\\n\", volume);\n\tprintf(\"Dimensional weight (pounds): %d\\n\", weight);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_01.c",
    "content": "/*\n * 建立并运行由 Kernighan 和 Ritchie 编写的著名的”hello, world“程序：\n *\n * #include <stdio.h>\n *\n * main()\n * {\n *     printf(\"hello, world\\n\");\n * }\n *\n * 在编译时是否有警告信息？如果有，需要如何进行修改呢？\n */\n\n/*\n * main 函数没有声明返回类型： warning: return type defaults to ‘int’ [-Wreturn-type]\n *\n * main 函数结尾没有 return 语句： warning: control reaches end of non-void function [-Wreturn-type]\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tprintf(\"hello, world\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_02.c",
    "content": "﻿/*\n思考下面的程序：\n\n#include <stdio.h>\n\nmain()\n{\n\tprintf(\"Parkinson's Law:\\nWork expands so as to \");\n\tprintf(\"fill the time\\n\");\n\tprintf(\"available for its completion.\\n\");\n\treturn 0;\n}\n\n(a) 请指出程序中的指令和语句\n(b) 程序的输出是什么？\n*/\n\n/*\n(a) 程序包含了一条指令（#include），四条语句（三个printf调用，一个return）\n(b) \nParkinson's Law:\nWork expands so as to fill the time\navailable for its completion.\n\n*/\n\n#include <stdio.h>\n\nint main()\n{\n\tprintf(\"Parkinson's Law:\\nWork expands so as to \");\n\tprintf(\"fill the time\\n\");\n\tprintf(\"available for its completion.\\n\");\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_03.c",
    "content": "/*\n * 编写一个程序，程序要使用 printf 在屏幕上显示出下面的图形：\n *\n *        *\n *       *\n *      *\n * *   *\n *  * *\n *   *\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tprintf(\"       *\\n\");\n\tprintf(\"      *\\n\");\n\tprintf(\"     *\\n\");\n\tprintf(\"*   *\\n\");\n\tprintf(\" * *\\n\");\n\tprintf(\"  *\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_04.c",
    "content": "/*\n通过下列方法缩写程序dweight.c: (1)用初始化语句替换对变量height、length和width的赋值语句；\n(2)去掉变量weight，在最后的printf语句中计算(valoume + 165) / 166。\n*/\n\n/* Computes the dimensional weight of a 12 * 10 * 8 box */\n\n#include <stdio.h>\n\nint main()\n{\n\tint length = 12, width = 10, height = 8, volume;\n\n\tvolume = length * width * height;\n\n\tprintf(\"Dimensions: %dx%dx%d\\n\", length, width, height);\n\tprintf(\"Volume (cubic inches): %d\\n\", volume);\n\tprintf(\"Dimensional weight (pounds): %d\\n\", (volume + 165) / 166);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_05.c",
    "content": "/*\n * 编写一个计算球体体积的程序，其中球体半径为10m，参考公式v=4/3πr3。注意，分数4/3应写为\n * 4.0/3.0。（如果分数写成4/3，会产生什么结果？）\n */\n\n/*\n * 如果写成4/3，那么结果恒等于1，不是正确的结果。\n */\n\n#include <stdio.h>\n\n#define PI 3.14\n\nint main()\n{\n\tfloat radius, volume;\n\n\tradius = 10;\n\tvolume = 4.0 / 3.0 * PI * radius * radius * radius;\n\n\tprintf(\"volume: %.2f\\n\", volume);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_06.c",
    "content": "/*\n * 编写一个程序用来声明几个 int 型和 float 型变量，不对这些变量进行初始化，然后打印出它们的值。\n * 这些值是否有规律？（通常情况下没有）\n */\n\n/*\n * 没有规律。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j, k;\n\tfloat x, y;\n\n\tprintf(\"i: %d\\n\", i);\n\tprintf(\"j: %d\\n\", j);\n\tprintf(\"k: %d\\n\", k);\n\n\tprintf(\"x: %f\\n\", x);\n\tprintf(\"y: %f\\n\", y);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_07.c",
    "content": "/*\n * 修改练习5中的程序，使用户可以自行录入球体的半径。\n */\n\n#include <stdio.h>\n\n#define PI 3.14\n\nint main()\n{\n\tfloat radius, volume;\n\n\tprintf(\"Enter radius: \");\n\tscanf(\"%f\", &radius);\n\n\tvolume = 4.0 / 3.0 * PI * radius * radius * radius;\n\n\tprintf(\"volume: %.2f\\n\", volume);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_08.c",
    "content": "/*\n * 编写一个程序，要求用户输入一个美金数量，然后显示出加了5%税率的相应金额。格式如下所示：\n * Enter a dollar amount: 100.00\n * With tax added: 105.00\n */\n\n#include <stdio.h>\n\n#define TAX_RATE 0.05\n\nint main()\n{\n\tfloat amount;\n\n\tprintf(\"Enter a dollar amount: \");\n\tscanf(\"%f\", &amount);\n\n\tprintf(\"With tax added: %.2f\\n\", amount + amount * TAX_RATE);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_09.md",
    "content": "修改练习7，要求用名为 PI 的宏表示π的值。\n\n见练习7。\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_10.c",
    "content": "/*\n * 判断下列哪些是不合法的C语言标识符？\n * (a) 100_bottles\n * (b) _100_bottles\n * (c) one_hundred_bottles\n * (d) bottles_by_the_hundred\n */\n\nint main()\n{\n\t/* int 100_bottles; */\t/* 不合法 */\n\tint _100_bottles;\n\tint one_hundred_bottles;\n\tint bottles_by_the_hundred;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_11.md",
    "content": "判断下列哪些是C语言的关键字？\n\n(a) for 是\n\n(b) If 不是\n\n(c) main 不是\n\n(d) printf 不是\n\n(e) while 是\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_12.md",
    "content": "下面的语句中有多少记号？\n\n```c\na=(3*q-p*p)/3;\n```\n\n有:\n\n1. a\n2. (\n3. 3\n4. \\*\n5. q\n6. -\n7. p\n8. \\*\n9. p\n10. )\n11. /\n12. 3\n13. ;\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/ex_13.md",
    "content": "在练习12的记号之间插入足够多的空格，使得上面的语句易于阅读。\n\n```c\na = (3 * q - p * p) / 3;\n```\n"
  },
  {
    "path": "codes/CProgramming/ch02_C语言基本概念/pun.c",
    "content": "/*\n * Name: pun.c\n * Purpose: Prints a bad pun.\n * Author: liudiwen\n * Date written: 28/8/2018\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tprintf(\"To C, or not to C, that is the question.\\n\");\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/addfrac.c",
    "content": "/* Add two fractions */\n\n#include <stdio.h>\n\nint main()\n{\n    int num1, denom1;\n    int num2, denom2;\n    int result_num, result_denom;\n\n    printf(\"Enter first fraction: \");\n    scanf(\"%d/%d\", &num1, &denom1);\n\n    printf(\"Enter second fraction: \");\n    scanf(\"%d/%d\", &num2, &denom2);\n\n    result_num = num1 * denom2 + num2 * denom1;\n    result_denom = denom1 * denom2;\n\n    printf(\"The sum is: %d/%d\\n\", result_num, result_denom);\n\n    return 0;\n}\n\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/ex_01.c",
    "content": "/*\n * 下面的 printf 函数调用产生的输出分别是什么？\n */\n\n#include <stdio.h>\n\nint main()\n{\n\t// `代表空格\n\n\t// a: ````86,1040\n\tprintf(\"%6d,%4d\", 86, 1040);\n\tputchar('\\n');\n\n\t// b: `3.02530e+01\n\tprintf(\"%12.5e\", 30.253);\n\tputchar('\\n');\n\n\t// c: 83.1620\n\tprintf(\"%.4f\", 83.162);\n\tputchar('\\n');\n\n\t// d: 1e-06\n\tprintf(\"%-6.2g\", .0000009979);\n\tputchar('\\n');\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/ex_02.c",
    "content": "/*\n * 编写 printf 函数调用以下列格式来显示 float 型变量x：\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tfloat x = 3.14;\n\n\t// (a) 指数表示形式；最小为8的字段宽度内左对齐；小数点后保留1位数字\n\tprintf(\"%-8.1e\\n\", x);\n\n\t// (b) 指数表示形式；最小为10的字段宽度内右对齐；小数点后保留6位数字\n\tprintf(\"%10.6e\\n\", x);\n\n\t// (c) 定点十进制表示形式；最小为8的字段宽度内左对齐；小数点后保留3位数字\n\tprintf(\"%-8.3f\\n\", x);\n\n\t// (d) 定点十进制表示形式；最小为6的字段宽度内右对齐；小数点后无数字\n\tprintf(\"%6.0f\\n\", x);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/ex_03.c",
    "content": "/*\n * 说明下列每对 scanf 格式串是否等价？如果不等价，请指出它们的差异。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j, k;\n\tfloat x, y;\n\n\t// (a) \"%d\" 与 \" %d\" 等价\n\t//scanf(\"%d\", &i); printf(\"%d\\n\", i);\n\t//scanf(\" %d\", &i); printf(\"%d\\n\", i);\n\n\t// (b) \"%d-%d-%d\" 与 \"%d -%d -%d\" 不等价，不能用第二个格式串的输入代替第一个\n\t//scanf(\"%d-%d-%d\", &i, &j, &k); printf(\"%d %d %d\\n\", i, j, k);\n\t//scanf(\"%d -%d -%d\", &i, &j, &k); printf(\"%d %d %d\\n\", i, j, k);\n\t\n\t// (c) “%f” 与 \"%f \" 不等价，第二个格式串的末尾是空格，必须读取一个非空白字符\n\t//scanf(\"%f\", &x); printf(\"%f\\n\", x);\n\t//scanf(\"%f \", &x); printf(\"%f\\n\", x);\n\n\t// (d) \"%f,%f\" 与 \"%f, %f\" 等价\n\tscanf(\"%f,%f\", &x, &y); printf(\"%f %f\\n\", x, y);\n\tscanf(\"%f, %f\", &x, &y); printf(\"%f %f\\n\", x, y);\n\t\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/ex_04.c",
    "content": "/*\n * 编写一个程序，接收用户录入的日期信息并且将其显示出来。其中，输入日期的形式为月/日/年（即\n * mm/dd/yy），输出日期的形式为年月日（即yymmdd）。格式如下所示：\n * Enter a date (mm/dd/yy): 2/17/96\n * You entered the date 960217\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint yy, mm, dd;\n\n\tprintf(\"Enter a date (mm/dd/yy): \");\n\tscanf(\"%d/%d/%d\", &mm, &dd, &yy);\n\n\tprintf(\"You entered the date %.2d%.2d%.2d\\n\", yy, mm, dd);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/ex_05.c",
    "content": "/*\n * 编写一个程序，对用户录入的产品信息进行格式化。程序运行后需有如下会话：\n * Enter item number: 583\n * Enter unit price: 13.5\n * Enter purchase date (mm/dd/yy): 10/24/95\n * Item    Unit    Purchase\n *         Price   Date\n * 583     $ 13.50 10/24/95\n *\n * 其中，数字项和日期项采用左对齐方式；单位价格采用右对齐方式。美元数量的最大取值为9999.99。\n * 提示：使用制表符控制列坐标。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint item_number;\n\tfloat unit_price;\n\tint mm, dd, yy;\n\n\t/* input */\n\tprintf(\"Enter item number: \");\n\tscanf(\"%d\", &item_number);\n\n\tprintf(\"Enter unit price: \");\n\tscanf(\"%f\", &unit_price);\n\n\tprintf(\"Enter purchase date (mm/dd/yy): \");\n\tscanf(\"%d/%d/%d\", &mm, &dd, &yy);\n\n\t/* output */\n\tprintf(\"Item\\tUnit\\t\\tPurchase\\n\");\n\tprintf(\"\\tPrice\\t\\tDate\\n\");\n\tprintf(\"%-d\\t$ %.2f\\t\\t%-d/%-d/%-d\\n\", item_number, unit_price, mm, dd, yy);\n\t\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/ex_06.c",
    "content": "/*\n * 图书用国际标准图书编号（ ISBN ）进行标识，如0-393-30375-6。编号中的第一个数字说明编写书籍\n * 所用的语言（例如，0表示英语，3表示德语）。接下来的一组数字表示出版社（例如，393是 W.W.Norton\n * 出版社的编号），而随后的数字则是出版社设定的，用来识别图书（例如，30375是 Stephen Jay Gould\n * 的 The Flamingo's Smile 一书的编号）。最后，结尾数字是“校验数字”，它用来验证前面数字的准确性。\n * 编写一个程序来分解用户录入的 ISBN 信息，格式如下：\n * Enter ISBN: 0-393-30375-6\n * Language: 0\n * Publisher: 393\n * Book Number: 30375\n * Check digit: 6\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint language, publisher, book_number, check_digit;\n\n\tprintf(\"Enter ISBN: \");\n\tscanf(\"%d-%d-%d-%d\", &language, &publisher, &book_number, &check_digit);\n\n\tprintf(\"Language: %d\\n\", language);\n\tprintf(\"Publisher: %d\\n\", publisher);\n\tprintf(\"Book Number: %d\\n\", book_number);\n\tprintf(\"Check digit: %d\\n\", check_digit);\n\t\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/ex_07.c",
    "content": "/*\n * 假设 scanf 函数调用的格式如下：\n * scanf(\"%d%f%d\", &i, &x, &j);\n * 如果用户录入如下信息：\n * 10.3 5 6\n * 调用执行后，变量i、x和j的值分别是多少？（假设变量i和变量j都是 int 型，而变量x是 float 型。）\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j;\n\tfloat x;\n\n\tscanf(\"%d%f%d\", &i, &x, &j);\n\tprintf(\"%d %f %d\\n\", i, x, j); // i = 10, x = 0.3, y = 5\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/ex_08.c",
    "content": "/*\n * 假设 scanf 函数调用的格式如下：\n * scanf(\"%f%d%f\", &x, &i, &y);\n * 如果用户录入如下信息：\n * 12.3 45.6 789\n * 调用执行后，变量x、i和y的值分别是多少？（假设变量x和变量y都是 float 型，而变量i是 int 型。）\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i;\n\tfloat x, y;\n\n\tscanf(\"%f%d%f\", &x, &i, &y);\n\tprintf(\"%f %d %f\\n\", x, i, y); // x = 12.3, i = 45, y = 0.6\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/example_escape_sequence.c",
    "content": "/*\n * 转移序列案例，见 p25\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tprintf(\"Item\\tUnit\\tPurchase\\n\\tPrice\\tDate\\n\");\n\n\tprintf(\"\\\"Hello!\\\"\\n\");\n\n\tprintf(\"\\\\\");\t/* prints one \\ character */\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/example_scanf.c",
    "content": "/*\n * scanf 案例，见 p26\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j;\n\tfloat x, y;\n\t\n\tscanf(\"%d%d%f%f\", &i, &j, &x, &y);\n\n\tprintf(\"i: %d\\n\", i);\n\tprintf(\"j: %d\\n\", j);\n\tprintf(\"x: %f\\n\", x);\n\tprintf(\"y: %f\\n\", y);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/stocks.c",
    "content": "/*\n * Computes the value of stock holdings\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint price, shares;\n\tfloat num, denom, value;\n\n\tprintf(\"Enter share price (must include a fraction): \");\n\tscanf(\"%d%f/%f\", &price, &num, &denom);\n\n\tprintf(\"Enter number of shares: \");\n\tscanf(\"%d\", &shares);\n\n\tvalue = (price + num / denom) * shares;\n\t\n\tprintf(\"Value of holdings: $%.2f\\n\", value);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch03_格式化的输入输出/tprintf.c",
    "content": "/* Print int and float values in various formats */\n\n#include <stdio.h>\n\nint main()\n{\n    int i;\n    float x;\n\n    i = 40;\n    x = 839.21f;\n\n    printf(\"|%d|%5d|%-5d|%5.3d|\\n\", i, i, i, i);\n    printf(\"|%10.3f|%10.3e|%10g|\\n\", x, x, x);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/ex_01.c",
    "content": "/*\n * 列出下列每段代码的输出结果。假设i、j和k都是 int 型变量。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j, k;\n\n\t/*\n\t * (a) 1 2\n\t */\n\t{\n\t\ti = 5; j = 3;\n\t\tprintf(\"%d %d\", i / j, i % j);\n\t}\n\tputchar('\\n');\n\n\t/*\n\t * (b) 0\n\t */\n\t{\n\t\ti = 2; j = 3;\n\t\tprintf(\"%d\", (i + 10) % j);\n\t}\n\tputchar('\\n');\n\n\t/*\n\t * (c) 1\n\t */\n\t{\n\t\ti = 7; j = 8; k = 9;\n\t\tprintf(\"%d\", (i + 10) % k / j);\n\t}\n\tputchar('\\n');\n\n\t/*\n\t * (d) 0\n\t */\n\t{\n\t\ti = 1; j = 2; k = 3;\n\t\tprintf(\"%d\", (i + 5) % (j + 2) / k);\n\t}\n\tputchar('\\n');\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/ex_02.c",
    "content": "/*\n * 如果i和j都是正整数，是否(-i)/j的值和-(i/j)的值始终一样？验证你的答案。\n *\n * 一样。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j;\n\n\tprintf(\"Enter i: \");\n\tscanf(\"%d\", &i);\n\n\tprintf(\"Enter j: \");\n\tscanf(\"%d\", &j);\n\n\tprintf(\"(-i)/j: %d\\n\", (-i)/j);\n\tprintf(\"-(i/j): %d\\n\", -(i/j));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/ex_03.c",
    "content": "/*\n * 编写程序实现数字反向，即根据用户输入的两位数，反向显示出该数相应位上数字。要求程序执行过\n * 程中需要具有下列显示信息。\n *\n * Enter a two-degit number: 28\n * The reversal is: 82\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint n;\n\n\tprintf(\"Enter a two-digit number: \");\n\tscanf(\"%d\", &n);\n\n\tprintf(\"The reversal is: %d%d\\n\", n%10, n/10);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/ex_04.c",
    "content": "/*\n * 扩展练习3中的程序使其可以处理三位数的反向。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint n;\n\n\tprintf(\"Enter a three-digit number: \" );\n\tscanf(\"%d\", &n);\n\n\tprintf(\"The reversal is: %d%d%d\", n%100%10, n%100/10, n/100);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/ex_05.c",
    "content": "/*\n * 重新编写练习4中的程序，使新程序不用数学式的分割数字方法就可以显示出三位的反向数。提示：\n * 复习程序upc.c。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint d1, d2, d3;\n\n\tprintf(\"Enter a three-digit number: \");\n\tscanf(\"%1d%1d%1d\", &d1, &d2, &d3);\n\n\tprintf(\"The reversal is: %d%d%d\\n\", d3, d2, d1);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/ex_06.c",
    "content": "/*\n * 列出下列每段代码的输出结果。假设i、j和k都是 int 型变量。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j, k;\n\n\t/*\n\t * (a) 63 8\n\t */\n\t{\n\t\ti = 7; j = 8;\n\t\ti *= j + 1;\n\t\tprintf(\"%d %d\", i, j);\n\t}\n\tputchar(10);\n\n\t/*\n\t * (b) 3 2 1\n\t */\n\t{\n\t\ti = j = k = 1;\n\t\ti += j += k;\n\t\tprintf(\"%d %d %d\", i, j, k);\n\t}\n\tputchar(10);\n\n\t/*\n\t * (c) 2 -1 3\n\t */\n\t{\n\t\ti = 1; j = 2; k = 3;\n\t\ti -= j -= k;\n\t\tprintf(\"%d %d %d\", i, j, k);\n\t}\n\tputchar(10);\n\n\t/*\n\t * (d) 0 0 0\n\t */\n\t{\n\t\ti = 2; j = 1; k = 0;\n\t\ti *= j *= k;\n\t\tprintf(\"%d %d %d\", i, j, k);\n\t}\n\tputchar(10);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/ex_07.c",
    "content": "/*\n * 列出下列每段代码的输出结果。假设i、j和k都是 int 型变量。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j, k;\n\n\t/*\n\t * (a) 0 2\n\t */\n\t{\n\t\ti = 1;\n\t\tprintf(\"%d \", i++ - 1);\n\t\tprintf(\"%d\", i);\n\t}\n\tputchar(10);\n\n\t/*\n\t * (b) 4 11 6\n\t */\n\t{\n\t\ti = 10; j = 5;\n\t\tprintf(\"%d \", i++ - ++j);\n\t\tprintf(\"%d %d\", i, j);\n\t}\n\tputchar(10);\n\n\t/*\n\t * (c) 0 8 7\n\t */\n\t{\n\t\ti = 7; j = 8;\n\t\tprintf(\"%d \", i++ - --j);\n\t\tprintf(\"%d %d\", i, j);\n\t}\n\tputchar(10);\n\n\t/*\n\t * (d) 3 4 5 4\n\t */\n\t{\n\t\ti = 3; j = 4; k = 5;\n\t\tprintf(\"%d \", i++ - j++ + --k);\n\t\tprintf(\"%d %d %d\", i, j, k);\n\t}\n\tputchar(10);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/ex_08.c",
    "content": "/*\n * 表达式++i和i++中只有一个是与表达式(i += 1)完全相同的，哪一个是呢？验证你的答案。\n *\n * ++i\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i;\n\n\ti = 1;\n\tprintf(\"%d\\n\", i++);\n\n\ti = 1;\n\tprintf(\"%d\\n\", ++i);\n\n\ti = 1;\n\tprintf(\"%d\\n\", (i += 1));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/ex_09.md",
    "content": "用圆括号来显示C语言编译器解释下列表达式的方法。\n\n(a) a * b - c * d + e\n\n((a * b) - (c * d)) + e\n\n(b) a / b % c / d\n\n((a / b) % c) / d\n\n(c) - a - b + c - + d\n\n(((- a) - b) + c) - (+ d)\n\n(d) a * - b / c - d\n\n((a * (- b)) / c) - d\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/ex_10.c",
    "content": "/*\n * 表达式(i++)+ (i--)共有多少种可能的值？假设i初始值为1，这些值都是什么？\n *\n * 先执行第一个表达式，且i已修改，结果为 1 + 2 = 3\n * 先执行第一个表达式，但i未修改，结果为 1 + 1 = 2\n * 先执行第二个表达式，且i已修改，结果为 0 + 1 = 1\n * 先执行第二个表达式，但i未修改，结果为 1 + 1 = 2\n *\n * 当前环境，实际运行结果是3\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint res, i = 1;\n\n\tres = (i++)+ (i--);\n\n\tprintf(\"%d\\n\", res);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/ex_11.md",
    "content": "请描述执行下列每条表达式语句后的效果。（假设i的初始值为1，j的初始值为2。）\n\n(a) i += j; 表达式结果为3，副作用是把i的值修改成3\n\n(b) i--; 表达式的结果为1，副作用是把i的值修改成0\n\n(b) i * j / i; 表达式的结果为2，结果被丢弃\n\n(d) i % ++j; 表达式的结果为1，副作用是把j的值修改成3\n"
  },
  {
    "path": "codes/CProgramming/ch04_表达式/upc.c",
    "content": "/* Computes a Universal Product Code check digit */\n\n/* Enter: 0 13800 15173 \n * Check: 5\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint d, i1, i2, i3, i4, i5, j1, j2, j3, j4, j5;\n\tint first_sum, second_sum, total, check_digit;\n\n\tprintf(\"Enter the first (single) digit: \");\n\tscanf(\"%1d\", &d);\n\n\tprintf(\"Enter first group of five digits: \");\n\tscanf(\"%1d%1d%1d%1d%1d\", &i1, &i2, &i3, &i4, &i5);\n\n\tprintf(\"Enter second group of five digits: \");\n\tscanf(\"%1d%1d%1d%1d%1d\", &j1, &j2, &j3, &j4, &j5);\n\n\tfirst_sum = d + i2 + i4 + j1 + j3 + j5;\n\tsecond_sum = i1 + i3 + i5 + j2 + j4;\n\n\ttotal = 3 * first_sum + second_sum;\n\n\tcheck_digit = 9 - ((total - 1) % 10);\n\n\tprintf(\"Check digit: %d\\n\", check_digit);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/broker.c",
    "content": "/* Calculates a brokers commision */\n\n#include <stdio.h>\n\nint main()\n{\n\tfloat value, commision;\n\n\tprintf(\"Enter value of trade: \");\n\tscanf(\"%f\", &value);\n\n\tif (value < 2500)\n\t\tcommision = 30.0f + 0.01 * 0.17 * value;\n\telse if (value < 6250)\n\t\tcommision = 56.0f + 0.01 * 0.66 * value;\n\telse if (value < 20000)\n\t\tcommision = 76.0f + 0.01 * 0.34 * value;\n\telse if (value < 50000)\n\t\tcommision = 100 + 0.01 * 0.22 * value;\n\telse if (value < 500000)\n\t\tcommision = 155 + 0.01 * 0.11 * value;\n\telse\n\t\tcommision = 255 + 0.01 * 0.09 * value;\n\n\tif (commision < 39)\n\t\tcommision = 39;\n\n\tprintf(\"The commision is: $%.2f\\n\", commision);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/date.c",
    "content": "/* Print a date in legal form */\n\n/* 如输入7/19/14，输出Dated this 19th day of July, 2014. */\n\n#include <stdio.h>\n\nint main()\n{\n\tint month, day, year;\n\n\tprintf(\"Enter date(mm/dd/yy): \");\n\tscanf(\"%d/%d/%d\", &month, &day, &year);\n\n\tprintf(\"Dated this %d\", day);\n\n\tswitch(day) \n\t{\n\t\tcase 1:\n\t\tcase 21:\n\t\t\tprintf(\"st\");\n\t\t\tbreak;\n\n\t\tcase 2:\n\t\tcase 22:\n\t\t\tprintf(\"nd\");\n\t\t\tbreak;\n\n\t\tcase 3:\n\t\tcase 23:\n\t\t\tprintf(\"rd\");\n\t\t\tbreak;\n\n\t\tdefault:\n\t\t\tprintf(\"th\");\n\t\t\tbreak;\n\t}\n\n\tprintf(\" day of \");\n\n\tswitch(month) \n\t{\n\t\tcase 1: printf(\"January\"); break;\n\t\tcase 2: printf(\"February\"); break;\n\t\tcase 3: printf(\"March\"); break;\n\t\tcase 4: printf(\"April\"); break;\n\t\tcase 5: printf(\"May\"); break;\n\t\tcase 6: printf(\"June\"); break;\n\t\tcase 7: printf(\"July\"); break;\n\t\tcase 8: printf(\"August\"); break;\n\t\tcase 9: printf(\"September\"); break;\n\t\tcase 10: printf(\"October\"); break;\n\t\tcase 11: printf(\"November\"); break;\n\t\tcase 12: printf(\"December\"); break;\n\t}\n\n\tprintf(\", 20%.2d.\", year);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_01.c",
    "content": "/*\n * 下列代码段对关系运算符和判等运算符进行了说明。假设i、j和k都是 int 型变量，请列出每道题中\n * 的输出结果。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j, k;\n\n\t// (a) 1\n\t{\n\t\ti = 2; j = 3;\n\t\tk = i * j == 6;\n\t\tprintf(\"%d\", k);\n\t}\n\tputchar(10);\n\n\t// (b) 1\n\t{\n\t\ti = 5; j = 10; k = 1;\n\t\tprintf(\"%d\", k > i < j);\n\t}\n\tputchar(10);\n\n\t// (c) 1\n\t{\n\t\ti = 3; j = 2; k = 1;\n\t\tprintf(\"%d\", i < j == j < k);\n\t}\n\tputchar(10);\n\n\t// (d) 1\n\t{\n\t\ti = 1; j = 2; k = 3;\n\t\tprintf(\"%d\", i < j || k);\n\t}\n\tputchar(10);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_02.c",
    "content": "/*\n * 下列代码段对逻辑运算符进行了说明。假设i、j和k都是 int 型变量，请列出每道题中的输出结果。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j, k;\n\n\t// (a) 1\n\t{\n\t\ti = 10; j = 5;\n\t\tprintf(\"%d\", !i < j);\n\t}\n\tputchar(10);\n\n\t// (b) 1\n\t{\n\t\ti = 2; j = 1;\n\t\tprintf(\"%d\", !!i + !j);\n\t}\n\tputchar(10);\n\n\t// (c) 1\n\t{\n\t\ti = 5; j = 0; k = -5;\n\t\tprintf(\"%d\", i && j || k);\n\t}\n\tputchar(10);\n\n\t// (d) 1\n\t{\n\t\ti = 1; j = 2; k = 3;\n\t\tprintf(\"%d\", i < j || k);\n\t}\n\tputchar(10);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_03.c",
    "content": "/*\n * 下列代码对逻辑表达式的短路行为进行了说明。假设i、j和k都是 int 型变量，请列出每道题中的\n * 输出结果。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j, k;\n\n\t// (a) 1\n\t{\n\t\ti = 3; j = 4; k = 5;\n\t\tprintf(\"%d\", i < j || ++j < k);\n\t}\n\tputchar(10);\n\n\t// (b) 07 8 9\n\t{\n\t\ti = 7; j = 8; k = 9;\n\t\tprintf(\"%d\", i - 7 && j ++ < k);\n\t\tprintf(\"%d %d %d\", i, j, k);\n\t}\n\tputchar(10);\n\n\t// (c) 18 8 9\n\t{\n\t\ti = 7; j = 8; k = 9;\n\t\tprintf(\"%d\", (i = j) || (j = k));\n\t\tprintf(\"%d %d %d\", i, j, k);\n\t}\n\tputchar(10);\n\n\t// (d) 12 1 1\n\t{\n\t\ti = 1; j = 1; k = 1;\n\t\tprintf(\"%d\", ++i || ++j && ++k);\n\t\tprintf(\"%d %d %d\", i, j, k);\n\t}\n\tputchar(10);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_04.c",
    "content": "/*\n * 编写一个单独的表达式，要求这个表达式的值根据i是否小于、等于或大于j而分别为-1、0或+1。\n */ \n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j, res;\n\n\tj = 10;\n\n\tprintf(\"j = %d, Enter i: \", j);\n\tscanf(\"%d\", &i);\n\n\tres = (i < j) ? -1 : (i == j ? 0 : 1);\n\n\tprintf(\"res: %d\\n\", res);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_05.c",
    "content": "/*\n * 编写一个程序用来确定一个数的位数：\n *\n * Enter a number: 374\n * The number 374 has 3 digits\n *\n * 假设输入的数最多不超过四位。提示：利用 if 语句进行数的判定。例如，如果数是在0到9之间的，那\n * 么位数为一。如果数是在10到99之间的，那么位数为二。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint n, digits;\n\n\tprintf(\"Enter a number: \");\n\tscanf(\"%d\", &n);\n\n\tif (n >= 0 && n <= 9) digits = 1;\n\telse if (n >= 10 && n <= 99) digits = 2;\n\telse if (n >= 100 && n <= 999) digits = 3;\n\telse digits = 4;\n\n\tprintf(\"The number %d has %d digits\\n\", n, digits);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_06.c",
    "content": "/*\n * 编写一个程序，要求用户输入24小时制的时间，然后显示12小时制的格式：\n *\n * Enter a 24-hour time: 21:11\n * Equivalent 12-hour time: 9:11 PM\n *\n * 注意不要把12:00显示成0:00\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint hh, mm;\n\n\tprintf(\"Enter a 24-hour time: \");\n\tscanf(\"%d:%d\", &hh, &mm);\n\n\tif (hh <= 12)\n\t\tprintf(\"Equivalent 12-hour time: %d:%d AM\\n\", hh, mm);\n\telse\n\t\tprintf(\"Equivalent 12-hour time: %d:%d PM\\n\", hh - 12, mm);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_07.c",
    "content": "/*\n * 同时采用下列两种变化对程序 borker.c 进行修改。\n *\n * （a）不再直接用交易额，而是要求用户输入股票的数量和每股的价格。\n * （b）增加语句用来计算经纪人竞争对手的佣金（少于2000股时佣金为每股33美元+3美分，2000股或更\n * 大股时佣金为每股33美元+2美分）。显示原有的经纪人的佣金的同时，显示竞争对手的佣金。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint stock_num;\n\tfloat stock_value, value, commision, competitor_commision;\n\n\tprintf(\"Enter stock_num: \");\n\tscanf(\"%d\", &stock_num);\n\tprintf(\"Enter stock_value: \");\n\tscanf(\"%f\", &stock_value);\n\n\tvalue = stock_num * stock_value;\n\n\tif (value < 2500)\n\t\tcommision = 30.0f + 0.01 * 0.17 * value;\n\telse if (value < 6250)\n\t\tcommision = 56.0f + 0.01 * 0.66 * value;\n\telse if (value < 20000)\n\t\tcommision = 76.0f + 0.01 * 0.34 * value;\n\telse if (value < 50000)\n\t\tcommision = 100 + 0.01 * 0.22 * value;\n\telse if (value < 500000)\n\t\tcommision = 155 + 0.01 * 0.11 * value;\n\telse\n\t\tcommision = 255 + 0.01 * 0.09 * value;\n\n\tif (commision < 39)\n\t\tcommision = 39;\n\n\t{\n\t\t/* calc for competitor commision */\n\t\tif (stock_num < 2000)\n\t\t\tcompetitor_commision = 33.03 * stock_num;\n\t\telse\n\t\t\tcompetitor_commision = 33.02 * stock_num;\n\t}\n\n\tprintf(\"The commision is: $%.2f\\n\", commision);\n\tprintf(\"The competition commision is: %.2f\\n\", competitor_commision);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_08.c",
    "content": "/*\n * 下面是蒲福风力的简单版本。蒲福风力等级是用于测量风力的。\n *\n *     速率（=海里/小时）    描述\n *     小于1                 Calm（无风）\n *     1~3                   Light air（轻风）\n *     4~27                  Breeze（微风）\n *     28~47                 Gale（大风）\n *     48~63                 Storm（暴风）\n *     大于63                Hurricane（飓风）\n *\n * 编写一个程序，要求用户输入风速（按照海里/小时），然后显示相应的描述。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint wind_speed;\n\n\tprintf(\"Enter wind speed: \");\n\tscanf(\"%d\", &wind_speed);\n\n\tif (wind_speed < 1)\n\t\tprintf(\"Calm\\n\");\n\telse if (wind_speed >= 1 && wind_speed <= 3)\n\t\tprintf(\"Light air\\n\");\n\telse if (wind_speed >= 4 && wind_speed <= 27)\n\t\tprintf(\"Breeze\\n\");\n\telse if (wind_speed >= 28 && wind_speed <= 47)\n\t\tprintf(\"Gale\\n\");\n\telse if (wind_speed >= 48 && wind_speed <= 63)\n\t\tprintf(\"Storm\\n\");\n\telse if (wind_speed > 63)\n\t\tprintf(\"Hurricane\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_09.c",
    "content": "/*\n * 在美国的某个州，未婚居民需要负担下列所得税：\n *\n *     收入                   税金\n *     未超过750美元          收入的1%\n *     750~2250美元           7.50美元加上超出750美元部分的2%\n *     2250~3750美元          37.50美元加上超出2250美元部分的3%\n *     3750~5250美元          82.50美元加上超出3750美元部分的4%\n *     5250~7000美元          142.50美元加上超出5250美元部分的5%\n *     超过7000美元           230.00美元加上超出7000美元部分的6%\n *\n *     编写一个程序，要求用户输入需纳税的收入，然后显示税金。\n */\n\n#include <stdio.h>\n\n#define PER 0.01\n\nint main()\n{\n\tfloat income, tax;\n\n\tprintf(\"Enter income: \");\n\tscanf(\"%f\", &income);\n\n\tif (income <= 750)\n\t\ttax = income * 1 * PER;\n\telse if (income > 750 && income <= 2250)\n\t\ttax = 7.50 + (income - 750) * 2 * PER;\n\telse if (income > 2250 && income <= 3750)\n\t\ttax = 37.50 + (income - 2250) * 3 * PER;\n\telse if (income > 3750 && income <= 5250)\n\t\ttax = 82.50 + (income - 3750) * 4 * PER;\n\telse if (income > 5250 && income <= 7000)\n\t\ttax = 142.50 + (income - 5250) * 5 * PER;\n\telse\n\t\ttax = 230.00 + (income - 7000) * 6 * PER;\n\n\tprintf(\"tax: %.2f\\n\", tax);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_10.c",
    "content": "/*\n * 修改4.1节的程序 upc.c ，使其可以检测 UPC 的有效性。在用户输入 UPC 后，程序将显示 VALID 或 NOT\n * VALID 。\n */\n\n/* \n * Valid entry: 0 13800 15173 5\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint d, i1, i2, i3, i4, i5, j1, j2, j3, j4, j5;\n\tint first_sum, second_sum, total, check_digit, enter_check_digit;\n\n\tprintf(\"Enter the first (single) digit: \");\n\tscanf(\"%1d\", &d);\n\n\tprintf(\"Enter first group of five digits: \");\n\tscanf(\"%1d%1d%1d%1d%1d\", &i1, &i2, &i3, &i4, &i5);\n\n\tprintf(\"Enter second group of five digits: \");\n\tscanf(\"%1d%1d%1d%1d%1d\", &j1, &j2, &j3, &j4, &j5);\n\n\tprintf(\"Enter check digit: \");\n\tscanf(\"%1d\", &enter_check_digit);\n\n\tfirst_sum = d + i2 + i4 + j1 + j3 + j5;\n\tsecond_sum = i1 + i3 + i5 + j2 + j4;\n\n\ttotal = 3 * first_sum + second_sum;\n\n\tcheck_digit = 9 - ((total - 1) % 10);\n\n\tif (check_digit == enter_check_digit)\n\t\tprintf(\"VALID\");\n\telse\n\t\tprintf(\"NOTVALID\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_11.c",
    "content": "/*\n * 下列 if 语句在C语言中是否合法？\n *\n * if (n >= 1 <= 10)\n *     printf(\"n is between 1 and 10\\n\");\n *\n * 如果合法，那么当n等于0时语句如何执行？\n */\n\n/*\n * 合法，n等于0时，会进入if内的语句。但这不是期望的。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint n;\n\tn = 0;\n\n\tif (n >= 1 <= 10)\n\t\tprintf(\"n is between 1 and 10\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_12.c",
    "content": "/*\n * 下列 if 语句在C语言中是否合法？\n * if (n == 1 - 10)\n *     printf(\"n is between 1 and 10\\n\");\n *\n * 如果合法，那么当n等于5时语句如何执行？\n */\n\n/*\n * 合法，n等于5时不会进入 if 语句。这样的写法也是错误的。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint n;\n\tn = 5;\n\n\tif (n == 1 - 10)\n\t\tprintf(\"n is between 1 and 10\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_13.c",
    "content": "/*\n * 如果i的值为17，那么下列语句显示的结果是什么？如果i的值为-17，那么下列语句显示的结果又是\n * 什么？\n *\n * printf(\"%d\\n\", i >= 0 ? i : -i);\n */\n\n/*\n * 都是17\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i;\n\n\ti = 17;\n\tprintf(\"%d\\n\", i >= 0 ? i : -i);\n\n\ti = -17;\n\tprintf(\"%d\\n\", i >= 0 ? i : -i);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_14.c",
    "content": "/*\n * 利用 switch 语句编写一个程序，把数字显示的成绩转化为字母显示的等级：\n *\n * Enter numerical grade: 84\n * Letter grade: B\n *\n * 使用下面这套等级评定规则：A=90~100，B=80~89，C=70~79，D=60~69，F=0~59。如果成绩\n * 高于100或低于0则显示出错信息。提示：把成绩拆分成2个数字，然后使用 switch 判定十位上的数字。\n *\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint number_grade, g;\n\tchar letter_grade;\n\n\tprintf(\"Enter numerical grade: \");\n\tscanf(\"%d\", &number_grade);\n\n\tif (number_grade < 0 || number_grade > 100) {\n\t\tprintf(\"Invalid grade\\n\");\n\t\treturn 1;\n\t}\n\n\tg = number_grade / 10;\n\n\tswitch (g) {\n\t\tcase 10:\n\t\tcase 9:\n\t\t\tletter_grade = 'A'; break;\n\n\t\tcase 8:\n\t\t\tletter_grade = 'B'; break;\n\n\t\tcase 7:\n\t\t\tletter_grade = 'C'; break;\n\n\t\tcase 6:\n\t\t\tletter_grade = 'D'; break;\n\n\t\tdefault:\n\t\t\tletter_grade = 'F'; break;\n\t}\n\n\tprintf(\"Letter grade: %c\\n\", letter_grade);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_15.c",
    "content": "/*\n * 编写一个程序，要求用户输入一个两位的数，然后显示这个数的英文单词：\n *\n * Enter a two-digit number: 45\n * You entered the number forty-five\n *\n * 提示：把数分解为两个数字。用一个 switch 语句显示第一位数字对应的单词（\"twenty\"、\"thirty\"\n * 等），用第二个 switch 语句显示第二位数字对应的单词。不要忘记11~19的数有特殊的处理要求。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint n1, n2; /* n1 十位，n2 个位 */\n\n\tprintf(\"Enter a two-digit number: \");\n\tscanf(\"%1d%1d\", &n1, &n2);\n\n\tprintf(\"You entered the number \");\n\n\tswitch (n1) {\n\t\tcase 0:\n\t\tcase 1:\n\t\t\tbreak;\n\n\t\tcase 2:\n\t\t\tprintf(\"twenty\"); break;\n\t\tcase 3:\n\t\t\tprintf(\"thirty\"); break;\n\t\tcase 4:\n\t\t\tprintf(\"forty\"); break;\n\t\tcase 5:\n\t\t\tprintf(\"fifty\"); break;\n\t\tcase 6:\n\t\t\tprintf(\"sixty\"); break;\n\t\tcase 7:\n\t\t\tprintf(\"seventy\"); break;\n\t\tcase 8:\n\t\t\tprintf(\"eighty\"); break;\n\t\tcase 9:\n\t\t\tprintf(\"ninety\"); break;\n\t}\n\n\tswitch (n2) {\n\t\tcase 0:\n\t\t\tif (n1 == 1) printf(\"ten\"); break;\n\t\tcase 1:\n\t\t\tif (n1 == 1) printf(\"eleven\"); else printf(\"-one\"); break;\n\t\tcase 2:\n\t\t\tif (n1 == 1) printf(\"twelve\"); else printf(\"-two\"); break;\n\t\tcase 3:\n\t\t\tif (n1 == 1) printf(\"thirteen\"); else printf(\"-three\"); break;\n\t\tcase 4:\n\t\t\tif (n1 == 1) printf(\"fourteen\"); else printf(\"-four\"); break;\n\t\tcase 5:\n\t\t\tif (n1 == 1) printf(\"fifteen\"); else printf(\"-five\"); break;\n\t\tcase 6:\n\t\t\tif (n1 == 1) printf(\"sixteen\"); else printf(\"-six\"); break;\n\t\tcase 7:\n\t\t\tif (n1 == 1) printf(\"seventeen\"); else printf(\"-seven\"); break;\n\t\tcase 8:\n\t\t\tif (n1 == 1) printf(\"eighteen\"); else printf(\"-eight\"); break;\n\t\tcase 9:\n\t\t\tif (n1 == 1) printf(\"nineteen\"); else printf(\"-nine\"); break;\n\t}\n\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch05_选择语句/ex_16.c",
    "content": "/*\n * 请写出下面程序段的输出结果？（假设i是整型变量）\n */\n\n/*\n * onetwo\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i;\n\n\ti = 1;\n\tswitch (i % 3) {\n\t\tcase 0: printf(\"zero\");\n\t\tcase 1: printf(\"one\");\n\t\tcase 2: printf(\"two\");\n\t}\n\n\tputchar(10);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/checking.c",
    "content": "/* Balances a checkbook */\n\n#include <stdio.h>\n\nint main()\n{\n\tint cmd;\n\tfloat balance = 0.0f, credit, debit;\n\n\tprintf(\"*** Checkbook balance program ***\\n\");\n\tprintf(\"Commands: 0=clear, 1=credit, 2=debit, 3=balance, 4=exit\\n\\n\");\n\n\twhile (1)\n\t{\n\t\tprintf(\"Enter command: \");\n\t\tscanf(\"%d\", &cmd);\n\n\t\tswitch (cmd)\n\t\t{\n\t\t\tcase 0:\n\t\t\t\tbalance = 0.0f;\n\t\t\t\tbreak;\n\t\t\tcase 1:\n\t\t\t\tprintf(\"Enter amount of credit: \");\n\t\t\t\tscanf(\"%f\", &credit);\n\t\t\t\tbalance += credit;\n\t\t\t\tbreak;\n\t\t\tcase 2:\n\t\t\t\tprintf(\"Enter amount of debit: \");\n\t\t\t\tscanf(\"%f\", &debit);\n\t\t\t\tbalance -= debit;\n\t\t\t\tbreak;\n\t\t\tcase 3:\n\t\t\t\tprintf(\"Current balance: %.2f\\n\", balance);\n\t\t\t\tbreak;\n\t\t\tcase 4:\n\t\t\t\treturn 0;\n\t\t\tdefault:\n\t\t\t\tprintf(\"Commands: 0=clear, 1=credit, 2=debit, 3=balance, 4=exit\\n\\n\");\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_01.c",
    "content": "/*\n * 编写程序，要求找到用户输入的一串数中的最大数。程序需要提示用户一个一个输入数。当用户输入\n * 0或负数时，程序必须显示输入的最大非负数：\n *\n * Enter a number: 60\n * Enter a number: 100.62\n * Enter a number: 0\n *\n * The largest number entered was 100.62\n *\n * 注意，输入的数不要求一定是整数。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tfloat n, largest;\n\n\tlargest = 0;\n\tdo {\n\t\tprintf(\"Enter a number: \");\n\t\tscanf(\"%f\", &n);\n\n\t\tif (largest < n) largest = n;\n\t\t\n\t} while (n > 0);\n\n\tprintf(\"The largest number entered was %g\\n\", largest);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_02.c",
    "content": "/*\n * 编写程序，要求用户输入两个整数，然后计算并显示这两个整数的最大公约数（GCD）：\n * \n * Enter two integers: 12 28\n * Greatest common divisor: 4\n *\n * 提示：求最大公约数的经典算法是 Euclid 算法，方法如下：分别让变量m和n存储两个数的值；用m除\n * 以n；把除数保存在m中，而把余数保存在n中；如果n为0，那么停止操作，m中的值是GCD；否则，\n * 从m除以n开始，重复上述除法过程。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint m, n, tmp;\n\n\tprintf(\"Enter two integers: \");\n\tscanf(\"%d%d\", &m, &n);\n\n\twhile (n != 0) {\n\t\ttmp = m;\n\t\tm = n;\n\t\tn = tmp % n;\n\t}\n\n\tprintf(\"Greatest common divisor: %d\\n\", m);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_03.c",
    "content": "/*\n * 编写程序，要求用户输入一个分数，然后将其约分为最简分式：\n *\n * Enter a fraction: 6/12\n * In lowest terms: 1/2\n *\n * 提示：为了把分数约分为最简分式，首先计算分子和分母的最大公约数；然后分子和分母分别都除以\n * 最大公约数。\n */ \n\n#include <stdio.h>\n\nint main()\n{\n\tint m, n, tmp, gcd, divisor;\n\n\tprintf(\"Enter a fraction: \");\n\tscanf(\"%d/%d\", &m, &n);\n\n\tgcd = m;\n\tdivisor = n;\n\twhile (divisor != 0) {\n\t\ttmp = gcd;\n\t\tgcd = divisor;\n\t\tdivisor = tmp % divisor;\n\t}\n\n\tprintf(\"In lowest terms: %d/%d\\n\", m/gcd, n/gcd);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_04.c",
    "content": "/*\n * 在5.2节的 broker.c 程序中添加循环以使用户可以输入多比交易，并且程序可以计算每次的佣金。程\n * 序在用户输入的交易额为0时终止。\n *\n * Enter value of trade: 30000\n * Commision: $166.00\n * Enter value of trade: 20000\n * Commision: $144.00\n * Enter value of trade: 0\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tfloat value, commision;\n\n\tprintf(\"Enter value of trade: \");\n\tscanf(\"%f\", &value);\n\n\twhile (value != 0) {\n\t\tif (value < 2500)\n\t\t\tcommision = 30.0f + 0.01 * 0.17 * value;\n\t\telse if (value < 6250)\n\t\t\tcommision = 56.0f + 0.01 * 0.66 * value;\n\t\telse if (value < 20000)\n\t\t\tcommision = 76.0f + 0.01 * 0.34 * value;\n\t\telse if (value < 50000)\n\t\t\tcommision = 100 + 0.01 * 0.22 * value;\n\t\telse if (value < 500000)\n\t\t\tcommision = 155 + 0.01 * 0.11 * value;\n\t\telse\n\t\t\tcommision = 255 + 0.01 * 0.09 * value;\n\n\t\tif (commision < 39)\n\t\t\tcommision = 39;\n\n\t\tprintf(\"The commision is: $%.2f\\n\", commision);\n\n\t\tprintf(\"Enter value of trade: \");\n\t\tscanf(\"%f\", &value);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_05.c",
    "content": "/*\n * 第4章中的练习3要求编写程序可以显示两位数字的反向。设计一个程序可以实现一位、两位、三位或者多位数的反向。提示：使用 do 循环重复除以10，直到值达到0为止。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint n, tmp;\n\n\tprintf(\"Enter a number: \");\n\tscanf(\"%d\", &n);\n\n\tdo {\n\t\ttmp = n - n / 10 * 10;\n\t\tn /= 10;\n\n\t\tprintf(\"%d\", tmp);\n\n\t} while (n);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_06.c",
    "content": "/*\n * 编写程序，要求提示用户输入一个数n，然后显示出1~n的所有偶数平方。例如，如果用户输入10，\n * 那么程序员应该显示出下列内容：\n *\n * 4\n * 16\n * 36\n * 64\n * 100\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint n, i;\n\n\tprintf(\"Enter a number: \");\n\tscanf(\"%d\", &n);\n\n\tfor (i = 2; i <= n; i += 2) {\n\t\tprintf(\"%d\\n\", i * i);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_07.c",
    "content": "/*\n * 重新安排程序 square3.c 以便 for 循环对变量i进行初始化，对变量i进行判定，并且对变量i进行\n * 自增操作。不需要重写程序，特别是不要使用任何乘法。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, n, odd, square;\n\n\tprintf(\"This program prints a table of squares.\\n\");\n\tprintf(\"Enter number of entries in table: \");\n\tscanf(\"%d\", &n);\n\n\todd = 3;\n\tfor (i = 1, square = 1; i <= n; odd += 2, ++i) {\n\t\tprintf(\"%10d%10d\\n\", i, square);\n\t\tsquare += odd;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_08.c",
    "content": "/*\n * 编写程序，要求显示出单月的日历。用户说明这个月的天数和本月起始日是星期几。\n *\n * Enter number of days in month: 31\n * Enter starting day of the week(1=Sun, 7=Sat): 3\n *\n *         1   2   3   4   5\n * 6   7   8   9   10  11  12\n * 13  14  15  16  17  18  19\n * 20  21  22  23  24  25  26\n * 27  28  29  30  31\n *\n * 提示：此程序不像看上去那么难。最重要的内容是 for 语句使用变量i从1计数到n，n是此月的天数，\n * 显示出i的每个值。在循环中，用 if 语句判定i是否是一个星期的最后一天，如果是，就显示一个换\n * 行符。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint days, starting_day, i, j;\n\n\tprintf(\"Enter number of days in month: \");\n\tscanf(\"%d\", &days);\n\n\tprintf(\"Enter starting day of the week(1=Sun, 7=Sat): \");\n\tscanf(\"%d\", &starting_day);\n\n\tprintf(\"Sun\\tMon\\tTues\\tWed\\tThur\\tFri\\tSat\\n\");\n\t\n\tfor (i = 1; i < starting_day; ++i)\n\t\tprintf(\"\\t\");\n\n\n\tfor (j = 1; j <= days; ++j, ++i) {\n\t\tprintf(\"%d\\t\", j);\n\n\t\tif (i >= 7) {\n\t\t\tprintf(\"\\n\");\n\t\t\ti = 0;\n\t\t}\n\t}\n\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_09.c",
    "content": "/*\n * 下面这条 for 语句的输出是什么？\n *\n * for (i = 5, j = i - 1; i > 0, j > 0; --i, j = i - 1)\n * \tprintf(\"%d \", i);\n */\n\n/*\n * r1: i = 5, j = 4 -> 5\n * r2: i = 4, j = 3 -> 4\n * r3: i = 3, j = 2 -> 3\n * r4: i = 2, j = 1 -> 2\n * r5: i = 1, j = 0\n *\n * result: 5 4 3 2\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j;\n\tfor (i = 5, j = i - 1; i > 0, j > 0; --i, j = i - 1)\n\t\tprintf(\"%d \", i);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_10.md",
    "content": "下列哪条语句和其他两条语句不等价（假设循环体都是一样的）？\n\n(a) for (i = 0; i < 10; i++) ...\n\n(b) for (i = 0; i < 10; ++i) ...\n\n(c) for (i = 0; i++ < 10; ) ...\n\n---\n\nc与a、b不等价。\n\na、b都是等循环体执行完毕后，对i做自增操作。而c是在循环体进入前就已经自增了。\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_11.md",
    "content": "下列哪条语句和其他两条语句不等价（假设循环体都是一样的）？\n\n(a) while (i < 10) {...}\n\n(b) for (; i < 10;) {...}\n\n(c) do {...} while (i < 10);\n\n---\n\nc与a、b不等价，因为c会首先执行一次循环体，然后再判断条件表达式。a和b是先判断条件表达式，然后才会执行循环体。\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_12.c",
    "content": "/*\n * 显示如何用等价的 goto 语句替换 continue 语句。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\t/* 输出10以内的奇数 */\n\tint i;\n\n\tfor (i = 0; i < 10; ++i) {\n\t\tif (i % 2 == 0) goto end_of_loop;\n\n\t\tprintf(\"%d \", i);\n\n\t\tend_of_loop:;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_13.c",
    "content": "/*\n * 下面的程序段产生的输出是什么？\n *\n * sum = 0;\n * for (i = 0; i < 10; ++i) {\n * \tif (i % 2) continue;\n * \tsum += i;\n * }\n * printf(\"%d\\n\", sum);\n */\n\n/*\n * 将10以内的所有偶数加起来的结果，2 + 4 + 6 + 8 = 20\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, sum;\n\n\tsum = 0;\n\tfor (i = 0; i < 10; ++i) {\n\t\tif (i % 2) continue;\n\t\tsum += i;\n\t}\n\tprintf(\"%d\\n\", sum);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_14.c",
    "content": "/*\n * 下面的“素数判定”循环作为示例出现在6.4节中：\n *\n * for (d = 2; d < n; d++)\n * \tif (n % d == 0) break;\n *\n * 这个循环不是很有效。用n除以2~n-1所有数的方法来判断它是否为素数是没有必要的。事实上，只\n * 需要检查不大于n的平方根的除数。利用这一点来修改循环。提示：不要试图计算出n的平方根，而\n * 是用d*d和n进行比较。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint n;\n\tint d;\n\n\tprintf(\"Enter a number: \");\n\tscanf(\"%d\", &n);\n\n\tfor (d = 2; d * d < n; ++d)\n\t\tif (n % d == 0) goto done;\n\ndone:\n\tif (d * d < n)\n\t\tprintf(\"%d is divisible by %d\\n\", n, d);\n\telse\n\t\tprintf(\"%d is prime\\n\", n);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_15.c",
    "content": "/*\n * 重写下面的循环，从而使其循环体为空。\n *\n * for (n = 0; m > 0; n++)\n * \tm /= 2;\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint n, m;\n\n\tm = 8;\n\tfor (n = 0; m > 0; m /= 2, n++);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/ex_16.md",
    "content": "找出下面程序段的错误并且修正它。\n\n```c\nif (n % 2 == 0);\n\tprintf(\"n is even\\n\");\n```\n\n---\n\nif 的条件判断后不应该是一个分号。正确的应该是：\n\n```c\nif (n % 2 == 0)\n\tprintf(\"%d is even\\n\", n);\n```\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/example_goto.c",
    "content": "/*\n * goto 语句（p71）\n */\n\n#include <stdio.h>\n\n/* 输入一个值，判断它是否是质数 */\nint main()\n{\n\tint n;\n\tint d;\n\n\tprintf(\"Enter a number: \");\n\tscanf(\"%d\", &n);\n\n\tfor (d = 2; d < n; ++d)\n\t\tif (n % d == 0) goto done;\n\ndone:\n\tif (d < n)\n\t\tprintf(\"%d is divisible by %d\\n\", n, d);\n\telse\n\t\tprintf(\"%d is prime\\n\", n);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/numdigit.c",
    "content": "/* Calculates the number of digits in an integer */\n\n#include <stdio.h>\n\nint main()\n{\n\tint digits = 0, n;\n\n\tprintf(\"Enter a nonnegative integer: \");\n\tscanf(\"%d\", &n);\n\n\tdo\n\t{\n\t\tn /= 10;\n\t\t++digits;\n\t} while (n > 0);\n\n\tprintf(\"The number has %d digit(s).\\n\", digits);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/square.c",
    "content": "/* Prints a table of squares using a while statement */\n\n#include <stdio.h>\n\nint main(void)\n{\n\tint i, n;\n\n\tprintf(\"This program prints a table of squares.\\n\");\n\tprintf(\"Enter number of entries in table: \");\n\tscanf(\"%d\", &n);\n\n\ti = 1;\n\twhile (i <= n)\n\t{\n\t\tprintf(\"%10d%10d\\n\", i, i * i);\n\n\t\t++i;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/square2.c",
    "content": "/* Prints a table of squares using a for statement */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, n;\n\n\tprintf(\"This program prints a table of squares.\\n\");\n\tprintf(\"Enter number of entries in table: \");\n\tscanf(\"%d\", &n);\n\n\tfor (i = 1; i <= n; ++i)\n\t\tprintf(\"%10d%10d\\n\", i, i * i);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/square3.c",
    "content": "/* Prints a table of squares using an odd method */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, n, odd, square;\n\n\tprintf(\"This program prints a table of squares.\\n\");\n\tprintf(\"Enter number of entries in table: \");\n\tscanf(\"%d\", &n);\n\n\ti = 1;\n\todd = 3;\n\tfor (square = 1; i <= n; odd += 2) {\n\t\tprintf(\"%10d%10d\\n\", i, square);\n\t\t++i;\n\t\tsquare += odd;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch06_循环/sum.c",
    "content": "/* Sums a serias of numbers */\n\n#include <stdio.h>\n\nint main()\n{\n\tint n, sum = 0;\n\n\tprintf(\"This program sums a serias of integers.\\n\");\n\tprintf(\"Enter integers(0 to terminate): \");\n\n\tscanf(\"%d\", &n);\n\twhile (n != 0)\n\t{\n\t\tsum += n;\n\t\tscanf(\"%d\", &n);\n\t}\n\n\tprintf(\"The sum is: %d\\n\", sum);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_01.c",
    "content": "/*\n * 请给出下列整型常量的十进制数值。\n *\n * (a) 077\n * (b) 0x77\n * (c) 0XABC\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tprintf(\"%d\\n\", 077); /* 63 */\n\tprintf(\"%d\\n\", 0x77); /* 119 */\n\tprintf(\"%d\\n\", 0XABC); /* 2748 */\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_02.c",
    "content": "/*\n * 如果i*i超出了 int 型的最大取值，那么6.3节的程序 square2.c 将失败（通常会显示奇怪的答案）。\n * 运行程序，并且确定导致失败的n的最小值。尝试把变量i的类型改成 short int 类型，并且再次\n * 运行程序。（不要忘记更新 printf 函数调用中的转换说明！）然后尝试改换成 long int 类型。从这\n * 些实验中，你能总结出在你的机器上用于存储整型的位数的多少吗？\n */\n\n/*\n * int 32位\n * short int 16位\n * long int 64位\n *\n * PS: 不太好用实验的方法总结出来，最好的方式是直接用 sizeof 运算符确定\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, n;\n\tlong int check;\n\n\tprintf(\"This program prints a table of squares.\\n\");\n\tprintf(\"Enter number of entries in table: \");\n\tscanf(\"%d\", &n);\n\n\tfor (i = 1; i <= n; ++i) {\n\t\tcheck = i;\n\t\tif (i * i != check * check) {\n\t\t\tprintf(\"hit invalid i: %d\\n\", i);\n\t\t\tbreak;\n\t\t}\n\n\t\tprintf(\"%10d%10d\\n\", i, i * i);\n\t}\n\n\tprintf(\"bit count of int: %lu\\n\", sizeof(int) * 8);\n\tprintf(\"bit count of short int: %lu\\n\", sizeof(short int) * 8);\n\tprintf(\"bit count of long int: %lu\\n\", sizeof(long int) * 8);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_03.c",
    "content": "/*\n * 下列哪些在C语言中不是合法的数？区分每一个合法的数是整数还是浮点数。\n *\n * (a) 010E2\n * (b) 32.1E+5\n * (c) 0790\n * (d) 100_000\n * (e) 3.978e-2\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i;\n\tdouble d;\n\n\td = 010E2; /* 浮点数 */\n\n\td = 32.1E+5; /* 浮点数 */\n\n\t/* i = 0790;  错误的八进制数，因9不存在于八进制中 */\n\n\t/* i = 100_000; 错误的书写方式 */\n\n\td = 3.978e-2; /* 浮点数 */\n\n\t/* ignore no-use warning */\n\t(void)i;\n\t(void)d;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_04.c",
    "content": "/*\n * 下列哪些在C语言中不是合法的类型？\n *\n * (a) short unsigned int\n * (b) short float\n * (c) long double\n * (d) unsigned long\n */\n\nint main()\n{\n\tshort unsigned int sui; (void)sui;\n\n\t/* short float sf;  不合法的 */\n\n\tlong double ld; (void)ld;\n\n\tunsigned long ul; (void)ul;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_05.c",
    "content": "/*\n * 修改程序 sum2.c 以便可以进行一串 double 型值的求和计算。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tdouble n, sum = 0;\n\n\tprintf(\"This program sums a series of numbers.\\n\");\n\tprintf(\"Enter numbers (0 to terminate): \");\n\n\tscanf(\"%lf\", &n);\n\twhile (n != 0) {\n\t\tsum += n;\n\t\tscanf(\"%lf\", &n);\n\t}\n\tprintf(\"The sum is: %f\\n\", sum);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_06.c",
    "content": "/*\n * 如果变量c是char类型，那么下列哪条语句是非法的？\n *\n * (a) i += c;\n * (b) c = 2 * c - 1;\n * (c) putchar(c);\n * (d) printf(c);\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tchar c;\n\tint i;\n\ti = 0;\n\tc = 0;\n\n\ti += c;\n\tc = 2 * c - 1;\n\tputchar(c);\n\t/* printf(c); */\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_07.md",
    "content": "下列哪条在书写数65上不是合法的方式？（假设字符集是ASCII）\n\n(a) 'A'\n\n(b) 0b1000001\n\n(c) 0101\n\n(d) 0x41\n\n---\n\n(b) 不是\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_08.c",
    "content": "/*\n * 修改6.3节的程序 square2.c 以便它在每24次平方后暂停并且显示下列信息：\n * Press Enter to continue...\n */\n\n/* Prints a table of squares using a for statement */\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, n;\n\tint ch;\n\n\tprintf(\"This program prints a table of squares.\\n\");\n\tprintf(\"Enter number of entries in table: \");\n\tscanf(\"%d\", &n);\n\tgetchar(); /* skip enter character */\n\n\tfor (i = 1; i <= n; ++i) {\n\t\tprintf(\"%10d%10d\\n\", i, i * i);\n\n\t\tif (i % 24 == 0) {\n\t\t\tdo {\n\t\t\t\tprintf(\"Press Enter to continue...\");\n\t\t\t\tch = getchar();\n\t\t\t} while (ch != 10) ;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_09.c",
    "content": "/*\n * 编写程序可以把字母格式的电话号码翻译成数值格式：\n *\n * Enter phone number: CALLATT\n * 2255288\n *\n * （万一没有电话在身边，后面有字母在键盘上的对应关系：2=ABC, 3=DEF, 4=GHI, 5=JKL, 6=MNO,\n * 7=PRS, 8=TUV, 9=WXY。）如果原始的电话号码包含非字母的字符（例如，数字或标点符号），那\n * 么保留下来不做变化：\n *\n * Enter phone number: 1-900-COL-LECT\n * 1-800-265-5328\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint n;\n\n\tprintf(\"Enter phone number: \");\n\n\twhile ((n = getchar()) != '\\n') {\n\t\tif (!isalpha(n)) {\n\t\t\tputchar(n);\n\t\t\tcontinue;\n\t\t}\n\n\t\tn = toupper(n);\n\t\tif (n == 'A' || n == 'B' || n == 'C') putchar('2');\n\t\telse if (n == 'D' || n == 'E' || n == 'F') putchar('3');\n\t\telse if (n == 'G' || n == 'H' || n == 'I') putchar('4');\n\t\telse if (n == 'J' || n == 'K' || n == 'L') putchar('5');\n\t\telse if (n == 'M' || n == 'N' || n == 'O') putchar('6');\n\t\telse if (n == 'P' || n == 'R' || n == 'S') putchar('7');\n\t\telse if (n == 'T' || n == 'U' || n == 'V') putchar('8');\n\t\telse if (n == 'W' || n == 'X' || n == 'Y') putchar('9');\n\t}\n\n\tputchar(10);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_10.c",
    "content": "/*\n * 在十字拼字游戏中，玩家利用小卡片组成单词，每个卡片包含字母和面值。面值根据字母的不同而\n * 不同，也就是说面值是基于字母变化的。（面值有：1-AEILNORSTU, 2-DG, 3-BCMP, 4-FHVWY, 5-K\n * , 8-JX, 10-QZ。）编写程序通过对字母对应的面值求和来计算单词的值：\n *\n * Enter a word: pitfall\n * Scrabble value: 12\n *\n * 编写的程序应该允许单词中混合出现大小写字母。提示：使用 toupper 库函数。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint ch;\n\tint value;\n\n\tprintf(\"Enter a word: \");\n\n\tvalue = 0;\n\twhile ((ch = getchar()) != '\\n') {\n\t\tch = toupper(ch);\n\n\t\tswitch (ch) {\n\t\t\tcase 'A':\n\t\t\tcase 'E':\n\t\t\tcase 'I':\n\t\t\tcase 'L':\n\t\t\tcase 'N':\n\t\t\tcase 'O':\n\t\t\tcase 'R':\n\t\t\tcase 'S':\n\t\t\tcase 'T':\n\t\t\tcase 'U':\n\t\t\t\tvalue += 1;\n\t\t\t\tbreak;\n\n\t\t\tcase 'D':\n\t\t\tcase 'G':\n\t\t\t\tvalue += 2;\n\t\t\t\tbreak;\n\n\t\t\tcase 'B':\n\t\t\tcase 'C':\n\t\t\tcase 'M':\n\t\t\tcase 'P':\n\t\t\t\tvalue += 3;\n\t\t\t\tbreak;\n\n\t\t\tcase 'F':\n\t\t\tcase 'H':\n\t\t\tcase 'V':\n\t\t\tcase 'W':\n\t\t\tcase 'Y':\n\t\t\t\tvalue += 4;\n\t\t\t\tbreak;\n\n\t\t\tcase 'K':\n\t\t\t\tvalue += 5;\n\t\t\t\tbreak;\n\n\t\t\tcase 'J':\n\t\t\tcase 'X':\n\t\t\t\tvalue += 8;\n\t\t\t\tbreak;\n\n\t\t\tcase 'Q':\n\t\t\tcase 'Z':\n\t\t\t\tvalue += 10;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tprintf(\"Scrabble value: %d\\n\", value);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_11.c",
    "content": "/*\n * 飞机票有冗长的标识数字，例如47715497443。为了有效，最后一位数字必须与其他位的数字为整体\n * 除以7后的余数相匹配。（例如，47715497443除以7的余数为3。）编写程序检查机票号是否有效：\n *\n * Enter ticket number: 47715497443\n * VALID\n *\n * 提示：不要试图在单步操作中读取数，而是使用 getchar 函数逐个获取数字。一次执行一个数字的除法，\n * 小心除法中不要包含最后一位数字。\n */\n\n/*\n * PS: 提示的意思是，为了防止整型数值溢出。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint ch;\n\tint digit;\t\t\t/* 当前处理的数字 0~9 */\n\tint check_number;\t/* 当前判断的数字，超过了7则使用一次取余运算 */\n\tint tmp;\n\n\tprintf(\"Enter ticket number: \");\n\t\n\twhile ((ch = getchar()) != '\\n') {\n\t\tdigit = ch - '0';\n\n\t\ttmp = check_number * 10 + digit;\n\t\tcheck_number = tmp >= 7 ? tmp % 7 : tmp;\n\t}\n\n\tcheck_number = (tmp - digit) / 10;\n\tif (check_number == digit)\n\t\tprintf(\"VALID\\n\");\n\telse\n\t\tprintf(\"INVALID\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_12.c",
    "content": "/*\n * 编写程序显示 sizeof(int) 、 sizeof(short int) 、 sizeof(long int) 、 sizeof(float) 、\n * sizeof(double) 和 sizeof(long double) 的值。\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tprintf(\"sizeof(int): %lu\\n\", sizeof(int));\n\tprintf(\"sizeof(short): %lu\\n\", sizeof(short));\n\tprintf(\"sizeof(long int): %lu\\n\", sizeof(long int));\n\tprintf(\"sizeof(float): %lu\\n\", sizeof(float));\n\tprintf(\"sizeof(double): %lu\\n\", sizeof(double));\n\tprintf(\"sizeof(long double): %lu\\n\", sizeof(long double));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_13.md",
    "content": "假设变量i和变量j都是 int 类型，那么表达式 i / j + 'a' 是什么类型？\n\n---\n\n是 int 类型。\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_15.md",
    "content": "假设变量i是 int 类型，变量f是 float 类型，并且变量d是 double 类型，那么表达式 `i * f / d` 是什么类型？\n\n---\n\ndouble\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_16.md",
    "content": "假设变量i是 int 类型，变量f是 float 类型，并且变量d是 double 类型，请解释在执行下列语句时发生了什么转换？\n\n```c\nd = i + f;\n```\n\n---\n\ni + f，将 int 转换成 float，然后将这个表达式的结果从 float 转换成 double 。\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_17.c",
    "content": "/*\n * 假设程序包含下列声明：\n *\n * char c = '\\1';\n * short int s = 2;\n * int i = -3;\n * long int m = 5;\n * float f = 6.5;\n * double d = 7.5;\n *\n * 请给出下列每个表达式的值和类型。\n *\n * (a) c * i\n * (b) s + m\n * (c) f / c\n * (d) d / s\n * (e) f - d\n * (f) (int)f\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tchar c = '\\1';\n\tshort int s = 2;\n\tint i = -3;\n\tlong int m = 5;\n\tfloat f = 6.5;\n\tdouble d = 7.5;\n\n\tprintf(\"%d\\n\", c * i);\t/* int, -3\t\t\t\t*/\n\tprintf(\"%ld\\n\", s + m);\t/* long int, 7 \t\t\t*/\n\tprintf(\"%f\\n\", f / c);\t/* float, 6.5\t\t\t*/\n\tprintf(\"%f\\n\", d / s);\t/* double, 3.75\t\t\t*/\n\tprintf(\"%f\\n\", f - d);\t/* double, -1.0\t\t\t*/\n\tprintf(\"%d\\n\", (int)f);\t/* int, 6\t\t\t\t*/\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_18.md",
    "content": "下列语句是否始终可以正确地计算出f的小数部分（假设f和 frac_part 都是 float 型的变量）？\n\n```c\nfrac_part = f - (int)f;\n```\n\n如果不是，那么出了什么问题？\n\n---\n\n可能不会正确计算，当f超出了 int 的取值范围。\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/ex_19.c",
    "content": "/*\n * 使用 typedef 产生名为 Int8 、 Int16 和 Int32 的类型。定义这些类型以便它们可以在你的机器上分\n * 别表示8位、16位和32位的整数。\n */\n\n/*\n * PS，我的机器架构是：x86_64\n */\n\n#include <stdio.h>\n\ntypedef char Int8;\ntypedef short Int16;\ntypedef int Int32;\n\nint main()\n{\n\tprintf(\"bits of Int8: %lu\\n\", 8 * sizeof(Int8));\n\tprintf(\"bits of Int16: %lu\\n\", 8 * sizeof(Int16));\n\tprintf(\"bits of Int32: %lu\\n\", 8 * sizeof(Int32));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/length.c",
    "content": "/* Determins the length of a message */\n\n#include <stdio.h>\n\n// 把输入行的剩余字符读完，返回读入的字符数，不包括\\n\nint skips_rest_line()\n{\n\tint length = 0;\n\twhile (getchar() != '\\n')\n\t{\n\t\t++ length;\n\t}\n\n\treturn length;\n}\n\nint main()\n{\n\tprintf(\"Enter a message: \");\n\tint length = skips_rest_line();\n\n\tprintf(\"Your message was %d character(s) long.\\n\", length);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/mytoupper.c",
    "content": "/* 字符小写转大写 */\n\n#include <stdio.h>\n\nchar my_toupper(char c)\n{\n\tif (c >= 'a' && c <= 'z')\n\t{\n\t\tc = c - 'a' + 'A';\n\t}\n\n\treturn c;\n}\n\nint main()\n{\n\tchar c;\n\n\tc = 'h';\n\tc = my_toupper(c);\n\n\tprintf(\"c = %c\\n\", c);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/scanf_char.c",
    "content": "/* 测试读入字符，且不要读入前面的空格 */\n\n#include <stdio.h>\n\nint main()\n{\n\tchar a, b;\n\n\tscanf(\"%c %c\", &a, &b); // a和b之间可以输入任意个空格\n\t// scanf(\"%c%c\", &a, &b); // 这样是不行的，b可以读取空格\n\n\tprintf(\"%c, %c\\n\", a, b);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/sum2.c",
    "content": "/*\n * 数列求和（改进版）（p81）\n */\n\n/* Sums a series of numbers (using long int variabels) */\n\n#include <stdio.h>\n\nint main()\n{\n\tlong int n, sum = 0;\n\n\tprintf(\"This program sums a series of integers.\\n\");\n\tprintf(\"Enter integers (0 to terminate): \");\n\n\tscanf(\"%ld\", &n);\n\twhile (n != 0) {\n\t\tsum += n;\n\t\tscanf(\"%ld\", &n);\n\t}\n\tprintf(\"The sum is: %ld\\n\", sum);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch07_基本类型/typedef_sizeof.c",
    "content": "/* 测试typedef和sizeof */\n\n#include <stdio.h>\n\ntypedef char Name[32];\n\nint main()\n{\n\tName name;\n\n\tprintf(\"sizeof name: %lu\\n\", sizeof(name));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/deal.c",
    "content": "/* Deals a random hand of cards. */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <time.h>\n#include <string.h>\n\n#define NUM_SUITS 4\n#define NUM_RANKS 13\n\nint main()\n{\n\tint inhand_cards[NUM_SUITS][NUM_RANKS];\n\tchar suit_list[NUM_SUITS] = {'c', 'd', 'h', 's'};\n\tchar rank_list[NUM_RANKS] = {'A', '2', '3', '4', '5', '6', '7', '8', '9', 't', 'J', 'Q', 'K'};\n\tint need_cards;\n\tint rank, suit;\n\n\tprintf(\"Enter number of cards in hand: \");\n\tscanf(\"%d\", &need_cards);\n\n\tprintf(\"Your hand:\");\n\n\tmemset(inhand_cards, 0, sizeof(inhand_cards));\n\n\tsrand((unsigned int)time(NULL));\n\twhile (need_cards > 0)\n\t{\n\t\tsuit = rand() % NUM_SUITS;\n\t\trank = rand() % NUM_RANKS;\n\t\tif (inhand_cards[suit][rank] == 0)\n\t\t{\n\t\t\tinhand_cards[suit][rank] = 1;\n\t\t\t-- need_cards;\n\n\t\t\tprintf(\" %c%c\", suit_list[suit], rank_list[rank]);\n\t\t}\n\t}\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_01.c",
    "content": "/*\n * 修改程序 repdigit.c ，要求修改后的程序可以显示出重复的数字（如果有的话）：\n *\n * Enter a number: 939577\n * Repeated digit(s): 7 9\n */\n\n/*\n * 程序：检查数中重复出现的数字（p101）\n *\n * Enter a number: 28212\n * Repeated digit\n */\n\n/* Checks numbers for repeated digits */\n\n#include <stdio.h>\n\ntypedef int Bool;\n\nint main()\n{\n\tint digit_seen_times[10] = {0};\n\tint digit;\n\tlong int n;\n\tint i;\n\n\tprintf(\"Enter a number: \");\n\tscanf(\"%ld\", &n);\n\n\twhile (n > 0) {\n\t\tdigit = n % 10;\n\t\t++digit_seen_times[digit];\n\t\tn /= 10;\n\t}\n\n\tprintf(\"Repeated digit(s):\");\n\tfor (i = 0; i < 10; i++) {\n\t\tif (digit_seen_times[i] > 1)\n\t\t\tprintf(\" %d\", i);\n\t}\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_02.c",
    "content": "/*\n * 修改程序 repdigit.c ，要求修改后的程序可以显示出一张列表，表内显示出每种数字在数中出现的\n * 次数：\n *\n * Enter a number: 41271092\n * Digit:\t\t0 1 2 3 4 5 6 7 8 9\n * Occurrences: 1 2 2 0 1 0 0 1 0 1\n */\n\n/*\n * 程序：检查数中重复出现的数字（p101）\n *\n * Enter a number: 28212\n * Repeated digit\n */\n\n/* Checks numbers for repeated digits */\n\n#include <stdio.h>\n\ntypedef int Bool;\n\nint main()\n{\n\tint digit_seen_times[10] = {0};\n\tint digit;\n\tlong int n;\n\tint i;\n\n\tprintf(\"Enter a number: \");\n\tscanf(\"%ld\", &n);\n\n\twhile (n > 0) {\n\t\tdigit = n % 10;\n\t\t++digit_seen_times[digit];\n\t\tn /= 10;\n\t}\n\n\tprintf(\"Digit:\\t\\t\");\n\tfor (i = 0; i < 10; i++)\n\t\tprintf(\" %d\", i);\n\tprintf(\"\\n\");\n\n\tprintf(\"Occurrences:\\t\");\n\tfor (i = 0; i < 10; i++) {\n\t\tprintf(\" %d\", digit_seen_times[i]);\n\t}\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_03.c",
    "content": "/*\n * 修改程序 repdigit.c，要求修改后的程序可以让用户录入多于一个的数进行重复数字的判断。当用\n * 户录入的数小于或等于0时，程序终止。\n */\n\n/*\n * 程序：检查数中重复出现的数字（p101）\n *\n * Enter a number: 28212\n * Repeated digit\n */\n\n/* Checks numbers for repeated digits */\n\n#include <stdio.h>\n\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Bool;\n\nint main()\n{\n\tBool digit_seen[10] = {0};\n\tint digit;\n\tint i;\n\tlong int n, tmp_n;\n\n\tprintf(\"Enter a number: \");\n\tscanf(\"%ld\", &n);\n\ttmp_n = n;\n\t\n\twhile (tmp_n > 0) {\n\t\t\n\t\tfor (i = 0; i < 10; i++)\n\t\t\tdigit_seen[i] = FALSE;\n\n\t\twhile (n > 0) {\n\t\t\tdigit = n % 10;\n\t\t\tif (digit_seen[digit])\n\t\t\t\tbreak;\n\t\t\tdigit_seen[digit] = TRUE;\n\t\t\tn /= 10;\n\t\t}\n\n\t\tif (n > 0)\n\t\t\tprintf(\"Repeated digit\\n\\n\");\n\t\telse\n\t\t\tprintf(\"No repeated digit\\n\\n\");\n\n\t\tprintf(\"Enter a number: \");\n\t\tscanf(\"%ld\", &n);\n\t\ttmp_n = n;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_04.md",
    "content": "已经讨论过利用表达式`sizeof(a) / sizeof(a[0])`进行数组元素个数的计算。表达式`sizeof(a)/sizeof(t)`也可以完成同样的工作，其中t表示数组a中元素的类型，但是这种方法被认为是一种较差的技术。这是为什么呢？\n\n---\n\n可能是因为这样就定义了一个不是很通用的宏。\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_05.c",
    "content": "/*\n * 修改程序 reverse.c ，利用表达式 sizeof(a) / sizeof(a[0]) （或者这个值的宏）来计算数组\n * 的长度。\n */\n\n/*\n * 程序：数列反向（p100）\n *\n * Enter 10 numbers: 34 82 49 102 7 94 23 11 50 31\n * In reverse order: 31 50 11 23 94 7 102 49 82 34\n */\n\n/* Reverses a series of numbers */\n\n#include <stdio.h>\n\n#define N 10\n#define ARR_LEN (sizeof(a) / sizeof(a[0]))\n\nint main()\n{\n\tint a[N], i;\n\n\tprintf(\"Enter %lu numbers: \", ARR_LEN);\n\tfor (i = 0; i < ARR_LEN; ++i)\n\t\tscanf(\"%d\", &a[i]);\n\n\tprintf(\"In reverse order:\");\n\tfor (i = ARR_LEN - 1; i >= 0; i--)\n\t\tprintf(\" %d\", a[i]);\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_06.c",
    "content": "/*\n * 修改程序 interest.c ，使得修改后的程序可以每月整合一次利息，而不再是每年整合一次利息。程\n * 序的输出格式不变；余额应该始终按每年一次的间隔显示。\n */\n\n/* Print a table of compound interest */\n\n#include <stdio.h>\n\n#define ARRAY_ITEM_COUNT(a) ((int)(sizeof(a) / sizeof(a[0])))\n#define INITIAL_BALANCE 100.00\n\nint main()\n{\n\tint i, low_rate, num_years, year, month;\n\tdouble value[8];\n\n\tprintf(\"Enter interest rate: \");\n\tscanf(\"%d\", &low_rate);\n\tprintf(\"Enter number of years: \");\n\tscanf(\"%d\", &num_years);\n\n\tprintf(\"\\nYears\");\n\tfor (i = 0; i < ARRAY_ITEM_COUNT(value); ++i)\n\t{\n\t\tprintf(\"\\t%d%%\", low_rate + i);\n\t\tvalue[i] = INITIAL_BALANCE;\n\t}\n\tprintf(\"\\n\");\n\n\tfor (year = 1; year <= num_years; ++year)\n\t{\n\t\tprintf(\"%d\", year);\n\t\tfor (i = 0; i < ARRAY_ITEM_COUNT(value); ++i)\n\t\t{\n\t\t\tfor (month = 1; month <= 12; ++month) {\n\t\t\t\tvalue[i] += (low_rate + i) / 100.0 * value[i];\n\t\t\t}\n\t\t\t\n\t\t\tprintf(\"\\t%.2f\", value[i]);\n\t\t}\n\t\tprintf(\"\\n\");\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_07.c",
    "content": "/*\n * 在线运动的名人之一是一个称为 BIFF 的家伙，他在编写消息上有独一无二的方法。下面是一条典\n * 型的 BIFF 公告：\n *\n * H3Y DUD3, C 15 R1LLY C00L!!!!!!!!!!\n *\n * 编写一个“ BIFF 过滤器”，它可以读取用户录入的消息并且把此消息翻译成 BIFF 的表达风格：\n *\n * Enter message: Hey dude, C is rilly cool\n * In BIFF-speak: H3Y DUD3, C 15 R1LLY C00L!!!!!!!!!!\n *\n * 程序需要把消息转换成大写字母，用数字代替特定的字母（A->4, B->8, E->3, I->1, O->0, S->5），然\n * 后添加10个感叹号。提示：在字符数组中存储原始消息，然后从数组头开始逐个翻译并且显示字符。\n */\n\n#include <stdio.h>\n#include <ctype.h>\n\n#define MESSAGE_LEN 32\n\nint main()\n{\n\tchar message[MESSAGE_LEN + 1];\n\tchar word;\n\tint i, word_cnt;\n\n\tword_cnt = 0;\n\n\tprintf(\"Enter message: \");\n\twhile ((word = getchar()) != '\\n') {\n\t\tmessage[word_cnt] = toupper(word);\n\t\t++word_cnt;\n\n\t\tif (word_cnt >= MESSAGE_LEN) break;\n\t}\n\tmessage[word_cnt] = '\\0';\n\n\tfor (i = 0; i < word_cnt; ++i) {\n\t\tswitch (message[i]) {\n\t\t\tcase 'A' : message[i] = '4'; break;\n\t\t\tcase 'B' : message[i] = '8'; break;\n\t\t\tcase 'E' : message[i] = '3'; break;\n\t\t\tcase 'I' : message[i] = '1'; break;\n\t\t\tcase 'O' : message[i] = '0'; break;\n\t\t\tcase 'S' : message[i] = '5'; break;\n\t\t}\n\t}\n\n\tprintf(\"In BIFF-speak: %s!!!!!!!!!!\\n\", message);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_08.md",
    "content": "“问与答”小节介绍了使用字母作为数组下标的方法。请描述一下如何使用数字（字符格式的）作为数组的下标。\n\n---\n\n用字符格式的数字减去`'0'`。\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_09.c",
    "content": "/*\n * 计算器、手表和其他电子设备经常依靠七段显示器进行数值的输出。为了组成数字，这类设备需要“打\n * 开”7个显示段中的某些部分，同时还需要“关闭”七个显示段中的其他部分：\n *\n * 。。。\n *\n *   0\n *   -\n * 5| |1\n *   - 6\n * 4| |2\n *   -\n *   3\n *\n * （后续描述过于复杂，不书写于此）\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tconst int segments[10][7] = {\n\t\t{1, 1, 1, 1, 1, 1, 0},\n\t\t{0, 1, 1, 0, 0, 0, 0},\n\t\t{1, 1, 0, 1, 1, 0, 1},\n\t\t{1, 1, 1, 1, 0, 0, 1},\n\t\t{0, 1, 2, 0, 0, 1, 1},\n\t\t{1, 0, 1, 1, 0, 1, 1},\n\t\t{1, 0, 1, 1, 1, 1, 1},\n\t\t{1, 1, 1, 0, 0, 0, 0},\n\t\t{1, 1, 1, 1, 1, 1, 1},\n\t\t{1, 1, 1, 1, 0, 1, 1}\n\t};\n\n\t(void)segments;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_10.c",
    "content": "/*\n * 利用8.2节的简洁描述对数组 segments （练习9中）的初始化式尽可能地进行化简。\n */\n\n/*\n * 计算器、手表和其他电子设备经常依靠七段显示器进行数值的输出。为了组成数字，这类设备需要“打\n * 开”7个显示段中的某些部分，同时还需要“关闭”七个显示段中的其他部分：\n *\n * 。。。\n *\n *   0\n *   -\n * 5| |1\n *   - 6\n * 4| |2\n *   -\n *   3\n *\n * （后续描述过于复杂，不书写于此）\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tconst int segments[10][7] = {\n\t\t{1, 1, 1, 1, 1, 1, 0},\n\t\t{0, 1, 1},\n\t\t{1, 1, 0, 1, 1, 0, 1},\n\t\t{1, 1, 1, 1, 0, 0, 1},\n\t\t{0, 1, 2, 0, 0, 1, 1},\n\t\t{1, 0, 1, 1, 0, 1, 1},\n\t\t{1, 0, 1, 1, 1, 1, 1},\n\t\t{1, 1, 1},\n\t\t{1, 1, 1, 1, 1, 1, 1},\n\t\t{1, 1, 1, 1, 0, 1, 1}\n\t};\n\n\t(void)segments;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_11.c",
    "content": "/*\n * 编写程序，要求此程序可以用来读取一个 5*5 的整数数组，然后显示出每行的求和结果和每列的\n * 求和结果。\n *\n * Enter row 1: 8 3 9 0 10\n * Enter row 2: 3 5 17 1 1\n * Enter row 3: 2 8 6 23 1\n * Enter row 4: 15 7 3 2 9\n * Enter row 5: 6 14 2 6 0\n *\n * Row totals: 30 27 40 36 28\n * Column totals: 34 37 37 32 21\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint arr[5][5];\n\tint i, j;\n\tint sum;\n\n\tfor (i = 0; i < 5; i++) {\n\t\tprintf(\"Enter row %d: \", i + 1);\n\t\tfor (j = 0; j < 5; j++) {\n\t\t\tscanf(\"%d\", &arr[i][j]);\n\t\t}\n\t}\n\n\tprintf(\"Row totals:\");\n\tfor (i = 0; i < 5; i++) {\n\t\tsum = 0;\n\t\tfor (j = 0; j < 5; j++) {\n\t\t\tsum += arr[i][j];\n\t\t}\n\t\tprintf(\" %d\", sum);\n\t}\n\tprintf(\"\\n\");\n\n\tprintf(\"Column totals:\");\n\tfor (j = 0; j < 5; j++) {\n\t\tsum = 0;\n\t\tfor (i = 0; i < 5; ++i) {\n\t\t\tsum += arr[i][j];\n\t\t}\n\t\tprintf(\" %d\", sum);\n\t}\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_12.c",
    "content": "/*\n * 修改练习11，要求修改后的程序可以提示每个学生5门测验的成绩，一共有5个学生，然后计算每个学\n * 生的5门测验的总分和平均分，还要列出每门测验的平均分、高分和低分。\n */\n\n/*\n * 编写程序，要求此程序可以用来读取一个 5*5 的整数数组，然后显示出每行的求和结果和每列的\n * 求和结果。\n *\n * Enter row 1: 8 3 9 0 10\n * Enter row 2: 3 5 17 1 1\n * Enter row 3: 2 8 6 23 1\n * Enter row 4: 15 7 3 2 9\n * Enter row 5: 6 14 2 6 0\n *\n * Row totals: 30 27 40 36 28\n * Column totals: 34 37 37 32 21\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint arr[5][5];\n\tint i, j;\n\tint sum;\n\tfloat avg[5];\n\tint min[5], max[5];\n\n\tfor (i = 0; i < 5; i++) {\n\t\tprintf(\"Enter score of student%d: \", i + 1);\n\t\tfor (j = 0; j < 5; j++) {\n\t\t\tscanf(\"%d\", &arr[i][j]);\n\t\t}\n\t}\n\n\tfor (i = 0; i < 5; i++) {\n\t\tsum = 0;\n\t\tfor (j = 0; j < 5; j++)\n\t\t\tsum += arr[i][j];\n\n\t\tprintf(\"student%d total score: %d, average score: %.2f\\n\", i + i, sum, sum / 5.0);\n\t}\n\n\tfor (j = 0; j < 5; j++) {\n\t\ti = 0;\n\t\tsum = 0;\n\t\tmin[j] = max[j] = arr[i][j];\n\t\tfor (; i < 5; ++i) {\n\t\t\tsum += arr[i][j];\n\n\t\t\tif (min[j] > arr[i][j]) min[j] = arr[i][j];\n\t\t\tif (max[j] < arr[i][j]) max[j] = arr[i][j];\n\t\t}\n\t\n\t\tavg[j] = sum / 5.0;\n\t}\n\n\tprintf(\"Avg score:\");\n\tfor (i = 0; i < 5; i++) printf(\" %.2f\", avg[i]);\n\tprintf(\"\\n\");\n\n\tprintf(\"Min score:\");\n\tfor (i = 0; i < 5; i++) printf(\" %d\", min[i]);\n\tprintf(\"\\n\");\n\n\tprintf(\"Max score:\");\n\tfor (i = 0; i < 5; i++) printf(\" %d\", max[i]);\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/ex_13.c",
    "content": "/*\n * 描述见书本。\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\nint main()\n{\n\tchar matrix[10][10];\n\tint i, j, x, y;\n\tint tmp, hit[4];\n\tchar word;\n\tint find;\n\n\tfor (i = 0; i < 10; i++)\n\t\tfor (j = 0; j < 10; j++)\n\t\t\tmatrix[i][j] = '.';\n\n\tsrand(time(NULL));\n\n\tword = 'A';\n\tx = y = 0;\n\t\n\tmatrix[x][y] = word;\n\twhile (word != 'Z') {\n\t\tfor (i = 0; i < 4; i++) hit[i] = 0;\n\n\t\tfind = 0;\n\n\t\tdo {\n\t\t\ti = x;\n\t\t\tj = y;\n\t\t\ttmp = rand() % 4;\n\t\t\thit[tmp] = 1;\n\t\t\tswitch (tmp) {\n\t\t\t\tcase 0 : j--; break;\n\t\t\t\tcase 1 : j++; break;\n\t\t\t\tcase 2 : i--; break;\n\t\t\t\tcase 3 : i++; break;\n\t\t\t}\n\n\t\t\tif (i >= 0 && i < 10 && j >= 0 && j < 10 && matrix[i][j] == '.') {\n\t\t\t\tfind = 1;\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\tif (hit[0] && hit[1] && hit[2] && hit[3]) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t} while (1);\n\n\t\tif (!find) break;\n\n\t\t++word;\n\t\tmatrix[i][j] = word;\n\n\t\tx = i;\n\t\ty = j;\n\t}\n\t\n\tfor (i = 0; i < 10; i++) {\n\t\tfor (j = 0; j < 10; j++) {\n\t\t\tputchar(matrix[i][j]);\n\t\t\tputchar(' ');\n\t\t}\n\t\tputchar(10);\n\t}\n\t\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/interest.c",
    "content": "/* Print a table of compound interest */\n\n#include <stdio.h>\n\n#define ARRAY_ITEM_COUNT(a) ((int)(sizeof(a) / sizeof(a[0])))\n#define INITIAL_BALANCE 100.00\n\nint main()\n{\n\tint i, low_rate, num_years, year;\n\tdouble value[8];\n\n\tprintf(\"Enter interest rate: \");\n\tscanf(\"%d\", &low_rate);\n\tprintf(\"Enter number of years: \");\n\tscanf(\"%d\", &num_years);\n\n\tprintf(\"\\nYears\");\n\tfor (i = 0; i < ARRAY_ITEM_COUNT(value); ++i)\n\t{\n\t\tprintf(\"%6d%%\", low_rate + i);\n\t\tvalue[i] = INITIAL_BALANCE;\n\t}\n\tprintf(\"\\n\");\n\n\tfor (year = 1; year <= num_years; ++year)\n\t{\n\t\tprintf(\"%3d     \", year);\n\t\tfor (i = 0; i < ARRAY_ITEM_COUNT(value); ++i)\n\t\t{\n\t\t\tvalue[i] += (low_rate + i) / 100.0 * value[i];\n\t\t\tprintf(\"%7.2f\", value[i]);\n\t\t}\n\t\tprintf(\"\\n\");\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/repdigit.c",
    "content": "/*\n * 程序：检查数中重复出现的数字（p101）\n *\n * Enter a number: 28212\n * Repeated digit\n */\n\n/* Checks numbers for repeated digits */\n\n#include <stdio.h>\n\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Bool;\n\nint main()\n{\n\tBool digit_seen[10] = {0};\n\tint digit;\n\tlong int n;\n\n\tprintf(\"Enter a number: \");\n\tscanf(\"%ld\", &n);\n\n\twhile (n > 0) {\n\t\tdigit = n % 10;\n\t\tif (digit_seen[digit])\n\t\t\tbreak;\n\t\tdigit_seen[digit] = TRUE;\n\t\tn /= 10;\n\t}\n\n\tif (n > 0)\n\t\tprintf(\"Repeated digit\\n\\n\");\n\telse\n\t\tprintf(\"No repeated digit\\n\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch08_数组/reverse.c",
    "content": "/*\n * 程序：数列反向（p100）\n *\n * Enter 10 numbers: 34 82 49 102 7 94 23 11 50 31\n * In reverse order: 31 50 11 23 94 7 102 49 82 34\n */\n\n/* Reverses a series of numbers */\n\n#include <stdio.h>\n\n#define N 10\n\nint main()\n{\n\tint a[N], i;\n\n\tprintf(\"Enter %d numbers: \", N);\n\tfor (i = 0; i < N; ++i)\n\t\tscanf(\"%d\", &a[i]);\n\n\tprintf(\"In reverse order:\");\n\tfor (i = N - 1; i >= 0; i--)\n\t\tprintf(\" %d\", a[i]);\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/average.c",
    "content": "/* Computes pairwise average of three numbers. */\n\n#include <stdio.h>\n\ndouble average(double a, double b)\n{\n\treturn (a + b) / 2;\n}\n\nint main()\n{\n\tdouble x, y, z;\n\n\tprintf(\"Enter three numbers: \");\n\tscanf(\"%lf%lf%lf\", &x, &y, &z);\n\n\tprintf(\"average of %g and %g: %g\\n\", x, y, average(x, y));\n\tprintf(\"average of %g and %g: %g\\n\", x, z, average(x, z));\n\tprintf(\"average of %g and %g: %g\\n\", z, y, average(z, y));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/countdown.c",
    "content": "/* Prints a countdown */\n\n#include <stdio.h>\n\nvoid print_count(int n)\n{\n\tprintf(\"T minus %2d and counting\\n\", n);\n}\n\nint main()\n{\n\tint i;\n\n\tfor (i = 10; i > 0; --i)\n\t{\n\t\tprint_count(i);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_01.c",
    "content": "/*\n * 下面计算三角形面积的函数有两处错误。找出这些错误，并且说明修改它们的方法。（提示：公式没有\n * 错误）\n *\n * float triangle_area(float base, height)\n * float product;\n * {\n * \tproduct = base * height;\n * \treturn (product / 2);\n * }\n */\n\n/*\n * 形参列表写法错误，应该为 float base, float height\n * product 的声明位置有错，应该在大括号里\n */\n\n#include <stdio.h>\n\nfloat triangle_area(float base, float height)\n{\n\tfloat product;\n\tproduct = base * height;\n\treturn (product / 2);\n}\n\nint main()\n{\n\tprintf(\"%g\\n\", triangle_area(3, 4));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_02.c",
    "content": "/*\n * 编写函数check(x, y, n)：如果x和y都落在0到n-1的闭区间内，那么使得函数check返回1。否则，\n * 函数应该返回0。假设x、y和n都是 int  类型。\n */\n\n#include <stdio.h>\n\nint check(int x, int y, int n)\n{\n\tif (x >= 0 && x < n && y >= 0 && y < n) return 1;\n\n\treturn 0;\n}\n\nint main()\n{\n\tprintf(\"%d\\n\", check(1, 2, 3));\n\tprintf(\"%d\\n\", check(1, 2, 2));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_03.c",
    "content": "/*\n * 编写函数gcd(m,n)用来计算整数m和n的最大公约数。（第6章的练习2描述了计算最大公约数的Euclid算法）\n */\n\n/*\n * 编写程序，要求用户输入两个整数，然后计算并显示这两个整数的最大公约数（GCD）：\n * \n * Enter two integers: 12 28\n * Greatest common divisor: 4\n *\n * 提示：求最大公约数的经典算法是 Euclid 算法，方法如下：分别让变量m和n存储两个数的值；用m除\n * 以n；把除数保存在m中，而把余数保存在n中；如果n为0，那么停止操作，m中的值是GCD；否则，\n * 从m除以n开始，重复上述除法过程。\n */\n\n#include <stdio.h>\n\nint gcd(int m, int n)\n{\n\tint tmp;\n\n\twhile (n != 0) {\n\t\ttmp = m;\n\t\tm = n;\n\t\tn = tmp % n;\n\t}\n\n\treturn m;\n}\n\nint main()\n{\n\tint m, n;\n\n\tprintf(\"Enter two integers: \");\n\tscanf(\"%d%d\", &m, &n);\n\n\tprintf(\"Greatest common divisor: %d\\n\", gcd(m, n));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_04.c",
    "content": "/*\n * 编写函数 day_of_year(month, day, year) , 使得函数返回某 month 某 day 是 year 这一年中的\n * 第几天（1和366之间的整数）。\n */\n\n#include <stdio.h>\n\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Bool;\n\nBool is_leap_year(int year)\n{\n\tif (0 != year % 100) return 0 == year % 4;\n\n\treturn 0 == year % 400;\n}\n\nint day_of_year(int month, int day, int year)\n{\n\tint days;\n\tint i;\n\n\tint month_days[12] = {\n\t\t31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31\n\t};\n\n\tif (is_leap_year(year)) month_days[1]++;\n\n\tdays = 0;\n\tfor (i = 0; i < month - 1; i++) {\n\t\tdays += month_days[i];\n\t}\n\n\tdays += day;\n\n\treturn days;\n}\n\nint main()\n{\n\tint month, day, year;\n\t\n\tprintf(\"Enter month day year: \");\n\tscanf(\"%d%d%d\", &month, &day, &year);\n\n\tprintf(\"days: %d\\n\", day_of_year(month, day, year));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_05.c",
    "content": "/*\n * 编写函数 num_digits(n) , 使得函数返回正整数n中数字的个数。提示：为了确定n中的数字的个数，\n * 把这个数反复除以10.当n达到0时，除法的次数表明了n最初拥有的数字的个数。\n */\n\n#include <stdio.h>\n\nint num_digits(n)\n{\n\tint cnt = 0;\n\tdo {\n\t\tn /= 10;\n\t\t++cnt;\n\t} while (n != 0);\n\n\treturn cnt;\n}\n\nint main()\n{\n\tint n;\n\tprintf(\"Enter num: \");\n\tscanf(\"%d\", &n);\n\n\tprintf(\"digits: %d\\n\", num_digits(n));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_06.c",
    "content": "/*\n * 编写函数 digit(n, k) , 使得函数返回正整数n中第k个数字（从右边算起）。例如，digit(829, 1)\n * 返回9， digit(829, 2) 返回2，而 digit(829, 3) 则返回8。如果k大于n所含的数字的个数，那么\n * 函数返回-1。\n */\n\n#include <stdio.h>\n\nint digit(int n, int k)\n{\n\tint i, tmp;\n\n\ti = 0;\n\tdo {\n\t\ttmp = n % 10;\n\t\tn /= 10;\n\n\t\ti++;\n\t\tif (i == k) return tmp;\n\t\n\t} while (n != 0);\n\n\treturn -1;\n}\n\nint main()\n{\n\tint n, k;\n\n\tprintf(\"Enter n k: \");\n\tscanf(\"%d%d\", &n, &k);\n\n\tprintf(\"%d\", digit(n, k));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_07.c",
    "content": "/*\n * 假设函数f有下列定义：\n *\n * int f(int a, int b) { ... }\n *\n * 那么下列哪条语句是合法的？（假设i的类型为 int 而x的类型为 float 。）\n */\n\nint f(int a, int b) { return 0; }\n\nint main()\n{\n\tint i;\n\tfloat x;\n\n\t(void)i;\n\t(void)x;\n\n\t/* a 合法 */\n\ti = f(83, 12);\n\n\t/* b 合法 */\n\tx = f(83, 12);\n\n\t/* c 合法 */\n\ti = f(3.15, 9.28);\n\n\t/* d 合法 */\n\tx = f(3.15, 9.28);\n\n\t/* e 合法 */\n\tf(83, 12);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_08.md",
    "content": "对于返回为空且有一个 float 型形式参数的函数，下列哪个函数原型是有效的？\n\n(a) void f(float x);\n\n(b) void f(float);\n\n(c) void f(x);\n\n(d) f(float x);\n\n---\n\n(a), (b)\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_09.c",
    "content": "/*\n * 下列程序的输出是什么？\n */\n\n/*\n * x = 1, y = 2\n */\n\n#include <stdio.h>\n\nvoid swap(int a, int b);\n\nint main()\n{\n\tint x = 1, y = 2;\n\n\tswap(x, y);\n\tprintf(\"x = %d, y = %d\\n\", x, y);\n\n\treturn 0;\n}\n\nvoid swap(int a, int b)\n{\n\tint temp;\n\n\ttemp = a;\n\ta = b;\n\tb = temp;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_10.c",
    "content": "/*\n * 编写函数，使得函数返回下列值。（假设a和n是形式参数，其中a是有 int 型值的数组，而n则是数组\n * 的长度。）\n *\n * (a) 数组a中的最大元素\n * (b) 数组a中所有元素的平均值\n * (c) 数组a中正数元素的数量\n */\n\n#include <stdio.h>\n\n#define ARRAY_SZ (int)(sizeof(a) / sizeof(a[0]))\n\nint array_max_element(int array[], int count);\nint array_average(int array[], int count);\nint array_positive_count(int array[], int count);\n\nint main()\n{\n\tint a[] = {1, 42, -3, -10, 6, -8};\n\n\tprintf(\"array max element: %d\\n\", array_max_element(a, ARRAY_SZ));\n\tprintf(\"array average: %d\\n\", array_average(a, ARRAY_SZ));\n\tprintf(\"array positive count: %d\\n\", array_positive_count(a, ARRAY_SZ));\n\n\treturn 0;\n}\n\nint array_max_element(int array[], int count)\n{\n\tint max_number = array[0];\n\tint i;\n\n\tfor (i = 1; i < count; i++) {\n\t\tif (max_number < array[i]) max_number = array[i];\n\t}\n\n\treturn max_number;\n}\n\nint array_average(int array[], int count)\n{\n\tint sum = 0;\n\tint i;\n\n\tfor (i = 0; i < count; i++) {\n\t\tsum += array[i];\n\t}\n\n\treturn sum / count;\n}\n\nint array_positive_count(int array[], int count)\n{\n\tint positive_cnt = 0;\n\tint i;\n\n\tfor (i = 0; i < count; i++) {\n\t\tif (array[i] > 0) positive_cnt++;\n\t}\n\n\treturn positive_cnt;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_11.md",
    "content": "如果数组a的所有元素值都为0，那么假设下列函数返回 TRUE ；如果数组的所有元素都是非零的，则函数返回 FALSE 。可惜的是，此函数有错误。请找出错误并说明修改它的方法。\n\n```c\nBool has_zero(int a[], int n)\n{\n\tint i;\n\tfor (i = 0; i < n; i++)\n\t\tif (a[i] == 0)\n\t\t\treturn TRUE;\n\t\telse\n\t\t\treturn FALSE;\n}\n```\n\n---\n\n题目描述似有误，应该是如果有元素为0，那么返回 TRUE ，否则返回 FALSE 。\n\n这样是这样的：\n\n```c\nBool has_zero(int a[], int n)\n{\n\tint i;\n\tfor (i = 0; i < n; i++)\n\t\tif (a[i] == 0) return TRUE;\n\n\treturn FALSE;\n}\n```\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_12.c",
    "content": "/*\n * 下面的（不要弄混）函数用来找到三个数的中间数。重新编写函数，使得它只有一条 return 语句。\n *\n * float median(float x, float y, float z)\n * {\n * \tif (x <= y)\n * \t\tif (y <= z) return y;\n * \t\telse if (x <= z) return z;\n * \t\telse return x;\n *\n * \tif (z <= y) return y;\n * \tif (x <= z) return x;\n * \treturn z;\n * }\n */\n\n#include <stdio.h>\n\nfloat median(float x, float y, float z)\n{\n\tfloat mid = z;\n\n\tif (x <= y)\n\t\tif (y <= z) mid = y;\n\t\telse if (x <= z) mid = z;\n\t\telse mid = x;\n\telse {\n\t\tif (z <= y) mid = y;\n\t\tif (x <= z) mid = x;\n\t}\n\n\treturn mid;\n}\n\nint main()\n{\n\tfloat x, y, z;\n\tprintf(\"Enter 3 float: \");\n\tscanf(\"%f%f%f\", &x, &y, &z);\n\n\tprintf(\"median: %g\\n\", median(x, y, z));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_13.c",
    "content": "/*\n * 请采用精简 power 函数的方法，来简化 fact 函数。\n */\n\n#include <stdio.h>\n\nint fact(int n)\n{\n\treturn n <= 1 ? 1 : n * fact(n - 1);\n}\n\nint main()\n{\n\tprintf(\"fact(5): %d\\n\", fact(5));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_14.c",
    "content": "/*\n * 请重新编写 fact 函数，使得编写后的函数不再递归。\n */\n\n#include <stdio.h>\n\nint fact(int n)\n{\n\tint res = 1, i;\n\n\tfor (i = 2; i <= n; i++) {\n\t\tres *= i;\n\t}\n\n\treturn res;\n}\n\nint main()\n{\n\tprintf(\"fact(5): %d\\n\", fact(5));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_15.c",
    "content": "/*\n * 编写递归版本的 gcd 函数（参加练习3）。有一种用于计算 gcd(m, n) 的策略；如果 n 为0，那么返回m；\n * 否则，递归地调用 gcd 函数，把n作为第一个实际参数进行传递，而把 m % n 作为第二个实际参数进行传\n * 递。\n */\n\n#include <stdio.h>\n\nint gcd(int m, int n)\n{\n\treturn n == 0 ? m : gcd(n, m % n);\n}\n\nint main()\n{\n\tint m, n;\n\n\tprintf(\"Enter two number: \");\n\tscanf(\"%d%d\", &m, &n);\n\n\tprintf(\"gcd: %d\\n\", gcd(m, n));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_16.c",
    "content": "/*\n * 思考下面这个“神秘的”函数：\n * void pb(int n)\n * {\n * \tif (n != 0) {\n * \t\tpb(n / 2);\n * \t\tputchar('O' + n % 2);\n * \t}\n * }\n *\n * 手动跟踪函数的执行，然后编写程序调试此函数，把用户录入的数传递给此函数。函数做了什么？\n */\n\n/*\n * 递归输出一些内容，每次调用输出O或者P\n */\n\n#include <stdio.h>\n\nvoid pb(int n)\n{\n\tif (n != 0) {\n\t\tpb(n / 2);\n\t\tputchar('O' + n % 2);\n\t}\n}\n\nint main()\n{\n\tint n;\n\tprintf(\"Enter n: \");\n\tscanf(\"%d\", &n);\n\n\tpb(n);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/ex_17.c",
    "content": "/*\n * 编写程序，要求用户录入一串整数（把这串整数存储在数组中），然后通过调用 selection_sort 函数\n * 来排序这些整数。在给定n个元素的数组后， selection_sort 函数必须做下列工作：\n * (a) 搜索数组找出数组中最大的元素，然后把它移到数组的最后\n * (b) 递归地调用函数本身来对前n-1个数组元素进行排序\n */\n\n#include <stdio.h>\n\n#define ARR_LEN 5\n\nvoid selection_sort(int array[], int count)\n{\n\tint i;\n\tint max_index, tmp;\n\n\tif (count <= 1) return;\n\n\tmax_index = 0;\n\tfor (i = 0; i < count; i++) {\n\t\tif (array[max_index] < array[i])\n\t\t\tmax_index = i;\n\t}\n\n\ttmp = array[count - 1];\n\tarray[count - 1] = array[max_index];\n\tarray[max_index] = tmp;\n\n\tselection_sort(array, count - 1);\n}\n\nint main()\n{\n\tint array[ARR_LEN];\n\tint i;\n\tprintf(\"Enter numbers: \");\n\tfor (i = 0; i < ARR_LEN; i++) {\n\t\tscanf(\"%d\", &array[i]);\n\t}\n\n\tselection_sort(array, ARR_LEN);\n\n\tfor (i = 0; i < ARR_LEN; i++) {\n\t\tprintf(\"%d \", array[i]);\n\t}\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/prime.c",
    "content": "/* Tests whether a number is prime */\n\n#include <stdio.h>\n\nint is_prime(int n)\n{\n\tint divisor;\n\n\tif (n <= 1) return 0;\n\n\tfor (divisor = 2; divisor * divisor <= n; ++ divisor)\n\t{\n\t\tif (n % divisor == 0)\n\t\t{\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\treturn 1;\n}\n\nint main()\n{\n\tint n;\n\n\tprintf(\"Enter a number: \");\n\tscanf(\"%d\", &n);\n\n\tif (is_prime(n))\n\t{\n\t\tprintf(\"Prime\\n\");\n\t}\n\telse\n\t{\n\t\tprintf(\"Not prime\\n\");\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/pun2.c",
    "content": "/* Prints a bad pun */\n\n#include <stdio.h>\n\nvoid print_pun(void)\n{\n\tprintf(\"To C or not to C, that is the question.\\n\");\n}\n\nint main()\n{\n\tprint_pun();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch09_函数/qsort.c",
    "content": "/* Sorts an array of integers using Quicksort algorithm */\n\n#include <stdio.h>\n\n#define LEN 10\n\nvoid quicksort(int a[], int low, int high);\nint split(int a[], int low, int high);\n\nint main()\n{\n\tint a[LEN], i;\n\n\tprintf(\"Enter %d numbers to be sorted: \", LEN);\n\tfor (i = 0; i < LEN; ++ i)\n\t{\n\t\tscanf(\"%d\", &a[i]);\n\t}\n\n\tquicksort(a, 0, LEN - 1);\n\n\tprintf(\"In sorted order:\");\n\tfor (i = 0 ; i < LEN; ++ i)\n\t{\n\t\tprintf(\" %d\", a[i]);\n\t}\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n\nvoid quicksort(int a[], int low, int high)\n{\n\tint middle;\n\n\tif (low >= high) return;\n\tmiddle = split(a, low, high);\n\n\tquicksort(a, low, middle - 1);\n\tquicksort(a, middle + 1, high);\n}\n\nint split(int a[], int low, int high)\n{\n\tint part_element = a[low];\n\n\tfor (;;)\n\t{\n\t\twhile (low < high && part_element <= a[high])\n\t\t{\n\t\t\thigh--;\n\t\t}\n\t\tif (low >= high) break;\n\t\ta[low++] = a[high];\n\n\t\twhile (low < high && a[low] <= part_element)\n\t\t{\n\t\t\tlow++;\n\t\t}\n\t\tif (low >= high) break;\n\t\ta[high--] = a[low];\n\t}\n\n\ta[high] = part_element;\n\treturn high;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch10_程序结构/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch10_程序结构/ex_01.c",
    "content": "/*\n * 修改栈示例使它存储字符而不是整数。接下来，增加 main 函数，用来要求用户输入一串圆括号或大\n * 括号，然后指出它们是否正确嵌套：\n *\n * Enter parenteses and/or braces: {{}{}}\n * Parenteses/braces are nested property\n *\n * 提示略。\n */\n\n#include <stdio.h>\n\n#define STACK_SIZE 100\n#define true 1\n#define false 0\n\n/* external variables */\nint contents[STACK_SIZE];\nint top = 0;\n\nvoid make_empty()\n{\n\ttop = 0;\n}\n\nint is_empty()\n{\n\treturn 0 == top;\n}\n\nint is_full()\n{\n\treturn STACK_SIZE == top;\n}\n\nvoid push(int i)\n{\n\tif ( !is_full())\n\t{\n\t\tcontents[top ++] = i;\n\t}\n}\n\nint pop()\n{\n\tif ( !is_empty())\n\t{\n\t\treturn contents[-- top];\n\t}\n\n\treturn -1;\n}\n\n//-------------------------------------\n\nint main()\n{\n\tint c, tmp;\n\tint check_succ;\n\t\n\tprintf(\"Enter parenteses and/or braces: \");\n\t\n\tcheck_succ = true;\n\twhile ((c = getchar()) != '\\n') {\n\t\tif (c == '{' || c == '(') {\n\t\t\tpush(c);\n\t\t\tcontinue;\n\t\t}\n\t\telse if (c == '}' || c == ')') {\n\t\t\ttmp = pop(c);\n\t\t\tif (c == '}' && tmp != '{') {\n\t\t\t\tcheck_succ = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\telse if (c == ')' && tmp != '(') {\n\t\t\t\tcheck_succ = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tcheck_succ = false;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!is_empty()) check_succ = false;\n\n\tif (check_succ) {\n\t\tprintf(\"Parenteses/braces are nested property\");\n\t}\n\telse {\n\t\tprintf(\"Parenteses/braces are not nested property\");\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch10_程序结构/ex_02.md",
    "content": "下面的程序框架只显示了函数定义和变量声明。\n\n```c\nint a;\n\nvoid f(int b) \n{\n\tint c;\n}\n\nvoid g(void)\n{\n\tint d;\n\t{\n\t\tint b;\n\t}\n}\n\nmain()\n{\n\tint f;\n}\n\n```\n\n对于下面每种作用域，列出在此作用域内的所有变量的名字和形式参数的名字：\n\n(a) f函数。\n\n(b) g函数。\n\n(c) 声明e的程序块。\n\n(d) main 函数。\n\n---\n\n(a) 局部自动变量c，形式参数b\n\n(b) 局部自动变量d，局部的程序块内的自动变量b\n\n(c) b\n\n(d) f\n"
  },
  {
    "path": "codes/CProgramming/ch10_程序结构/ex_03.c",
    "content": "/*\n * 修改poker.c程序，把数组 num_in_rank 和数组 num_in_suit 移入 main 函数。 main 函数将把这\n * 两个数组作为实际参数传递给 read_cards 函数和 analyze_hand 函数。\n */\n\n/*\n * Classifies a poker hand\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#define NUM_RANKS 13\n#define NUM_SUITS 4\n#define NUM_CARDS 5\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Bool;\n\nBool straight, flush, four, three;\nint pairs; /* can be 0, 1, or 2 */\n\nvoid read_cards(int num_in_rank[], int num_in_suit[]);\nvoid analyze_hand(int num_in_rank[], int num_in_suit[]);\nvoid print_result(void);\n\n/*\n * main: Calls read_cards, analyse_hand, and print_result\n * \t\t repeatedly.\n */\nint main()\n{\n\tint num_in_rank[NUM_RANKS];\n\tint num_in_suit[NUM_SUITS];\n\n\tfor (;;) { /* infinite loop */\n\t\tread_cards(num_in_rank, num_in_suit);\n\t\tanalyze_hand(num_in_rank, num_in_suit);\n\t\tprint_result();\n\t}\n\n\treturn 0;\n}\n\n/*\n * read_cards: Reads the cards into the external\n * \t\t\t   variables num_in_rank and num_in_suit;\n * \t\t\t   checks for bad cards and duplicate cards.\n */\nvoid read_cards(int num_in_rank[], int num_in_suit[])\n{\n\tBool card_exists[NUM_RANKS][NUM_SUITS];\n\tchar ch, rank_ch, suit_ch;\n\tint rank, suit;\n\tBool bad_card;\n\tint cards_read = 0;\n\n\tfor (rank = 0; rank < NUM_RANKS; rank++) {\n\t\tnum_in_rank[rank] = 0;\n\t\tfor (suit = 0; suit < NUM_SUITS; ++suit) {\n\t\t\tcard_exists[rank][suit] = FALSE;\n\t\t}\n\t}\n\n\tfor (suit = 0; suit < NUM_SUITS; suit++) {\n\t\tnum_in_suit[suit] = 0;\n\t}\n\n\twhile (cards_read < NUM_CARDS) {\n\t\tbad_card = FALSE;\n\n\t\tprintf(\"Enter a card: \");\n\n\t\trank_ch = getchar();\n\t\tswitch(rank_ch) {\n\t\t\tcase '0':\t\t\texit(EXIT_SUCCESS); break;\n\t\t\tcase '2':\t\t\trank = 0; break;\n\t\t\tcase '3':\t\t\trank = 1; break;\n\t\t\tcase '4':\t\t\trank = 2; break;\n\t\t\tcase '5':\t\t\trank = 3; break;\n\t\t\tcase '6':\t\t\trank = 4; break;\n\t\t\tcase '7':\t\t\trank = 5; break;\n\t\t\tcase '8':\t\t\trank = 6; break;\n\t\t\tcase '9':\t\t\trank = 7; break;\n\t\t\tcase 't': case 'T':\trank = 8; break;\n\t\t\tcase 'j': case 'J':\trank = 9; break;\n\t\t\tcase 'q': case 'Q': rank = 10;break;\n\t\t\tcase 'k': case 'K': rank = 11;break;\n\t\t\tcase 'a': case 'A': rank = 12;break;\n\t\t\tdefault:\t\t\tbad_card = TRUE;\n\t\t}\n\n\t\tsuit_ch = getchar();\n\t\tswitch(suit_ch) {\n\t\t\tcase 'c': case 'C':\tsuit = 0; break;\n\t\t\tcase 'd': case 'D': suit = 1; break;\n\t\t\tcase 'h': case 'H': suit = 2; break;\n\t\t\tcase 's': case 'S': suit = 3; break;\n\t\t\tdefault:\t\t\tbad_card = TRUE;\n\t\t}\n\n\t\twhile ((ch = getchar()) != '\\n')\n\t\t\tif (ch != ' ') bad_card = TRUE;\n\n\t\tif (bad_card)\n\t\t\tprintf(\"Bad card; ignored.\\n\");\n\t\telse if (card_exists[rank][suit])\n\t\t\tprintf(\"Duplicate card; ignored.\\n\");\n\t\telse {\n\t\t\tnum_in_rank[rank]++;\n\t\t\tnum_in_suit[suit]++;\n\t\t\tcard_exists[rank][suit] = TRUE;\n\t\t\tcards_read++;\n\t\t}\n\t}\n}\n\n/*\n * analyze_hand: Determines whether the hand contains a\n * \t\t\t     straight, a flush, four-of-a-kind,\n * \t\t\t     and/or a three-of-a-kind; determines the\n * \t\t\t     number of pairs; stores the result into\n * \t\t\t     the external variables straight, flush,\n * \t\t\t     four, three, and pairs.\n */\n\nvoid analyze_hand(int num_in_rank[], int num_in_suit[])\n{\n\tint num_consec = 0;\n\tint rank, suit;\n\n\tstraight = FALSE;\n\tflush = FALSE;\n\tfour = FALSE;\n\tthree = FALSE;\n\tpairs = 0;\n\n\t/* check for flush */\n\tfor (suit = 0; suit < NUM_SUITS; suit++) {\n\t\tif (num_in_suit[suit] == NUM_CARDS)\n\t\t\tflush = TRUE;\n\t}\n\n\t/* check for straight */\n\trank = 0;\n\twhile (num_in_rank[rank] == 0) rank++;\n\tfor (; rank < NUM_RANKS && num_in_rank[rank]; ++rank)\n\t\tnum_consec++;\n\tif (num_consec == NUM_CARDS) {\n\t\tstraight = TRUE;\n\t\treturn;\n\t}\n\n\t/* check for 4-of-kind, 3-of-kind, and pairs */\n\tfor (rank = 0; rank < NUM_RANKS; rank++) {\n\t\tif (num_in_rank[rank] == 4) four = TRUE;\n\t\tif (num_in_rank[rank] == 3) three = TRUE;\n\t\tif (num_in_rank[rank] == 2) pairs++;\n\t}\n}\n\n/*\n * print_result: Notifies the user of the result, using\n * \t\t\t     the external variables straight, flush,\n * \t\t\t     four, three, and pairs.\n */\nvoid print_result()\n{\n\tif (straight && flush) \tprintf(\"Straight flush\\n\\n\");\n\telse if (four)\t\t\tprintf(\"Four of a kind\\n\\n\");\n\telse if (three &&\n\t\t\t pairs == 1)\tprintf(\"Full house\\n\\n\");\n\telse if (flush)\t\t\tprintf(\"Flush\\n\\n\");\n\telse if (straight)\t\tprintf(\"Straight\\n\\n\");\n\telse if (three)\t\t\tprintf(\"Three of a kind\\n\\n\");\n\telse if (pairs == 2)\tprintf(\"Two Pair\\n\\n\");\n\telse if (pairs == 1)\tprintf(\"Pair\\n\\n\");\n\telse\t\t\t\t\tprintf(\"High card\\n\\n\");\n}\n"
  },
  {
    "path": "codes/CProgramming/ch10_程序结构/ex_04.c",
    "content": "/*\n * 把数组 num_in_rank 、数组 num_in_suit 和数组 card_exists 从 poker.c 程序中去掉。程序改用\n * 5*2d的数组来代替存储牌。\n */\n\n/*\n * Classifies a poker hand\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#define NUM_RANKS 13\n#define NUM_SUITS 4\n#define NUM_CARDS 5\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Bool;\n\nint cards[NUM_CARDS][2]; /* 第一列为 rank ，第二列为 suit */\nBool straight, flush, four, three;\nint pairs; /* can be 0, 1, or 2 */\n\nvoid sort_by_rank(int cards[][2], int count);\nBool card_exists(int cards[][2], int count, int rank, int suit);\nvoid read_cards(void);\nvoid analyze_hand(void);\nvoid print_result(void);\n\n/*\n * main: Calls read_cards, analyse_hand, and print_result\n * \t\t repeatedly.\n */\nint main()\n{\n\tfor (;;) { /* infinite loop */\n\t\tread_cards();\n\t\tanalyze_hand();\n\t\tprint_result();\n\t}\n\n\treturn 0;\n}\n\n/*\n * sort_by_rank: 按照 rank 将 cards 排序（升序）\n */\nvoid sort_by_rank(int cards[][2], int count)\n{\n\tint i, max_index, tmp;\n\n\tif (count <= 1) return;\n\n\tmax_index = 0;\n\tfor (i = 1; i < count; i++) {\n\t\tif (cards[i][0] > cards[max_index][0])\n\t\t\tmax_index = i;\n\t}\n\n\ttmp = cards[count - 1][0];\n\tcards[count - 1][0] = cards[max_index][0];\n\tcards[max_index][0] = tmp;\n\n\ttmp = cards[count - 1][1];\n\tcards[count - 1][1] = cards[max_index][1];\n\tcards[max_index][1] = tmp;\n\n\tsort_by_rank(cards, count - 1);\n}\n\n/*\n * card_exists: 查询 rank 和 suit 代表的卡牌是否已经存在于 cards\n */\nBool card_exists(int cards[][2], int count, int rank, int suit)\n{\n\tint i;\n\n\tfor (i = 0; i < count; i++) {\n\t\tif (cards[i][0] == rank && cards[i][1] == suit)\n\t\t\treturn TRUE;\n\t}\n\n\treturn FALSE;\n}\n\n/*\n * read_cards: Reads the cards into the external\n * \t\t\t   variables num_in_rank and num_in_suit;\n * \t\t\t   checks for bad cards and duplicate cards.\n */\nvoid read_cards(void)\n{\n\tchar ch, rank_ch, suit_ch;\n\tint i, j, rank, suit;\n\tBool bad_card;\n\tint cards_read = 0;\n\n\tfor (i = 0; i < NUM_CARDS; i++) {\n\t\tfor (j = 0; j < 2; j++) {\n\t\t\tcards[i][j] = -1;\n\t\t}\n\t}\n\n\twhile (cards_read < NUM_CARDS) {\n\t\tbad_card = FALSE;\n\n\t\tprintf(\"Enter a card: \");\n\n\t\trank_ch = getchar();\n\t\tswitch(rank_ch) {\n\t\t\tcase '0':\t\t\texit(EXIT_SUCCESS); break;\n\t\t\tcase '2':\t\t\trank = 0; break;\n\t\t\tcase '3':\t\t\trank = 1; break;\n\t\t\tcase '4':\t\t\trank = 2; break;\n\t\t\tcase '5':\t\t\trank = 3; break;\n\t\t\tcase '6':\t\t\trank = 4; break;\n\t\t\tcase '7':\t\t\trank = 5; break;\n\t\t\tcase '8':\t\t\trank = 6; break;\n\t\t\tcase '9':\t\t\trank = 7; break;\n\t\t\tcase 't': case 'T':\trank = 8; break;\n\t\t\tcase 'j': case 'J':\trank = 9; break;\n\t\t\tcase 'q': case 'Q': rank = 10;break;\n\t\t\tcase 'k': case 'K': rank = 11;break;\n\t\t\tcase 'a': case 'A': rank = 12;break;\n\t\t\tdefault:\t\t\tbad_card = TRUE;\n\t\t}\n\n\t\tsuit_ch = getchar();\n\t\tswitch(suit_ch) {\n\t\t\tcase 'c': case 'C':\tsuit = 0; break;\n\t\t\tcase 'd': case 'D': suit = 1; break;\n\t\t\tcase 'h': case 'H': suit = 2; break;\n\t\t\tcase 's': case 'S': suit = 3; break;\n\t\t\tdefault:\t\t\tbad_card = TRUE;\n\t\t}\n\n\t\twhile ((ch = getchar()) != '\\n')\n\t\t\tif (ch != ' ') bad_card = TRUE;\n\n\t\tif (bad_card)\n\t\t\tprintf(\"Bad card; ignored.\\n\");\n\t\telse if (card_exists(cards, cards_read, rank, suit))\n\t\t\tprintf(\"Duplicate card; ignored.\\n\");\n\t\telse {\n\t\t\tcards[cards_read][0] = rank;\n\t\t\tcards[cards_read][1] = suit;\n\t\t\tcards_read++;\n\t\t}\n\t}\n}\n\n/*\n * analyze_hand: Determines whether the hand contains a\n * \t\t\t     straight, a flush, four-of-a-kind,\n * \t\t\t     and/or a three-of-a-kind; determines the\n * \t\t\t     number of pairs; stores the result into\n * \t\t\t     the external variables straight, flush,\n * \t\t\t     four, three, and pairs.\n */\n\nvoid analyze_hand()\n{\n\tint num_consec = 0;\n\tint i, tmp;\n\n\tsort_by_rank(cards, NUM_CARDS);\n\n\tstraight = FALSE;\n\tflush = FALSE;\n\tfour = FALSE;\n\tthree = FALSE;\n\tpairs = 0;\n\n\t/* check for flush */\n\tflush = TRUE;\n\tfor (i = 1; i < NUM_CARDS; i++) {\n\t\tif (cards[i][1] != cards[0][1])\n\t\t\tflush = FALSE;\n\t}\n\n\t/* check for straight */\n\tstraight = TRUE;\n\tfor (i = 1; i < NUM_CARDS; i++) {\n\t\tif (cards[i][0] != cards[i - 1][0] + 1)\n\t\t\tstraight = FALSE;\n\t}\n\n\t/* check for 4-of-kind, 3-of-kind, and pairs */\n\ttmp = cards[0][0];\n\tnum_consec = 1;\n\tfor (i = 1; i < NUM_CARDS; i++) {\n\t\tif (cards[i][0] == tmp) {\n\t\t\tnum_consec++;\n\t\t\tif (num_consec == 4) four = TRUE;\n\t\t\tif (num_consec == 3) three = TRUE;\n\t\t\tif (num_consec == 2) pairs++;\n\t\t}\n\t\telse {\n\t\t\ttmp = cards[i][0];\n\t\t\tnum_consec = 1;\n\t\t}\n\t}\n}\n\n/*\n * print_result: Notifies the user of the result, using\n * \t\t\t     the external variables straight, flush,\n * \t\t\t     four, three, and pairs.\n */\nvoid print_result()\n{\n\tif (straight && flush) \tprintf(\"Straight flush\\n\\n\");\n\telse if (four)\t\t\tprintf(\"Four of a kind\\n\\n\");\n\telse if (three &&\n\t\t\t pairs == 1)\tprintf(\"Full house\\n\\n\");\n\telse if (flush)\t\t\tprintf(\"Flush\\n\\n\");\n\telse if (straight)\t\tprintf(\"Straight\\n\\n\");\n\telse if (three)\t\t\tprintf(\"Three of a kind\\n\\n\");\n\telse if (pairs == 2)\tprintf(\"Two Pair\\n\\n\");\n\telse if (pairs == 1)\tprintf(\"Pair\\n\\n\");\n\telse\t\t\t\t\tprintf(\"High card\\n\\n\");\n}\n"
  },
  {
    "path": "codes/CProgramming/ch10_程序结构/ex_05.c",
    "content": "/*\n * 修改 poker.c 程序，使其识别牌的额外类别——“同花大顺”（A、K、Q、J，以及同样花色的10）。\n * 同花大顺的级别高于其他所有的类别。\n */\n\n/*\n * Classifies a poker hand\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#define NUM_RANKS 13\n#define NUM_SUITS 4\n#define NUM_CARDS 5\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Bool;\n\nint cards[NUM_CARDS][2]; /* 第一列为 rank ，第二列为 suit */\nBool straight, big_flush, flush, four, three;\nint pairs; /* can be 0, 1, or 2 */\n\nvoid sort_by_rank(int cards[][2], int count);\nBool card_exists(int cards[][2], int count, int rank, int suit);\nvoid read_cards(void);\nvoid analyze_hand(void);\nvoid print_result(void);\n\n/*\n * main: Calls read_cards, analyse_hand, and print_result\n * \t\t repeatedly.\n */\nint main()\n{\n\tfor (;;) { /* infinite loop */\n\t\tread_cards();\n\t\tanalyze_hand();\n\t\tprint_result();\n\t}\n\n\treturn 0;\n}\n\n/*\n * sort_by_rank: 按照 rank 将 cards 排序（升序）\n */\nvoid sort_by_rank(int cards[][2], int count)\n{\n\tint i, max_index, tmp;\n\n\tif (count <= 1) return;\n\n\tmax_index = 0;\n\tfor (i = 1; i < count; i++) {\n\t\tif (cards[i][0] > cards[max_index][0])\n\t\t\tmax_index = i;\n\t}\n\n\ttmp = cards[count - 1][0];\n\tcards[count - 1][0] = cards[max_index][0];\n\tcards[max_index][0] = tmp;\n\n\ttmp = cards[count - 1][1];\n\tcards[count - 1][1] = cards[max_index][1];\n\tcards[max_index][1] = tmp;\n\n\tsort_by_rank(cards, count - 1);\n}\n\n/*\n * card_exists: 查询 rank 和 suit 代表的卡牌是否已经存在于 cards\n */\nBool card_exists(int cards[][2], int count, int rank, int suit)\n{\n\tint i;\n\n\tfor (i = 0; i < count; i++) {\n\t\tif (cards[i][0] == rank && cards[i][1] == suit)\n\t\t\treturn TRUE;\n\t}\n\n\treturn FALSE;\n}\n\n/*\n * read_cards: Reads the cards into the external\n * \t\t\t   variables num_in_rank and num_in_suit;\n * \t\t\t   checks for bad cards and duplicate cards.\n */\nvoid read_cards(void)\n{\n\tchar ch, rank_ch, suit_ch;\n\tint i, j, rank, suit;\n\tBool bad_card;\n\tint cards_read = 0;\n\n\tfor (i = 0; i < NUM_CARDS; i++) {\n\t\tfor (j = 0; j < 2; j++) {\n\t\t\tcards[i][j] = -1;\n\t\t}\n\t}\n\n\twhile (cards_read < NUM_CARDS) {\n\t\tbad_card = FALSE;\n\n\t\tprintf(\"Enter a card: \");\n\n\t\trank_ch = getchar();\n\t\tswitch(rank_ch) {\n\t\t\tcase '0':\t\t\texit(EXIT_SUCCESS); break;\n\t\t\tcase '2':\t\t\trank = 0; break;\n\t\t\tcase '3':\t\t\trank = 1; break;\n\t\t\tcase '4':\t\t\trank = 2; break;\n\t\t\tcase '5':\t\t\trank = 3; break;\n\t\t\tcase '6':\t\t\trank = 4; break;\n\t\t\tcase '7':\t\t\trank = 5; break;\n\t\t\tcase '8':\t\t\trank = 6; break;\n\t\t\tcase '9':\t\t\trank = 7; break;\n\t\t\tcase 't': case 'T':\trank = 8; break;\n\t\t\tcase 'j': case 'J':\trank = 9; break;\n\t\t\tcase 'q': case 'Q': rank = 10;break;\n\t\t\tcase 'k': case 'K': rank = 11;break;\n\t\t\tcase 'a': case 'A': rank = 12;break;\n\t\t\tdefault:\t\t\tbad_card = TRUE;\n\t\t}\n\n\t\tsuit_ch = getchar();\n\t\tswitch(suit_ch) {\n\t\t\tcase 'c': case 'C':\tsuit = 0; break;\n\t\t\tcase 'd': case 'D': suit = 1; break;\n\t\t\tcase 'h': case 'H': suit = 2; break;\n\t\t\tcase 's': case 'S': suit = 3; break;\n\t\t\tdefault:\t\t\tbad_card = TRUE;\n\t\t}\n\n\t\twhile ((ch = getchar()) != '\\n')\n\t\t\tif (ch != ' ') bad_card = TRUE;\n\n\t\tif (bad_card)\n\t\t\tprintf(\"Bad card; ignored.\\n\");\n\t\telse if (card_exists(cards, cards_read, rank, suit))\n\t\t\tprintf(\"Duplicate card; ignored.\\n\");\n\t\telse {\n\t\t\tcards[cards_read][0] = rank;\n\t\t\tcards[cards_read][1] = suit;\n\t\t\tcards_read++;\n\t\t}\n\t}\n}\n\n/*\n * analyze_hand: Determines whether the hand contains a\n * \t\t\t     straight, a flush, four-of-a-kind,\n * \t\t\t     and/or a three-of-a-kind; determines the\n * \t\t\t     number of pairs; stores the result into\n * \t\t\t     the external variables straight, flush,\n * \t\t\t     four, three, and pairs.\n */\n\nvoid analyze_hand()\n{\n\tint num_consec = 0;\n\tint i, tmp;\n\n\tsort_by_rank(cards, NUM_CARDS);\n\n\tstraight = FALSE;\n\tbig_flush = FALSE;\n\tflush = FALSE;\n\tfour = FALSE;\n\tthree = FALSE;\n\tpairs = 0;\n\n\t/* check for flush */\n\tflush = TRUE;\n\tfor (i = 1; i < NUM_CARDS; i++) {\n\t\tif (cards[i][1] != cards[0][1])\n\t\t\tflush = FALSE;\n\t}\n\n\t/* check for straight */\n\tstraight = TRUE;\n\tfor (i = 1; i < NUM_CARDS; i++) {\n\t\tif (cards[i][0] != cards[i - 1][0] + 1)\n\t\t\tstraight = FALSE;\n\t}\n\n\t/* check for big flush */\n\tif (straight && flush) {\n\t\tif (cards[0][0] == 8)\n\t\t\tbig_flush = TRUE;\n\t}\n\n\t/* check for 4-of-kind, 3-of-kind, and pairs */\n\ttmp = cards[0][0];\n\tnum_consec = 1;\n\tfor (i = 1; i < NUM_CARDS; i++) {\n\t\tif (cards[i][0] == tmp) {\n\t\t\tnum_consec++;\n\t\t\tif (num_consec == 4) four = TRUE;\n\t\t\tif (num_consec == 3) three = TRUE;\n\t\t\tif (num_consec == 2) pairs++;\n\t\t}\n\t\telse {\n\t\t\ttmp = cards[i][0];\n\t\t\tnum_consec = 1;\n\t\t}\n\t}\n}\n\n/*\n * print_result: Notifies the user of the result, using\n * \t\t\t     the external variables straight, flush,\n * \t\t\t     four, three, and pairs.\n */\nvoid print_result()\n{\n\tif (big_flush)\t\t\tprintf(\"Big flush\\n\\n\");\n\telse if (straight && flush) \tprintf(\"Straight flush\\n\\n\");\n\telse if (four)\t\t\tprintf(\"Four of a kind\\n\\n\");\n\telse if (three &&\n\t\t\t pairs == 1)\tprintf(\"Full house\\n\\n\");\n\telse if (flush)\t\t\tprintf(\"Flush\\n\\n\");\n\telse if (straight)\t\tprintf(\"Straight\\n\\n\");\n\telse if (three)\t\t\tprintf(\"Three of a kind\\n\\n\");\n\telse if (pairs == 2)\tprintf(\"Two Pair\\n\\n\");\n\telse if (pairs == 1)\tprintf(\"Pair\\n\\n\");\n\telse\t\t\t\t\tprintf(\"High card\\n\\n\");\n}\n"
  },
  {
    "path": "codes/CProgramming/ch10_程序结构/ex_06.c",
    "content": "/*\n * 修改 poker.c 程序，使其允许“小A顺”（即A、2、3、4和5）。\n */\n\n/*\n * Classifies a poker hand\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#define NUM_RANKS 13\n#define NUM_SUITS 4\n#define NUM_CARDS 5\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Bool;\n\nint cards[NUM_CARDS][2]; /* 第一列为 rank ，第二列为 suit */\nBool straight, small_flush, flush, four, three;\nint pairs; /* can be 0, 1, or 2 */\n\nvoid sort_by_rank(int cards[][2], int count);\nBool card_exists(int cards[][2], int count, int rank, int suit);\nvoid read_cards(void);\nvoid analyze_hand(void);\nvoid print_result(void);\n\n/*\n * main: Calls read_cards, analyse_hand, and print_result\n * \t\t repeatedly.\n */\nint main()\n{\n\tfor (;;) { /* infinite loop */\n\t\tread_cards();\n\t\tanalyze_hand();\n\t\tprint_result();\n\t}\n\n\treturn 0;\n}\n\n/*\n * sort_by_rank: 按照 rank 将 cards 排序（升序）\n */\nvoid sort_by_rank(int cards[][2], int count)\n{\n\tint i, max_index, tmp;\n\n\tif (count <= 1) return;\n\n\tmax_index = 0;\n\tfor (i = 1; i < count; i++) {\n\t\tif (cards[i][0] > cards[max_index][0])\n\t\t\tmax_index = i;\n\t}\n\n\ttmp = cards[count - 1][0];\n\tcards[count - 1][0] = cards[max_index][0];\n\tcards[max_index][0] = tmp;\n\n\ttmp = cards[count - 1][1];\n\tcards[count - 1][1] = cards[max_index][1];\n\tcards[max_index][1] = tmp;\n\n\tsort_by_rank(cards, count - 1);\n}\n\n/*\n * card_exists: 查询 rank 和 suit 代表的卡牌是否已经存在于 cards\n */\nBool card_exists(int cards[][2], int count, int rank, int suit)\n{\n\tint i;\n\n\tfor (i = 0; i < count; i++) {\n\t\tif (cards[i][0] == rank && cards[i][1] == suit)\n\t\t\treturn TRUE;\n\t}\n\n\treturn FALSE;\n}\n\n/*\n * read_cards: Reads the cards into the external\n * \t\t\t   variables num_in_rank and num_in_suit;\n * \t\t\t   checks for bad cards and duplicate cards.\n */\nvoid read_cards(void)\n{\n\tchar ch, rank_ch, suit_ch;\n\tint i, j, rank, suit;\n\tBool bad_card;\n\tint cards_read = 0;\n\n\tfor (i = 0; i < NUM_CARDS; i++) {\n\t\tfor (j = 0; j < 2; j++) {\n\t\t\tcards[i][j] = -1;\n\t\t}\n\t}\n\n\twhile (cards_read < NUM_CARDS) {\n\t\tbad_card = FALSE;\n\n\t\tprintf(\"Enter a card: \");\n\n\t\trank_ch = getchar();\n\t\tswitch(rank_ch) {\n\t\t\tcase '0':\t\t\texit(EXIT_SUCCESS); break;\n\t\t\tcase '2':\t\t\trank = 0; break;\n\t\t\tcase '3':\t\t\trank = 1; break;\n\t\t\tcase '4':\t\t\trank = 2; break;\n\t\t\tcase '5':\t\t\trank = 3; break;\n\t\t\tcase '6':\t\t\trank = 4; break;\n\t\t\tcase '7':\t\t\trank = 5; break;\n\t\t\tcase '8':\t\t\trank = 6; break;\n\t\t\tcase '9':\t\t\trank = 7; break;\n\t\t\tcase 't': case 'T':\trank = 8; break;\n\t\t\tcase 'j': case 'J':\trank = 9; break;\n\t\t\tcase 'q': case 'Q': rank = 10;break;\n\t\t\tcase 'k': case 'K': rank = 11;break;\n\t\t\tcase 'a': case 'A': rank = 12;break;\n\t\t\tdefault:\t\t\tbad_card = TRUE;\n\t\t}\n\n\t\tsuit_ch = getchar();\n\t\tswitch(suit_ch) {\n\t\t\tcase 'c': case 'C':\tsuit = 0; break;\n\t\t\tcase 'd': case 'D': suit = 1; break;\n\t\t\tcase 'h': case 'H': suit = 2; break;\n\t\t\tcase 's': case 'S': suit = 3; break;\n\t\t\tdefault:\t\t\tbad_card = TRUE;\n\t\t}\n\n\t\twhile ((ch = getchar()) != '\\n')\n\t\t\tif (ch != ' ') bad_card = TRUE;\n\n\t\tif (bad_card)\n\t\t\tprintf(\"Bad card; ignored.\\n\");\n\t\telse if (card_exists(cards, cards_read, rank, suit))\n\t\t\tprintf(\"Duplicate card; ignored.\\n\");\n\t\telse {\n\t\t\tcards[cards_read][0] = rank;\n\t\t\tcards[cards_read][1] = suit;\n\t\t\tcards_read++;\n\t\t}\n\t}\n}\n\n/*\n * analyze_hand: Determines whether the hand contains a\n * \t\t\t     straight, a flush, four-of-a-kind,\n * \t\t\t     and/or a three-of-a-kind; determines the\n * \t\t\t     number of pairs; stores the result into\n * \t\t\t     the external variables straight, flush,\n * \t\t\t     four, three, and pairs.\n */\n\nvoid analyze_hand()\n{\n\tint num_consec = 0;\n\tint i, tmp;\n\n\tsort_by_rank(cards, NUM_CARDS);\n\n\tstraight = FALSE;\n\tsmall_flush = FALSE;\n\tflush = FALSE;\n\tfour = FALSE;\n\tthree = FALSE;\n\tpairs = 0;\n\n\t/* check for flush */\n\tflush = TRUE;\n\tfor (i = 1; i < NUM_CARDS; i++) {\n\t\tif (cards[i][1] != cards[0][1])\n\t\t\tflush = FALSE;\n\t}\n\n\t/* check for straight */\n\tstraight = TRUE;\n\tfor (i = 1; i < NUM_CARDS; i++) {\n\t\tif (cards[i][0] != cards[i - 1][0] + 1)\n\t\t\tstraight = FALSE;\n\t}\n\n\t/* check for small flush */\n\tif (cards[NUM_CARDS - 1][0] == 12) {\n\t\tsmall_flush = TRUE;\n\t\tfor (i = 1; i < NUM_CARDS - 1; i++) {\n\t\t\tif (cards[i][0] != cards[i - 1][0] + 1)\n\t\t\t\tsmall_flush = FALSE;\n\t\t}\n\t}\n\n\t/* check for 4-of-kind, 3-of-kind, and pairs */\n\ttmp = cards[0][0];\n\tnum_consec = 1;\n\tfor (i = 1; i < NUM_CARDS; i++) {\n\t\tif (cards[i][0] == tmp) {\n\t\t\tnum_consec++;\n\t\t\tif (num_consec == 4) four = TRUE;\n\t\t\tif (num_consec == 3) three = TRUE;\n\t\t\tif (num_consec == 2) pairs++;\n\t\t}\n\t\telse {\n\t\t\ttmp = cards[i][0];\n\t\t\tnum_consec = 1;\n\t\t}\n\t}\n}\n\n/*\n * print_result: Notifies the user of the result, using\n * \t\t\t     the external variables straight, flush,\n * \t\t\t     four, three, and pairs.\n */\nvoid print_result()\n{\n\tif (straight && flush) \tprintf(\"Straight flush\\n\\n\");\n\telse if (small_flush)\tprintf(\"Small flush\\n\\n\");\n\telse if (four)\t\t\tprintf(\"Four of a kind\\n\\n\");\n\telse if (three &&\n\t\t\t pairs == 1)\tprintf(\"Full house\\n\\n\");\n\telse if (flush)\t\t\tprintf(\"Flush\\n\\n\");\n\telse if (straight)\t\tprintf(\"Straight\\n\\n\");\n\telse if (three)\t\t\tprintf(\"Three of a kind\\n\\n\");\n\telse if (pairs == 2)\tprintf(\"Two Pair\\n\\n\");\n\telse if (pairs == 1)\tprintf(\"Pair\\n\\n\");\n\telse\t\t\t\t\tprintf(\"High card\\n\\n\");\n}\n"
  },
  {
    "path": "codes/CProgramming/ch10_程序结构/ex_07.c",
    "content": "/*\n * 编写下列函数：\n *\n * int *find_middle(int a[], int n);\n *\n * 当传递长度为n的数组a时，函数将返回指向数组的中间元素的指针。（如果n是偶数，选择较大下标的\n * 中间元素。例如，如果n=4，中间元素是a[2]，不是a[1]。）\n */\n\n#include <stdio.h>\n\nint *find_middle(int a[], int n);\n\nint main()\n{\n\tint a[5] = {1, 4, 3, 10, 42};\n\tint b[4] = {42, 100, 10, 2};\n\n\tprintf(\"middle of a: %d\\n\", *find_middle(a, 5));\n\tprintf(\"middle of b: %d\\n\", *find_middle(b, 4));\n\n\treturn 0;\n}\n\nint *find_middle(int a[], int n)\n{\n\treturn &a[n/2];\n}\n"
  },
  {
    "path": "codes/CProgramming/ch10_程序结构/guess.c",
    "content": "/* Asks user to guess a hidden number */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <time.h>\n\n#define MAX_NUMBER 100\n\n/* prototypes */\nint new_secret_number();\nvoid read_guesses(int secret_number);\n\nint main()\n{\n\tchar command;\n\tint secret_number;\n\n\tprintf(\"Guess the secret_number between 1 and %d.\\n\", MAX_NUMBER);\n\tsrand((unsigned int)time(0));\n\n\tdo\n\t{\n\t\tsecret_number = new_secret_number();\n\n\t\tprintf(\"A new number has been choosen.\\n\");\n\n\t\tread_guesses(secret_number);\n\n\t\tprintf(\"Play again? [Y/N] \");\n\t\tscanf(\" %c\", &command);\n\t\tprintf(\"\\n\");\n\n\t} while (command == 'Y' || command == 'y');\n\n\treturn 0;\n}\n\n\n/*\n @return: 生成一个新的随机数\n*/\nint new_secret_number()\n{\n\treturn rand() % MAX_NUMBER + 1;\n}\n\n/*\n 读取用户输入，判断是否符合secret_number，并给予提示\n*/\nvoid read_guesses(int secret_number)\n{\n\tint guess_number, guess_times;\n\n\tfor (;;)\n\t{\n\t\t++ guess_times;\n\n\t\tprintf(\"Enter guesses: \");\n\t\tscanf(\"%d\", &guess_number);\n\n\t\tif (guess_number == secret_number)\n\t\t{\n\t\t\tprintf(\"You won in %d guess(es)\\n\", guess_times);\n\t\t\tbreak;\n\t\t}\n\t\telse if (guess_number > secret_number)\n\t\t{\n\t\t\tprintf(\"Too high, try again.\\n\");\n\t\t}\n\t\telse\n\t\t{\n\t\t\tprintf(\"Too low, try again.\\n\");\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "codes/CProgramming/ch10_程序结构/poker.c",
    "content": "/*\n * Classifies a poker hand\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#define NUM_RANKS 13\n#define NUM_SUITS 4\n#define NUM_CARDS 5\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Bool;\n\nint num_in_rank[NUM_RANKS];\nint num_in_suit[NUM_SUITS];\nBool straight, flush, four, three;\nint pairs; /* can be 0, 1, or 2 */\n\nvoid read_cards(void);\nvoid analyze_hand(void);\nvoid print_result(void);\n\n/*\n * main: Calls read_cards, analyse_hand, and print_result\n * \t\t repeatedly.\n */\nint main()\n{\n\tfor (;;) { /* infinite loop */\n\t\tread_cards();\n\t\tanalyze_hand();\n\t\tprint_result();\n\t}\n\n\treturn 0;\n}\n\n/*\n * read_cards: Reads the cards into the external\n * \t\t\t   variables num_in_rank and num_in_suit;\n * \t\t\t   checks for bad cards and duplicate cards.\n */\nvoid read_cards(void)\n{\n\tBool card_exists[NUM_RANKS][NUM_SUITS];\n\tchar ch, rank_ch, suit_ch;\n\tint rank, suit;\n\tBool bad_card;\n\tint cards_read = 0;\n\n\tfor (rank = 0; rank < NUM_RANKS; rank++) {\n\t\tnum_in_rank[rank] = 0;\n\t\tfor (suit = 0; suit < NUM_SUITS; ++suit) {\n\t\t\tcard_exists[rank][suit] = FALSE;\n\t\t}\n\t}\n\n\tfor (suit = 0; suit < NUM_SUITS; suit++) {\n\t\tnum_in_suit[suit] = 0;\n\t}\n\n\twhile (cards_read < NUM_CARDS) {\n\t\tbad_card = FALSE;\n\n\t\tprintf(\"Enter a card: \");\n\n\t\trank_ch = getchar();\n\t\tswitch(rank_ch) {\n\t\t\tcase '0':\t\t\texit(EXIT_SUCCESS); break;\n\t\t\tcase '2':\t\t\trank = 0; break;\n\t\t\tcase '3':\t\t\trank = 1; break;\n\t\t\tcase '4':\t\t\trank = 2; break;\n\t\t\tcase '5':\t\t\trank = 3; break;\n\t\t\tcase '6':\t\t\trank = 4; break;\n\t\t\tcase '7':\t\t\trank = 5; break;\n\t\t\tcase '8':\t\t\trank = 6; break;\n\t\t\tcase '9':\t\t\trank = 7; break;\n\t\t\tcase 't': case 'T':\trank = 8; break;\n\t\t\tcase 'j': case 'J':\trank = 9; break;\n\t\t\tcase 'q': case 'Q': rank = 10;break;\n\t\t\tcase 'k': case 'K': rank = 11;break;\n\t\t\tcase 'a': case 'A': rank = 12;break;\n\t\t\tdefault:\t\t\tbad_card = TRUE;\n\t\t}\n\n\t\tsuit_ch = getchar();\n\t\tswitch(suit_ch) {\n\t\t\tcase 'c': case 'C':\tsuit = 0; break;\n\t\t\tcase 'd': case 'D': suit = 1; break;\n\t\t\tcase 'h': case 'H': suit = 2; break;\n\t\t\tcase 's': case 'S': suit = 3; break;\n\t\t\tdefault:\t\t\tbad_card = TRUE;\n\t\t}\n\n\t\twhile ((ch = getchar()) != '\\n')\n\t\t\tif (ch != ' ') bad_card = TRUE;\n\n\t\tif (bad_card)\n\t\t\tprintf(\"Bad card; ignored.\\n\");\n\t\telse if (card_exists[rank][suit])\n\t\t\tprintf(\"Duplicate card; ignored.\\n\");\n\t\telse {\n\t\t\tnum_in_rank[rank]++;\n\t\t\tnum_in_suit[suit]++;\n\t\t\tcard_exists[rank][suit] = TRUE;\n\t\t\tcards_read++;\n\t\t}\n\t}\n}\n\n/*\n * analyze_hand: Determines whether the hand contains a\n * \t\t\t     straight, a flush, four-of-a-kind,\n * \t\t\t     and/or a three-of-a-kind; determines the\n * \t\t\t     number of pairs; stores the result into\n * \t\t\t     the external variables straight, flush,\n * \t\t\t     four, three, and pairs.\n */\n\nvoid analyze_hand()\n{\n\tint num_consec = 0;\n\tint rank, suit;\n\n\tstraight = FALSE;\n\tflush = FALSE;\n\tfour = FALSE;\n\tthree = FALSE;\n\tpairs = 0;\n\n\t/* check for flush */\n\tfor (suit = 0; suit < NUM_SUITS; suit++) {\n\t\tif (num_in_suit[suit] == NUM_CARDS)\n\t\t\tflush = TRUE;\n\t}\n\n\t/* check for straight */\n\trank = 0;\n\twhile (num_in_rank[rank] == 0) rank++;\n\tfor (; rank < NUM_RANKS && num_in_rank[rank]; ++rank)\n\t\tnum_consec++;\n\tif (num_consec == NUM_CARDS) {\n\t\tstraight = TRUE;\n\t\treturn;\n\t}\n\n\t/* check for 4-of-kind, 3-of-kind, and pairs */\n\tfor (rank = 0; rank < NUM_RANKS; rank++) {\n\t\tif (num_in_rank[rank] == 4) four = TRUE;\n\t\tif (num_in_rank[rank] == 3) three = TRUE;\n\t\tif (num_in_rank[rank] == 2) pairs++;\n\t}\n}\n\n/*\n * print_result: Notifies the user of the result, using\n * \t\t\t     the external variables straight, flush,\n * \t\t\t     four, three, and pairs.\n */\nvoid print_result()\n{\n\tif (straight && flush) \tprintf(\"Straight flush\\n\\n\");\n\telse if (four)\t\t\tprintf(\"Four of a kind\\n\\n\");\n\telse if (three &&\n\t\t\t pairs == 1)\tprintf(\"Full house\\n\\n\");\n\telse if (flush)\t\t\tprintf(\"Flush\\n\\n\");\n\telse if (straight)\t\tprintf(\"Straight\\n\\n\");\n\telse if (three)\t\t\tprintf(\"Three of a kind\\n\\n\");\n\telse if (pairs == 2)\tprintf(\"Two Pair\\n\\n\");\n\telse if (pairs == 1)\tprintf(\"Pair\\n\\n\");\n\telse\t\t\t\t\tprintf(\"High card\\n\\n\");\n}\n"
  },
  {
    "path": "codes/CProgramming/ch10_程序结构/stack.c",
    "content": "#include <stdio.h>\n\n#define STACK_SIZE 100\n\n/* external variables */\nint contents[STACK_SIZE];\nint top = 0;\n\nvoid make_empty()\n{\n\ttop = 0;\n}\n\nint is_empty()\n{\n\treturn 0 == top;\n}\n\nint is_full()\n{\n\treturn STACK_SIZE == top;\n}\n\nvoid push(int i)\n{\n\tif ( !is_full())\n\t{\n\t\tcontents[top ++] = i;\n\t}\n}\n\nint pop()\n{\n\tif ( !is_empty())\n\t{\n\t\treturn contents[-- top];\n\t}\n\n\treturn -1;\n}\n\n//-------------------------------------\n\nint main()\n{\n\tint i;\n\n\tfor (i = 0; i < 10; ++ i)\n\t{\n\t\tpush(i);\n\t}\n\n\twhile ( !is_empty())\n\t{\n\t\tprintf(\"%d\\n\", pop());\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch11_指针/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch11_指针/ex_01.md",
    "content": "如果i是变量，并且p指向i，那么下列哪个表达式是i的别名？\n\n(a) *p\n\n(b) &p\n\n(c) *&p\n\n(d) &*p\n\n(e) *i\n\n(f) &i\n\n(g) *&i\n\n(h) &*i\n\n---\n\n(a) (g)\n"
  },
  {
    "path": "codes/CProgramming/ch11_指针/ex_02.md",
    "content": "如果i是 int 型变量，而且p和q是指向 int 的指针，下列哪个赋值是合法的？\n\n(a) p = i;\n\n(b) *p = &i;\n\n(c) &p = q;\n\n(d) p = &p;\n\n(e) p = &p;\n\n(f) p = q;\n\n(g) p = *q;\n\n(h) *p = q;\n\n(i) *p = *q;\n\n---\n\n(f) (i)\n"
  },
  {
    "path": "codes/CProgramming/ch11_指针/ex_03.c",
    "content": "/*\n * 下列函数假设用来计算数组a中元素的和以及平均值，且数组a长度为n。 avg 和 sum 指向函数需要修改\n * 的变量。函数有几个错误，请找出这些错误并修改它们。\n *\n * void avg_sum(float a[], int n, float *avg, float *sum)\n * {\n * \tint i;\n * \t\n * \tsum = 0.0\n * \tfor (i = 0; i < n; i++)\n * \t\tsum += a[i];\n *\tavg = sum / n;\n * }\n */\n\n#include <stdio.h>\n\nvoid avg_sum(float a[], int n, float *avg, float *sum)\n{\n\tint i;\n\n\t*sum = 0.0; /* 此处修改 sum -> *sum */\n\tfor (i = 0; i < n; i++)\n\t\t*sum += a[i]; /* 此处修改 sum -> *sum */\n\t*avg = *sum / n; /* 此处修改 sum -> *sum avg -> *avg */\n}\n\nint main()\n{\n\tfloat a[3] = {1.1, 2.2, 3.3};\n\tfloat avg, sum;\n\n\tavg_sum(a, 3, &avg, &sum);\n\n\tprintf(\"avg: %f, sum: %f\\n\", avg, sum);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch11_指针/ex_04.c",
    "content": "/*\n * 编写下列函数：\n *\n * void swap(int *x, int *y);\n *\n * 当传递两个变量的地址时， swap 函数应该交换两者的值：\n *\n * swap(&x, &y);\n *\n * 利用此函数修改第9章中练习9的程序，使它可以完成这项工作。\n */\n\n#include <stdio.h>\n\nvoid swap(int *a, int *b);\n\nint main()\n{\n\tint x = 1, y = 2;\n\n\tswap(&x, &y);\n\tprintf(\"x = %d, y = %d\\n\", x, y);\n\n\treturn 0;\n}\n\nvoid swap(int *a, int *b)\n{\n\tint temp;\n\n\ttemp = *a;\n\t*a = *b;\n\t*b = temp;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch11_指针/ex_05.c",
    "content": "/*\n * 编写下列函数：\n *\n * void split_time(long int total_sec,\n * \t\t\t\t   int *hr, int *min, int *sec);\n *\n * total_sec 是从午夜计算的秒数表示的时间。hr、min、和sec都是指向变量的指针，这些变量在\n * 函数中分别存储着按小时算（0~23）、按分钟算（0~59）和按秒算（0~59）的等价时间。\n */\n\n#include <stdio.h>\n\nvoid split_time(long int total_sec, int *hr, int *min, int *sec);\n\nint main()\n{\n\tlong int total_sec;\n\tint hr, min, sec;\n\n\tprintf(\"Enter sec: \");\n\tscanf(\"%ld\", &total_sec);\n\n\tsplit_time(total_sec, &hr, &min, &sec);\n\n\tprintf(\"%2.2d:%2.2d:%2.2d\\n\", hr, min, sec);\n\n\treturn 0;\n}\n\nvoid split_time(long int total_sec, int *hr, int *min, int *sec)\n{\n\t*hr = total_sec / 3600;\n\t*min = total_sec / 60 % 60;\n\t*sec = total_sec % 60;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch11_指针/ex_06.c",
    "content": "/*\n * 编写下列函数：\n *\n * void find_two_largest(int a[], int n, int *largest, int *second_largest);\n *\n * 当传递长度为n的数组a时，函数将在数组a中搜寻最大元素和第二大元素，把它们分别\n * 存储在 largest 和 second_largest 指向的变量中。\n */\n\n#include <stdio.h>\n\nvoid find_two_largest(int a[], int n, int *largest, int *second_largest);\n\nint main()\n{\n\tint a[5] = {4, 41, 42, 100, 9};\n\tint largest, second_largest;\n\n\tfind_two_largest(a, 5, &largest, &second_largest);\n\n\tprintf(\"largest: %d, second_largest: %d\\n\", largest, second_largest);\n\n\treturn 0;\n}\n\nvoid find_two_largest(int a[], int n, int *largest, int *second_largest)\n{\n\tint i, tmp;\n\n\tif (n <= 1) {\n\t\t*largest = *second_largest = a[0];\n\t\treturn;\n\t}\n\n\t*largest = a[0];\n\t*second_largest = a[1];\n\n\tif (*largest < *second_largest) {\n\t\ttmp = *largest;\n\t\t*largest = *second_largest;\n\t\t*second_largest = tmp;\n\t}\n\n\tfor (i = 2; i < n; i++) {\n\t\tif (*second_largest < a[i]) {\n\t\t\tif (*largest < a[i]) {\n\t\t\t\t*second_largest = *largest;\n\t\t\t\t*largest = a[i];\n\t\t\t}\n\t\t\telse if (a[i] != *largest) {\n\t\t\t\t*second_largest = a[i];\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "codes/CProgramming/ch11_指针/maxmin.c",
    "content": "/*\n * Finds the largest and smallest elements in an array\n */\n\n#include <stdio.h>\n\n#define N 10\n\nvoid max_min(int a[], int n, int *max, int *min);\n\nint main()\n{\n\tint b[N], i, big, small;\n\n\tprintf(\"Enter %d number: \", N);\n\tfor (i = 0; i < N; i++)\n\t\tscanf(\"%d\", &b[i]);\n\n\tmax_min(b, N, &big, &small);\n\n\tprintf(\"Largest: %d\\n\", big);\n\tprintf(\"Smallest: %d\\n\", small);\n\n\treturn 0;\n}\n\nvoid max_min(int a[], int n, int *max, int *min)\n{\n\tint i;\n\n\t*max = *min = a[0];\n\tfor (i = 1; i < N; i++) {\n\t\tif (a[i] > *max)\n\t\t\t*max = a[i];\n\t\telse if (a[i] < *min)\n\t\t\t*min = a[i];\n\t}\n}\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_01.c",
    "content": "/*\n * 假设下列声明是有效的：\n *\n * int a[] = {5, 15, 34, 54, 14, 2, 52, 72};\n * int *p = &a[1], *q = &a[5];\n *\n * (a) *(p + 3)的值是多少？\n *\n * (b) *(q - 3)的值是多少？\n *\n * (c) p-q的值是多少？\n *\n * (d) p<q的结果是真还是假？\n *\n * (e) *p<*q的结果是真还是假？\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tint a[] = {5, 15, 34, 54, 14, 2, 52, 72};\n\tint *p = &a[1], *q = &a[5];\n\n\t/* (a) 14 */\n\tprintf(\"(a) %d\\n\", *(p + 3));\n\n\t/* (b) 34 */\n\tprintf(\"(b) %d\\n\", *(q - 3));\n\n\t/* (c) -4 */\n\tprintf(\"(c) %ld\\n\", p-q);\n\n\t/* (d) true */\n\tprintf(\"(d) %s\\n\", p<q ? \"true\" : \"false\");\n\n\t/* (e) false */\n\tprintf(\"(e) %s\\n\", *p<*q ? \"true\" : \"false\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_02.md",
    "content": "假设 high, low 和 middle 都是具有相同类型的指针，并且 low 和 high 指向数组元素。下面的语句为什么是不合法的，如何修改它？\n\n```c\nmiddle = (low + hight) / 2;\n```\n\n---\n\n因为C语言不支持两个指针相加。改为：\n\n```c\nmiddle = low + (high - low) / 2;\n```\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_03.c",
    "content": "/*\n * 在下列语句执行后，数组a的内容会是什么？\n */\n\n/*\n * 反转a\n */\n\n#include <stdio.h>\n\n#define N 10\n\nint main()\n{\n\tint a[N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\tint *p = &a[0], *q = &a[N - 1], temp;\n\n\twhile (p < q) {\n\t\ttemp = *p;\n\t\t*p++ = *q;\n\t\t*q-- = temp;\n\t}\n\n\tfor (p = a; p < a + N; p++)\n\t\tprintf(\"%d \", *p);\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_04.c",
    "content": "/*\n * (a) 编写程序，用来读取一条消息，然后反向显示出这条消息。程序的输出格式如下：\n *\n * Enter a message: Don't get mad, get even.\n * Reversal is: .neve teg ,dam teg t'noD\n *\n * 提示：读消息一次读取一个字符（用 getchar 函数），并且把这些字符存储在数组中，档数组满了或\n * 者读到字符'\\n'时停止读操作。\n *\n * (b) 修改上述程序，用指针来代替整数来跟踪数组中的当前位置。\n */\n\n#include <stdio.h>\n\n#define LEN 30\n\nvoid solution_a()\n{\n\tchar str[LEN + 1] = {0};\n\tint ch;\n\tint i, count;\n\n\tprintf(\"Enter a message: \");\n\tcount = 0;\n\twhile (count < LEN && (ch = getchar()) != '\\n') {\n\t\tstr[count++] = (char)ch;\n\t}\n\n\tprintf(\"Reversal is: \");\n\tfor (i = count - 1; i >= 0; i--) {\n\t\tputchar(str[i]);\n\t}\n\tprintf(\"\\n\");\n}\n\nvoid solution_b()\n{\n\tchar str[LEN + 1] = {0};\n\tint ch;\n\tchar *p;\n\n\tprintf(\"Enter a message: \");\n\tp = str;\n\twhile (p != str + LEN && (ch = getchar()) != '\\n') {\n\t\t*p++ = (char)ch;\n\t}\n\n\tprintf(\"Reversal is: \");\n\twhile (p != str) {\n\t\tputchar(*--p);\n\t}\n\tprintf(\"\\n\");\n}\n\nint main()\n{\n\t//solution_a();\n\tsolution_b();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_05.c",
    "content": "/*\n * (a) 编写程序，用来读一条消息，然后检查这条消息是否是回文（信息中从左到右的字母和从右到左\n * 的字母完全一样）：\n *\n * Enter a message: He lived as a devil, eh?\n * Palindrome\n *\n * Enter a message: Madam, I am Adam.\n * Not a palindrome\n *\n * 忽略所有不是字母的字符。用整型变量来跟踪数组内的位置。\n * (b) 修改上述程序，使用指针来代替整数跟踪数组的位置。\n */\n\n#include <stdio.h>\n#include <ctype.h>\n\n#define LEN 30\n\nvoid solution_a()\n{\n\tchar str[LEN + 1] = {0};\n\tint i, j, count;\n\tint ch;\n\n\tprintf(\"Enter a message: \");\n\tcount = 0;\n\twhile (count < LEN && (ch = getchar()) != '\\n') {\n\t\tstr[count++] = (char)ch;\n\t}\n\n\ti = 0;\n\tj = count - 1;\n\n\twhile (i < j) {\n\t\tif (!isalpha(str[i])) { i++; continue; }\n\t\tif (!isalpha(str[j])) { j--; continue; }\n\n\t\tif (toupper(str[i]) != toupper(str[j])) {\n\t\t\tprintf(\"Not a palindrome\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\ti++;\n\t\tj--;\n\t}\n\n\tprintf(\"Palindrome\\n\");\n}\n\nvoid solution_b()\n{\n\tchar str[LEN + 1] = {0};\n\tint ch;\n\tchar *p, *q;\n\n\tprintf(\"Enter a message: \");\n\tp = str;\n\twhile (p != str + LEN && (ch = getchar()) != '\\n') {\n\t\t*p++ = (char)ch;\n\t}\n\n\tq = p - 1;\n\tp = str;\n\n\twhile (p < q) {\n\t\tif (!isalpha(*p)) { p++; continue; }\n\t\tif (!isalpha(*q)) { q--; continue; }\n\n\t\tif (toupper(*p) != toupper(*q)) {\n\t\t\tprintf(\"Not a palindrome\\n\");\n\t\t\treturn;\n\t\t}\n\n\t\tp++;\n\t\tq--;\n\t}\n\n\tprintf(\"Palindrome\\n\");\n}\n\nint main()\n{\n\t//solution_a();\n\tsolution_b();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_06.c",
    "content": "/*\n * 用指针变量 top_ptr 代替整型变量 top 来重新编写栈函数 make_empty、is_empty 和\n * is_full（10.2节）。\n */\n\n#include <stdio.h>\n\n#define STACK_SIZE 100\n\n/* external variables */\nint contents[STACK_SIZE];\nint *top_ptr = contents;\n\nvoid make_empty()\n{\n\ttop_ptr = contents;\n}\n\nint is_empty()\n{\n\treturn contents == top_ptr;\n}\n\nint is_full()\n{\n\treturn contents + STACK_SIZE == top_ptr;\n}\n\nvoid push(int i)\n{\n\tif ( !is_full())\n\t{\n\t\t*top_ptr++ = i;\n\t}\n}\n\nint pop()\n{\n\tif ( !is_empty())\n\t{\n\t\treturn *--top_ptr;\n\t}\n\n\treturn -1;\n}\n\n//-------------------------------------\n\nint main()\n{\n\tint i;\n\n\tfor (i = 0; i < 10; ++ i)\n\t{\n\t\tpush(i);\n\t}\n\n\twhile ( !is_empty())\n\t{\n\t\tprintf(\"%d\\n\", pop());\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_07.md",
    "content": "假设a是一维数组而p是指针变量。如果刚执行了赋值操作p = a，那么由于类型不匹配，下列哪些表达式是不合法的？正确的表达式中，哪些为真（即有非零值）？\n\n(a) p == a[0]\n\n(b) p == &a[0]\n\n(c) *p == a[0]\n\n(d) p[0] == a[0]\n\n---\n\n不合法的有：(a)\n\n为真的有：(b) (c) (d)\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_08.md",
    "content": "请利用数组名可以用作指针的事实简化练习4中(b)的程序。\n\n---\n\n略。\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_09.md",
    "content": "请利用数组名可以用作指针的事实简化练习5中(b)的程序。\n\n---\n\n略。\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_10.c",
    "content": "/*\n * 用指针的算术运算代替数组的下标来重新编写下列函数。（换句话说，消除变量i和所有用[]运算的\n * 地方。）改动尽可能少。\n *\n * int sum_array(int a[], int n)\n * {\n * \tint i, sum;\n *\n * \tsum = 0;\n * \tfor (i = 0; i < n; i++)\n * \t\tsum += a[i];\n * \treturn sum;\n * }\n */\n\n#include <stdio.h>\n\nint sum_array(int a[], int n)\n{\n\tint *p, sum;\n\n\tfor (p = a; p < a + n; p++)\n\t\tsum += *p;\n\treturn sum;\n}\n\nint main()\n{\n\tint a[4] = {1, 2, 3, 4};\n\n\tprintf(\"sum: %d\\n\", sum_array(a, 4));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_11.c",
    "content": "/*\n * 编写下列函数：\n *\n * Bool search(int a[], int n, int key);\n *\n * a是要搜寻的数组，n是数组内元素的数量，而且key是搜索键。如果key与数组a的某个元素匹配了，\n * 那么 search 函数必须返回 TRUE ，否则返回 FALSE 。要求使用指针算术运算而不是下标来访问数组元素。\n */\n\n#include <stdio.h>\n\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Bool;\n\nBool search(int a[], int n, int key)\n{\n\tint* p;\n\n\tfor (p = a; p != a + n; p++) {\n\t\tif (*p == key) return TRUE;\n\t}\n\treturn FALSE;\n}\n\nint main()\n{\n\tint a[4] = {1, 2, 3, 4};\n\n\tprintf(\"1 in array: %s\\n\", (search(a, 4, 1) ? \"yes\" : \"no\"));\n\tprintf(\"5 in array: %s\\n\", (search(a, 4, 5) ? \"yes\" : \"no\"));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_12.c",
    "content": "/*\n * 8.2节有一个代码段是用两个嵌套的for循环初始化数组 ident ，此数组是用作恒等矩阵。请重新编写\n * 这段代码，采用一个指针来逐步访问数组中的元素，且每次一个元素。提示：因为不能用 row 和 col\n * 来索引变量，所以不会容易知道哪里存储1。但是，可以利用数组的第一个元素必须是1这个事实，\n * 接着N个元素都必须是0，再接下来的元素是1，以此类推。用变量来跟踪连续0的数量，并把此变量存储\n * 起来。当计数达到N时，就是存储1的时候了。\n */\n\n#include <stdio.h>\n\n#define N 10\n\nvoid print(float ident[][N], int count)\n{\n\tint row, col;\n\n\tfor (row = 0; row < count; row++) {\n\t\tfor (col = 0; col < N; col++)\n\t\t\tprintf(\"%.1f \", ident[row][col]);\n\t\tprintf(\"\\n\");\n\t}\n}\n\nvoid book_version()\n{\n\tfloat ident[N][N];\n\tint row, col;\n\n\tfor (row = 0; row < N; row++)\n\t\tfor (col = 0; col < N; col++)\n\t\t\tif (row == col)\n\t\t\t\tident[row][col] = 1.0;\n\t\t\telse\n\t\t\t\tident[row][col] = 0.0;\n\n\tprint(ident, N);\n}\n\nvoid ex_version()\n{\n\tfloat ident[N][N];\n\tfloat *p;\n\tint cnt_zero;\n\n\tcnt_zero = 0;\n\tfor (p = &ident[0][0]; p <= &ident[N-1][N-1]; p++) {\n\t\tif (cnt_zero == 0) *p = 1.0;\n\t\telse *p = 0.0;\n\n\t\tcnt_zero++;\n\t\tif (cnt_zero > N) cnt_zero = 0;\n\t}\n\n\tprint(ident, N);\n}\n\nint main()\n{\n\t//book_version();\n\tex_version();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_13.c",
    "content": "/*\n * 假设下列数组含有一周24小时的温度读数，数组的每一行是某一天的读数：\n *\n * int temperatures[7][24];\n *\n * 编写语句，使用 search 函数在整个 temperatures 数组中寻找值32。\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Bool;\n\nBool search(int a[], int n, int key)\n{\n\tint* p;\n\n\tfor (p = a; p != a + n; p++) {\n\t\tif (*p == key) return TRUE;\n\t}\n\treturn FALSE;\n}\n\nint main()\n{\n\tint temperatures[7][24];\n\n\tmemset(temperatures, 0, sizeof(temperatures));\n\n\tif (search(&temperatures[0][0], 7 * 24, 32)) {\n\t\tprintf(\"Find\\n\");\n\t}\n\telse {\n\t\tprintf(\"Not Find\\n\");\n\t}\n\n\ttemperatures[1][22] = 32;\n\n\tif (search(&temperatures[0][0], 7 * 24, 32)) {\n\t\tprintf(\"Find\\n\");\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_14.c",
    "content": "/*\n * 编写循环用来显示出（练习13中的）temperatures数组中行i存储的所有温度读数。利用指针来访\n * 问该行中的每个元素。\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define TRUE 1\n#define FALSE 0\n\ntypedef int Bool;\n\nBool search(int a[], int n, int key)\n{\n\tint* p;\n\n\tfor (p = a; p != a + n; p++) {\n\t\tif (*p == key) return TRUE;\n\t}\n\treturn FALSE;\n}\n\nint main()\n{\n\tint temperatures[7][24] = {\n\t\t{0},\n\t\t{22, 25, 26, 0},\n\t\t{0},\n\t\t{0},\n\t\t{0},\n\t\t{0},\n\t\t{0},\n\t};\n\n\tint *p;\n\tint i = 1;\n\n\tfor (p = &temperatures[i][0]; p != &temperatures[i][24]; p++) {\n\t\tprintf(\"%d \", *p);\n\t}\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/ex_15.c",
    "content": "/*\n * 编写循环用来显示（练习13中的）temperatures数组一星期中每一天的最高温度。循环体应该调用\n * find_largest 函数，且一次传递数组的一行。\n */\n\n#include <stdio.h>\n\nint find_largest(int a[], int count)\n{\n\tint max;\n\tint i;\n\n\tmax = a[0];\n\tfor (i = 1; i < count; i++) {\n\t\tif (a[i] > max) max = a[i];\n\t}\n\n\treturn max;\n}\n\nint main()\n{\n\tint temperatures[7][24] = {\n\t\t{22, 21, 20},\n\t\t{20, 19, 17},\n\t\t{0},\n\t\t{0},\n\t\t{0},\n\t\t{0},\n\t\t{0},\n\t};\n\n\tint i;\n\n\tfor (i = 0; i < 7; i++) {\n\t\tprintf(\"max of day %d: %d\\n\", i+1, find_largest(&temperatures[i][0], 24));\n\t}\n\t\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch12_指针和数组/reverse2.c",
    "content": "/*\n * 程序：数列反向（改进版） \n */\n\n/* Reverses a series of numbers (pointer version) */\n\n#include <stdio.h>\n\n#define N 10\n\nint main()\n{\n\tint a[N], *p;\n\n\tprintf(\"Enter %d numbers: \", N);\n\tfor (p = a; p < a + N; p++)\n\t\tscanf(\"%d\", p);\n\n\tprintf(\"In reverse order:\");\n\tfor (p = a + N - 1; p >= a; p--)\n\t\tprintf(\" %d\", *p);\n\tprintf(\"\\n\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/count_space.c",
    "content": "/*\n * 统计字符串中空格的数量\n */\n\n#include <stdio.h>\n\nint count_space(const char *s)\n{\n\tint count = 0;\n\tfor (; *s != '\\0'; s++)\n\t\tif (*s == ' ')\n\t\t\tcount++;\n\treturn count;\n}\n\nint main()\n{\n\tprintf(\"space: %d\\n\", count_space(\"Hello World !!\"));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_01.md",
    "content": "下面的函数调用应该是写出单独一个换行符，但是其中有一些是错误的。请指出哪些调用是错误的，并说明理由。\n\n(a) printf(\"%c\", '\\n'); // 正确\n\n(b) printf(\"%c\", \"\\n\");\t// 错误，\"\\n\"的类型是指针，无法匹配转换说明\n\n(c) printf(\"%s\", '\\n');\t// 错误，'\\n'的类型不匹配转换说明符%s\n\n(d) printf(\"%s\", \"\\n\"); // 正确\n\n(e) printf('\\n');\t\t// 错误，printf 第一个参数类型是字符串\n\n(f) printf(\"\\n\");\t\t// 正确\n\n(g) putchar('\\n');\t\t// 正确\n\n(h) putchar(\"\\n\");\t\t// 错误，putchar 的参数类型是一个整数\n\n(i) puts('\\n');\t\t\t// 错误，puts 的参数类型是一个字符串\n\n(j) puts(\"\\n\");\t\t\t// 错误，puts 会自动附加一个换行符\n\n(k) puts(\"\");\t\t\t// 正确"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_02.md",
    "content": "假设p的定义如下所示：\n\n```c\nchar *p = \"abc\";\n```\n\n下列哪些函数调用是合法的？请说明每个合法的函数调用的输出，并解释为什么其他的是非法的。\n\n(a) putchar(p);\t\t// 非法的，参数类型不匹配\n\n(b) putchar(\\*p);\t// 合法，输出一个字符a\n\n(c) puts(p);\t\t// 合法，输出字符串\"abc\\n\"\n\n(d) puts(\\*p);\t\t// 非法的，参数类型不匹配"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_03.c",
    "content": "/*\n 假设按如下方式调用 scanf 函数：\n scanf(\"%d%s%d\", &i, s, &j);\n 如果用户输入12abc34 56def78，那么调用后i, s和j的值分别是多少？（假设i和j是int型变量，\n s是字符数组）\n*/\n\n/*\n i: 12\n s: abc34\n j: 56\n*/\n\n#include <stdio.h>\n\nint main()\n{\n\tint i, j;\n\tchar s[10];\n\n\tscanf(\"%d%s%d\", &i, s, &j);\n\n\tprintf(\"i: %d\\n\", i);\n\tprintf(\"s: %s\\n\", s);\n\tprintf(\"j: %d\\n\", j);\n\n\treturn 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_04.c",
    "content": "/*\n 按照下述要求分别实现 read_line 函数：\n\n (a) 在开始存储输入字符前跳过空白字符。\n (b) 在读入第一个空白字符时停止。提示：调用 isspace 函数来检查字符是否为空白字符。\n (c) 在读入第一个换行符时停止，然后把换行符存储到字符串中。\n (d) 把没有空间存储的字符留下以备后用\n*/\n\n#include <stdio.h>\n#include <ctype.h>\n\n#define LEN 20\n\nint read_line(char str[], int n)\n{\n\tchar ch;\n\tint i = 0;\n\n\twhile ((ch = getchar()) != '\\n') {\n\t\tif (i == 0 && isspace(ch))\n\t\t\tcontinue;\n\n\t\tif (i != 0 && isspace(ch))\n\t\t\tbreak;\n\n\t\tif (i < n - 1)\n\t\t\tstr[i++] = ch;\n\t}\n\n\tif (i < n - 1)\n\t{\n\t\tstr[i++] = '\\n';\n\t}\n\t\n\tstr[i] = '\\0';\n\n\treturn i;\n}\n\nint main()\n{\n\tchar str[LEN + 1];\n\tint len;\n\n\tlen = read_line(str, LEN);\n\tprintf(\"read %d charactors: %s\\n\", len, str);\n\tlen = read_line(str, LEN);\n\tprintf(\"read %d charactors: %s\\n\", len, str);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_05.c",
    "content": "/*\n * (a) 编写名为 strcap 的函数用来把参数中的字母都改为大写字母。参数是空字符结尾的字符串，且此\n * 字符串包含任意的 ASCII 字符。使用数组下标的方式访问字符串中的字符。提示：使用 toupper 函数\n * 把每个字符转换成大写。\n *\n * (b) 重写 strcap 函数，这次使用指针来访问字符串中的字符。\n */\n\n#include <stdio.h>\n#include <ctype.h>\n\nchar* strcap_a(char *s)\n{\n\tint i = 0;\n\twhile (s[i]) {\n\t\tif (isalpha(s[i]))\n\t\t\ts[i] = toupper(s[i]);\n\n\t\ti++;\n\t}\n\n\treturn s;\n}\n\nchar* strcap_b(char *s)\n{\n\tchar *p = s;\n\twhile (*p) {\n\t\tif (isalpha(*p))\n\t\t\t*p = toupper(*p);\n\t\tp++;\n\t}\n\treturn s;\n}\n\nint main()\n{\n\tchar s[] = \"Hello World\";\n\n\t//printf(\"to upper: %s\\n\", strcap_a(s));\n\tprintf(\"to upper: %s\\n\", strcap_b(s));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_06.c",
    "content": "/*\n * 编写名为 censor 的函数，用来把字符串中出现的每一处字母\"foo\"替换为\"xxx\"。例如，字符串\n * \"food fool\"会变为\"xxxd xxxl\"。在不失清晰性的前提下程序越短越好。\n */\n\n#include <stdio.h>\n#include <string.h>\n\nchar* censor(char *s)\n{\n\tchar* cursor;\n\tchar* end;\n\tint i;\n\n\tcursor = s;\n\tend = s + strlen(s);\n\n\twhile (cursor + 3 < end) {\n\t\tif (0 == strncmp(cursor, \"foo\", 3)) {\n\t\t\tfor (i = 0; i < 3; i++)\n\t\t\t\t*cursor++ = 'x';\n\t\t}\n\t\telse\n\t\t\tcursor++;\n\t}\n\n\treturn s;\n}\n\nint read_line(char str[], int n)\n{\n\tchar ch;\n\tint i = 0;\n\n\twhile ((ch = getchar()) != '\\n') {\n\t\tif (i < n)\n\t\t\tstr[i++] = ch;\n\t}\n\tstr[i] = '\\0';\n\n\treturn i;\n}\n\nint main()\n{\n\tchar s[20];\n\n\tprintf(\"Enter str: \");\n\tread_line(s, 20);\n\n\n\tprintf(\"s: %s\\n\", censor(s));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_07.c",
    "content": "/*\n * 下面程序的输出是什么？\n *\n * #include <stdio.h>\n *\n * main()\n * {\n * \tchar s[] = \"Hsjodi\", *p;\n *\n * \tfor (p = &s[5]; p >= s; p--) --*p;\n * \tputs(s);\n * \treturn 0;\n * }\n */\n\n/*\n * Grinch\n */\n\n#include <stdio.h>\n\nint main()\n{\n\tchar s[] = \"Hsjodi\", *p;\n\tfor (p = &s[5]; p >= s; p--) --*p;\n\t\tputs(s);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_08.c",
    "content": "/*\n * 函数f如下所示：\n *\n * int f(char *s, char *t)\n * {\n * \tchar *p1, *p2;\n *\n * \tfor (p1 = s; *p1; p1++) {\n * \t\tfor (p2 = t; *p2; p2++)\n * \t\t\tif (*p1 == *p2) break;\n * \t\tif (*p2 == '\\0') break;\n * \t}\n * \treturn p1 - s;\n * }\n *\n * (a) f(\"abcd\", \"babc\") 的值是多少？\n * (b) f(\"abcd\", \"bcd\") 的值是多少？\n * (c) 通常情况下，当传递两个字符串s和t时，函数的返回值是什么？\n */\n\n/*\n * (a) 3\n * (b) 0\n * (c) 遍历s，对于当前的字符，如果不存在于t中，则返回这个字符所在的位置。\n */\n\n#include <stdio.h>\n\nint f(char *s, char *t)\n{\n\tchar *p1, *p2;\n\tfor (p1 = s; *p1; p1++) {\n\t\tfor (p2 = t; *p2; p2++)\n\t\t\tif (*p1 == *p2) break;\n\t\tif (*p2 == '\\0') break;\n\t}\n\treturn p1 - s;\n}\n\nint main()\n{\n\tprintf(\"%d\\n\", f(\"abcd\", \"babc\"));\n\tprintf(\"%d\\n\", f(\"abcd\", \"bcd\"));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_09.md",
    "content": "假设 str 是字符数组，下面哪条语句与其他3条语句不等价？\n\n(a) *str = 0;\n\n(b) str[0] = '\\0';\n\n(c) strcpy(str, \"\");\n\n(d) strcat(str, \"\");\n\n---\n\n(d)\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_10.c",
    "content": "/*\n * 在执行下列语句后，字符串 str 的值是什么？\n *\n * strcpy(str, \"tire-bouchon\");\n * strcpy(&str[4], \"d-or-wi\");\n * strcat(str, \"red?\");\n */\n\n/*\n * tired-or-wired?\n */\n\n#include <stdio.h>\n#include <string.h>\n\nint main()\n{\n\tchar str[20];\n\n\tstrcpy(str, \"tire-bouchon\");\n\tstrcpy(&str[4], \"d-or-wi\");\n\tstrcat(str, \"red?\");\n\n\tprintf(\"%s\\n\", str);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_11.c",
    "content": "/*\n * 在执行下列语句后，字符串s1与s2的值各是多少？\n *\n * strcpy(s1, \"computer\");\n * strcpy(s2, \"science\");\n * if (strcmp(s1, s2) < 0)\n * \tstrcat(s1, s2);\n * else\n * \tstrcat(s2, s1);\n * s2[strlen(s2)-6] = '\\0';\n */\n\n/*\n * s1: computerscience\n * s2: s\n */\n\n#include <stdio.h>\n#include <string.h>\n\nint main()\n{\n\tchar s1[20], s2[20];\n\n\tstrcpy(s1, \"computer\");\n\tstrcpy(s2, \"science\");\n\tif (strcmp(s1, s2) < 0)\n\t\tstrcat(s1, s2);\n\telse\n\t\tstrcat(s2, s1);\n\ts2[strlen(s2)-6] = '\\0';\n\n\tprintf(\"s1: %s\\n\", s1);\n\tprintf(\"s2: %s\\n\", s2);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_12.md",
    "content": "下面的函数假设用来创建字符串的相同副本。请指出这个函数中的错误？\n\n```c\nchar *strdup(const char *p)\n{\n\tchar *q;\n\tstrcpy(q, p);\n\treturn q;\n}\n```\n\n---\n\nq没有指向一个可用的字符数组，无法拷贝字符串。\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_13.c",
    "content": "/*\n * 在本章的末尾“问与答”小节说明了利用数组下标的方式来编写 strcmp 函数的方法。请用指针算术\n * 运算的方法来修改此函数。\n */\n\n#include <stdio.h>\n\nint strcmp_(char *s, char *t)\n{\n\twhile (*s == *t) {\n\t\tif (*s == '\\0')\n\t\t\treturn 0;\n\n\t\ts++;\n\t\tt++;\n\t}\n\treturn *s - *t;\n}\n\nint main()\n{\n\tprintf(\"%d\\n\", strcmp_(\"Hello\", \"Hi\"));\n\tprintf(\"%d\\n\", strcmp_(\"Hi\", \"Hi\"));\n\tprintf(\"%d\\n\", strcmp_(\"Hi\", \"Hello\"));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_14.c",
    "content": "/*\n * 编写程序用来找到一组单词中“最大”单词和“最小”单词。当用户输入单词后，程序根据字典的排序\n * 决定排在最前面和最后面的单词。当用户输入了4个字母的单词时，程序必须停止读入。假设所有\n * 单词都不超过20个字母。程序与用户的交互显示如下所示：\n *\n * Enter word: dog\n * Enter word: zebra\n * Enter word: rabbit\n * Enter word: catfish\n * Enter word: walrus\n * Enter word: cat\n * Enter word: fish\n * Smallest word: cat\n * Largest word: zebra\n *\n * 提示：使用两个名为 smallest_word 和 largest_word 的字符串来记录当前输入“最小”单词和\n * “最大”单词。每次用户输入新单词，就用 strcmp 函数把它与 smallest_word 进行比较。如果\n * 新的单词比 smallest_word 小，就用 strcpy 函数把新单词保存到 smallest_word 中。用类似\n * 的方式与 largest_word 也进行比较。用 strlen 函数用来判断用户输入4个字符的单词的时候。\n */\n\n#include <stdio.h>\n#include <string.h>\n\n#define LEN 20\n\nint main()\n{\n\tchar smallest_word[LEN + 1];\n\tchar largest_word[LEN + 1];\n\tchar word[LEN + 1];\n\n\tprintf(\"Enter word: \");\n\tscanf(\"%s\", word);\n\n\tstrcpy(smallest_word, word);\n\tstrcpy(largest_word, word);\n\n\twhile (strlen(word) != 4) {\n\t\tif (strcmp(smallest_word, word) > 0) strcpy(smallest_word, word);\n\t\tif (strcmp(largest_word, word) < 0) strcpy(largest_word, word);\n\n\t\tprintf(\"Enter word: \");\n\t\tscanf(\"%s\", word);\n\t}\n\n\tprintf(\"Smallest word: %s\\n\", smallest_word);\n\tprintf(\"Largest word: %s\\n\", largest_word);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_15.c",
    "content": "/*\n * 按如下方式改进 remind.c 程序：\n *\n * (a) 如果对应的天为负数或大于31，那么先是显示出错误信息，然后忽略提示。提示：使用 continue 语句\n *\n * (b) 允许用户输入天、24小时格式的时间（可能空白）和提示。显示的提示列表必须先按天排序存储，然后\n * 再根据时间排序存储。（原始的 remind.c 程序允许用户输入时间，但是它把时间作为提示的一部分来处理）\n *\n * (c) 程序需显示一年的提示列表。要求用户按照月/日的格式输入日期。\n */\n\n/*\n * remind.c\n *\n * Prints a one-month reminder list\n */\n\n/*\n * Enter day and reminder: 09/27 10:56 Susan birthday\n */\n\n#include <stdio.h>\n#include <string.h>\n\n#define MAX_REMIND 50\n#define MSG_LEN 60\n\nint read_line(char str[], int n);\n\nint main()\n{\n\tchar reminders[MAX_REMIND][MSG_LEN + 3];\n\tchar day_str[12], msg_str[MSG_LEN + 1];\n\tint day, month, hour, min, i, j, num_remind = 0;\n\n\tfor (;;) {\n\t\tif (num_remind == MAX_REMIND) {\n\t\t\tprintf(\"-- No space left --\\n\");\n\t\t\tbreak;\n\t\t}\n\n\t\tprintf(\"Enter day and reminder: \");\n\t\tday = 0;\n\t\tscanf(\"%2d/%2d\", &month, &day);\n\t\tif (day == 0) break;\n\n\t\tif (day < 0 || day > 31) {\n\t\t\tprintf(\"Bad day, ignored\\n\");\n\t\t\twhile (getchar() != '\\n');\n\t\t\tcontinue;\n\t\t}\n\n\t\tscanf(\"%2d:%2d\", &hour, &min);\n\n\t\tsprintf(day_str, \"%.2d/%.2d %.2d:%.2d\", month, day, hour, min);\n\t\tread_line(msg_str, MSG_LEN);\n\n\t\tfor (i = 0; i < num_remind; i++)\n\t\t\tif (strcmp(day_str, reminders[i]) < 0)\n\t\t\t\tbreak;\n\t\tfor (j = num_remind; j > i; j--)\n\t\t\tstrcpy(reminders[j], reminders[j-1]);\n\n\t\tstrcpy(reminders[i], day_str);\n\t\tstrcat(reminders[i], msg_str);\n\n\t\tnum_remind++;\n\t}\n\n\tprintf(\"\\nDay Reminder\\n\");\n\tfor (i = 0; i < num_remind; i++)\n\t\tprintf(\" %s\\n\", reminders[i]);\n\n\treturn 0;\n}\n\nint read_line(char str[], int n)\n{\n\tchar ch;\n\tint i = 0;\n\n\twhile ((ch = getchar()) != '\\n') {\n\t\tif (i < n)\n\t\t\tstr[i++] = ch;\n\t}\n\tstr[i] = '\\0';\n\treturn i;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_16.c",
    "content": "/*\n * 利用13.6节的方法来精简 count_space 函数。特别是要用 while 循环替换for语句\n */\n\n/*\n * 统计字符串中空格的数量\n */\n\n#include <stdio.h>\n\nint count_space(const char *s)\n{\n\tint count = 0;\n\twhile (*s)\n\t\tif (*s++ == ' ') count++;\n\n\treturn count;\n}\n\nint main()\n{\n\tprintf(\"space: %d\\n\", count_space(\"Hello World !!\"));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_17.c",
    "content": "/*\n * 修改8.2节的 deal.c 程序，使它显示出牌的全名：\n *\n * Enter number of cards in hand: 5\n * Your hand:\n * Seven of clubs\n * Two of spades\n * Five of diamonds\n * Ace of spaded\n * Two of hearts\n *\n * 提示：用指向字符串的指针数组来替换数组 rank_code 和数组 suit_code;\n */\n\n/* Deals a random hand of cards. */\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <time.h>\n#include <string.h>\n\n#define NUM_SUITS 4\n#define NUM_RANKS 13\n\nint main()\n{\n\tint inhand_cards[NUM_SUITS][NUM_RANKS];\n\tchar* suit_list[NUM_SUITS] = {\"clubs\", \"diamonds\", \"hearts\", \"spades\"};\n\tchar* rank_list[NUM_RANKS] = {\"ACE\", \"TWO\", \"Three\", \"Four\", \"Five\", \"Six\", \"Seven\", \"Eight\", \"Nine\", \"Ten\", \"Jack\", \"Queue\", \"King\"};\n\tint need_cards;\n\tint rank, suit;\n\n\tprintf(\"Enter number of cards in hand: \");\n\tscanf(\"%d\", &need_cards);\n\n\tprintf(\"Your hand:\\n\");\n\n\tmemset(inhand_cards, 0, sizeof(inhand_cards));\n\n\tsrand((unsigned int)time(NULL));\n\twhile (need_cards > 0)\n\t{\n\t\tsuit = rand() % NUM_SUITS;\n\t\trank = rand() % NUM_RANKS;\n\t\tif (inhand_cards[suit][rank] == 0)\n\t\t{\n\t\t\tinhand_cards[suit][rank] = 1;\n\t\t\t-- need_cards;\n\n\t\t\tprintf(\"%s of %s\\n\", rank_list[rank], suit_list[suit]);\n\t\t}\n\t}\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_18.md",
    "content": "编写名为 reverse.c 的程序，用来把命令行参数按反序输出。如果按下述方式执行程序：\n\nreverse void and null\n\n产生的输出为：\n\nnull and void\n\n---\n\n见 [reverse.c](./reverse.c)\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_19.c",
    "content": "/*\n * 编写名为 sum.c 的程序，用来对命令行参数求和。假设参数都是整数。如果按下述方式执行程序：\n *\n * sum 8 24 62\n *\n * 产生的输出应为\n *\n * Total: 94\n *\n * 提示：用 atoi 函数把每个命令行参数从字符串格式转换为整数格式。\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n\nint main(int argc, char* argv[])\n{\n\tint sum;\n\tint i;\n\n\tfor (i = 1; i < argc; i++)\n\t\tsum += atoi(argv[i]);\n\n\tprintf(\"Total: %d\\n\", sum);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/ex_20.c",
    "content": "/*\n * 改进程序 planets.c ，使它在比较命令行参数与 planets 数组中的字符串时忽略大小写。\n */\n\n/*\n * planet.c\n *\n * Checks planet names\n */\n\n#include <stdio.h>\n#include <string.h>\n#include <ctype.h>\n\n#define NUM_PLANETS 9\n\nvoid ToUpper(const char* s, char *out)\n{\n\twhile (*s) {\n\t\t*out++ = toupper(*s++);\n\t}\n}\n\nint main(int argc, char *argv[])\n{\n\tchar *planets[] = {\n\t\t\"Mercury\",\n\t\t\"Venus\",\n\t\t\"Earth\",\n\t\t\"Mars\",\n\t\t\"Jupiter\",\n\t\t\"Saturn\",\n\t\t\"Uranus\",\n\t\t\"Neptune\",\n\t\t\"Pluto\",\n\t};\n\n\tint i, j;\n\tfor (i = 1; i < argc; i++) {\n\t\tfor (j = 0; j < NUM_PLANETS; j++) {\n\t\t\tchar s1[20], s2[20];\n\t\t\tToUpper(argv[i], s1);\n\t\t\tToUpper(planets[j], s2);\n\n\t\t\tif (strcmp(s1, s2) == 0) {\n\t\t\t\tprintf(\"%s is planet %d\\n\", argv[i], j + 1);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (j == NUM_PLANETS) {\n\t\t\tprintf(\"%s is not a planet\\n\", argv[i]);\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/planet.c",
    "content": "/*\n * planet.c\n *\n * Checks planet names\n */\n\n#include <stdio.h>\n#include <string.h>\n\n#define NUM_PLANETS 9\n\nint main(int argc, char *argv[])\n{\n\tchar *planets[] = {\n\t\t\"Mercury\",\n\t\t\"Venus\",\n\t\t\"Earth\",\n\t\t\"Mars\",\n\t\t\"Jupiter\",\n\t\t\"Saturn\",\n\t\t\"Uranus\",\n\t\t\"Neptune\",\n\t\t\"Pluto\",\n\t};\n\n\tint i, j;\n\tfor (i = 1; i < argc; i++) {\n\t\tfor (j = 0; j < NUM_PLANETS; j++) {\n\t\t\tif (strcmp(argv[i], planets[j]) == 0) {\n\t\t\t\tprintf(\"%s is planet %d\\n\", argv[i], j + 1);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (j == NUM_PLANETS) {\n\t\t\tprintf(\"%s is not a planet\\n\", argv[i]);\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/read_line.c",
    "content": "/*\n * 读取一行字符串，返回读取的数量\n */\n\n#include <stdio.h>\n\n#define LEN 20\n\nint read_line(char str[], int n)\n{\n\tchar ch;\n\tint i = 0;\n\n\twhile ((ch = getchar()) != '\\n') {\n\t\tif (i < n)\n\t\t\tstr[i++] = ch;\n\t}\n\tstr[i] = '\\0';\n\n\treturn i;\n}\n\nint main()\n{\n\tchar str[LEN + 1];\n\tint len;\n\n\tlen = read_line(str, LEN);\n\tprintf(\"read %d charactors: %s\\n\", len, str);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/remind.c",
    "content": "/*\n * remind.c\n *\n * Prints a one-month reminder list\n */\n\n#include <stdio.h>\n#include <string.h>\n\n#define MAX_REMIND 50\n#define MSG_LEN 60\n\nint read_line(char str[], int n);\n\nint main()\n{\n\tchar reminders[MAX_REMIND][MSG_LEN + 3];\n\tchar day_str[3], msg_str[MSG_LEN + 1];\n\tint day, i, j, num_remind = 0;\n\n\tfor (;;) {\n\t\tif (num_remind == MAX_REMIND) {\n\t\t\tprintf(\"-- No space left --\\n\");\n\t\t\tbreak;\n\t\t}\n\n\t\tprintf(\"Enter day and reminder: \");\n\t\tscanf(\"%2d\", &day);\n\t\tif (day == 0) break;\n\n\t\tsprintf(day_str, \"%2d\", day);\n\t\tread_line(msg_str, MSG_LEN);\n\n\t\tfor (i = 0; i < num_remind; i++)\n\t\t\tif (strcmp(day_str, reminders[i]) < 0)\n\t\t\t\tbreak;\n\t\tfor (j = num_remind; j > i; j--)\n\t\t\tstrcpy(reminders[j], reminders[j-1]);\n\n\t\tstrcpy(reminders[i], day_str);\n\t\tstrcat(reminders[i], msg_str);\n\n\t\tnum_remind++;\n\t}\n\n\tprintf(\"\\nDay Reminder\\n\");\n\tfor (i = 0; i < num_remind; i++)\n\t\tprintf(\" %s\\n\", reminders[i]);\n\n\treturn 0;\n}\n\nint read_line(char str[], int n)\n{\n\tchar ch;\n\tint i = 0;\n\n\twhile ((ch = getchar()) != '\\n') {\n\t\tif (i < n)\n\t\t\tstr[i++] = ch;\n\t}\n\tstr[i] = '\\0';\n\treturn i;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/reverse.c",
    "content": "/*\n * 把命令行参数反序输出。如果按下述方式执行程序：\n *\n * reverse void and null\n *\n * 产生的输出应为\n *\n * null and void\n */\n\n#include <stdio.h>\n\nint main(int argc, char* argv[])\n{\n\tint i;\n\n\tfor (i = argc - 1; i >= 1; i--) {\n\t\tprintf(\"%s \", argv[i]);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/strcat.c",
    "content": "/*\n * strcat 实现\n */\n\n#include <stdio.h>\n\nchar *strcat_(char *s1, const char *s2)\n{\n\tchar *p = s1;\n\n\twhile (*p)\n\t\tp++;\n\twhile ((*p++ = *s2++) != '\\0');\n\treturn s1;\n}\n\nint main()\n{\n\tchar str[20] = \"Hello\";\n\n\tprintf(\"str: %s\\n\", strcat_(str, \" World\"));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/strcmp.c",
    "content": "/*\n * strcmp 的实现\n */\n\n#include <stdio.h>\n\nint strcmp_(char *s, char *t)\n{\n\tint i;\n\n\tfor (i = 0; s[i] == t[i]; i++)\n\t\tif (s[i] == '\\0')\n\t\t\treturn 0;\n\treturn s[i] - t[i];\n}\n\nint main()\n{\n\tprintf(\"%d\\n\", strcmp_(\"Hello\", \"Hi\"));\n\tprintf(\"%d\\n\", strcmp_(\"Hi\", \"Hi\"));\n\tprintf(\"%d\\n\", strcmp_(\"Hi\", \"Hello\"));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch13_字符串/strlen.c",
    "content": "/*\n * strlen 的实现\n */\n\n#include <stdio.h>\n\nsize_t strlen_(const char *s)\n{\n\tconst char *p = s;\n\n\twhile (*s)\n\t\ts++;\n\treturn s - p;\n}\n\nint main()\n{\n\tchar str[] = \"Hello world\";\n\n\tprintf(\"len of str(%s) is: %d\\n\", str, (int)strlen_(str));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"ex_12.c\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/ex_01.c",
    "content": "/*\n * 编写宏来计算下面的值。\n * (a) x的立方。\n * (b) x除以4的余数。\n * (c) 如果x与y的乘积小于100值为1，否则值为0。\n */\n\n#include <stdio.h>\n\n#define EX_A(x) ((x)*(x)*(x))\n#define EX_B(x) ((x) % 4)\n#define EX_C(x, y) ((x) * (y) < 100 ? 1 : 0)\n\nint main()\n{\n\tint x = 10;\n\tint y = 20;\n\n\tprintf(\"%d\\n\", EX_A(x));\n\tprintf(\"%d\\n\", EX_B(x));\n\tprintf(\"%d\\n\", EX_C(x, y));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/ex_02.c",
    "content": "/*\n * 编写一个宏 NELEMS(a) 来计算一个一维数组a中元素的个数。提示：使用 sizeof 运算符。\n */\n\n#include <stdio.h>\n\n#define NELEMS(a) ((int)(sizeof(a)/sizeof(a[0])))\n\nint main()\n{\n\tchar str[] = \"Hello World\";\n\n\tprintf(\"real len of str: %d\\n\", NELEMS(str));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/ex_03.c",
    "content": "/*\n * 假定 DOUBLE 是如下宏：\n *\n * #define DOUBLE(x) 2*x\n *\n * (a) DOUBLE(1+2) 的值是多少？\n * (b) 4/DOUBLE(2) 的值是多少？\n * (c) 改正 DOUBLE 的定义\n */\n\n/*\n * (a) 4\n * (b) 4\n */\n\n#include <stdio.h>\n\n#define DOUBLE(x) 2*x\n#define DOUBLE_FIX(x) (2*(x))\n\nint main()\n{\n\tprintf(\"%d\\n\", DOUBLE(1+2));\n\tprintf(\"%d\\n\", 4/DOUBLE(2));\n\n\tprintf(\"%d\\n\", DOUBLE_FIX(1+2));\n\tprintf(\"%d\\n\", 4/DOUBLE_FIX(2));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/ex_04.c",
    "content": "/*\n * 针对下面每一个宏，举例说明宏的问题，并提出修改方法。\n *\n * (a) #define AVG(x,y) (x+y)/2\n * (b) #define AREA(x,y) (x) * (y)\n */\n\n#include <stdio.h>\n\n#define AVG(x,y) (x+y)/2\n#define AREA(x,y) (x) * (y)\n\n#define AVG_FIX(x, y) (((x) + (y))/2)\n#define AREA_FIX(x, y) ((x) * (y))\n\nint main()\n{\n\tint res;\n\tdouble d_res;\n\n\tres = AVG(4 || 1, 10); // 期望为1和10的平均值，但实际上计算了1/2\n\tprintf(\"res: %d\\n\", res);\n\n\tres = AVG_FIX(4 || 1, 10);\n\tprintf(\"res: %d\\n\", res);\n\n\td_res = (int)AREA(3, 1.5);\t// 期望为4，但实际上还是4.5\n\tprintf(\"d_res: %g\\n\", d_res);\n\n\td_res = (int)AREA_FIX(3, 1.5);\n\tprintf(\"d_res: %g\\n\", d_res);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/ex_05.c",
    "content": "/*\n * 下面的宏定义有问题：\n *\n * #define ABS(a) ((a)<0?-(a):a)\n *\n * 举例说明为什么 ABS 不能正常工作，并提出修改方法。你可以假定 ABS 的参数没有副作用。\n */\n\n/*\n * PS，尚未找到不能工作的例子。\n */\n\n#include <stdio.h>\n\n#define ABS(a) ((a)<0?-(a):a)\n#define ABS_FIX(a) ((a)<0?-(a):(a)) /* 应当给最后的一个a加上括号 */\n\nint main()\n{\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/ex_06.c",
    "content": "/*\n * 假定 TOUPPER 定义成下面的宏：\n *\n * #define TOUPPER(c) ('a'<=(c)&&(c)<='z'?(c)-'a'+'A':(c))\n *\n * 假设s是一个字符串，i是一个 int 型变量。给出下面每个代码段所产生的输出。\n *\n * (a) strcpy(s, \"abcd\");\n * i = 0;\n * putchar(TOUPPER(s[++i]));\n *\n * (b) strcpy(s, \"0123\");\n * i = 0;\n * putchar(TOUPPER(s[++i]));\n */\n\n/*\n * (a) D\n * (b) 2\n *\n * PS: 在参数列表里使用++，使得宏有副作用\n */\n\n#include <stdio.h>\n#include <string.h>\n\n#define TOUPPER(c) ('a'<=(c)&&(c)<='z'?(c)-'a'+'A':(c))\n\nint main()\n{\n\tint i;\n\tchar s[20];\n\n\t/* a */\n\tstrcpy(s, \"abcd\");\n\ti = 0;\n\tputchar(TOUPPER(s[++i]));\n\n\t/* b */\n\tstrcpy(s, \"0123\");\n\ti = 0;\n\tputchar(TOUPPER(s[++i]));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/ex_07.c",
    "content": "/*\n * (a) 编写宏 DISP(f, x) ，使其扩展后调用 printf 函数来显示函数f的参数为x时的值。例如：\n *\n * DISP(sqrt, 3.0);\n *\n * 应该扩展为：\n *\n * printf(\"sqrt(%g) = %g\\n\", 3.0, sqrt(3.0));\n *\n * (b) 编写宏 DISP2(f, x, y) ，类似 DISP 但应用于有两个参数的函数。\n */\n\n#include <stdio.h>\n#include <math.h>\n\n#define DISP(f, x) (printf(#f \"(%g) = %g\\n\", (x), (f)(x)))\n\n#define DISP2(f, x, y) (printf(#f \"(%g, %g) = %g\\n\", (x), (y), (f)(x, y)))\n\ndouble sum(double a, double b)\n{\n\treturn a + b;\n}\n\nint main()\n{\n\tDISP(sqrt, 3.0);\n\tDISP2(sum, 3.5, 4.3);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/ex_08.c",
    "content": "/*\n * 假定 GENERIC_MAX 是如下宏：\n *\n * #define GENERIC_MAX(type)\\\n * type type##_max(type x, type y)\\\n * {\\\n * \treturn x > y ? x : y;\\\n * }\n *\n * (a) 写出 GENERIC_MAX(long) 被预处理器扩展后的形式\n * (b) 解释为什么 GENERIC_MAX 不能应用在像 unsigned long 这样的基本类型上？\n * (c) 如何使 GENERIC_MAX 对任何基本类型都可以正常工作？提示：不要改变 GENERIC_MAX 的定义。\n */\n\n/*\n * (a)\n * long long_max(long x, long y)\n * {\n * \treturn x > y ? x : y;\n * }\n *\n * (b) 因为 unsigned long 有一个空格，没办法用 ## 连接成一个合法的函数名\n *\n * (c) 可以使用类型别名，如下\n */\n\n#include <stdio.h>\n\n#define GENERIC_MAX(type)\\\ntype type##_max(type x, type y)\\\n{\\\n\treturn x > y ? x : y;\\\n}\n\ntypedef unsigned long UIntLong;\n\nGENERIC_MAX(UIntLong);\n\nint main()\n{\n\tprintf(\"%ld\\n\", UIntLong_max(10, 42));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/ex_09.c",
    "content": "/*\n * 如果需要一个宏，使它展开后包含当前行号和文件名。换而言之，我们会写：\n * const char *str = LINE_FILE;\n * 扩展后为\n * const char *str = \"Line 10 of file foo.c\";\n *\n * 其中 foo.c 是包含程序的文件，10是调用 LINE_FILE 行的行号。\n */\n\n#include <stdio.h>\n\n#define LINE_FILE line_file(__LINE__)\n\nconst char *line_file(int line)\n{\n\tstatic char buff[100];\n\n\tsprintf(buff, \"Line %d of file %s\", line, __FILE__);\n\treturn buff;\n}\n\nint main()\n{\n\tconst char *str = LINE_FILE;\n\tprintf(\"%s\\n\", str);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/ex_10.c",
    "content": "/*\n * 假定宏M有如下定义：\n * #define M 10\n *\n * 下面那项测试会失败？\n * (a) #if M\n * (b) #ifdef M\n * (c) #ifndef M\n * (d) #if defined(M)\n * (e) #if !defined(M)\n */\n\n/*\n * (c) (e)\n */\n\n#include <stdio.h>\n\n#define M 10\n\nint main()\n{\n\t#if M\n\tprintf(\"(a)\\n\");\n\t#endif\n\n\t#ifdef M\n\tprintf(\"(b)\\n\");\n\t#endif\n\n\t#ifndef M\n\tprintf(\"(c)\\n\");\n\t#endif\n\n\t#if defined(M)\n\tprintf(\"(d)\\n\");\n\t#endif\n\n\t#if !defined(M)\n\tprintf(\"(e)\\n\");\n\t#endif\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/ex_11.c",
    "content": "/*\n * (a) 指出下面的程序预处理后的形式。\n *\n * #define N 10\n *\n * void f(void);\n *\n * main()\n * {\n * \tf();\n * \t#ifdef N\n * \t#undef N\n * \t#endif\n * \treturn 0;\n * }\n *\n * void f(void)\n * {\n * #if defined(N)\n * \tprintf(\"N is %d\\n\", N);\n * #else\n * \tprintf(\"N is undefined\\n\");\n * #endif\n * }\n *\n * (b) 这个程序的输出是什么？\n */\n\n/*\n * 预处理后为：\n *\n * void f(void)\n * {\n * \tprintf(\"N is undefined\\n\");\n * }\n *\n * 输出为：\n *\n * N is undefined\n */\n\n#include <stdio.h>\n\n#define N 10\nvoid f(void);\nint main()\n{\n\tf();\n\t#ifdef N\n\t#undef N\n\t#endif\n\treturn 0;\n}\n\nvoid f(void)\n{\n#if defined(N)\nprintf(\"N is %d\\n\", N);\n#else\nprintf(\"N is undefined\\n\");\n#endif\n}\n"
  },
  {
    "path": "codes/CProgramming/ch14_预处理器/ex_12.c",
    "content": "/*\n * 指出下面的程序预处理后的形式。其中有几行可能会导致编译错误，请找出这些错误。\n *\n * #define N = 10\n * #define INC(x) x+1\n * #define SUB(x,y) x - y\n * #define SQR(x) ((x)*(x))\n * #define CUBE(x) (SQR(x)*(x))\n * #define M1(x,y) x##y\n * #define M2(x,y) #x #y\n *\n * main()\n * {\n * \tint a[N], i, j, k, m;\n * \t#ifdef N\n * \ti = j;\n * \t#else\n * \tj = i;\n * \t#endif\n * \ti = 10 * INC(j);\n * \ti = SUB(j, k);\n * \ti = SQR(SQR(j++));\n * \ti = CUBE(j);\n * \ti = M1(j, k);\n * \tputs(M2(i, j));\n * \t#undef SQR\n * \ti = SQR(j);\n * \t#define SQR\n * \ti = SQR(j);\n * \treturn 0;\n * }\n */\n\n#include <stdio.h>\n\n#define N = 10\n#define INC(x) x+1\n#define SUB(x,y) x - y\n#define SQR(x) ((x)*(x))\n#define CUBE(x) (SQR(x)*(x))\n#define M1(x,y) x##y\n#define M2(x,y) #x #y\n\nint main()\n{\n\tint a[=10], i, j, k, m; /* 编译错误 */\n\ti = j;\n\ti = 10 * j+1;\n\ti = j - k;\n\ti = ((((j++)*(j++)))*(((j++)*(j++))));\n\ti = (((j)*(j))*(j));\n\ti = jk;\t/* 编译错误 */\n\tputs(\"i\" \"j\");\n\ti = SQR(j); /* 编译错误 */\n\ti =; /* 编译错误 */\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_04/bar.c",
    "content": "/*\n * bar.c\n */\n\n#include <stdio.h>\n\nextern long int i;\n\nextern int Get();\nextern int GetAddress();\n\nint main()\n{\n\ti = 0;\n\n\tprintf(\"i: %ld\\n\", i);\n\tprintf(\"i from foo: %i\\n\", Get());\n\n\tprintf(\"address of i: %p\\n\", &i);\n\tprintf(\"address of i from foo: %p\\n\", GetAddress());\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_04/foo.c",
    "content": "/*\n * foo.c\n */\n\nint i = 100000;\n\nint Get()\n{\n\treturn i;\n}\n\nint* GetAddress()\n{\n\treturn &i;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_04/makefile",
    "content": "main: bar.o foo.o\n\tgcc -o main bar.o foo.o\n\nbar.o: bar.c\nfoo.o: foo.c\n\nclean:\n\t@rm -f main *.o\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_06/main.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n\n#include \"stack.h\"\n#include \"token.h\"\n\n/*\n    获取逆波兰表达式的值，输入#或EOF结束表达式：\n    30 5 - 7 * #\n\n    结果等于 (30 - 5) * 7 = 175\n*/\n\nint main()\n{\n    TokenType token_type;\n    int n1, n2;\n    int result;\n\n    while (1)\n    {\n        token_type = next_token();\n        if (token_type == TYPE_END)\n        {\n            break;\n        }\n        else if (token_type == TYPE_NUMBER)\n        {\n            stack_push(get_last_number());\n        }\n        else if (token_type == TYPE_OP)\n        {\n            n1 = stack_pop();\n            n2 = stack_pop();\n            switch(get_last_op())\n            {\n                case '+': result = n2 + n1;\n                break;\n\n                case '-': result = n2 - n1;\n                break;\n\n                case '*': result = n2 * n1;\n                break;\n\n                case '\\\\': result = n2 / n1;\n                break;\n            }\n            stack_push(result);\n        }\n    }\n\n    printf(\"result: %d\\n\", stack_pop());\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_06/makefile",
    "content": "CC=gcc\n\nmain: main.o stack.o token.o\n\tgcc -g -Wall -o main main.o stack.o token.o\n\nmain.o: main.c stack.h token.h\nstack.o: stack.c stack.h\ntoken.o: token.c token.h\n\nclean:\n\t@rm -f main *.o *.exe\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_06/stack.c",
    "content": "#include \"stack.h\"\n#include <assert.h>\n\n#define STACK_SIZE 100\n\n/* external variables */\nint contents[STACK_SIZE];\nint top = 0;\n\nvoid stack_make_empty()\n{\n\ttop = 0;\n}\n\nint stack_is_empty()\n{\n\treturn 0 == top;\n}\n\nPRIVATE int stack_is_full()\n{\n\treturn STACK_SIZE == top;\n}\n\nvoid stack_push(int i)\n{\n\tassert(!stack_is_full());\n\t\n\tcontents[top ++] = i;\n}\n\nint stack_pop()\n{\n\tassert(!stack_is_empty());\n\n\treturn contents[-- top];\n}"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_06/stack.h",
    "content": "#ifndef STACK_H\n#define STACK_H\n\n#define PUBLIC /* empty */\n#define PRIVATE static\n\nPUBLIC void stack_make_empty();\nPUBLIC int stack_is_empty();\nPUBLIC void stack_push();\nPUBLIC int stack_pop();\n\n#endif // STACK_H"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_06/token.c",
    "content": "#include \"token.h\"\n#include <stdio.h>\n#include <ctype.h>\n#include <stdlib.h>\n\nstatic int last_number;\nstatic char last_op;\n\nint get_last_number()\n{\n    return last_number;\n}\n\nchar get_last_op()\n{\n    return last_op;\n}\n\nTokenType next_token()\n{\n    char c;\n    int number_str_index = 0;\n    char number_str[100];\n    while (1)\n    {\n        c = getchar();\n        if (c == ' ' || c == '\\n') break;\n        if (c == '#' || c == EOF) break;\n\n        if (c == '+' || c== '-' || c == '*' || c == '\\\\')\n        {\n            last_op = c;\n            return TYPE_OP;\n        }\n\n        if (isdigit(c))\n        {\n            number_str[number_str_index++] = c;\n        }\n    }\n\n    if (number_str_index > 0)\n    {\n        number_str[number_str_index] = '\\0';\n        last_number = atoi(number_str);\n        return TYPE_NUMBER;\n    }\n\n    if (c == '#' || c == EOF)\n    {\n        return TYPE_END;\n    }\n    else\n    {\n        return TYPE_CONTINUE;\n    }\n}"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_06/token.h",
    "content": "#ifndef TOKEN_H\n#define TOKEN_H\n\n#define TYPE_NUMBER 0\n#define TYPE_OP 1\n#define TYPE_END 2\n#define TYPE_CONTINUE 3\n\ntypedef int TokenType;\n\nTokenType next_token();\nint get_last_number();\nchar get_last_op();\n\n#endif // TOKEN_H"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_07/f1.c",
    "content": "#include \"f1.h\"\n#include \"f2.h\""
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_07/f1.h",
    "content": "#ifndef F1_H\n#define F1_H\n\n#endif // F1_h"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_07/f2.c",
    "content": "#include \"f1.h\"\n#include \"f2.h\""
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_07/f2.h",
    "content": "#ifndef F2_H\n#define F2_H\n\n#endif // F2_H"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_07/main.c",
    "content": "#include \"f1.h\"\n\nint main()\n{\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/dir_ex_07/makefile",
    "content": "CC=gcc\n\ndemo: main.o f1.o f2.o\n\t$(CC) -o demo main.o f1.o f2.o\n\nf1.o: f1.c f1.h f2.h\nf2.o: f2.c f1.h f2.h\nmain.o: main.c f1.h\n\nclean:\n\t@rm -f *.o *.exe main"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/ex_01.md",
    "content": "15.1节列出了把程序分割成多个源程序的几个优点。\n\n(a) 请描述几个其他的优点。\n\n(b) 请描述一些缺点。\n\n---\n\n(a) 方便多人协同开发程序，不同的人负责不同的模块（文件）。可以将源程序编译为库，而不是可执行程序，库可以重复利用。方便制定模块的接口，预先定义而不具体实现，从而快速搭建整体程序逻辑。\n\n(b) 构建相对复杂。理解程序需要分别理解各个模块，甚至依赖相关文档，不适合微型程序。\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/ex_02.md",
    "content": "下列哪个不应该放置在头文件中？为什么？\n\n(a) 函数原型。\n\n(b) 函数定义。\n\n(c) 宏定义。\n\n(d) 类型定义。\n\n---\n\n(b) 不应该，为了避免重复的函数定义。\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/ex_03.md",
    "content": "如果文件是我们已经编写好的，那么已经看到用`#include <文件>`代替`#include \"文件\"`可能无法工作。如果文件是系统头文件，那么用`#include \"文件\"`代替`#include <文件>`是否有什么问题？\n\n---\n\n可能会在程序自己的目录找到一个同名的文件，从而未能正确使用系统的头文件。\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/ex_04.md",
    "content": "假设文件foo.c定义了外部变量i如下：\n\nint i;\n\n而且文件bar.c以下列方式声明此变量：\n\nextern long int i;\n\n(a) 假设sizeof(int)和sizeof(long int)是完全一样的。如果文件bar.c中的一个函数给i赋值为0，请解释会发生什么？\n\n(b) 假设sizeof(int)小于sizeof(long int)。请重复(1)的问题。\n\n---\n\n(a) 两个i实质上是一个存储位置，因此会把foo.c中的i也变成0。\n\n(b) 会修改foo.c中的变量，但类型不一致，导致访问时取得的值不一致。\n\n见 [测试程序](./dir_ex_04)\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/ex_05.md",
    "content": "程序fmt通过在单词间插入额外的空格来调整行。当前编写的函数 writen_line 的方法是，与开始处的单词间隔相比，靠近行末尾单词的间隔略宽一些。（例如，靠近末尾的单词彼此之间可能有3个空格，而靠近开始的单词彼此之间可能只有两个空格。）请通过替换函数 write_line 来改进此程序，替换后的函数可以在靠近行末尾处的单词之间放置较大的空隙，而在行开始处的单词之间放置一般空隙。\n\n---\n\n见 [line.c中的write_line_ex5函数](./format_program/line.c)\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/ex_06.md",
    "content": "利用15.2节中的设计，编写实现逆波兰表达式计算器的程序。使计算机可以实现双目运算+、-、*和/。假设它们的含义和在C语言中一样。\n\n见[测试程序](./dir_ex_06)"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/ex_07.md",
    "content": "假设程序有3个源文件构成：main.c、f1.c和f2.c，此外还包括l两个头文件f1.h和f2.h。全部3个源文件都包含f1.h，但是只有f1.c和f2.c包含f2.h。为此程序编写UNIX makefile。假设需要可执行文件名未为demo。\n\n见[程序](./dir_ex_07)"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/ex_08.md",
    "content": "下面的问题引用了练习7的描述。\n\n(a) 当程序第一次构建时，需要对哪些文件进行编译？\n\n(b) 如果在程序构建后对f1.c进行修改，那么需要对哪个（些）文件进行重新编译？\n\n(c) 如果在程序构建后对f1.h进行修改，那么需要对哪个（些）文件进行重新编译？\n\n(d) 如果在程序构建后对f2.h进行修改，那么需要对哪个（些）文件进行重新编译？\n\n---\n\n(a) main.c f1.c f2.c\n\n(b) f1.c\n\n(c) main.c f1.c f2.c\n\n(d) f1.c f2.c"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/ex_09.md",
    "content": "(a) 修改程序 fmt ，使函数 read_word （代替 main 函数）在被截短的单词末尾存储\\*字符。\n\n(b) 如果按照(a)进行了修改，那么需要对哪个（些）文件进行重新编译？\n\n---\n\n(a) 见[程序](./format_program/word.c)中的 read_word_ex09 函数。\n\n(b) main.c word.c"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/format_program/fmt.c",
    "content": "/* Formats a file of text */\n\n#include <string.h>\n#include \"line.h\"\n#include \"word.h\"\n\nint main()\n{\n\tchar word[MAX_WORD_LEN + 2];\n\tint word_len;\n\n\tclear_line();\n\n\t/*\n\tfor (;;) {\n\t\tread_word(word, MAX_WORD_LEN + 1);\n\t\tword_len = strlen(word);\n\t\tif (word_len == 0) {\n\t\t\tflush_line();\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (word_len > MAX_WORD_LEN)\n\t\t\tword[MAX_WORD_LEN] = '*';\n\n\t\tif (word_len + 1 > space_remaining()) {\n\t\t\t//write_line();\n\t\t\twrite_line_ex5();\n\t\t\tclear_line();\n\t\t}\n\t\tadd_word(word);\n\t}\n\t*/\n\n\t/*\n\t\tfor ex_09\n\t*/\n\tfor (;;) {\n\t\tread_word_ex09(word, MAX_WORD_LEN + 1);\n\t\tword_len = strlen(word);\n\t\tif (word_len == 0) {\n\t\t\tflush_line();\n\t\t\treturn 0;\n\t\t}\n\n\t\tif (word_len + 1 > space_remaining()) {\n\t\t\t//write_line();\n\t\t\twrite_line_ex5();\n\t\t\tclear_line();\n\t\t}\n\t\tadd_word(word);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/format_program/line.c",
    "content": "#include <stdio.h>\n#include <string.h>\n#include \"line.h\"\n\n#define MAX_LINE_LEN 60\n\nchar line[MAX_LINE_LEN + 1];\nint line_len = 0;\nint num_words = 0;\n\nvoid clear_line(void)\n{\n\tline[0] = '\\0';\n\tline_len = 0;\n\tnum_words = 0;\n}\n\nvoid add_word(const char *word)\n{\n\tif (num_words > 0) {\n\t\tline[line_len] = ' ';\n\t\tline[line_len+1] = '\\0';\n\t\tline_len++;\n\t}\n\n\tstrcat(line, word);\n\tline_len += strlen(word);\n\tnum_words++;\n}\n\nint space_remaining(void)\n{\n\treturn MAX_LINE_LEN - line_len;\n}\n\nvoid write_line(void)\n{\n\tint extra_spaces, spaces_to_insert, i, j;\n\n\textra_spaces = MAX_LINE_LEN - line_len;\n\tfor (i = 0; i < line_len; i++) {\n\t\tif (line[i] != ' ')\n\t\t\tputchar(line[i]);\n\t\telse {\n\t\t\tspaces_to_insert = extra_spaces / (num_words - 1);\n\t\t\tfor (j = 1; j <= spaces_to_insert + 1; j++)\n\t\t\t\tputchar(' ');\n\t\t\textra_spaces -= spaces_to_insert;\n\t\t\tnum_words--;\n\t\t}\n\t}\n\n\tputchar('\\n');\n}\n\n// 练习5：在靠近末尾处的的单词之间放置较大的空隙 ，在行开始处的单词之间放置一般空隙\nvoid write_line_ex5(void)\n{\n\tint extra_spaces, i, j;\n\textra_spaces = MAX_LINE_LEN - line_len;\n\tfor (i = 0; i < line_len; i++) {\n\t\tif (line[i] != ' ')\n\t\t\tputchar(line[i]);\n\t\telse {\n\t\t\tif (num_words != 2) {\n\t\t\t\tputchar(' ');\n\t\t\t}\n\t\t\telse {\n\t\t\t\tfor (j = 1; j < extra_spaces + 1; j++)\n\t\t\t\t\tputchar(' ');\n\t\t\t}\n\t\t\tnum_words--;\n\t\t}\n\t}\n\n\tputchar('\\n');\n}\n\nvoid flush_line(void)\n{\n\tif (line_len > 0)\n\t\tputs(line);\n}\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/format_program/line.h",
    "content": "#ifndef LINE_H\n#define LINE_H\n\n/*\n * clear_line: Clears the current line.\n */\nvoid clear_line(void);\n\n/*\n * add_word: Adds word to the end of the current line.\n *           If this is not the first word on the line,\n *           puts one space before word.\n */\nvoid add_word(const char *word);\n\n/*\n * space_remaining: Returns the number of characters left\n *                  in the current line.\n */\nint space_remaining(void);\n\n/*\n * write_line: Writes the current line with justification\n */\nvoid write_line(void);\n\n/*\n * flush_line: Writes the current line without justification.\n *             If the line is empty, does nothing.\n */\nvoid flush_line(void);\n\n/*\n * 练习5改写的 write_line ,要求靠近末尾处的的单词之间放置较大的空隙 ，在行开始处的单词之间放置一般空隙\n */\nvoid write_line_ex5(void);\n\n#endif\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/format_program/makefile",
    "content": "format: fmt.o word.o line.o\n\tgcc -o format fmt.o word.o line.o\n\nfmt.o: fmt.c line.h word.h\n\tgcc -c fmt.c\n\nline.o: line.c line.h\n\tgcc -c line.c\n\nword.o: word.c word.h\n\tgcc -c word.c\n\nclean:\n\trm -f *.o format*\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/format_program/quote",
    "content": "C\tis quirky,    floawed,    and an\nenormous success.\t\twhile accidents of    history\n surely   helped,   it evidently\tsatisfied   a   need\n \t\tfor a    system   implementation\tlanguage   efficient\n  enough to\t\tdisplace\t\t\tassembly   language,\n     yet sufficiently    abstract\tand fluent \tto describe\n\talgorithms\tand\t\t  interactions    int a wide\tvariety\nof \t  environments. \n\t\t\t\t\t\t-- Dennis \tM.\t\t\tRitchie\n\t\t\tvarylongwordddddddddddddddddddddd\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/format_program/word.c",
    "content": "#include <stdio.h>\n#include <string.h>\n#include \"word.h\"\n\nint read_char(void)\n{\n\tint ch = getchar();\n\n\tif (ch == '\\n' || ch == '\\t')\n\t\treturn ' ';\n\treturn ch;\n}\n\nvoid read_word(char *word, int len)\n{\n\tint ch, pos = 0;\n\twhile ((ch = read_char()) == ' ');\n\n\twhile (ch != ' ' && ch != EOF) {\n\t\tif (pos < len)\n\t\t\tword[pos++] = ch;\n\t\tch = read_char();\n\t}\n\n\tword[pos] = '\\0';\n}\n\nvoid read_word_ex09(char *word, int len)\n{\n\tint ch, pos = 0;\n\tint word_len;\n\twhile ((ch = read_char()) == ' ');\n\n\twhile (ch != ' ' && ch != EOF) {\n\t\tif (pos < len)\n\t\t\tword[pos++] = ch;\n\t\tch = read_char();\n\t}\n\t\n\tword[pos] = '\\0';\n\n\tword_len = strlen(word);\n\tif (word_len > MAX_WORD_LEN)\n\t\t\tword[MAX_WORD_LEN] = '*';\n}"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/format_program/word.h",
    "content": "#ifndef WORD_H\n#define WORD_H\n\n#define MAX_WORD_LEN 20\n\n/*\n * read_word: Reads the next word from the input and\n * \t\t\t  stores it in word. Makes word empty if no\n * \t\t\t  word could be read because of end-of-file.\n * \t\t\t  Truncates the word if its length exceeds len.\n * \n */\nvoid read_word(char *word, int len);\n\n/*\n * for ex_09\n */\nvoid read_word_ex09(char *word, int len);\n\n#endif\n"
  },
  {
    "path": "codes/CProgramming/ch15_编写大规模程序/format_program/核心伪代码.txt",
    "content": "for (;;) {\n\t读单词;\n\tif (不能读单词){\n\t\t不用调整地写行缓冲区的内容;\n\t\t终止程序;\n\t}\n\n\tif (单词不适合在行缓冲区中) {\n\t\t调整地写行缓冲区的内容;\n\t\t清除行缓冲区;\n\t}\n\n\t往行缓冲区中添加的单词;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/dir_ex_08/invent.c",
    "content": "/*\n    invent.c\n    Maintains a parts database (array version)\n*/\n\n#include <stdio.h>\n#include \"readline.h\"\n\n#define NAME_LEN 25\n#define MAX_PARTS 100\n\nstruct part {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n};\n\nint find_part(struct part inventory[MAX_PARTS], int num_parts, int number);\nvoid insert(struct part inventory[MAX_PARTS], int *num_parts);\nvoid search(struct part inventory[MAX_PARTS], int num_parts);\nvoid update(struct part inventory[MAX_PARTS], int num_parts);\nvoid print(struct part inventory[MAX_PARTS], int num_parts);\n\n/*\n    main: Prompts the user to enter an operator code,\n        then calls a function to perform the requested\n        action. Repeats until the user enters the\n        command 'q'. Prints an error message if th user\n        enters an illegal code.\n*/\nint main(int argc, char const *argv[])\n{\n    struct part inventory[MAX_PARTS];\n    int num_parts = 0;      /* number of parts currently stored */\n\n    char code;\n\n    for (;;) {\n        printf(\"Enter operation code: \");\n        scanf(\" %c\", &code);\n        while (getchar() != '\\n'); /* skips to end of line */\n\n        switch(code) {\n            case 'i': insert(inventory, &num_parts); break;\n            case 's': search(inventory, num_parts); break;\n            case 'u': update(inventory, num_parts); break;\n            case 'p': print(inventory, num_parts); break;\n            case 'q': printf(\"bye\\n\"); return 0;\n            default: printf(\"Illegal code\\n\"); break;\n        }\n        printf(\"\\n\");\n    }\n\n    return 0;\n}\n\n/*\n    find_part: Looks up a part number in the inventory\n            array. Returns the array index if the part\n            number is found; otherwise, return -1.\n*/\nint find_part(struct part inventory[MAX_PARTS], int num_parts, int number)\n{\n    int i;\n    for (i = 0; i < num_parts; i++) {\n        if (inventory[i].number == number)\n            return i;\n    }\n    return -1;\n}\n\n/*\n    insert: Prompts the user for information about a new\n            part and then inserts the part into the\n            database. Prints an error message and returns\n            prematurely if the part already exists or the\n            database if full.\n*/\nvoid insert(struct part inventory[MAX_PARTS], int *num_parts)\n{\n    int part_number;\n\n    if (*num_parts == MAX_PARTS) {\n        printf(\"Database is full; cant't add more parts.\\n\");\n        return;\n    }\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &part_number);\n    if (find_part(inventory, *num_parts, part_number) >= 0) {\n        printf(\"Part already exists.\\n\");\n        return;\n    }\n\n    inventory[*num_parts].number = part_number;\n    \n    printf(\"Enter part name: \");\n    read_line(inventory[*num_parts].name, NAME_LEN);\n    \n    printf(\"Enter quantity on hand: \");\n    scanf(\"%d\", &inventory[*num_parts].on_hand);\n    \n    (*num_parts)++;\n}\n\n/*\n    search: Prompts the user to enter a part number, then\n            looks up the part in the database. If the part\n            exists, prints the name and quantity on hand;\n            if not, prints an error message.\n*/\nvoid search(struct part inventory[MAX_PARTS], int num_parts)\n{\n    int i, number;\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    i = find_part(inventory, num_parts, number);\n    if (i >= 0) {\n        printf(\"Part name: %s\\n\", inventory[i].name);\n        printf(\"Quantity on hand: %d\\n\", inventory[i].on_hand);\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    update: Prompts the user to enter a part number.\n            Prints an error message if the part doesn't\n            exist; otherwise, prompts the user to enter\n            change in quantity on hand and updates the\n            database.\n*/\nvoid update(struct part inventory[MAX_PARTS], int num_parts)\n{\n    int i, number, change;\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    \n    i = find_part(inventory, num_parts, number);\n    if (i >= 0) {\n        printf(\"Enter change in quantity on hand: \");\n        scanf(\"%d\", &change);\n        inventory[i].on_hand += change;\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    print: Prints a listing of all parts in the database,\n            showing the part number, part name, and\n            quantity on hand. Parts are printed in the\n            order in which they were entered into the\n            database.\n*/\nvoid print(struct part inventory[MAX_PARTS], int num_parts)\n{\n    int i;\n    printf(\"Part number    Part Name          Quantity on Hand\\n\");\n    for (i = 0; i < num_parts; i++) {\n        printf(\"%7d     %-25s%11d\\n\", inventory[i].number, inventory[i].name, inventory[i].on_hand);\n    }\n}"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/dir_ex_08/makefile",
    "content": "CC=gcc\n\nprogram: invent.o readline.o\n\t$(CC) -g -Wall -o program invent.o readline.o\n\ninvent.o: invent.c readline.h\nreadline.o: readline.c readline.h\n\nclean:\n\t@rm -f *.o *.exe programi"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/dir_ex_08/readline.c",
    "content": "#include <ctype.h>\n#include <stdio.h>\n#include \"readline.h\"\n\nint read_line(char str[], int n)\n{\n    int ch, i = 0;\n\n    while (isspace(ch = getchar()));\n\n    while (1) {            \n        if (ch == '\\n' || ch == EOF) break;\n\n        if (i < n) {\n            str[i++] = ch;\n        }\n        ch = getchar();\n    }\n\n    str[i] = '\\0';\n\n    return i;\n}"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/dir_ex_08/readline.h",
    "content": "#ifndef READLINE_H\n#define READLINE_H\n\n/*\n    read_line: Skips leading white-space characters, then\n                reads the remainder of the input line and\n                stores it in str. Truncates the line if its\n                length exceeds n. Returns the number of\n                characters stored.\n*/\nint read_line(char str[], int n);\n\n#endif // READLINE_H"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/dir_ex_09/invent.c",
    "content": "/*\n    invent.c\n    Maintains a parts database (array version)\n*/\n\n#include <stdio.h>\n#include \"readline.h\"\n\n#define NAME_LEN 25\n#define MAX_PARTS 100\n\nstruct part {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n    int price;\n} inventory[MAX_PARTS];\n\nint num_parts = 0;      /* number of parts currently stored */\n\nint find_part(int number);\nvoid insert(void);\nvoid search(void);\nvoid update(void);\nvoid update_price(void);\nvoid print(void);\n\n/*\n    main: Prompts the user to enter an operator code,\n        then calls a function to perform the requested\n        action. Repeats until the user enters the\n        command 'q'. Prints an error message if th user\n        enters an illegal code.\n*/\nint main(int argc, char const *argv[])\n{\n    char code;\n\n    for (;;) {\n        printf(\"Enter operation code: \");\n        scanf(\" %c\", &code);\n        while (getchar() != '\\n'); /* skips to end of line */\n\n        switch(code) {\n            case 'i': insert(); break;\n            case 's': search(); break;\n            case 'u': update(); break;\n            case 'j': update_price(); break;\n            case 'p': print(); break;\n            case 'q': printf(\"bye\\n\"); return 0;\n            default: printf(\"Illegal code\\n\"); break;\n        }\n        printf(\"\\n\");\n    }\n\n    return 0;\n}\n\n/*\n    find_part: Looks up a part number in the inventory\n            array. Returns the array index if the part\n            number is found; otherwise, return -1.\n*/\nint find_part(int number)\n{\n    int i;\n    for (i = 0; i < num_parts; i++) {\n        if (inventory[i].number == number)\n            return i;\n    }\n    return -1;\n}\n\n/*\n    insert: Prompts the user for information about a new\n            part and then inserts the part into the\n            database. Prints an error message and returns\n            prematurely if the part already exists or the\n            database if full.\n*/\nvoid insert(void)\n{\n    int part_number;\n\n    if (num_parts == MAX_PARTS) {\n        printf(\"Database is full; cant't add more parts.\\n\");\n        return;\n    }\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &part_number);\n    if (find_part(part_number) >= 0) {\n        printf(\"Part already exists.\\n\");\n        return;\n    }\n\n    inventory[num_parts].number = part_number;\n    \n    printf(\"Enter part name: \");\n    read_line(inventory[num_parts].name, NAME_LEN);\n    \n    printf(\"Enter quantity on hand: \");\n    scanf(\"%d\", &inventory[num_parts].on_hand);\n\n    printf(\"Enter part price: \");\n    scanf(\"%d\", &inventory[num_parts].price);\n    \n    num_parts++;\n}\n\n/*\n    search: Prompts the user to enter a part number, then\n            looks up the part in the database. If the part\n            exists, prints the name and quantity on hand;\n            if not, prints an error message.\n*/\nvoid search(void)\n{\n    int i, number;\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    i = find_part(number);\n    if (i >= 0) {\n        printf(\"Part name: %s\\n\", inventory[i].name);\n        printf(\"Quantity on hand: %d\\n\", inventory[i].on_hand);\n        printf(\"Part price: %d\\n\", inventory[i].price);\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    update: Prompts the user to enter a part number.\n            Prints an error message if the part doesn't\n            exist; otherwise, prompts the user to enter\n            change in quantity on hand and updates the\n            database.\n*/\nvoid update(void)\n{\n    int i, number, change;\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    \n    i = find_part(number);\n    if (i >= 0) {\n        printf(\"Enter change in quantity on hand: \");\n        scanf(\"%d\", &change);\n        inventory[i].on_hand += change;\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\nvoid update_price(void)\n{\n    int i, number, change;\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    \n    i = find_part(number);\n    if (i >= 0) {\n        printf(\"Enter new price: \");\n        scanf(\"%d\", &change);\n        inventory[i].price = change;\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    print: Prints a listing of all parts in the database,\n            showing the part number, part name, and\n            quantity on hand. Parts are printed in the\n            order in which they were entered into the\n            database.\n*/\nvoid print(void)\n{\n    int i;\n    printf(\"Part number    Part Name          Quantity on Hand\\tprice\\n\");\n    for (i = 0; i < num_parts; i++) {\n        printf(\"%7d     %-25s%11d\\t%d\\n\", inventory[i].number, inventory[i].name, inventory[i].on_hand, inventory[i].price);\n    }\n}"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/dir_ex_09/makefile",
    "content": "CC=gcc\n\nprogram: invent.o readline.o\n\t$(CC) -g -Wall -o program invent.o readline.o\n\ninvent.o: invent.c readline.h\nreadline.o: readline.c readline.h\n\nclean:\n\t@rm -f *.o *.exe programi"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/dir_ex_09/readline.c",
    "content": "#include <ctype.h>\n#include <stdio.h>\n#include \"readline.h\"\n\nint read_line(char str[], int n)\n{\n    int ch, i = 0;\n\n    while (isspace(ch = getchar()));\n\n    while (1) {            \n        if (ch == '\\n' || ch == EOF) break;\n\n        if (i < n) {\n            str[i++] = ch;\n        }\n        ch = getchar();\n    }\n\n    str[i] = '\\0';\n\n    return i;\n}"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/dir_ex_09/readline.h",
    "content": "#ifndef READLINE_H\n#define READLINE_H\n\n/*\n    read_line: Skips leading white-space characters, then\n                reads the remainder of the input line and\n                stores it in str. Truncates the line if its\n                length exceeds n. Returns the number of\n                characters stored.\n*/\nint read_line(char str[], int n);\n\n#endif // READLINE_H"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_01.c",
    "content": "/*\n    在下列声明中，结构x和结构y都拥有名为x和y的成员：\n\n    struct { int x, y; } x;\n    struct { int x, y; } y;\n\n    基于独立的基础而言，这些声明是否合法呢？两个声明是否可以同时出现在程序中呢？请证明你的\n    想法是正确的。\n\n    answer: 可以。编译通过即证明。\n*/\n\nstruct { int x, y; } x;\nstruct { int x, y; } y;\n\nint main(int argc, char const *argv[])\n{\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_02.c",
    "content": "/*\n    (a) 声明名为c1, c2和c3的结构变量，每个结构变量都拥有 double 型的成员 re 和im 。\n    (b) 修改(a)中的声明，使c1的成员初始值为0.0和1.0，而c2的成员初始值为1.0和0.0。(c3无初始值。)\n    (c) 编写名为 make_complex 的函数，此函数用来把两个实际参数（两个参数的类型都是 double 型）\n        存储在 complex 型结构中，然后返回此结构。\n    (d) 编写名为 add_complex 的函数，此函数用来把两个实际参数（都是 complex 型结构）的相应成员\n        进行相加运算，然后返回结果（另一个 complex 型结构）。\n*/\n\n/* a */\n/*\nstruct {\n    double re, im;\n} c1, c2, c3;\n*/\n\n/* b */\nstruct {\n    double re, im;\n} c1 = {0.0, 1.0}, c2 = {1.0, 0.0}, c3;\n\n/* c */\ntypedef struct complex {\n    double param1;\n    double param2;\n} complex;\ncomplex make_complex(double p1, double p2)\n{\n    complex c = {p1, p2};\n    return c;\n}\n\n/* d */\ncomplex add_complex(complex c1, complex c2)\n{\n    complex c;\n    c.param1 = c1.param1 + c2.param1;\n    c.param2 = c1.param2 + c2.param2;\n    return c;\n}\n\nint main(int argc, char const *argv[])\n{\n    /* code */\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_04.md",
    "content": "重做练习3的各种操作，这次要求使用类型来命名 complex 。\n\n---\n\n略。"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_05.c",
    "content": "/*\n    下列结构用来存储图形屏幕上的对象信息。结构 point 用来存储屏幕上点的x轴和y轴坐标，结构\n    rectangle 用来存储矩形的左上和右下坐标点。\n\n    struct point { int x, y;};\n    struct rectangle { struct point upper_left, lower_right; };\n\n    编写函数，要求可以在 rectangle 型结构变量r上执行下列操作，且r作为实际参数传递。\n\n    (a) 计算r的面积。\n    (b) 计算r的中心，并且把此中心作为 point 型的值返回。\n    (c) 移动r，方法是x单元按照X轴方向移动，y单元按照y轴移动，并且返回r修改后的内容。（x和y是函数额外的实际参数。）\n    (d) 确定点p是否位于r内，返回 TRUE 或者 FALSE 。（p是具有 struct point 类型的额外的实际参数。）\n*/\n\n#include <stdio.h>\n\nstruct point\n{\n    int x, y;\n};\nstruct rectangle\n{\n    struct point upper_left, lower_right;\n};\n\n/* a */\nint GetArea(struct rectangle r)\n{\n    int h = r.lower_right.x - r.upper_left.x;\n    int w = r.lower_right.y - r.upper_left.y;\n    return h * w;\n}\n\n/* b */\nstruct point GetMidPoint(struct rectangle r)\n{\n    int mid_x = r.upper_left.x + (r.lower_right.x - r.upper_left.x) / 2;\n    int mid_y = r.upper_left.y + (r.lower_right.y - r.upper_left.y) / 2;\n    struct point mid_point = {mid_x, mid_y};\n    return mid_point;\n}\n\n/* c */\nstruct rectangle move(struct rectangle r, int x, int y)\n{\n    r.lower_right.x += x;\n    r.upper_left.x += x;\n\n    r.lower_right.y += y;\n    r.upper_left.y += y;\n\n    return r;\n}\n\n/* d */\ntypedef enum\n{\n    TRUE,\n    FALSE\n} Bool;\nBool IsIn(struct rectangle r, struct point p)\n{\n    return p.x >= r.upper_left.x && p.x <= r.lower_right.x &&\n           p.y >= r.upper_left.y && p.y <= r.lower_right.y;\n}\n\nint main(int argc, char const *argv[])\n{\n    struct rectangle r = {{0, 0}, {6, 8}}; /* 以左上角为坐标轴原点 */\n\n    printf(\"area: %d\\n\", GetArea(r));\n    printf(\"mid point: (%d,%d)\\n\", GetMidPoint(r).x, GetMidPoint(r).y);\n\n    r = move(r, -3, -4);\n    printf(\"move (-3, -4): {(%d, %d), (%d, %d)}\\n\", r.upper_left.x, r.upper_left.y, r.lower_right.x, r.lower_right.y);\n\n    struct point p = {1, 1};\n    printf(\"point(1, 1) is in rectangle? %s\\n\", IsIn(r, p) ? \"yes\" : \"no\");\n\n    struct point p2 = {4, 1};\n    printf(\"point(4, 1) is in rectangle? %s\\n\", IsIn(r, p2) ? \"yes\" : \"no\");\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_06.c",
    "content": "/*\n    编写程序用来要求用户录入国家（地区）名称，然后可以在数组 country_codes 中查找到它。如果\n    找到对应的国家（地区）名称，程序需要显示相应的国家（地区）电话代码。如果没有找到，程序应该\n    显示出错消息。\n*/\n\n#include <stdio.h>\n#include <string.h>\n#include <ctype.h>\n\n#define NAME_LEN 25\n#define CODE_LEN 30\n#define COUNT_COUNT 100\n\nint country_count = 0;\n\nstruct counry {\n    char name[NAME_LEN + 1];\n    char phone_code[CODE_LEN + 1];\n} country_codes[COUNT_COUNT];\n\nint read_line(char str[], int n)\n{\n    int ch, i = 0;\n\n    while (isspace(ch = getchar()));\n\n    while (1) {            \n        if (ch == '\\n' || ch == EOF) break;\n\n        if (i < n) {\n            str[i++] = ch;\n        }\n        ch = getchar();\n    }\n\n    str[i] = '\\0';\n\n    return i;\n}\n\n/* 根据姓名查询，返回下标，-1代表没有找到 */\nint find(char *name)\n{\n    int i;\n    for (i = 0; i < country_count; i++) {\n        if (strcmp(name, country_codes[i].name) == 0) {\n            return i;\n        }\n    }\n    return -1;\n}\n\nvoid insert()\n{\n    char name[NAME_LEN + 1];\n    char phone_code[CODE_LEN + 1];\n\n    if (country_count >= COUNT_COUNT) {\n        printf(\"database full!\\n\");\n        return;\n    }\n\n    printf(\"Enter country name: \");\n    read_line(name, NAME_LEN);\n    if (find(name) >= 0) {\n        printf(\"country already exist!\\n\");\n        return;\n    }\n\n    printf(\"Enter phone code: \");\n    read_line(phone_code, CODE_LEN);\n\n    strcpy(country_codes[country_count].name, name);\n    strcpy(country_codes[country_count].phone_code, phone_code);\n\n    country_count++;\n}\n\nvoid search()\n{\n    char name[NAME_LEN + 1];\n    printf(\"Enter country name you want to search: \");\n    read_line(name, NAME_LEN);\n\n    int index = find(name);\n    if (index < 0) {\n        printf(\"no such country\\n\");\n        return;\n    }\n\n    printf(\"country's phone code is: %s\\n\", country_codes[index].phone_code);\n}\n\nint main(int argc, char const *argv[])\n{\n    char op;\n    for (;;) {\n        printf(\"Enter options(i for insert, s for search, q for quit): \");\n        scanf(\" %c\", &op);\n        while (getchar() != '\\n');\n\n        switch (op) {\n            case 'i': insert(); break;\n            case 's': search(); break;\n            case 'q': printf(\"bye\\n\"); return 0;\n            default: printf(\"error input\\n\"); break;\n        }\n    }\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_07.md",
    "content": "修改程序 invent.c 使p(显示)操作可以根据零件数显示存储的零件。\n\n---\n\n见[print_ex07函数](./program_parts/invent.c)"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_08.md",
    "content": "修改程序 invent.c 使 inventory 和 num_parts 成为 main 函数的局部内容。\n\n---\n\n见[程序](./dir_ex_08)"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_09.md",
    "content": "通过为结构 part 添加成员 price 来修改程序 invent.c 。 insert 函数应该要求用户录入新的数据项的价格。 search 函数和 print 函数应该显示价格。添加新的命令，从而允许用户改变零件的价格。\n\n---\n\n见[程序](./dir_ex_09)"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_10.c",
    "content": "/*\n    假设s具有下列结构：\n    struct {\n        float a;\n        union {\n            char b[4];\n            float c;\n            int d;\n        } e;\n    };\n\n    如果 char 型值占有1个字节， int 型值占有2个字节，而 float 型值占有4个字节，那么编译器将为s分配多大的内存空间？\n    （假设编译器没有在成员之间留“空洞”。）\n*/\n\n/*\n    8个字节。\n*/\n\n#include <stdio.h>\n\n#pragma pack(push, 1)   /* 1字节对齐，保证不留空洞 */\n\nstruct T {\n    float a;\n    union {\n        char b[4];\n        float c;\n        int d;\n    } e;\n};\n\n#pragma pack(pop)\n\nint main(int argc, char const *argv[])\n{\n    printf(\"%u\\n\", sizeof(struct T));\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_11.c",
    "content": "/*\n    假设s具有下列结构（ point 是在练习5中声明的结构标记 ）：\n\n    struct shape {\n        int shape_kind;\n        struct point center;\n        union {\n            struct {\n                int length, width;\n            } rectangle;\n\n            struct {\n                int radius;\n            } circle;\n        } u;\n    } s;\n\n    请指出下列哪些语句是合法的，并且说明如何修改不合法的语句：\n    (a) s.shape_kind = RECTANGLE;\n    (b) s.center.x = 10;\n    (c) s.length = 25;\n    (d) s.u.rectangle.width = 8;\n    (e) s.u.circle = 5;\n    (f) s.u.radius = 5;\n*/\n\nenum { RECTANGLE, CIRCLE };\n\nstruct point {\n    int x, y;\n};\n\nstruct shape {\n    int shape_kind;\n    struct point center;\n    union {\n        struct {\n            int length, width;\n        } rectangle;\n\n        struct {\n            int radius;\n        } circle;\n    } u;\n} s;\n\nint main(int argc, char const *argv[])\n{\n    /* a 合法 */\n    s.shape_kind = RECTANGLE;\n\n    /* b 合法 */\n    s.center.x = 10;\n\n    /* c 不合法，已修改 */\n    s.u.rectangle.length = 25;\n\n    /* d 合法 */\n    s.u.rectangle.width = 8;\n\n    /* e 不合法，已修改 */\n    s.u.circle.radius = 5;\n\n    /* f 不合法，已修改 */\n    s.u.circle.radius = 5;\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_12.c",
    "content": "/*\n    假设 shape 是练习11中声明的结构标记。编写函数用来在 shape 型结构变量s上完成下列操作，并且s\n    作为实际参数在函数间传递：\n\n    (a) 计算s的面积。\n    (b) 计算s的中心，返回 point 型的中心值。\n    (c) 移动s，方法是x单元按照x轴方向移动，y单元按照y轴移动，并且返回s修改后的内容。（x和y是函数额外的实际参数。）\n    (d) 确定点p是否位于s内，返回 TRUE 或者 FALSE 。（p是具有 struct point 类型的额外的实际参数。）\n*/\n\n#include <stdio.h>\n\n#define PI 3.14\n\ntypedef enum { TRUE, FALSE } Bool;\n\nenum { RECTANGLE, CIRCLE };\n\nstruct point {\n    int x, y;\n};\n\nstruct shape {\n    int shape_kind;\n    struct point center;\n    union {\n        struct {\n            int length, width;\n        } rectangle;\n\n        struct {\n            int radius;\n        } circle;\n    } u;\n};\n\ntypedef struct point Point;\ntypedef struct shape Shape;\n\n/* a */\ndouble GetArea(const Shape* shape);\n\n/* b */\nPoint GetCenter(const Shape* shape)\n{\n    return shape->center;\n}\n\n/* c */\nvoid Move(Shape* shape, int x, int y)\n{\n    shape->center.x += x;\n    shape->center.y += y;\n}\n\n/* d */\nBool IsIn(const Shape* shape, Point p)\n{\n    if (shape->shape_kind == RECTANGLE) {\n        Point upper_left, lower_right;\n        upper_left.x = shape->center.x - shape->u.rectangle.length / 2;\n        upper_left.y = shape->center.y - shape->u.rectangle.width / 2;\n        lower_right.x = shape->center.x + shape->u.rectangle.length / 2;\n        lower_right.y = shape->center.y + shape->u.rectangle.width / 2;\n\n        return p.x >= upper_left.x && p.x <= lower_right.x &&\n                p.y >= upper_left.y && p.y <= lower_right.y;\n    }\n    else {\n        return (p.x - shape->center.x) * (p.x - shape->center.x) + (p.y - shape->center.y) * (p.y - shape->center.y) <= shape->u.circle.radius * shape->u.circle.radius;\n    }\n}\n\nint main(int argc, char const *argv[])\n{\n    Point zero = {0, 0};\n\n    Shape s;\n    s.shape_kind = RECTANGLE;\n    s.center.x = s.center.y = 0;\n    s.u.rectangle.length = 6;\n    s.u.rectangle.width = 8;\n    /* Move(&s, 7, 7); */\n    printf(\"Area of rectangle s: %g\\n\", GetArea(&s));\n    printf(\"Is point(0, 0) in s: %s\\n\", IsIn(&s, zero) ? \"yes\" : \"no\");\n\n    s.shape_kind = CIRCLE;\n    s.u.circle.radius = 6;\n    printf(\"Area of circle s: %g\\n\", GetArea(&s));\n    printf(\"Is point(0, 0) in s: %s\\n\", IsIn(&s, zero) ? \"yes\" : \"no\");\n\n    return 0;\n}\n\ndouble GetArea(const Shape* shape)\n{\n    if (shape->shape_kind == RECTANGLE) {\n        return shape->u.rectangle.length * shape->u.rectangle.width;\n    }\n    else {\n        return PI * shape->u.circle.radius * shape->u.circle.radius;\n    }\n}"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_13.md",
    "content": "编写一个类似于 invent.c 的程序，利用 catalog_item 型结构来存储礼品册中数据项的信息。\n\n---\n\n略。"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_14.c",
    "content": "/*\n    (a) 为枚举声明标记，此枚举的值表示一个星期的7天。\n    (b) 用 typedef 定义(a)中枚举的名字。\n*/\n\n/*\n    (a)\n    enum { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY };\n*/\n\ntypedef enum { SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } WEEKDAY;\n\nint main(int argc, char const *argv[])\n{\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_15.md",
    "content": "下列关于枚举常量的叙述哪些是正确的？\n\n(a) 枚举常量可以表示程序员指明的任何整数。\n\n(b) 枚举常量具有的性质和用`#define`产生的常量的性质完全一样\n\n(c) 枚举常量的默认值为0,1,2...\n\n(d) 枚举中的任何常量必须具有不同的值。\n\n(e) 枚举常量在表达式中可以作为整数使用。\n\n---\n\n(a) (c) (e)"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_16.md",
    "content": "假设b和i具有如下形式的声明：\n\n```c\nenum { FALSE, TRUE } b;\nint i;\n```\n\n下列哪些语句是合法的？哪些是“安全的”（始终产生有意义的结果）？\n\n(a) b = FALSE;\n\n(b) b = i;\n\n(c) b++;\n\n(d) i = b;\n\n(e) 2 * b + i;\n\n---\n\n全部合法。\n\n安全的有：(b) (d)"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/ex_17.c",
    "content": "/*\n    (a) 棋盘的每个角上可能会有一个棋子，即兵、士、相、车、皇后或国王，也可能为空。每个棋子可能为\n        黑色的也可能是白色的。请定义两个枚举类型 Piece 用来包含7种可能的值（其中一种为\"空\"），\n        Color 用来表示两种颜色。\n    (b) 利用(a)中的类型，定义名为 Square 的结构类型，使此类型可以存储棋子的类型和颜色。\n    (c) 利用(b)中的类型 Square ，声明一个名为 board 的8*8的数组，此数组可以用来存储棋盘上的全部内容。\n    (d) 给(c)中的声明添加初始值，使 board 的初始值对应日常国际象棋比赛开始时的棋子布置。\n*/\n\ntypedef enum piece {\n    NonePiece,\n    Pawn,\n    Knight,\n    Bishop,\n    Rook,\n    Queen,\n    King\n} Piece;\n\ntypedef enum color {\n    NoneColor,\n    White,\n    Black\n} Color;\n\ntypedef struct square {\n    Piece piece;\n    Color color;\n} Square;\n\nSquare board[8][8] = {\n    { {Rook, Black}, {Knight, Black}, {Bishop, Black}, {King, Black}, {Queen, Black}, {Bishop, Black}, {Knight, Black}, {Rook, Black} },\n    { {Pawn, Black}, {Pawn, Black}, {Pawn, Black}, {Pawn, Black}, {Pawn, Black}, {Pawn, Black}, {Pawn, Black}, {Pawn, Black} },\n    { {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0} },\n    { {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0} },\n    { {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0} },\n    { {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0} },\n    { {Pawn, White}, {Pawn, White}, {Pawn, White}, {Pawn, White}, {Pawn, White}, {Pawn, White}, {Pawn, White}, {Pawn, White} },\n    { {Rook, White}, {Knight, White}, {Bishop, White}, {King, White}, {Queen, White}, {Bishop, White}, {Knight, White}, {Rook, White} },\n};\n\nint main(int argc, char const *argv[])\n{\n    /* code */\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/program_parts/invent.c",
    "content": "/*\n    invent.c\n    Maintains a parts database (array version)\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include \"readline.h\"\n\n#define NAME_LEN 25\n#define MAX_PARTS 100\n\nstruct part {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n} inventory[MAX_PARTS];\n\nint num_parts = 0;      /* number of parts currently stored */\n\nint find_part(int number);\nvoid insert(void);\nvoid search(void);\nvoid update(void);\nvoid print(void);\nvoid print_ex07(void);\nvoid print_ch17_ex16(void);\n\n/*\n    main: Prompts the user to enter an operator code,\n        then calls a function to perform the requested\n        action. Repeats until the user enters the\n        command 'q'. Prints an error message if th user\n        enters an illegal code.\n*/\nint main(int argc, char const *argv[])\n{\n    char code;\n\n    for (;;) {\n        printf(\"Enter operation code: \");\n        scanf(\" %c\", &code);\n        while (getchar() != '\\n'); /* skips to end of line */\n\n        switch(code) {\n            case 'i': insert(); break;\n            case 's': search(); break;\n            case 'u': update(); break;\n            case 'p': print_ch17_ex16(); break;\n            case 'q': printf(\"bye\\n\"); return 0;\n            default: printf(\"Illegal code\\n\"); break;\n        }\n        printf(\"\\n\");\n    }\n\n    return 0;\n}\n\n/*\n    find_part: Looks up a part number in the inventory\n            array. Returns the array index if the part\n            number is found; otherwise, return -1.\n*/\nint find_part(int number)\n{\n    int i;\n    for (i = 0; i < num_parts; i++) {\n        if (inventory[i].number == number)\n            return i;\n    }\n    return -1;\n}\n\n/*\n    insert: Prompts the user for information about a new\n            part and then inserts the part into the\n            database. Prints an error message and returns\n            prematurely if the part already exists or the\n            database if full.\n*/\nvoid insert(void)\n{\n    int part_number;\n\n    if (num_parts == MAX_PARTS) {\n        printf(\"Database is full; cant't add more parts.\\n\");\n        return;\n    }\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &part_number);\n    if (find_part(part_number) >= 0) {\n        printf(\"Part already exists.\\n\");\n        return;\n    }\n\n    inventory[num_parts].number = part_number;\n    \n    printf(\"Enter part name: \");\n    read_line(inventory[num_parts].name, NAME_LEN);\n    \n    printf(\"Enter quantity on hand: \");\n    scanf(\"%d\", &inventory[num_parts].on_hand);\n    \n    num_parts++;\n}\n\n/*\n    search: Prompts the user to enter a part number, then\n            looks up the part in the database. If the part\n            exists, prints the name and quantity on hand;\n            if not, prints an error message.\n*/\nvoid search(void)\n{\n    int i, number;\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    i = find_part(number);\n    if (i >= 0) {\n        printf(\"Part name: %s\\n\", inventory[i].name);\n        printf(\"Quantity on hand: %d\\n\", inventory[i].on_hand);\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    update: Prompts the user to enter a part number.\n            Prints an error message if the part doesn't\n            exist; otherwise, prompts the user to enter\n            change in quantity on hand and updates the\n            database.\n*/\nvoid update(void)\n{\n    int i, number, change;\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    \n    i = find_part(number);\n    if (i >= 0) {\n        printf(\"Enter change in quantity on hand: \");\n        scanf(\"%d\", &change);\n        inventory[i].on_hand += change;\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    print: Prints a listing of all parts in the database,\n            showing the part number, part name, and\n            quantity on hand. Parts are printed in the\n            order in which they were entered into the\n            database.\n*/\nvoid print(void)\n{\n    int i;\n    printf(\"Part number    Part Name          Quantity on Hand\\n\");\n    for (i = 0; i < num_parts; i++) {\n        printf(\"%7d     %-25s%11d\\n\", inventory[i].number, inventory[i].name, inventory[i].on_hand);\n    }\n}\n\nvoid print_ex07(void)\n{\n    int i;\n    int number;\n    printf(\"Enter min on_hand number: \");\n    scanf(\"%d\", &number);\n\n    printf(\"Part number    Part Name          Quantity on Hand\\n\");\n    for (i = 0; i < num_parts; i++) {\n        if (inventory[i].on_hand >= number)\n            printf(\"%7d     %-25s%11d\\n\", inventory[i].number, inventory[i].name, inventory[i].on_hand);\n    }\n}\n\nint compare_parts(const void *p, const void *q)\n{\n    return ((struct part*)q)->number - ((struct part*)p)->number;\n}\n\nvoid print_ch17_ex16(void)\n{\n    int i;\n\n    qsort(inventory, num_parts, sizeof(*inventory), compare_parts);\n\n    printf(\"Part number    Part Name          Quantity on Hand\\n\");\n    for (i = 0; i < num_parts; i++) {\n        printf(\"%7d     %-25s%11d\\n\", inventory[i].number, inventory[i].name, inventory[i].on_hand);\n    }\n}"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/program_parts/makefile",
    "content": "CC=gcc\n\nprogram: invent.o readline.o\n\t$(CC) -g -Wall -o program invent.o readline.o\n\ninvent.o: invent.c readline.h\nreadline.o: readline.c readline.h\n\nclean:\n\t@rm -f *.o *.exe programi"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/program_parts/readline.c",
    "content": "#include <ctype.h>\n#include <stdio.h>\n#include \"readline.h\"\n\nint read_line(char str[], int n)\n{\n    int ch, i = 0;\n\n    while (isspace(ch = getchar()));\n\n    while (1) {            \n        if (ch == '\\n' || ch == EOF) break;\n\n        if (i < n) {\n            str[i++] = ch;\n        }\n        ch = getchar();\n    }\n\n    str[i] = '\\0';\n\n    return i;\n}"
  },
  {
    "path": "codes/CProgramming/ch16_结构_联合_枚举/program_parts/readline.h",
    "content": "#ifndef READLINE_H\n#define READLINE_H\n\n/*\n    read_line: Skips leading white-space characters, then\n                reads the remainder of the input line and\n                stores it in str. Truncates the line if its\n                length exceeds n. Returns the number of\n                characters stored.\n*/\nint read_line(char str[], int n);\n\n#endif // READLINE_H"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/dir_ex_04/invent.c",
    "content": "/*\n    invent.c\n    Maintains a parts database (array version)\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include \"readline.h\"\n\n#define NAME_LEN 25\n\nstruct part {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n};\n\nint part_count = 10;\nstruct part *inventory;\n\nint num_parts = 0;      /* number of parts currently stored */\n\nint find_part(int number);\nvoid insert(void);\nvoid search(void);\nvoid update(void);\nvoid print(void);\n\n/*\n    main: Prompts the user to enter an operator code,\n        then calls a function to perform the requested\n        action. Repeats until the user enters the\n        command 'q'. Prints an error message if th user\n        enters an illegal code.\n*/\nint main(int argc, char const *argv[])\n{\n    char code;\n\n    inventory = malloc(sizeof(*inventory) * part_count);\n    if (inventory == NULL) {\n        printf(\"No space left!\\n\");\n        return 1;\n    }\n\n    for (;;) {\n        printf(\"Enter operation code: \");\n        scanf(\" %c\", &code);\n        while (getchar() != '\\n'); /* skips to end of line */\n\n        switch(code) {\n            case 'i': insert(); break;\n            case 's': search(); break;\n            case 'u': update(); break;\n            case 'p': print(); break;\n            case 'q': printf(\"bye\\n\"); free(inventory); return 0;\n            default: printf(\"Illegal code\\n\"); break;\n        }\n        printf(\"\\n\");\n    }\n\n    free(inventory);\n    return 0;\n}\n\n/*\n    find_part: Looks up a part number in the inventory\n            array. Returns the array index if the part\n            number is found; otherwise, return -1.\n*/\nint find_part(int number)\n{\n    int i;\n    for (i = 0; i < num_parts; i++) {\n        if (inventory[i].number == number)\n            return i;\n    }\n    return -1;\n}\n\n/*\n    insert: Prompts the user for information about a new\n            part and then inserts the part into the\n            database. Prints an error message and returns\n            prematurely if the part already exists or the\n            database if full.\n*/\nvoid insert(void)\n{\n    int part_number;\n    struct part *tmp_inventory;\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &part_number);\n    if (find_part(part_number) >= 0) {\n        printf(\"Part already exists.\\n\");\n        return;\n    }\n\n    if (num_parts == part_count) {\n        tmp_inventory = realloc(inventory, sizeof(*inventory) * part_count * 2);\n        if (tmp_inventory == NULL) {\n            printf(\"Insert failure: no space left!\\n\");\n            return;\n        }\n        inventory = tmp_inventory;\n        part_count *= 2;\n    }\n\n    inventory[num_parts].number = part_number;\n    \n    printf(\"Enter part name: \");\n    read_line(inventory[num_parts].name, NAME_LEN);\n    \n    printf(\"Enter quantity on hand: \");\n    scanf(\"%d\", &inventory[num_parts].on_hand);\n    \n    num_parts++;\n}\n\n/*\n    search: Prompts the user to enter a part number, then\n            looks up the part in the database. If the part\n            exists, prints the name and quantity on hand;\n            if not, prints an error message.\n*/\nvoid search(void)\n{\n    int i, number;\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    i = find_part(number);\n    if (i >= 0) {\n        printf(\"Part name: %s\\n\", inventory[i].name);\n        printf(\"Quantity on hand: %d\\n\", inventory[i].on_hand);\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    update: Prompts the user to enter a part number.\n            Prints an error message if the part doesn't\n            exist; otherwise, prompts the user to enter\n            change in quantity on hand and updates the\n            database.\n*/\nvoid update(void)\n{\n    int i, number, change;\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    \n    i = find_part(number);\n    if (i >= 0) {\n        printf(\"Enter change in quantity on hand: \");\n        scanf(\"%d\", &change);\n        inventory[i].on_hand += change;\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    print: Prints a listing of all parts in the database,\n            showing the part number, part name, and\n            quantity on hand. Parts are printed in the\n            order in which they were entered into the\n            database.\n*/\nvoid print(void)\n{\n    int i;\n    printf(\"Part number    Part Name          Quantity on Hand\\n\");\n    for (i = 0; i < num_parts; i++) {\n        printf(\"%7d     %-25s%11d\\n\", inventory[i].number, inventory[i].name, inventory[i].on_hand);\n    }\n}"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/dir_ex_04/makefile",
    "content": "CC=gcc\n\nprogram: invent.o readline.o\n\t$(CC) -g -Wall -o program invent.o readline.o\n\ninvent.o: invent.c readline.h\nreadline.o: readline.c readline.h\n\nclean:\n\t@rm -f *.o *.exe programi"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/dir_ex_04/readline.c",
    "content": "#include <ctype.h>\n#include <stdio.h>\n#include \"readline.h\"\n\nint read_line(char str[], int n)\n{\n    int ch, i = 0;\n\n    while (isspace(ch = getchar()));\n\n    while (1) {            \n        if (ch == '\\n' || ch == EOF) break;\n\n        if (i < n) {\n            str[i++] = ch;\n        }\n        ch = getchar();\n    }\n\n    str[i] = '\\0';\n\n    return i;\n}"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/dir_ex_04/readline.h",
    "content": "#ifndef READLINE_H\n#define READLINE_H\n\n/*\n    read_line: Skips leading white-space characters, then\n                reads the remainder of the input line and\n                stores it in str. Truncates the line if its\n                length exceeds n. Returns the number of\n                characters stored.\n*/\nint read_line(char str[], int n);\n\n#endif // READLINE_H"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_01.c",
    "content": "/*\n    每次调用时都检查函数 malloc 的返回值是件很烦人的事情。请编写一个名为 my_malloc 的函数，用\n    来作为 malloc 函数的“包装器”。当调用函数 my_malloc 并且要求分配n个字节时，它会转到调用\n    malloc 函数，判断 malloc 函数确实没有返回空指针，然后返回来自 malloc 的指针。如果 malloc\n    返回空指针，那么函数 my_malloc 显示出错信息并且终止程序。\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nvoid *my_malloc(size_t count, size_t sz);\n\nint main(int argc, char const *argv[])\n{\n    char *str = my_malloc(6, sizeof(char));\n    strcpy(str, \"Hello\");\n\n    printf(\"%s\\n\", str);\n\n    free(str);\n\n    return 0;\n}\n\nvoid *my_malloc(size_t count, size_t sz)\n{\n    void *ret = malloc(count * sz);\n    if (ret == NULL) {\n        printf(\"No space left!\\n\");\n        exit(EXIT_FAILURE);\n    }\n    return ret;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_02.c",
    "content": "/*\n    请编写名为 strdup 的函数，此函数使用动态存储分配来产生字符串的副本。例如，调用\n    p = strdup(str);\n    将为和 str 长度相同的字符串分配内存空间，并且把字符串 str 的内容复制给新字符串，然后返回指向\n    新字符串的指针。如果分配内存失败，那么函数 strdup 返回空指针。\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n\nchar* strdup(const char* str);\n\nint main(int argc, char const *argv[])\n{\n    char *str = strdup(\"Hello World\");\n\n    printf(\"%s\\n\", str);\n\n    free(str);\n    \n    return 0;\n}\n\nchar* strdup(const char* str)\n{\n    char *ret, *p;\n    const char *q;\n    int str_len = 0;\n\n    q = str;\n    while (*str++ != '\\0') {\n        str_len++;\n    }\n\n    ret = (char*)malloc(1 + str_len * sizeof(char));\n    if (ret != NULL) {\n        p = ret;\n        while ((*p++ = *q++));\n    }\n    return ret;\n}"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_03.c",
    "content": "/*\n    请编写一个程序把用户录入的一系列单词进行排序，并且显示删除的重复部分。提示：采用指针数组，\n    且每个指针都指向动态分配的字符串。额外加分：使用 qsort 函数（17.7节）进行排序操作。\n*/\n\n#include <stdlib.h>\n#include <stdio.h>\n#include <string.h>\n\n#define WORD_COUNT 10\n#define WORD_LEN 32\n\nint find_word(char *words[WORD_COUNT], int word_count, const char *word)\n{\n    int i;\n    for (i = 0; i < word_count; i++) {\n        if (strcmp(words[i], word) == 0)\n            return i;\n    }\n    return -1;\n}\n\nint compare_str(const void *p1, const void *p2)\n{\n    return strcmp(*(char**)p1, *(char**)p2);\n}\n\nint main(int argc, char const *argv[])\n{\n    int word_count = 0;\n    int i;\n    char *words[WORD_COUNT] = {0};\n    char word[WORD_LEN];\n\n    while (1) {\n        printf(\"Enter word(# to quit): \");\n        scanf(\"%s\", word);\n        if (word[0] == '#') break;\n        \n        if (find_word(words, word_count, word) >= 0) {\n            printf(\"Duplicate word: %s\\n\", word);\n            continue;\n        }\n        \n        words[word_count] = malloc(sizeof(char) * strlen(word) + 1);\n        strcpy(words[word_count], word);\n        word_count++;\n    }\n\n    qsort(words, word_count, sizeof(char*), compare_str);\n\n    printf(\"words:\");\n    for (i = 0; i < word_count; i++) {\n        printf(\" %s\", words[i]);\n    }\n    printf(\"\\n\");\n\n    for (i = 0; i < word_count; i++) {\n        free(words[i]);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_04.md",
    "content": "请修改程序 invent.c (16.3节)，使其可以对数组 inventory 进行动态内存分配，并且稍后在填满时再次进行内存分配。初始使用 malloc 为拥有10个 part 结构的数组分配足够的内存空间。当数组没有足够的空间给新的零件时，使用 realloc 函数来使内存数量加倍。在每次数组变满时重复加倍操作步骤。\n\n---\n\n见 [程序](./dir_ex_04)"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_05.c",
    "content": "/*\n    假设下列声明有效：\n\n    struct point { int x, y; };\n    struct rectangle { struct point upper_left, lower_right; };\n    struct rectangle *p;\n\n    假设希望p指向结构 rectangle , 其中此结构左上角位于(0,1)的位置上，而右下角位于(1,0)的位置\n    上。请编写一系列语句用来分配这样一个结构，并且像说明的那样进行初始化。\n*/\n\n#include <stdlib.h>\n#include <stdio.h>\n\nstruct point { int x, y; };\nstruct rectangle { struct point upper_left, lower_right; };\nstruct rectangle *p;\n\nvoid print_rectangle(struct rectangle *p)\n{\n    printf(\"{(%d,%d), (%d,%d)}\",\n        p->upper_left.x,\n        p->upper_left.y,\n        p->lower_right.x,\n        p->lower_right.y);\n}\n\nint main(int argc, char const *argv[])\n{\n    p = malloc(sizeof(struct rectangle));\n    if (p == NULL) {\n        printf(\"No space left!\\n\");\n        return 1;\n    }\n    p->upper_left.x = 0;\n    p->upper_left.y = 1;\n    p->lower_right.x = 1;\n    p->lower_right.y = 0;\n\n    printf(\"p: \");\n    print_rectangle(p);\n    printf(\"\\n\");\n\n    free(p);\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_06.c",
    "content": "/*\n    假设f和p的声明如下所示：\n\n    struct {\n        union {\n            char a, b;\n            int c;\n        }d;\n        int e[5];\n    }f, *p = &f;\n\n    那么下列哪些语句是合法的？\n    (a) p->b = ' ';\n    (b) p->e[3] = 10;\n    (c) (*p).d.a = '*';\n    (d) p->d->c = 20;\n*/\n\n/*\n    合法的有：b c\n*/\n\nstruct {\n    union {\n        char a, b;\n        int c;\n    }d;\n    int e[5];\n}f, *p = &f;\n\nint main(int argc, char const *argv[])\n{\n    /* a */\n    /* p->b = ' '; */\n\n    /* b */\n    p->e[3] = 10;\n\n    /* c */\n    (*p).d.a = '*';\n\n    /* d */\n    /* p->d->c = 20; */\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_07.md",
    "content": "请修改函数 delete_from_list 使它只使用一个指针变量而不是两个（即 cur 和 prev）。\n\n---\n\n见 [list.c](./list/list.c) 中的 delete_from_list_ex_07 函数。"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_08.md",
    "content": "假设下列循环删除了链表中的全部节点，并且释放了占用的内存。但是，此循环有错误。请解释错误是什么并且说明如何修正错误。\n\n```c\nfor (p = first; p != NULL; p = p->next)\n    free(p);\n```\n\n---\n\n错误在于 free(p) 将释放掉p所指向的内容，包括 next ，因此 p = p->next 的操作将是没有意义的。修改成：\n\n```c\nstruct node *tmp;\nfor (p = first; p != NULL;) {\n    tmp = p;\n    p = p->next;\n    free(tmp);\n}\n```"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_09.md",
    "content": "请修改程序 invent2.c, 方法是增加允许用户把零件从数据库中删除的e（擦出）命令。\n\n---\n\n见[程序](./program_parts/invent.c) 中的 erase 函数。"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_10.c",
    "content": "/*\n    15.2 节描述的文件 stack.c 提供了在栈中进行整数排序的函数。在那一节中，栈是用数组实现的。请修改程序\n    stack.c 从而使栈现在可以作为链表来存储。使用单独一个指向链表首节点的指针变量（栈\n    “顶”）来替换变量 contents 和变量 top 。在 stack.c 中编写的函数要使用此指针。删除函数\n    is_full , 用返回 TRUE (如果创建的节点可以获得内存)或 FALSE (如果创建的节点无法获得内存)\n    的函数 push 来代替。 \n*/\n\n#include <stdlib.h>\n#include <stdio.h>\n\ntypedef enum { TRUE, FALSE } Bool;\n\nstruct node {\n    int value;\n    struct node *next;\n};\n\nint is_empty(struct node *stack)\n{\n\treturn stack == NULL;\n}\n\nBool push(struct node **stack, int i)\n{\n\tstruct node *node = malloc(sizeof(struct node));\n    if (node == NULL) {\n        return FALSE;\n    }\n\n    node->value = i;\n    node->next = *stack;\n\n    *stack = node;\n\n    return TRUE;\n}\n\nint pop(struct node **stack)\n{\n\tstruct node *node;\n    int v;\n\n    node = *stack;\n    *stack = node->next;\n\n    v = node->value;\n    free(node);\n\n\treturn v;\n}\n\nvoid make_empty(struct node **stack)\n{\n    while (*stack) {\n        pop(stack);\n    }\n}\n\n//-------------------------------------\n\nint main()\n{\n\tint i;\n    struct node *stack = NULL;\n\n\tfor (i = 0; i < 10; ++ i)\n\t{\n\t\tpush(&stack, i);\n\t}\n\n    /* make_empty(&stack); */\n\n\twhile ( !is_empty(stack))\n\t{\n\t\tprintf(\"%d \", pop(&stack));\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_11.md",
    "content": "请修改函数 delete_from_list (17.5节)，使函数的第一个实际参数是 `struct node **` 类型（即指向链表首节点的指针），并且返回类型是 void 。在删除了期望的节点后，函数 delete_from_list 必须修改第一个实际参数，使其指向该链表。\n\n---\n\n见 [list.c](./list/list.c) 中的 delete_from_list_ex_11 函数。"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_12.c",
    "content": "/*\n    请说明下列程序的输出结果，并且说明理由。\n\n    #include <stdio.h>\n\n    int f1(int (*f)(int));\n    int f2(int i);\n\n    int main()\n    {\n        printf(\"Answer: %d\\n\", f1(f2));\n        return 0;\n    }\n\n    int f1(int (*f)(int))\n    {\n        int n = 0;\n        while ((*f)(n)) n++;\n        return n;\n    }\n\n    int f2(int i)\n    {\n        return i * i + i - 12;\n    }\n*/\n\n/*\n    Answer: 3\n    当n为3时，f1 中的 while 循环条件检查失败，返回\n*/\n\n#include <stdio.h>\n\nint f1(int (*f)(int));\nint f2(int i);\n\nint main()\n{\n    printf(\"Answer: %d\\n\", f1(f2));\n    return 0;\n}\n\nint f1(int (*f)(int))\n{\n    int n = 0;\n    while ((*f)(n)) n++;\n    return n;\n}\n\nint f2(int i)\n{\n    return i * i + i - 12;\n}"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_13.c",
    "content": "/*\n    请编写下列函数。函数 sum(g, i, j) 的调用应该返回 g(i) + ... + g(j) 。\n\n    int sum(int (*f)(int), int start, int end);\n*/\n\n#include <stdio.h>\n\nint sum(int (*f)(int), int start, int end)\n{\n    int sum = 0;\n    while (start <= end) {\n        sum += f(start++);\n    }\n    return sum;\n}\n\nint func(int i)\n{\n    return i;\n}\n\nint main(int argc, char const *argv[])\n{\n    printf(\"sum(0 to 100): %d\\n\", sum(func, 0, 100));\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_14.c",
    "content": "/*\n    设a是有100个整数的数组。请编写函数 qsort 的调用，此调用只对数组a中后50个元素进行排序。\n    （不需要编写比较函数）。\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\n#define NUM 100\n\nint compare_int(const void* p1, const void* p2)\n{\n    return *(int*)p1 - *(int*)p2;\n}\n\nint main(int argc, char const *argv[])\n{\n    int a[NUM];\n    int i;\n\n    srand(time(NULL));\n\n    for (int i = 0; i < NUM; i++) {\n        a[i] = rand() % 100;\n    }\n\n    qsort(a + NUM / 2, NUM / 2, sizeof(int), compare_int);\n\n    printf(\"prev 50 number:\");\n    for (i = 0; i < NUM / 2; i++) {\n        printf(\" %d\", a[i]);\n    }\n    printf(\"\\n\");\n\n    printf(\"last 50 number:\");\n    for (i = NUM / 2; i < NUM; i++) {\n        printf(\" %d\", a[i]);\n    }\n    printf(\"\\n\");\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_15.md",
    "content": "请修改函数 compare_parts 使零件根据编号进行降序排列。\n\n---\n\n```c\nint compare_parts(const void *p, const void *q)\n{\n    return ((struct part*)q)->number - ((struct part*)p)->number;\n}\n```"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_16.md",
    "content": "请修改程序 invent.c (16.3节)，使p（显示）命令显示零件之前调用函数 qsort 对数组 inventory 进行排序。\n\n---\n\n见 [invent.c](../ch16_结构_联合_枚举/program_parts/invent.c) 中的 print_ch17_ex16 函数。"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/ex_17.c",
    "content": "/*\n    请编写一个函数，要求在给定字符串作为实际参数时，此函数搜索下列所示的结构数组寻找匹配的\n    命令名，然后调用和匹配名称相关的函数：\n\n    struct {\n        char *cmd_name;\n        void (*cmd_pointer)(void);\n    } file_cmd[] = {\n        {\"new\", new_cmd},\n        {\"open\", open_cmd}\n    };\n*/\n\n#include <string.h>\n#include <stdio.h>\n\n#define ARR_LEN(arr) ((int)sizeof(arr)/sizeof((arr)[0]))\n\nvoid new_cmd(void)\n{\n    printf(\"new_cmd called\\n\");\n}\n\nvoid open_cmd(void)\n{\n    printf(\"open_cmd called\\n\");\n}\n\nstruct {\n    char *cmd_name;\n    void (*cmd_pointer)(void);\n} file_cmd[] = {\n    {\"new\", new_cmd},\n    {\"open\", open_cmd}\n};\n\nvoid Invoke(const char *func_name)\n{\n    int i;\n    for (i = 0; i < ARR_LEN(file_cmd); i++) {\n        if (strcmp(file_cmd[i].cmd_name, func_name) == 0) {\n            (*file_cmd[i].cmd_pointer)();\n            return;\n        }\n    }\n\n    printf(\"No such function\\n\");\n}\n\nint main(int argc, char const *argv[])\n{\n    char func_name[32];\n    printf(\"Enter func_name: \");\n    scanf(\"%s\", func_name);\n    Invoke(func_name);\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/list/list.c",
    "content": "#include \"list.h\"\n#include <stdio.h>\n#include <stdlib.h>\n\nstruct node* add_to_list(struct node *list, int n)\n{\n    struct node *new_node;\n\n    new_node = malloc(sizeof(struct node));\n    if (new_node == NULL) {\n        printf(\"Error: malloc failed in add_to_list\\n\");\n        exit(EXIT_FAILURE);\n    }\n\n    new_node->value = n;\n    new_node->next = list;\n    return new_node;\n}\n\nstruct node* search_list(struct node *list, int n)\n{\n    struct node *p;\n\n    for (p = list; p != NULL; p = p->next) {\n        if (p->value == n)\n            return p;\n    }\n    return NULL;\n}\n\nstruct node* delete_from_list(struct node *list, int n)\n{\n    struct node *cur, *prev;\n\n    for (cur = list, prev = NULL;\n        cur != NULL && cur->value != n;\n        prev = cur, cur = cur->next);\n\n    if (cur == NULL)\n        return list;            /* n was not found */\n\n    if (prev == NULL)\n        list = list->next;      /* n is in the first node */\n    else\n        prev->next = cur->next; /* n is in some other node */\n\n    free(cur);\n    return list;\n}\n\nstruct node* delete_from_list_ex_07(struct node *list, int n)\n{\n    struct node *prev;\n\n    for (prev = NULL;\n        list != NULL && list->value != n;\n        prev = list, list = list->next);\n\n    if (list == NULL)\n        return NULL;             /* n was not found */\n\n    if (prev == NULL) {\n        prev = list;\n        list = list->next;      /* n is in the first node */\n        free(prev);\n        return list;\n    }\n    else {\n        prev->next = list->next; /* n is in some other node */\n        prev = list;\n        free(prev);\n        return NULL;\n    }\n}\n\nvoid delete_from_list_ex_11(struct node **list, int n)\n{\n    struct node *cur, *prev;\n\n    for (cur = (*list), prev = NULL;\n        cur != NULL && cur->value != n;\n        prev = cur, cur = cur->next);\n\n    if (cur == NULL)\n        return;                         /* n was not found */\n\n    if (prev == NULL)\n        (*list) = (*list)->next;        /* n is in the first node */\n    else\n        prev->next = cur->next;         /* n is in some other node */\n\n    free(cur);\n}\n\nvoid destroy_list(struct node *list)\n{\n    struct node *tmp;\n    while (list) {\n        tmp = list->next;\n        free(list);\n        list = tmp;\n    }\n}"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/list/list.h",
    "content": "#ifndef LIST_H\n#define LIST_H\n\nstruct node {\n    int value;                      /* data stored in the node */\n    struct node *next;              /* pointer to the next node */\n};\n\n/*\n    创建一个新节点将其插入到首部\n    list 是旧链表首节点\n    n 存储新节点的值\n\n    返回插入的新节点\n*/\nstruct node* add_to_list(struct node *list, int n);\n\n/*\n    搜索链表\n    list 是链表首节点\n    n 是要搜索的节点的值\n    \n    返回搜索到的节点的指针，找不到返回空指针\n*/\nstruct node* search_list(struct node *list, int n);\n\n/*\n    删除节点\n    list 是链表首节点\n    n 是要删除的节点的值\n\n    返回首节点\n*/\nstruct node* delete_from_list(struct node *list, int n);\n\n/* 返回 NULL 代表没有修改首节点，否则返回首节点 */\nstruct node* delete_from_list_ex_07(struct node *list, int n);\n\nvoid delete_from_list_ex_11(struct node **list, int n);\n\n/*\n    销毁链表\n*/\nvoid destroy_list(struct node *list);\n\n#endif // LIST_H"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/list/main.c",
    "content": "#include \"list.h\"\n#include <stdio.h>\n#include <stdlib.h>\n\nvoid print_list(struct node *list)\n{\n    for (; list != NULL; list = list->next) {\n        printf(\"%d \", list->value);\n    }\n    printf(\"\\n\");\n}\n\nstruct node* read_numbers()\n{\n    struct node *first = NULL;\n    int n;\n\n    printf(\"Enter a series of integers(0 to terminate): \");\n    for (;;) {\n        scanf(\"%d\", &n);\n        if (n == 0) return first;\n\n        first = add_to_list(first, n);\n    }\n}\n\nvoid search(struct node *list)\n{\n    int n;\n    struct node *node;\n\n    printf(\"Enter number to search: \");\n    scanf(\"%d\", &n);\n\n    node = search_list(list, n);\n    if (node) {\n        printf(\"Find node: %p\\n\", node);\n    }\n    else {\n        printf(\"Can't find node\\n\");\n    }\n}\n\nvoid delete(struct node **list)\n{\n    int n;\n    struct node *node;\n\n    printf(\"Enter number to delete: \");\n    scanf(\"%d\", &n);\n\n    /* *list = delete_from_list(*list, n); */\n    \n    /*\n    node = delete_from_list_ex_07(*list, n);\n    if (node != NULL) {\n        *list = node;\n    }\n    */\n\n   delete_from_list_ex_11(list, n);\n}\n\nint main(int argc, char const *argv[])\n{\n    struct node *list = read_numbers();\n    print_list(list);\n\n    search(list);\n    delete(&list);\n\n    print_list(list);\n\n    destroy_list(list);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/list/makefile",
    "content": "CC=gcc\n\nprogram: list.o main.o\n\t$(CC) -g -Wall -o program list.o main.o\n\nlist.o: list.c list.h\nmain.o: main.c list.h\n\nclean:\n\t@rm -f *.o *.exe program"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/program_parts/invent.c",
    "content": "/*\n    invent.c\n    Maintains a parts database (array version)\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include \"readline.h\"\n\n#define NAME_LEN 25\n#define MAX_PARTS 100\n\nstruct part {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n    struct part *next;\n};\n\nstruct part_pointer_pair {\n    struct part* prev;\n    struct part* cur;\n};\n\nstruct part *inventory = NULL; /* point to first part */\n\nint num_parts = 0;      /* number of parts currently stored */\n\nstruct part* find_part(int number);\nstruct part_pointer_pair find_part2(int number);\n\nvoid insert(void);\nvoid search(void);\nvoid update(void);\nvoid erase(void);\nvoid print(void);\n\n/*\n    main: Prompts the user to enter an operator code,\n        then calls a function to perform the requested\n        action. Repeats until the user enters the\n        command 'q'. Prints an error message if th user\n        enters an illegal code.\n*/\nint main(int argc, char const *argv[])\n{\n    char code;\n\n    for (;;) {\n        printf(\"Enter operation code: \");\n        scanf(\" %c\", &code);\n        while (getchar() != '\\n'); /* skips to end of line */\n\n        switch(code) {\n            case 'i': insert(); break;\n            case 's': search(); break;\n            case 'u': update(); break;\n            case 'e': erase(); break;\n            case 'p': print(); break;\n            case 'q': printf(\"bye\\n\"); return 0;\n            default: printf(\"Illegal code\\n\"); break;\n        }\n        printf(\"\\n\");\n    }\n\n    return 0;\n}\n\n/*\n    find_part: Looks up a part number in the inventory\n            list. Returns the pointer if the part\n            number is found; otherwise, return NULL.\n*/\nstruct part* find_part(int number)\n{\n    struct part *p;\n    for (p = inventory; p != NULL && number > p->number;\n        p = p->next);\n    \n    if (p != NULL && number == p->number)\n        return p;\n\n    return NULL;\n}\n\nstruct part_pointer_pair find_part2(int number)\n{\n    struct part *p, *prev = NULL;\n    struct part_pointer_pair ppp = {NULL, NULL};\n\n    for (p = inventory; p != NULL && number > p->number;\n        prev = p, p = p->next);\n    \n    if (p != NULL && number == p->number) {\n        ppp.prev = prev;\n        ppp.cur = p;\n    }\n\n    return ppp;\n}\n\n/*\n    insert: Prompts the user for information about a new\n            part and then inserts the part into the\n            inventory list; the list remains sorted by\n            part number. Prints an error message and\n            returns prematurely if the part already exists\n            or space could not be allocated for the part.\n*/\nvoid insert(void)\n{\n    struct part *cur, *prev, *new_node;\n\n    new_node = malloc(sizeof(struct part));\n    if (new_node == NULL) {\n        printf(\"Database is full; can't add more parts.\\n\");\n        return;\n    }\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &new_node->number);\n\n    for (cur = inventory, prev = NULL;\n        cur != NULL && new_node->number > cur->number;\n        prev = cur, cur = cur->next);\n\n    if (cur != NULL && new_node->number == cur->number) {\n        printf(\"Part already exists.\\n\");\n        free(new_node);\n        return;\n    }\n\n    printf(\"Enter part name: \");\n    read_line(new_node->name, NAME_LEN);\n    printf(\"Enter quantity on hand: \");\n    scanf(\"%d\", &new_node->on_hand);\n\n    new_node->next = cur;\n    if (prev == NULL)\n        inventory = new_node;\n    else\n        prev->next = new_node;\n}\n\n/*\n    search: Prompts the user to enter a part number, then\n            looks up the part in the database. If the part\n            exists, prints the name and quantity on hand;\n            if not, prints an error message.\n*/\nvoid search(void)\n{\n    int number;\n    struct part *p;\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    p = find_part(number);\n    if (p != NULL) {\n        printf(\"Part name: %s\\n\", p->name);\n        printf(\"Quantity on hand: %d\\n\", p->on_hand);\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    update: Prompts the user to enter a part number.\n            Prints an error message if the part doesn't\n            exist; otherwise, prompts the user to enter\n            change in quantity on hand and updates the\n            database.\n*/\nvoid update(void)\n{\n    int number, change;\n    struct part *p;\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    \n    p = find_part(number);\n    if (p != NULL) {\n        printf(\"Enter change in quantity on hand: \");\n        scanf(\"%d\", &change);\n        p->on_hand += change;\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    for ex_09\n*/\nvoid erase(void)\n{\n    int number;\n    struct part *p;\n    struct part_pointer_pair ppp;\n\n    printf(\"Enter part number to delete: \");\n    scanf(\"%d\", &number);\n\n    ppp = find_part2(number);\n    if (ppp.cur != NULL) {\n        if (ppp.prev != NULL) {\n            ppp.prev->next = ppp.cur->next;\n            free(ppp.cur);\n        }\n        else {\n            /* erase header */\n            free(ppp.cur);\n            inventory = NULL;\n        }\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    print: Prints a listing of all parts in the database,\n            showing the part number, part name, and\n            quantity on hand. Parts are printed in the\n            order in which they were entered into the\n            database.\n*/\nvoid print(void)\n{\n    struct part* p;\n    printf(\"Part number    Part Name          Quantity on Hand\\n\");\n    for (p = inventory; p != NULL; p = p->next) {\n        printf(\"%7d     %-25s%11d\\n\", p->number, p->name, p->on_hand);\n    }\n}"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/program_parts/makefile",
    "content": "CC=gcc\n\nprogram: invent.o readline.o\n\t$(CC) -g -Wall -o program invent.o readline.o\n\ninvent.o: invent.c readline.h\nreadline.o: readline.c readline.h\n\nclean:\n\t@rm -f *.o *.exe programi"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/program_parts/readline.c",
    "content": "#include <ctype.h>\n#include <stdio.h>\n#include \"readline.h\"\n\nint read_line(char str[], int n)\n{\n    int ch, i = 0;\n\n    while (isspace(ch = getchar()));\n\n    while (1) {            \n        if (ch == '\\n' || ch == EOF) break;\n\n        if (i < n) {\n            str[i++] = ch;\n        }\n        ch = getchar();\n    }\n\n    str[i] = '\\0';\n\n    return i;\n}"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/program_parts/readline.h",
    "content": "#ifndef READLINE_H\n#define READLINE_H\n\n/*\n    read_line: Skips leading white-space characters, then\n                reads the remainder of the input line and\n                stores it in str. Truncates the line if its\n                length exceeds n. Returns the number of\n                characters stored.\n*/\nint read_line(char str[], int n);\n\n#endif // READLINE_H"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/remind2.c",
    "content": "/*\n * remind2.c\n *\n * Prints a one-month reminder list\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define MAX_REMIND 50\n#define MSG_LEN 60\n\nint read_line(char str[], int n);\n\nint main()\n{\n\tchar *reminders[MAX_REMIND];\n\tchar day_str[3], msg_str[MSG_LEN + 1];\n\tint day, i, j, num_remind = 0;\n\n\tfor (;;) {\n\t\tif (num_remind == MAX_REMIND) {\n\t\t\tprintf(\"-- No space left --\\n\");\n\t\t\tbreak;\n\t\t}\n\n\t\tprintf(\"Enter day and reminder: \");\n\t\tscanf(\"%2d\", &day);\n\t\tif (day == 0) break;\n\n\t\tsprintf(day_str, \"%2d\", day);\n\t\tread_line(msg_str, MSG_LEN);\n\n\t\tfor (i = 0; i < num_remind; i++)\n\t\t\tif (strcmp(day_str, reminders[i]) < 0)\n\t\t\t\tbreak;\n\t\tfor (j = num_remind; j > i; j--)\n\t\t\treminders[j] = reminders[j - 1];\n\n\t\treminders[i] = malloc(2 + strlen(msg_str) + 1);\n\t\tif (reminders[i] == NULL) {\n\t\t\tprintf(\"-- No space left --\\n\");\n\t\t\tbreak;\n\t\t}\n\n\t\tstrcpy(reminders[i], day_str);\n\t\tstrcat(reminders[i], msg_str);\n\n\t\tnum_remind++;\n\t}\n\n\tprintf(\"\\nDay Reminder\\n\");\n\tfor (i = 0; i < num_remind; i++)\n\t\tprintf(\" %s\\n\", reminders[i]);\n\n\tfor (i = 0; i < num_remind; i++) {\n\t\tfree(reminders[i]);\n\t}\n\n\treturn 0;\n}\n\nint read_line(char str[], int n)\n{\n\tchar ch;\n\tint i = 0;\n\n\twhile ((ch = getchar()) != '\\n') {\n\t\tif (i < n)\n\t\t\tstr[i++] = ch;\n\t}\n\tstr[i] = '\\0';\n\treturn i;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch17_指针的高级应用/tabulate.c",
    "content": "/*\n    tabulate.c\n    三角函数的列表值\n*/\n\n#include <stdio.h>\n#include <math.h>\n\nvoid tabulate(double (*f)(double), double first, double last, double incre);\n\n#define PRINT_TAB(func)\\\ndo {\\\n    printf(\"x\\t\" #func \"(x)\\n\");\\\n    printf(\"--------------\\n\");\\\n    tabulate((func), first, last, incre);\\\n    printf(\"\\n\");\\\n} while (0)\n\nint main(int argc, char const *argv[])\n{\n    double first, last, incre;\n\n    printf(\"Enter first: \");\n    scanf(\"%lf\", &first);\n\n    printf(\"Enter last: \");\n    scanf(\"%lf\", &last);\n\n    printf(\"Enter incre: \");\n    scanf(\"%lf\", &incre);\n\n    PRINT_TAB(cos);\n\n    PRINT_TAB(sin);\n\n    PRINT_TAB(tan);\n    \n    return 0;\n}\n\nvoid tabulate(double (*f)(double), double first, double last, double incre)\n{\n    double x;\n    int i, intervals;\n\n    intervals = ceil((last - first) / incre);\n\n    for (i = 0; i <= intervals; i++) {\n        x = first + incre * i;\n        printf(\"%.2f\\t%.3f\\n\", x, f(x));\n    }\n}"
  },
  {
    "path": "codes/CProgramming/ch18_声明/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch18_声明/ex_01.md",
    "content": "请指出下列声明的存储类型、类型限定符、类型说明符、声明符和初始化式。\n\n(a) `static char **lookup(int level);`\n\n(b) `volatile unsigned long io_flags;`\n\n(c) `extern char *file name[MAX_FILES], path[];`\n\n(d) `static const char token_buf[] = \"\"`;\n\n---\n\n(a) 存储类型是 static ，没有类型限定符，类型说明符是`char**`，声明符是`lookup()`，没有初始化式\n\n(b) 存储类型需要看变量定义的地方，如果在块内，则是 auto ，否则是 static 。类型限定符是volatile 。类型说明符是 unsigned long ，声明符是 io_flags ，没有初始化式。\n\n(c) 存储类型是 extern ，没有类型限定符，类型说明符是 `char*`，声明符是`name[]`和`path[]`，没有初始化式。\n\n(d) 存储类型是 static , 类型限定符是 const , 类型说明符是 `char` ，声明符是 `token_buf[]` ，初始化式是 `= \"\"`;"
  },
  {
    "path": "codes/CProgramming/ch18_声明/ex_02.md",
    "content": "用 auto, extern, register 和 static 来回答下列问题。\n\n(a) 哪种存储类型可以用于说明能被几个文件共享的变量或函数?\n\n(b) 假设变量x可以被一个文件中的几个函数共享，但是对其他文件中的函数却是隐藏的。那么变量x应该被声明为哪种存储类型呢？\n\n(c) 哪些存储类型会影响变量的存储期限？\n\n---\n\n(a) extern\n\n(b) static\n\n(c) auto 和 register 是自动存储期限，extern 和 static 是静态存储期限。"
  },
  {
    "path": "codes/CProgramming/ch18_声明/ex_03.md",
    "content": "请列出下列文件中每个变量和形式参数的存储期限、作用域和链接：\n\n```c\nextern float a;\nvoid f(register double b)\n{\n    static int c;\n    auto char d;\n}\n```\n\n---\n\n- a, 静态存储期限、文件作用域、外部链接\n\n- f，静态存储期限、文件作用域、外部链接\n\n- b, 自动存储期限、块作用域、内部链接\n\n- c, 静态存储期限、块作用域、内部链接\n\n- d, 自动存储期限、块作用域、内部链接"
  },
  {
    "path": "codes/CProgramming/ch18_声明/ex_04.c",
    "content": "/*\n    假设f是下列函数。如果在此之前f从没有被调用过，那么f(10)的值是多少呢？如果在此之前f已经\n    被调用过5次了，那么f(10)的值又是多少呢？\n\n    int f(int i)\n    {\n        static int j = 0;\n        return i * j++;\n    }\n*/\n\n/*\n    如果从未被调用过，那么 f(10) 的结果是：0\n    如果在此之前f已经被调用过5次，那么 f(10) 的结果是：50\n*/\n\n#include <stdio.h>\n\nint f(int i)\n{\n    static int j = 0;\n    return i * j++;\n}\n\nint main(int argc, char const *argv[])\n{\n    int i;\n    for (i = 0; i < 5; i++)\n        f(10);\n\n    printf(\"%d\\n\", f(10));\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch18_声明/ex_05.md",
    "content": "假设声明x为 const 型对象，那么下列关于x的语句哪条是假的呢？\n\n(a) 如果x的类型是 int 型，那么可以用它来声明数组的长度。\n\n(b) 编译器将查到没有对x进行赋值。\n\n(c) x和变量遵循同样的作用域规则。\n\n(d) x可以是任意类型。\n\n---\n\n假的有： (a) (b) (d)\n\nPS: c没有看明白。"
  },
  {
    "path": "codes/CProgramming/ch18_声明/ex_06.c",
    "content": "/*\n    请编写下列每个声明指定的x类型的完整描述。\n\n    (a) char (*x[10])(int)\n    (b) int (*x(int))[5];\n    (c) float *(*x(void))(int)[10];\n    (d) void (*x(int, void (*y)(int)))(int);\n*/\n\n/*\n    (a) 一个名为x的具有10个元素的数组，元素类型是一个函数指针，指向的函数类型的 char (*f)(int)\n    (b) x是一个函数，其参数是一个 int ,返回一个指向5个 int 元素的数组的指针\n    (c) 有问题，编译提示错误，函数返回了一个数组（C语言中函数没有办法返回数组）\n    (d) x是一个函数，参数列表是(int, void (*y)(int)), 其中参数y是一个函数指针，指向的函数有一个 int 型参数，\n        无返回类型；x的返回类型也是一个函数指针，指向的函数有一个 int 型参数，无返回类型\n*/\n\nchar test_a(int i) { return 0; }\n\nfloat (*test_c(int i))[10] {\n    static float a[10];\n    return &a;\n}\n\nvoid test_d(int i) {}\n\nchar (*x_a[10])(int);\n\nint (*x_b(int i))[5] {\n    static int a[5];\n    return &a;\n}\n\n/* float *(*x_c(void))(int)[10]; */\n\nvoid (*x_d(int i, void (*y)(int)))(int)\n{\n    return test_d;\n}\n\nint main(int argc, char const *argv[])\n{\n    x_a[0] = test_a;\n\n    x_d(0, test_d);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch18_声明/ex_07.c",
    "content": "/*\n    请利用一系列的类型定义来简化练习6中的每个声明。\n*/\n\n/*\n    for a\n*/\ntypedef char (*FuncA)(int);\ntypedef FuncA ArrA[10];\n\n/*\n    for b\n*/\ntypedef int Arr5Int[5];\ntypedef Arr5Int* (*FuncB)(int);\n\n/*\n    for d\n*/\ntypedef void (*FuncDRet)(int);\ntypedef FuncDRet (*FuncD)(int, FuncDRet);\n\nchar test_a(int i) { return 0; }\n\nvoid test_d(int i) {}\n\nchar (*x_a[10])(int);\n\nint (*x_b(int i))[5] {\n    static int a[5];\n    return &a;\n}\n\n/* float *(*x_c(void))(int)[10]; */\n\nvoid (*x_d(int i, void (*y)(int)))(int)\n{\n    return test_d;\n}\n\nint main(int argc, char const *argv[])\n{\n    ArrA arr_a;\n    arr_a[0] = test_a;\n    arr_a[0](0);\n\n    FuncB func_b;\n    func_b = x_b;\n    func_b(0);\n\n    FuncD func_d;\n    func_d = x_d;\n    func_d(0, test_d);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch18_声明/ex_08.c",
    "content": "/*\n    请为下列变量和函数编写声明：\n    (a) p是指向函数的指针，并且此函数带有字符型指针作为实际参数，函数返回的也是字符型指针。\n    (b) f是带有两个实际参数的函数：一个参数是指向结构的指针p，且此结构标记为t；另一个参数是长\n        整数n。f返回指向函数的指针，且指向的函数没有实际参数也无返回值。\n    (c) a是含有4个元素的数组，且每个元素都是指向函数的指针，而这些函数都是没有实际参数且无返回\n        值的。a的元素初始指向的函数名分别是 insert, search, update 和 print 。\n    (d) b是含有10个元素的数组，且每个元素都是指向函数的指针，而这些函数都有两个int型实际参数\n        且返回标记为t的结构。\n*/\n\n/* for a */\nchar* (*p)(char*);\n\n/* for b */\ntypedef struct t_ { int v; } t;\ntypedef void (*bret_func)();\nbret_func f(t *p, long n);\n\n/* for c */\nvoid insert() {}\nvoid search() {}\nvoid update() {}\nvoid print() {}\ntypedef void (*a_func)();\na_func a[4] = {insert, search, update, print};\n\n/* for d */\ntypedef t* (*d_func)(int, int);\nd_func d[10];\n\nint main(int argc, char const *argv[])\n{\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch18_声明/ex_09.c",
    "content": "/*\n    在18.4节看到了下列非发的声明：\n\n    int f(int)[];           // Functions can't return arrays\n    int g(int)(int);        // Functions can't return functions\n    int a[10](int);         // Array elements can't be functions\n\n    然而，可以通过使用指针获得相似的效果：函数可以返回指向数组第一个元素的指针，也可以返回\n    指向函数的指针，而且数组的元素可以是指向函数的指针。请根据这些描述重新修订上述每个声明。 \n*/\n\nint (*f(int i))[]\n{\n    static int arr[3];\n    return &arr;\n}\n\nint test_g(int i) { return 0; }\nint (*g(int i))(int)\n{\n    return test_g;\n}\n\nint (*a[10])(int);\n\nint main(int argc, char const *argv[])\n{\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch18_声明/ex_10.c",
    "content": "/*\n    下列哪些声明是合法的？（假设 PI 是表示3.14159的宏。）\n    (a) char c = 65;\n    (b) static int i = 5, j = i * i;\n    (c) float f = 2 * PI;\n    (d) double angles[] = {0, PI / 2, PI, 3 * PI / 2};\n*/\n\n/*\n    合法的有：a c d\n*/\n\n#define PI 3.14159\n\nchar c = 65;\n/*static int i = 5, j = i * i;*/\nfloat f = 2 * PI;\ndouble angles[] = {0, PI / 2, PI, 3 * PI / 2};\n\nint main(int argc, char const *argv[])\n{\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch18_声明/ex_11.c",
    "content": "/*\n    下列哪些类型的变量不能进行初始化？\n\n    (a) 数组变量\n    (b) 枚举变量\n    (c) 结构变量\n    (d) 联合变量\n    (e) 不是上述类型的变量\n*/\n\n/*\n    e\n*/\n\nenum B { FOO, GOO };\n\nstruct S { int v; };\n\nunion U { int i; char v[4]; };\n\nint main(int argc, char const *argv[])\n{\n    int arr[] = {0};\n    (void)arr;\n\n    enum B b = FOO;\n    (void)b;\n\n    struct S s = {0};\n    (void)s;\n\n    union U u = {0};\n    (void)u;\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch18_声明/ex_12.md",
    "content": "变量的哪种性质用来确定是否具有默认的初始值？\n\n(a) 存储期限\n\n(b) 作用域\n\n(c) 链接\n\n(d) 类型\n\n---\n\na"
  },
  {
    "path": "codes/CProgramming/ch18_声明/example_register.c",
    "content": "/*\n    register 存储类型\n    声明变量具有 register 存储类型就要求编译器把变量存储在寄存器中，而不是内存中。\n    但，这是一种要求而不是命令，编译器可以自由地把 register 型变量存储在内存中。\n\n    register 存储类型只对声明在块内的变量有效， register 型变量和自动变量的存储期限、\n    作用域和链接一样，但是它没有地址，不能对其做取地址的运算。\n\n    register 存储类型最好用于频繁进行访问和更新的变量，例如，作为 for 语句的循环因子。\n\n    但今天的编译器已经可以自动决定变量保留在寄存器中是否可以获得最大的好处。\n\n    PS：一般不必使用 register\n*/\n\n#include <stdio.h>\n\nint sum_array(int a[], int n)\n{\n    register int i;\n    int sum = 0;\n    for (i = 0; i < n; i++) {\n        sum += a[i];\n    }\n    return sum;\n}\n\nint main(int argc, char const *argv[])\n{\n    int a[4] = {1, 2, 3, 4};\n\n    printf(\"sum of a: %d\\n\", sum_array(a, 4));\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\t# for cpp files\n\tarr_cpp_files=(ex_07.cpp)\n\tlocal i=0\n\twhile [ \"$i\" != ${#arr_cpp_files[@]} ]\n\tdo\n\t\tlocal cpp_file=${arr_cpp_files[$i]}\n\t\tlocal exe_file=${cpp_file%%.cpp*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\telse\n\t\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\t\tfi\n\t\t\n\t\ti=$((i + 1))\n\tdone\n\t\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/ex_01.c",
    "content": "/*\n    队列类似于栈，两者的差异是队列的数据从一端添加，而从另一端按 FIFO （先进先出）的方式删除。\n    对于队列的操作可以包括：\n    - 向队列的末端加入一个数据。\n    - 从队列的开始删除一个数据。\n    - 返回队列的第一个数据（不改变队列）。\n    - 返回队列的末尾数据（不改变队列）。\n    - 检查队列是否为空。\n\n    以头文件 queue 的形式给队列定义一个接口\n*/\n\n/*\n    见 ./queue/queue.h\n*/\n\n#include \"./queue/queue.h\"\n\nint main(int argc, char const *argv[])\n{\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/ex_02.md",
    "content": "修改文件 `stack2.c`，以使用 PUBLIC 宏和 PRIVATE 宏。\n\n---\n\n见 [程序](../ch15_编写大规模程序/dir_ex_06/stack.c)"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/ex_03.md",
    "content": "(a) 按照练习1中的描述用数组实现一个队列模块。\n\n(b) 按照练习2中的描述用链表实现一个队列模块。\n\n---\n\n见[程序](./queue)"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/ex_04.md",
    "content": "(a) 编写一个基于数组的 stack 类型的实现。\n\n(b) 使用链表替换数组，重写上面的 stack 类型。（给出 stack.h 和 stack.c）\n\n---\n\n见[程序](./stack)"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/ex_05.md",
    "content": "(a) 将练习1中的 queue.h 头文件加以修改，使之定义一个 Queue 类型。同时修改 queue.h 头文件中的函数，用 Queue （或`Queue*`）作为形式参数。\n\n(b) 使用数组实现 Queue 类型。\n\n(c) 使用链表实现 Queue 类型。\n\n---\n\n见[程序](./queue2)"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/ex_06.md",
    "content": "Fraction 类需要一个析构函数吗？验证你的答案。\n\n---\n\n不需要，因为它没有动态分配的成员。验证略。"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/ex_07.cpp",
    "content": "/*\n    给 Fraction 类添加重载运算符+, -, 和/。写出这些运算符的定义以及 print 函数和 reduce 函数的\n    实现。\n*/\n\n#include <iostream>\n#include <string>\n\nclass Fraction {\n    friend Fraction operator+(const Fraction&, const Fraction&);\n    friend Fraction operator-(const Fraction&, const Fraction&);\n    friend Fraction operator/(const Fraction&, const Fraction&);\npublic:\n    Fraction() : numberator(0), denominator(0) {}\n    Fraction(int n, int d) : numberator(n), denominator(d) {}\n\n    const char* get()\n    {\n        dec = std::to_string(numberator) + \"/\" + std::to_string(denominator);\n        return dec.c_str();\n    }\n\n    void print()\n    {\n        printf(\"%d/%d\\n\", numberator, denominator);\n    }\n    void reduce()\n    {\n        int m, n, tmp;\n        m = numberator;\n        n = denominator;\n\n        while (n != 0) {\n            tmp = m;\n            m = n;\n            n = tmp % n;\n        }\n\n        numberator /= m;\n        denominator /= m;\n    }\n\nprivate:\n\tint numberator;\n\tint denominator;\n    std::string dec;\n};\n\nFraction operator+(const Fraction& lhs, const Fraction& rhs)\n{\n    Fraction ret;\n    ret.numberator = lhs.numberator * rhs.denominator + rhs.numberator * lhs.denominator;\n    ret.denominator = lhs.denominator * rhs.denominator;\n    ret.reduce();\n    return ret;\n}\nFraction operator-(const Fraction& lhs, const Fraction& rhs)\n{\n    Fraction ret;\n    ret.numberator = lhs.numberator * rhs.denominator - rhs.numberator * lhs.denominator;\n    ret.denominator = lhs.denominator * rhs.denominator;\n    ret.reduce();\n    return ret;\n}\nFraction operator/(const Fraction& lhs, const Fraction& rhs)\n{\n    Fraction ret;\n    ret.numberator = lhs.numberator * rhs.denominator;\n    ret.denominator = lhs.denominator * rhs.numberator;\n    ret.reduce();\n    return ret;\n}\n\nint main(int argc, char const *argv[])\n{\n    Fraction f1(6, 8);\n    f1.print();\n    f1.reduce();\n    printf(\"after reduce: \");\n    f1.print();\n\n    Fraction f2(2, 5);\n    printf(\"%s + %s = %s\\n\", f1.get(), f2.get(), (f1 + f2).get());\n    printf(\"%s - %s = %s\\n\", f1.get(), f2.get(), (f1 - f2).get());\n    printf(\"%s / %s = %s\\n\", f1.get(), f2.get(), (f1 / f2).get());\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/ex_08.md",
    "content": "与C语言的 printf 和 scanf 相比，C++语言的 `<<` 和 `>>` 运算符有什么优点？\n\n---\n\n比较安全，因重载可输出类型更多样。"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/ex_09.md",
    "content": "将练习5的 Queue 类型改成 Queue 类。\n\n---\n\n见 [程序](./queue3)"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/ex_10.md",
    "content": "将练习9的 Queue 类改成 Queue 模板。\n\n---\n\n见 [程序](./queue4)"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue/main.c",
    "content": "#include <stdio.h>\n#include \"queue.h\"\n\nint main(int argc, char const *argv[])\n{\n    int i;\n\n    init_queue();\n\n    for (i = 0; i < 10; i++) {\n        queue_push(i);\n    }\n\n    printf(\"first of queue: %d\\n\", queue_first());\n    printf(\"last of queue: %d\\n\", queue_last());\n\n    queue_pop();\n    printf(\"after pop, first of queue: %d, last of queue: %d\\n\", queue_first(), queue_last());\n\n    printf(\"before pop all, queue is empty? %s\\n\", queue_is_empty() ? \"yes\" : \"no\");\n    while (!queue_is_empty()) {\n        queue_pop();\n    }\n    printf(\"after pop all, queue is empty? %s\\n\", queue_is_empty() ? \"yes\" : \"no\");\n\n    destroy_queue();\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue/makefile",
    "content": "QUEUE_OBJ=queue_impl2.o\n\nCC=gcc\n\nprogram: main.o $(QUEUE_OBJ)\n\t$(CC) -g -Wall -o program main.o $(QUEUE_OBJ)\n\nmain.o: main.c queue.h\nqueue_impl1.o: queue_impl1.c queue.h\nqueue_impl2.o: queue_impl2.c queue.h\n\nclean:\n\t@rm -f *.o *.exe program"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue/queue.h",
    "content": "#ifndef QUEUE_H\n#define QUEUE_H\n\ntypedef int ValueType;\ntypedef enum { TRUE, FALSE } Bool;\n\n/*\n    初始化队列\n*/\nvoid init_queue();\n\n/*\n    销毁队列\n*/\nvoid destroy_queue();\n\n/*\n    向队列的末端加入一个数据。\n*/\nvoid queue_push(ValueType v);\n\n/*\n    从队列的开始删除一个数据。\n*/\nvoid queue_pop();\n\n/*\n    返回队列的第一个数据（不改变队列）。\n*/\nValueType queue_first();\n\n/*\n    返回队列的末尾数据（不改变队列）。\n*/\nValueType queue_last();\n\n/*\n    检查队列是否为空。\n*/\nBool queue_is_empty();\n\n#endif // QUEUE_H"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue/queue_impl1.c",
    "content": "#include \"queue.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n/*\n    用数组实现队列\n*/\n\n#define INIT_COUNT 2\n\nstatic int value_max_count = INIT_COUNT;\nstatic int value_count = 0;\nstatic ValueType *queue;\n\n/*\n    初始化队列\n*/\nvoid init_queue()\n{\n    printf(\"init_queue, queue implement by array\\n\");\n    queue = malloc(sizeof(ValueType) * value_max_count);\n}\n\n/*\n    销毁队列\n*/\nvoid destroy_queue()\n{\n    free(queue);\n}\n\n/*\n    向队列的末端加入一个数据。\n*/\nvoid queue_push(ValueType v)\n{\n    ValueType *queue_tmp;\n\n    if (value_count >= value_max_count) {\n        queue_tmp = realloc(queue, sizeof(ValueType) * value_max_count * 2);\n        if (queue_tmp == NULL) {\n            printf(\"queue_push failure: No space left!\");\n            return;\n        }\n        queue = queue_tmp;\n        value_max_count *= 2;\n    }\n\n    queue[value_count++] = v;\n}\n\n/*\n    从队列的开始删除一个数据。\n*/\nvoid queue_pop()\n{\n    if (queue_is_empty()) {\n        printf(\"queue_pop failure: queue is empty!\\n\");\n        return;\n    }\n\n    value_count--;\n    memcpy(queue, queue + 1, sizeof(ValueType) * value_count);\n}\n\n/*\n    返回队列的第一个数据（不改变队列）。\n*/\nValueType queue_first()\n{\n    static ValueType v_for_ret;\n\n    if (queue_is_empty()) {\n        printf(\"queue_first failure: queue is empty!\\n\");\n        return v_for_ret;\n    }\n\n    return queue[0];\n}\n\n/*\n    返回队列的末尾数据（不改变队列）。\n*/\nValueType queue_last()\n{\nstatic ValueType v_for_ret;\n\n    if (queue_is_empty()) {\n        printf(\"queue_last failure: queue is empty!\\n\");\n        return v_for_ret;\n    }\n\n    return queue[value_count - 1];\n}\n\n/*\n    检查队列是否为空。\n*/\nBool queue_is_empty()\n{\n    return value_count == 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue/queue_impl2.c",
    "content": "#include \"queue.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define PUBLIC /* empty */\n#define PRIVATE static\n\nstruct node {\n    ValueType v;\n    struct node *next;\n};\n\nstatic struct node *first;\nstatic struct node *last;\n\n/*\n    初始化队列\n*/\nPUBLIC void init_queue()\n{\n    printf(\"init_queue, queue implement by list\\n\");\n    return;\n}\n\n/*\n    销毁队列\n*/\nPUBLIC void destroy_queue()\n{\n    struct node *tmp;\n    while (first != last) {\n        tmp = first->next;\n        free(first);\n        first = tmp;\n    }\n\n    if (last)\n        free(last);\n\n    return;\n}\n\n/*\n    向队列的末端加入一个数据。\n*/\nPUBLIC void queue_push(ValueType v)\n{\n    struct node *tmp = malloc(sizeof(ValueType));\n    if (tmp == NULL) {\n        printf(\"queue_push failure: No space left!\\n\");\n        return;\n    }\n    tmp->v = v;\n    tmp->next = NULL;\n\n    if (last == NULL) {\n        first = last = tmp;\n    }\n    else {\n        last->next = tmp;\n        last = tmp;\n    }\n}\n\n/*\n    从队列的开始删除一个数据。\n*/\nPUBLIC void queue_pop()\n{\n    struct node *tmp;\n    if (queue_is_empty()) {\n        printf(\"queue_pop failure: queue is empty!\\n\");\n        return;\n    }\n\n    tmp = first->next;\n    free(first);\n    first = tmp;\n}\n\n/*\n    返回队列的第一个数据（不改变队列）。\n*/\nPUBLIC ValueType queue_first()\n{\n    static ValueType v_to_ret;\n    if (queue_is_empty()) {\n        printf(\"queue_first failure: queue is empty!\\n\");\n        return v_to_ret;\n    }\n\n    return first->v;\n}\n\n/*\n    返回队列的末尾数据（不改变队列）。\n*/\nPUBLIC ValueType queue_last()\n{\n    static ValueType v_to_ret;\n    if (queue_is_empty()) {\n        printf(\"queue_last failure: queue is empty!\\n\");\n        return v_to_ret;\n    }\n\n    return last->v;\n}\n\n/*\n    检查队列是否为空。\n*/\nPUBLIC Bool queue_is_empty()\n{\n    return first == NULL;\n}"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue2/main.c",
    "content": "#include <stdio.h>\n#include \"queue.h\"\n\nint main(int argc, char const *argv[])\n{\n    int i;\n\n    Queue *queue = init_queue();\n\n    for (i = 0; i < 10; i++) {\n        queue_push(queue, i);\n    }\n\n    printf(\"first of queue: %d\\n\", queue_first(queue));\n    printf(\"last of queue: %d\\n\", queue_last(queue));\n\n    queue_pop(queue);\n    printf(\"after pop, first of queue: %d, last of queue: %d\\n\", queue_first(queue), queue_last(queue));\n\n    printf(\"before pop all, queue is empty? %s\\n\", queue_is_empty(queue) ? \"yes\" : \"no\");\n    while (!queue_is_empty(queue)) {\n        queue_pop(queue);\n    }\n    printf(\"after pop all, queue is empty? %s\\n\", queue_is_empty(queue) ? \"yes\" : \"no\");\n\n    destroy_queue(queue);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue2/makefile",
    "content": "QUEUE_OBJ=queue.o\n\nCC=gcc\n\nprogram: main.o $(QUEUE_OBJ)\n\t$(CC) -g -Wall -o program main.o $(QUEUE_OBJ)\n\nmain.o: main.c queue.h\nqueue.o: queue.c queue.h\nqueue_impl2.o: queue_impl2.c queue.h\n\nclean:\n\t@rm -f *.o *.exe program"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue2/queue.c",
    "content": "#include \"queue.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#ifdef IMPL_BY_ARR\n/*\n    用数组实现队列\n*/\n\n#define INIT_COUNT 2\n\n/*\n    初始化队列\n*/\nQueue* init_queue()\n{\n    printf(\"init_queue, queue implement by array\\n\");\n\n    Queue *queue = malloc(sizeof(Queue));\n    if (queue == NULL) {\n        printf(\"init_queue failure: no space left!\\n\");\n        exit(1);\n    }\n\n    queue->queue = malloc(sizeof(ValueType) * INIT_COUNT);\n    queue->value_max_count = INIT_COUNT;\n    queue->value_count = 0;\n}\n\n/*\n    销毁队列\n*/\nvoid destroy_queue(Queue *queue)\n{\n    free(queue->queue);\n    free(queue);\n}\n\n/*\n    向队列的末端加入一个数据。\n*/\nvoid queue_push(Queue *queue, ValueType v)\n{\n    ValueType *queue_tmp;\n\n    if (queue->value_count >= queue->value_max_count) {\n        queue_tmp = realloc(queue->queue, sizeof(ValueType) * queue->value_max_count * 2);\n        if (queue_tmp == NULL) {\n            printf(\"queue_push failure: No space left!\");\n            return;\n        }\n        queue->queue = queue_tmp;\n        queue->value_max_count *= 2;\n    }\n\n    queue->queue[queue->value_count++] = v;\n}\n\n/*\n    从队列的开始删除一个数据。\n*/\nvoid queue_pop(Queue *queue)\n{\n    if (queue_is_empty(queue)) {\n        printf(\"queue_pop failure: queue is empty!\\n\");\n        return;\n    }\n\n    queue->value_count--;\n    memcpy(queue->queue, queue->queue + 1, sizeof(ValueType) * queue->value_count);\n}\n\n/*\n    返回队列的第一个数据（不改变队列）。\n*/\nValueType queue_first(Queue *queue)\n{\n    static ValueType v_for_ret;\n\n    if (queue_is_empty(queue)) {\n        printf(\"queue_first failure: queue is empty!\\n\");\n        return v_for_ret;\n    }\n\n    return queue->queue[0];\n}\n\n/*\n    返回队列的末尾数据（不改变队列）。\n*/\nValueType queue_last(Queue *queue)\n{\n    static ValueType v_for_ret;\n\n    if (queue_is_empty(queue)) {\n        printf(\"queue_last failure: queue is empty!\\n\");\n        return v_for_ret;\n    }\n\n    return queue->queue[queue->value_count - 1];\n}\n\n/*\n    检查队列是否为空。\n*/\nBool queue_is_empty(Queue *queue)\n{\n    return queue->value_count == 0;\n}\n\n#endif /* IMPL_BY_ARR */\n\n#ifdef IMPL_BY_LIST\n\n/*\n    初始化队列\n*/\nQueue* init_queue()\n{\n    printf(\"init_queue, queue implement by list\\n\");\n\n    Queue *queue = malloc(sizeof(Queue));\n    queue->first = NULL;\n    queue->last = NULL;\n\n    return queue;\n}\n\n/*\n    销毁队列\n*/\nvoid destroy_queue(Queue *queue)\n{\n    struct node *tmp;\n    while (queue->first != queue->last) {\n        tmp = queue->first->next;\n        free(queue->first);\n        queue->first = tmp;\n    }\n\n    if (queue->last)\n        free(queue->last);\n\n    return;\n}\n\n/*\n    向队列的末端加入一个数据。\n*/\nvoid queue_push(Queue *queue, ValueType v)\n{\n    struct node *tmp = malloc(sizeof(ValueType));\n    if (tmp == NULL) {\n        printf(\"queue_push failure: No space left!\\n\");\n        return;\n    }\n    tmp->v = v;\n    tmp->next = NULL;\n\n    if (queue->last == NULL) {\n        queue->first = queue->last = tmp;\n    }\n    else {\n        queue->last->next = tmp;\n        queue->last = tmp;\n    }\n}\n\n/*\n    从队列的开始删除一个数据。\n*/\nvoid queue_pop(Queue *queue)\n{\n    struct node *tmp;\n    if (queue_is_empty(queue)) {\n        printf(\"queue_pop failure: queue is empty!\\n\");\n        return;\n    }\n\n    tmp = queue->first->next;\n    free(queue->first);\n    queue->first = tmp;\n}\n\n/*\n    返回队列的第一个数据（不改变队列）。\n*/\nValueType queue_first(Queue *queue)\n{\n    static ValueType v_to_ret;\n    if (queue_is_empty(queue)) {\n        printf(\"queue_first failure: queue is empty!\\n\");\n        return v_to_ret;\n    }\n\n    return queue->first->v;\n}\n\n/*\n    返回队列的末尾数据（不改变队列）。\n*/\nValueType queue_last(Queue *queue)\n{\n    static ValueType v_to_ret;\n    if (queue_is_empty(queue)) {\n        printf(\"queue_last failure: queue is empty!\\n\");\n        return v_to_ret;\n    }\n\n    return queue->last->v;\n}\n\n/*\n    检查队列是否为空。\n*/\nBool queue_is_empty(Queue *queue)\n{\n    return queue->first == NULL;\n}\n\n#endif /* IMPL_BY_LIST */"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue2/queue.h",
    "content": "#ifndef QUEUE_H\n#define QUEUE_H\n\n/* #define IMPL_BY_ARR */\n#define IMPL_BY_LIST\n\ntypedef int ValueType;\ntypedef enum { TRUE, FALSE } Bool;\n\n/* 数组定义的 Queue */\n#ifdef IMPL_BY_ARR\nstruct queue {\n    int value_max_count;\n    int value_count;\n    ValueType *queue;\n};\n#endif\n\n/* 链表定义的 Queue */\n#ifdef IMPL_BY_LIST\nstruct node {\n    ValueType v;\n    struct node *next;\n};\n\nstruct queue {\n    struct node *first;\n    struct node *last;\n};\n#endif\n\ntypedef struct queue Queue;\n\n/*\n    初始化队列\n*/\nQueue* init_queue();\n\n/*\n    销毁队列\n*/\nvoid destroy_queue(Queue *queue);\n\n/*\n    向队列的末端加入一个数据。\n*/\nvoid queue_push(Queue *queue, ValueType v);\n\n/*\n    从队列的开始删除一个数据。\n*/\nvoid queue_pop(Queue *queue);\n\n/*\n    返回队列的第一个数据（不改变队列）。\n*/\nValueType queue_first(Queue *queue);\n\n/*\n    返回队列的末尾数据（不改变队列）。\n*/\nValueType queue_last(Queue *queue);\n\n/*\n    检查队列是否为空。\n*/\nBool queue_is_empty(Queue *queue);\n\n#endif // QUEUE_H"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue3/main.cpp",
    "content": "#include <stdio.h>\n#include \"queue.h\"\n\nint main(int argc, char const *argv[])\n{\n    int i;\n\n    Queue queue;\n\n    for (i = 0; i < 10; i++) {\n        queue.push(i);\n    }\n\n    printf(\"first of queue: %d\\n\", queue.first());\n    printf(\"last of queue: %d\\n\", queue.last());\n\n    queue.pop();\n    printf(\"after pop, first of queue: %d, last of queue: %d\\n\", queue.first(), queue.last());\n\n    printf(\"before pop all, queue is empty? %s\\n\", queue.is_empty() ? \"yes\" : \"no\");\n    while (!queue.is_empty()) {\n        queue.pop();\n    }\n    printf(\"after pop all, queue is empty? %s\\n\", queue.is_empty() ? \"yes\" : \"no\");\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue3/makefile",
    "content": "QUEUE_OBJ=queue.o\n\nCC=g++ -std=c++11\n\nprogram: main.o $(QUEUE_OBJ)\n\t$(CC) -g -Wall -o program main.o $(QUEUE_OBJ)\n\nmain.o: main.cpp queue.h\nqueue.o: queue.cpp queue.h\n\nclean:\n\t@rm -f *.o *.exe program"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue3/queue.cpp",
    "content": "#include \"queue.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\n#define INIT_COUNT 2\n\nQueue::Queue() : value_max_count(0), value_count(0), queue(nullptr)\n{\n    queue = (ValueType*)malloc(sizeof(ValueType) * INIT_COUNT);\n    if (queue == NULL) {\n        printf(\"init_queue failure: no space left!\\n\");\n        exit(1);\n    }\n    value_max_count = INIT_COUNT;\n    value_count = 0;\n}\n\nQueue::~Queue()\n{\n    free(queue);\n}\n\n/*\n    向队列的末端加入一个数据。\n*/\nvoid Queue::push(ValueType v)\n{\n    ValueType *tmp;\n\n    if (value_count >= value_max_count) {\n        tmp = (ValueType*)realloc(queue, sizeof(ValueType) * value_max_count * 2);\n        if (tmp == NULL) {\n            printf(\"push failure: No space left!\");\n            return;\n        }\n        queue = tmp;\n        value_max_count *= 2;\n    }\n\n    queue[value_count++] = v;\n}\n\n/*\n    从队列的开始删除一个数据。\n*/\nvoid Queue::pop()\n{\n    if (is_empty()) {\n        printf(\"pop failure: queue is empty!\\n\");\n        return;\n    }\n\n    value_count--;\n    memcpy(queue, queue + 1, sizeof(ValueType) * value_count);\n}\n\n/*\n    返回队列的第一个数据（不改变队列）。\n*/\nValueType Queue::first()\n{\n    static ValueType v_for_ret;\n\n    if (is_empty()) {\n        printf(\"first failure: queue is empty!\\n\");\n        return v_for_ret;\n    }\n\n    return queue[0];\n}\n\n/*\n    返回队列的末尾数据（不改变队列）。\n*/\nValueType Queue::last()\n{\n    static ValueType v_for_ret;\n\n    if (is_empty()) {\n        printf(\"last failure: queue is empty!\\n\");\n        return v_for_ret;\n    }\n\n    return queue[value_count - 1];\n}\n\n/*\n    检查队列是否为空。\n*/\nbool Queue::is_empty()\n{\n    return value_count == 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue3/queue.h",
    "content": "#ifndef QUEUE_H\n#define QUEUE_H\n\n#define IMPL_BY_ARR\n\ntypedef int ValueType;\n\n/* 数组定义的 Queue */\nclass Queue {\npublic:\n    Queue();\n    ~Queue();\n    Queue(const Queue&) = delete;\n    Queue& operator=(const Queue&) = delete;\n\n    /*\n    向队列的末端加入一个数据。\n    */\n    void push(ValueType v);\n\n    /*\n        从队列的开始删除一个数据。\n    */\n    void pop();\n\n    /*\n        返回队列的第一个数据（不改变队列）。\n    */\n    ValueType first();\n\n    /*\n        返回队列的末尾数据（不改变队列）。\n    */\n    ValueType last();\n\n    /*\n        检查队列是否为空。\n    */\n    bool is_empty();\n\nprivate:\n    int value_max_count;\n    int value_count;\n    ValueType *queue;\n};\n\n#endif // QUEUE_H"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue4/main.cpp",
    "content": "#include <stdio.h>\n#include <string>\n#include <iostream>\n#include \"queue.h\"\n\nint main(int argc, char const *argv[])\n{\n\t{\n\t\tQueue<int> queue;\n\n\t\tqueue.push(1);\n\t\tqueue.push(2);\n\t\tqueue.push(3);\n\t\tqueue.push(4);\n\n\t\tprintf(\"first of queue: %d\\n\", queue.first());\n\t\tprintf(\"last of queue: %d\\n\", queue.last());\n\n\t\tqueue.pop();\n\t\tprintf(\"after pop, first of queue: %d, last of queue: %d\\n\", queue.first(), queue.last());\n\n\t\tprintf(\"before pop all, queue is empty? %s\\n\", queue.is_empty() ? \"yes\" : \"no\");\n\t\twhile (!queue.is_empty()) {\n\t\t\tqueue.pop();\n\t\t}\n\t\tprintf(\"after pop all, queue is empty? %s\\n\", queue.is_empty() ? \"yes\" : \"no\");\n\t}\n\n\t{\n\t\tQueue<std::string> queue;\n\n\t\tqueue.push(\"Hello\");\n\t\tqueue.push(\"World\");\n\t\tqueue.push(\"Boo\");\n\t\tqueue.push(\"Goo\");\n\n\t\tprintf(\"first of queue: %s\\n\", queue.first().c_str());\n\t\tprintf(\"last of queue: %s\\n\", queue.last().c_str());\n\n\t\tqueue.pop();\n\t\tprintf(\"after pop, first of queue: %s, last of queue: %s\\n\", queue.first().c_str(), queue.last().c_str());\n\n\t\tprintf(\"before pop all, queue is empty? %s\\n\", queue.is_empty() ? \"yes\" : \"no\");\n\t\twhile (!queue.is_empty()) {\n\t\t\tqueue.pop();\n\t\t}\n\t\tprintf(\"after pop all, queue is empty? %s\\n\", queue.is_empty() ? \"yes\" : \"no\");\n\t}\n\n\tstd::cin.get();\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue4/makefile",
    "content": "QUEUE_OBJ=queue.o\n\nCC=g++ -std=c++11\n\nprogram: main.o $(QUEUE_OBJ)\n\t$(CC) -g -Wall -o program main.o $(QUEUE_OBJ)\n\nmain.o: main.cpp queue.h\nqueue.o: queue.cpp queue.h\n\nclean:\n\t@rm -f *.o *.exe program"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue4/queue.cpp",
    "content": "#include \"queue.h\"\n"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/queue4/queue.h",
    "content": "#ifndef QUEUE_H\n#define QUEUE_H\n\n#include <cstdlib>\n#include <cstdio>\n#include <cstring>\n\n#define IMPL_BY_ARR\n#define INIT_COUNT 2\n\n/* 数组定义的 Queue */\ntemplate<typename ValueType>\nclass Queue {\npublic:\n    Queue() : value_max_count(0), value_count(0), queue(nullptr)\n    {\n\t\tqueue = new ValueType[INIT_COUNT]();\n        if (queue == NULL) {\n            printf(\"init_queue failure: no space left!\\n\");\n            exit(1);\n        }\n        value_max_count = INIT_COUNT;\n        value_count = 0;\n    }\n\n    ~Queue()\n    {\n\t\tdelete[] queue;\n    }\n\n    /*\n        向队列的末端加入一个数据。\n    */\n    void push(ValueType v)\n    {\n        ValueType *tmp;\n\n        if (value_count >= value_max_count) {\n            tmp = new ValueType[value_max_count * 2]();\n\t\t\tif (!tmp) {\n\t\t\t\tprintf(\"push failure: No space left!\\n\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tfor (int i = 0; i < value_max_count; ++i) {\n\t\t\t\t*(tmp + i) = *(queue + i);\n\t\t\t}\n\n\t\t\tdelete[] queue;\n            queue = tmp;\n            value_max_count *= 2;\n        }\n\n        *(queue + value_count) = v;\n\t\tvalue_count++;\n    }\n\n    /*\n        从队列的开始删除一个数据。\n    */\n    void pop()\n    {\n        if (is_empty()) {\n            printf(\"pop failure: queue is empty!\\n\");\n            return;\n        }\n\n\t\tfor (int i = 0; i < value_count - 1; ++i)\n\t\t{\n\t\t\t*(queue + i) = *(queue + i + 1);\n\t\t}\n\t\tvalue_count--;\n    }\n\n    /*\n        返回队列的第一个数据（不改变队列）。\n    */\n    ValueType first()\n    {\n        static ValueType v_for_ret;\n\n        if (is_empty()) {\n            printf(\"first failure: queue is empty!\\n\");\n            return v_for_ret;\n        }\n\n        return queue[0];\n    }\n\n    /*\n        返回队列的末尾数据（不改变队列）。\n    */\n    ValueType last()\n    {\n        static ValueType v_for_ret;\n\n        if (is_empty()) {\n            printf(\"last failure: queue is empty!\\n\");\n            return v_for_ret;\n        }\n\n        return queue[value_count - 1];\n    }\n\n    /*\n        检查队列是否为空。\n    */\n    bool is_empty()\n    {\n        return value_count == 0;\n    }\n\nprivate:\n    int value_max_count;\n    int value_count;\n    ValueType *queue;\n};\n\n#endif // QUEUE_H"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/stack/main.c",
    "content": "#include <stdio.h>\n#include <stdlib.h>\n\n#include \"stack.h\"\n\nint main()\n{\n    int i;\n    Stack *stack = stack_init();\n\n    for (i = 0; i < 10; i++)\n        stack_push(stack, i);\n\n    /* stack_make_empty(stack); */\n    while (!stack_is_empty(stack)) {\n        printf(\"pop: %d\\n\", stack_pop(stack));\n    }\n\n    stack_destroy(stack);\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/stack/makefile",
    "content": "CC=gcc\n\nmain: main.o stack.o\n\tgcc -g -Wall -o main main.o stack.o\n\nmain.o: main.c stack.h\nstack.o: stack.c stack.h\n\nclean:\n\t@rm -f main *.o *.exe\n"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/stack/stack.c",
    "content": "#include \"stack.h\"\n#include <stdio.h>\n#include <stdlib.h>\n#include <assert.h>\n\n#ifdef IMPL_BY_ARR\nStack* stack_init()\n{\n\tprintf(\"stack_init with array impl\\n\");\n\tStack *stack = malloc(sizeof(Stack));\n\tassert(stack != NULL);\n\tstack->top = 0;\n\tstack->size = 2;\t/* init size */\n\tstack->values = malloc(sizeof(ValueType) * stack->size);\n\tassert(stack->values != NULL);\n\treturn stack;\n}\n\nvoid stack_destroy(Stack *stack)\n{\n\tfree(stack->values);\n\tfree(stack);\n}\n\nvoid stack_make_empty(Stack *stack)\n{\n\tstack->top = 0;\n}\n\nint stack_is_empty(Stack *stack)\n{\n\treturn 0 == stack->top;\n}\n\nvoid stack_push(Stack *stack, ValueType i)\n{\n\tValueType *tmp;\n\tif (stack->top == stack->size) {\n\t\ttmp = realloc(stack->values, sizeof(ValueType) * stack->size * 2);\n\t\tif (tmp == NULL) {\n\t\t\tprintf(\"stack_push failure: No space left\\n\");\n\t\t\treturn;\n\t\t}\n\t\tstack->values = tmp;\n\t}\n\tstack->values[stack->top++] = i;\n}\n\nValueType stack_pop(Stack *stack)\n{\n\tassert(!stack_is_empty(stack));\n\treturn stack->values[--stack->top];\n}\n#endif /* IMPL_BY_ARR */\n\n#ifdef IMPL_BY_LIST\nStack* stack_init()\n{\n\tprintf(\"stack_init with list impl\\n\");\n\tStack *stack = malloc(sizeof(Stack));\n\tassert(stack != NULL);\n\tstack->header = NULL;\n\treturn stack;\n}\n\nvoid stack_destroy(Stack *stack)\n{\n\tstruct node *tmp_node;\n\twhile (stack->header) {\n\t\ttmp_node = stack->header->next;\n\t\tfree(stack->header);\n\t\tstack->header = tmp_node;\n\t}\n\tfree(stack);\n}\n\nvoid stack_make_empty(Stack *stack)\n{\n\tstruct node *tmp_node;\n\twhile (stack->header) {\n\t\ttmp_node = stack->header->next;\n\t\tfree(stack->header);\n\t\tstack->header = tmp_node;\n\t}\n}\n\nint stack_is_empty(Stack *stack)\n{\n\treturn stack->header == NULL;\n}\n\nvoid stack_push(Stack *stack, ValueType v)\n{\n\tstruct node *tmp_node = malloc(sizeof(ValueType));\n\ttmp_node->value = v;\n\ttmp_node->next = stack->header;\n\tstack->header = tmp_node;\n}\n\nValueType stack_pop(Stack *stack)\n{\n\tstruct node *tmp_node;\n\tValueType ret;\n\tassert(!stack_is_empty(stack));\n\t\n\ttmp_node = stack->header;\n\tret = stack->header->value;\n\tstack->header = stack->header->next;\n\tfree(tmp_node);\n\n\treturn ret;\n}\n\n#endif /* IMPL_BY_LIST */"
  },
  {
    "path": "codes/CProgramming/ch19_程序设计/stack/stack.h",
    "content": "#ifndef STACK_H\n#define STACK_H\n\n#define IMPL_BY_ARR\n/* #define IMPL_BY_LIST */\n\ntypedef int ValueType;\n\n/* 使用数组实现的 stack 结构 */\n#ifdef IMPL_BY_ARR\nstruct stack {\n    int top;\n    int size;\n    ValueType *values;\n};\n#endif\n\n/* 使用链表实现的 stack 结构 */\n#ifdef IMPL_BY_LIST\nstruct node {\n    ValueType value;\n    struct node *next;\n};\n\nstruct stack {\n    struct node *header;\n};\n#endif\n\ntypedef struct stack Stack;\n\nStack* stack_init();\nvoid stack_destroy(Stack *stack);\nvoid stack_make_empty(Stack *stack);\nint stack_is_empty(Stack *stack);\nvoid stack_push(Stack *stack, ValueType v);\nValueType stack_pop(Stack *stack);\n\n#endif // STACK_H"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/ex_01.c",
    "content": "/*\n    指出下面每一个代码段的输出。假定i, j和k都是 unsigned int 类型的变量。\n\n    (a) i = 8; j = 9;\n        printf(\"%d\", i >> 1 + j >> 1);\n\n    (b) i = 1;\n        printf(\"%d\", i & ~i);\n\n    (c) i = 2; j = 1; k = 0;\n        printf(\"%d\", ~i & j ^ k);\n\n    (d) i = 7; j = 8; k = 9;\n        printf(\"%d\", i ^ j & k);\n*/\n\n/*\n    (a) 0\n    (b) 0\n    (c) 1\n    (d) 15\n*/\n\n#include <stdio.h>\n\nint main(int argc, char const *argv[])\n{\n    int i, j, k;\n\n    i = 8; j = 9;\n    printf(\"a: %d\\n\", i >> 1 + j >> 1);\n\n    i = 1;\n    printf(\"b: %d\\n\", i & ~i);\n\n    i = 2; j = 1; k = 0;\n    printf(\"c: %d\\n\", ~i & j ^ k);\n    \n    i = 7; j = 8; k = 9;\n    printf(\"d: %d\\n\", i ^ j & k);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/ex_02.c",
    "content": "/*\n    请说出如何“切换”一个位（从0改为1或从1改为0）.通过编写一条语句切换变量i的第4位来说明\n    这种方法。\n*/\n\n/*\n    对该位取反。\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nvoid PrintFlag(int _flag)\n{\n\tchar buf[65] = { 0 };\n\n\tint flag = 0;\n\tmemcpy(&flag, &_flag, sizeof(_flag));\n\n\titoa(flag, buf, 2);\n\n\tprintf(\"flag: %s\\n\", buf);\n}\n\nint main(int argc, char const *argv[])\n{\n    int i = 0xFF; /* 11111111 */\n    \n    PrintFlag(i);\n\n    i &= ~(1 << 3);\n    PrintFlag(i);\n\n    i |= (1 << 3);\n    PrintFlag(i);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/ex_03.c",
    "content": "/*\n    请解释下面的宏对它的实际参数起什么作用。假设参数具有相同类型。\n\n    #define M(x,y) ((x)^=(y), (y)^=(x), (x)^=(y))\n*/\n\n/*\n    根据测试，作用是交换 x y\n*/\n\n#include <stdio.h>\n\n#define M(x,y) ((x)^=(y), (y)^=(x), (x)^=(y))\n\nint main(int argc, char const *argv[])\n{\n    int x = 500, y = 28;\n    M(x, y);\n\n    printf(\"%d %d\\n\", x, y);\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/ex_04.c",
    "content": "/*\n    在计算机图形处理中，颜色通常是用3个数存储的，分别代表红、绿和蓝3种颜色。假定每个颜色需要\n    8位来存储，而且我们希望将三种颜色一起存放在一个长整型数据中。请编写一个名为 MK_COLOR 的宏，\n    包括3个参数（红、绿、蓝的强度）。 MK_COLOR 宏需要返回一个 long int 值，其中后3个字节分别\n    包含红、绿和蓝，红作为最后一个字节。\n*/\n\n#include <stdio.h>\n\nstruct color_field {\n    unsigned long:8;\n    unsigned long b:8;\n    unsigned long g:8;\n    unsigned long r:8;\n};\n\nunion color {\n    struct color_field cf;\n    long int v;\n};\n\nlong int mk_color(int r, int g, int b)\n{\n    union color c;\n    c.cf.r = r;\n    c.cf.g = g;\n    c.cf.b = b;\n    return c.v;\n}\n#define MK_COLOR(r, g, b) mk_color(r, g, b)\n\nvoid get_color(long int v, unsigned char colors[3])\n{\n    union color c;\n    c.v = v;\n    colors[0] = c.cf.r;\n    colors[1] = c.cf.g;\n    colors[2] = c.cf.b;\n}\n\nint main(int argc, char const *argv[])\n{\n    printf(\"%ld\\n\", MK_COLOR(244, 122, 60));\n\n    unsigned char colors[3];\n    get_color(MK_COLOR(244, 122, 60), colors);\n    printf(\"%u %u %u\\n\", colors[0], colors[1], colors[2]);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/ex_05.c",
    "content": "/*\n    编写名字为 GET_RED, GET_GREEN, GET_BLUE 3个宏，并以一个给定的颜色值作为参数（见练习4）。\n    宏会返回一个8位的值表示给定颜色中红、绿、或蓝。\n*/\n\n#include <stdio.h>\n\nstruct color_field {\n    unsigned long:8;\n    unsigned long b:8;\n    unsigned long g:8;\n    unsigned long r:8;\n};\n\nunion color {\n    struct color_field cf;\n    long int v;\n};\n\nlong int mk_color(int r, int g, int b)\n{\n    union color c;\n    c.cf.r = r;\n    c.cf.g = g;\n    c.cf.b = b;\n    return c.v;\n}\n#define MK_COLOR(r, g, b) mk_color(r, g, b)\n\nvoid get_color(long int v, unsigned char colors[3])\n{\n    union color c;\n    c.v = v;\n    colors[0] = c.cf.r;\n    colors[1] = c.cf.g;\n    colors[2] = c.cf.b;\n}\n\nenum color_type { RED, GREEN, BLUE };\nunsigned char get_certain_color(long int v, enum color_type ct)\n{\n    unsigned char colors[3];\n    get_color(v, colors);\n    return colors[ct];\n}\n\n#define GET_RED(v) get_certain_color(v, RED)\n#define GET_GREEN(v) get_certain_color(v, GREEN)\n#define GET_BLUE(v) get_certain_color(v, BLUE)\n\nint main(int argc, char const *argv[])\n{\n    long int v = MK_COLOR(244, 122, 60);\n    printf(\"%ld\\n\", v);\n\n    unsigned char colors[3];\n    get_color(v, colors);\n    printf(\"%u %u %u\\n\", GET_RED(v), GET_GREEN(v), GET_BLUE(v));\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/ex_06.c",
    "content": "/*\n    (a) 使用按位运算符编写如下函数：\n        unsigned short int swap_byte(unsigned short int i);\n        函数 swap_byte 的返回值是将i的两个字节调换后产生的结果。（在大多数计算机中，短整型数据占\n        两个字节。）例如，假设i的值是0x1234(二进制形式为00010010 00110100)，那么 swap_byte\n        的返回值应该为0x3412(二进制形式00110100 00010010)。编写一个程序来测试你的函数。程序\n        以十六进制读入数，然后交换两个字节并显示出来：\n        Enter a hexadecimal number: 1234\n        Number with byte swapped:   3412\n        提示：使用%hx转换来读入和输出十六进制数\n\n    (b) 将 swap_byte 函数的函数体化简为一条语句。\n*/\n\n#include <stdio.h>\n\nstruct trans_field {\n    unsigned short low:8;\n    unsigned short high:8;\n};\n\nunion trans {\n    struct trans_field tf;\n    unsigned short int i;\n};\n\nunsigned short int swap_byte(unsigned short int i)\n{\n    /* tricks for b, 复合语句也算一条语句 */\n    {\n        unsigned short tmp;\n\n        union trans t;\n        t.i = i;\n        \n        tmp = t.tf.low;\n        t.tf.low = t.tf.high;\n        t.tf.high = tmp;\n\n        return t.i;\n    }\n}\n\nint main(int argc, char const *argv[])\n{\n    unsigned short int i;\n\n    printf(\"Enter a hexadecimal number: \");\n    scanf(\"%hx\", &i);\n    printf(\"Number with byte swapped: %hx\\n\", swap_byte(i));\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/ex_07.c",
    "content": "/*\n    编写如下函数：\n    unsigned int rotate_left(unsigned int i, int n);\n    unsigned int rotate_right(unsigned int i, int n);\n\n    函数 rotate_left(i, n)的值应是将i左移n位并将从左侧移入i右端而产生的结果。\n    （例如，假定整型占16位， rotate_left(0x1234, 4) 将返回0x2341。）类似地，\n    函数 rotate_right 也类似，只是将数字中的位向右循环移位。\n*/\n\n/*\n    PS: unsigned int 在我的机子上是32位，不方便输入测试，故采用 unsigned short\n*/\n\n#include <stdio.h>\n\ntypedef unsigned short UInt16;\n\ntypedef enum { LEFT, RIGHT } CutDir;\nstatic unsigned short cut(UInt16 i, CutDir dir, int n)\n{\n    UInt16 mask = 0;\n    const int bits = sizeof(UInt16) * 8;\n    int b;\n\n    if (dir == LEFT) {\n        for (b = bits - 1; b > bits - 1 - n; b--) {\n            mask |= (1 << b);\n        }\n    }\n    else if (dir == RIGHT) {\n        for (b = 0; b < n; b++) {\n            mask |= (1 << b);\n        }\n    }\n    else {\n        return i;\n    }\n\n    i &= mask;\n    if (dir == LEFT) {\n        i = i >> (bits - n);\n    }\n    else {\n        i = i << (bits - n);\n    }\n\n    return i;\n}\n\nUInt16 rotate_left(UInt16 i, int n)\n{\n    UInt16 cutted;\n\n    cutted = cut(i, LEFT, n);\n\n    i = i << n;\n    i |= cutted;\n\n    return i;\n}\nUInt16 rotate_right(UInt16 i, int n)\n{\n    UInt16 cutted;\n\n    cutted = cut(i, RIGHT, n);\n\n    i = i >> n;\n    i |= cutted;\n\n    return i;\n}\n\nint main(int argc, char const *argv[])\n{\n    UInt16 res;\n    UInt16 i = 0x1234;\n\n    printf(\"%hx\\n\", i);\n    \n    res = rotate_left(i, 4);\n    printf(\"%hx\\n\", res);\n\n    res = rotate_right(i, 4);\n    printf(\"%hx\\n\", res);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/ex_08.c",
    "content": "/*\n    假定函数f的实现如下：\n\n    unsigned int f(unsigned int i, int m, int n)\n    {\n        return (i >> (m+1-n) & ~(~0 << n));\n    }\n\n    (a) ~(~0 << n)的结果是什么？\n    (b) 函数f的作用是什么？\n*/\n\n/*\n    (a) 将1左移n位，然后对每一位取反\n    (b) 将i右移一个值，这个值是 (m+1-n) 与 ~(~0 << n)) 的位与的结果\n*/\n\n#include <stdio.h>\n\nunsigned int f(unsigned int i, int m, int n)\n{\n    return (i >> (m+1-n) & ~(~0 << n));\n}\n\nint main(int argc, char const *argv[])\n{\n    unsigned int i = 0x1111;\n\n    printf(\"%hx\\n\", f(i, 4, 1));\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/ex_09.c",
    "content": "/*\n    当按照IEEE浮点标准存储浮点数时，一个 float 类型的值由一个符号位（最左边的位或最重要的位），\n    8个指数位以及23个小数位依次组成。请设计一个32位的结构类型，包含与符号位、指数位和小数位\n    相对应的位域成员。声明的位域类型为 unsigned int 。请参考你的用户手册来决定位域的顺序。\n    警告：一些编译器会限制位域在16位以内，因此当你编译这个结构\n*/\n\nstruct type_field {\n    unsigned int d:23;      /* 小数位 */\n    unsigned int e:8;       /* 指数位 */\n    unsigned int s:1;       /* 符号位 */\n};\n\nint main(int argc, char const *argv[])\n{\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/ex_10.c",
    "content": "/*\n    请设计一个联合类型，使一个32位的值既可以看作是一个 float 型的值，也可以看作是练习9中定义\n    的结构。写一个程序将1存储在结构的符号位，将128存储在指数位，0存储在小数位。然后按 float\n    值的形式显示存储在联合中的值。（如果你的位域设置正确的话，结果应该是-2.0。）\n*/\n\n#include <stdio.h>\n\nstruct type_field {\n    unsigned int d:23;      /* 小数位 */\n    unsigned int e:8;       /* 指数位 */\n    unsigned int s:1;       /* 符号位 */\n};\n\nunion type {\n    struct type_field tf;\n    float f;\n};\n\nint main(int argc, char const *argv[])\n{\n    union type t;\n    t.tf.s = 1;\n    t.tf.e = 128;\n    t.tf.d = 0;\n\n    printf(\"%.1f\\n\", t.f);\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/test_bit_field.c",
    "content": "#include <stdio.h>\n\n/*\n    位域\n*/\n\n/*\n    定义位域\n    为了可移植性，位域应该声明为 unsigned int\n*/\nstruct file_date {\n    unsigned int day: 5;\n    unsigned int month: 4;\n    unsigned int year: 7;\n};\n\nint main(int argc, char const *argv[])\n{\n    printf(\"sizeof file_data: %u\\n\", sizeof(struct file_date));\n\n    struct file_date fd;\n    fd.day = 20;\n    fd.month = 12;\n    fd.year = 48;\n\n    printf(\"file date: %d-%d-%d\\n\", 1970 + fd.year, fd.month, fd.day);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch20_低级程序设计/xor.c",
    "content": "/* Perform XOR encryption */\n\n#include <ctype.h>\n#include <stdio.h>\n\n#define KEY '&'\n\nint main(int argc, char const *argv[])\n{\n    int orig_char, new_char;\n\n    while ((orig_char = getchar()) != EOF) {\n        new_char = orig_char ^ KEY;\n        if (iscntrl(orig_char) || iscntrl(new_char))\n            putchar(orig_char);\n        else\n            putchar(new_char);\n    }\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch21_标准库/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc -g -Wall $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch21_标准库/ex_01.md",
    "content": "在你的系统中找到存放头文件的位置。找出那些非标准的头，并指明每一个的用途。\n\n---\n\nwindows: C:/Program Files (x86)/Windows Kits/10/Include/10.0.10240.0/ucrt\n\nCentOS: /usr/include\n\n指明用途略过。"
  },
  {
    "path": "codes/CProgramming/ch21_标准库/ex_02.md",
    "content": "在存放头文件的目录中（见练习1），找到一个使用宏来隐藏函数的标准头。\n\n---\n\n比如 stdio.h 中的 getc 宏。"
  },
  {
    "path": "codes/CProgramming/ch21_标准库/ex_03.c",
    "content": "/*\n    当使用宏隐藏函数时，在头文件中哪一个必须放在前面：宏定义还是函数原型？验证你的结论。\n*/\n\n/*\n    函数要放在前面\n*/\n\n#include <stdio.h>\n\nvoid Test()\n{\n    printf(\"I am function\\b\");\n}\n\n#define Test() printf(\"I am macro\\n\")\n\nint main(int argc, char const *argv[])\n{\n    Test();\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch21_标准库/ex_04.md",
    "content": "你期望在哪个标准头中可以分别找到下面描述的函数或宏？\n\n(a) 可以得到当前是星期几的函数。\n\n(b) 判断一个字符是否是数字的函数。\n\n(c) 给出最大的 unsigned int 的值的宏定义。\n\n(d) 对一个浮点数，得到比它大的最近的整数的函数。\n\n(e) 指定一个字符包含多少位的宏。\n\n(f) 指定在一个 double 类型的值中，有效位个数的宏。\n\n(g) 在字符串中查找特定字符的函数。\n\n(h) 用来以读的方式打开一个文件的函数。\n\n---\n\n(a) time.h\n\n(b) ctype.h\n\n(c) limits.h\n\n(d) math.h\n\n(e) stddef.h\n\n(f) float.h\n\n(g) string.h\n\n(h) stdio.h"
  },
  {
    "path": "codes/CProgramming/ch21_标准库/ex_05.c",
    "content": "/*\n    编写一个程序，声明书中的结构s，并显示出成员a, b, c的大小和偏移量。（使用 sizeof 来得到大小，\n    使用 offsetof 来得到偏移量。）同时使程序显示出整个结构的大小。根据这些信息，判断结构中是否\n    包含空洞（无效字节）。如果包含，指出每一个的位置和大小。\n*/\n\n/*\n    包含了空洞，a与b之间有3个字节的空洞。\n*/\n\n#include <stddef.h>\n#include <stdio.h>\n\nstruct s {\n    char a;\n    int b[2];\n    float c;\n} v;\n\nint main(int argc, char const *argv[])\n{\n    printf(\"sizeof a: %u, offset a: %u\\n\", sizeof(v.a), offsetof(struct s, a));  /* 一定是0，因为第一个成员的地址与自身地址相同 */\n\n    printf(\"sizeof b: %u, offset b: %u\\n\", sizeof(v.b), offsetof(struct s, b));\n\n    printf(\"sizeof c: %u, offset c: %u\\n\", sizeof(v.c), offsetof(struct s, c));\n\n    printf(\"sizeof struct s: %u\\n\", sizeof(struct s));\n    \n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc $c_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/canopen.c",
    "content": "/* Checks whether a file can be opened for reading */\n\n#include <stdio.h>\n\nint main(int argc, char const *argv[])\n{\n    FILE *fp;\n    if (argc != 2) {\n        printf(\"usage: canopen filename\\n\");\n        return 1;\n    }\n\n    if ((fp = fopen(argv[1], \"r\")) == NULL) {\n        printf(\"%s can't be opened\\n\", argv[1]);\n        return 1;\n    }\n\n    printf(\"%s can be opened\\n\", argv[1]);\n    fclose(fp);\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/dir_ex_19/invent.c",
    "content": "/*\n    invent.c\n    Maintains a parts database (array version)\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n#include \"readline.h\"\n\n#define NAME_LEN 25\n#define MAX_PARTS 100\n\nstruct part {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n} inventory[MAX_PARTS];\n\nint num_parts = 0;      /* number of parts currently stored */\n\nint find_part(int number);\nvoid insert(void);\nvoid search(void);\nvoid update(void);\nvoid print(void);\nvoid dump(void);\nvoid restore(void);\n\n/*\n    main: Prompts the user to enter an operator code,\n        then calls a function to perform the requested\n        action. Repeats until the user enters the\n        command 'q'. Prints an error message if th user\n        enters an illegal code.\n*/\nint main(int argc, char const *argv[])\n{\n    char code;\n\n    for (;;) {\n        printf(\"Enter operation code: \");\n        scanf(\" %c\", &code);\n        while (getchar() != '\\n'); /* skips to end of line */\n\n        switch(code) {\n            case 'i': insert(); break;\n            case 's': search(); break;\n            case 'u': update(); break;\n            case 'p': print(); break;\n            case 'd': dump(); break;\n            case 'r': restore(); break;\n            case 'q': printf(\"bye\\n\"); return 0;\n            default: printf(\"Illegal code\\n\"); break;\n        }\n        printf(\"\\n\");\n    }\n\n    return 0;\n}\n\n/*\n    find_part: Looks up a part number in the inventory\n            array. Returns the array index if the part\n            number is found; otherwise, return -1.\n*/\nint find_part(int number)\n{\n    int i;\n    for (i = 0; i < num_parts; i++) {\n        if (inventory[i].number == number)\n            return i;\n    }\n    return -1;\n}\n\n/*\n    insert: Prompts the user for information about a new\n            part and then inserts the part into the\n            database. Prints an error message and returns\n            prematurely if the part already exists or the\n            database if full.\n*/\nvoid insert(void)\n{\n    int part_number;\n\n    if (num_parts == MAX_PARTS) {\n        printf(\"Database is full; cant't add more parts.\\n\");\n        return;\n    }\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &part_number);\n    if (find_part(part_number) >= 0) {\n        printf(\"Part already exists.\\n\");\n        return;\n    }\n\n    inventory[num_parts].number = part_number;\n    \n    printf(\"Enter part name: \");\n    read_line(inventory[num_parts].name, NAME_LEN);\n    \n    printf(\"Enter quantity on hand: \");\n    scanf(\"%d\", &inventory[num_parts].on_hand);\n    \n    num_parts++;\n}\n\n/*\n    search: Prompts the user to enter a part number, then\n            looks up the part in the database. If the part\n            exists, prints the name and quantity on hand;\n            if not, prints an error message.\n*/\nvoid search(void)\n{\n    int i, number;\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    i = find_part(number);\n    if (i >= 0) {\n        printf(\"Part name: %s\\n\", inventory[i].name);\n        printf(\"Quantity on hand: %d\\n\", inventory[i].on_hand);\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    update: Prompts the user to enter a part number.\n            Prints an error message if the part doesn't\n            exist; otherwise, prompts the user to enter\n            change in quantity on hand and updates the\n            database.\n*/\nvoid update(void)\n{\n    int i, number, change;\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    \n    i = find_part(number);\n    if (i >= 0) {\n        printf(\"Enter change in quantity on hand: \");\n        scanf(\"%d\", &change);\n        inventory[i].on_hand += change;\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    print: Prints a listing of all parts in the database,\n            showing the part number, part name, and\n            quantity on hand. Parts are printed in the\n            order in which they were entered into the\n            database.\n*/\nvoid print(void)\n{\n    int i;\n    printf(\"Part number    Part Name          Quantity on Hand\\n\");\n    for (i = 0; i < num_parts; i++) {\n        printf(\"%7d     %-25s%11d\\n\", inventory[i].number, inventory[i].name, inventory[i].on_hand);\n    }\n}\n\nint compare_parts(const void *p, const void *q)\n{\n    return ((struct part*)q)->number - ((struct part*)p)->number;\n}\n\nvoid dump(void)\n{\n    char file_name[128];\n    FILE *fp;\n\n    if (num_parts <= 0) {\n        printf(\"no parts to save\\n\");\n        return;\n    }\n\n    printf(\"Enter name of output file: \");\n    scanf(\"%s\", file_name);\n\n    fp = fopen(file_name, \"wb\");\n    if (!fp) {\n        fprintf(stderr, \"fopen failed\\n\");\n        return;\n    }\n\n    fwrite(inventory, sizeof(inventory[0]), num_parts, fp);\n\n    fclose(fp);\n}\n\nvoid restore(void)\n{\n    char file_name[128];\n    FILE *fp;\n    int count_read, i;\n\n    printf(\"Enter name of input file: \");\n    scanf(\"%s\", file_name);\n\n    fp = fopen(file_name, \"rb\");\n    if (!fp) {\n        fprintf(stderr, \"fopen failed\\n\");\n        return;\n    }\n\n    struct part inventory_tmp[MAX_PARTS];\n\n    count_read = fread(inventory_tmp, sizeof(inventory_tmp[0]), MAX_PARTS, fp);\n\n    if (count_read > 0) {\n        num_parts = count_read;\n        memcpy(inventory, inventory_tmp, sizeof(inventory));\n    }\n\n    fclose(fp);\n}"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/dir_ex_19/makefile",
    "content": "CC=gcc\n\nprogram: invent.o readline.o\n\t$(CC) -g -Wall -o program invent.o readline.o\n\ninvent.o: invent.c readline.h\nreadline.o: readline.c readline.h\n\nclean:\n\t@rm -f *.o *.exe programi"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/dir_ex_19/readline.c",
    "content": "#include <ctype.h>\n#include <stdio.h>\n#include \"readline.h\"\n\nint read_line(char str[], int n)\n{\n    int ch, i = 0;\n\n    while (isspace(ch = getchar()));\n\n    while (1) {            \n        if (ch == '\\n' || ch == EOF) break;\n\n        if (i < n) {\n            str[i++] = ch;\n        }\n        ch = getchar();\n    }\n\n    str[i] = '\\0';\n\n    return i;\n}"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/dir_ex_19/readline.h",
    "content": "#ifndef READLINE_H\n#define READLINE_H\n\n/*\n    read_line: Skips leading white-space characters, then\n                reads the remainder of the input line and\n                stores it in str. Truncates the line if its\n                length exceeds n. Returns the number of\n                characters stored.\n*/\nint read_line(char str[], int n);\n\n#endif // READLINE_H"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/dir_ex_21/invent.c",
    "content": "/*\n    invent.c\n    Maintains a parts database (array version)\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include \"readline.h\"\n\n#define NAME_LEN 25\n#define MAX_PARTS 100\n\nstruct part {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n    struct part *next;\n};\n\nstruct part_pointer_pair {\n    struct part* prev;\n    struct part* cur;\n};\n\nstruct part *inventory = NULL; /* point to first part */\n\nint num_parts = 0;      /* number of parts currently stored */\n\nstruct part* find_part(int number);\nstruct part_pointer_pair find_part2(int number);\n\nvoid insert(void);\nvoid search(void);\nvoid update(void);\nvoid erase(void);\nvoid print(void);\nvoid dump(void);\nvoid restore(void);\n\n/*\n    main: Prompts the user to enter an operator code,\n        then calls a function to perform the requested\n        action. Repeats until the user enters the\n        command 'q'. Prints an error message if th user\n        enters an illegal code.\n*/\nint main(int argc, char const *argv[])\n{\n    char code;\n\n    for (;;) {\n        printf(\"Enter operation code: \");\n        scanf(\" %c\", &code);\n        while (getchar() != '\\n'); /* skips to end of line */\n\n        switch(code) {\n            case 'i': insert(); break;\n            case 's': search(); break;\n            case 'u': update(); break;\n            case 'e': erase(); break;\n            case 'p': print(); break;\n            case 'd': dump(); break;\n            case 'r': restore(); break;\n            case 'q': printf(\"bye\\n\"); return 0;\n            default: printf(\"Illegal code\\n\"); break;\n        }\n        printf(\"\\n\");\n    }\n\n    return 0;\n}\n\n/*\n    find_part: Looks up a part number in the inventory\n            list. Returns the pointer if the part\n            number is found; otherwise, return NULL.\n*/\nstruct part* find_part(int number)\n{\n    struct part *p;\n    for (p = inventory; p != NULL && number > p->number;\n        p = p->next);\n    \n    if (p != NULL && number == p->number)\n        return p;\n\n    return NULL;\n}\n\nstruct part_pointer_pair find_part2(int number)\n{\n    struct part *p, *prev = NULL;\n    struct part_pointer_pair ppp = {NULL, NULL};\n\n    for (p = inventory; p != NULL && number > p->number;\n        prev = p, p = p->next);\n    \n    if (p != NULL && number == p->number) {\n        ppp.prev = prev;\n        ppp.cur = p;\n    }\n\n    return ppp;\n}\n\n/*\n    insert: Prompts the user for information about a new\n            part and then inserts the part into the\n            inventory list; the list remains sorted by\n            part number. Prints an error message and\n            returns prematurely if the part already exists\n            or space could not be allocated for the part.\n*/\nvoid insert(void)\n{\n    struct part *cur, *prev, *new_node;\n\n    new_node = malloc(sizeof(struct part));\n    if (new_node == NULL) {\n        printf(\"Database is full; can't add more parts.\\n\");\n        return;\n    }\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &new_node->number);\n\n    for (cur = inventory, prev = NULL;\n        cur != NULL && new_node->number > cur->number;\n        prev = cur, cur = cur->next);\n\n    if (cur != NULL && new_node->number == cur->number) {\n        printf(\"Part already exists.\\n\");\n        free(new_node);\n        return;\n    }\n\n    printf(\"Enter part name: \");\n    read_line(new_node->name, NAME_LEN);\n    printf(\"Enter quantity on hand: \");\n    scanf(\"%d\", &new_node->on_hand);\n\n    new_node->next = cur;\n    if (prev == NULL)\n        inventory = new_node;\n    else\n        prev->next = new_node;\n}\n\n/*\n    search: Prompts the user to enter a part number, then\n            looks up the part in the database. If the part\n            exists, prints the name and quantity on hand;\n            if not, prints an error message.\n*/\nvoid search(void)\n{\n    int number;\n    struct part *p;\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    p = find_part(number);\n    if (p != NULL) {\n        printf(\"Part name: %s\\n\", p->name);\n        printf(\"Quantity on hand: %d\\n\", p->on_hand);\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    update: Prompts the user to enter a part number.\n            Prints an error message if the part doesn't\n            exist; otherwise, prompts the user to enter\n            change in quantity on hand and updates the\n            database.\n*/\nvoid update(void)\n{\n    int number, change;\n    struct part *p;\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    \n    p = find_part(number);\n    if (p != NULL) {\n        printf(\"Enter change in quantity on hand: \");\n        scanf(\"%d\", &change);\n        p->on_hand += change;\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    for ex_09\n*/\nvoid erase(void)\n{\n    int number;\n    struct part *p;\n    struct part_pointer_pair ppp;\n\n    printf(\"Enter part number to delete: \");\n    scanf(\"%d\", &number);\n\n    ppp = find_part2(number);\n    if (ppp.cur != NULL) {\n        if (ppp.prev != NULL) {\n            ppp.prev->next = ppp.cur->next;\n            free(ppp.cur);\n        }\n        else {\n            /* erase header */\n            free(ppp.cur);\n            inventory = NULL;\n        }\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n}\n\n/*\n    print: Prints a listing of all parts in the database,\n            showing the part number, part name, and\n            quantity on hand. Parts are printed in the\n            order in which they were entered into the\n            database.\n*/\nvoid print(void)\n{\n    struct part* p;\n    printf(\"Part number    Part Name          Quantity on Hand\\n\");\n    for (p = inventory; p != NULL; p = p->next) {\n        printf(\"%7d     %-25s%11d\\n\", p->number, p->name, p->on_hand);\n    }\n}\n\nvoid dump(void)\n{\n    char file_name[128];\n    FILE *fp;\n    struct part *p;\n\n    if (inventory == NULL) {\n        printf(\"no parts to save\\n\");\n        return;\n    }\n\n    printf(\"Enter name of output file: \");\n    scanf(\"%s\", file_name);\n\n    fp = fopen(file_name, \"wb\");\n    if (!fp) {\n        fprintf(stderr, \"fopen failed\\n\");\n        return;\n    }\n\n    for (p = inventory; p != NULL; p = p->next) {\n        fwrite(p, sizeof(inventory[0]), 1, fp);\n    }\n\n    fclose(fp);\n}\n\nvoid restore(void)\n{\n    char file_name[128];\n    FILE *fp;\n    int count_read, i;\n    struct part *p, *tmp;\n    struct part part_tmp;\n\n    printf(\"Enter name of input file: \");\n    scanf(\"%s\", file_name);\n\n    fp = fopen(file_name, \"rb\");\n    if (!fp) {\n        fprintf(stderr, \"fopen failed\\n\");\n        return;\n    }\n\n    /* 销毁现有的表 */\n    for (p = inventory; p != NULL;) {\n        tmp = p;\n        p = p->next;\n        free(tmp);\n    }\n    inventory = NULL;\n\n    /* 从文件中读取 */\n    while (fread(&part_tmp, sizeof(part_tmp), 1, fp) > 0) {\n        tmp = malloc(sizeof(part_tmp));\n        *tmp = part_tmp;\n        if (inventory == NULL) {\n            inventory = tmp;\n            p = inventory;\n        }\n        else {\n            p->next = tmp;\n            p = p->next;\n        }\n    }\n    \n    fclose(fp);\n}"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/dir_ex_21/makefile",
    "content": "CC=gcc\n\nprogram: invent.o readline.o\n\t$(CC) -g -Wall -o program invent.o readline.o\n\ninvent.o: invent.c readline.h\nreadline.o: readline.c readline.h\n\nclean:\n\t@rm -f *.o *.exe programi"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/dir_ex_21/readline.c",
    "content": "#include <ctype.h>\n#include <stdio.h>\n#include \"readline.h\"\n\nint read_line(char str[], int n)\n{\n    int ch, i = 0;\n\n    while (isspace(ch = getchar()));\n\n    while (1) {            \n        if (ch == '\\n' || ch == EOF) break;\n\n        if (i < n) {\n            str[i++] = ch;\n        }\n        ch = getchar();\n    }\n\n    str[i] = '\\0';\n\n    return i;\n}"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/dir_ex_21/readline.h",
    "content": "#ifndef READLINE_H\n#define READLINE_H\n\n/*\n    read_line: Skips leading white-space characters, then\n                reads the remainder of the input line and\n                stores it in str. Truncates the line if its\n                length exceeds n. Returns the number of\n                characters stored.\n*/\nint read_line(char str[], int n);\n\n#endif // READLINE_H"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_01.md",
    "content": "指出下列每个文件可能是包含文本数据还是二进制数据。\n\n(a) 由C语言编译器产生的目标代码文件。\n\n(b) 由C语言编译器产生的程序列表。\n\n(c) 从一台计算机发送到另一台计算器的电子邮件。\n\n(d) 含有图形映像的文件。\n\n---\n\n二进制：a, c, d\n\n文本：b"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_02.md",
    "content": "指出在下列每种情况下会把哪种模式字符串传递给 fopen 函数：\n\n(a) 数据库管理系统打开含有将被更新的记录的文件。\n\n(b) 邮件程序打开存有消息的文件以便可以在文件末尾添加额外信息。\n\n(c) 图形程序打开含有将被显示在屏幕上的图片文件。\n\n(d) 操作系统命令解释器打开含有将被执行的命令的“批文件”（或者“壳脚本”）。\n\n---\n\n(a) \"rb+\"\n\n(b) \"a\"\n\n(c) \"rb\"\n\n(d) \"r\""
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_03.c",
    "content": "/*\n    扩充 canopen 程序，以便用户可以把任意数量的文件名放置在命令行中：\n\n    canopen foo bar baz\n\n    这个程序应该为每个文件分别显示出 can be opened 消息或者 can't be opened 消息。如果命令\n    行中没有参数，那么程序应该返回2；如果无法打开任何文件，那么程序返回1；如果可以打开所有文件，\n    程序应返回0.\n*/\n\n#include <stdio.h>\n\nint main(int argc, char const *argv[])\n{\n    FILE *fp;\n    int i;\n    int opened_times;\n    if (argc < 2) {\n        printf(\"usage: canopen filename\\n\");\n        return 2;\n    }\n\n    opened_times = 0;\n    for (i = 1; i < argc; i++) {\n        if ((fp = fopen(argv[i], \"r\")) == NULL) {\n            printf(\"%s can't be opened\\n\", argv[i]);\n        }\n        else {\n            printf(\"%s can be opened\\n\", argv[i]);\n            opened_times++;\n        }\n        \n        fclose(fp);\n    }\n\n    if (opened_times == 0) return 1;\n\n    if (opened_times < argc - 1) return 3;\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_04.c",
    "content": "/*\n    请指出如果 printf 函数用 %#012.5g 作为转换说明来执行显示操作，下列数据显示的形式：\n\n    (a) 83.7361\n    (b) 29748.6607\n    (c) 1054932234.0\n    (d) 0.0000235218\n*/\n\n/*\n    (a) 00000083.736\n    (b) 00000029749.\n    (c) 01.0549e+009\n    (d) 02.3529e-005\n*/\n\n#include <stdio.h>\n\nint main(int argc, char const *argv[])\n{\n    printf(\"%#012.5g\\n\", 83.7361);\n    printf(\"%#012.5g\\n\", 29748.6607);\n    printf(\"%#012.5g\\n\", 1054932234.0);\n    printf(\"%#012.5g\\n\", 00.000023529);\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_05.c",
    "content": "/*\n    printf 函数的转换说明 %.4d 和 %04d 有区别吗？如果有，请说明区别是什么。\n*/\n\n/*\n    区别是，如果指定了精度，那么 04d 中的0就没有意义了。\n*/\n\n#include <stdio.h>\n\nint main(int argc, char const *argv[])\n{\n    int a = 42;\n\n    printf(\"%.4d\\n\", a);\n    printf(\"%04d\\n\", a);\n    printf(\"%05.4d\\n\", a);\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_06.c",
    "content": "/*\n    编写 printf 函数调用，要求如果变量 widget （类型为 int 型）的值为1，则显示 1 widget ；如果值为n，则显示出 n\n    widgets 。不允许使用 if 语句或任何其他任何语句；答案必须是一个单独的一个 printf 调用。\n*/\n\n#include <stdio.h>\n\nint main(int argc, char const *argv[])\n{\n    int widget = 1;\n\n    printf(\"%d widget%s\\n\", widget, widget > 1 ? \"s\" : \"\");\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_07.c",
    "content": "/*\n    假设按照下列形式调用 scanf 函数：\n    n = scanf(\"%d%f%d\", &i, &x, &j);\n    (其中，i, j, 和n都是 int 型变量。)假设输入流含有下面所示的字符，请指出\n    这个调用后i, j, n和x的值。此外，请说明一下调用会消耗掉哪些字符。\n\n    (a) 10`20`30-\n    (b) 1.0`2.0`3.0-\n    (c) 0.1`0.2`0.3-\n    (d) .1`.2`.3-\n*/\n\n/*\n    (a) i 10, j 30, x 20.0, n 3, 消耗 10`20`30\n    (b) i 1, j 2, x 0.0, n 3, 消耗 1.0`2\n    (c) i 0, j 0, x 0.1, n 3, 消耗 0.1`0\n    (d) i, j, x 为定义, n 0, 无消耗\n*/\n\n#include <stdio.h>\n\nint main()\n{\n    int i, j;\n    float x;\n    int n;\n\n    n = scanf(\"%d%f%d\", &i, &x, &j);\n    printf(\"n: %d\\n\", n);\n    printf(\"i: %d\\n\", i);\n    printf(\"j: %d\\n\", j);\n    printf(\"x: %f\\n\", x);\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_08.c",
    "content": "/*\n    在前面几章中，当希望跳过空白字符而读取非空白字符时，已经使用过 scanf 函数的 \" %c\" 格式串。而一些程序员用\"%1s\"来代替。这两种方法等效吗？如果不等效，区别是什么？\n*/\n\n/*\n    经过测试，是等价的。\n*/\n\n#include <stdio.h>\n\nint main()\n{\n    char c;\n\n    //scanf(\" %c\", &c);\n    scanf(\"%1s\", &c);\n    printf(\"%d\\n\", c);\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_09.c",
    "content": "/*\n    要想从标准输入流中读取一个字符，下列调用方式哪种是无效的？\n\n    (a) getch()\n    (b) getchar()\n    (c) getc(stdin)\n    (d) fgetc(stdin)\n*/\n\n/*\n    经过测试，都可以，但 getch() 不是标准库函数。 \n*/\n\n#include <stdio.h>\n\nint main(int argc, char const *argv[])\n{\n    int c;\n\n    //c = getch();\n    //c = getchar();\n    //c = getc(stdin);\n    c = fgetc(stdin);\n    printf(\"c: %c\\n\", c);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_10.md",
    "content": "程序 fcopy 有一个小缺陷：当它向目的的文件写时无法检查错误。虽然在写操作过程中错误是极少的，但是偶尔会发生（比如，磁盘可能会变满）。假设希望一旦发生错误，程序可以写出错误消息并且立刻终止，请说明如何为 fcopy.c 添加遗漏的错误检查。\n\n---\n\n使用 ferror(fp) 判断一下，是否是遇到了流错误。\n\n```c\nwhile ((ch = getc(source_fp)) != EOF) {\n    if (ferror(fp)) {\n        fprintf(stderr, \"getc error\\n\");\n        return 1;\n    }\n    putc(ch, dest_fp);\n}\n```"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_11.md",
    "content": "在程序 fcopy 中出现了下列循环：\n\n```c\nwhile ((ch = getc(source_fp)) != EOF)\n    putc(ch, dest_fp)\n```\n\n假设忽略表达式 ch = getc(source_fp) 两边的圆括号：\n\n```c\nwhile (ch = getc(source_fp) != EOF)\n    putc(ch, dest_fp)\n```\n\n程序可以无错通过编译？如果可以，那么运行时程序会做些什么呢？\n\n---\n\n可以通过编译，但是会一直输出整数1对应的字符到文件，因为运算是从右边开始，`getc(source_fp) != EOF`的结果在遇到文件尾之前一直是1。"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_12.c",
    "content": "/*\n    编写一个名为 toupper 的程序，用来把文件中的所有字母转换成大写形式。（其他非字母字符不改变。）\n    用户将采用命令行上输入文件名。\n\n    toupper test.doc\n\n    让程序 toupper 把输出写到 stdout 中。\n*/\n\n/*\n    程序名就叫 ex_12.exe\n*/\n\n#include <stdio.h>\n#include <ctype.h>\n\nint main(int argc, char const *argv[])\n{\n    int c;\n    FILE *fp;\n\n    if (argc != 2) {\n        printf(\"usage: %s <file_name>\\n\", argv[0]);\n        return 1;\n    }\n\n    fp = fopen(argv[1], \"r\");\n    if (!fp) {\n        printf(\"fopen failure\\n\");\n        return 2;\n    }\n\n    while ((c = fgetc(fp)) != EOF) {\n        putchar(toupper(c));\n    }\n\n    fclose(fp);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_13.c",
    "content": "/*\n    编写一个名为 fact 的程序，通过把任意数量的文件写到标准输出中而把这些文件一个接一个的“拼接”\n    起来，而且文件之间没有间隙。例如，下列命令将在屏幕上显示文件 f1.c f2.c f3.c ：\n\n    fcat f1.c f2.c f3.c\n\n    如果任何文件都无法打开，那么程序 fcat 应该发出错误信息。提示：因为每次只可以打开一个文件，\n    所以程序 fcat 只需要一个文件指针变量。一旦对一个文件完成操作，程序 fcat 在打开下一个文件时\n    可以使用同一个文件指针变量。\n*/\n\n/*\n    PS: 程序名就叫 ex_13.exe\n*/\n\n#include <stdio.h>\n\nint main(int argc, char const *argv[])\n{\n    int i, opened_count = 0;\n    int ch;\n    FILE *fp;\n    if (argc < 2) {\n        fprintf(stderr, \"usage: ex_13.exe <file_list>\");\n        return 1;\n    }\n\n    for (i = 1; i < argc; i++) {\n        fp = fopen(argv[i], \"r\");\n        if (!fp) continue;\n\n        while ((ch = fgetc(fp)) != EOF)\n            putchar(ch);\n\n        opened_count++;\n        fclose(fp);\n    }\n\n    if (opened_count == 0) {\n        fprintf(stderr, \"no file opened\\n\");\n        return 2;\n    }\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_14.c",
    "content": "/*\n    让下列每一个程序都通过命令行获得文件名，并把输出写到 stdout 中。\n\n    (a) 编写一个名为 cntchar 的程序，用来统计文本文件中字符的数量。\n    (b) 编写一个名为 cntword 的程序，用来统计文本文件中单词的数量（所谓“单词”指的是不含空白字符的任意序列）。\n    (c) 编写一个名为 cntline 的程序，用来统计文本文件中行的数量。\n*/\n\n/*\n    PS: 直接写成函数\n*/\n\n#include <stdio.h>\n#include <ctype.h>\n\nvoid cntchar(const char *file_name)\n{\n    FILE *fp;\n    int ch;\n    int count = 0;\n    fp = fopen(file_name, \"r\");\n\n    if (!fp) {\n        fprintf(stderr, \"fopen failure\\n\");\n        return;\n    }\n\n    while ((ch = fgetc(fp)) != EOF) {\n        count++;\n    }\n\n    printf(\"character's count: %d\\n\", count);\n\n    fclose(fp);\n}\n\nstatic int read_word(FILE *fp)\n{\n    int ch;\n\n    while (isspace((ch = fgetc(fp))));\n    if (ch == EOF) return 0;\n\n    while ((ch = fgetc(fp)) != EOF) {\n        if (isspace(ch)) {\n            break;\n        }\n    }\n\n    return 1;\n}\n\nvoid cntword(const char *file_name)\n{\n    FILE *fp;\n    int ch;\n    int count = 0;\n    fp = fopen(file_name, \"r\");\n\n    if (!fp) {\n        fprintf(stderr, \"fopen failure\\n\");\n        return;\n    }\n\n    while (read_word(fp)) {\n        count++;\n    }\n\n    printf(\"word's count: %d\\n\", count);\n\n    fclose(fp);\n}\n\n// 返回读取的行中的字符数\nstatic int read_line(FILE *fp)\n{\n    int ch;\n    ch = fgetc(fp);\n    if (ch == EOF) return 0;\n\n    while (ch != '\\n' && ch != EOF) {\n        ch = fgetc(fp);\n    }\n\n    return 1;\n}\n\nvoid cntline(const char *file_name)\n{\n    FILE *fp;\n    int ch;\n    int count = 0;\n    fp = fopen(file_name, \"r\");\n\n    if (!fp) {\n        fprintf(stderr, \"fopen failure\\n\");\n        return;\n    }\n\n    while (read_line(fp)) {\n        count++;\n    }\n\n    printf(\"line's count: %d\\n\", count);\n\n    fclose(fp);\n}\n\nint main(int argc, char const *argv[])\n{\n    if (argc != 2) {\n        printf(\"usage: %s file_name\\n\", argv[0]);\n        return 1;\n    }\n\n    cntchar(argv[1]);\n    cntword(argv[1]);\n    cntline(argv[1]);\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_15.c",
    "content": "/*\n    20.1节中的程序 xor 拒绝对原始格式或加密格式中是控制字符的字节进行加密。现在可以摆脱这种限制了。\n    修改此程序使输入文件名和输出文件名都是命令行的参数。以二进制形式打开这两个文件，并且把用来检查\n    原始字符或加密字符是否是控制字符的判断删除。\n*/\n\n#include <ctype.h>\n#include <stdio.h>\n\n#define KEY '&'\n\nint main(int argc, char const *argv[])\n{\n    int orig_char, new_char;\n    FILE *in_fp, *out_fp;\n\n    if (argc != 3) {\n        printf(\"usage: %s <input_file> <output_file>\\n\", argv[0]);\n        return 1;\n    }\n\n    in_fp = fopen(argv[1], \"rb\");\n    if (!in_fp) {\n        printf(\"fopen %s failure\\n\", argv[1]);\n        return 2;\n    }\n    out_fp = fopen(argv[2], \"wb\");\n    if (!out_fp) {\n        fclose(in_fp);\n        printf(\"fopen %s failure\\n\", argv[2]);\n        return 3;\n    }\n\n    while ((orig_char = fgetc(in_fp)) != EOF) {\n        new_char = orig_char ^ KEY;\n        fputc(new_char, out_fp);\n    }\n\n    fclose(in_fp);\n    fclose(out_fp);\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_16.c",
    "content": "/*\n    编写一个名为 hexdump 的程序，以十六进制代码序列的形式显示文件中的字节，且每行显示20个代码\n    （如下所示）。\n\n    43 68 61 ....\n\n    用户需要在命令行中指定文件名。请确保文件以\"rb\"模式打开。\n*/\n\n#include <stdio.h>\n\nint main(int argc, char const *argv[])\n{\n    FILE *fp;\n    int ch;\n    int count = 0;\n\n    if (argc != 2)  {\n        printf(\"usage: %s <file_name>\\n\", argv[0]);\n        return 1;\n    }\n\n    fp = fopen(argv[1], \"rb\");\n    if (!fp) {\n        printf(\"fopen failure\\n\");\n        return 2;\n    }\n\n    while ((ch = fgetc(fp)) != EOF) {\n        printf(\"%02x \", ch);\n        count++;\n        if (count % 20 == 0)\n            printf(\"\\n\");\n    }\n\n    fclose(fp);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_17.c",
    "content": "/*\n    在进行文件内容压缩的众多方法中，最简单快捷的方法之一是行程长度编码方式。这种方法通过一对\n    字节替换相同的字节序列来进行文件的压缩：重复计数后面跟着重复的字节。例如，假设文件以下列\n    字节序列开始进行压缩（以十六进制形式显示）：\n    46 6f 6f\n    压缩后的文件将包含下列字节：\n    01 46 02 6f\n    如果原始文件包含许多相同字节的长序列，那么行程长度编码的方法非常适用。最差的情况是行程长度编码\n    可能实际上是文件长度的两倍。\n\n    (a) 请编写名为 comp 的程序，此程序使用行程长度编码方法来压缩文件。为了运行程序 comp ,将使用\n    下列格式的命令行：\n    comp 原始文件 压缩后的文件\n    如果压缩后的文件没有扩大，那么程序 comp 将添加扩展名 .rle 。例如，命令\n    comp foo bar\n    将会使程序 comp 创建名为 bar.rle 的文件，并且把文件 foo 的压缩版本写到此文件中。（程序 comp\n    将在文件 bar.rle 开始处保存文件 foo 的名字。）提示：练习16中的程序 hexdump 可以用来调试/\n\n    (b) 请编写名为 uncomp 的程序，此程序是程序 comp 的反向操作。程序 uncomp 的命令行格式为：\n    uncomp 压缩后的文件\n    如果压缩后的文件没有扩充，那么程序 uncomp 将添加扩展名 .rle 。例如，命令\n    uncomp bar\n    将会使程序 uncomp 打开文件 bar.rle ，并且写出文件内容的未压缩版，同时把文件的名字保存在 bar.rle\n    的开始处。\n*/\n\n#include <stdio.h>\n\nvoid comp(const char *in, const char *out)\n{\n    FILE *in_fp, *out_fp, *out_fp2;\n    int ch_store, ch_cur;\n    int count_read = 0, count_write = 0;\n    unsigned char count_store;\n\n    in_fp = fopen(in, \"rb\");\n    if (!in_fp) {\n        printf(\"fopen %s failed\\n\", in);\n        return;\n    }\n\n    out_fp = fopen(out, \"wb+\");\n    if (!out_fp) {\n        fclose(in_fp);\n        printf(\"fopen %s failed\\n\", out);\n        return;\n    }\n\n    ch_cur = fgetc(in_fp);\n    ch_store = ch_cur;\n    count_store = 1;\n    count_read = 1;\n    while (ch_cur != EOF) {\n        ch_cur = fgetc(in_fp);\n        ++count_read;\n\n        if (count_store >= 255 || ch_cur != ch_store) {\n            fputc(count_store, out_fp);\n            fputc(ch_store, out_fp);\n            count_store = 1;\n            ch_store = ch_cur;\n            count_write += 2;\n        }\n        else {\n            count_store++;\n        }\n    }\n\n    if (count_read > count_write) {\n        char buffer[128];\n        snprintf(buffer, 128, \"%s.rle\", out);\n        out_fp2 = fopen(buffer, \"wb\");\n        if (out_fp2)\n        {\n            rewind(out_fp);\n            fputs(in, out_fp2);\n            while ((ch_cur = fgetc(out_fp)) != EOF) {\n                fputc(ch_cur, out_fp2);\n            }\n            fclose(out_fp2);\n        }\n    }\n\n    fclose(in_fp);\n    fclose(out_fp);\n}\n\nvoid uncomp(const char *in)\n{\n    FILE *in_fp, *tmp_fp, *out_fp;\n    int count, i;\n    int count_read, count_write;\n    int ch;\n    char buffer[128];\n    snprintf(buffer, 128, \"%s.rle\", in);\n    \n    in_fp = fopen(in, \"rb\");\n    if (!in_fp) {\n        printf(\"fopen failure\\n\");\n        return;\n    }\n\n    tmp_fp = tmpfile();\n\n    do {\n        count = fgetc(in_fp);\n        ch = fgetc(in_fp);\n        for (i = 0 ; i < count; ++i) {\n            printf(\"%02x \", ch);\n            fputc(ch, tmp_fp);\n            count_write++;\n        }\n        count_read += 2;\n    } while (ch != EOF);\n\n    if (count_write > count_read) {\n        out_fp = fopen(buffer, \"wb\");\n        if (out_fp) {\n            fputs(in, out_fp);\n            rewind(tmp_fp);\n            while ((ch = fgetc(tmp_fp)) != EOF) {\n                fputc(ch, out_fp);\n            }\n            fclose(out_fp);\n        }\n    }\n\n    fclose(in_fp);\n    fclose(tmp_fp);\n}\n\nint main(int argc, char const *argv[])\n{\n    /* a */\n    /*{\n        if (argc != 3) {\n            printf(\"usage: %s <in_file> <out_file>\\n\", argv[0]);\n            return 1;\n        }\n\n        comp(argv[1], argv[2]);\n    }*/\n\n    /* b */\n    {\n        if (argc != 2) {\n            printf(\"usage: %s <in_file>\\n\", argv[0]);\n            return 1;\n        }\n\n        uncomp(argv[1]);\n    }\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_18.c",
    "content": "/*\n    (a) 请编写自己版本的 fgets 函数，使此函数的操作尽可能和实际的 fgets 函数相同。特别是一定要\n        确保函数具有正确的返回值。为了避免和标准库发生冲突，请不要把自己编写的函数也命名为 fgets 。\n\n    (b) 请编写自己版本的 fputs 函数，规则和要求和 (a) 一样。\n*/\n\n#include <stdio.h>\n\nchar* my_fgets(char* s, int n, FILE *stream)\n{\n    int ch;\n    int count_read = 0;\n\n    if (n <= 1) {\n        s[0] = '\\0';\n        return s;\n    }\n    \n    if (ferror(stream) || feof(stream))\n            return NULL;\n\n    do {\n        ch = fgetc(stream);\n        if (ferror(stream) || feof(stream))\n            break;\n\n        s[count_read++] = ch;\n    } while (ch != '\\n' && count_read < n - 1);\n\n    s[count_read] = '\\0';\n\n    return s;\n}\n\nint my_fputs(const char *str, FILE *stream)\n{\n    if (!str || ferror(stream) || feof(stream)) return -1;\n\n    printf(\"%s\", str);\n    return 1;\n}\n\nint main(int argc, char const *argv[])\n{\n    char buffer[8];\n    while (my_fgets(buffer, 8, stdin)) {\n        my_fputs(buffer, stdout);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_19.md",
    "content": "通过添加两个新的操作方式修改16.3节中的 invent 程序。\n\n- 在指定文件中保存数据库。\n\n- 从指定文件中装载数据库。\n\n分别使用代码d（转储）和r（恢复）来表示这两种操作。与用户的交互应该按照下列显示进行：\n\nEnter operation code: d\nEnter name of output file: invent.dat\n\nEnter operator code: r\nEnter name of input file: invent.dat\n\n---\n\n见 [程序](./dir_ex_19)"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_20.c",
    "content": "/*\n    编写程序对由 invent 程序存储的含有零件记录的两个文件进行合并（见练习19）。假设每个文件中的记录都是根据零件编号进行排序的，\n    而且希望结果文件也应是排序好的。如果两个文件都拥有相同编号的零件，那么要对记录中存储的数量进行合并。（作为连贯的检查，程序\n    要比较零件的名称，并且在不匹配时显示出错信息。）程序在命令行上要包含输入文件名以及合并后的文件名。\n*/\n\n#include <stdio.h>\n#include <string.h>\n\n#define NAME_LEN 25\n#define MAX_PARTS 100\n\ntypedef struct part {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n} Part;\n\nint main(int argc, char const *argv[])\n{\n    const char* in_file1, *in_file2, *out_file;\n    FILE *in_fp1, *in_fp2, *out_fp;\n    Part in_part1[MAX_PARTS], in_part2[MAX_PARTS], out_part[MAX_PARTS];\n    int in_part1_count, in_part2_count, out_part_count;\n    int i, j;\n    Part tmp_part;\n\n    /* 获得输入的文件名和输出的文件名 */\n    if (argc != 4) {\n        printf(\"usage: %s <in_file1> <in_file2> <out_file>\\n\", argv[0]);\n        return 1;\n    }\n    in_file1 = argv[1];\n    in_file2 = argv[2];\n    out_file = argv[3];\n\n    /* 读取两个文件 */\n    in_fp1 = fopen(in_file1, \"rb\");\n    if (!in_fp1) {\n        printf(\"fopen %s failed\\n\", in_file1);\n        return 2;\n    }\n\n    in_fp2 = fopen(in_file2, \"rb\");\n    if (!in_fp2) {\n        fclose(in_fp1);\n        printf(\"fopen %s failed\\n\", in_file2);\n        return 2;\n    }\n\n    /* 读取文件的内容到数组，关闭文件 */\n    in_part1_count = fread(in_part1, sizeof(in_part1[0]), MAX_PARTS, in_fp1);\n    in_part2_count = fread(in_part2, sizeof(in_part2[0]), MAX_PARTS, in_fp2);\n\n    fclose(in_fp1);\n    fclose(in_fp2);\n\n    /* 进行合并数组，从小到大排序 */\n    out_part_count = 0;\n    i = 0;\n    j = 0;\n    while ((i < in_part1_count || j < in_part2_count) && out_part_count < MAX_PARTS) {\n        if (i == in_part1_count) {\n            tmp_part = in_part2[j++];\n        }\n        else if (j == in_part2_count)  {\n            tmp_part = in_part1[i++];\n        }\n        else if (in_part1[i].number < in_part2[j].number) {\n            tmp_part = in_part1[i++];\n        }\n        else if (in_part1[i].number > in_part2[j].number) {\n            tmp_part = in_part2[j++];\n        }\n        else if (in_part1[i].number == in_part2[j].number) {\n            if (strcmp(in_part1[i].name, in_part2[j].name) == 0) {\n                tmp_part = in_part1[i];\n                tmp_part.on_hand += in_part2[j].on_hand;\n                i++;\n                j++;\n            }\n            else {\n                printf(\"part1[%d %s] not compare with part2[%d %s], ignored merge!\\n\", \n                    in_part1[i].number, in_part1[i].name,\n                    in_part2[j].number, in_part2[j].name);\n                i++;\n                j++;\n                continue;\n            }\n        }\n\n        out_part[out_part_count++] = tmp_part;\n    }\n\n    if (out_part_count <= 0) {\n        printf(\"no part to merge\\n\");\n        return 2;\n    }\n    \n    /* 输出到文件，关闭输出文件 */\n    out_fp = fopen(out_file, \"wb\");\n    if (!out_fp) {\n        printf(\"fopen %s failed\\n\", out_file);\n        return 3;\n    }\n\n    fwrite(out_part, sizeof(out_part[0]), out_part_count, out_fp);\n\n    fclose(out_fp);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_21.md",
    "content": "修改17.5节中的程序 invent2 ,方法是添加练习19中描述的d（转储）操作和r（恢复）操作。因为零件的结构不存储在数组中，所以d操作无法通过单独一个 fwrite 调用来保存所有内容。因而，它需要访问链表中的每个节点，保存零件编号、名称以及文件中现有零件的数量。（不保存指针 next ，因为一旦程序终止，此指针将不会有效。）当程序从文件中读取零件时，r操作将每次一个节点地重新构建链表。\n\n见[程序](./dir_ex_21)"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_22.c",
    "content": "/*\n    编写 fseek 函数的调用来在二进制文件中执行下列文件定位操作，其中，二进制文件的数据以64字节\n    “记录”的形式进行排列。采用 fp 作为下列每种情况中的文件指针。\n    (a) 移动到记录n的开始处（假设文件中的首记录记为0）。\n    (b) 移动到文件中最后一条记录的开始处。\n    (c) 向前移动一条记录。\n    (d) 向后移动两条记录。\n*/\n\n#include <stdio.h>\n\n#define LEN 5\n\nint main(int argc, char const *argv[])\n{\n    // 准备浮点值的数组\n    double A[LEN] = {1., 2., 3., 4., 5.};\n    double b;\n\n    // 写入数组到文件\n    FILE *fp = fopen(\"example.dat\", \"wb\");\n    fwrite(A, sizeof(double), LEN, fp);\n    fclose(fp);\n\n    // 读数组中第浮点数入\n    fp = fopen(\"example.dat\", \"rb\");\n    \n    /* a */\n    fseek(fp, 0, SEEK_SET);\n    fread(&b, sizeof(b), 1, fp);\n    printf(\"a: %f, record: %d\\n\", b, ftell(fp) / sizeof(double));\n\n    /* b */\n    fseek(fp, -1 * sizeof(double), SEEK_END);\n    fread(&b, sizeof(b), 1, fp);\n    printf(\"b: %f, record: %d\\n\", b, ftell(fp) / sizeof(double));\n\n    /* c */\n    fseek(fp, -1 * sizeof(double), SEEK_CUR);\n    fread(&b, sizeof(b), 1, fp);\n    printf(\"c: %f, record: %d\\n\", b, ftell(fp) / sizeof(double));\n\n    /* d */\n    rewind(fp);\n    fseek(fp, 2 * sizeof(double), SEEK_CUR);\n    fread(&b, sizeof(b), 1, fp);\n    printf(\"d: %f, record: %d\\n\", b, ftell(fp) / sizeof(double));\n\n    fclose(fp);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/ex_23.c",
    "content": "/*\n    编写一个名为 dispdate 的程序，用来从命令行读取数据，并且按照下列格式显示出来：\n\n    September 13, 1995\n\n    允许用户以9-13-95或者9/13/95的形式录入日期，并假设日期中没有空格。如果没有按照指定格式\n    录入日期，那么程序显示出错信息。提示：使用 sscanf 函数从命令行参数中截取出年、月、日。\n*/\n\n#include <stdio.h>\n\nint main(int argc, char const *argv[])\n{\n    int year, month, day;\n    const char* month_str[12] = {\n        \"January\",\n        \"February\",\n        \"March\",\n        \"April\",\n        \"May\",\n        \"June\",\n        \"July\",\n        \"August\",\n        \"September\",\n        \"October\",\n        \"November\",\n        \"December\"\n    };\n\n    if (argc < 2) {\n        printf(\"usage: %s <date>\\n\", argv[0]);\n        printf(\"example: %s 9-13-95\\n\");\n        return 1;\n    }\n\n    if (sscanf(argv[1], \"%d-%d-%d\", &month, &day, &year) == 3) {\n    }\n    else if (sscanf(argv[1], \"%d/%d/%d\", &month, &day, &year) == 3) {\n    }\n    else {\n        printf(\"format error, example: 9-13-95\\n\");\n        return 2;\n    }\n\n    printf(\"%s %d, %d\\n\", month_str[month - 1], day, year + 1900);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/fcopy.c",
    "content": "/* Copies a file */\n\n#include <stdio.h>\n#include <stdlib.h>\n\nint main(int argc, char const *argv[])\n{\n    FILE *source_fp, *dest_fp;\n    int ch;\n\n    if (argc != 3) {\n        fprintf(stderr, \"usage: fcopy source dest\\n\");\n        exit(EXIT_FAILURE);\n    }\n\n    if ((source_fp = fopen(argv[1], \"rb\")) == NULL) {\n        fprintf(stderr, \"Can't open %s\\n\", argv[1]);\n        exit(EXIT_FAILURE);\n    }\n\n    if ((dest_fp = fopen(argv[2], \"wb\")) == NULL) {\n        fprintf(stderr, \"Can't open %s\\n\", argv[2]);\n        fclose(source_fp);\n        exit(EXIT_FAILURE);\n    }\n\n    while ((ch = getc(source_fp)) != EOF) {\n        putc(ch, dest_fp);\n        /*printf(\"out: %c:%d\\n\", ch, ch);*/\n    }\n\n    fclose(source_fp);\n    fclose(dest_fp);\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/invclear.c",
    "content": "/* Modifies a file of part records by setting the quantity on hand to zero */\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#define NAME_LEN 25\n#define MAX_PARTS 100\n\nstruct part {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n} inventory[MAX_PARTS];\n\nint num_parts;\n\nint main()\n{\n    FILE *fp;\n    int i;\n\n    if ((fp = fopen(\"invent.dat\", \"rb+\")) == NULL) {\n        fprintf(stderr, \"Can't open inventory file\\n\");\n        return 1;\n    }\n\n    num_parts = fread(inventory, sizeof(struct part), MAX_PARTS, fp);\n    for (i = 0; i < num_parts; i++) {\n        inventory[i].on_hand = 0;\n    }\n\n    rewind(fp);\n    fwrite(inventory, sizeof(struct part), num_parts, fp);\n    fclose(fp);\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/invent",
    "content": "\u0005a\u0003b\u0001c\u0001d"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/invent.rle",
    "content": "inventaaaaabbbcd"
  },
  {
    "path": "codes/CProgramming/ch22_输入_输出/test_enter.c",
    "content": "/*\n    测试标准输入中的回车键\n\n    默认情况下，在敲击回车后，才会把输入流送给程序处理\n*/\n\n#include <stdio.h>\n\nint main(int argc, char* argv[])\n{\n    // getchar，会读取换行符\n    {\n        printf(\"Test getchar()\\n\");\n        int ch;\n        ch = getchar();\n        printf(\"read: 0x%02x\\t%d\\n\", ch, ch);\n    }\n    \n    // fgets，只有数组足够，才存放换行符\n    {\n        printf(\"\\nTest fgets()\\n\");\n        char buffer[4];\n        fgets(buffer, 4, stdin);\n        char* p = buffer;\n        while (*p) {\n            printf(\"read: 0x%02x\\t%d\\n\", *p, *p);\n            p++;\n        }\n    }\n\n    // scanf，读取字符串时，遇到空白即停止扫描，因此不会读取换行符（以及其他空白字符）\n    {\n        printf(\"\\nTest scanf()\\n\");\n        char buffer[4];\n        scanf(\"%s\", buffer);\n        char *p = buffer;\n        while (*p) {\n            printf(\"read: 0x%02x\\t%d\\n\", *p, *p);\n            p++;\n        }\n    }\n    \n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc $c_file -o $exe_file -lm\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_01.c",
    "content": "/*\n    编写一个程序，使用下面的公式求方程 ... 的根 ...\n    程序需要提示a, b, c的值，然后显示出x的两个解。...\n*/\n\n#include <stdio.h>\n#include <math.h>\n\nint main()\n{\n    double a, b, c;\n    double tmp;\n\n    printf(\"Enter a b c: \");\n    scanf(\"%lf%lf%lf\", &a, &b, &c);\n\n    tmp = pow(b, 2) - 4 * a * c;\n    if (tmp < 0) {\n        printf(\"imaginary number!\\n\");\n        return 1;\n    }\n\n    printf(\"res1: %f\\n\", (-b + sqrt(tmp)) / (2 * a));\n    printf(\"res2: %f\\n\", (-b - sqrt(tmp)) / (2 * a));\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_02.c",
    "content": "/*\n    扩展 round 函数，使它可以将x舍入成小数点后n位。例如，调用 round(3.14159, 3) 会返回3.142。\n    提示：将x乘以10n，舍入成最近的整数，再除以10n。确保你的函数对正数x和负数x都可以正常工作。\n*/\n\n#include <stdio.h>\n#include <math.h>\n\ndouble my_round(double x, int n)\n{\n    x = x * pow(10, n);\n    x = round(x);\n    x = x / pow(10, n);\n    return x;\n}\n\nint main()\n{\n    printf(\"round(3.14159, 3): %g\\n\", my_round(3.14159, 3));\n    printf(\"round(-3.14159, 3): %g\\n\", my_round(-3.14159, 3));\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_03.c",
    "content": "/*\n    使用 isalpha 函数和 isalnum 函数编写一个函数，用来检查一个字符串是否符合C语言标识符语法\n    （也就是说，它由字母、数字和下划线组成，并以字母或下划线开始）。\n*/\n\n#include <ctype.h>\n#include <stdio.h>\n\nint is_correct_identifier(const char *identifier)\n{\n    if (*identifier != '_' && !isalpha(*identifier)) {\n        return 0;\n    }\n\n    while (*(++identifier) != '\\0') {\n        if (*identifier != '_' && !isalnum(*identifier)) {\n            return 0;\n        }\n    }\n\n    return 1;\n}\n\nint main()\n{\n    char identifier[128];\n    printf(\"Enter a identifier: \");\n    scanf(\"%s\", identifier);\n\n    printf(\"%s\\n\", is_correct_identifier(identifier) ? \"correct\" : \"not correct\");\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_04.c",
    "content": "/*\n    编写一个程序，将文件从标准输入复制到标准输出，删除所有空行（仅包含空白字符的行）。\n*/\n\n#include <stdio.h>\n#include <string.h>\n#include <ctype.h>\n\nint is_blank_line(const char *line)\n{\n    if (line[strlen(line) - 1] != '\\n')\n        return 0;\n\n    while (*line != '\\0') {\n        if (!isspace(*line))\n            return 0;\n\n        line++;\n    }\n    return 1;\n}\n\nint main()\n{\n    char buffer[32];\n\n    while (fgets(buffer, sizeof(buffer), stdin)) {\n        if (is_blank_line(buffer))\n            continue;\n        fputs(buffer, stdout);\n    }\n  \n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_05.c",
    "content": "/*\n    编写一个程序，将文件从标准输入复制到标准输出，将每个单词的首字母大写。\n*/\n\n#include <stdio.h>\n#include <ctype.h>\n\nint main()\n{\n    int ch;\n    int need_check;\n\n    need_check = 1;\n    while ((ch = fgetc(stdin)) != EOF) {\n        if (!isalpha(ch) && ch != '_')\n            need_check = 1;\n\n        if (need_check && isalpha(ch)) {\n            ch = toupper(ch);\n            need_check = 0;\n        }\n\n        fputc(ch, stdout);\n    }\n  \n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_06.c",
    "content": "/*\n    对于下面列举的每种情况，指出使用 memcpy, memmove, strcpy 和 strncpy 中哪一个函数最好。\n    假定所列举的行为都是由一个函数调用完成的。\n\n    (a) 将数组中的每个元素都下移一个位置，以便将第0个位置空出给新的元素。\n    (b) 通过将后面的所有字符都前移一个字节来删除以空字符结尾的字符串中的第一个字符。\n    (c) 将一个字符串复制到一个字符数组中，这个字符数组的大小可能不够存放整个字符串。如果数组\n        太小，就将字符串截断，而且在字符数组的末尾不需要空字符作为结尾。\n    (d) 将一个数组变量的内容复制到另一个数组变量中。\n*/\n\n/*\n    a b: memmove\n    c: strncpy\n    d: strcpy\n*/\n\n#include <string.h>\n#include <stdio.h>\n\nvoid a()\n{\n    char str[7] = \"Hello\";\n\n    memmove(str + 1, str, 5);\n    str[0] = 'X';\n\n    printf(\"%s\\n\", str);\n}\n\nvoid b()\n{\n    char str[] = \"Hello\";\n\n    memmove(str, str + 1, 5);\n\n    printf(\"%s\\n\", str);\n}\n\nvoid c()\n{\n    char str1[] = \"Hello\";\n    char str2[3];\n    int i;\n\n    strncpy(str2, str1, sizeof(str2));\n\n    for (i = 0; i < 3; i++) {\n        printf(\"%c\", str2[i]);\n    }\n    printf(\"\\n\");\n}\n\nvoid d()\n{\n    char str1[] = \"Hello\";\n    char str2[6];\n\n    strcpy(str2, str1);\n\n    printf(\"%s\\n\", str2);\n}\n\nint main()\n{\n    a();\n    b();\n    c();\n    d();\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_07.c",
    "content": "/*\n    在23.5节中阐述了如何反复调用 strchr 函数在字符串中找到所有出现的指定字符。那么能否通过反复\n    调用 strrchr 函数反向找到所有出现的指定字符呢？\n*/\n\n/*\n    可以。\n*/\n\n#include <stdio.h>\n#include <string.h>\n\nint main()\n{\n    char str[] = \"hello world, opps\";\n    char *p;\n    char ch;\n\n    printf(\"str is: %s\\n\", str);\n    printf(\"Enter character you want to find: \");\n    scanf(\" %c\", &ch);\n\n    printf(\"find pos:\");\n    do {\n        p = strrchr(str, ch);\n        if (!p) break;\n\n        printf(\" %d\", p - str);\n        str[p - str] = '\\0';\n\n    } while (1);\n    printf(\"\\n\");\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_08.c",
    "content": "/*\n    使用 strchr 函数编写如下函数:\n    int numchar(const char *s, char ch);\n    函数 numchar 返回字符 ch 在字符串中出现的次数。\n*/\n\n#include <stdio.h>\n#include <string.h>\n\nint numchar(const char *s, char ch)\n{\n    int num = 0;\n    const char *p;\n\n    p = strchr(s, ch);\n    while (p != NULL) {\n        ++num;\n        p = strchr(p + 1, ch);\n    }\n\n    return num;\n}\n\nint main()\n{\n    const char *str = \"hello world\";\n    char ch;\n\n    printf(\"str is: %s\\n\", str);\n    printf(\"Enter character you want to find: \");\n    scanf(\" %c\", &ch);\n\n    printf(\"number of %c is: %d\\n\", ch, numchar(str, ch));\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_09.md",
    "content": "使用一个 strchr 函数调用来替换下面 if 语句中的测试条件：\n\n```c\nif (ch == 'a' || ch == 'b' || ch == 'c') ...\n```\n\n---\n\n```c\nif (strchr(s, 'a') || strchr(s, 'b') || strchr(s, 'b')) ...\n```\n\nPS: 这题是什么意思，没有看明白。"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_10.c",
    "content": "/*\n    使用一个 strstr 函数调用来替换下面 if 语句中的测试条件：\n\n    if (strcmp(str, \"foo\") == 0 || strcmp(str, \"bar\") == 0 ||\n        strcmp(str, \"baz\") == 0) ...\n\n    提示：将字符串子面量合并到一个字符串中，并使用一个特殊的字符分割它们。你的解决方案是否对\n    str 的内容有所依赖呢？\n*/\n\n/*\n    有所依赖， str 不可以使用分割的字符。\n*/\n\n#include <stdio.h>\n#include <string.h>\n\nvoid old()\n{\n    char str[128];\n    printf(\"Enter str: \");\n    scanf(\"%s\", str);\n\n    if (strcmp(str, \"foo\") == 0 || strcmp(str, \"bar\") == 0 ||\n        strcmp(str, \"baz\") == 0) {\n        printf(\"yes, compare\\n\");\n    }\n    else {\n        printf(\"not compare\\n\");\n    }\n}\n\nvoid new()\n{\n    char str[128];\n    printf(\"Enter str: \");\n    scanf(\"%s\", str);\n    const char *compare = \"foo:bar:baz\";\n\n    if (strstr(compare, str) != NULL) {\n        printf(\"yes, compare\\n\");\n    }\n    else {\n        printf(\"not compare\\n\");\n    }\n}\n\nint main()\n{\n    new();\n    \n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_11.c",
    "content": "/*\n    编写一个程序，提示用户输入一系列单词。然后按相反的顺序显示出来。将输入按字符串的形式读入，\n    然后使用 strtok 函数将它们重新分割成单词。\n*/\n\n#include <stdio.h>\n#include <string.h>\n\nint main()\n{\n    char buffer[1024];\n    int stack_count = 0;\n    const char *stack[1024];\n    char *token;\n\n    printf(\"Enter words: \");\n    fgets(buffer, sizeof(buffer), stdin);\n    if (buffer[strlen(buffer) - 1] == '\\n')\n        buffer[strlen(buffer) - 1] = '\\0';\n\n    token = strtok(buffer, \" \");\n\twhile (token != NULL) {\n        stack[stack_count++] = token;\n\t\ttoken = strtok(NULL, \" \");\n\t}\n\n    printf(\"reserve: \");\n    while (stack_count > 0) {\n        printf(\"%s \", stack[--stack_count]);\n    }\n    printf(\"\\n\");\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_12.c",
    "content": "/*\n    编写一个 memset 函数的调用，将一个空字符结尾的字符串s的最后n个字符替换为'!'字符。\n*/\n\n#include <stdio.h>\n#include <string.h>\n\nint main()\n{\n    char str[] = \"hello world\";\n    int n;\n\n    printf(\"str is: %s\\n\", str);\n    printf(\"Enter n: \");\n    scanf(\"%d\", &n);\n\n    if (n <= 0 || n > strlen(str)) {\n        printf(\"n's valid range: [%d, %d]\\n\", 1, strlen(str));\n        return 1;\n    }\n\n    memset(str + strlen(str) - n, '!', n);\n    printf(\"str now is: %s\\n\", str);\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/ex_13.c",
    "content": "/*\n    许多<string.h>的版本提供了额外的（非标准的）函数，例如下面列出的一些函数。使用标准C的特性\n    给出每一个函数的实现。\n\n    (a) strdup(s) 返回一个指针，该指针指向通过调用 malloc 函数获得的内存中保存的s的一个副本。\n        如果没有足够的内存可以分配。则返回空指针。\n    (b) stricmp(s1, s2) 与 strcmp 函数类似，但不考虑字母的大小写。\n    (c) strlwr(s) 将s中的大写字母转换为小写字母，其他字符不变；返回s。\n    (d) strrev(s) 反转字符从s中的字符顺序（除了空字符）；返回s。\n    (e) strset(s, ch) 将s用 ch 的副本填充；返回s。\n*/\n\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n\nchar* strdup(const char *s)\n{\n    char *p = malloc(sizeof(*s) * (strlen(s) + 1));\n    if (p == NULL)\n        return NULL;\n\n    strcpy(p, s);\n    return p;\n}\n\nvoid test_strdup()\n{\n    const char *str = \"Hello world\";\n    char *p = strdup(str);\n    if (p) {\n        printf(\"dup str: %s\\n\", p);\n        free(p);\n    }\n}\n\nint stricmp(const char *s1, const char *s2)\n{\n    while (*s1 != '\\0' && *s2 != '\\0') {\n        if (toupper(*s1) > toupper(*s2))\n            return 1;\n        else if (toupper(*s1) < toupper(*s2))\n            return -1;\n        s1++;\n        s2++;\n    }\n\n    if (*s1 != '\\0' && *s2 == '\\0')\n        return 1;\n    else if (*s1 == '\\0' && *s2 != '\\0')\n        return -1;\n    else\n        return 0;\n}\n\nvoid test_stricmp()\n{\n    char str1[128];\n    char str2[128];\n    int res;\n\n    printf(\"Enter str1: \");\n    scanf(\"%s\", str1);\n    printf(\"Enter str2: \");\n    scanf(\"%s\", str2);\n\n    res = stricmp(str1, str2);\n    if (res > 0)\n        printf(\"%s > %s\\n\", str1, str2);\n    else if (res < 0)\n        printf(\"%s < %s\\n\", str1, str2);\n    else\n        printf(\"%s == %s\\n\", str1, str2);\n}\n\nchar* strlwr(char *s)\n{\n    char *ret = s;\n    while (*s != '\\0') {\n        if (isupper(*s)) {\n            *s = tolower(*s);\n        }\n        s++;\n    }\n    return ret;\n}\n\nvoid test_strlwr()\n{\n    char str[128];\n\n    printf(\"Enter str: \");\n    scanf(\"%s\", str);\n\n    printf(\"to lower: %s\\n\", strlwr(str));\n}\n\nchar* strrev(char *s)\n{\n    char *p = s;\n    char *q = s + strlen(s) - 1;\n    char tmp;\n\n    while (p != q) {\n        tmp = *p;\n        *p = *q;\n        *q = tmp;\n\n        p++;\n        q--;\n    }\n\n    return s;\n}\n\nvoid test_strrev()\n{\n    char str[128];\n\n    printf(\"Enter str: \");\n    scanf(\"%s\", str);\n\n    printf(\"reverse: %s\\n\", strrev(str));\n}\n\nchar* strset(char *s, char ch)\n{\n    char *p = s;\n    while (*p != '\\0') {\n        *p = ch;\n        p++;\n    }\n    return s;\n}\n\nvoid test_strset()\n{\n    char str[128];\n\n    printf(\"Enter str: \");\n    scanf(\"%s\", str);\n\n    printf(\"after set: %s\\n\", strset(str, '*'));\n}\n\nint main()\n{\n    //test_strdup();\n    //test_stricmp();\n    //test_strlwr();\n    //test_strrev();\n    test_strset();\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch23_库对数值和字符数据的支持/tcasemap.c",
    "content": "/*\n    Tests the case-mapping functions\n*/\n\n#include <stdio.h>\n#include <ctype.h>\n\nint main(int argc, char const *argv[])\n{\n    char *p;\n    for (p = \"aA0!\"; *p != '\\0'; p++) {\n        printf(\"tolower('%c') is '%c'; \", *p, tolower(*p));\n        printf(\"toupper('%c') is '%c'\\n\", *p, toupper(*p));\n    }\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch24_错误处理/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc $c_file -o $exe_file -lm\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch24_错误处理/dir_ex_04/invent.c",
    "content": "/*\n    invent.c\n    Maintains a parts database (array version)\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <setjmp.h>\n#include \"readline.h\"\n\n#define NAME_LEN 25\n#define MAX_PARTS 100\n\njmp_buf jmp_point;\n\nstruct part {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n} inventory[MAX_PARTS];\n\nint num_parts = 0;      /* number of parts currently stored */\n\nint find_part(int number);\nvoid insert(void);\nvoid search(void);\nvoid update(void);\nvoid print(void);\n\n/*\n    main: Prompts the user to enter an operator code,\n        then calls a function to perform the requested\n        action. Repeats until the user enters the\n        command 'q'. Prints an error message if th user\n        enters an illegal code.\n*/\nint main(int argc, char const *argv[])\n{\n    char code;\n\n    {\n        if (setjmp(jmp_point) != 0) {\n            printf(\"\\n\");\n        }\n\n        printf(\"Enter operation code: \");\n        scanf(\" %c\", &code);\n        while (getchar() != '\\n'); /* skips to end of line */\n\n        switch(code) {\n            case 'i': insert(); break;\n            case 's': search(); break;\n            case 'u': update(); break;\n            case 'p': print(); break;\n            case 'q': printf(\"bye\\n\"); return 0;\n            default: printf(\"Illegal code\\n\"); break;\n        }\n    }\n\n    return 0;\n}\n\n/*\n    find_part: Looks up a part number in the inventory\n            array. Returns the array index if the part\n            number is found; otherwise, return -1.\n*/\nint find_part(int number)\n{\n    int i;\n    for (i = 0; i < num_parts; i++) {\n        if (inventory[i].number == number)\n            return i;\n    }\n    return -1;\n}\n\n/*\n    insert: Prompts the user for information about a new\n            part and then inserts the part into the\n            database. Prints an error message and returns\n            prematurely if the part already exists or the\n            database if full.\n*/\nvoid insert(void)\n{\n    int part_number;\n\n    if (num_parts == MAX_PARTS) {\n        printf(\"Database is full; cant't add more parts.\\n\");\n        return;\n    }\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &part_number);\n    if (find_part(part_number) >= 0) {\n        printf(\"Part already exists.\\n\");\n        return;\n    }\n\n    inventory[num_parts].number = part_number;\n    \n    printf(\"Enter part name: \");\n    read_line(inventory[num_parts].name, NAME_LEN);\n    \n    printf(\"Enter quantity on hand: \");\n    scanf(\"%d\", &inventory[num_parts].on_hand);\n    \n    num_parts++;\n\n    longjmp(jmp_point, 1);\n}\n\n/*\n    search: Prompts the user to enter a part number, then\n            looks up the part in the database. If the part\n            exists, prints the name and quantity on hand;\n            if not, prints an error message.\n*/\nvoid search(void)\n{\n    int i, number;\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    i = find_part(number);\n    if (i >= 0) {\n        printf(\"Part name: %s\\n\", inventory[i].name);\n        printf(\"Quantity on hand: %d\\n\", inventory[i].on_hand);\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n\n    longjmp(jmp_point, 1);\n}\n\n/*\n    update: Prompts the user to enter a part number.\n            Prints an error message if the part doesn't\n            exist; otherwise, prompts the user to enter\n            change in quantity on hand and updates the\n            database.\n*/\nvoid update(void)\n{\n    int i, number, change;\n\n    printf(\"Enter part number: \");\n    scanf(\"%d\", &number);\n    \n    i = find_part(number);\n    if (i >= 0) {\n        printf(\"Enter change in quantity on hand: \");\n        scanf(\"%d\", &change);\n        inventory[i].on_hand += change;\n    }\n    else {\n        printf(\"Part not found.\\n\");\n    }\n\n    longjmp(jmp_point, 1);\n}\n\n/*\n    print: Prints a listing of all parts in the database,\n            showing the part number, part name, and\n            quantity on hand. Parts are printed in the\n            order in which they were entered into the\n            database.\n*/\nvoid print(void)\n{\n    int i;\n    printf(\"Part number    Part Name          Quantity on Hand\\n\");\n    for (i = 0; i < num_parts; i++) {\n        printf(\"%7d     %-25s%11d\\n\", inventory[i].number, inventory[i].name, inventory[i].on_hand);\n    }\n\n    longjmp(jmp_point, 1);\n}\n\nint compare_parts(const void *p, const void *q)\n{\n    return ((struct part*)q)->number - ((struct part*)p)->number;\n}"
  },
  {
    "path": "codes/CProgramming/ch24_错误处理/dir_ex_04/makefile",
    "content": "CC=gcc\n\nprogram: invent.o readline.o\n\t$(CC) -g -Wall -o program invent.o readline.o\n\ninvent.o: invent.c readline.h\nreadline.o: readline.c readline.h\n\nclean:\n\t@rm -f *.o *.exe programi"
  },
  {
    "path": "codes/CProgramming/ch24_错误处理/dir_ex_04/readline.c",
    "content": "#include <ctype.h>\n#include <stdio.h>\n#include \"readline.h\"\n\nint read_line(char str[], int n)\n{\n    int ch, i = 0;\n\n    while (isspace(ch = getchar()));\n\n    while (1) {            \n        if (ch == '\\n' || ch == EOF) break;\n\n        if (i < n) {\n            str[i++] = ch;\n        }\n        ch = getchar();\n    }\n\n    str[i] = '\\0';\n\n    return i;\n}"
  },
  {
    "path": "codes/CProgramming/ch24_错误处理/dir_ex_04/readline.h",
    "content": "#ifndef READLINE_H\n#define READLINE_H\n\n/*\n    read_line: Skips leading white-space characters, then\n                reads the remainder of the input line and\n                stores it in str. Truncates the line if its\n                length exceeds n. Returns the number of\n                characters stored.\n*/\nint read_line(char str[], int n);\n\n#endif // READLINE_H"
  },
  {
    "path": "codes/CProgramming/ch24_错误处理/ex_01.md",
    "content": "(a) 断言可以用来检测两种问题：(1) 如果程序正确执行就不应该发生的问题；(2) 超出程序控制范围之外的问题。请解释为什么 assert 更适用于第一类问题？\n\n(b) 请举出3个超出程序控制范围之外的问题的例子。\n\n---\n\n(a) assert 是一种同步执行的命令，没有办法处理超出程序控制之外的问题。\n\n(b) 比如运行时错误，例如除以0；或者程序收到了终端中断信号；或者电脑断电。"
  },
  {
    "path": "codes/CProgramming/ch24_错误处理/ex_02.c",
    "content": "/*\n    (a) 编写一个名为 try_math_fnc 的“包装”函数，用来调用数学函数（假定有一个 double 类型的\n        参数，并返回一个 double 类型的值），然后检测调用是否成功。下面是使用 try_math_fnc 函数的\n        例子：\n\n        y = try_math_fnc(sqrt, x, \"Error in call of sqrt\");\n\n        如果调用 sqrt(x) 成功， try_math_fnc 返回 sqrt 函数计算的结果。如果调用失败， try_math_fnc\n        需要调用 perror 显示消息 Error in call of sqrt ，然后调用 exit 函数终止程序。\n\n    (b) 编写一个与 try_math_fnc 具有相同的效果的宏，但是要求使用函数的名字来构造出错消息：\n\n        y = TRY_MATH_FNC(sqrt, x);\n\n        如果调用 sqrt 失败，显示的出错消息应该是 \"Error in call of sqrt\"。提示：让 TRY_MATH_FNC\n        调用 try_math_fnc 。\n*/\n\n#include <math.h>\n#include <stdio.h>\n#include <errno.h>\n#include <stdlib.h>\n\n#define TRY_MATH_FNC(func, param)\\\n    try_math_fnc((func), (param), \"Error in call of \" #func)\n\ntypedef double (*pFnc)(double);\n\ndouble try_math_fnc(pFnc func, double param, const char* error_desc)\n{\n    double res = func(param);\n    if (errno != 0) {\n        printf(\"%s\\n\", error_desc);\n        exit(1);\n    }\n\n    return res;\n}\n\nint main()\n{\n    double d;\n    printf(\"Enter d: \");\n    scanf(\"%lf\", &d);\n\n    //printf(\"%f\\n\", try_math_fnc(sqrt, d, \"Error in call of sqrt\"));\n    printf(\"%f\\n\", TRY_MATH_FNC(sqrt, d));\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch24_错误处理/ex_03.c",
    "content": "/*\n    给 SIGINT 编写一个信号处理函数，用来记录它被调用了多少次。要求处理函数必须忽略前两次发生\n    的信号，并在第三次发生时终止程序（通过调用 exit ）。\n*/\n\n#include <signal.h>\n#include <stdio.h>\n#include <stdlib.h>\n\nvoid handle_sigint(int sig)\n{\n    static int call_times = 0;\n\n    call_times++;\n    printf(\"catch sigint %d times\\n\", call_times);\n\n    if (call_times >= 3) {\n        exit(1);\n    }\n}\n\nint main()\n{\n    signal(SIGINT, handle_sigint);\n    while (1) {}\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch24_错误处理/ex_04.md",
    "content": "在 invent 程序中（16.3节）， main 函数中用一个 for 循环来提示用户输入一个操作代码，读入代码，然后根据代码调用 insert, search, update, print 。以这种方法在 main 函数中加入一个 setjmp 调用，要求使随后的 longjmp 调用会返回到 for 循环。（在调用 longjmp 函数后，用户会被提示输入一个操作码，随后程序正常执行。） setjmp 宏需要一个 jmp_buf 类型的变量，这个变量应该在哪里声明呢？\n\n---\n\n见[程序](./dir_ex_04)\n\njmp_buf 应该声明到全局空间。"
  },
  {
    "path": "codes/CProgramming/ch24_错误处理/tsetjmp.c",
    "content": "/*\n    #include <setjmp.h>\n    int setjmp(jmp_buf env);\n    void longjmo(jmp_buf env, int val);\n\n    setjmp 标记程序中的一个位置，保存位置信息到 env ，随后使用 longjmp ，并利用此 env 跳转到该位置，\n    随后再次调用 setjmp。\n    setjmp 在第一次调用的时候（标记的时候），返回。后随后被跳转的时候，返回的是 val 。\n\n    书中介绍，此函数主要用于错误的调试。\n*/\n\n#include <setjmp.h>\n#include <stdio.h>\n\nstatic jmp_buf env;\n\nvoid f1();\nvoid f2();\n\nint main()\n{\n    int ret;\n\n    ret = setjmp(env);\n    printf(\"setjmp returned %d\\n\", ret);\n    if (ret != 0) {\n        printf(\"Program terminates: longjmp called\\n\");\n        return 0;\n    }\n    f1();\n\n    /* Never reach here */\n    printf(\"Program terminates normally\\n\");\n\n    return 0;\n}\n\nvoid f1()\n{\n    printf(\"f1 begins\\n\");\n    f2();\n    printf(\"f1 returns\\n\"); /* Never reach here */\n}\n\nvoid f2()\n{\n    printf(\"f2 begins\\n\");\n    longjmp(env, 1);\n    printf(\"f2 returns\\n\"); /* Never reach here */\n}"
  },
  {
    "path": "codes/CProgramming/ch24_错误处理/tsignal.c",
    "content": "/* Tests signals */\n\n#include <signal.h>\n#include <stdio.h>\n\nvoid handler(int sig);\nvoid raise_sig(void);\n\nint main()\n{\n    void (*orig_handler)(int);\n\n    printf(\"Installing handler for signal %d\\n\", SIGILL);\n    orig_handler = signal(SIGILL, handler);\n    raise_sig();\n\n    printf(\"Changing handler to SIG_IGN\\n\");\n    signal(SIGILL, SIG_IGN);\n    raise_sig();\n\n    printf(\"Restoring original handler\\n\");\n    signal(SIGILL, orig_handler);\n    raise_sig();\n\n    /* Never reach here */\n    printf(\"Program terminates normally\\n\");\n\n    return 0;\n}\n\nvoid handler(int sig)\n{\n    printf(\"Handler called for signal %d\\n\", sig);\n}\n\nvoid raise_sig(void)\n{\n    raise(SIGILL);\n}"
  },
  {
    "path": "codes/CProgramming/ch25_国际化特性/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc $c_file -o $exe_file -lm\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch25_国际化特性/ex_01.c",
    "content": "/*\n * 请确定你用的编译器支持哪种地区。\n */\n\n/*\n * 至少是支持简体中文（中国）的。\n */\n\n#include <stdio.h>\n#include <locale.h>\n#include <wchar.h>\n\nvoid test1()\n{\n\tsetlocale(LC_ALL, \"zh_CN.UTF-8\");\n\n\twprintf(L\"你好啊，中国\\n\");\n}\n\nint main()\n{\n\ttest1();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch25_国际化特性/ex_02.c",
    "content": "/*\n * 编写一个程序，用来测试你用的编译器的\"\"（本地）地区是否和\"C\"地区一样。\n */\n\n/*\n * 不一样，\"\"是 en_US.utf8 ，而 \"C\" 是 C 。\n */\n\n#include <stdio.h>\n#include <locale.h>\n#include <wchar.h>\n\nvoid test2()\n{\n\tprintf(\"'': %s\\n\", setlocale(LC_ALL, \"\"));\n\tprintf(\"'C': %s\\n\", setlocale(LC_ALL, \"C\"));\n\n}\n\nint main()\n{\n\ttest2();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch25_国际化特性/ex_03.c",
    "content": "/*\n * 用于 kanji （日文中的汉字）的 Shift-JIS 编码要求每个字符是单字节或者双字节的。如果字符的第\n * 一个字节位于 0x81 和 0x9f 之间，或者位于 0xe0 和 0xef 之间，那么就需要第二个字节。（把任何其\n * 他字节看成是整个字符。）第二个字节必须在 0x40 和 0x7e 之间，或者在 0x80 和 0xfc 之间。（所有\n * 的范围都包含边界值。）对于下面的每个字符串，当传递其作为参数时，请指出25.2节的 mbcheck 函数\n * 将返回的值。\n *\n * ... 略\n */\n\n/*\n * a: -1\n * b: -1\n * c: -1\n * d: -1\n */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <locale.h>\n#include <limits.h>\n\nint mbcheck(const char *s)\n{\n    int n;\n\n    for (mblen(NULL, 0); ; s += n)\n        if ((n = mblen(s, MB_CUR_MAX)) <= 0)\n            return n;\n}\n\nint main()\n{\n\tprintf(\"a: %d\\n\", mbcheck(\"\\x05\\x87\\x80\\x36\\xed\\xaa\"));\n\tprintf(\"b: %d\\n\", mbcheck(\"\\x20\\xe4\\x50\\x88\\x3f\"));\n\tprintf(\"c: %d\\n\", mbcheck(\"\\xde\\xad\\xbe\\xef\"));\n\tprintf(\"d: %d\\n\", mbcheck(\"\\x8a\\x60\\x92\\x74\\x41\"));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch25_国际化特性/ex_04.md",
    "content": "请通过尽可能多的用三字符替换字符的方法来修改下面的程序段。\n\n```c\nwhile ((orig_char = getchar()) != EOF) {\n\tnew_char = orig_char ^ KEY;\n\tif (iscntrl(orig_char) || iscntrl(new_char))\n\t\tputchar(orig_char);\n\telse\n\t\tputchar(new_char);\n}\n```\n\n---\n\n```c\nwhile ((orig_char = getchar()) != EOF) ??<\n\tnew_char = orig_char ??' KEY;\n\tif (iscntrl(orig_char) ??!??! iscntrl(new_char))\n\t\tputchar(orig_char);\n\telse\n\t\tputchar(new_char);\n??>\n```\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/airmiles.c",
    "content": "/* Determines air mileage from New York to other cities */\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <string.h>\n\nstruct city_info {\n    char *city;\n    int miles;\n};\n\nint compare_city(const void *key_ptr, const void *element_ptr);\n\nint main()\n{\n    char city_name[80];\n    struct city_info *ptr;\n    const struct city_info mileage[] = {\n        {\"Acapulco\", 2260}, {\"Amsterdam\", 3639},\n        {\"Antigua\", 1783}, {\"Aruba\", 1963}\n    };\n\n    printf(\"Enter city name: \");\n    scanf(\"%80[^\\n]\", city_name);\n    ptr = bsearch(city_name, mileage, sizeof(mileage)/sizeof(mileage[0]), sizeof(mileage[0]), compare_city);\n    if (ptr != NULL)\n        printf(\"%s is %d miles from New York City.\\n\", city_name, ptr->miles);\n    else\n        printf(\"%s wasn't found.\\n\", city_name);\n\n    return 0;\n}\n\nint compare_city(const void *key_ptr, const void *element_ptr)\n{\n    return strcmp((char*)key_ptr, ((struct city_info*)element_ptr)->city);\n}"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_c_files=`ls *.c`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor c_file in $all_c_files; do\n\t\texe_file=${c_file%%.c*}.exe\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $c_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $c_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $c_file -> $exe_file\"\n\t\tgcc $c_file -o $exe_file -lm\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/datetime.c",
    "content": "/* Displays the current date and time in three formats */\n\n#include <stdio.h>\n#include <time.h>\n\nint main(int argc, char const *argv[])\n{\n    time_t current = time(NULL);\n    struct tm *ptr;\n    char date_time[19];\n    int hour;\n    char am_or_pm;\n\n    /* print date and time in default format */\n    puts(ctime(&current));\n\n    /* print date and time using strftime to format */\n    strftime(date_time, sizeof(date_time), \"%m-%d-%y %I:%M%p\\n\", localtime(&current));\n    puts(date_time);\n\n    /* print date and time using custom formatting */\n    ptr = localtime(&current);\n    hour = ptr->tm_hour;\n    if (hour <= 11)\n        am_or_pm = 'a';\n    else {\n        hour -= 12;\n        am_or_pm = 'p';\n    }\n    if (hour == 0)\n        hour = 12;\n    printf(\"%.2d-%.2d-%.2d %2d:%.2d%c\\n\", ptr->tm_mon + 1,\n            ptr->tm_mday, ptr->tm_year, hour, ptr->tm_min, am_or_pm);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/errorf.c",
    "content": "/*\n    使用包装函数的例子 vfprintf\n*/\n\n#include <stdarg.h>\n#include <stdio.h>\n\nint errorf(const char *format, ...)\n{\n    static int num_errors = 0;\n    int n;\n    va_list ap;\n\n    num_errors++;\n    fprintf(stderr, \"*** Error %d: \", num_errors);\n    va_start(ap, format);\n    n = vfprintf(stderr, format, ap);\n    va_end(ap);\n\n    fprintf(stderr, \"\\n\");\n    return n;\n}\n\nint main(int argc, char const *argv[])\n{\n    errorf(\"Wow crush!\");\n    errorf(\"No No No\");\n    errorf(\"Oh dear...\");\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_01.c",
    "content": "/*\n    重新编写 max_int 函数，要求不再把传递整数作为第一个参数，我们必须采用0作为最后一个\n    参数。提示： max_int 函数必须至少有一个“正常”的参数，所以不能把参数n移走，相反假设它是\n    要比较的数之一。\n*/\n\n#include <stdarg.h>\n#include <stdio.h>\n\nint max_int(int n, ...)\n{\n    va_list ap;\n    int i, current = n, largest;\n\n    va_start(ap, n);\n    largest = current;\n    while (current != 0) {\n        current = va_arg(ap, int);\n        if (current > largest)\n            largest = current;\n    }\n\n    va_end(ap);\n    return largest;\n}\n\nint main(int argc, char const *argv[])\n{\n    printf(\"max of {1, 2, 42, 10, 9}: %d\\n\", max_int(1, 2, 42, 10, 9, 0));\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_02.c",
    "content": "/*\n    编写 printf 函数的简写版，要求新函数只有一种转换说明 %d ，并且在第一个参数后边的所有参数都\n    必须是 int 类型的。\n*/\n\n#include <stdarg.h>\n#include <stdio.h>\n\nint my_printf(const char* format, ...)\n{\n    int num_write = 0;\n    int tmp;\n    const char* p;\n    va_list ap;\n\n    p = format;\n    va_start(ap, format);\n    while (*p != '\\0') {\n        if (*p == '%') {\n            p++;\n            if (*p == 'd') {\n                tmp = va_arg(ap, int);\n                char buffer[1024];\n                sprintf(buffer, \"%d\", tmp);\n                fputs(buffer, stdout);\n            }\n            else {\n                fputc('?', stdout);\n            }\n        }\n        else {\n            fputc(*p, stdout);\n        }\n        p++;\n    }\n\n    va_end(ap);\n\n    return num_write;\n}\n\nint main(int argc, char const *argv[])\n{\n    my_printf(\"numbers: %d %d %d\\n\", 100, 42, 88);\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_03.c",
    "content": "/*\n    编写下列函数：\n\n    char* vstrcat(const char *first, ...);\n\n    假设 vstrcat 函数最后一个参数必须是空指针（强制成 char* 类型）外，全部参数都是字符串。\n    函数返回的指针指向动态分配的且含有参数拼接的字符串。如果没有足够的内存，那么 vstrcat 函数\n    应该返回空指针。提示： vstrcat 函数必须遍历参数两次：一次用来确定返回字符串需要的内存数量，\n    另一次用来把参数复制到字符串中。\n*/\n\n#include <stdarg.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdlib.h>\n\nchar* vstrcat(const char *first, ...)\n{\n    size_t len;\n    const char* p;\n    char *str;\n    va_list ap;\n\n    /* allocate memory */\n    {\n        len = 0;\n        p = first;\n        va_start(ap, first);\n\n        while (p != NULL) {\n            len += strlen(p);\n            p = va_arg(ap, const char*);\n        }\n        len += 1;\n\n        str = malloc(sizeof(char) * len);\n        if (!str) {\n            return NULL;\n            va_end(ap);\n        }\n\n        va_end(ap);\n    }\n    \n    /* cat string */\n    {\n        len = 0;\n        p = first;\n        va_start(ap, first);\n\n        while (p != NULL) {\n            strcpy(str + len, p);\n            len += strlen(p);\n            p = va_arg(ap, const char*);\n        }\n\n        va_end(ap);\n    }\n\n    return str;\n}\n\nint main(int argc, char const *argv[])\n{\n    char *str = vstrcat(\"Hello\", \" World\", \"!!!\", NULL);\n\n    printf(\"%s\\n\", str);\n\n    free(str);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_04.c",
    "content": "/*\n    解释说明下列语句的含义。假设 value 是 long int 型的变量， p 是 char* 型的变量。\n\n    value = strtol(p, &p, 10);\n*/\n\n/*\n    把 p 所指向的字符串转译成 long int ，如果消耗掉了所有的字符，那么 p 最终指向空字符。\n*/\n\n#include <stdlib.h>\n#include <stdio.h>\n\nint main(int argc, char const *argv[])\n{\n    long int value;\n    char *p = \"12345\";\n\n    value = strtol(p, &p, 10);\n    if (*p == '\\0') {\n        printf(\"%ld\\n\", value);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_05.c",
    "content": "/*\n    编写一条可以随机从7、11、15或19中取一个数分配给变量n的语句。\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n\n#define LOOP_TIMES 100000\n\nint main(int argc, char const *argv[])\n{\n    int n;\n    int hit[100] = {0};\n    int loop = 0;\n\n    /* 复合语句也是一条语句啦... */\n    while (loop++ < LOOP_TIMES)\n    {\n        int list[] = {7, 11, 15, 19};  \n        n = list[rand() % (sizeof(list) / sizeof(list[0]))];\n\n        hit[n]++;\n    }\n\n    printf(\"num\\ttimes\\tpercent\\n\");\n    printf(\"7\\t%d\\t%g\\n\", hit[7], hit[7] / (double)LOOP_TIMES);\n    printf(\"11\\t%d\\t%g\\n\", hit[11], hit[11] / (double)LOOP_TIMES);\n    printf(\"15\\t%d\\t%g\\n\", hit[15], hit[15] / (double)LOOP_TIMES);\n    printf(\"19\\t%d\\t%g\\n\", hit[19], hit[19] / (double)LOOP_TIMES);\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_06.c",
    "content": "/*\n    编写一个可以随机返回 double 型值 d （0.0 <= d < 1.0）的函数。\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n\ndouble random_double()\n{\n    return (rand() % 100) / 100.0;\n}\n\nint main(int argc, char const *argv[])\n{\n    double num;\n    int hit_range[5] = {0};\n    int loop = 0;\n\n    while (loop++ < 100000) {\n        num = random_double();\n        //printf(\"%g\\n\", num);\n\n        if (num >= 0.0 && num < 0.2) {\n            hit_range[0]++;\n        }\n        else if (num >= 0.2 && num < 0.4) {\n            hit_range[1]++;\n        }\n        else if (num >= 0.4 && num < 0.6) {\n            hit_range[2]++;\n        }\n        else if (num >= 0.6 && num < 0.8) {\n            hit_range[3]++;\n        }\n        else {\n            hit_range[4]++;\n        }\n    }\n\n    printf(\"range\\t\\ttimes\\n\");\n    printf(\"[0.0, 0.2)\\t%d\\n\", hit_range[0]);\n    printf(\"[0.2, 0.4)\\t%d\\n\", hit_range[1]);\n    printf(\"[0.4, 0.6)\\t%d\\n\", hit_range[2]);\n    printf(\"[0.6, 0.8)\\t%d\\n\", hit_range[3]);\n    printf(\"[0.8, 1.0)\\t%d\\n\", hit_range[4]);\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_07.c",
    "content": "/*\n    编写一个程序，用来模拟称为“掷双骰”游戏。程序要通过随机选择1到6之间的两个数来“滚动”一对\n    模拟的骰子。如果两个数的和是7或11，那么程序显示信息 player wins 。如果和为2、3或12，则\n    显示 Player lose 。否则，程序要重复滚动骰子直到再一次达到原始和（Player wins）或者骰子\n    合计为7（Player lose）为止。程序需要在每次模拟滚动后显示一下骰子的值。\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\nint main(int argc, char const *argv[])\n{\n    /* 开始，定义变量，设置随机种子 */\n    int num1, num2, sum;\n    srand(time(NULL));\n\n    /* 接受一次输入，滚动一次骰子，并判断结果 */\n    while (1) {\n        printf(\"Press any key to start rolling ...\");\n        getchar();\n\n        num1 = rand() % 6 + 1;\n        num2 = rand() % 6 + 1;\n\n        printf(\"numbers: {%d %d}\\n\", num1, num2);\n\n        sum = num1 + num2;\n        if (sum == 7 || sum == 11) {\n            printf(\"Player win\\n\");\n            return 0;\n        }\n        else if (sum == 2 || sum == 3 || sum == 12) {\n            printf(\"Player lose\\n\");\n            return 0;\n        }\n    }\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_08.c",
    "content": "/*\n    (a) 编写一个程序，使它可以调用 rand 函数1000次并且显示函数返回值的最低位（如果返回值\n        是偶数，则为0；如果返回值为奇数，则为1。）你看到过什么模式吗？（ rand 的返回值的\n        最后几位往往不是特别随机的。 ）\n\n    (b) 如何改进 rand 函数的随机性，使它可以在一个小范围内产生数？\n*/\n\n/*\n    看到过什么模式吗？这句话问的是什么，我没有搞懂。\n\n    通过使用 % 100 这样的操作，可以产生范围小的数，但是能否改进其随机性，我不是很肯定。\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\nint main(int argc, char const *argv[])\n{\n    int loop = 0;\n    int num;\n    int hit[2] = {0};\n    srand(time(NULL));\n\n    while (loop++ < 1000) {\n        num = rand() % 100;\n        printf(\"%d\\t%s\\n\", num, num % 2 == 0 ? \"0\" : \"1\");\\\n        if (num % 2 == 0) {\n            hit[0]++;\n        }\n        else {\n            hit[1]++;\n        }\n    }\n\n    printf(\"hit even number times: %d\\n\", hit[0]);\n    printf(\"hit odd number times: %d\\n\", hit[1]);\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_09.c",
    "content": "/*\n    编写两个函数来测试 atexit 函数。一个函数显示 That's all, ，另一个显示 folks! 。在程序终止时\n    用 atexit 函数来注册这两个要调用的函数。请一定确保两个函数按照正确的顺序进行调用，只有这样\n    才可以在屏幕上看到 That's all, folks! 。\n*/\n\n#include <stdlib.h>\n#include <stdio.h>\n\nvoid f1()\n{\n    printf(\"That's all, \");\n}\n\nvoid f2()\n{\n    printf(\"folks!\\n\");\n}\n\nint main(int argc, char const *argv[])\n{\n    atexit(f2);\n    atexit(f1);\n    \n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_10.c",
    "content": "/*\n    编写一个程序，用 clock 函数来测算 qsort 函数对有100个整数的数组进行排序所用的时间，其中此\n    数组初始时是反序排列元素的。确保该程序还可以用于有1000个整数的数组或10000个整数的数组。\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\n#define LEN 1000\n\nint compare(const void* p1, const void* p2)\n{\n    return *(int*)p1 - *(int*)p2;\n}\n\nint main(int argc, char const *argv[])\n{\n    int arr[LEN];\n    int i;\n    int num = LEN;\n    for (i = 0; i < LEN; i++) {\n        arr[i] = num--;\n    }\n\n    clock_t start_clock = clock();\n\n    qsort(arr, LEN, sizeof(arr[0]), compare);\n\n    clock_t end_clock = clock();\n\n    printf(\"cost time: %g ms\\n\", (end_clock - start_clock) / (double)CLOCKS_PER_SEC);\n    \n    for (i = 0; i < LEN; i++) {\n        printf(\"%d \", arr[i]);\n    }\n    printf(\"\\n\");\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_11.c",
    "content": "/*\n    编写一个函数，要求向此函数传递年（比如，1996）时，函数返回一个表示在年份开始处的 time_t 值。\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\ntime_t f()\n{\n    int year;\n    printf(\"Enter year: \");\n    scanf(\"%d\", &year);\n\n    struct tm t;\n    t.tm_mday = 1;\n    t.tm_mon = 0;\n    t.tm_year = year - 1900;\n    t.tm_sec = 0;\n    t.tm_min = 0;\n    t.tm_hour = 0;\n    t.tm_isdst = -1;\n\n    return mktime(&t);\n}\n\nint main(int argc, char const *argv[])\n{\n    time_t t = f();\n    printf(\"%s\\n\", ctime(&t));\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_12.c",
    "content": "/*\n    编写一个程序，提示用户录入一个日期（月、日和年）和一个整数，然后显示n天后的日期。\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\ntime_t f()\n{\n    int month, day, year;\n    int n;\n    printf(\"Enter year: \");\n    scanf(\"%d\", &year);\n    printf(\"Enter month: \");\n    scanf(\"%d\", &month);\n    printf(\"Enter day: \");\n    scanf(\"%d\", &day);\n    printf(\"Enter pass days: \");\n    scanf(\"%d\", &n);\n\n    struct tm t;\n    t.tm_mday = day + n;\n    t.tm_mon = month - 1;\n    t.tm_year = year - 1900;\n    t.tm_sec = 0;\n    t.tm_min = 0;\n    t.tm_hour = 0;\n    t.tm_isdst = -1;\n\n    return mktime(&t);\n}\n\nint main(int argc, char const *argv[])\n{\n    time_t t = f();\n    printf(\"%s\\n\", ctime(&t));\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_13.c",
    "content": "/*\n    编写一个程序，提示用户录入两个日期，然后显示两个日期之间相差的天数。提示：请使用 mktime\n    函数和 difftime 函数。\n*/\n\n#include <stdio.h>\n#include <stdlib.h>\n#include <time.h>\n\nvoid f()\n{\n    int month, day, year;\n    int i;\n    time_t time_list[2];\n\n    for (i = 0; i < 2; i++) {\n        printf(\"Enter year: \");\n        scanf(\"%d\", &year);\n        printf(\"Enter month: \");\n        scanf(\"%d\", &month);\n        printf(\"Enter day: \");\n        scanf(\"%d\", &day);\n\n        struct tm t;\n        t.tm_mday = day;\n        t.tm_mon = month - 1;\n        t.tm_year = year - 1900;\n        t.tm_sec = 0;\n        t.tm_min = 0;\n        t.tm_hour = 0;\n        t.tm_isdst = -1;\n\n        time_list[i] = mktime(&t);\n    }\n    \n    printf(\"diff days: %g\\n\", difftime(time_list[1], time_list[0]) / (24 * 3600));\n}\n\nint main(int argc, char const *argv[])\n{\n    f();\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/ex_14.c",
    "content": "/*\n    编写一个可以按照下列每种格式显示当前日期和时间的程序。请使用 strftime 函数来完成全部或\n    大部分格式化工作。\n\n    (a) Tuesday, August 30, 1994    05:07p\n    (b) Tue, 30 Aug 94  17:07\n    (c) 08/30/94 5:07:12 PM\n*/\n\n#include <stdio.h>\n#include <time.h>\n\nvoid a()\n{\n    time_t now = time(NULL);\n    struct tm *tmp_now = localtime(&now);\n\n    char date[1024];\n    int len = strftime(date, 1024, \"%A, %B %d, %Y\\t%I:%M\", tmp_now);\n    char a_or_p = 'a';\n    if (tmp_now->tm_hour > 11)\n        a_or_p = 'p';\n    date[len++] = a_or_p;\n    date[len] = '\\0';\n    printf(\"%s\\n\", date);\n}\n\nvoid b()\n{\n    time_t now = time(NULL);\n    struct tm *tmp_now = localtime(&now);\n\n    char date[1024];\n    int len = strftime(date, 1024, \"%a, %d %b %y\\t%H:%M\", tmp_now);\n    printf(\"%s\\n\", date);\n}\n\nvoid c()\n{\n    time_t now = time(NULL);\n    struct tm *tmp_now = localtime(&now);\n\n    char date[1024];\n    int len = strftime(date, 1024, \"%m/%d/%y %I:%M:%S %p\", tmp_now);\n    printf(\"%s\\n\", date);\n}\n\nint main(int argc, char const *argv[])\n{\n    a();\n    b();\n    c();\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/max_int.c",
    "content": "#include <stdarg.h>\n#include <stdio.h>\n\nint max_int(int n, ...)\n{\n    va_list ap;\n    int i, current, largest;\n\n    va_start(ap, n);\n    largest = va_arg(ap, int);\n\n    for (i = 1; i < n; i++) {\n        current = va_arg(ap, int);\n        if (current > largest)\n            largest = current;\n    }\n\n    va_end(ap);\n    return largest;\n}\n\nint main()\n{\n    printf(\"%d\\n\", max_int(3, 10, 5, 20));\n\n    return 0;\n}"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/trand.c",
    "content": "/* Tests the pseudo-random sequence generation functions */\n\n#include <stdio.h>\n#include <stdlib.h>\n\nint main(int argc, char const *argv[])\n{\n    int i, seed;\n\n    printf(\"This program displays the first ten values of rand.\\n\");\n\n    for (;;) {\n        for (i = 0; i < 10; i++)\n            printf(\"%d \", rand());\n        printf(\"\\n\\n\");\n        printf(\"Enter new seed value (0 to terminate): \");\n        scanf(\"%d\", &seed);\n        if (seed == 0)\n            break;\n        srand(seed);\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/ch26_其他库函数/tstrconv.c",
    "content": "/* Tests string conversion functions */\n\n#include <errno.h>\n#include <stdio.h>\n#include <stdlib.h>\n\n#define CHK_VALID printf(\"\\t\\t%s\\t%s\\n\",\\\n        errno != ERANGE ? \"Yes\" : \"No\",\\\n        *ptr == '\\0' ? \"Yes\" : \"No\")\n\nint main(int argc, char const *argv[])\n{\n    char *ptr;\n\n    if (argc != 2) {\n        printf(\"usage: tstrconv string\\n\");\n        return 1;\n    }\n\n    printf(\"Function\\tReturn Value\\n\");\n    printf(\"--------\\t------------\\n\");\n    printf(\"atof\\t\\t%g\\n\", atof(argv[1]));\n    printf(\"atoi\\t\\t%d\\n\", atoi(argv[1]));\n    printf(\"atol\\t\\t%ld\\n\", atol(argv[1]));\n    \n    printf(\"\\nFunction\\tReturn Value\\tValid?\\tStringConsumed?\\n\");\n    printf(\"--------\\t------------\\t------\\t---------------\\n\");\n\n    errno = 0;\n    printf(\"strtod\\t\\t%g\", strtod(argv[1], &ptr));\n    CHK_VALID;\n\n    errno = 0;\n    printf(\"strtol\\t\\t%ld\", strtol(argv[1], &ptr, 10));\n    CHK_VALID;\n\n    errno = 0;\n    printf(\"strtoul\\t\\t%lu\", strtoul(argv[1], &ptr, 10));\n    CHK_VALID;\n\n    return 0;\n}\n"
  },
  {
    "path": "codes/CProgramming/gen_readme.sh",
    "content": "#! /bin/bash\n\n# 创建 README.md 的脚本\n\nfilename=README.md\nchapters=26\n\nmain()\n{\n\trm -f $filename\n\ttouch $filename\n\n\techo -e \"# C语言程序设计：现代方法\\n\" >> $filename\n\techo -e \"学习环境：CentOS 7, VS Code & MinGW & Git-Bash\\n\" >> $filename\n\techo -e \"第25章 测试多字符与宽字符采取的环境是 CentOS7 ，在 Windows 下测试会出现问题\\n\" >> $filename\n\techo -e \"---\\n\" >> $filename\n\n\t# gen exercises\n\techo -e \"## 练习题\\n\" >> $filename\n\tlocal i=2\n\twhile [ $i -le $chapters ]; do\n\t\tlocal chapter_prefix=ch$(printf \"%.2d\\n\" $i)\n\t\tlocal chapter_num=$i\n\t\ti=$((i+1))\n\n\t\tlocal chapter_dir=$(ls -d $chapter_prefix* 2>/dev/null)\n\t\tif [ \"$chapter_dir\" == \"\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tcd \"$chapter_dir\"\n\n\t\tlocal ex_files=$(ls -f ex_*)\n\n\t\tlocal chapter_name=\"第${chapter_num}章 ${chapter_dir#*_}\"\n\t\techo -e \"### $chapter_name\\n\" >> ../$filename\n\n\t\techo \"|A|L|L|E|X|E|\" >> ../$filename\n\t\techo \"| :-: | :-: | :-: | :-: | :-: | :-: |\" >> ../$filename\n\t\t\t\n\t\tlocal num=0 # 第几个练习\n\t\tfor ex_file in $ex_files; do\n\t\t\tnum=$((num + 1))\n\t\t\techo -e \"|[练习$num](./$chapter_dir/$ex_file \\\"./$chapter_dir/$ex_file\\\")\\c\" >> ../$filename\n\n\t\t\t# 6列一行\n\t\t\tif [ $((num % 6)) = \"0\" ]; then\n\t\t\t\techo \"|\" >> ../$filename\n\t\t\tfi\n\t\tdone\n\n\t\tif [ $((num % 6)) != \"0\" ]; then\n\t\t\techo \"|\" >> ../$filename\n\t\tfi\n\n\t\techo >> ../$filename\n\n\t\tcd ..\n\tdone\n\n}\n\nmain $@\n"
  },
  {
    "path": "codes/CppPrimer/README.md",
    "content": "# C++ Primer第五版学习笔记\n\n学习环境：CentOS7 gcc4.8.5\n\n以下内容需要更高的编译器版本支持：\n\n- 正则表达式库\n\n- 一些IO操纵符，比如hexfloat\n\n可以使用[在线编译器](http://coliru.stacked-crooked.com/)编译运行相关代码。\n\n---\n\n## 第1章 开始\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习1.1](ch01_Getting_Started/exercise_1_01.cpp \"./ch01_Getting_Started/exercise_1_01.cpp\")|[练习1.2](ch01_Getting_Started/exercise_1_02.cpp \"./ch01_Getting_Started/exercise_1_02.cpp\")|[练习1.3](ch01_Getting_Started/exercise_1_03.cpp \"./ch01_Getting_Started/exercise_1_03.cpp\")|[练习1.4](ch01_Getting_Started/exercise_1_04.cpp \"./ch01_Getting_Started/exercise_1_04.cpp\")|[练习1.5](ch01_Getting_Started/exercise_1_05.cpp \"./ch01_Getting_Started/exercise_1_05.cpp\")|[练习1.6](ch01_Getting_Started/exercise_1_06.cpp \"./ch01_Getting_Started/exercise_1_06.cpp\")|\n|[练习1.7](ch01_Getting_Started/exercise_1_07.cpp \"./ch01_Getting_Started/exercise_1_07.cpp\")|[练习1.8](ch01_Getting_Started/exercise_1_08.cpp \"./ch01_Getting_Started/exercise_1_08.cpp\")|[练习1.9](ch01_Getting_Started/exercise_1_09.cpp \"./ch01_Getting_Started/exercise_1_09.cpp\")|[练习1.10](ch01_Getting_Started/exercise_1_10.cpp \"./ch01_Getting_Started/exercise_1_10.cpp\")|[练习1.11](ch01_Getting_Started/exercise_1_11.cpp \"./ch01_Getting_Started/exercise_1_11.cpp\")|[练习1.12](ch01_Getting_Started/exercise_1_12.cpp \"./ch01_Getting_Started/exercise_1_12.cpp\")|\n|[练习1.13](ch01_Getting_Started/exercise_1_13.cpp \"./ch01_Getting_Started/exercise_1_13.cpp\")|[练习1.14](ch01_Getting_Started/exercise_1_14.cpp \"./ch01_Getting_Started/exercise_1_14.cpp\")|[练习1.15](ch01_Getting_Started/exercise_1_15.cpp \"./ch01_Getting_Started/exercise_1_15.cpp\")|[练习1.16](ch01_Getting_Started/exercise_1_16.cpp \"./ch01_Getting_Started/exercise_1_16.cpp\")|[练习1.17](ch01_Getting_Started/exercise_1_17.cpp \"./ch01_Getting_Started/exercise_1_17.cpp\")|[练习1.18](ch01_Getting_Started/exercise_1_18.cpp \"./ch01_Getting_Started/exercise_1_18.cpp\")|\n|[练习1.19](ch01_Getting_Started/exercise_1_19.cpp \"./ch01_Getting_Started/exercise_1_19.cpp\")|[练习1.20](ch01_Getting_Started/exercise_1_20.cpp \"./ch01_Getting_Started/exercise_1_20.cpp\")|[练习1.21](ch01_Getting_Started/exercise_1_21.cpp \"./ch01_Getting_Started/exercise_1_21.cpp\")|[练习1.22](ch01_Getting_Started/exercise_1_22.cpp \"./ch01_Getting_Started/exercise_1_22.cpp\")|[练习1.23](ch01_Getting_Started/exercise_1_23.cpp \"./ch01_Getting_Started/exercise_1_23.cpp\")|[练习1.24](ch01_Getting_Started/exercise_1_24.cpp \"./ch01_Getting_Started/exercise_1_24.cpp\")|\n|[练习1.25](ch01_Getting_Started/exercise_1_25.cpp \"./ch01_Getting_Started/exercise_1_25.cpp\")|\n\n### 案例代码\n\n- [C++中的注释使用（p8）](ch01_Getting_Started/example_comments.cpp)\n\n- [for语句（p11）](ch01_Getting_Started/example_for.cpp)\n\n- [if语句（p15）](ch01_Getting_Started/example_if.cpp)\n\n- [使用IO库（p5）](ch01_Getting_Started/example_iostream.cpp)\n\n- [最简单的C++程序（p2）](ch01_Getting_Started/example_main.cpp)\n\n- [while语句（p10）](ch01_Getting_Started/example_while.cpp)\n\n## 第2章 变量和基本类型\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习2.1](ch02_Variables_and_Basic_Types/exercise_2_01.txt \"./ch02_Variables_and_Basic_Types/exercise_2_01.txt\")|[练习2.2](ch02_Variables_and_Basic_Types/exercise_2_02.txt \"./ch02_Variables_and_Basic_Types/exercise_2_02.txt\")|[练习2.3](ch02_Variables_and_Basic_Types/exercise_2_03.txt \"./ch02_Variables_and_Basic_Types/exercise_2_03.txt\")|[练习2.4](ch02_Variables_and_Basic_Types/exercise_2_04.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_04.cpp\")|[练习2.5](ch02_Variables_and_Basic_Types/exercise_2_05.txt \"./ch02_Variables_and_Basic_Types/exercise_2_05.txt\")|[练习2.6](ch02_Variables_and_Basic_Types/exercise_2_06.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_06.cpp\")|\n|[练习2.7](ch02_Variables_and_Basic_Types/exercise_2_07.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_07.cpp\")|[练习2.8](ch02_Variables_and_Basic_Types/exercise_2_08.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_08.cpp\")|[练习2.9](ch02_Variables_and_Basic_Types/exercise_2_09.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_09.cpp\")|[练习2.10](ch02_Variables_and_Basic_Types/exercise_2_10.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_10.cpp\")|[练习2.11](ch02_Variables_and_Basic_Types/exercise_2_11.txt \"./ch02_Variables_and_Basic_Types/exercise_2_11.txt\")|[练习2.12](ch02_Variables_and_Basic_Types/exercise_2_12.txt \"./ch02_Variables_and_Basic_Types/exercise_2_12.txt\")|\n|[练习2.13](ch02_Variables_and_Basic_Types/exercise_2_13.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_13.cpp\")|[练习2.14](ch02_Variables_and_Basic_Types/exercise_2_14.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_14.cpp\")|[练习2.15](ch02_Variables_and_Basic_Types/exercise_2_15.txt \"./ch02_Variables_and_Basic_Types/exercise_2_15.txt\")|[练习2.16](ch02_Variables_and_Basic_Types/exercise_2_16.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_16.cpp\")|[练习2.17](ch02_Variables_and_Basic_Types/exercise_2_17.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_17.cpp\")|[练习2.18](ch02_Variables_and_Basic_Types/exercise_2_18.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_18.cpp\")|\n|[练习2.19](ch02_Variables_and_Basic_Types/exercise_2_19.txt \"./ch02_Variables_and_Basic_Types/exercise_2_19.txt\")|[练习2.20](ch02_Variables_and_Basic_Types/exercise_2_20.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_20.cpp\")|[练习2.21](ch02_Variables_and_Basic_Types/exercise_2_21.txt \"./ch02_Variables_and_Basic_Types/exercise_2_21.txt\")|[练习2.22](ch02_Variables_and_Basic_Types/exercise_2_22.txt \"./ch02_Variables_and_Basic_Types/exercise_2_22.txt\")|[练习2.23](ch02_Variables_and_Basic_Types/exercise_2_23.txt \"./ch02_Variables_and_Basic_Types/exercise_2_23.txt\")|[练习2.24](ch02_Variables_and_Basic_Types/exercise_2_24.txt \"./ch02_Variables_and_Basic_Types/exercise_2_24.txt\")|\n|[练习2.25](ch02_Variables_and_Basic_Types/exercise_2_25.txt \"./ch02_Variables_and_Basic_Types/exercise_2_25.txt\")|[练习2.26](ch02_Variables_and_Basic_Types/exercise_2_26.txt \"./ch02_Variables_and_Basic_Types/exercise_2_26.txt\")|[练习2.27](ch02_Variables_and_Basic_Types/exercise_2_27.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_27.cpp\")|[练习2.28](ch02_Variables_and_Basic_Types/exercise_2_28.txt \"./ch02_Variables_and_Basic_Types/exercise_2_28.txt\")|[练习2.29](ch02_Variables_and_Basic_Types/exercise_2_29.txt \"./ch02_Variables_and_Basic_Types/exercise_2_29.txt\")|[练习2.30](ch02_Variables_and_Basic_Types/exercise_2_30.txt \"./ch02_Variables_and_Basic_Types/exercise_2_30.txt\")|\n|[练习2.31](ch02_Variables_and_Basic_Types/exercise_2_31.txt \"./ch02_Variables_and_Basic_Types/exercise_2_31.txt\")|[练习2.32](ch02_Variables_and_Basic_Types/exercise_2_32.txt \"./ch02_Variables_and_Basic_Types/exercise_2_32.txt\")|[练习2.33](ch02_Variables_and_Basic_Types/exercise_2_33.txt \"./ch02_Variables_and_Basic_Types/exercise_2_33.txt\")|[练习2.34](ch02_Variables_and_Basic_Types/exercise_2_34.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_34.cpp\")|[练习2.35](ch02_Variables_and_Basic_Types/exercise_2_35.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_35.cpp\")|[练习2.36](ch02_Variables_and_Basic_Types/exercise_2_36.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_36.cpp\")|\n|[练习2.37](ch02_Variables_and_Basic_Types/exercise_2_37.txt \"./ch02_Variables_and_Basic_Types/exercise_2_37.txt\")|[练习2.38](ch02_Variables_and_Basic_Types/exercise_2_38.md \"./ch02_Variables_and_Basic_Types/exercise_2_38.md\")|[练习2.39](ch02_Variables_and_Basic_Types/exercise_2_39.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_39.cpp\")|[练习2.40](ch02_Variables_and_Basic_Types/exercise_2_40.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_40.cpp\")|[练习2.41](ch02_Variables_and_Basic_Types/exercise_2_41.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_41.cpp\")|[练习2.42](ch02_Variables_and_Basic_Types/exercise_2_42.cpp \"./ch02_Variables_and_Basic_Types/exercise_2_42.cpp\")|\n\n## 第3章 字符串、向量和数组\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习3.1](ch03_Strings_Vectors_and_Arrays/exercise_3_01.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_01.cpp\")|[练习3.2](ch03_Strings_Vectors_and_Arrays/exercise_3_02.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_02.cpp\")|[练习3.3](ch03_Strings_Vectors_and_Arrays/exercise_3_03.txt \"./ch03_Strings_Vectors_and_Arrays/exercise_3_03.txt\")|[练习3.4a](ch03_Strings_Vectors_and_Arrays/exercise_3_04a.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_04a.cpp\")|[练习3.4b](ch03_Strings_Vectors_and_Arrays/exercise_3_04b.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_04b.cpp\")|[练习3.5a](ch03_Strings_Vectors_and_Arrays/exercise_3_05a.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_05a.cpp\")|\n|[练习3.5b](ch03_Strings_Vectors_and_Arrays/exercise_3_05b.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_05b.cpp\")|[练习3.6](ch03_Strings_Vectors_and_Arrays/exercise_3_06.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_06.cpp\")|[练习3.7](ch03_Strings_Vectors_and_Arrays/exercise_3_07.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_07.cpp\")|[练习3.8a](ch03_Strings_Vectors_and_Arrays/exercise_3_08a.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_08a.cpp\")|[练习3.8b](ch03_Strings_Vectors_and_Arrays/exercise_3_08b.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_08b.cpp\")|[练习3.9](ch03_Strings_Vectors_and_Arrays/exercise_3_09.txt \"./ch03_Strings_Vectors_and_Arrays/exercise_3_09.txt\")|\n|[练习3.10](ch03_Strings_Vectors_and_Arrays/exercise_3_10.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_10.cpp\")|[练习3.11](ch03_Strings_Vectors_and_Arrays/exercise_3_11.txt \"./ch03_Strings_Vectors_and_Arrays/exercise_3_11.txt\")|[练习3.12](ch03_Strings_Vectors_and_Arrays/exercise_3_12.txt \"./ch03_Strings_Vectors_and_Arrays/exercise_3_12.txt\")|[练习3.13](ch03_Strings_Vectors_and_Arrays/exercise_3_13.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_13.cpp\")|[练习3.14](ch03_Strings_Vectors_and_Arrays/exercise_3_14.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_14.cpp\")|[练习3.15](ch03_Strings_Vectors_and_Arrays/exercise_3_15.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_15.cpp\")|\n|[练习3.16](ch03_Strings_Vectors_and_Arrays/exercise_3_16.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_16.cpp\")|[练习3.17](ch03_Strings_Vectors_and_Arrays/exercise_3_17.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_17.cpp\")|[练习3.18](ch03_Strings_Vectors_and_Arrays/exercise_3_18.txt \"./ch03_Strings_Vectors_and_Arrays/exercise_3_18.txt\")|[练习3.19](ch03_Strings_Vectors_and_Arrays/exercise_3_19.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_19.cpp\")|[练习3.20a](ch03_Strings_Vectors_and_Arrays/exercise_3_20a.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_20a.cpp\")|[练习3.20b](ch03_Strings_Vectors_and_Arrays/exercise_3_20b.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_20b.cpp\")|\n|[练习3.21](ch03_Strings_Vectors_and_Arrays/exercise_3_21.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_21.cpp\")|[练习3.22](ch03_Strings_Vectors_and_Arrays/exercise_3_22.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_22.cpp\")|[练习3.23](ch03_Strings_Vectors_and_Arrays/exercise_3_23.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_23.cpp\")|[练习3.24a](ch03_Strings_Vectors_and_Arrays/exercise_3_24a.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_24a.cpp\")|[练习3.24b](ch03_Strings_Vectors_and_Arrays/exercise_3_24b.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_24b.cpp\")|[练习3.25](ch03_Strings_Vectors_and_Arrays/exercise_3_25.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_25.cpp\")|\n|[练习3.26](ch03_Strings_Vectors_and_Arrays/exercise_3_26.txt \"./ch03_Strings_Vectors_and_Arrays/exercise_3_26.txt\")|[练习3.27](ch03_Strings_Vectors_and_Arrays/exercise_3_27.txt \"./ch03_Strings_Vectors_and_Arrays/exercise_3_27.txt\")|[练习3.28](ch03_Strings_Vectors_and_Arrays/exercise_3_28.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_28.cpp\")|[练习3.29](ch03_Strings_Vectors_and_Arrays/exercise_3_29.txt \"./ch03_Strings_Vectors_and_Arrays/exercise_3_29.txt\")|[练习3.30](ch03_Strings_Vectors_and_Arrays/exercise_3_30.txt \"./ch03_Strings_Vectors_and_Arrays/exercise_3_30.txt\")|[练习3.31](ch03_Strings_Vectors_and_Arrays/exercise_3_31.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_31.cpp\")|\n|[练习3.32a](ch03_Strings_Vectors_and_Arrays/exercise_3_32a.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_32a.cpp\")|[练习3.32b](ch03_Strings_Vectors_and_Arrays/exercise_3_32b.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_32b.cpp\")|[练习3.33](ch03_Strings_Vectors_and_Arrays/exercise_3_33.txt \"./ch03_Strings_Vectors_and_Arrays/exercise_3_33.txt\")|[练习3.34](ch03_Strings_Vectors_and_Arrays/exercise_3_34.txt \"./ch03_Strings_Vectors_and_Arrays/exercise_3_34.txt\")|[练习3.35](ch03_Strings_Vectors_and_Arrays/exercise_3_35.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_35.cpp\")|[练习3.36a](ch03_Strings_Vectors_and_Arrays/exercise_3_36a.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_36a.cpp\")|\n|[练习3.36b](ch03_Strings_Vectors_and_Arrays/exercise_3_36b.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_36b.cpp\")|[练习3.37](ch03_Strings_Vectors_and_Arrays/exercise_3_37.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_37.cpp\")|[练习3.38](ch03_Strings_Vectors_and_Arrays/exercise_3_38.txt \"./ch03_Strings_Vectors_and_Arrays/exercise_3_38.txt\")|[练习3.39a](ch03_Strings_Vectors_and_Arrays/exercise_3_39a.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_39a.cpp\")|[练习3.39b](ch03_Strings_Vectors_and_Arrays/exercise_3_39b.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_39b.cpp\")|[练习3.40](ch03_Strings_Vectors_and_Arrays/exercise_3_40.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_40.cpp\")|\n|[练习3.41](ch03_Strings_Vectors_and_Arrays/exercise_3_41.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_41.cpp\")|[练习3.42](ch03_Strings_Vectors_and_Arrays/exercise_3_42.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_42.cpp\")|[练习3.43a](ch03_Strings_Vectors_and_Arrays/exercise_3_43a.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_43a.cpp\")|[练习3.43b](ch03_Strings_Vectors_and_Arrays/exercise_3_43b.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_43b.cpp\")|[练习3.43c](ch03_Strings_Vectors_and_Arrays/exercise_3_43c.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_43c.cpp\")|[练习3.44](ch03_Strings_Vectors_and_Arrays/exercise_3_44.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_44.cpp\")|\n|[练习3.45](ch03_Strings_Vectors_and_Arrays/exercise_3_45.cpp \"./ch03_Strings_Vectors_and_Arrays/exercise_3_45.cpp\")|\n\n### 案例代码\n\n- [使用getline读取一整行（p78）](ch03_Strings_Vectors_and_Arrays/example_getline.cpp)\n\n- [一维数组模拟二维数组](ch03_Strings_Vectors_and_Arrays/example_normal_array_simulate_dimensional_array.cpp)\n\n- [向vector对象中添加元素（p90）](ch03_Strings_Vectors_and_Arrays/example_push_back.cpp)\n\n- [使用基于范围for语句处理每个字符（p82）](ch03_Strings_Vectors_and_Arrays/example_range_for.cpp)\n\n- [读取未知数量的string对象（p78）](ch03_Strings_Vectors_and_Arrays/example_read_string.cpp)\n\n- [迭代器运算符（p96）](ch03_Strings_Vectors_and_Arrays/example_using_iterators.cpp)\n\n- [使用下标执行迭代（p85）](ch03_Strings_Vectors_and_Arrays/example_using_subscript.cpp)\n\n## 第4章 表达式\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习4.1](ch04_Expressions/exercise_4_01.txt \"./ch04_Expressions/exercise_4_01.txt\")|[练习4.2](ch04_Expressions/exercise_4_02.txt \"./ch04_Expressions/exercise_4_02.txt\")|[练习4.3](ch04_Expressions/exercise_4_03.txt \"./ch04_Expressions/exercise_4_03.txt\")|[练习4.4](ch04_Expressions/exercise_4_04.cpp \"./ch04_Expressions/exercise_4_04.cpp\")|[练习4.5](ch04_Expressions/exercise_4_05.txt \"./ch04_Expressions/exercise_4_05.txt\")|[练习4.6](ch04_Expressions/exercise_4_06.txt \"./ch04_Expressions/exercise_4_06.txt\")|\n|[练习4.7](ch04_Expressions/exercise_4_07.cpp \"./ch04_Expressions/exercise_4_07.cpp\")|[练习4.8](ch04_Expressions/exercise_4_08.txt \"./ch04_Expressions/exercise_4_08.txt\")|[练习4.9](ch04_Expressions/exercise_4_09.txt \"./ch04_Expressions/exercise_4_09.txt\")|[练习4.10](ch04_Expressions/exercise_4_10.cpp \"./ch04_Expressions/exercise_4_10.cpp\")|[练习4.11](ch04_Expressions/exercise_4_11.txt \"./ch04_Expressions/exercise_4_11.txt\")|[练习4.12](ch04_Expressions/exercise_4_12.txt \"./ch04_Expressions/exercise_4_12.txt\")|\n|[练习4.13](ch04_Expressions/exercise_4_13.cpp \"./ch04_Expressions/exercise_4_13.cpp\")|[练习4.14](ch04_Expressions/exercise_4_14.txt \"./ch04_Expressions/exercise_4_14.txt\")|[练习4.15](ch04_Expressions/exercise_4_15.txt \"./ch04_Expressions/exercise_4_15.txt\")|[练习4.16](ch04_Expressions/exercise_4_16.cpp \"./ch04_Expressions/exercise_4_16.cpp\")|[练习4.17](ch04_Expressions/exercise_4_17.txt \"./ch04_Expressions/exercise_4_17.txt\")|[练习4.18](ch04_Expressions/exercise_4_18.txt \"./ch04_Expressions/exercise_4_18.txt\")|\n|[练习4.19](ch04_Expressions/exercise_4_19.txt \"./ch04_Expressions/exercise_4_19.txt\")|[练习4.20](ch04_Expressions/exercise_4_20.txt \"./ch04_Expressions/exercise_4_20.txt\")|[练习4.21](ch04_Expressions/exercise_4_21.cpp \"./ch04_Expressions/exercise_4_21.cpp\")|[练习4.22a](ch04_Expressions/exercise_4_22a.cpp \"./ch04_Expressions/exercise_4_22a.cpp\")|[练习4.22b](ch04_Expressions/exercise_4_22b.cpp \"./ch04_Expressions/exercise_4_22b.cpp\")|[练习4.23](ch04_Expressions/exercise_4_23.cpp \"./ch04_Expressions/exercise_4_23.cpp\")|\n|[练习4.24](ch04_Expressions/exercise_4_24.txt \"./ch04_Expressions/exercise_4_24.txt\")|[练习4.25](ch04_Expressions/exercise_4_25.cpp \"./ch04_Expressions/exercise_4_25.cpp\")|[练习4.26](ch04_Expressions/exercise_4_26.txt \"./ch04_Expressions/exercise_4_26.txt\")|[练习4.27](ch04_Expressions/exercise_4_27.cpp \"./ch04_Expressions/exercise_4_27.cpp\")|[练习4.28](ch04_Expressions/exercise_4_28.cpp \"./ch04_Expressions/exercise_4_28.cpp\")|[练习4.29](ch04_Expressions/exercise_4_29.cpp \"./ch04_Expressions/exercise_4_29.cpp\")|\n|[练习4.30](ch04_Expressions/exercise_4_30.txt \"./ch04_Expressions/exercise_4_30.txt\")|[练习4.31](ch04_Expressions/exercise_4_31.cpp \"./ch04_Expressions/exercise_4_31.cpp\")|[练习4.32](ch04_Expressions/exercise_4_32.txt \"./ch04_Expressions/exercise_4_32.txt\")|[练习4.33](ch04_Expressions/exercise_4_33.txt \"./ch04_Expressions/exercise_4_33.txt\")|[练习4.34](ch04_Expressions/exercise_4_34.txt \"./ch04_Expressions/exercise_4_34.txt\")|[练习4.35](ch04_Expressions/exercise_4_35.txt \"./ch04_Expressions/exercise_4_35.txt\")|\n|[练习4.36](ch04_Expressions/exercise_4_36.txt \"./ch04_Expressions/exercise_4_36.txt\")|[练习4.37](ch04_Expressions/exercise_4_37.txt \"./ch04_Expressions/exercise_4_37.txt\")|[练习4.38](ch04_Expressions/exercise_4_38.txt \"./ch04_Expressions/exercise_4_38.txt\")|\n\n### 案例代码\n\n- [逗号运算符（p140）](ch04_Expressions/example_comma_operator.cpp)\n\n- [在一条语句中混用解引用和递增运算符（p132）](ch04_Expressions/example_increment_operator.cpp)\n\n## 第5章 语句\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习5.1](ch05_Statements/exercise_5_01.txt \"./ch05_Statements/exercise_5_01.txt\")|[练习5.2](ch05_Statements/exercise_5_02.txt \"./ch05_Statements/exercise_5_02.txt\")|[练习5.3](ch05_Statements/exercise_5_03.cpp \"./ch05_Statements/exercise_5_03.cpp\")|[练习5.4](ch05_Statements/exercise_5_04.txt \"./ch05_Statements/exercise_5_04.txt\")|[练习5.5](ch05_Statements/exercise_5_05.cpp \"./ch05_Statements/exercise_5_05.cpp\")|[练习5.6](ch05_Statements/exercise_5_06.cpp \"./ch05_Statements/exercise_5_06.cpp\")|\n|[练习5.7](ch05_Statements/exercise_5_07.txt \"./ch05_Statements/exercise_5_07.txt\")|[练习5.8](ch05_Statements/exercise_5_08.txt \"./ch05_Statements/exercise_5_08.txt\")|[练习5.9](ch05_Statements/exercise_5_09.cpp \"./ch05_Statements/exercise_5_09.cpp\")|[练习5.10](ch05_Statements/exercise_5_10.cpp \"./ch05_Statements/exercise_5_10.cpp\")|[练习5.11](ch05_Statements/exercise_5_11.cpp \"./ch05_Statements/exercise_5_11.cpp\")|[练习5.12](ch05_Statements/exercise_5_12.cpp \"./ch05_Statements/exercise_5_12.cpp\")|\n|[练习5.13](ch05_Statements/exercise_5_13.txt \"./ch05_Statements/exercise_5_13.txt\")|[练习5.14](ch05_Statements/exercise_5_14.cpp \"./ch05_Statements/exercise_5_14.cpp\")|[练习5.15](ch05_Statements/exercise_5_15.txt \"./ch05_Statements/exercise_5_15.txt\")|[练习5.16a](ch05_Statements/exercise_5_16a.cpp \"./ch05_Statements/exercise_5_16a.cpp\")|[练习5.16b](ch05_Statements/exercise_5_16b.cpp \"./ch05_Statements/exercise_5_16b.cpp\")|[练习5.16c](ch05_Statements/exercise_5_16c.cpp \"./ch05_Statements/exercise_5_16c.cpp\")|\n|[练习5.16d](ch05_Statements/exercise_5_16d.cpp \"./ch05_Statements/exercise_5_16d.cpp\")|[练习5.17](ch05_Statements/exercise_5_17.cpp \"./ch05_Statements/exercise_5_17.cpp\")|[练习5.18](ch05_Statements/exercise_5_18.txt \"./ch05_Statements/exercise_5_18.txt\")|[练习5.19](ch05_Statements/exercise_5_19.cpp \"./ch05_Statements/exercise_5_19.cpp\")|[练习5.20](ch05_Statements/exercise_5_20.cpp \"./ch05_Statements/exercise_5_20.cpp\")|[练习5.21](ch05_Statements/exercise_5_21.cpp \"./ch05_Statements/exercise_5_21.cpp\")|\n|[练习5.22](ch05_Statements/exercise_5_22.txt \"./ch05_Statements/exercise_5_22.txt\")|[练习5.23](ch05_Statements/exercise_5_23.cpp \"./ch05_Statements/exercise_5_23.cpp\")|[练习5.24](ch05_Statements/exercise_5_24.cpp \"./ch05_Statements/exercise_5_24.cpp\")|[练习5.25](ch05_Statements/exercise_5_25.cpp \"./ch05_Statements/exercise_5_25.cpp\")|\n\n### 案例代码\n\n- [do while语句（p169）](ch05_Statements/example_do_while.cpp)\n\n- [嵌套if语句（p157）](ch05_Statements/example_if_statement.cpp)\n\n## 第6章 函数\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习6.1](ch06_Functions/exercise_6_01.txt \"./ch06_Functions/exercise_6_01.txt\")|[练习6.2](ch06_Functions/exercise_6_02.txt \"./ch06_Functions/exercise_6_02.txt\")|[练习6.3](ch06_Functions/exercise_6_03.cpp \"./ch06_Functions/exercise_6_03.cpp\")|[练习6.4](ch06_Functions/exercise_6_04.cpp \"./ch06_Functions/exercise_6_04.cpp\")|[练习6.5](ch06_Functions/exercise_6_05.cpp \"./ch06_Functions/exercise_6_05.cpp\")|[练习6.6](ch06_Functions/exercise_6_06.cpp \"./ch06_Functions/exercise_6_06.cpp\")|\n|[练习6.7](ch06_Functions/exercise_6_07.cpp \"./ch06_Functions/exercise_6_07.cpp\")|[练习6.8](ch06_Functions/exercise_6_08.txt \"./ch06_Functions/exercise_6_08.txt\")|[练习6.9](ch06_Functions/exercise_6_09 \"./ch06_Functions/exercise_6_09\")|[练习6.10](ch06_Functions/exercise_6_10.cpp \"./ch06_Functions/exercise_6_10.cpp\")|[练习6.11](ch06_Functions/exercise_6_11.cpp \"./ch06_Functions/exercise_6_11.cpp\")|[练习6.12](ch06_Functions/exercise_6_12.cpp \"./ch06_Functions/exercise_6_12.cpp\")|\n|[练习6.13](ch06_Functions/exercise_6_13.txt \"./ch06_Functions/exercise_6_13.txt\")|[练习6.14](ch06_Functions/exercise_6_14.txt \"./ch06_Functions/exercise_6_14.txt\")|[练习6.15](ch06_Functions/exercise_6_15.txt \"./ch06_Functions/exercise_6_15.txt\")|[练习6.16](ch06_Functions/exercise_6_16.md \"./ch06_Functions/exercise_6_16.md\")|[练习6.17](ch06_Functions/exercise_6_17.cpp \"./ch06_Functions/exercise_6_17.cpp\")|[练习6.18](ch06_Functions/exercise_6_18.md \"./ch06_Functions/exercise_6_18.md\")|\n|[练习6.19](ch06_Functions/exercise_6_19.md \"./ch06_Functions/exercise_6_19.md\")|[练习6.20](ch06_Functions/exercise_6_20.md \"./ch06_Functions/exercise_6_20.md\")|[练习6.21](ch06_Functions/exercise_6_21.cpp \"./ch06_Functions/exercise_6_21.cpp\")|[练习6.22](ch06_Functions/exercise_6_22.cpp \"./ch06_Functions/exercise_6_22.cpp\")|[练习6.23](ch06_Functions/exercise_6_23.cpp \"./ch06_Functions/exercise_6_23.cpp\")|[练习6.24](ch06_Functions/exercise_6_24.cpp \"./ch06_Functions/exercise_6_24.cpp\")|\n|[练习6.25](ch06_Functions/exercise_6_25.cpp \"./ch06_Functions/exercise_6_25.cpp\")|[练习6.26](ch06_Functions/exercise_6_26.cpp \"./ch06_Functions/exercise_6_26.cpp\")|[练习6.27](ch06_Functions/exercise_6_27.cpp \"./ch06_Functions/exercise_6_27.cpp\")|[练习6.28](ch06_Functions/exercise_6_28.md \"./ch06_Functions/exercise_6_28.md\")|[练习6.29](ch06_Functions/exercise_6_29.md \"./ch06_Functions/exercise_6_29.md\")|[练习6.30](ch06_Functions/exercise_6_30.cpp \"./ch06_Functions/exercise_6_30.cpp\")|\n|[练习6.31](ch06_Functions/exercise_6_31.md \"./ch06_Functions/exercise_6_31.md\")|[练习6.32](ch06_Functions/exercise_6_32.md \"./ch06_Functions/exercise_6_32.md\")|[练习6.33](ch06_Functions/exercise_6_33.cpp \"./ch06_Functions/exercise_6_33.cpp\")|[练习6.34](ch06_Functions/exercise_6_34.cpp \"./ch06_Functions/exercise_6_34.cpp\")|[练习6.35](ch06_Functions/exercise_6_35.md \"./ch06_Functions/exercise_6_35.md\")|[练习6.36](ch06_Functions/exercise_6_36.cpp \"./ch06_Functions/exercise_6_36.cpp\")|\n|[练习6.37](ch06_Functions/exercise_6_37.cpp \"./ch06_Functions/exercise_6_37.cpp\")|[练习6.38](ch06_Functions/exercise_6_38.cpp \"./ch06_Functions/exercise_6_38.cpp\")|[练习6.39](ch06_Functions/exercise_6_39.md \"./ch06_Functions/exercise_6_39.md\")|[练习6.40](ch06_Functions/exercise_6_40.md \"./ch06_Functions/exercise_6_40.md\")|[练习6.41](ch06_Functions/exercise_6_41.md \"./ch06_Functions/exercise_6_41.md\")|[练习6.42](ch06_Functions/exercise_6_42.cpp \"./ch06_Functions/exercise_6_42.cpp\")|\n|[练习6.43](ch06_Functions/exercise_6_43.md \"./ch06_Functions/exercise_6_43.md\")|[练习6.44](ch06_Functions/exercise_6_44.cpp \"./ch06_Functions/exercise_6_44.cpp\")|[练习6.45](ch06_Functions/exercise_6_45.md \"./ch06_Functions/exercise_6_45.md\")|[练习6.46](ch06_Functions/exercise_6_46.cpp \"./ch06_Functions/exercise_6_46.cpp\")|[练习6.47](ch06_Functions/exercise_6_47.cpp \"./ch06_Functions/exercise_6_47.cpp\")|[练习6.48](ch06_Functions/exercise_6_48.md \"./ch06_Functions/exercise_6_48.md\")|\n|[练习6.49](ch06_Functions/exercise_6_49.md \"./ch06_Functions/exercise_6_49.md\")|[练习6.50](ch06_Functions/exercise_6_50.md \"./ch06_Functions/exercise_6_50.md\")|[练习6.51](ch06_Functions/exercise_6_51.cpp \"./ch06_Functions/exercise_6_51.cpp\")|[练习6.52](ch06_Functions/exercise_6_52.md \"./ch06_Functions/exercise_6_52.md\")|[练习6.53](ch06_Functions/exercise_6_53.md \"./ch06_Functions/exercise_6_53.md\")|[练习6.54](ch06_Functions/exercise_6_54.cpp \"./ch06_Functions/exercise_6_54.cpp\")|\n|[练习6.55](ch06_Functions/exercise_6_55.cpp \"./ch06_Functions/exercise_6_55.cpp\")|[练习6.56](ch06_Functions/exercise_6_56.cpp \"./ch06_Functions/exercise_6_56.cpp\")|\n\n### 案例代码\n\n- [数组形参（p193）](ch06_Functions/example_array_param.cpp)\n\n- [const_cast和重载（p209）](ch06_Functions/example_const_cast_and_overloading.cpp)\n\n- [无返回值函数（p200）](ch06_Functions/example_functions_with_no_return_value.cpp)\n\n- [initializer_list形参（可变形参，p198）](ch06_Functions/example_initializer_list_param.cpp)\n\n- [内联函数可避免函数调用开销（p213）](ch06_Functions/example_inline_functions.cpp)\n\n- [传递多维数组给形参（p195）](ch06_Functions/example_passing_a_multidimensional_array.cpp)\n\n- [使用预处理变量输出错误信息（p216）](ch06_Functions/example_preprocessor_variable.cpp)\n\n- [递归（p204）](ch06_Functions/example_recursion.cpp)\n\n- [主函数main的返回值（p203）](ch06_Functions/example_return_from_main.cpp)\n\n- [返回数组指针（使用decltype，p206）](ch06_Functions/example_returning_a_pointer_to_an_array.cpp)\n\n- [使用引用传递避免拷贝（p189）](ch06_Functions/example_using_ref_to_avoid_cp.cpp)\n\n- [使用引用形参返回额外信息（p189）](ch06_Functions/example_using_ref_to_return_info.cpp)\n\n## 第7章 类\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习7.1](ch07_Classes/exercise_7_01.cpp \"./ch07_Classes/exercise_7_01.cpp\")|[练习7.2](ch07_Classes/exercise_7_02.cpp \"./ch07_Classes/exercise_7_02.cpp\")|[练习7.3](ch07_Classes/exercise_7_03.cpp \"./ch07_Classes/exercise_7_03.cpp\")|[练习7.4](ch07_Classes/exercise_7_04.cpp \"./ch07_Classes/exercise_7_04.cpp\")|[练习7.5](ch07_Classes/exercise_7_05.cpp \"./ch07_Classes/exercise_7_05.cpp\")|[练习7.6](ch07_Classes/exercise_7_06.cpp \"./ch07_Classes/exercise_7_06.cpp\")|\n|[练习7.7](ch07_Classes/exercise_7_07.cpp \"./ch07_Classes/exercise_7_07.cpp\")|[练习7.8](ch07_Classes/exercise_7_08.md \"./ch07_Classes/exercise_7_08.md\")|[练习7.9](ch07_Classes/exercise_7_09.cpp \"./ch07_Classes/exercise_7_09.cpp\")|[练习7.10](ch07_Classes/exercise_7_10.md \"./ch07_Classes/exercise_7_10.md\")|[练习7.11](ch07_Classes/exercise_7_11.cpp \"./ch07_Classes/exercise_7_11.cpp\")|[练习7.12](ch07_Classes/exercise_7_12.cpp \"./ch07_Classes/exercise_7_12.cpp\")|\n|[练习7.13](ch07_Classes/exercise_7_13.cpp \"./ch07_Classes/exercise_7_13.cpp\")|[练习7.14](ch07_Classes/exercise_7_14.md \"./ch07_Classes/exercise_7_14.md\")|[练习7.15](ch07_Classes/exercise_7_15.cpp \"./ch07_Classes/exercise_7_15.cpp\")|[练习7.16](ch07_Classes/exercise_7_16.md \"./ch07_Classes/exercise_7_16.md\")|[练习7.17](ch07_Classes/exercise_7_17.md \"./ch07_Classes/exercise_7_17.md\")|[练习7.18](ch07_Classes/exercise_7_18.md \"./ch07_Classes/exercise_7_18.md\")|\n|[练习7.19](ch07_Classes/exercise_7_19.cpp \"./ch07_Classes/exercise_7_19.cpp\")|[练习7.20](ch07_Classes/exercise_7_20.md \"./ch07_Classes/exercise_7_20.md\")|[练习7.21](ch07_Classes/exercise_7_21.md \"./ch07_Classes/exercise_7_21.md\")|[练习7.22](ch07_Classes/exercise_7_22.cpp \"./ch07_Classes/exercise_7_22.cpp\")|[练习7.23](ch07_Classes/exercise_7_23.cpp \"./ch07_Classes/exercise_7_23.cpp\")|[练习7.24](ch07_Classes/exercise_7_24.cpp \"./ch07_Classes/exercise_7_24.cpp\")|\n|[练习7.25](ch07_Classes/exercise_7_25.md \"./ch07_Classes/exercise_7_25.md\")|[练习7.26](ch07_Classes/exercise_7_26.md \"./ch07_Classes/exercise_7_26.md\")|[练习7.27](ch07_Classes/exercise_7_27.cpp \"./ch07_Classes/exercise_7_27.cpp\")|[练习7.28](ch07_Classes/exercise_7_28.md \"./ch07_Classes/exercise_7_28.md\")|[练习7.29](ch07_Classes/exercise_7_29.cpp \"./ch07_Classes/exercise_7_29.cpp\")|[练习7.30](ch07_Classes/exercise_7_30.md \"./ch07_Classes/exercise_7_30.md\")|\n|[练习7.31](ch07_Classes/exercise_7_31.cpp \"./ch07_Classes/exercise_7_31.cpp\")|[练习7.32](ch07_Classes/exercise_7_32.cpp \"./ch07_Classes/exercise_7_32.cpp\")|[练习7.33](ch07_Classes/exercise_7_33.md \"./ch07_Classes/exercise_7_33.md\")|[练习7.34](ch07_Classes/exercise_7_34.md \"./ch07_Classes/exercise_7_34.md\")|[练习7.35](ch07_Classes/exercise_7_35.cpp \"./ch07_Classes/exercise_7_35.cpp\")|[练习7.36](ch07_Classes/exercise_7_36.cpp \"./ch07_Classes/exercise_7_36.cpp\")|\n|[练习7.37](ch07_Classes/exercise_7_37.md \"./ch07_Classes/exercise_7_37.md\")|[练习7.38](ch07_Classes/exercise_7_38.cpp \"./ch07_Classes/exercise_7_38.cpp\")|[练习7.39](ch07_Classes/exercise_7_39.md \"./ch07_Classes/exercise_7_39.md\")|[练习7.40](ch07_Classes/exercise_7_40.cpp \"./ch07_Classes/exercise_7_40.cpp\")|[练习7.41](ch07_Classes/exercise_7_41.cpp \"./ch07_Classes/exercise_7_41.cpp\")|[练习7.42](ch07_Classes/exercise_7_42.cpp \"./ch07_Classes/exercise_7_42.cpp\")|\n|[练习7.43](ch07_Classes/exercise_7_43.cpp \"./ch07_Classes/exercise_7_43.cpp\")|[练习7.44](ch07_Classes/exercise_7_44.md \"./ch07_Classes/exercise_7_44.md\")|[练习7.45](ch07_Classes/exercise_7_45.md \"./ch07_Classes/exercise_7_45.md\")|[练习7.46](ch07_Classes/exercise_7_46.md \"./ch07_Classes/exercise_7_46.md\")|[练习7.47](ch07_Classes/exercise_7_47.md \"./ch07_Classes/exercise_7_47.md\")|[练习7.48](ch07_Classes/exercise_7_48.md \"./ch07_Classes/exercise_7_48.md\")|\n|[练习7.49](ch07_Classes/exercise_7_49.md \"./ch07_Classes/exercise_7_49.md\")|[练习7.50](ch07_Classes/exercise_7_50.md \"./ch07_Classes/exercise_7_50.md\")|[练习7.51](ch07_Classes/exercise_7_51.md \"./ch07_Classes/exercise_7_51.md\")|[练习7.52](ch07_Classes/exercise_7_52.md \"./ch07_Classes/exercise_7_52.md\")|[练习7.53](ch07_Classes/exercise_7_53.md \"./ch07_Classes/exercise_7_53.md\")|[练习7.54](ch07_Classes/exercise_7_54.md \"./ch07_Classes/exercise_7_54.md\")|\n|[练习7.55](ch07_Classes/exercise_7_55.md \"./ch07_Classes/exercise_7_55.md\")|[练习7.56](ch07_Classes/exercise_7_56.md \"./ch07_Classes/exercise_7_56.md\")|[练习7.57](ch07_Classes/exercise_7_57.md \"./ch07_Classes/exercise_7_57.md\")|[练习7.58](ch07_Classes/exercise_7_58.md \"./ch07_Classes/exercise_7_58.md\")|\n\n### 案例代码\n\n- [类的静态成员（p269）](ch07_Classes/example_Account)\n\n- [字面值常量类（p267）](ch07_Classes/example_literal_classes)\n\n- [Person类的设计与实现](ch07_Classes/example_Person.cpp)\n\n- [Sales_data案例（自p228始）](ch07_Classes/example_Sales_data)\n\n- [窗口类的设计与实现(自p243始)](ch07_Classes/example_Screen)\n\n## 第8章 IO库\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习8.1](ch08_The_IO_Library/exercise_8_01.cpp \"./ch08_The_IO_Library/exercise_8_01.cpp\")|[练习8.2](ch08_The_IO_Library/exercise_8_02.md \"./ch08_The_IO_Library/exercise_8_02.md\")|[练习8.3](ch08_The_IO_Library/exercise_8_03.md \"./ch08_The_IO_Library/exercise_8_03.md\")|[练习8.4](ch08_The_IO_Library/exercise_8_04.cpp \"./ch08_The_IO_Library/exercise_8_04.cpp\")|[练习8.5](ch08_The_IO_Library/exercise_8_05.cpp \"./ch08_The_IO_Library/exercise_8_05.cpp\")|[练习8.6](ch08_The_IO_Library/exercise_8_06.cpp \"./ch08_The_IO_Library/exercise_8_06.cpp\")|\n|[练习8.7](ch08_The_IO_Library/exercise_8_07.cpp \"./ch08_The_IO_Library/exercise_8_07.cpp\")|[练习8.8](ch08_The_IO_Library/exercise_8_08.cpp \"./ch08_The_IO_Library/exercise_8_08.cpp\")|[练习8.9](ch08_The_IO_Library/exercise_8_09.cpp \"./ch08_The_IO_Library/exercise_8_09.cpp\")|[练习8.10](ch08_The_IO_Library/exercise_8_10.cpp \"./ch08_The_IO_Library/exercise_8_10.cpp\")|[练习8.11](ch08_The_IO_Library/exercise_8_11.cpp \"./ch08_The_IO_Library/exercise_8_11.cpp\")|[练习8.12](ch08_The_IO_Library/exercise_8_12.md \"./ch08_The_IO_Library/exercise_8_12.md\")|\n|[练习8.13](ch08_The_IO_Library/exercise_8_13.cpp \"./ch08_The_IO_Library/exercise_8_13.cpp\")|[练习8.14](ch08_The_IO_Library/exercise_8_14.md \"./ch08_The_IO_Library/exercise_8_14.md\")|\n\n### 案例代码\n\n- [使用fstream代替iostream&读取Sales_data（p284）](ch08_The_IO_Library/example_fstream.cpp)\n\n## 第9章 顺序容器\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习9.1](ch09_Sequential_Containers/exercise_9_01.md \"./ch09_Sequential_Containers/exercise_9_01.md\")|[练习9.2](ch09_Sequential_Containers/exercise_9_02.cpp \"./ch09_Sequential_Containers/exercise_9_02.cpp\")|[练习9.3](ch09_Sequential_Containers/exercise_9_03.md \"./ch09_Sequential_Containers/exercise_9_03.md\")|[练习9.4](ch09_Sequential_Containers/exercise_9_04.cpp \"./ch09_Sequential_Containers/exercise_9_04.cpp\")|[练习9.5](ch09_Sequential_Containers/exercise_9_05.cpp \"./ch09_Sequential_Containers/exercise_9_05.cpp\")|[练习9.6](ch09_Sequential_Containers/exercise_9_06.md \"./ch09_Sequential_Containers/exercise_9_06.md\")|\n|[练习9.7](ch09_Sequential_Containers/exercise_9_07.md \"./ch09_Sequential_Containers/exercise_9_07.md\")|[练习9.8](ch09_Sequential_Containers/exercise_9_08.md \"./ch09_Sequential_Containers/exercise_9_08.md\")|[练习9.9](ch09_Sequential_Containers/exercise_9_09.md \"./ch09_Sequential_Containers/exercise_9_09.md\")|[练习9.10](ch09_Sequential_Containers/exercise_9_10.md \"./ch09_Sequential_Containers/exercise_9_10.md\")|[练习9.11](ch09_Sequential_Containers/exercise_9_11.cpp \"./ch09_Sequential_Containers/exercise_9_11.cpp\")|[练习9.12](ch09_Sequential_Containers/exercise_9_12.md \"./ch09_Sequential_Containers/exercise_9_12.md\")|\n|[练习9.13](ch09_Sequential_Containers/exercise_9_13.cpp \"./ch09_Sequential_Containers/exercise_9_13.cpp\")|[练习9.14](ch09_Sequential_Containers/exercise_9_14.cpp \"./ch09_Sequential_Containers/exercise_9_14.cpp\")|[练习9.15](ch09_Sequential_Containers/exercise_9_15.cpp \"./ch09_Sequential_Containers/exercise_9_15.cpp\")|[练习9.16](ch09_Sequential_Containers/exercise_9_16.cpp \"./ch09_Sequential_Containers/exercise_9_16.cpp\")|[练习9.17](ch09_Sequential_Containers/exercise_9_17.md \"./ch09_Sequential_Containers/exercise_9_17.md\")|[练习9.18](ch09_Sequential_Containers/exercise_9_18.cpp \"./ch09_Sequential_Containers/exercise_9_18.cpp\")|\n|[练习9.19](ch09_Sequential_Containers/exercise_9_19.cpp \"./ch09_Sequential_Containers/exercise_9_19.cpp\")|[练习9.20](ch09_Sequential_Containers/exercise_9_20.cpp \"./ch09_Sequential_Containers/exercise_9_20.cpp\")|[练习9.21](ch09_Sequential_Containers/exercise_9_21.md \"./ch09_Sequential_Containers/exercise_9_21.md\")|[练习9.22](ch09_Sequential_Containers/exercise_9_22.md \"./ch09_Sequential_Containers/exercise_9_22.md\")|[练习9.23](ch09_Sequential_Containers/exercise_9_23.md \"./ch09_Sequential_Containers/exercise_9_23.md\")|[练习9.24](ch09_Sequential_Containers/exercise_9_24.cpp \"./ch09_Sequential_Containers/exercise_9_24.cpp\")|\n|[练习9.25](ch09_Sequential_Containers/exercise_9_25.cpp \"./ch09_Sequential_Containers/exercise_9_25.cpp\")|[练习9.26](ch09_Sequential_Containers/exercise_9_26.cpp \"./ch09_Sequential_Containers/exercise_9_26.cpp\")|[练习9.27](ch09_Sequential_Containers/exercise_9_27.cpp \"./ch09_Sequential_Containers/exercise_9_27.cpp\")|[练习9.28](ch09_Sequential_Containers/exercise_9_28.cpp \"./ch09_Sequential_Containers/exercise_9_28.cpp\")|[练习9.29](ch09_Sequential_Containers/exercise_9_29.md \"./ch09_Sequential_Containers/exercise_9_29.md\")|[练习9.30](ch09_Sequential_Containers/exercise_9_30.md \"./ch09_Sequential_Containers/exercise_9_30.md\")|\n|[练习9.31](ch09_Sequential_Containers/exercise_9_31.cpp \"./ch09_Sequential_Containers/exercise_9_31.cpp\")|[练习9.32](ch09_Sequential_Containers/exercise_9_32.md \"./ch09_Sequential_Containers/exercise_9_32.md\")|[练习9.33](ch09_Sequential_Containers/exercise_9_33.cpp \"./ch09_Sequential_Containers/exercise_9_33.cpp\")|[练习9.34](ch09_Sequential_Containers/exercise_9_34.cpp \"./ch09_Sequential_Containers/exercise_9_34.cpp\")|[练习9.35](ch09_Sequential_Containers/exercise_9_35.md \"./ch09_Sequential_Containers/exercise_9_35.md\")|[练习9.36](ch09_Sequential_Containers/exercise_9_36.md \"./ch09_Sequential_Containers/exercise_9_36.md\")|\n|[练习9.37](ch09_Sequential_Containers/exercise_9_37.md \"./ch09_Sequential_Containers/exercise_9_37.md\")|[练习9.38](ch09_Sequential_Containers/exercise_9_38.cpp \"./ch09_Sequential_Containers/exercise_9_38.cpp\")|[练习9.39](ch09_Sequential_Containers/exercise_9_39.md \"./ch09_Sequential_Containers/exercise_9_39.md\")|[练习9.40](ch09_Sequential_Containers/exercise_9_40.cpp \"./ch09_Sequential_Containers/exercise_9_40.cpp\")|[练习9.41](ch09_Sequential_Containers/exercise_9_41.cpp \"./ch09_Sequential_Containers/exercise_9_41.cpp\")|[练习9.42](ch09_Sequential_Containers/exercise_9_42.md \"./ch09_Sequential_Containers/exercise_9_42.md\")|\n|[练习9.43](ch09_Sequential_Containers/exercise_9_43.cpp \"./ch09_Sequential_Containers/exercise_9_43.cpp\")|[练习9.44](ch09_Sequential_Containers/exercise_9_44.cpp \"./ch09_Sequential_Containers/exercise_9_44.cpp\")|[练习9.45](ch09_Sequential_Containers/exercise_9_45.cpp \"./ch09_Sequential_Containers/exercise_9_45.cpp\")|[练习9.46](ch09_Sequential_Containers/exercise_9_46.cpp \"./ch09_Sequential_Containers/exercise_9_46.cpp\")|[练习9.47](ch09_Sequential_Containers/exercise_9_47.cpp \"./ch09_Sequential_Containers/exercise_9_47.cpp\")|[练习9.48](ch09_Sequential_Containers/exercise_9_48.cpp \"./ch09_Sequential_Containers/exercise_9_48.cpp\")|\n|[练习9.49](ch09_Sequential_Containers/exercise_9_49.cpp \"./ch09_Sequential_Containers/exercise_9_49.cpp\")|[练习9.50](ch09_Sequential_Containers/exercise_9_50.cpp \"./ch09_Sequential_Containers/exercise_9_50.cpp\")|[练习9.51](ch09_Sequential_Containers/exercise_9_51.cpp \"./ch09_Sequential_Containers/exercise_9_51.cpp\")|[练习9.52](ch09_Sequential_Containers/exercise_9_52.cpp \"./ch09_Sequential_Containers/exercise_9_52.cpp\")|\n\n### 案例代码\n\n- [改变容器的循环程序（p316）](ch09_Sequential_Containers/example_change_container_in_loop.cpp)\n\n- [从容器内部删除一个元素（p312）](ch09_Sequential_Containers/example_erase.cpp)\n\n- [forward_list操作（p313）](ch09_Sequential_Containers/example_forward_list.cpp)\n\n- [使用insert的返回值（p308）](ch09_Sequential_Containers/example_insert.cpp)\n\n- [迭代器范围iterator range（p296）](ch09_Sequential_Containers/example_iterator_range.cpp)\n\n## 第10章 泛型算法\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习10.1](ch10_Generic_Algorithms/exercise_10_01.cpp \"./ch10_Generic_Algorithms/exercise_10_01.cpp\")|[练习10.2](ch10_Generic_Algorithms/exercise_10_02.cpp \"./ch10_Generic_Algorithms/exercise_10_02.cpp\")|[练习10.3](ch10_Generic_Algorithms/exercise_10_03.cpp \"./ch10_Generic_Algorithms/exercise_10_03.cpp\")|[练习10.4](ch10_Generic_Algorithms/exercise_10_04.cpp \"./ch10_Generic_Algorithms/exercise_10_04.cpp\")|[练习10.5](ch10_Generic_Algorithms/exercise_10_05.md \"./ch10_Generic_Algorithms/exercise_10_05.md\")|[练习10.6](ch10_Generic_Algorithms/exercise_10_06.cpp \"./ch10_Generic_Algorithms/exercise_10_06.cpp\")|\n|[练习10.7](ch10_Generic_Algorithms/exercise_10_07.md \"./ch10_Generic_Algorithms/exercise_10_07.md\")|[练习10.8](ch10_Generic_Algorithms/exercise_10_08.md \"./ch10_Generic_Algorithms/exercise_10_08.md\")|[练习10.9](ch10_Generic_Algorithms/exercise_10_09.cpp \"./ch10_Generic_Algorithms/exercise_10_09.cpp\")|[练习10.10](ch10_Generic_Algorithms/exercise_10_10.md \"./ch10_Generic_Algorithms/exercise_10_10.md\")|[练习10.11](ch10_Generic_Algorithms/exercise_10_11.cpp \"./ch10_Generic_Algorithms/exercise_10_11.cpp\")|[练习10.12](ch10_Generic_Algorithms/exercise_10_12.cpp \"./ch10_Generic_Algorithms/exercise_10_12.cpp\")|\n|[练习10.13](ch10_Generic_Algorithms/exercise_10_13.cpp \"./ch10_Generic_Algorithms/exercise_10_13.cpp\")|[练习10.14](ch10_Generic_Algorithms/exercise_10_14.cpp \"./ch10_Generic_Algorithms/exercise_10_14.cpp\")|[练习10.15](ch10_Generic_Algorithms/exercise_10_15.cpp \"./ch10_Generic_Algorithms/exercise_10_15.cpp\")|[练习10.16](ch10_Generic_Algorithms/exercise_10_16.cpp \"./ch10_Generic_Algorithms/exercise_10_16.cpp\")|[练习10.17](ch10_Generic_Algorithms/exercise_10_17.cpp \"./ch10_Generic_Algorithms/exercise_10_17.cpp\")|[练习10.18](ch10_Generic_Algorithms/exercise_10_18.cpp \"./ch10_Generic_Algorithms/exercise_10_18.cpp\")|\n|[练习10.19](ch10_Generic_Algorithms/exercise_10_19.cpp \"./ch10_Generic_Algorithms/exercise_10_19.cpp\")|[练习10.20](ch10_Generic_Algorithms/exercise_10_20.cpp \"./ch10_Generic_Algorithms/exercise_10_20.cpp\")|[练习10.21](ch10_Generic_Algorithms/exercise_10_21.cpp \"./ch10_Generic_Algorithms/exercise_10_21.cpp\")|[练习10.22](ch10_Generic_Algorithms/exercise_10_22.cpp \"./ch10_Generic_Algorithms/exercise_10_22.cpp\")|[练习10.23](ch10_Generic_Algorithms/exercise_10_23.md \"./ch10_Generic_Algorithms/exercise_10_23.md\")|[练习10.24](ch10_Generic_Algorithms/exercise_10_24.cpp \"./ch10_Generic_Algorithms/exercise_10_24.cpp\")|\n|[练习10.25](ch10_Generic_Algorithms/exercise_10_25.cpp \"./ch10_Generic_Algorithms/exercise_10_25.cpp\")|[练习10.26](ch10_Generic_Algorithms/exercise_10_26.md \"./ch10_Generic_Algorithms/exercise_10_26.md\")|[练习10.27](ch10_Generic_Algorithms/exercise_10_27.cpp \"./ch10_Generic_Algorithms/exercise_10_27.cpp\")|[练习10.28](ch10_Generic_Algorithms/exercise_10_28.cpp \"./ch10_Generic_Algorithms/exercise_10_28.cpp\")|[练习10.29](ch10_Generic_Algorithms/exercise_10_29.cpp \"./ch10_Generic_Algorithms/exercise_10_29.cpp\")|[练习10.30](ch10_Generic_Algorithms/exercise_10_30.cpp \"./ch10_Generic_Algorithms/exercise_10_30.cpp\")|\n|[练习10.31](ch10_Generic_Algorithms/exercise_10_31.cpp \"./ch10_Generic_Algorithms/exercise_10_31.cpp\")|[练习10.32](ch10_Generic_Algorithms/exercise_10_32.cpp \"./ch10_Generic_Algorithms/exercise_10_32.cpp\")|[练习10.33](ch10_Generic_Algorithms/exercise_10_33.cpp \"./ch10_Generic_Algorithms/exercise_10_33.cpp\")|[练习10.34](ch10_Generic_Algorithms/exercise_10_34.cpp \"./ch10_Generic_Algorithms/exercise_10_34.cpp\")|[练习10.35](ch10_Generic_Algorithms/exercise_10_35.cpp \"./ch10_Generic_Algorithms/exercise_10_35.cpp\")|[练习10.36](ch10_Generic_Algorithms/exercise_10_36.cpp \"./ch10_Generic_Algorithms/exercise_10_36.cpp\")|\n|[练习10.37](ch10_Generic_Algorithms/exercise_10_37.cpp \"./ch10_Generic_Algorithms/exercise_10_37.cpp\")|[练习10.38](ch10_Generic_Algorithms/exercise_10_38.md \"./ch10_Generic_Algorithms/exercise_10_38.md\")|[练习10.39](ch10_Generic_Algorithms/exercise_10_39.md \"./ch10_Generic_Algorithms/exercise_10_39.md\")|[练习10.40](ch10_Generic_Algorithms/exercise_10_40.md \"./ch10_Generic_Algorithms/exercise_10_40.md\")|[练习10.41](ch10_Generic_Algorithms/exercise_10_41.md \"./ch10_Generic_Algorithms/exercise_10_41.md\")|[练习10.42](ch10_Generic_Algorithms/exercise_10_42.cpp \"./ch10_Generic_Algorithms/exercise_10_42.cpp\")|\n\n### 案例代码\n\n- [介绍back_inserter(p341)](ch10_Generic_Algorithms/example_back_inserter.cpp)\n\n- [istream_iterator操作（p359）](ch10_Generic_Algorithms/example_istream_iterator.cpp)\n\n- [使用lambda案例（p349）](ch10_Generic_Algorithms/example_lambda.cpp)\n\n- [ostream_iterator操作（p361）](ch10_Generic_Algorithms/example_ostream_iterator.cpp)\n\n- [stable_partition，将序列分为两组，满足谓词的放在前面一组](ch10_Generic_Algorithms/example_stable_partition.cpp)\n\n- [stable_sort（p345）](ch10_Generic_Algorithms/example_stable_sort.cpp)\n\n- [sort和unique函数，消除重复单词（p343）](ch10_Generic_Algorithms/example_unique.cpp)\n\n## 第11章 关联容器\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习11.1](ch11_Associative_Containers/exercise_11_01.md \"./ch11_Associative_Containers/exercise_11_01.md\")|[练习11.2](ch11_Associative_Containers/exercise_11_02.md \"./ch11_Associative_Containers/exercise_11_02.md\")|[练习11.3](ch11_Associative_Containers/exercise_11_03.cpp \"./ch11_Associative_Containers/exercise_11_03.cpp\")|[练习11.4](ch11_Associative_Containers/exercise_11_04.cpp \"./ch11_Associative_Containers/exercise_11_04.cpp\")|[练习11.5](ch11_Associative_Containers/exercise_11_05.md \"./ch11_Associative_Containers/exercise_11_05.md\")|[练习11.6](ch11_Associative_Containers/exercise_11_06.md \"./ch11_Associative_Containers/exercise_11_06.md\")|\n|[练习11.7](ch11_Associative_Containers/exercise_11_07.cpp \"./ch11_Associative_Containers/exercise_11_07.cpp\")|[练习11.8](ch11_Associative_Containers/exercise_11_08.cpp \"./ch11_Associative_Containers/exercise_11_08.cpp\")|[练习11.9](ch11_Associative_Containers/exercise_11_09.cpp \"./ch11_Associative_Containers/exercise_11_09.cpp\")|[练习11.10](ch11_Associative_Containers/exercise_11_10.md \"./ch11_Associative_Containers/exercise_11_10.md\")|[练习11.11](ch11_Associative_Containers/exercise_11_11.cpp \"./ch11_Associative_Containers/exercise_11_11.cpp\")|[练习11.12](ch11_Associative_Containers/exercise_11_12.cpp \"./ch11_Associative_Containers/exercise_11_12.cpp\")|\n|[练习11.13](ch11_Associative_Containers/exercise_11_13.cpp \"./ch11_Associative_Containers/exercise_11_13.cpp\")|[练习11.14](ch11_Associative_Containers/exercise_11_14.cpp \"./ch11_Associative_Containers/exercise_11_14.cpp\")|[练习11.15](ch11_Associative_Containers/exercise_11_15.md \"./ch11_Associative_Containers/exercise_11_15.md\")|[练习11.16](ch11_Associative_Containers/exercise_11_16.md \"./ch11_Associative_Containers/exercise_11_16.md\")|[练习11.17](ch11_Associative_Containers/exercise_11_17.cpp \"./ch11_Associative_Containers/exercise_11_17.cpp\")|[练习11.18](ch11_Associative_Containers/exercise_11_18.md \"./ch11_Associative_Containers/exercise_11_18.md\")|\n|[练习11.19](ch11_Associative_Containers/exercise_11_19.cpp \"./ch11_Associative_Containers/exercise_11_19.cpp\")|[练习11.20](ch11_Associative_Containers/exercise_11_20.cpp \"./ch11_Associative_Containers/exercise_11_20.cpp\")|[练习11.21](ch11_Associative_Containers/exercise_11_21.md \"./ch11_Associative_Containers/exercise_11_21.md\")|[练习11.22](ch11_Associative_Containers/exercise_11_22.md \"./ch11_Associative_Containers/exercise_11_22.md\")|[练习11.23](ch11_Associative_Containers/exercise_11_23.cpp \"./ch11_Associative_Containers/exercise_11_23.cpp\")|[练习11.24](ch11_Associative_Containers/exercise_11_24.md \"./ch11_Associative_Containers/exercise_11_24.md\")|\n|[练习11.25](ch11_Associative_Containers/exercise_11_25.md \"./ch11_Associative_Containers/exercise_11_25.md\")|[练习11.26](ch11_Associative_Containers/exercise_11_26.md \"./ch11_Associative_Containers/exercise_11_26.md\")|[练习11.27](ch11_Associative_Containers/exercise_11_27.md \"./ch11_Associative_Containers/exercise_11_27.md\")|[练习11.28](ch11_Associative_Containers/exercise_11_28.cpp \"./ch11_Associative_Containers/exercise_11_28.cpp\")|[练习11.29](ch11_Associative_Containers/exercise_11_29.md \"./ch11_Associative_Containers/exercise_11_29.md\")|[练习11.30](ch11_Associative_Containers/exercise_11_30.md \"./ch11_Associative_Containers/exercise_11_30.md\")|\n|[练习11.31](ch11_Associative_Containers/exercise_11_31.cpp \"./ch11_Associative_Containers/exercise_11_31.cpp\")|[练习11.32](ch11_Associative_Containers/exercise_11_32.cpp \"./ch11_Associative_Containers/exercise_11_32.cpp\")|[练习11.33](ch11_Associative_Containers/exercise_11_33.cpp \"./ch11_Associative_Containers/exercise_11_33.cpp\")|[练习11.34](ch11_Associative_Containers/exercise_11_34.md \"./ch11_Associative_Containers/exercise_11_34.md\")|[练习11.35](ch11_Associative_Containers/exercise_11_35.md \"./ch11_Associative_Containers/exercise_11_35.md\")|[练习11.36](ch11_Associative_Containers/exercise_11_36.md \"./ch11_Associative_Containers/exercise_11_36.md\")|\n|[练习11.37](ch11_Associative_Containers/exercise_11_37.md \"./ch11_Associative_Containers/exercise_11_37.md\")|[练习11.38](ch11_Associative_Containers/exercise_11_38.cpp \"./ch11_Associative_Containers/exercise_11_38.cpp\")|\n\n### 案例代码\n\n- [初始化multiset(p377)](ch11_Associative_Containers/example_init_multiset.cpp)\n\n- [创建pair对象的函数](ch11_Associative_Containers/example_make_pair.cpp)\n\n- [使用map（p375）](ch11_Associative_Containers/example_map.cpp)\n\n- [使用set（p375）](ch11_Associative_Containers/example_set.cpp)\n\n- [无序容器对关键字的要求（p396）](ch11_Associative_Containers/example_unordered_set.cpp)\n\n- [单词转换程序（p392）](ch11_Associative_Containers/example_word_transform.cpp)\n\n## 第12章 动态内存\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习12.1](ch12_Dynamic_Memory/exercise_12_01.cpp \"./ch12_Dynamic_Memory/exercise_12_01.cpp\")|[练习12.2](ch12_Dynamic_Memory/exercise_12_02.md \"./ch12_Dynamic_Memory/exercise_12_02.md\")|[练习12.3](ch12_Dynamic_Memory/exercise_12_03.md \"./ch12_Dynamic_Memory/exercise_12_03.md\")|[练习12.4](ch12_Dynamic_Memory/exercise_12_04.md \"./ch12_Dynamic_Memory/exercise_12_04.md\")|[练习12.5](ch12_Dynamic_Memory/exercise_12_05.md \"./ch12_Dynamic_Memory/exercise_12_05.md\")|[练习12.6](ch12_Dynamic_Memory/exercise_12_06.cpp \"./ch12_Dynamic_Memory/exercise_12_06.cpp\")|\n|[练习12.7](ch12_Dynamic_Memory/exercise_12_07.cpp \"./ch12_Dynamic_Memory/exercise_12_07.cpp\")|[练习12.8](ch12_Dynamic_Memory/exercise_12_08.md \"./ch12_Dynamic_Memory/exercise_12_08.md\")|[练习12.9](ch12_Dynamic_Memory/exercise_12_09.md \"./ch12_Dynamic_Memory/exercise_12_09.md\")|[练习12.10](ch12_Dynamic_Memory/exercise_12_10.md \"./ch12_Dynamic_Memory/exercise_12_10.md\")|[练习12.11](ch12_Dynamic_Memory/exercise_12_11.md \"./ch12_Dynamic_Memory/exercise_12_11.md\")|[练习12.12](ch12_Dynamic_Memory/exercise_12_12.md \"./ch12_Dynamic_Memory/exercise_12_12.md\")|\n|[练习12.13](ch12_Dynamic_Memory/exercise_12_13.md \"./ch12_Dynamic_Memory/exercise_12_13.md\")|[练习12.14](ch12_Dynamic_Memory/exercise_12_14.md \"./ch12_Dynamic_Memory/exercise_12_14.md\")|[练习12.15](ch12_Dynamic_Memory/exercise_12_15.cpp \"./ch12_Dynamic_Memory/exercise_12_15.cpp\")|[练习12.16](ch12_Dynamic_Memory/exercise_12_16.cpp \"./ch12_Dynamic_Memory/exercise_12_16.cpp\")|[练习12.17](ch12_Dynamic_Memory/exercise_12_17.md \"./ch12_Dynamic_Memory/exercise_12_17.md\")|[练习12.18](ch12_Dynamic_Memory/exercise_12_18.md \"./ch12_Dynamic_Memory/exercise_12_18.md\")|\n|[练习12.19](ch12_Dynamic_Memory/exercise_12_19.md \"./ch12_Dynamic_Memory/exercise_12_19.md\")|[练习12.20](ch12_Dynamic_Memory/exercise_12_20.cpp \"./ch12_Dynamic_Memory/exercise_12_20.cpp\")|[练习12.21](ch12_Dynamic_Memory/exercise_12_21.md \"./ch12_Dynamic_Memory/exercise_12_21.md\")|[练习12.22](ch12_Dynamic_Memory/exercise_12_22.md \"./ch12_Dynamic_Memory/exercise_12_22.md\")|[练习12.23](ch12_Dynamic_Memory/exercise_12_23.cpp \"./ch12_Dynamic_Memory/exercise_12_23.cpp\")|[练习12.24](ch12_Dynamic_Memory/exercise_12_24.cpp \"./ch12_Dynamic_Memory/exercise_12_24.cpp\")|\n|[练习12.25](ch12_Dynamic_Memory/exercise_12_25.md \"./ch12_Dynamic_Memory/exercise_12_25.md\")|[练习12.26](ch12_Dynamic_Memory/exercise_12_26.cpp \"./ch12_Dynamic_Memory/exercise_12_26.cpp\")|[练习12.27](ch12_Dynamic_Memory/exercise_12_27.cpp \"./ch12_Dynamic_Memory/exercise_12_27.cpp\")|[练习12.28](ch12_Dynamic_Memory/exercise_12_28.cpp \"./ch12_Dynamic_Memory/exercise_12_28.cpp\")|[练习12.29](ch12_Dynamic_Memory/exercise_12_29.md \"./ch12_Dynamic_Memory/exercise_12_29.md\")|[练习12.30](ch12_Dynamic_Memory/exercise_12_30.md \"./ch12_Dynamic_Memory/exercise_12_30.md\")|\n|[练习12.31](ch12_Dynamic_Memory/exercise_12_31.md \"./ch12_Dynamic_Memory/exercise_12_31.md\")|[练习12.32](ch12_Dynamic_Memory/exercise_12_32.cpp \"./ch12_Dynamic_Memory/exercise_12_32.cpp\")|[练习12.33](ch12_Dynamic_Memory/exercise_12_33.md \"./ch12_Dynamic_Memory/exercise_12_33.md\")|\n\n### 案例代码\n\n- [allocator类（427）](ch12_Dynamic_Memory/example_allocator.cpp)\n\n- [使用智能指针共享底层数据(p405)](ch12_Dynamic_Memory/example_StrBlob)\n\n- [weak_ptr的一个例子(p421)](ch12_Dynamic_Memory/example_StrBlobPtr)\n\n- [文本查询程序(p430始)](ch12_Dynamic_Memory/example_TextQuery)\n\n- [使用我们自己的释放操作（p416）](ch12_Dynamic_Memory/example_using_own_deletion.cpp)\n\n## 第13章 拷贝控制\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习13.1](ch13_Copy_Control/exercise_13_01.md \"./ch13_Copy_Control/exercise_13_01.md\")|[练习13.2](ch13_Copy_Control/exercise_13_02.md \"./ch13_Copy_Control/exercise_13_02.md\")|[练习13.3](ch13_Copy_Control/exercise_13_03.md \"./ch13_Copy_Control/exercise_13_03.md\")|[练习13.4](ch13_Copy_Control/exercise_13_04.md \"./ch13_Copy_Control/exercise_13_04.md\")|[练习13.5](ch13_Copy_Control/exercise_13_05.cpp \"./ch13_Copy_Control/exercise_13_05.cpp\")|[练习13.6](ch13_Copy_Control/exercise_13_06.md \"./ch13_Copy_Control/exercise_13_06.md\")|\n|[练习13.7](ch13_Copy_Control/exercise_13_07.md \"./ch13_Copy_Control/exercise_13_07.md\")|[练习13.8](ch13_Copy_Control/exercise_13_08.cpp \"./ch13_Copy_Control/exercise_13_08.cpp\")|[练习13.9](ch13_Copy_Control/exercise_13_09.md \"./ch13_Copy_Control/exercise_13_09.md\")|[练习13.10](ch13_Copy_Control/exercise_13_10.md \"./ch13_Copy_Control/exercise_13_10.md\")|[练习13.11](ch13_Copy_Control/exercise_13_11.cpp \"./ch13_Copy_Control/exercise_13_11.cpp\")|[练习13.12](ch13_Copy_Control/exercise_13_12.md \"./ch13_Copy_Control/exercise_13_12.md\")|\n|[练习13.13](ch13_Copy_Control/exercise_13_13.cpp \"./ch13_Copy_Control/exercise_13_13.cpp\")|[练习13.14](ch13_Copy_Control/exercise_13_14.md \"./ch13_Copy_Control/exercise_13_14.md\")|[练习13.15](ch13_Copy_Control/exercise_13_15.md \"./ch13_Copy_Control/exercise_13_15.md\")|[练习13.16](ch13_Copy_Control/exercise_13_16.md \"./ch13_Copy_Control/exercise_13_16.md\")|[练习13.17](ch13_Copy_Control/exercise_13_17.cpp \"./ch13_Copy_Control/exercise_13_17.cpp\")|[练习13.18](ch13_Copy_Control/exercise_13_18.cpp \"./ch13_Copy_Control/exercise_13_18.cpp\")|\n|[练习13.19](ch13_Copy_Control/exercise_13_19.cpp \"./ch13_Copy_Control/exercise_13_19.cpp\")|[练习13.20](ch13_Copy_Control/exercise_13_20.md \"./ch13_Copy_Control/exercise_13_20.md\")|[练习13.21](ch13_Copy_Control/exercise_13_21.md \"./ch13_Copy_Control/exercise_13_21.md\")|[练习13.22](ch13_Copy_Control/exercise_13_22.md \"./ch13_Copy_Control/exercise_13_22.md\")|[练习13.23](ch13_Copy_Control/exercise_13_23.md \"./ch13_Copy_Control/exercise_13_23.md\")|[练习13.24](ch13_Copy_Control/exercise_13_24.md \"./ch13_Copy_Control/exercise_13_24.md\")|\n|[练习13.25](ch13_Copy_Control/exercise_13_25.md \"./ch13_Copy_Control/exercise_13_25.md\")|[练习13.26](ch13_Copy_Control/exercise_13_26.cpp \"./ch13_Copy_Control/exercise_13_26.cpp\")|[练习13.27](ch13_Copy_Control/exercise_13_27.md \"./ch13_Copy_Control/exercise_13_27.md\")|[练习13.28](ch13_Copy_Control/exercise_13_28.cpp \"./ch13_Copy_Control/exercise_13_28.cpp\")|[练习13.29](ch13_Copy_Control/exercise_13_29.md \"./ch13_Copy_Control/exercise_13_29.md\")|[练习13.30](ch13_Copy_Control/exercise_13_30.cpp \"./ch13_Copy_Control/exercise_13_30.cpp\")|\n|[练习13.31](ch13_Copy_Control/exercise_13_31.cpp \"./ch13_Copy_Control/exercise_13_31.cpp\")|[练习13.32](ch13_Copy_Control/exercise_13_32.md \"./ch13_Copy_Control/exercise_13_32.md\")|[练习13.33](ch13_Copy_Control/exercise_13_33.md \"./ch13_Copy_Control/exercise_13_33.md\")|[练习13.34](ch13_Copy_Control/exercise_13_34.md \"./ch13_Copy_Control/exercise_13_34.md\")|[练习13.35](ch13_Copy_Control/exercise_13_35.md \"./ch13_Copy_Control/exercise_13_35.md\")|[练习13.36](ch13_Copy_Control/exercise_13_36.md \"./ch13_Copy_Control/exercise_13_36.md\")|\n|[练习13.37](ch13_Copy_Control/exercise_13_37.md \"./ch13_Copy_Control/exercise_13_37.md\")|[练习13.38](ch13_Copy_Control/exercise_13_38.md \"./ch13_Copy_Control/exercise_13_38.md\")|[练习13.39](ch13_Copy_Control/exercise_13_39.md \"./ch13_Copy_Control/exercise_13_39.md\")|[练习13.40](ch13_Copy_Control/exercise_13_40.md \"./ch13_Copy_Control/exercise_13_40.md\")|[练习13.41](ch13_Copy_Control/exercise_13_41.md \"./ch13_Copy_Control/exercise_13_41.md\")|[练习13.42](ch13_Copy_Control/exercise_13_42.md \"./ch13_Copy_Control/exercise_13_42.md\")|\n|[练习13.43](ch13_Copy_Control/exercise_13_43.md \"./ch13_Copy_Control/exercise_13_43.md\")|[练习13.44](ch13_Copy_Control/exercise_13_44.md \"./ch13_Copy_Control/exercise_13_44.md\")|[练习13.45](ch13_Copy_Control/exercise_13_45.md \"./ch13_Copy_Control/exercise_13_45.md\")|[练习13.46](ch13_Copy_Control/exercise_13_46.md \"./ch13_Copy_Control/exercise_13_46.md\")|[练习13.47](ch13_Copy_Control/exercise_13_47.md \"./ch13_Copy_Control/exercise_13_47.md\")|[练习13.48](ch13_Copy_Control/exercise_13_48.md \"./ch13_Copy_Control/exercise_13_48.md\")|\n|[练习13.49](ch13_Copy_Control/exercise_13_49.md \"./ch13_Copy_Control/exercise_13_49.md\")|[练习13.50](ch13_Copy_Control/exercise_13_50.md \"./ch13_Copy_Control/exercise_13_50.md\")|[练习13.51](ch13_Copy_Control/exercise_13_51.md \"./ch13_Copy_Control/exercise_13_51.md\")|[练习13.52](ch13_Copy_Control/exercise_13_52.md \"./ch13_Copy_Control/exercise_13_52.md\")|[练习13.53](ch13_Copy_Control/exercise_13_53.cpp \"./ch13_Copy_Control/exercise_13_53.cpp\")|[练习13.54](ch13_Copy_Control/exercise_13_54.md \"./ch13_Copy_Control/exercise_13_54.md\")|\n|[练习13.55](ch13_Copy_Control/exercise_13_55.md \"./ch13_Copy_Control/exercise_13_55.md\")|[练习13.56](ch13_Copy_Control/exercise_13_56.md \"./ch13_Copy_Control/exercise_13_56.md\")|[练习13.57](ch13_Copy_Control/exercise_13_57.md \"./ch13_Copy_Control/exercise_13_57.md\")|[练习13.58](ch13_Copy_Control/exercise_13_58.cpp \"./ch13_Copy_Control/exercise_13_58.cpp\")|\n\n### 案例代码\n\n- [拷贝控制示例（p460）](ch13_Copy_Control/example_Copy_Control)\n\n- [使用引用计数的类（p456）](ch13_Copy_Control/example_Reference_Count)\n\n- [引用限定符（p483）](ch13_Copy_Control/example_Reference_Qualifier)\n\n- [自定义String简化版本（p470练习13.44）](ch13_Copy_Control/example_String)\n\n- [动态内存管理类（p464）](ch13_Copy_Control/example_StrVec)\n\n## 第14章 操作重载与类型转换\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习14.1](ch14_Overloaded_Operations_and_Conversions/exercise_14_01.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_01.md\")|[练习14.2](ch14_Overloaded_Operations_and_Conversions/exercise_14_02.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_02.md\")|[练习14.3](ch14_Overloaded_Operations_and_Conversions/exercise_14_03.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_03.md\")|[练习14.4](ch14_Overloaded_Operations_and_Conversions/exercise_14_04.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_04.md\")|[练习14.5](ch14_Overloaded_Operations_and_Conversions/exercise_14_05.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_05.cpp\")|[练习14.6](ch14_Overloaded_Operations_and_Conversions/exercise_14_06.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_06.md\")|\n|[练习14.7](ch14_Overloaded_Operations_and_Conversions/exercise_14_07.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_07.md\")|[练习14.8](ch14_Overloaded_Operations_and_Conversions/exercise_14_08.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_08.md\")|[练习14.9](ch14_Overloaded_Operations_and_Conversions/exercise_14_09.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_09.md\")|[练习14.10](ch14_Overloaded_Operations_and_Conversions/exercise_14_10.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_10.md\")|[练习14.11](ch14_Overloaded_Operations_and_Conversions/exercise_14_11.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_11.md\")|[练习14.12](ch14_Overloaded_Operations_and_Conversions/exercise_14_12.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_12.md\")|\n|[练习14.13](ch14_Overloaded_Operations_and_Conversions/exercise_14_13.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_13.md\")|[练习14.14](ch14_Overloaded_Operations_and_Conversions/exercise_14_14.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_14.md\")|[练习14.15](ch14_Overloaded_Operations_and_Conversions/exercise_14_15.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_15.md\")|[练习14.16](ch14_Overloaded_Operations_and_Conversions/exercise_14_16.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_16.md\")|[练习14.17](ch14_Overloaded_Operations_and_Conversions/exercise_14_17.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_17.md\")|[练习14.18](ch14_Overloaded_Operations_and_Conversions/exercise_14_18.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_18.md\")|\n|[练习14.19](ch14_Overloaded_Operations_and_Conversions/exercise_14_19.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_19.md\")|[练习14.20](ch14_Overloaded_Operations_and_Conversions/exercise_14_20.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_20.md\")|[练习14.21](ch14_Overloaded_Operations_and_Conversions/exercise_14_21.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_21.md\")|[练习14.22](ch14_Overloaded_Operations_and_Conversions/exercise_14_22.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_22.md\")|[练习14.23](ch14_Overloaded_Operations_and_Conversions/exercise_14_23.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_23.md\")|[练习14.24](ch14_Overloaded_Operations_and_Conversions/exercise_14_24.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_24.cpp\")|\n|[练习14.25](ch14_Overloaded_Operations_and_Conversions/exercise_14_25.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_25.md\")|[练习14.26](ch14_Overloaded_Operations_and_Conversions/exercise_14_26.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_26.md\")|[练习14.27](ch14_Overloaded_Operations_and_Conversions/exercise_14_27.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_27.md\")|[练习14.28](ch14_Overloaded_Operations_and_Conversions/exercise_14_28.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_28.md\")|[练习14.29](ch14_Overloaded_Operations_and_Conversions/exercise_14_29.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_29.md\")|[练习14.30](ch14_Overloaded_Operations_and_Conversions/exercise_14_30.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_30.md\")|\n|[练习14.31](ch14_Overloaded_Operations_and_Conversions/exercise_14_31.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_31.md\")|[练习14.32](ch14_Overloaded_Operations_and_Conversions/exercise_14_32.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_32.cpp\")|[练习14.33](ch14_Overloaded_Operations_and_Conversions/exercise_14_33.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_33.md\")|[练习14.34](ch14_Overloaded_Operations_and_Conversions/exercise_14_34.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_34.cpp\")|[练习14.35](ch14_Overloaded_Operations_and_Conversions/exercise_14_35.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_35.cpp\")|[练习14.36](ch14_Overloaded_Operations_and_Conversions/exercise_14_36.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_36.cpp\")|\n|[练习14.37](ch14_Overloaded_Operations_and_Conversions/exercise_14_37.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_37.cpp\")|[练习14.38](ch14_Overloaded_Operations_and_Conversions/exercise_14_38.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_38.cpp\")|[练习14.39](ch14_Overloaded_Operations_and_Conversions/exercise_14_39.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_39.cpp\")|[练习14.40](ch14_Overloaded_Operations_and_Conversions/exercise_14_40.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_40.cpp\")|[练习14.41](ch14_Overloaded_Operations_and_Conversions/exercise_14_41.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_41.md\")|[练习14.42](ch14_Overloaded_Operations_and_Conversions/exercise_14_42.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_42.cpp\")|\n|[练习14.43](ch14_Overloaded_Operations_and_Conversions/exercise_14_43.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_43.cpp\")|[练习14.44](ch14_Overloaded_Operations_and_Conversions/exercise_14_44.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_44.cpp\")|[练习14.45](ch14_Overloaded_Operations_and_Conversions/exercise_14_45.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_45.md\")|[练习14.46](ch14_Overloaded_Operations_and_Conversions/exercise_14_46.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_46.md\")|[练习14.47](ch14_Overloaded_Operations_and_Conversions/exercise_14_47.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_47.md\")|[练习14.48](ch14_Overloaded_Operations_and_Conversions/exercise_14_48.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_48.md\")|\n|[练习14.49](ch14_Overloaded_Operations_and_Conversions/exercise_14_49.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_49.md\")|[练习14.50](ch14_Overloaded_Operations_and_Conversions/exercise_14_50.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_50.cpp\")|[练习14.51](ch14_Overloaded_Operations_and_Conversions/exercise_14_51.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_51.cpp\")|[练习14.52](ch14_Overloaded_Operations_and_Conversions/exercise_14_52.md \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_52.md\")|[练习14.53](ch14_Overloaded_Operations_and_Conversions/exercise_14_53.cpp \"./ch14_Overloaded_Operations_and_Conversions/exercise_14_53.cpp\")|\n\n### 案例代码\n\n- [定义含有类型转换运算符的类（p514）](ch14_Overloaded_Operations_and_Conversions/example_conversion_operators.cpp)\n\n- [函数调用运算符（p506）](ch14_Overloaded_Operations_and_Conversions/example_function-call_operator.cpp)\n\n- [可调用对象与function（p511）](ch14_Overloaded_Operations_and_Conversions/example_function.cpp)\n\n- [标准库定义的函数对象（p509）](ch14_Overloaded_Operations_and_Conversions/example_std_funcobj.cpp)\n\n## 第15章 面向对象程序设计\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习15.1](ch15_Object-Oriented_Programming/exercise_15_01.md \"./ch15_Object-Oriented_Programming/exercise_15_01.md\")|[练习15.2](ch15_Object-Oriented_Programming/exercise_15_02.md \"./ch15_Object-Oriented_Programming/exercise_15_02.md\")|[练习15.3](ch15_Object-Oriented_Programming/exercise_15_03.md \"./ch15_Object-Oriented_Programming/exercise_15_03.md\")|[练习15.4](ch15_Object-Oriented_Programming/exercise_15_04.md \"./ch15_Object-Oriented_Programming/exercise_15_04.md\")|[练习15.5](ch15_Object-Oriented_Programming/exercise_15_05.md \"./ch15_Object-Oriented_Programming/exercise_15_05.md\")|[练习15.6](ch15_Object-Oriented_Programming/exercise_15_06.cpp \"./ch15_Object-Oriented_Programming/exercise_15_06.cpp\")|\n|[练习15.7](ch15_Object-Oriented_Programming/exercise_15_07.cpp \"./ch15_Object-Oriented_Programming/exercise_15_07.cpp\")|[练习15.8](ch15_Object-Oriented_Programming/exercise_15_08.md \"./ch15_Object-Oriented_Programming/exercise_15_08.md\")|[练习15.9](ch15_Object-Oriented_Programming/exercise_15_09.md \"./ch15_Object-Oriented_Programming/exercise_15_09.md\")|[练习15.10](ch15_Object-Oriented_Programming/exercise_15_10.md \"./ch15_Object-Oriented_Programming/exercise_15_10.md\")|[练习15.11](ch15_Object-Oriented_Programming/exercise_15_11.md \"./ch15_Object-Oriented_Programming/exercise_15_11.md\")|[练习15.12](ch15_Object-Oriented_Programming/exercise_15_12.md \"./ch15_Object-Oriented_Programming/exercise_15_12.md\")|\n|[练习15.13](ch15_Object-Oriented_Programming/exercise_15_13.md \"./ch15_Object-Oriented_Programming/exercise_15_13.md\")|[练习15.14](ch15_Object-Oriented_Programming/exercise_15_14.md \"./ch15_Object-Oriented_Programming/exercise_15_14.md\")|[练习15.15](ch15_Object-Oriented_Programming/exercise_15_15.md \"./ch15_Object-Oriented_Programming/exercise_15_15.md\")|[练习15.16](ch15_Object-Oriented_Programming/exercise_15_16.cpp \"./ch15_Object-Oriented_Programming/exercise_15_16.cpp\")|[练习15.17](ch15_Object-Oriented_Programming/exercise_15_17.md \"./ch15_Object-Oriented_Programming/exercise_15_17.md\")|[练习15.18](ch15_Object-Oriented_Programming/exercise_15_18.cpp \"./ch15_Object-Oriented_Programming/exercise_15_18.cpp\")|\n|[练习15.19](ch15_Object-Oriented_Programming/exercise_15_19.cpp \"./ch15_Object-Oriented_Programming/exercise_15_19.cpp\")|[练习15.20](ch15_Object-Oriented_Programming/exercise_15_20.md \"./ch15_Object-Oriented_Programming/exercise_15_20.md\")|[练习15.21](ch15_Object-Oriented_Programming/exercise_15_21.cpp \"./ch15_Object-Oriented_Programming/exercise_15_21.cpp\")|[练习15.22](ch15_Object-Oriented_Programming/exercise_15_22.md \"./ch15_Object-Oriented_Programming/exercise_15_22.md\")|[练习15.23](ch15_Object-Oriented_Programming/exercise_15_23.cpp \"./ch15_Object-Oriented_Programming/exercise_15_23.cpp\")|[练习15.24](ch15_Object-Oriented_Programming/exercise_15_24.md \"./ch15_Object-Oriented_Programming/exercise_15_24.md\")|\n|[练习15.25](ch15_Object-Oriented_Programming/exercise_15_25.md \"./ch15_Object-Oriented_Programming/exercise_15_25.md\")|[练习15.26](ch15_Object-Oriented_Programming/exercise_15_26.md \"./ch15_Object-Oriented_Programming/exercise_15_26.md\")|[练习15.27](ch15_Object-Oriented_Programming/exercise_15_27.cpp \"./ch15_Object-Oriented_Programming/exercise_15_27.cpp\")|[练习15.28](ch15_Object-Oriented_Programming/exercise_15_28.md \"./ch15_Object-Oriented_Programming/exercise_15_28.md\")|[练习15.29](ch15_Object-Oriented_Programming/exercise_15_29.md \"./ch15_Object-Oriented_Programming/exercise_15_29.md\")|[练习15.30](ch15_Object-Oriented_Programming/exercise_15_30.md \"./ch15_Object-Oriented_Programming/exercise_15_30.md\")|\n|[练习15.31](ch15_Object-Oriented_Programming/exercise_15_31.md \"./ch15_Object-Oriented_Programming/exercise_15_31.md\")|[练习15.32](ch15_Object-Oriented_Programming/exercise_15_32.md \"./ch15_Object-Oriented_Programming/exercise_15_32.md\")|[练习15.33](ch15_Object-Oriented_Programming/exercise_15_33.md \"./ch15_Object-Oriented_Programming/exercise_15_33.md\")|[练习15.34](ch15_Object-Oriented_Programming/exercise_15_34.md \"./ch15_Object-Oriented_Programming/exercise_15_34.md\")|[练习15.35](ch15_Object-Oriented_Programming/exercise_15_35.md \"./ch15_Object-Oriented_Programming/exercise_15_35.md\")|[练习15.36](ch15_Object-Oriented_Programming/exercise_15_36.md \"./ch15_Object-Oriented_Programming/exercise_15_36.md\")|\n|[练习15.37](ch15_Object-Oriented_Programming/exercise_15_37.md \"./ch15_Object-Oriented_Programming/exercise_15_37.md\")|[练习15.38](ch15_Object-Oriented_Programming/exercise_15_38.md \"./ch15_Object-Oriented_Programming/exercise_15_38.md\")|[练习15.39](ch15_Object-Oriented_Programming/exercise_15_39.md \"./ch15_Object-Oriented_Programming/exercise_15_39.md\")|[练习15.40](ch15_Object-Oriented_Programming/exercise_15_40.md \"./ch15_Object-Oriented_Programming/exercise_15_40.md\")|[练习15.41](ch15_Object-Oriented_Programming/exercise_15_41.md \"./ch15_Object-Oriented_Programming/exercise_15_41.md\")|[练习15.42](ch15_Object-Oriented_Programming/exercise_15_42.md \"./ch15_Object-Oriented_Programming/exercise_15_42.md\")|\n\n### 案例代码\n\n- [抽象基类（p540）](ch15_Object-Oriented_Programming/example_abstract_base_class.cpp)\n\n- [定义基类（p528）](ch15_Object-Oriented_Programming/example_base_class.cpp)\n\n- [编写Basket类（容器与继承案例，p559）](ch15_Object-Oriented_Programming/example_basket.cpp)\n\n- [派生类的拷贝控制成员（p554）](ch15_Object-Oriented_Programming/example_derived_class_copy_control_members.cpp)\n\n- [定义派生类（p529）](ch15_Object-Oriented_Programming/example_derived_class.cpp)\n\n- [文本查询程序再探(p562)](ch15_Object-Oriented_Programming/example_TextQuery)\n\n## 第16章 模板与泛型编程\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习16.1](ch16_Templates_and_GenericProgramming/exercise_16_01.md \"./ch16_Templates_and_GenericProgramming/exercise_16_01.md\")|[练习16.2](ch16_Templates_and_GenericProgramming/exercise_16_02.md \"./ch16_Templates_and_GenericProgramming/exercise_16_02.md\")|[练习16.3](ch16_Templates_and_GenericProgramming/exercise_16_03.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_03.cpp\")|[练习16.4](ch16_Templates_and_GenericProgramming/exercise_16_04.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_04.cpp\")|[练习16.5](ch16_Templates_and_GenericProgramming/exercise_16_05.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_05.cpp\")|[练习16.6](ch16_Templates_and_GenericProgramming/exercise_16_06.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_06.cpp\")|\n|[练习16.7](ch16_Templates_and_GenericProgramming/exercise_16_07.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_07.cpp\")|[练习16.8](ch16_Templates_and_GenericProgramming/exercise_16_08.md \"./ch16_Templates_and_GenericProgramming/exercise_16_08.md\")|[练习16.9](ch16_Templates_and_GenericProgramming/exercise_16_09.md \"./ch16_Templates_and_GenericProgramming/exercise_16_09.md\")|[练习16.10](ch16_Templates_and_GenericProgramming/exercise_16_10.md \"./ch16_Templates_and_GenericProgramming/exercise_16_10.md\")|[练习16.11](ch16_Templates_and_GenericProgramming/exercise_16_11.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_11.cpp\")|[练习16.12](ch16_Templates_and_GenericProgramming/exercise_16_12.md \"./ch16_Templates_and_GenericProgramming/exercise_16_12.md\")|\n|[练习16.13](ch16_Templates_and_GenericProgramming/exercise_16_13.md \"./ch16_Templates_and_GenericProgramming/exercise_16_13.md\")|[练习16.14](ch16_Templates_and_GenericProgramming/exercise_16_14.md \"./ch16_Templates_and_GenericProgramming/exercise_16_14.md\")|[练习16.15](ch16_Templates_and_GenericProgramming/exercise_16_15.md \"./ch16_Templates_and_GenericProgramming/exercise_16_15.md\")|[练习16.16](ch16_Templates_and_GenericProgramming/exercise_16_16.md \"./ch16_Templates_and_GenericProgramming/exercise_16_16.md\")|[练习16.17](ch16_Templates_and_GenericProgramming/exercise_16_17.md \"./ch16_Templates_and_GenericProgramming/exercise_16_17.md\")|[练习16.18](ch16_Templates_and_GenericProgramming/exercise_16_18.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_18.cpp\")|\n|[练习16.19](ch16_Templates_and_GenericProgramming/exercise_16_19.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_19.cpp\")|[练习16.20](ch16_Templates_and_GenericProgramming/exercise_16_20.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_20.cpp\")|[练习16.21](ch16_Templates_and_GenericProgramming/exercise_16_21.md \"./ch16_Templates_and_GenericProgramming/exercise_16_21.md\")|[练习16.22](ch16_Templates_and_GenericProgramming/exercise_16_22.md \"./ch16_Templates_and_GenericProgramming/exercise_16_22.md\")|[练习16.23](ch16_Templates_and_GenericProgramming/exercise_16_23.md \"./ch16_Templates_and_GenericProgramming/exercise_16_23.md\")|[练习16.24](ch16_Templates_and_GenericProgramming/exercise_16_24.md \"./ch16_Templates_and_GenericProgramming/exercise_16_24.md\")|\n|[练习16.25](ch16_Templates_and_GenericProgramming/exercise_16_25.md \"./ch16_Templates_and_GenericProgramming/exercise_16_25.md\")|[练习16.26](ch16_Templates_and_GenericProgramming/exercise_16_26.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_26.cpp\")|[练习16.27](ch16_Templates_and_GenericProgramming/exercise_16_27.md \"./ch16_Templates_and_GenericProgramming/exercise_16_27.md\")|[练习16.28](ch16_Templates_and_GenericProgramming/exercise_16_28.md \"./ch16_Templates_and_GenericProgramming/exercise_16_28.md\")|[练习16.29](ch16_Templates_and_GenericProgramming/exercise_16_29.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_29.cpp\")|[练习16.30](ch16_Templates_and_GenericProgramming/exercise_16_30.md \"./ch16_Templates_and_GenericProgramming/exercise_16_30.md\")|\n|[练习16.31](ch16_Templates_and_GenericProgramming/exercise_16_31.md \"./ch16_Templates_and_GenericProgramming/exercise_16_31.md\")|[练习16.32](ch16_Templates_and_GenericProgramming/exercise_16_32.md \"./ch16_Templates_and_GenericProgramming/exercise_16_32.md\")|[练习16.33](ch16_Templates_and_GenericProgramming/exercise_16_33.md \"./ch16_Templates_and_GenericProgramming/exercise_16_33.md\")|[练习16.34](ch16_Templates_and_GenericProgramming/exercise_16_34.md \"./ch16_Templates_and_GenericProgramming/exercise_16_34.md\")|[练习16.35](ch16_Templates_and_GenericProgramming/exercise_16_35.md \"./ch16_Templates_and_GenericProgramming/exercise_16_35.md\")|[练习16.36](ch16_Templates_and_GenericProgramming/exercise_16_36.md \"./ch16_Templates_and_GenericProgramming/exercise_16_36.md\")|\n|[练习16.37](ch16_Templates_and_GenericProgramming/exercise_16_37.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_37.cpp\")|[练习16.38](ch16_Templates_and_GenericProgramming/exercise_16_38.md \"./ch16_Templates_and_GenericProgramming/exercise_16_38.md\")|[练习16.39](ch16_Templates_and_GenericProgramming/exercise_16_39.md \"./ch16_Templates_and_GenericProgramming/exercise_16_39.md\")|[练习16.40](ch16_Templates_and_GenericProgramming/exercise_16_40.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_40.cpp\")|[练习16.41](ch16_Templates_and_GenericProgramming/exercise_16_41.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_41.cpp\")|[练习16.42](ch16_Templates_and_GenericProgramming/exercise_16_42.md \"./ch16_Templates_and_GenericProgramming/exercise_16_42.md\")|\n|[练习16.43](ch16_Templates_and_GenericProgramming/exercise_16_43.md \"./ch16_Templates_and_GenericProgramming/exercise_16_43.md\")|[练习16.44](ch16_Templates_and_GenericProgramming/exercise_16_44.md \"./ch16_Templates_and_GenericProgramming/exercise_16_44.md\")|[练习16.45](ch16_Templates_and_GenericProgramming/exercise_16_45.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_45.cpp\")|[练习16.46](ch16_Templates_and_GenericProgramming/exercise_16_46.md \"./ch16_Templates_and_GenericProgramming/exercise_16_46.md\")|[练习16.47](ch16_Templates_and_GenericProgramming/exercise_16_47.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_47.cpp\")|[练习16.48](ch16_Templates_and_GenericProgramming/exercise_16_48.md \"./ch16_Templates_and_GenericProgramming/exercise_16_48.md\")|\n|[练习16.49](ch16_Templates_and_GenericProgramming/exercise_16_49.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_49.cpp\")|[练习16.50](ch16_Templates_and_GenericProgramming/exercise_16_50.md \"./ch16_Templates_and_GenericProgramming/exercise_16_50.md\")|[练习16.51](ch16_Templates_and_GenericProgramming/exercise_16_51.md \"./ch16_Templates_and_GenericProgramming/exercise_16_51.md\")|[练习16.52](ch16_Templates_and_GenericProgramming/exercise_16_52.md \"./ch16_Templates_and_GenericProgramming/exercise_16_52.md\")|[练习16.53](ch16_Templates_and_GenericProgramming/exercise_16_53.md \"./ch16_Templates_and_GenericProgramming/exercise_16_53.md\")|[练习16.54](ch16_Templates_and_GenericProgramming/exercise_16_54.md \"./ch16_Templates_and_GenericProgramming/exercise_16_54.md\")|\n|[练习16.55](ch16_Templates_and_GenericProgramming/exercise_16_55.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_55.cpp\")|[练习16.56](ch16_Templates_and_GenericProgramming/exercise_16_56.md \"./ch16_Templates_and_GenericProgramming/exercise_16_56.md\")|[练习16.57](ch16_Templates_and_GenericProgramming/exercise_16_57.md \"./ch16_Templates_and_GenericProgramming/exercise_16_57.md\")|[练习16.58](ch16_Templates_and_GenericProgramming/exercise_16_58.md \"./ch16_Templates_and_GenericProgramming/exercise_16_58.md\")|[练习16.59](ch16_Templates_and_GenericProgramming/exercise_16_59.md \"./ch16_Templates_and_GenericProgramming/exercise_16_59.md\")|[练习16.60](ch16_Templates_and_GenericProgramming/exercise_16_60.md \"./ch16_Templates_and_GenericProgramming/exercise_16_60.md\")|\n|[练习16.61](ch16_Templates_and_GenericProgramming/exercise_16_61.md \"./ch16_Templates_and_GenericProgramming/exercise_16_61.md\")|[练习16.62](ch16_Templates_and_GenericProgramming/exercise_16_62.md \"./ch16_Templates_and_GenericProgramming/exercise_16_62.md\")|[练习16.63](ch16_Templates_and_GenericProgramming/exercise_16_63.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_63.cpp\")|[练习16.64](ch16_Templates_and_GenericProgramming/exercise_16_64.md \"./ch16_Templates_and_GenericProgramming/exercise_16_64.md\")|[练习16.65](ch16_Templates_and_GenericProgramming/exercise_16_65.cpp \"./ch16_Templates_and_GenericProgramming/exercise_16_65.cpp\")|[练习16.66](ch16_Templates_and_GenericProgramming/exercise_16_66.md \"./ch16_Templates_and_GenericProgramming/exercise_16_66.md\")|\n|[练习16.67](ch16_Templates_and_GenericProgramming/exercise_16_67.md \"./ch16_Templates_and_GenericProgramming/exercise_16_67.md\")|\n\n### 案例代码\n\n- [自定义的make_shared，练习16.61](ch16_Templates_and_GenericProgramming/example_customer_MakeShared)\n\n- [控制实例化（597）](ch16_Templates_and_GenericProgramming/example_explicit_instantiation)\n\n- [函数模板（p578）](ch16_Templates_and_GenericProgramming/example_function_template.cpp)\n\n- [成员模板（p595）](ch16_Templates_and_GenericProgramming/example_member_template)\n\n- [重载与模板(p615)](ch16_Templates_and_GenericProgramming/example_overload_and_template.cpp)\n\n- [窗口类的设计与实现(练习16.14, 16.15, p592)](ch16_Templates_and_GenericProgramming/example_Screen)\n\n- [自己实现智能指针（p600, 练习16.28）](ch16_Templates_and_GenericProgramming/example_SmartPointer)\n\n- [类模板（p583）](ch16_Templates_and_GenericProgramming/example_template_class)\n\n- [编写可变参数函数模板（p620）](ch16_Templates_and_GenericProgramming/example_variadic_template2.cpp)\n\n- [可变参数模板（p618）](ch16_Templates_and_GenericProgramming/example_variadic_template.cpp)\n\n- [动态内存管理类（练习16.16，p592）](ch16_Templates_and_GenericProgramming/example_Vec)\n\n## 第17章 标准库特殊设施\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习17.1](ch17_Specialized_Library_Facilities/exercise_17_01.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_01.cpp\")|[练习17.2](ch17_Specialized_Library_Facilities/exercise_17_02.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_02.cpp\")|[练习17.3](ch17_Specialized_Library_Facilities/exercise_17_03.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_03.cpp\")|[练习17.4](ch17_Specialized_Library_Facilities/exercise_17_04.md \"./ch17_Specialized_Library_Facilities/exercise_17_04.md\")|[练习17.5](ch17_Specialized_Library_Facilities/exercise_17_05.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_05.cpp\")|[练习17.6](ch17_Specialized_Library_Facilities/exercise_17_06.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_06.cpp\")|\n|[练习17.7](ch17_Specialized_Library_Facilities/exercise_17_07.md \"./ch17_Specialized_Library_Facilities/exercise_17_07.md\")|[练习17.8](ch17_Specialized_Library_Facilities/exercise_17_08.md \"./ch17_Specialized_Library_Facilities/exercise_17_08.md\")|[练习17.9](ch17_Specialized_Library_Facilities/exercise_17_09.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_09.cpp\")|[练习17.10](ch17_Specialized_Library_Facilities/exercise_17_10.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_10.cpp\")|[练习17.11](ch17_Specialized_Library_Facilities/exercise_17_11.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_11.cpp\")|[练习17.12](ch17_Specialized_Library_Facilities/exercise_17_12.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_12.cpp\")|\n|[练习17.13](ch17_Specialized_Library_Facilities/exercise_17_13.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_13.cpp\")|[练习17.14](ch17_Specialized_Library_Facilities/exercise_17_14.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_14.cpp\")|[练习17.15](ch17_Specialized_Library_Facilities/exercise_17_15.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_15.cpp\")|[练习17.16](ch17_Specialized_Library_Facilities/exercise_17_16.md \"./ch17_Specialized_Library_Facilities/exercise_17_16.md\")|[练习17.17](ch17_Specialized_Library_Facilities/exercise_17_17.md \"./ch17_Specialized_Library_Facilities/exercise_17_17.md\")|[练习17.18](ch17_Specialized_Library_Facilities/exercise_17_18.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_18.cpp\")|\n|[练习17.19](ch17_Specialized_Library_Facilities/exercise_17_19.md \"./ch17_Specialized_Library_Facilities/exercise_17_19.md\")|[练习17.20](ch17_Specialized_Library_Facilities/exercise_17_20.md \"./ch17_Specialized_Library_Facilities/exercise_17_20.md\")|[练习17.21](ch17_Specialized_Library_Facilities/exercise_17_21.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_21.cpp\")|[练习17.22](ch17_Specialized_Library_Facilities/exercise_17_22.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_22.cpp\")|[练习17.23](ch17_Specialized_Library_Facilities/exercise_17_23.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_23.cpp\")|[练习17.24](ch17_Specialized_Library_Facilities/exercise_17_24.md \"./ch17_Specialized_Library_Facilities/exercise_17_24.md\")|\n|[练习17.25](ch17_Specialized_Library_Facilities/exercise_17_25.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_25.cpp\")|[练习17.26](ch17_Specialized_Library_Facilities/exercise_17_26.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_26.cpp\")|[练习17.27](ch17_Specialized_Library_Facilities/exercise_17_27.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_27.cpp\")|[练习17.28](ch17_Specialized_Library_Facilities/exercise_17_28.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_28.cpp\")|[练习17.29](ch17_Specialized_Library_Facilities/exercise_17_29.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_29.cpp\")|[练习17.30](ch17_Specialized_Library_Facilities/exercise_17_30.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_30.cpp\")|\n|[练习17.31](ch17_Specialized_Library_Facilities/exercise_17_31.md \"./ch17_Specialized_Library_Facilities/exercise_17_31.md\")|[练习17.32](ch17_Specialized_Library_Facilities/exercise_17_32.md \"./ch17_Specialized_Library_Facilities/exercise_17_32.md\")|[练习17.33](ch17_Specialized_Library_Facilities/exercise_17_33.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_33.cpp\")|[练习17.34](ch17_Specialized_Library_Facilities/exercise_17_34.md \"./ch17_Specialized_Library_Facilities/exercise_17_34.md\")|[练习17.35](ch17_Specialized_Library_Facilities/exercise_17_35.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_35.cpp\")|[练习17.36](ch17_Specialized_Library_Facilities/exercise_17_36.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_36.cpp\")|\n|[练习17.37](ch17_Specialized_Library_Facilities/exercise_17_37.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_37.cpp\")|[练习17.38](ch17_Specialized_Library_Facilities/exercise_17_38.cpp \"./ch17_Specialized_Library_Facilities/exercise_17_38.cpp\")|[练习17.39](ch17_Specialized_Library_Facilities/exercise_17_39.md \"./ch17_Specialized_Library_Facilities/exercise_17_39.md\")|\n\n### 案例代码\n\n- [格式化输入与输出（p666）](ch17_Specialized_Library_Facilities/example_formatted_io.cpp)\n\n- [使用子表达式验证电话号码（p654）](ch17_Specialized_Library_Facilities/example_phone_number.cpp)\n\n- [随机数（p659）](ch17_Specialized_Library_Facilities/example_random.cpp)\n\n- [指定或使用正则表达式时的错误（p648）](ch17_Specialized_Library_Facilities/example_regex_error.cpp)\n\n- [使用regex_replace（p657）](ch17_Specialized_Library_Facilities/example_regex_replace.cpp)\n\n- [seek和tell函数（p676）](ch17_Specialized_Library_Facilities/example_seek_tell.cpp)\n\n- [匹配与Regex迭代器类型（p650）](ch17_Specialized_Library_Facilities/example_sregex_iterator.cpp)\n\n- [使用tuple返回多个值（p638）](ch17_Specialized_Library_Facilities/example_tuple.cpp)\n\n- [未格式化IO（p673）](ch17_Specialized_Library_Facilities/example_unformatted_io.cpp)\n\n- [使用正则表达式库（p646）](ch17_Specialized_Library_Facilities/example_using_regex.cpp)\n\n- [使用子表达式（p653）](ch17_Specialized_Library_Facilities/example_using_subexpression.cpp)\n\n## 第18章 用于大型程序的工具\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习18.1](ch18_Tools_for_Large_Programs/exercise_18_01.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_01.cpp\")|[练习18.2](ch18_Tools_for_Large_Programs/exercise_18_02.md \"./ch18_Tools_for_Large_Programs/exercise_18_02.md\")|[练习18.3](ch18_Tools_for_Large_Programs/exercise_18_03.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_03.cpp\")|[练习18.4](ch18_Tools_for_Large_Programs/exercise_18_04.md \"./ch18_Tools_for_Large_Programs/exercise_18_04.md\")|[练习18.5](ch18_Tools_for_Large_Programs/exercise_18_05.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_05.cpp\")|[练习18.6](ch18_Tools_for_Large_Programs/exercise_18_06.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_06.cpp\")|\n|[练习18.7](ch18_Tools_for_Large_Programs/exercise_18_07.md \"./ch18_Tools_for_Large_Programs/exercise_18_07.md\")|[练习18.8](ch18_Tools_for_Large_Programs/exercise_18_08.md \"./ch18_Tools_for_Large_Programs/exercise_18_08.md\")|[练习18.9](ch18_Tools_for_Large_Programs/exercise_18_09.md \"./ch18_Tools_for_Large_Programs/exercise_18_09.md\")|[练习18.10](ch18_Tools_for_Large_Programs/exercise_18_10.md \"./ch18_Tools_for_Large_Programs/exercise_18_10.md\")|[练习18.11](ch18_Tools_for_Large_Programs/exercise_18_11.md \"./ch18_Tools_for_Large_Programs/exercise_18_11.md\")|[练习18.12](ch18_Tools_for_Large_Programs/exercise_18_12.md \"./ch18_Tools_for_Large_Programs/exercise_18_12.md\")|\n|[练习18.13](ch18_Tools_for_Large_Programs/exercise_18_13.md \"./ch18_Tools_for_Large_Programs/exercise_18_13.md\")|[练习18.14](ch18_Tools_for_Large_Programs/exercise_18_14.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_14.cpp\")|[练习18.15](ch18_Tools_for_Large_Programs/exercise_18_15.md \"./ch18_Tools_for_Large_Programs/exercise_18_15.md\")|[练习18.16](ch18_Tools_for_Large_Programs/exercise_18_16.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_16.cpp\")|[练习18.17](ch18_Tools_for_Large_Programs/exercise_18_17.md \"./ch18_Tools_for_Large_Programs/exercise_18_17.md\")|[练习18.18](ch18_Tools_for_Large_Programs/exercise_18_18.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_18.cpp\")|\n|[练习18.19](ch18_Tools_for_Large_Programs/exercise_18_19.md \"./ch18_Tools_for_Large_Programs/exercise_18_19.md\")|[练习18.20](ch18_Tools_for_Large_Programs/exercise_18_20.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_20.cpp\")|[练习18.21](ch18_Tools_for_Large_Programs/exercise_18_21.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_21.cpp\")|[练习18.22](ch18_Tools_for_Large_Programs/exercise_18_22.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_22.cpp\")|[练习18.23](ch18_Tools_for_Large_Programs/exercise_18_23.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_23.cpp\")|[练习18.24](ch18_Tools_for_Large_Programs/exercise_18_24.md \"./ch18_Tools_for_Large_Programs/exercise_18_24.md\")|\n|[练习18.25](ch18_Tools_for_Large_Programs/exercise_18_25.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_25.cpp\")|[练习18.26](ch18_Tools_for_Large_Programs/exercise_18_26.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_26.cpp\")|[练习18.27](ch18_Tools_for_Large_Programs/exercise_18_27.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_27.cpp\")|[练习18.28](ch18_Tools_for_Large_Programs/exercise_18_28.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_28.cpp\")|[练习18.29](ch18_Tools_for_Large_Programs/exercise_18_29.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_29.cpp\")|[练习18.30](ch18_Tools_for_Large_Programs/exercise_18_30.cpp \"./ch18_Tools_for_Large_Programs/exercise_18_30.cpp\")|\n\n## 第19章 特殊工具与技术\n\n|A|L|L|E|X|E|\n| :-: | :-: | :-: | :-: | :-: | :-: |\n|[练习19.1](ch19_Specialized_Tools_and_Techniques/exercise_19_01.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_01.cpp\")|[练习19.2](ch19_Specialized_Tools_and_Techniques/exercise_19_02.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_02.md\")|[练习19.3](ch19_Specialized_Tools_and_Techniques/exercise_19_03.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_03.cpp\")|[练习19.4](ch19_Specialized_Tools_and_Techniques/exercise_19_04.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_04.cpp\")|[练习19.5](ch19_Specialized_Tools_and_Techniques/exercise_19_05.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_05.md\")|[练习19.6](ch19_Specialized_Tools_and_Techniques/exercise_19_06.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_06.cpp\")|\n|[练习19.7](ch19_Specialized_Tools_and_Techniques/exercise_19_07.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_07.cpp\")|[练习19.8](ch19_Specialized_Tools_and_Techniques/exercise_19_08.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_08.cpp\")|[练习19.9](ch19_Specialized_Tools_and_Techniques/exercise_19_09.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_09.md\")|[练习19.10](ch19_Specialized_Tools_and_Techniques/exercise_19_10.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_10.cpp\")|[练习19.11](ch19_Specialized_Tools_and_Techniques/exercise_19_11.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_11.md\")|[练习19.12](ch19_Specialized_Tools_and_Techniques/exercise_19_12.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_12.cpp\")|\n|[练习19.13](ch19_Specialized_Tools_and_Techniques/exercise_19_13.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_13.cpp\")|[练习19.14](ch19_Specialized_Tools_and_Techniques/exercise_19_14.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_14.cpp\")|[练习19.15](ch19_Specialized_Tools_and_Techniques/exercise_19_15.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_15.md\")|[练习19.16](ch19_Specialized_Tools_and_Techniques/exercise_19_16.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_16.md\")|[练习19.17](ch19_Specialized_Tools_and_Techniques/exercise_19_17.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_17.md\")|[练习19.18](ch19_Specialized_Tools_and_Techniques/exercise_19_18.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_18.cpp\")|\n|[练习19.19](ch19_Specialized_Tools_and_Techniques/exercise_19_19.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_19.cpp\")|[练习19.20](ch19_Specialized_Tools_and_Techniques/exercise_19_20.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_20.md\")|[练习19.21](ch19_Specialized_Tools_and_Techniques/exercise_19_21.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_21.md\")|[练习19.22](ch19_Specialized_Tools_and_Techniques/exercise_19_22.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_22.md\")|[练习19.23](ch19_Specialized_Tools_and_Techniques/exercise_19_23.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_23.md\")|[练习19.24](ch19_Specialized_Tools_and_Techniques/exercise_19_24.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_24.md\")|\n|[练习19.25](ch19_Specialized_Tools_and_Techniques/exercise_19_25.md \"./ch19_Specialized_Tools_and_Techniques/exercise_19_25.md\")|[练习19.26](ch19_Specialized_Tools_and_Techniques/exercise_19_26.cpp \"./ch19_Specialized_Tools_and_Techniques/exercise_19_26.cpp\")|\n\n### 案例代码\n\n- [链接指示，extern \"C\"（758）](ch19_Specialized_Tools_and_Techniques/example_extern_C)\n\n- [成员指针函数表（p743）](ch19_Specialized_Tools_and_Techniques/example_function_table.cpp)\n\n- [将成员函数用作可调用对象（p744）](ch19_Specialized_Tools_and_Techniques/example_memfunc_as_callable_obj.cpp)\n\n- [定位new表达式（p729）](ch19_Specialized_Tools_and_Techniques/example_placement_new.cpp)\n\n- [类成员指针（p739）](ch19_Specialized_Tools_and_Techniques/example_pointer_to_member.cpp)\n\n- [type_info类（p735）](ch19_Specialized_Tools_and_Techniques/example_type_info.cpp)\n\n- [含有类类型成员的union（p750）](ch19_Specialized_Tools_and_Techniques/example_union_with_class.cpp)\n\n- [使用RTTI（p733）](ch19_Specialized_Tools_and_Techniques/example_use_RTTI.cpp)\n\n"
  },
  {
    "path": "codes/CppPrimer/build_all.sh",
    "content": "#! /bin/bash\n\n# 编译所有文件，用于全局检测\n\nmain()\n{\n\tfor directory in ./ch*\n\tdo\n\t\tcd $directory\n\n\t\tif ! sh build.sh ; then\n\t\t\treturn 1\n\t\tfi\n\n\t\t# 进入example目录，这些目录采用Makefile来编译文件\n\t\tfor example_d in ./example*\n\t\tdo\n\t\t\tif [ -d $example_d ]; then\n\t\t\t\tcd $example_d\n\n\t\t\t\tmake\n\n\t\t\t\tcd ..\n\t\t\tfi\n\t\tdone\n\n\t\tcd ..\n\tdone\n\n\treturn 0\n}\n\ntime main\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/Sales_item.h",
    "content": "/*\n * This file contains code from \"C++ Primer, Fifth Edition\", by Stanley B.\n * Lippman, Josee Lajoie, and Barbara E. Moo, and is covered under the\n * copyright and warranty notices given in that book:\n * \n * \"Copyright (c) 2013 by Objectwrite, Inc., Josee Lajoie, and Barbara E. Moo.\"\n * \n * \n * \"The authors and publisher have taken care in the preparation of this book,\n * but make no expressed or implied warranty of any kind and assume no\n * responsibility for errors or omissions. No liability is assumed for\n * incidental or consequential damages in connection with or arising out of the\n * use of the information or programs contained herein.\"\n * \n * Permission is granted for this code to be used for educational purposes in\n * association with the book, given proper citation if and when posted or\n * reproduced.Any commercial use of this code requires the explicit written\n * permission of the publisher, Addison-Wesley Professional, a division of\n * Pearson Education, Inc. Send your request for permission, stating clearly\n * what code you would like to use, and in what specific way, to the following\n * address: \n * \n *     Pearson Education, Inc.\n *     Rights and Permissions Department\n *     One Lake Street\n *     Upper Saddle River, NJ  07458\n *     Fax: (201) 236-3290\n*/ \n\n/* This file defines the Sales_item class used in chapter 1.\n * The code used in this file will be explained in\n * Chapter 7 (Classes) and Chapter 14 (Overloaded Operators)\n * Readers shouldn't try to understand the code in this file\n * until they have read those chapters.\n*/\n\n#ifndef SALESITEM_H\n// we're here only if SALESITEM_H has not yet been defined \n#define SALESITEM_H\n\n// Definition of Sales_item class and related functions goes here\n#include <iostream>\n#include <string>\n\nclass Sales_item {\n// these declarations are explained section 7.2.1, p. 270 \n// and in chapter 14, pages 557, 558, 561\nfriend std::istream& operator>>(std::istream&, Sales_item&);\nfriend std::ostream& operator<<(std::ostream&, const Sales_item&);\nfriend bool operator<(const Sales_item&, const Sales_item&);\nfriend bool \noperator==(const Sales_item&, const Sales_item&);\npublic:\n    // constructors are explained in section 7.1.4, pages 262 - 265\n    // default constructor needed to initialize members of built-in type\n    Sales_item() = default;\n    Sales_item(const std::string &book): bookNo(book) { }\n    Sales_item(std::istream &is) { is >> *this; }\npublic:\n    // operations on Sales_item objects\n    // member binary operator: left-hand operand bound to implicit this pointer\n    Sales_item& operator+=(const Sales_item&);\n    \n    // operations on Sales_item objects\n    std::string isbn() const { return bookNo; }\n    double avg_price() const;\n// private members as before\nprivate:\n    std::string bookNo;      // implicitly initialized to the empty string\n    unsigned units_sold = 0; // explicitly initialized\n    double revenue = 0.0;\n};\n\n// used in chapter 10\ninline\nbool compareIsbn(const Sales_item &lhs, const Sales_item &rhs) \n{ return lhs.isbn() == rhs.isbn(); }\n\n// nonmember binary operator: must declare a parameter for each operand\nSales_item operator+(const Sales_item&, const Sales_item&);\n\ninline bool \noperator==(const Sales_item &lhs, const Sales_item &rhs)\n{\n    // must be made a friend of Sales_item\n    return lhs.units_sold == rhs.units_sold &&\n           lhs.revenue == rhs.revenue &&\n           lhs.isbn() == rhs.isbn();\n}\n\ninline bool \noperator!=(const Sales_item &lhs, const Sales_item &rhs)\n{\n    return !(lhs == rhs); // != defined in terms of operator==\n}\n\n// assumes that both objects refer to the same ISBN\nSales_item& Sales_item::operator+=(const Sales_item& rhs) \n{\n    units_sold += rhs.units_sold; \n    revenue += rhs.revenue; \n    return *this;\n}\n\n// assumes that both objects refer to the same ISBN\nSales_item \noperator+(const Sales_item& lhs, const Sales_item& rhs) \n{\n    Sales_item ret(lhs);  // copy (|lhs|) into a local object that we'll return\n    ret += rhs;           // add in the contents of (|rhs|) \n    return ret;           // return (|ret|) by value\n}\n\nstd::istream& \noperator>>(std::istream& in, Sales_item& s)\n{\n    double price;\n    in >> s.bookNo >> s.units_sold >> price;\n    // check that the inputs succeeded\n    if (in)\n        s.revenue = s.units_sold * price;\n    else \n        s = Sales_item();  // input failed: reset object to default state\n    return in;\n}\n\nstd::ostream& \noperator<<(std::ostream& out, const Sales_item& s)\n{\n    out << s.isbn() << \" \" << s.units_sold << \" \"\n        << s.revenue << \" \" << s.avg_price();\n    return out;\n}\n\ndouble Sales_item::avg_price() const\n{\n    if (units_sold) \n        return revenue/units_sold; \n    else \n        return 0;\n}\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"exercise_1_07.cpp exercise_1_15.cpp\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/data/add_item",
    "content": "0-201-78345-X 3 20.00\n0-201-78345-X 2 25.00\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/data/book_sales",
    "content": "0-201-70353-X 4 24.99\n0-201-82470-1 4 45.39\n0-201-88954-4 2 15.00 \n0-201-88954-4 5 12.00 \n0-201-88954-4 7 12.00 \n0-201-88954-4 2 12.00 \n0-399-82477-1 2 45.39\n0-399-82477-1 3 45.39\n0-201-78345-X 3 20.00\n0-201-78345-X 2 25.00\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/example_comments.cpp",
    "content": "// example: C++中的注释使用（p8）\n\n#include <iostream>\n\n/*\n * 简单主函数：\n * 读取两个数，求它们的和\n */\nint main()\n{\n\t// 提示用户输入两个数\n\tstd::cout << \"Enter two numbers:\" << std::endl;\n\n\tint a = 0, b = 0;\t// 保存读入的输入数据的变量\n\tstd::cin >> a >> b;\t// 读取输入数据\n\n\tstd::cout << \"The sum is: \" << a + b << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/example_for.cpp",
    "content": "// example: for语句（p11）\n\n#include <iostream>\n\nint main()\n{\n\tint sum = 0;\n\n\t// 从1加到10\n\tfor (int val = 1; val <= 10; ++val)\n\t\tsum += val;\n\n\tstd::cout << \"Sum of 1 to 10 inclusive is \" << sum << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/example_if.cpp",
    "content": "// example: if语句（p15）\n\n#include <iostream>\n\n// 统计输入中每个值连续出现了多少次\nint main()\n{\n\t// currVal是我们正在统计的数；我们将读入的新值存入val\n\tint currVal = 0, val = 0;\n\n\tif (std::cin >> currVal) {\n\t\tint cnt = 1;\t// 保存正在处理的当前值的个数\n\t\twhile (std::cin >> val) {\t// 读取剩余的数\n\t\t\tif (val == currVal)\t\t// 如果值相同，将cnt加1，否则打印前一个值的个数\n\t\t\t\t++cnt;\n\t\t\telse {\n\t\t\t\tstd::cout << currVal << \" occurs \" << cnt << \" times\" << std::endl;\n\t\t\t\tcurrVal = val;\t// 记住新值\n\t\t\t\tcnt = 1;\t\t// 重置计算器\n\t\t\t}\n\t\t}\n\n\t\t// 记住打印文件最后一个值的个数\n\t\tstd::cout << currVal << \" occurs \" << cnt << \" times\" << std::endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/example_iostream.cpp",
    "content": "// example: 使用IO库（p5）\n\n#include <iostream>\n\nint main()\n{\n\tstd::cout << \"Enter two numbers:\" << std::endl;\n\tint v1 = 0, v2 = 0;\n\tstd::cin >> v1 >> v2;\n\tstd::cout << \"The sum of \" << v1 << \" and \" << v2 << \" is \"\n\t\t\t  << v1 + v2 << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/example_main.cpp",
    "content": "// example: 最简单的C++程序（p2）\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/example_while.cpp",
    "content": "// example: while语句（p10）\n\n#include <iostream>\n\nint main()\n{\n\tint sum = 0, val = 1;\n\n\t// 只要val的值小于等于10，while循环就会持续执行\n\twhile (val <= 10) {\n\t\tsum += val;\t\t// 将sum + val赋予sum\n\t\t++val;\t// 将val加1\n\t}\n\n\tstd::cout << \"Sum of 1 to 10 inclusive is \" << sum << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_01.cpp",
    "content": "/* \n * 练习1.1：查询你使用的编译器文档，确定它所使用的文件名约定。编译并运行第2页的main程序\n*/\n\n/*\n * $ g++ -o exercise_1_1 exercise_1_1.cpp\n*/\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_02.cpp",
    "content": "/*\n * 练习1.2：改写程序，让它返回-1。返回值-1通常被当作程序错误的标识。重新编译并运行你的程序，观察你的系统如何处理main返回的错误标识\n */\n\n/*\n * $ g++ exercise_1_2.cpp\n * $ ./a.out\n * $ echo $?\n * 255\n */\n\nint main()\n{\n\treturn -1;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_03.cpp",
    "content": "/*\n * 练习1.3：编写程序，在标准输出上打印Hello, World\n */\n\n#include <iostream>\n\nint main()\n{\n\tstd::cout << \"Hello, World\" << std::endl;\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_04.cpp",
    "content": "/*\n * 练习1.4：我们的程序使用加法运算符+来将两个数相加。编写程序使用乘法运算符*，来打印两个数的积\n *\n */\n\n#include <iostream>\n\nint main()\n{\n\tstd::cout << \"Enter two numbers:\" << std::endl;\n\tint v1 = 0, v2 = 0;\n\tstd::cin >> v1 >> v2;\n\tstd::cout << \"The product of \" << v1 << \" and \" << v2 << \" is \"\n\t\t\t  << v1 * v2 << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_05.cpp",
    "content": "/*\n * 练习1.5：我们将所有输出操作放在一条很长的语句中。重写程序，将每个运算对象的打印操作放在一条独立的语句中。\n */\n\n#include <iostream>\n\nint main()\n{\n\tstd::cout << \"Enter two numbers:\";\n\tstd::cout << std::endl;\n\n\tint v1 = 0, v2 = 0;\n\tstd::cin >> v1 >> v2;\n\n\tstd::cout << \"The sum of \";\n\tstd::cout << v1;\n\tstd::cout << \" and \";\n\tstd::cout << v2;\n\tstd::cout << \" is \";\n\tstd::cout << v1 + v2;\n\tstd::cout << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_06.cpp",
    "content": "/*\n * 练习1.6：解释下面程序片段是否合法：\n * std::cout << \"The sum of \" << v1;\n *           << \" and \" << v2;\n *           << \" is \" << v1 + v2 << std::endl;\n */\n\n/*\n * 不合法。\n * expected primary-expression before ‘<<’ token\n * 解决办法：移除多余的分号。\n *\n * std::cout << \"The sum of \" << v1\n *           << \" and \" << v2\n *           << \" is \" << v1 + v2 << std::endl;\n *\n */\n\n#include <iostream>\n\nint main()\n{\n\tint v1 = 0, v2 = 0;\n\tstd::cout << \"The sum of \" << v1\n\t          << \" and \" << v2\n\t\t\t  << \" is \" << v1 + v2 << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_07.cpp",
    "content": "/*\n * 练习1.7：编译一个包含不正确的嵌套注释的程序，观察编译器返回的错误信息\n */\n\n/*\n * $ g++ exercise_1_7.cpp\n * error: expected unqualified-id before ‘/’ token\n */\n\n/*\n * /*这是一个错误的嵌套注释*/\n *\n */\nint main()\n{\n\treturn 0;\n}\n\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_08.cpp",
    "content": "// 练习1.8：指出下列哪些输出语句是合法的（如果有的话）\n\n// std::cout << \"/*\";\n// std::cout << \"*/\";\n// std::cout << /* \"*/\" */;\n// std::cout << /* \"*/\" /* \"/*\" */;\n\n// 预测编译这些语句会产生什么样的结果，实际编译这些语句来验证你的答案（编写一个小程序，每次将上述一条语句作为其主体），改正每个编译错误\n\n/*\n * 只有第三个不合法，在其最后添加一个\"\n */\n\n// 结果： /**/ */ /* \n\n#include <iostream>\n\nint main()\n{\n\tstd::cout << \"/*\";\n\tstd::cout << \"*/\";\n\tstd::cout << /* \"*/\" */\";\n\tstd::cout << /* \"*/\" /* \"/*\" */;\n\n\treturn 0;\n}\n\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_09.cpp",
    "content": "/*\n * 练习1.9：编写程序，使用while循环将50到100的整数相加\n */\n\n#include <iostream>\n\nint main()\n{\n\tint sum = 0, i = 50;\n\n\twhile (i <= 100) {\n\t\tsum += i;\n\t\t++i;\n\t}\n\n\tstd::cout << \"Sum of 50 to 100 inclusive is \" << sum << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_10.cpp",
    "content": "/*\n * 练习1.10：除了++运算符将运算对象的值增加1之外，还有一个递减运算符（--）实现将值减少1。编写程序，使用递减运算符在循环中按递减顺序打印出10到0之间的整数。\n */\n\n#include <iostream>\n\nint main()\n{\n\tint val = 10;\n\twhile (val >= 0) {\n\t\tstd::cout << val << std::endl;\n\t\t--val;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_11.cpp",
    "content": "/*\n * 练习1.11：编写程序，提示用户输入两个整数，打印出这两个整数所指定的范围内的所有整数。\n */\n\n#include <iostream>\n\nint main()\n{\n\tint lval = 0, rval = 0;\n\n\tstd::cout << \"Enter two integers:\" << std::endl;\n\tstd::cin >> lval >> rval;\n\n\twhile (lval <= rval) {\n\t\tstd::cout << lval << std::endl;\n\t\tlval++;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_12.cpp",
    "content": "/*\n * 练习1.12：下面的for循环完成了什么功能？sum的终值是多少？\n *\n * int sum = 0;\n * for (int i = -100; i <= 100; ++i)\n *     sum += i;\n */\n\n/*\n * 求-100到100之间所有整数的和，终值是0。\n */\n\n#include <iostream>\n\nint main()\n{\n\tint sum = 0;\n\tfor (int i = -100; i <= 100; ++i)\n\t\tsum += i;\n\n\tstd::cout << sum << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_13.cpp",
    "content": "/*\n * 练习1.13：使用for循环重做1.4.1节中的所有练习（第11页）\n */\n\n#include <iostream>\n\n// 练习1.9\n/*\nint main()\n{\n\tint sum = 0;\n\tfor (int val = 50; val <= 100; ++val)\n\t\tsum += val;\n\n\tstd::cout << \"Sum of 50 to 100 is \" << sum << std::endl;\n\n\treturn 0;\n}\n*/\n\n// 练习1.10\n/*\nint main()\n{\n\tfor (int val = 10; val >= 0; --val)\n\t\tstd::cout << val << std::endl;\n\n\treturn 0;\n}\n*/\n\n// 练习1.11\nint main()\n{\n\tint lval = 0, rval = 0;\n\n\tstd::cout << \"Enter two integers:\" << std::endl;\n\tstd::cin >> lval >> rval;\n\n\tfor (int i = lval; i <= rval; ++i)\n\t\tstd::cout << i << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_14.cpp",
    "content": "/*\n * 练习1.14：对比for循环和while循环，两种形式的优缺点各是什么？\n */\n\n/*\n * for循环更擅长处理这样一种模式：在循环条件中检测变量、在循环体内递增变量，for语句简化了这种模式。while循环对于这种情况的处理会相对复杂。\n *\n * for循环通常在每次循环中都改变控制变量，从而不适合处理那些不确定循环次数的循环，而while循环可以在循环体内比较自由地控制循环变量。\n *\n * 更多参考：https://stackoverflow.com/questions/2950931/for-vs-while-in-c-programming\n */\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_15.cpp",
    "content": "/*\n * 练习1.15：编写程序，包含第14页“再探编译”中讨论的常见错误。熟悉编译器生成的错误信息。\n */\n\n#include <iostream>\n\nint main()\n{\n\t// 语法错误（syntax error）\n\t//std::cout << \"Hello\" << std::endl:\n\n\t// 类型错误（type error）\n\t//int a = \"Hello\";\n\n\t// 声明错误（declaration error）\n\tcout << \"Hello\" << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_16.cpp",
    "content": "/*\n * 练习1.16：编写程序，从cin读取一组数，输出其和\n */\n\n#include <iostream>\n\nint main()\n{\n\tint sum = 0, val = 0;\n\n\twhile (std::cin >> val)\n\t\tsum += val;\n\n\tstd::cout << \"Sum is: \" << sum << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_17.cpp",
    "content": "/*\n * 练习1.17：如果输入的值都是相等的，本节程序会输出什么？如果没有重复值，输出又会怎样？\n */\n\n/*\n * 如下：\n * $ ./example_if\n * 1 1 1 1 1\n * 1 occurs 5 times\n *\n * $ ./example_if\n * 1 2 3 4 5\n * 1 occurs 1 times\n * 2 occurs 1 times\n * 3 occurs 1 times\n * 4 occurs 1 times\n * 5 occurs 1 times\n *\n * 在最后一个值的个数输出之前，必须键入CTRL+D，才能输出最后一个值的个数\n *\n */\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_18.cpp",
    "content": "/*\n * 练习1.18：编译并运行本节的程序，给它输入全都相等的值。再次运行程序，输入没有重复的值。\n */\n\n/*\n * 见上一题的解答。\n */\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_19.cpp",
    "content": "/*\n * 练习1.19：修改你为1.4.1节练习1.10（第11页）所编写的程序（打印一个范围内的数），使其能够处理用户输入的第一个数比第二个数小的情况\n */\n\n/*\n * 书本上似有误，应该是修改练习1.11\n */\n\n#include <iostream>\n\nint main()\n{\n\tint lval = 0, rval = 0;\n\n\tstd::cout << \"Enter two integers:\" << std::endl;\n\tstd::cin >> lval >> rval;\n\n\tif (lval > rval) {\n\t\tint tmp = rval;\n\t\trval = lval;\n\t\tlval = tmp;\n\t}\n\n\twhile (lval <= rval) {\n\t\tstd::cout << lval << std::endl;\n\t\tlval++;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_20.cpp",
    "content": "/*\n * 练习1.20：在网站http://www.informit.com/title/032174113上，第一章的代码目录中包含了头文件Sales_item.h。将它拷贝到你自己的工作目录中。用它编写一个程序，读取一组书籍销售记录，将每条记录打印到标准输出上。\n */\n\n/*\n * $ ./exercise_1_20 < data/book_sales\n */\n\n#include <iostream>\n#include \"Sales_item.h\"\n\nint main()\n{\n\tSales_item book;\n\n\twhile (std::cin >> book)\n\t\tstd::cout << book << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_21.cpp",
    "content": "/*\n * 练习1.21：编写程序，读取两个ISBN相同的Sales_item对象，输出它们的和。\n */\n\n/*\n * $ ./exercise_1_21 < data/add_item\n */\n\n#include <iostream>\n#include \"Sales_item.h\"\n\nint main()\n{\n\tSales_item book1, book2;\n\n\tstd::cin >> book1 >> book2;\n\n\tif (book1.isbn() != book2.isbn()) {\n\t\tstd::cerr << \"Data must refer to same ISBN.\" << std::endl;\n\t\treturn 1;\n\t}\n\n\tstd::cout << book1 + book2 << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_22.cpp",
    "content": "/*\n * 练习1.22：编写程序，读取多个具有相同ISBN的销售记录，输出所有记录的和\n */\n\n/*\n * Tip：和1.6节的事例程序一样\n *\n * $ ./exercise_1_22 < data/book_sales\n */\n\n#include <iostream>\n#include \"Sales_item.h\"\n\nint main()\n{\n\tSales_item total;\n\n\tif (std::cin >> total) {\n\t\tSales_item trans;\n\n\t\twhile (std::cin >> trans) {\n\t\t\tif (trans.isbn() == total.isbn())\n\t\t\t\ttotal += trans;\n\t\t\telse {\n\t\t\t\tstd::cout << total << std::endl;\n\t\t\t\ttotal = trans;\n\t\t\t}\n\t\t}\n\n\t\tstd::cout << total << std::endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_23.cpp",
    "content": "/*\n * 练习1.23：编写程序，读取多条销售记录，并统计每个ISBN（每本书）有几条销售记录。\n */\n\n/*\n * $ ./exercise_1_23 < data/book_sales\n */\n\n#include <iostream>\n#include \"Sales_item.h\"\n\nint main()\n{\n\tSales_item item;\n\n\tif (std::cin >> item) {\n\t\tint cnt = 1;\n\t\tSales_item tmp_item;\n\n\t\twhile (std::cin >> tmp_item) {\n\t\t\tif (tmp_item.isbn() == item.isbn())\n\t\t\t\t++cnt;\n\t\t\telse {\n\t\t\t\tstd::cout << item.isbn() << \" has \" << cnt << \" transaction(s)\"\n\t\t\t\t          << std::endl;\n\t\t\t\titem = tmp_item;\n\t\t\t\tcnt = 1;\n\t\t\t}\n\t\t}\n\t\tstd::cout << item.isbn() << \" has \" << cnt << \" transaction(s)\"\n\t\t          << std::endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_24.cpp",
    "content": "/*\n * 练习1.24：输入表示多个ISBN的多条销售记录来测试上一个程序，每个ISBN记录应聚在一起。\n */\n\n/*\n * $ ./exercise_1_23 < data/book_sales\n */\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/exercise_1_25.cpp",
    "content": "/*\n * 练习1.25：借助网站上的Sales_item.h头文件，编译并运行本节给出的书店程序。\n */\n\n/*\n * $ g++ -g -Wall -std=c++11 -o usage_Sales_item_avg_price usage_Sales_item_avg_price.cpp\n * $ ./usage_Sales_item_avg_price < data/book_sales\n */\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/test_clog.cpp",
    "content": "// $ 以下命令说明clog关联到标准错误\n// $ ./test_clog 2> /dev/null\n// $ ./test_clog > /dev/null\n// $ ./test_clog\n\n#include <iostream>\n\nint main()\n{\n\tstd::clog << \"This is a log message\" << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/usage_Sales_item_avg_price.cpp",
    "content": "// 1.6节书店程序，从一个文件中读取销售记录，生成每本书的销售报告，显示售出册数、总销售额和平均售价。我们假定每个ISBN书号的所有销售记录在文件中是聚在一起保存的。\n\n// $ ./usage_Sales_item_avg_price < data/book_sales\n\n#include <iostream>\n#include \"Sales_item.h\"\n\nint main()\n{\n\tSales_item total; // variable to hold data for the next transaction\n\n\t// read the first transaction and ensure that there are data to process\n\tif (std::cin >> total) {\n\t\tSales_item trans; // vairable to hold the running sum\n\t\t// read and process the remaining transactions\n\t\twhile (std::cin >> trans) {\n\t\t\t// if we're still processing the same book\n\t\t\tif (total.isbn() == trans.isbn())\n\t\t\t\ttotal += trans; // update the running total\n\t\t\telse {\n\t\t\t\t// print results for the previous book\n\t\t\t\tstd::cout << total << std::endl;\n\t\t\t\ttotal = trans; // total now refers to the next book\n\t\t\t}\n\t\t}\n\t\tstd::cout << total << std::endl; // print the last transaction\n\t}\n\telse {\n\t\t// no input warn the user\n\t\tstd::cerr << \"No data?!\" << std::endl;\n\t\treturn -1;\t// indicate failure\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/usage_add_Sales_item.cpp",
    "content": "// 将两个Sales_item对象相加\n\n#include <iostream>\n#include \"Sales_item.h\"\n\nint main()\n{\n\tSales_item item1, item2;\n\tstd::cin >> item1 >> item2;\n\n\t// 首先检查item1和item2是否表示相同的书\n\tif (item1.isbn() == item2.isbn()) {\n\t\tstd::cout << item1 + item2 << std::endl;\n\t\treturn 0; // 表示成功\n\t}\n\telse {\n\t\tstd::cerr << \"Data must refer to same ISBN\"\n\t\t          << std::endl;\n\t\treturn -1; // 表示失败\n\t}\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch01_Getting_Started/usage_read_inputs.cpp",
    "content": "#include <iostream>\n\n// 对用户输入的一组数求和\nint main()\n{\n\tint sum = 0, value = 0;\n\n\t// 读取数据直到遇到文件尾，计算所有读入的值的和\n\twhile (std::cin >> value)\n\t\tsum += value;\n\n\tstd::cout << \"Sum is: \" << sum << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/Sales_data.h",
    "content": "#ifndef SALES_DATA_H\n#define SALES_DATA_H\n\n#include <string>\n\nstruct Sales_data\n{\n\tstd::string bookNo;\n\tunsigned int units_sold = {0};\n\tdouble revenue = {0.0};\n};\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"exercise_2_06.cpp\\\n\t       exercise_2_09.cpp\\\n\t       exercise_2_10.cpp\\\n\t       exercise_2_34.cpp\\\n\t       exercise_2_35.cpp\\\n\t       exercise_2_39.cpp\\\n\t       test_list_initialization.cpp\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/data/add_item",
    "content": "0-201-78345-X 3 20.00\n0-201-78345-X 2 25.00\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/data/book_sales",
    "content": "0-201-70353-X 4 24.99\n0-201-82470-1 4 45.39\n0-201-88954-4 2 15.00 \n0-201-88954-4 5 12.00 \n0-201-88954-4 7 12.00 \n0-201-88954-4 2 12.00 \n0-399-82477-1 2 45.39\n0-399-82477-1 3 45.39\n0-201-78345-X 3 20.00\n0-201-78345-X 2 25.00\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_01.txt",
    "content": "练习2.1：类型int、long、long long和short的区别是什么？无符号类型和带符号类型的区别是什么？float和double的区别是什么？\n\nint、long、long long和short它们的尺寸可能不一样，C++标准给出了最小尺寸要求，编译器可以给予更大的尺寸。无符号类型只能表示大于等于0的数。float和double的尺寸通常不一样，float通常有7个有效位，double通常有16个。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_02.txt",
    "content": "练习2.2：计算按揭贷款时，对于利率、本金和付款分别应选择何种数据类型？说明你的理由。\n\n使用double，因为都要使用小数。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_03.txt",
    "content": "练习2.3：读程序写结果。\n\nunsigned u = 10, u2 = 42;\nstd::cout << u2 - u << std::endl;\t--> 32\nstd::cout << u - u2 << std::endl;\t--> 4294967264\n\nint i = 10, i2 = 42;\nstd::cout << i2-i<<std::endl;\t\t--> 32\nstd::cout << i - i2<<std::endl;\t\t--> -32\nstd::cout << i - u<< std::endl;\t\t--> 0\nstd::cout << u - i<<std::endl;\t\t--> 0\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_04.cpp",
    "content": "/*\n * 练习2.4：编写程序检测你的估计是否正确，如果不正确，请仔细研读本节直到弄明白问题所在。\n */\n\n#include <iostream>\n\nint main()\n{\n\tunsigned u = 10, u2 = 42;\n\tstd::cout << u2 - u << std::endl;\n\tstd::cout << u - u2 << std::endl;\n\n\tint i = 10, i2 = 42;\n\tstd::cout << i2-i<<std::endl;\n\tstd::cout << i - i2<<std::endl;\n\tstd::cout << i - u<< std::endl;\n\tstd::cout << u - i<<std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_05.txt",
    "content": "练习2.5：指出下述字面值的数据类型并说明每一组内几种字面值的区别：\n\n(a) 'a', L'a', \"a\", L\"a\"\n(b) 10, 10u, 10L, 10uL, 012, 0xC\n(c) 3.14, 3.14f, 3.14L\n(d) 10, 10u, 10., 10e-2\n\n(a) 'a'的类型是char，L‘a’的类型是wchar_t，”a“的类型是以空字符结尾的char数组，L\"a\"的类型是以空字符结尾的wchar_t数组。\n(b) 10的类型是int，10u的类型是unsigned int，10L的类型是long，10uL的类型是unsigend long，012的类型是int，是一个用八进制表示的整数；0xC的类型是int，是一个用十六进制表示的整数。\n(c) 3.14的类型是double，3.14f的类型是float，3.14L的类型是long double。\n(d) 10的类型是int，10u的类型是unsigned int，10.的类型是double，10e-2的类型是double。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_06.cpp",
    "content": "/*\n * 练习2.6：下面两组定义是否有区别，如果有，请叙述之：\n *\n * int month = 9, day = 7;\n * int month = 09, day = 07;\n */\n\n/*\n * 第一行定义的是十进制整数。\n * 第二行中int mount = 09是非法的，9超出了八进制的表示范围；day = 07定义的是八进制整数。\n *\n * $ g++ -g -Wall exercise_2_6.cpp\n * error: invalid digit \"9\" in octal constant\n */\n\nint main()\n{\n\t//int month = 9, day = 7;\n\tint month = 09, day = 07;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_07.cpp",
    "content": "/*\n * 练习2.7：下述字面值表示何种含义？它们各自的数据类型是什么？\n *\n * (a) \"Who goes with F\\145rgus?\\012\"\n * (b) 3.14e1L\t\t(c) 1024f\t\t(d) 3.14L \n */\n\n/*\n * (a) 表示字符串字面量：\"Who goes with Fergus?(换行)\"\n * (b) long double\n * (c) float\n * (d) long double\n *\n * ASCII码参考：http://www.asciitable.com/\n */\n\n#include <iostream>\n\nint main()\n{\n\tstd::cout << \"Who goes with F\\145rgus?\\012\";\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_08.cpp",
    "content": "/*\n * 练习2.8：请利用转义序列编写一段程序，要求先输出2M，然后转到新一行。修改程序使其先输出2，然后输出制表符，再输出M，最后转到新一行。\n */\n\n#include <iostream>\n\nint main()\n{\n\tstd::cout << \"\\x32\\x4d\\n\";\n\t\n\tstd::cout << \"\\x32\\t\\x4d\\n\";\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_09.cpp",
    "content": "/*\n * 练习2.9：解释下列定义的含义。对于非法的定义，请说明错在何处并将其改正。\n *\n * (a) std::cin >> int input_value;\t\t(b) int i = { 3.14 };\n * (c) double salary = wage = 9999.99;\t(d) int i = 3.14;\n */\n\n/*\n * (a) 格式错误。改正如下：\n *\n * int input_value = 0;\n * std::cin >> input_value;\n *\n * (b) 错误，列表初始化时，如果存在丢失信息的风险，编译器会报错。改为：double i = { 3.14 };\n *\n * (c) 格式错误。改为：double salary = 9999.99, wage = 9999.99; 或者先定义wage。\n *\n * (d) 正确。\n */\n\n#include <iostream>\n\nint main()\n{\n\t//std::cin >> int input_value;\n\tint input_value = 0;\n\tstd::cin >> input_value;\n\n\t//int i = { 3.14 };\n\tdouble i = { 3.14 };\n\n\t//double salary = wage = 9999.99;\n\tdouble salary = 9999.99, wage = 9999.99;\n\n\tint i2 = 3.14;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_10.cpp",
    "content": "/*\n * 练习2.10：下列变量的初值分别是什么？\n *\n * std::string global_str;\n * int global_int;\n * int main()\n * {\n *     int local_int;\n *     std::string local_str;\n * }\n */\n\n/*\n * global_str是一个空串，global_int是0，local_int的值未定义，local_str是一个空串。\n */\n\n#include <iostream>\n#include <string>\n\nstd::string global_str;\nint global_int;\nint main()\n{\n\tint local_int;\n\tstd::string local_str;\n\n\tstd::cout << \"global_str: \" << global_str << std::endl;\n\tstd::cout << \"global_int: \" << global_int << std::endl;\n\tstd::cout << \"local_int: \" << local_int << std::endl;\n\tstd::cout << \"local_str: \" << local_str << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_11.txt",
    "content": "练习2.11：指出下面的语句是声明还是定义：\n\n(a) extern int ix = 1024;\n(b) int iy;\n(c) extern int iz;\n\n(a) 定义。(b) 定义。(c) 声明。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_12.txt",
    "content": "练习2.12：请指出下面的名字中哪些是非法的？\n\n(a) int double = 3.14;\t\t(b) int _;\n(c) int catch-22;\t\t(d) int 1_or_2 = 1;\n(e) double Double = 3.14;\n\n非法的有：(a), (c), (d)。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_13.cpp",
    "content": "/*\n * 练习2.13：下面程序中j的值是多少？\n *\n * int i = 42;\n * int main()\n * {\n *     int i = 100;\n *     int j = i;\n * }\n */\n\n/*\n * 100。\n */\n\n#include <iostream>\n\nint i = 42;\nint main()\n{\n\tint i = 100;\n\tint j = i;\n\n\tstd::cout << j << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_14.cpp",
    "content": "/*\n * 练习2.14：下面的程序合法吗？如果合法，它将输出什么？\n *\n * int i = 100, sum = 0;\n * for (int i = 0; i != 10; ++i)\n *     sum += i;\n * std::cout << i << \" \" << sum << std::endl;\n */\n\n/*\n * 合法，将输出：100 45（换行）\n */\n\n#include <iostream>\n\nint main()\n{\n\tint i = 100, sum = 0;\n\tfor (int i = 0; i != 10; ++i)\n\t\tsum += i;\n\tstd::cout << i << \" \" << sum << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_15.txt",
    "content": "练习2.15：下面的哪个定义是不合法的？为什么？\n\n(a) int ival = 1.01;\t\t(b) int &ival1 = 1.01;\n(c) int &rval2 = ival;\t\t(d) int &rval3;\n\n(b)不合法，因为引用不能绑定到一个字面值上。\n(d)不合法，因为引用必须初始化。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_16.cpp",
    "content": "/*\n * 练习2.16：考察下面的所有赋值然后回答：哪些赋值是不合法的？为什么？哪些赋值是合法的？它们执行了什么样的操作？\n *\n * int i = 0, &r1 = i;\tdouble d = 0, &r2 = d;\n * (a) r2 = 3.14159;\t(b) r2 = r1;\n * (c) i = r2;\t\t\t(d) r1 = d;\n */\n\n/*\n * (a)合法，将3.14159赋值给r2绑定到的对象d。\n * (b)合法，将r1绑定到的对象i的值赋值给r2绑定到的对象d。\n * (c)合法，将r2绑定到的对象d的值赋值给i。\n * (d)合法，将d的值赋值给r1绑定到的对象i。\n */\n\nint main()\n{\n\tint i = 0, &r1 = i;  double d = 0, &r2 = d;\n\n\tr2 = r1;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_17.cpp",
    "content": "/*\n * 练习2.17：执行下面的代码段将输出什么结果？\n *\n * int i, &ri = i;\n * i = 5; ri = 10;\n * std::cout << i << \" \" << ri << std::endl;\n */\n\n/*\n * 10 10(换行)\n */\n\n#include <iostream>\n\nint main()\n{\n\tint i, &ri = i;\n\ti = 5; ri = 10;\n\tstd::cout << i << \" \" << ri << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_18.cpp",
    "content": "/*\n * 练习2.18：编写代码分别更改指针的值以及指针所指对象的值。\n */\n\n#include <iostream>\n\nint main()\n{\n\tint a = 0;\n\tint *p = nullptr;\n\n\tp = &a;\n\t*p = 10;\n\n\tstd::cout << *p << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_19.txt",
    "content": "练习2.19：说明指针和引用的主要区别。\n\n指针是一个对象，因此可以通过赋值来改变其值。\n引用是一个对象的别名，必须初始化以绑定到对象，且之后无法更改绑定的对象。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_20.cpp",
    "content": "/*\n * 练习2.20：请叙述下面这段代码的作用。\n *\n * int i = 42;\n * int *p1 = &i;\n * *p1 = *p1 * *p1;\n */\n\n/*\n * 通过指针p1，求得i的值的平方，将其赋值给i。\n */\n\n#include <iostream>\n\nint main()\n{\n\tint i = 42;\n\tint *p1 = &i;\n\t*p1 = *p1 * *p1;\n\n\tstd::cout << \"42 * 42 = \" << 42 * 42 << std::endl;\n\tstd::cout << \"i = \" << i << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_21.txt",
    "content": "练习2.21：请解释下述定义。在这些定义中有非法的吗？如果有，为什么？\n\nint i = 0;\n(a) double* dp = &i; (b) int *ip = i; (c) int *p = &i;\n\n(a)非法，dp所指的对象类型必须是int。\n(b)非法，不能把int变量赋值给一个指针。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_22.txt",
    "content": "练习2.22：假设p是一个int型指针，请说明下述代码的含义。\n\nif (p) // ...\nif (*p) // ...\n\n---\n\nif (p) // ...  表示当p不是空指针时，if条件为真。\nif (*p) // ... 表示当*p的值不为0时，if条件为真。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_23.txt",
    "content": "练习2.23：给定指针p，你能知道它是否指向了一个合法的对象吗？如果能，叙述判断的思路；如果不能，也请说明原因。\n\n不能，因为信息量不足。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_24.txt",
    "content": "练习2.24：在下面这段代码中为什么p合法而lp非法？\n\nint i = 42;\t\tvoid *p = &i;\t\tlong *lp = &i;\n\n因为类型是void*的指针可以指向任何类型的对象，而long*的指针只能指向long类型的对象。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_25.txt",
    "content": "练习2.25：说明下列变量的类型和值。\n\n(a) int* ip, i, &r = i; (b) int i, *ip = 0; (c) int* ip, ip2;\n\n---\n\n(a) ip是一个指向int的指针，i是一个int型变量，r是一个指向i的引用。如果在块内定义它们，ip和i的值都是未定义的，否则是0。\n\n(b) i是一个int型变量，ip是一个指向int的指针。如果在块内定义它们，i的值是未定义的，否则是0。ip的值是0。\n\n(c) ip是一个指向int的指针，ip2是一个int型变量。如果在块内定义它们，ip和ip2的值都是未定义的，否则是0。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_26.txt",
    "content": "练习2.24：下面哪些句子是合法的？如果有不合法的句子，请说明为什么？\n\n(a) const int buf;\t\t(b) int cnt = 0;\n(c) const int sz = cnt;\t(d) ++cnt; ++sz;\n\n---\n\n(a) 不合法，必须初始化一个const变量。\n(b) 合法。\n(c) 合法。\n(d) 不合法，不能修改一个const变量（sz）。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_27.cpp",
    "content": "/*\n * 练习2.27：下面哪些初始化是合法的？请说明原因。\n\n(a) int i = -1, &r = 0;\t\t\t(b) int *const p2 = &i2;\n(c) const int i = -1, &r = 0;\t(d) const int *const p3 = &i2;\n(e) const int *p1 = &i2;\t\t(f) const int &const r2;\n(g) const int i2 = i, &r = i;\n\n*/\n\n/*\n\n(a) 非法，r是一个普通引用，不能绑定到字面量。\n(b) 合法，但前提是i2不是const int。\n(c) 合法。r是一个常量引用，可以绑定到字面量。\n(d) 合法。\n(e) 合法。\n(f) 非法。r2是一个引用，必须初始化，且没有const引用，只有指向常量的引用。\n(g) 合法。\n\n*/\n\nint main()\n{\n\t//const int i2 = 0;\n\tint i2 = 0;\n\tint *const p2 = &i2;\n\t*p2 = 1; // just use p2 to ignore warning\n\n\t//const int &const r2;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_28.txt",
    "content": "练习2.28：说明下面的这些定义是什么意思，挑出其中不合法的。\n\n(a) int i, *const cp;\t\t(b) int *p1, *const p2;\n(c) const int ic, &r = ic;\t(d) const int *const p3;\n(e) const int *p;\n\n---\n\n(a) 定义一个名为i的int型变量，cp定义错误，常量指针必须初始化。\n(b) 定义一个名为p1的指向int的指针，p2定义错误，常量指针必须初始化。\n(c) ic定义错误，const int必须初始化。\n(d) p3定义错误，常量指针必须初始化。\n(e) 定义一个名为p的指向const int的指针。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_29.txt",
    "content": "练习2.29：假设已有上一个练习中定义的那些变量，则下面的哪些语句是合法的？请说明原因。\n\n(a) i = ic;\t\t\t(b) p1 = p2;\n(c) p1 = &ic;\t\t(d) p3 = &ic;\n(e) p2 = p1;\t\t(f) ic = *p3;\n\n---\n\n(a) 合法。\n(b) 非法，p3指向了const int，而p1不能指向const int。\n(c) 非法，p1不能指向const int。\n(d) 非法，p3不能指向const int。\n(e) 非法，p2是常量，不能修改其值。\n(f) 非法，ic是常量，不能修改其值。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_30.txt",
    "content": "练习2.30：对下面的这些语句，请说明对象被声明成了顶层const还是底层const？\n\nconst int v2 = 0;\tint v1 = v2;\nint *p1 = &v1, &r1 = v1;\nconst int *p2 = &v2, *const p3 = &i, &r2 = v2;\n\n---\n\nv2是顶层const，p2是底层const，p3既是顶层const也是底层const, r2是底层const。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_31.txt",
    "content": "练习2.31：假设已有上一个练习中所做的那些声明，则下面的哪些语句是合法的？请说明顶层const和底层const在每个例子中有何体现。\n\nr1 = v2;\np1 = p2; p2 = p1;\np1 = p3; p2 = p3;\n\n---\n\nr1 = v2; 是合法的，v2是顶层const，可以赋值给别的变量。\np1 - p2; 是非法的，因为p2是底层const，不能赋值给普通指针。\np2 = p1; 是合法的，p2没有顶层const属性，可以被修改。\np1 = p3; 是非法的，因为p3拥有底层const属性，不能赋值给普通指针。\np2 = p3; 是合法的，p2没有顶层const属性，可以被修改。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_32.txt",
    "content": "练习2.32：下面的代码是否合法？如果合法，请设法将其修改正确。\n\nint null = 0, *p = null;\n\n---\n\n不合法，p是指针，不能把int变量赋值给它。改为: int null = 0, *p = &null;\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_33.txt",
    "content": "练习2.33：利用本节定义的变量，判断下列语句的运行结果。\n\na = 42; b = 42; c = 42;\nd = 42; e = 42; g = 42;\n\n---\n\na = 42; 结果是变量a的值变为42。\nb = 42; 结果是变量b的值变为42。\nc = 42; 结果是变量c的值变为42。\nd = 42; 非法的赋值，不能用整型值给指针赋值。\ne = 42; 非法的赋值，不能用整型值给指针赋值。\ng = 42; 非法的赋值，g是一个常量引用，不能修改其值。\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_34.cpp",
    "content": "/*\n * 练习2.34：基于上一个练习中的变量和语句编写一段程序，输出赋值前后变量的内容，你刚才的推断正确吗？如果不对，请反复研读本节的示例直到你明白错在何处位置。\n */\n\n#include <iostream>\n\nint main()\n{\n\tint i = 0, &r = i;\n\tconst int ci = i, &cr = ci;\n\n\tauto a = r;\t// a is int\n\tauto b = ci;\t// b is int\n\tauto c = cr;\t// c is int\n\tauto d = &i;\t// d is int*\n\tauto e = &ci;\t// e is const int*\n\tauto &g = ci;\t// g is const int&\n\n\ta = 42;\n\tstd::cout << \"a: \" << a << std::endl;\n\n\tb = 42;\n\tstd::cout << \"b: \" << b << std::endl;\n\n\tc = 42;\n\tstd::cout << \"c: \" << c << std::endl;\n\n\t//d = 42;\n\t//e = 42;\n\t//g = 42;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_35.cpp",
    "content": "/*\n * 练习2.35：判断下列定义推断出的类型是什么，然后编写程序进行验证。\n *\n * const int i = 42;\n * auto j = i; const auto &k = i; auto *p = &i;\n * const auto j2 = i, &k2 = i;\n */\n\n/*\n * j：int\n * k：const int&，绑定到i\n * p：const int*，指向i\n * j2：const int\n * k2：const int&，绑定到i\n */\n\n#include <iostream>\n\nint main()\n{\n\tconst int i = 42;\n\tauto j = i; const auto &k = i; auto *p = &i;\n\tconst auto j2 = i, &k2 = i;\n\n\tj = 80;\n\tstd::cout << \"j: \" << j << \"\\t\" << \"i: \" << i << std::endl;\n\n\tstd::cout << \"addr of j: \" << &j << \"\\t\" << \"addr of i: \" << &i << std::endl;\n\t\n\t// 下面编译报错\n\t//k = 100;\n\t//*p = 100;\n\t//k2 = 100;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_36.cpp",
    "content": "/*\n * 练习2.36：关于下段代码，请指出每一个变量的类型以及程序结束时它们各自的值\n *\n * int a = 3, b = 4;\n * decltype(a) c = a;\n * decltype((b)) d = a;\n * ++c;\n * ++d;\n */\n\n/*\n * a: int, 结束时值为4。\n * b: int, 结束时值为4。\n * c：int，结束时值为4。\n * d：int&，结束时值为4。\n */\n\n#include <iostream>\n\nint main()\n{\n\tint a = 3, b = 4;\n\tdecltype(a) c = a;\n\tdecltype((b)) d = a;\n\t++c;\n\t++d;\n\n\tstd::cout << \"a: \" << a << std::endl;\n\tstd::cout << \"b: \" << b << std::endl;\n\tstd::cout << \"c: \" << c << std::endl;\n\tstd::cout << \"d: \" << d << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_37.txt",
    "content": "练习2.37：赋值时会产生引用的一类典型的表达式，引用的类型就是左值的类型。也就是说，如果i是int，则表达式i=x的类型是int&。根据这一点，请指出下面的代码中每一个变量的类型和值。\n\nint a = 3, b = 4;\ndecltype(a) c = a;\ndecltype(a = b) d = a;\n\n---\n\na: int, 3\nb: int, 4\nc: int, 3\nd: int&, 3\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_38.md",
    "content": "练习2.38：说明由decltype指定类型和由auto指定类型有何区别。请举出一个例子，decltype指定的类型与auto指定的类型一样；再举一个例子，decltype指定的类型与auto指定的类型不一样。\n\nauto定义的变量必须有初始值，因为编译器是根据其初始值推断其类型，decltype是根据给它的表达式来推断类型。\nauto定义的类型和其初始值类型可能并不完全一样，decltype得到的类型和给它的表达式类型一样。\nauto一般会忽略掉顶层const，而decltype会保留。\n\n一样的例子：\nint i = 0;\ndecltype(i) a = i; // a is int\nauto b = i; // b is int\n\n不一样的类型：\nint i = 0, &r = i;\ndecltype(r) a = i; // a is int&\nauto b = r; // b is int\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_39.cpp",
    "content": "/*\n * 练习2.39：编译下面的程序观察其运行结果，注意，如果忘记写类定义体后面的分号会发生什么情况？记录下相关信息，以后可能会有用。\n */\n\n/*\n * $ g++ -g -Wall -o exercise_2_39 exercise_2_39.cpp\n * error: expected ‘;’ after struct definition\n */\n\nstruct Foo {} // 注意：没有分号\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_40.cpp",
    "content": "/*\n * 练习2.40：根据自己的理解写出Sales_data类，最好与书中的例子有所区别\n */\n\n#include <string>\n\nstruct Sales_data\n{\n\tstd::string bookNo;\n\tunsigned int units_sold = {0};\n\tdouble revenue = {0.0};\n};\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_41.cpp",
    "content": "/*\n * 练习2.41：使用你自己的Sales_data类重写1.5.1节（第20页）、1.5.2节（第21页）和1.6节（第22页）的练习。眼下先把Sales_data类的定义和main函数放在同一个文件里。\n */\n\n#include <iostream>\n#include <string>\n\nstruct Sales_data\n{\n\tstd::string bookNo;\n\tunsigned int units_sold = {0};\n\tdouble revenue = {0.0};\n};\n\n// 1.5.1节，1.20\n// $ ./exercise_2_41 < data/book_sales\n/*\nint main()\n{\n\tSales_data sales_data;\n\tdouble price = 0.0;\n\n\twhile (std::cin >> sales_data.bookNo >> sales_data.units_sold >> price) {\n\t\tsales_data.revenue = price * sales_data.units_sold;\n\t\tstd::cout << sales_data.bookNo << \" \"\n\t\t\t  << sales_data.units_sold << \" \"\n\t\t\t  << sales_data.revenue << \" \"\n\t\t\t  << price \n\t\t\t  << std::endl;\n\t}\n\n\treturn 0;\n}\n*/\n\n// 1.5.1节，1.21\n// $ ./exercise_2_41 < data/add_item \n\n/*\nint main()\n{\n\tSales_data sales_data1, sales_data2;\n\tdouble price = 0.0;\n\n\tstd::cin >> sales_data1.bookNo >> sales_data1.units_sold >> price;\n\tsales_data1.revenue = price * sales_data1.units_sold;\n\n\tstd::cin >> sales_data2.bookNo >> sales_data2.units_sold >> price;\n\tsales_data2.revenue = price * sales_data2.units_sold;\n\n\tif (!std::cin) {\n\t\tstd::cerr << \"Read Sales_data failed.\" << std::endl;\n\t\treturn 1;\n\t}\n\n\tif (sales_data1.bookNo != sales_data2.bookNo) {\n\t\tstd::cerr << \"Data must refer to same ISBN.\" << std::endl;\n\t\treturn 1;\n\t}\n\n\tSales_data total;\n\ttotal.bookNo = sales_data1.bookNo;\n\ttotal.units_sold = sales_data1.units_sold + sales_data2.units_sold;\n\ttotal.revenue = sales_data1.revenue + sales_data2.revenue;\n\n\tdouble avg_price = 0.0;\n\tif (total.units_sold > 0)\n\t\tavg_price = total.revenue / total.units_sold;\n\n\tstd::cout << total.bookNo << \" \"\n\t\t  << total.units_sold << \" \"\n\t\t  << total.revenue << \" \"\n\t\t  << avg_price << std::endl;\n\n\treturn 0;\n}\n*/\n\n// 1.5.1节，1.22\n// ./exercise_2_41 < data/book_sales\n/*\nint main()\n{\n\tSales_data total;\n\tdouble price = 0.0;\n\n\tif (std::cin >> total.bookNo >> total.units_sold >> price) {\n\t\ttotal.revenue = total.units_sold * price;\n\n\t\tSales_data trans;\n\t\tdouble trans_price = 0.0;\n\t\twhile (std::cin >> trans.bookNo >> trans.units_sold >> trans_price) {\n\t\t\ttrans.revenue = trans.units_sold * trans_price;\n\n\t\t\tif (trans.bookNo == total.bookNo) {\n\t\t\t\ttotal.units_sold += trans.units_sold;\n\t\t\t\ttotal.revenue += trans.revenue;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tdouble avg_price = 0.0;\n\t\t\t\tif (total.units_sold > 0)\n\t\t\t\t\tavg_price = total.revenue / total.units_sold;\n\t\t\t\tstd::cout << total.bookNo << \" \"\n\t\t\t\t\t  << total.units_sold << \" \"\n\t\t\t\t\t  << total.revenue << \" \"\n\t\t\t\t\t  << avg_price << std::endl;\n\n\t\t\t\ttotal = trans;\n\t\t\t}\n\t\t}\n\n\t\tdouble avg_price = 0.0;\n\t\tif (total.units_sold > 0)\n\t\t\tavg_price = total.revenue / total.units_sold;\n\t\tstd::cout << total.bookNo << \" \"\n\t\t\t  << total.units_sold << \" \"\n\t\t\t  << total.revenue << \" \"\n\t\t\t  << avg_price << std::endl;\n\t}\n\n\treturn 0;\n}\n*/\n\n// 1.5.2节，1.23\n// $ ./exercise_2_41 < data/book_sales\n/*\nint main()\n{\n\tSales_data data;\n\tdouble price = 0.0;\n\n\tif (std::cin >> data.bookNo >> data.units_sold >> price) {\n\t\tdata.revenue = price * data.units_sold;\n\t\tint cnt = 1;\n\t\tSales_data tmp_data;\n\t\tdouble tmp_price = 0.0;\n\n\t\twhile (std::cin >> tmp_data.bookNo >> tmp_data.units_sold >> tmp_price) {\n\t\t\ttmp_data.revenue = tmp_data.units_sold * tmp_price;\n\t\t\tif (tmp_data.bookNo == data.bookNo)\n\t\t\t\t++cnt;\n\t\t\telse {\n\t\t\t\tstd::cout << data.bookNo << \" has \" << cnt << \" transaction(s)\" << std::endl;\n\t\t\t\tdata = tmp_data;\n\t\t\t\tcnt = 1;\n\t\t\t}\n\t\t}\n\t\tstd::cout << data.bookNo << \" has \" << cnt << \" transaction(s)\"; \n\t}\n\treturn 0;\n}*/\n\n// 1.6节，1.25\n// $ ./exercise_2_41 < data/book_sales\n\nint main()\n{\n\tSales_data total;\n\tdouble price = 0.0;\n\n\tif (std::cin >> total.bookNo >> total.units_sold >> price) {\n\t\ttotal.revenue = total.units_sold * price;\n\t\tSales_data trans;\n\t\tdouble trans_price = 0.0;\n\t\twhile (std::cin >> trans.bookNo >> trans.units_sold >> trans_price) {\n\t\t\ttrans.revenue = trans.units_sold * trans_price;\n\t\t\tif (total.bookNo == trans.bookNo) {\n\t\t\t\ttotal.units_sold += trans.units_sold;\n\t\t\t\ttotal.revenue += trans.revenue;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tdouble avg_price = 0.0;\n\t\t\t\tif (total.units_sold > 0) {\n\t\t\t\t\tavg_price = total.revenue / total.units_sold;\n\t\t\t\t}\n\t\t\t\tstd::cout << total.bookNo  << \" \" << total.units_sold << \" \" << total.revenue << \" \" << avg_price << std::endl;\n\t\t\t\ttotal = trans;\n\t\t\t}\n\t\t}\n\t\tdouble avg_price = 0.0;\n\t\tif (total.units_sold > 0) {\n\t\t\tavg_price = total.revenue / total.units_sold;\n\t\t}\n\t\tstd::cout << total.bookNo << \" \" << total.units_sold << \" \" << total.revenue  << \" \" << avg_price  << std::endl;\n\t}\n\telse {\n\t\tstd::cerr << \"No data?!\" << std::endl;\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/exercise_2_42.cpp",
    "content": "/*\n * 练习2.42，根据你自己的理解重写一个Sales_data.h头文件，并以此为基础重做2.6.2节的练习。\n */\n\n#include <iostream>\n#include <string>\n#include \"Sales_data.h\"\n\n// 1.5.1节，1.20\n// $ ./exercise_2_42 < data/book_sales\n/*\nint main()\n{\n\tSales_data sales_data;\n\tdouble price = 0.0;\n\n\twhile (std::cin >> sales_data.bookNo >> sales_data.units_sold >> price) {\n\t\tsales_data.revenue = price * sales_data.units_sold;\n\t\tstd::cout << sales_data.bookNo << \" \"\n\t\t\t  << sales_data.units_sold << \" \"\n\t\t\t  << sales_data.revenue << \" \"\n\t\t\t  << price \n\t\t\t  << std::endl;\n\t}\n\n\treturn 0;\n}\n*/\n\n// 1.5.1节，1.21\n// $ ./exercise_2_42 < data/add_item \n\n/*\nint main()\n{\n\tSales_data sales_data1, sales_data2;\n\tdouble price = 0.0;\n\n\tstd::cin >> sales_data1.bookNo >> sales_data1.units_sold >> price;\n\tsales_data1.revenue = price * sales_data1.units_sold;\n\n\tstd::cin >> sales_data2.bookNo >> sales_data2.units_sold >> price;\n\tsales_data2.revenue = price * sales_data2.units_sold;\n\n\tif (!std::cin) {\n\t\tstd::cerr << \"Read Sales_data failed.\" << std::endl;\n\t\treturn 1;\n\t}\n\n\tif (sales_data1.bookNo != sales_data2.bookNo) {\n\t\tstd::cerr << \"Data must refer to same ISBN.\" << std::endl;\n\t\treturn 1;\n\t}\n\n\tSales_data total;\n\ttotal.bookNo = sales_data1.bookNo;\n\ttotal.units_sold = sales_data1.units_sold + sales_data2.units_sold;\n\ttotal.revenue = sales_data1.revenue + sales_data2.revenue;\n\n\tdouble avg_price = 0.0;\n\tif (total.units_sold > 0)\n\t\tavg_price = total.revenue / total.units_sold;\n\n\tstd::cout << total.bookNo << \" \"\n\t\t  << total.units_sold << \" \"\n\t\t  << total.revenue << \" \"\n\t\t  << avg_price << std::endl;\n\n\treturn 0;\n}\n*/\n\n// 1.5.1节，1.22\n// ./exercise_2_42 < data/book_sales\n/*\nint main()\n{\n\tSales_data total;\n\tdouble price = 0.0;\n\n\tif (std::cin >> total.bookNo >> total.units_sold >> price) {\n\t\ttotal.revenue = total.units_sold * price;\n\n\t\tSales_data trans;\n\t\tdouble trans_price = 0.0;\n\t\twhile (std::cin >> trans.bookNo >> trans.units_sold >> trans_price) {\n\t\t\ttrans.revenue = trans.units_sold * trans_price;\n\n\t\t\tif (trans.bookNo == total.bookNo) {\n\t\t\t\ttotal.units_sold += trans.units_sold;\n\t\t\t\ttotal.revenue += trans.revenue;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tdouble avg_price = 0.0;\n\t\t\t\tif (total.units_sold > 0)\n\t\t\t\t\tavg_price = total.revenue / total.units_sold;\n\t\t\t\tstd::cout << total.bookNo << \" \"\n\t\t\t\t\t  << total.units_sold << \" \"\n\t\t\t\t\t  << total.revenue << \" \"\n\t\t\t\t\t  << avg_price << std::endl;\n\n\t\t\t\ttotal = trans;\n\t\t\t}\n\t\t}\n\n\t\tdouble avg_price = 0.0;\n\t\tif (total.units_sold > 0)\n\t\t\tavg_price = total.revenue / total.units_sold;\n\t\tstd::cout << total.bookNo << \" \"\n\t\t\t  << total.units_sold << \" \"\n\t\t\t  << total.revenue << \" \"\n\t\t\t  << avg_price << std::endl;\n\t}\n\n\treturn 0;\n}\n*/\n\n// 1.5.2节，1.23\n// $ ./exercise_2_42 < data/book_sales\n/*\nint main()\n{\n\tSales_data data;\n\tdouble price = 0.0;\n\n\tif (std::cin >> data.bookNo >> data.units_sold >> price) {\n\t\tdata.revenue = price * data.units_sold;\n\t\tint cnt = 1;\n\t\tSales_data tmp_data;\n\t\tdouble tmp_price = 0.0;\n\n\t\twhile (std::cin >> tmp_data.bookNo >> tmp_data.units_sold >> tmp_price) {\n\t\t\ttmp_data.revenue = tmp_data.units_sold * tmp_price;\n\t\t\tif (tmp_data.bookNo == data.bookNo)\n\t\t\t\t++cnt;\n\t\t\telse {\n\t\t\t\tstd::cout << data.bookNo << \" has \" << cnt << \" transaction(s)\" << std::endl;\n\t\t\t\tdata = tmp_data;\n\t\t\t\tcnt = 1;\n\t\t\t}\n\t\t}\n\t\tstd::cout << data.bookNo << \" has \" << cnt << \" transaction(s)\"; \n\t}\n\treturn 0;\n}*/\n\n// 1.6节，1.25\n// $ ./exercise_2_42 < data/book_sales\n\nint main()\n{\n\tSales_data total;\n\tdouble price = 0.0;\n\n\tif (std::cin >> total.bookNo >> total.units_sold >> price) {\n\t\ttotal.revenue = total.units_sold * price;\n\t\tSales_data trans;\n\t\tdouble trans_price = 0.0;\n\t\twhile (std::cin >> trans.bookNo >> trans.units_sold >> trans_price) {\n\t\t\ttrans.revenue = trans.units_sold * trans_price;\n\t\t\tif (total.bookNo == trans.bookNo) {\n\t\t\t\ttotal.units_sold += trans.units_sold;\n\t\t\t\ttotal.revenue += trans.revenue;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tdouble avg_price = 0.0;\n\t\t\t\tif (total.units_sold > 0) {\n\t\t\t\t\tavg_price = total.revenue / total.units_sold;\n\t\t\t\t}\n\t\t\t\tstd::cout << total.bookNo  << \" \" << total.units_sold << \" \" << total.revenue << \" \" << avg_price << std::endl;\n\t\t\t\ttotal = trans;\n\t\t\t}\n\t\t}\n\t\tdouble avg_price = 0.0;\n\t\tif (total.units_sold > 0) {\n\t\t\tavg_price = total.revenue / total.units_sold;\n\t\t}\n\t\tstd::cout << total.bookNo << \" \" << total.units_sold << \" \" << total.revenue  << \" \" << avg_price  << std::endl;\n\t}\n\telse {\n\t\tstd::cerr << \"No data?!\" << std::endl;\n\t\treturn -1;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/test_char.cpp",
    "content": "#include <iostream>\n\nint main()\n{\n\t// 测试char的实际表现类型（signed or unsigned）\n\tchar a = -1;\n\tif (a < 0)\n\t\tstd::cout << \"On this compiler, char is signed\" << std::endl;\n\telse\n\t\tstd::cout << \"On this compiler, char is unsigned\" << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/test_list_initialization.cpp",
    "content": "// $ g++ -g -Wall test_list_initialization.cpp -o test_list_initialization\n\n#include <iostream>\n\nint main()\n{\n\tint a = {3.14};\n\n\tstd::cout << a << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch02_Variables_and_Basic_Types/test_literal.cpp",
    "content": "#include <iostream>\n\nint main()\n{\n\tstd::cout << \"sizeof 20: \" << sizeof(20) << std::endl;\n\tstd::cout << \"sizeof 20LL: \" << sizeof(20LL) << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/example_getline.cpp",
    "content": "// example: 使用getline读取一整行（p78）\n// ./example_getline < example_getline.cpp\n\n#include <iostream>\n#include <string>\n\nusing std::cin;\nusing std::cout;\nusing std::endl;\nusing std::string;\n\nint main()\n{\n\tstring line;\n\t// 每次读取一整行，直至到达文件尾\n\twhile (getline(cin, line))\n\t\tcout << line << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/example_normal_array_simulate_dimensional_array.cpp",
    "content": "// example: 一维数组模拟二维数组\n\n#include <iostream>\n\nusing namespace std;\n\nconstexpr int ROW = 4;\nconstexpr int COL = 4;\n\n// 这样子也可以模拟一个二维数组\nint a[ROW * COL];\n\n// 根据传入的行列数得到真实的下标\nint get_idx(int r, int c)\n{\n\treturn r * COL + c;\n}\n\n// 初始化（遍历）的方法\nvoid init_func()\n{\n\tint tmp = 1;\n\tfor (int i = 0; i < ROW; ++i)\n\t\tfor (int j = 0; j < COL; ++j) {\n\t\t\tint idx = get_idx(i, j);\n\t\t\ta[idx] = tmp++;\n\t\t}\n}\n\n// 输出（遍历）一行的方法\nvoid print_row(int r)\n{\n\tif (r < 0 || r >= ROW) return;\n\n\tint start_idx = r * COL;\n\tint end_idx = start_idx + COL;\n\n\tfor (int i = start_idx; i < end_idx; ++i)\n\t\tcout << a[i] << \" \";\n\n\tcout << endl;\n}\n\n// 清空（另一种遍历）的方法\nvoid clear_arr()\n{\n\tfor (int i = 0; i < ROW * COL; ++i)\n\t\ta[i] = 0;\n}\n\nint main()\n{\n\tinit_func();\n\n\tprint_row(3);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/example_push_back.cpp",
    "content": "// example: 向vector对象中添加元素（p90）\n// 从标准输入中读取单词，将其作为vector对象的元素存储\n\n#include <string>\n#include <iostream>\n#include <vector>\n\nusing std::cin;\nusing std::string;\nusing std::vector;\n\nint main()\n{\n\tstring word;\n\tvector<string> text;\n\twhile (cin >> word) {\n\t\ttext.push_back(word);\t// 把word添加到text后面\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/example_range_for.cpp",
    "content": "// example: 使用基于范围for语句处理每个字符（p82）\n\n#include <iostream>\n#include <string>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tstring str(\"some string\");\n\t// 每行输出str中的一个字符\n\tfor (auto c : str)\n\t\tcout << c << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/example_read_string.cpp",
    "content": "// example: 读取未知数量的string对象（p78）\n\n/*\n * ./example_read_string < example_read_string.cpp\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cin;\nusing std::cout;\nusing std::endl;\nusing std::string;\n\nint main()\n{\n\tstring word;\n\twhile (cin >> word)\n\t\tcout << word << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/example_using_iterators.cpp",
    "content": "// example: 迭代器运算符（p96）\n// 把string对象的第一个字母改为大写形式\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::string;\n\nint main()\n{\n\tstring s(\"some string\");\n\tif (s.begin() != s.end()) {\t// 确保s非空\n\t\tauto it = s.begin();\t// it表示s的第一个字符\n\t\t*it = toupper(*it);\t\t// 将当前字符改写成大写形式\n\t}\n\n\tcout << s << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/example_using_subscript.cpp",
    "content": "// example: 使用下标执行迭代（p85）\n// 使用下标进行迭代\n\n#include <iostream>\n#include <string>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tstring s(\"Hello World\");\n\n\t// 依次处理s中的字符直至我们处理完全部字符或遇到一个空白\n\tfor (decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index)\n\t\ts[index] = toupper(s[index]);\n\n\tcout << s << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_01.cpp",
    "content": "/*\n * 练习3.1：使用恰当的using声明重做1.4.1节（第11页）和2.6.2节（第67页）的练习。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\n// 1.4.1节 练习1.9\n/*\nint main()\n{\n\tint sum = 0, i = 50;\n\n\twhile (i <= 100) {\n\t\tsum += i;\n\t\t++i;\n\t}\n\n\tcout << \"Sum of 50 to 100 inclusive is \" << sum << endl;\n\n\treturn 0;\n}\n*/\n\n// 1.4.1节 练习1.10\n/*\nint main()\n{\n\tint val = 10;\n\twhile (val >= 0) {\n\t\tcout << val << endl;\n\t\t--val;\n\t}\n\n\treturn 0;\n}\n*/\n\n// 1.4.1节 练习1.11\n\nint main()\n{\n\tint lval = 0, rval = 0;\n\n\tcout << \"Enter two integers:\" << endl;\n\tcin >> lval >> rval;\n\n\twhile (lval <= rval) {\n\t\tcout << lval << endl;\n\t\tlval++;\n\t}\n\n\treturn 0;\n}\n\n// 2.6.2节练习，类似，去掉std::即可 \n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_02.cpp",
    "content": "/*\n * 练习3.2：编写一段程序从标准输入中一次读入一整行，然后修改该程序使其一次读入一个词。\n */\n\n// ./exercise_3_02 < exercise_3_02.cpp\n\n#include <iostream>\n#include <string>\n\nusing std::cin;\nusing std::cout;\nusing std::endl;\nusing std::string;\n/*\nint main()\n{\n\tstring line;\n\twhile (getline(cin, line))\n\t\tcout << line << endl;\n\n\treturn 0;\n}*/\n\nint main()\n{\n\tstring word;\n\twhile (cin >> word)\n\t\tcout << word << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_03.txt",
    "content": "练习3.3：请说明string类的输入运算符和getline函数分别是如何处理空白字符的。\n\n输入运算符会忽略读入的空白字符。getline只会忽略掉一行中最后的换行符，并会保留输入时的空白字符。\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_04a.cpp",
    "content": "/*\n * 练习3.4：编写一段程序读入两个字符串，比较其是否相等并输出结果。如果不想等，输出较大的那个字符串。改写上述程序，比较输入的两个字符串是否等长，如果不等长，输出长度较大的那个字符串。\n */\n\n#include <iostream>\n\nusing std::string;\nusing std::cin;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tstring str1, str2;\n\n\tcout << \"Enter two strings:\" << endl;\n\tcin >> str1 >> str2;\n\n\tif (str1 == str2)\n\t\tcout << \"Equal\" << endl;\n\telse {\n\t\tcout << \"Not equal\" << endl;\n\t\t\n\t\tif (str1 > str2)\n\t\t\tcout << str1 << endl;\n\t\telse\n\t\t\tcout << str2 << endl;\n\t}\n\n\t\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_04b.cpp",
    "content": "/*\n * 练习3.4：编写一段程序读入两个字符串，比较其是否相等并输出结果。如果不想等，输出较大的那个字符串。改写上述程序，比较输入的两个字符串是否等长，如果不等长，输出长度较大的那个字符串。\n */\n\n#include <iostream>\n\nusing std::string;\nusing std::cin;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tstring str1, str2;\n\n\tcout << \"Enter two strings:\" << endl;\n\tcin >> str1 >> str2;\n\n\tif (str1.size() == str2.size())\n\t\tcout << \"Size equal\" << endl;\n\telse {\n\t\tcout << \"Size not equal\" << endl;\n\t\t\n\t\tif (str1.size() > str2.size())\n\t\t\tcout << str1 << endl;\n\t\telse\n\t\t\tcout << str2 << endl;\n\t}\n\n\t\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_05a.cpp",
    "content": "/*\n * 练习3.5：编写一段程序从标准输入中读入多个字符串并将它们连接在一起，输出连接成的大字符串。然后修改上述程序，用空格把输入的多个字符串分割开来。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::string;\nusing std::cin;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tstring word;\n\tstring result_str;\n\twhile (cin >> word)\n\t\tresult_str += word;\n\n\tcout << result_str << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_05b.cpp",
    "content": "/*\n * 练习3.5：编写一段程序从标准输入中读入多个字符串并将它们连接在一起，输出连接成的大字符串。然后修改上述程序，用空格把输入的多个字符串分割开来。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::string;\nusing std::cin;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tstring word;\n\tstring result_str;\n\twhile (cin >> word)\n\t{\n\t\tresult_str += word;\n\t\tresult_str += ' ';\n\t}\n\n\tcout << result_str << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_06.cpp",
    "content": "/*\n * 练习3.6：编写一段程序，使用范围for语句将字符串内的所有字符用X代替。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tstring s(\"Hello World\");\n\tcout << s << endl;\n\n\tfor (auto &c : s)\n\t\tc = 'X';\n\n\tcout << s << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_07.cpp",
    "content": "/*\n * 练习3.7：就上一题完成的程序而言，如果将循环变量的类型设为char将发生什么？先估计一下结果，然后实际编程进行验证。\n */\n\n/*\n * 没有区别。auto得到的类型就是char。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tstring s(\"Hello World\");\n\tcout << s << endl;\n\n\tfor (char &c : s)\n\t\tc = 'X';\n\n\tcout << s << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_08a.cpp",
    "content": "/*\n * 练习3.8：分别用while循环和传统的for循环重写第一题的程序，你觉得哪种形式更好呢？为什么？\n */\n\n#include <iostream>\n#include <string>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tstring s(\"Hello World\");\n\tcout << s << endl;\n\n\tfor (decltype(s.size()) i = 0; i != s.size(); ++i)\n\t\ts[i] = 'X';\n\n\tcout << s << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_08b.cpp",
    "content": "/*\n * 练习3.8：分别用while循环和传统的for循环重写第一题的程序，你觉得哪种形式更好呢？为什么？\n */\n\n/*\n * for循环更好，因为循环变量写在内部，且用的是迭代形式。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tstring s(\"Hello World\");\n\tcout << s << endl;\n\n\tdecltype(s.size()) index = 0;\n\twhile (index != s.size())\n\t\ts[index++] = 'X';\n\n\tcout << s << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_09.txt",
    "content": "练习3.9：下面的程序有何作用？它合法吗？如果不合法，为什么？\n\nstring s;\ncout << s[0] << endl;\n\n---\n\n作用是把字符串中的第一个字符输出。不合法，因为字符串为空，第一个字符并不存在，出现了越界的错误。\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_10.cpp",
    "content": "/*\n * 练习3.10：编写一段程序，读入一个包含标点符号的字符串，将标点符号去除后输出字符串剩余的部分。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tstring s;\n\tcout << \"Enter a string with punctuation(s):\" << endl;\n\tcin >> s;\n\n\tfor (auto c : s)\n\t\tif (!ispunct(c))\n\t\t\tcout << c;\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_11.txt",
    "content": "练习3.11：下面的范围for语句合法吗？如果合法，c的类型是什么？\n\nconst string s = \"Keep out!\";\nfor (auto &c : s) { /* ... */ }\n\n---\n\n如果不改变c的值，就是合法的，c的类型是const char&。\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_12.txt",
    "content": "练习3.12：下列vector对象的定义有不正确的吗？如果有，请指出来。对于正确的，描述其执行结果；对于不正确的，说明其错误原因。\n\n(a) vector<vector<int>> ivec;\n(b) vector<string> svec = ivec;\n(c) vector<string> svec(10, \"null\");\n\n---\n\n(a) 正确，定义一个空的vector，其元素类型是vector<int>。\n(b) 错误，ivec和svec的类型不一样，不能拷贝初始化。\n(c) 正确，svec是一个有10个元素的vector，其元素类型是string，内容都是\"null\"。\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_13.cpp",
    "content": "/*\n * 练习3.13：下列的vector对象各包含多少个元素？这些元素的值分别是多少？\n *\n * (a) vector<int> v1;\t\t\t\t(b) vector<int> v2(10);\n * (c) vector<int> v3(10, 42);\t\t(d) vector<int> v4{10};\n * (e) vector<int> v5{10, 42};\t\t(f) vector<string> v6{10};\n * (g) vector<string> v7{10, \"hi\"};\n */\n\n/*\n * (a) 0个元素。\n * (b) 10个元素，都是0。\n * (c) 10个元素，都是42。\n * (d) 1个元素，10。\n * (e) 2个元素，10,42。\n * (f) 10个元素，都是空string。\n * (g) 10个元素，都是字符串hi。\n */\n\n#include <iostream>\n#include <string>\n#include <vector>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\nusing std::vector;\n\nint main()\n{\n\tvector<string> v7{10, \"hi\"};\n\tcout << v7.size() << \" element(s):\";\n\tfor (auto &s : v7)\n\t\tcout << \" \" << s;\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_14.cpp",
    "content": "/*\n * 练习3.14：编写一段程序，用cin读入一组整数并把它们存入一个vector对象。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cin;\nusing std::vector;\n\nint main()\n{\n\tint a = 0;\n\tvector<int> ivec;\n\twhile (cin >> a) {\n\t\tivec.push_back(a);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_15.cpp",
    "content": "/*\n * 练习3.15：改写上题的程序，不过这次读入的是字符串。\n */\n\n#include <iostream>\n#include <vector>\n#include <string>\n\nusing std::cin;\nusing std::vector;\nusing std::string;\n\nint main()\n{\n\tstring str;\n\tvector<string> svec;\n\twhile (cin >> str) {\n\t\tsvec.push_back(str);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_16.cpp",
    "content": "/*\n * 练习3.16：编写一段程序，把练习3.13中vector对象的容量和其具体内容输出出来，检验你之前的回答是否正确，如果不对，回头重新学习3.3.1节（第87页）直到弄明白错在何处位置。\n */\n\n#include <iostream>\n#include <string>\n#include <vector>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\nusing std::vector;\n\n#define PRINT_VEC(vec) \\\ndo { \\\n\tcout << #vec << \" has \" << (vec).size() << \" element(s):\"; \\\n\tfor (auto it = (vec).cbegin(); it != (vec).cend(); ++it) \\\n\t\tcout << \" \" << *it; \\\n\tcout << endl; \\\n} while(0)\n\nint main()\n{\n\tvector<int> v1;\t\t\t\t\t\t// 0个元素\n\tvector<int> v2(10);\t\t\t\t\t// 10个元素，都是0\n\tvector<int> v3(10, 42);\t\t\t\t// 10个元素，都是42\n\tvector<int> v4{10};\t\t\t\t\t// 1个元素，10\n\tvector<int> v5{10, 42};\t\t\t\t// 2个元素，10,42\n\tvector<string> v6{10};\t\t\t\t// 10个元素，都是空string\n\tvector<string> v7{10, \"hi\"};\t\t// 10个元素，都是字符串hi\n\n\tPRINT_VEC(v1);\n\tPRINT_VEC(v2);\n\tPRINT_VEC(v3);\n\tPRINT_VEC(v4);\n\tPRINT_VEC(v5);\n\tPRINT_VEC(v6);\n\tPRINT_VEC(v7);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_17.cpp",
    "content": "/*\n * 从cin读入一组词并把它们存入一个vector对象，然后设法把所有词都改写为大写形式。输出改变后的结果，每个词占一行。\n */\n\n#include <iostream>\n#include <string>\n#include <vector>\n\nusing std::vector;\nusing std::string;\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tvector<string> svec;\n\tstring word;\n\n\twhile (cin >> word)\n\t\tsvec.push_back(word);\n\t\n\tfor (auto &w : svec) {\n\t\tfor (auto &c : w) {\n\t\t\tc = toupper(c);\n\t\t}\n\t}\n\n\tfor (const auto &w : svec)\n\t\tcout << w << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_18.txt",
    "content": "练习3.18：下面的程序合法吗？如果不合法，你准备如何修改？\n\nvector<int> ivec;\nivec[0] 42;\n\n---\n\n不合法，ivec没有元素，无法访问第一个元素。可以这样修改：\n\nvector<int> ivec;\nivec.push_back(42);\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_19.cpp",
    "content": "/*\n * 练习3.19：如果想定义一个含有10个元素的vector对象，所有元素的值都是42，请列举三种不同的实现方法。哪种方法更好呢？为什么？\n */\n\n/*\n * 如下，第一种更好。因为10个元素都一样。参见书本p91。\n */\n\n#include <vector>\nusing std::vector;\n\nint main()\n{\n\tvector<int> ivec1(10, 42);\n\tvector<int> ivec2{42, 42, 42, 42, 42, 42, 42, 42, 42, 42};\n\n\tvector<int> ivec3;\n\tfor (int i = 0; i < 10; ++i)\n\t\tivec3.push_back(42);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_20a.cpp",
    "content": "/*\n * 练习3.20：读入一组整数并把它们存入一个vector对象，将每对相邻整数的和输出出来。改写你的程序，这次要求先输出第1个和最后一个元素的和，接着输出第2个和倒数第2个元素的和，以此类推。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cin;\nusing std::cout;\nusing std::endl;\nusing std::vector;\n\nint main()\n{\n\tvector<int> ivec;\n\tint a = 0;\n\twhile (cin >> a)\n\t\tivec.push_back(a);\n\n\tfor (decltype(ivec.size()) i = 1; i < ivec.size(); ++i)\n\t\tcout << ivec[i] + ivec[i - 1] << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_20b.cpp",
    "content": "/*\n * 练习3.20：读入一组整数并把它们存入一个vector对象，将每对相邻整数的和输出出来。改写你的程序，这次要求先输出第1个和最后一个元素的和，接着输出第2个和倒数第2个元素的和，以此类推。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cin;\nusing std::cout;\nusing std::endl;\nusing std::vector;\n\nint main()\n{\n\tvector<int> ivec;\n\tint a = 0;\n\twhile (cin >> a)\n\t\tivec.push_back(a);\n\n\tif (ivec.empty())\n\t\treturn 0;\n\n\tusing idx = decltype(ivec.size());\n\tidx beg_i = 0;\n\tidx end_i = ivec.size() - 1;\n\n\twhile (beg_i < end_i) {\n\t\tcout << ivec[beg_i] + ivec[end_i] << endl;\n\t\t++beg_i;\n\t\t--end_i;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_21.cpp",
    "content": "/*\n *  练习3.21：请使用迭代器重做3.3.3节（第94页）的第一个练习。\n */\n\n#include <iostream>\n#include <string>\n#include <vector>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\nusing std::vector;\n\n#define PRINT_VEC(vec) \\\ndo { \\\n\tcout << #vec << \" has \" << (vec).size() << \" element(s):\"; \\\n\tfor (auto it = (vec).cbegin(); it != (vec).cend(); ++it) \\\n\t\tcout << \" \" << *it; \\\n\tcout << endl; \\\n} while(0)\n\nint main()\n{\n\tvector<int> v1;\t\t\t\t\t\t// 0个元素\n\tvector<int> v2(10);\t\t\t\t\t// 10个元素，都是0\n\tvector<int> v3(10, 42);\t\t\t\t// 10个元素，都是42\n\tvector<int> v4{10};\t\t\t\t\t// 1个元素，10\n\tvector<int> v5{10, 42};\t\t\t\t// 2个元素，10,42\n\tvector<string> v6{10};\t\t\t\t// 10个元素，都是空string\n\tvector<string> v7{10, \"hi\"};\t\t// 10个元素，都是字符串hi\n\n\tPRINT_VEC(v1);\n\tPRINT_VEC(v2);\n\tPRINT_VEC(v3);\n\tPRINT_VEC(v4);\n\tPRINT_VEC(v5);\n\tPRINT_VEC(v6);\n\tPRINT_VEC(v7);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_22.cpp",
    "content": "/*\n * 练习3.22：修改之前那个输出text第一段的程序，首先把text的第一段全部改成大写形式，然后再输出它。\n */\n\n#include <iostream>\n#include <string>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::string;\nusing std::vector;\n\nint main()\n{\n\tvector<string> text = {\"Hello World!\", \"\", \"Hi There!\"};\n\n\tfor (auto it = text.begin(); it != text.end() && !it->empty(); ++it) {\n\t\tfor (auto s_it = it->begin(); s_it != it->end(); ++s_it) {\n\t\t\t*s_it = toupper(*s_it);\n\t\t}\n\t\tcout << *it << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_23.cpp",
    "content": "/*\n * 练习3.23：编写一段程序，创建一个含有10个整数的vector对象，然后使用迭代器将所有元素的值都变成原来的两倍。输出vector对象的内容，检验程序是否正确。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::vector;\n\nint main()\n{\n\tvector<int> ivec;\n\tfor (int i = 1; i <= 10; ++i)\n\t\tivec.push_back(i);\n\n\tfor (auto it = ivec.begin(); it != ivec.end(); ++it)\n\t\t*it *= 2;\n\n\tfor (auto it = ivec.cbegin(); it != ivec.cend(); ++it)\n\t\tcout << *it << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_24a.cpp",
    "content": "/*\n * 练习3.24：请使用迭代器重做3.3.3节（第94页）的最后一个练习。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cin;\nusing std::cout;\nusing std::endl;\nusing std::vector;\n\nint main()\n{\n\tvector<int> ivec;\n\tint a = 0;\n\twhile (cin >> a)\n\t\tivec.push_back(a);\n\n\tif (ivec.begin() == ivec.end())\n\t\treturn 0;\n\n\tfor (auto it = ivec.cbegin() + 1; it != ivec.cend(); ++it)\n\t\tcout << *it + *(it - 1) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_24b.cpp",
    "content": "/*\n * 练习3.24：请使用迭代器重做3.3.3节（第94页）的最后一个练习。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cin;\nusing std::cout;\nusing std::endl;\nusing std::vector;\n\nint main()\n{\n\tvector<int> ivec;\n\tint a = 0;\n\twhile (cin >> a)\n\t\tivec.push_back(a);\n\n\tif (ivec.empty())\n\t\treturn 0;\n\n\tauto beg = ivec.cbegin();\n\tauto end = ivec.cend() - 1;\n\n\twhile (beg < end) {\n\t\tcout << *beg + *end << endl;\n\t\t++beg;\n\t\t--end;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_25.cpp",
    "content": "/*\n * 练习3.25：3.3.3节（第93页）划分分数段的程序是使用下标运算符实现的，请利用迭代器改写该程序并实现完全相同的功能。\n */\n\n/*\n * 42 65 95 100 39 67 95 76 88 76 83 92 76 93\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\n\nint main()\n{\n\t// 以10分为一个分数段统计成绩的数量：0~9, 10~19,...,90~99, 100\n\tvector<unsigned> scores(11, 0);\t\t// 11个分数段，全都初始化为0\n\n\tunsigned grade;\n\twhile (cin >> grade) {\t\t// 读取成绩\n\t\tif (grade <= 100) {\t\t// 只处理有效成绩\n\t\t\t*(scores.begin() + grade / 10) += 1;\n\t\t}\n\t}\n\n\tfor (auto i : scores)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_26.txt",
    "content": "练习3.26：在100页的二分搜索程序中，为什么用的是mid = beg + (end - beg) / 2，而非mid = (beg + end) / 2;？\n\n因为vector没有两个迭代器相加的操作。\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_27.txt",
    "content": "练习3.27：假设txt_size是一个无参数的函数，它的返回值是int。请问下列哪个定义是非法的？为什么？\n\nunsigned buf_size = 1024;\n(a) ia[buf_size];\t\t(b) int ia[4 * 7 - 14];\n(c) int ia[txt_size()];\t(d) char str[11] = \"fundamental\";\n\n---\n\n(a) 非法，因为buf_size不是常量表达式，不能作为数组的维度。\n(b) 合法。\n(c) 只有当txt_size的返回值是一个constexpr时才合法。\n(d) 非法，没有剩余的空间存储空字符。\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_28.cpp",
    "content": "/*\n * 练习3.28：下列数组中元素的值是什么？\n */\n\n#include <string>\n#include <iostream>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\n\n#define PRINT_ARRAY(arr, len) \\\ndo {\\\n\tcout << #arr << \":\"; \\\n\tfor (int i = 0; i < len; ++i) \\\n\t\tcout << \" \" << arr[i]; \\\n\tcout << endl; \\\n} while(0) \\\n\n//------------------------------------\n\nstring sa[10];\t// 都是空字符串\nint ia[10]; \t// 都是0\nint main()\n{\n\tstring sa2[10];\t// 都是空字符串\n\tint ia2[10];\t// 都是未定义的值\n\n//------------------------------------\n\n\tPRINT_ARRAY(sa, 10);\n\tPRINT_ARRAY(ia, 10);\n\tPRINT_ARRAY(sa2, 10);\n\tPRINT_ARRAY(ia2, 10);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_29.txt",
    "content": "练习3.29：相比于vector来说，数组有哪些缺点，请列举一些。\n\n没有办法动态增长。\n没有迭代器，只能使用下标获取其中的元素。\n不支持拷贝初始化和赋值运算。\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_30.txt",
    "content": "练习3.30：指出下面代码中的索引错误。\n\nconstexpr size_t array_size = 10;\nint ia[array_size];\nfor (size_t ix = 1; ix <= array_size; ++ix)\n\tia[ix] = ix;\n\n---\n\n出现下标越界的错误，ix不能等于array_size。\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_31.cpp",
    "content": "/*\n * 练习3.31：编写一段程序，定义一个含有10个int的数组，令每个元素的值就是其下标值。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tint arr[10] = {};\n\tfor (int i = 0; i < 10; ++i)\n\t\tarr[i] = i;\n\n\tfor (auto i : arr)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_32a.cpp",
    "content": "/*\n * 练习3.32：将上一题刚刚创建的数据拷贝给另外一个数组。利用vector重写程序，实现类似的功能。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tconstexpr size_t LEN = 10;\n\n\tint arr[LEN] = {};\n\tfor (size_t i = 0; i < LEN; ++i)\n\t\tarr[i] = i;\n\n\t// copy array\n\tint arr2[LEN] = {};\n\tfor (size_t i = 0; i < LEN; ++i)\n\t\tarr2[i] = arr[i];\n\n\t// print copied array\n\tfor (auto i : arr2)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_32b.cpp",
    "content": "/*\n * 练习3.32：将上一题刚刚创建的数据拷贝给另外一个数组。利用vector重写程序，实现类似的功能。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::vector;\n\nint main()\n{\n\tvector<int> vec1;\n\tfor (int i = 0; i < 10; ++i)\n\t\tvec1.push_back(i);\n\n\tvector<int> vec2(vec1);\n\tfor (auto i : vec2)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_33.txt",
    "content": "练习3.33：对于104页的程序来说，如果不初始化scores将发生什么？\n\nscores数组里的元素将拥有未定义的值，导致计算结果未知。\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_34.txt",
    "content": "练习3.34：假定p1和p2指向同一个数组中的元素，则下面程序的功能是什么？什么情况下该程序是非法的？\n\np1 += p2 - p1;\n\n---\n\n等同于p1 = p2; 这段程序是合法的。\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_35.cpp",
    "content": "/*\n * 练习3.35：编写一段程序，利用指针将数组中的元素置为0。\n */\n\n#include <iostream>\n\nusing std::begin;\nusing std::end;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tint arr[5] = {1, 2, 3, 4, 5};\n\n\tauto b = begin(arr);\n\tauto e = end(arr);\n\tauto len = e - b;\n\n\twhile (b < e) {\n\t\t*b = 0;\n\t\t++b;\n\t}\n\n\tfor (int i = 0; i < len; ++i)\n\t\tcout << arr[i] << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_36a.cpp",
    "content": "/*\n * 练习3.36：编写一段程序，比较两个数组是否相等。再写一段程序，比较两个vector对象是否相等。\n */\n\n#include <iostream>\n\nusing std::begin;\nusing std::end;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tint arr1[] = {1, 2, 3};\n\tint arr2[] = {1, 2, 3};\n\n\tauto len1 = end(arr1) - begin(arr1);\n\tauto len2 = end(arr2) - begin(arr2);\n\n\tbool is_equal = true;\n\tif (len1 != len2)\n\t\tis_equal = false;\n\telse\n\t{\n\t\tfor (int i = 0; i < len1; ++i)\n\t\t\tif (arr1[i] != arr2[i]) {\n\t\t\t\tis_equal = false;\n\t\t\t\tbreak;\n\t\t\t}\n\t}\n\n\tif (is_equal)\n\t\tcout << \"Is equal\" << endl;\n\telse\n\t\tcout << \"Is not equal\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_36b.cpp",
    "content": "/*\n * 练习3.36：编写一段程序，比较两个数组是否相等。再写一段程序，比较两个vector对象是否相等。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::vector;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tvector<int> vec1 = {1, 2, 3};\n\tvector<int> vec2 = {1, 2, 3};\n\n\tbool is_equal = (vec1 == vec2);\n\n\tif (is_equal)\n\t\tcout << \"Is equal\" << endl;\n\telse\n\t\tcout << \"Is not equal\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_37.cpp",
    "content": "/*\n * 练习3.37：下面的程序是何含义，程序的输出结果是什么？\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\t//--------------------------------------------\n\tconst char ca[] = {'h', 'e', 'l', 'l', 'o'};\n\tconst char *cp = ca;\n\twhile (*cp) {\n\t\tcout << *cp << endl;\n\t\t++cp;\n\t}\n\t//--------------------------------------------\n\t\n\t// 上面这段程序是想输出ca数组的内容，然而此数组未以空字符结尾，运行的结果未定义\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_38.txt",
    "content": "练习3.38：在本节中我们提到，将两个指针相加不但是非法的，而且也没什么意义。请问为什么两个指针相加没什么意义？\n\n因为没有办法解释这样的行为。见：https://stackoverflow.com/questions/2935038/why-cant-i-add-pointers\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_39a.cpp",
    "content": "/*\n * 编写一段程序，比较两个string对象。再编写一段程序，比较两个C风格字符串的内容。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::string;\n\nint main()\n{\n\tstring str1 = \"hello\";\n\tstring str2 = \"hi\";\n\n\tif (str1 == str2)\n\t\tcout << \"str1 == str2\" << endl;\n\telse if (str1 > str2)\n\t\tcout << \"str1 > str2\" << endl;\n\telse\n\t\tcout << \"str1 < str2\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_39b.cpp",
    "content": "/*\n * 编写一段程序，比较两个string对象。再编写一段程序，比较两个C风格字符串的内容。\n */\n\n#include <iostream>\n#include <cstring>\n\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tconst char *str1 = \"hello\";\n\tconst char *str2 = \"hi\";\n\n\tauto res = strcmp(str1, str2);\n\n\tif (0 == res)\n\t\tcout << \"str1 == str2\" << endl;\n\telse if (res > 0)\n\t\tcout << \"str1 > str2\" << endl;\n\telse\n\t\tcout << \"str1 < str2\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_40.cpp",
    "content": "/*\n * 练习3.40：编写一段程序，定义两个字符数组并用字符串字面值初始化它们；接着再定义一个字符数组存放前两个数组连接后的结果。使用strcpy和strcat把前两个数组的内容拷贝到第三个数组中。\n */\n\n#include <iostream>\n#include <cstring>\n\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tchar str1[] = \"Hello\";\n\tchar str2[] = \"World\";\n\t\n\tconstexpr size_t sz = sizeof(str1) + sizeof(str2) - 1;\n\tchar str3[sz] = {0};\n\n\tstrcpy(str3, str1);\n\tstrcat(str3, str2);\n\n\tcout << str3 << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_41.cpp",
    "content": "/*\n * 练习3.41：编写一段程序，用整型数组初始化一个vector对象。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::endl;\nusing std::cout;\nusing std::vector;\n\nint main()\n{\n\tint a[] = {1, 2, 3, 4, 5};\n\tvector<int> vec(std::begin(a), std::end(a));\n\n\tfor (auto i : vec)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_42.cpp",
    "content": "/*\n * 练习3.42：编写一段程序，将含有整数元素的vector对象拷贝给一个整型数组。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::endl;\nusing std::cout;\nusing std::vector;\n\nint main()\n{\n\tvector<int> vec = {1, 2, 3, 4, 5};\n\n\tconstexpr size_t LEN = 5;\n\tint a[LEN] = {0};\n\n\tsize_t cnt = 0;\n\tfor (auto i : vec) {\n\t\ta[cnt++] = i;\n\t\tif (cnt >= LEN)\n\t\t\tbreak;\n\t}\n\n\tfor (size_t i = 0; i < LEN; ++i)\n\t\tcout << a[i] << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_43a.cpp",
    "content": "/*\n * 练习3.43：编写3个不同版本的程序，令其均能输出ia的元素。版本1使用范围for语句管理迭代过程；版本2和版本3都使用普通的for语句，其中版本2要求用下标运算符，版本3要求用指针。此外，在所有3个版本的程序中都要直接写出数据类型，而不能使用类型别名、auto关键字或decltype关键字。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::cin;\nusing std::endl;\n\nint main()\n{\n\tconstexpr size_t ROW = 3;\n\tconstexpr size_t COL = 4;\n\tint a[ROW][COL] =\n\t{\n\t\t{0, 1, 2, 3},\n\t\t{4, 5, 6, 7},\n\t\t{8, 9, 10, 11}\n\t};\n\n\tfor (const auto &row : a) {\n\t\tfor (const auto &col : row) {\n\t\t\tcout << col << \" \";\n\t\t}\n\t\tcout << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_43b.cpp",
    "content": "/*\n * 练习3.43：编写3个不同版本的程序，令其均能输出ia的元素。版本1使用范围for语句管理迭代过程；版本2和版本3都使用普通的for语句，其中版本2要求用下标运算符，版本3要求用指针。此外，在所有3个版本的程序中都要直接写出数据类型，而不能使用类型别名、auto关键字或decltype关键字。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::cin;\nusing std::endl;\n\nint main()\n{\n\tconstexpr size_t ROW = 3;\n\tconstexpr size_t COL = 4;\n\tint a[ROW][COL] =\n\t{\n\t\t{0, 1, 2, 3},\n\t\t{4, 5, 6, 7},\n\t\t{8, 9, 10, 11}\n\t};\n\n\tfor (size_t i = 0; i < ROW; ++i) {\n\t\tfor (size_t j = 0; j < COL; ++j) {\n\t\t\tcout << a[i][j] << \" \";\n\t\t}\n\t\tcout << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_43c.cpp",
    "content": "/*\n * 练习3.43：编写3个不同版本的程序，令其均能输出ia的元素。版本1使用范围for语句管理迭代过程；版本2和版本3都使用普通的for语句，其中版本2要求用下标运算符，版本3要求用指针。此外，在所有3个版本的程序中都要直接写出数据类型，而不能使用类型别名、auto关键字或decltype关键字。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::cin;\nusing std::endl;\n\nint main()\n{\n\tconstexpr size_t ROW = 3;\n\tconstexpr size_t COL = 4;\n\tint a[ROW][COL] =\n\t{\n\t\t{0, 1, 2, 3},\n\t\t{4, 5, 6, 7},\n\t\t{8, 9, 10, 11}\n\t};\n\n\tfor (int (*p)[COL] = a; p != a + ROW; ++p) {\n\t\tfor (int *q = *p; q != *p + COL; ++q) {\n\t\t\tcout << *q << \" \";\n\t\t}\n\t\tcout << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_44.cpp",
    "content": "/*\n * 练习3.43：改写上一个练习中的程序，使用类型别名来代替循环控制变量的类型。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::cin;\nusing std::endl;\n\nint main()\n{\n\tconstexpr size_t ROW = 3;\n\tconstexpr size_t COL = 4;\n\tint a[ROW][COL] =\n\t{\n\t\t{0, 1, 2, 3},\n\t\t{4, 5, 6, 7},\n\t\t{8, 9, 10, 11}\n\t};\n\n\tusing int_arr = int[COL];\n\tfor (int_arr *p = a; p != a + ROW; ++p) {\n\t\tfor (int *q = *p; q != *p + COL; ++q) {\n\t\t\tcout << *q << \" \";\n\t\t}\n\t\tcout << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/exercise_3_45.cpp",
    "content": "/*\n * 练习3.43：再一次改写程序，这次使用auto关键字。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::cin;\nusing std::endl;\n\nint main()\n{\n\tconstexpr size_t ROW = 3;\n\tconstexpr size_t COL = 4;\n\tint a[ROW][COL] =\n\t{\n\t\t{0, 1, 2, 3},\n\t\t{4, 5, 6, 7},\n\t\t{8, 9, 10, 11}\n\t};\n\n\tfor (auto p = a; p != a + ROW; ++p) {\n\t\tfor (auto q = *p; q != *p + COL; ++q) {\n\t\t\tcout << *q << \" \";\n\t\t}\n\t\tcout << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/usage_bsearch.cpp",
    "content": "// 使用迭代器进行二分搜索\n\n#include <iostream>\n#include <vector>\n\nusing std::vector;\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tvector<int> ivec = {1, 3, 4, 5, 8, 9, 10};\n\tint sought = 0;\n\tcout << \"Enter a number you want to find:\" << endl;\n\tcin >> sought;\n\n\t// ivec必须是有序的\n\t// beg和end表示我们的搜索的范围\n\tauto beg = ivec.begin(), end = ivec.end();\n\tauto mid = ivec.begin() + (end - beg) / 2; // 初始状态下的中间点\n\n\t// 当还有元素尚未检查并且我们还没有找到sought时执行循环\n\twhile (mid != end && *mid != sought) {\n\t\tif (sought < *mid)\n\t\t\tend = mid;\n\t\telse\n\t\t\tbeg = mid + 1;\n\t\tmid = beg + (end - beg) / 2;\t// 新的中间点\n\t}\n\n\tif (mid != end)\n\t\tcout << \"Find the number\" << endl;\n\telse\n\t\tcout << \"Not find the number\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/usage_count_punct_in_string.cpp",
    "content": "// 使用范围for语句和ispunct函数来统计string对象中标点符号的个数\n\n#include <iostream>\n#include <string>\n#include <cctype>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tstring s(\"Hello World!!!!\");\n\t\n\tdecltype(s.size()) punct_cnt = 0;\n\t// 统计s中的标点符号\n\tfor (auto c : s)\n\t\tif (ispunct(c))\n\t\t\t++punct_cnt;\n\t\n\tcout << punct_cnt\n\t     << \" punctuation characters in \" << s << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch03_Strings_Vectors_and_Arrays/usage_string_toupper.cpp",
    "content": "// 把字符串改写为大写字母的形式\n\n#include <iostream>\n#include <string>\n\nusing std::string;\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tstring s(\"Hello World\");\n\t// 转换成大写形式\n\tfor (auto &c : s)\n\t\tc = toupper(c);\t// c是一个引用，因此赋值语句将改变s中字符的值\n\tcout << s << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"test_sizeof.cpp\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/example_comma_operator.cpp",
    "content": "// example: 逗号运算符（p140）\n\n/*\n * 逗号运算符的使用例子，逗号运算符经常被用在for循环当中\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\n\nint main()\n{\n\tvector<int> ivec(10);\n\n\tvector<int>::size_type cnt = ivec.size();\n\t// 将把从size到1的值赋给ivec的元素\n\tfor (vector<int>::size_type ix = 0; ix != ivec.size(); ++ix, --cnt)\n\t{\n\t\tivec[ix] = cnt;\n\t}\n\n\t//----------------------------------------\n\t\n\tfor (auto i : ivec)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/example_increment_operator.cpp",
    "content": "// example: 在一条语句中混用解引用和递增运算符（p132）\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\n\nint main()\n{\n\tvector<int> v = {1, 2, 3, -1, 4};\n\n\tauto pbeg = v.begin();\n\t// 输出元素直至遇到第一个负数为止\n\twhile (pbeg != v.end() && *pbeg >= 0)\n\t\tcout << *pbeg++ << endl;\t// 输出当前值，并将pbeg向前移动一个元素\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_01.txt",
    "content": "练习4.1：表达式5+10*20/2的求值结果是多少？\n\n105\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_02.txt",
    "content": "练习4.2：根据4.12节中的表，在下述表达式的合理位置添加括号，使得添加括号后运算对象的组合顺序与添加括号前一致。\n\n(a) *vec.begin()\t\t(b) *vec.begin() + 1\n\n---\n\n(a) *(vec.begin())\t\t(b) (*(vec.begin())) + 1\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_03.txt",
    "content": "练习4.3：C++语言没有明确规定大多数二元运算符的求值顺序，给编译器优化留下了余地。这种策略实际上是在代码生成效率和程序潜在缺陷之间进行了权衡，你认为这可以接受吗？请说出你的理由。\n\n可以接受。因为C++更注重效率，这也是它的优势之一。只要搞明白求值顺序的概念，不触碰未定义的行为，就可以避免踩到陷阱。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_04.cpp",
    "content": "/*\n * 练习4.4：在下面的表达式中添加括号，说明其求值的过程及最终结果。编写程序编译该（不加括号的）表达式并输出其结果验证之前的推断。\n *\n * 12 / 3 * 4 + 5 * 15 + 24 % 4 / 2\n */\n\n/*\n * ((12 / 3) * 4) + (5 * 15) + (24 % (4 / 2))\n *\n * 结果是：16 + 75 + 0 = 91\n */\n\n#include <iostream>\n\nint main()\n{\n\tstd::cout << 12 / 3 * 4 + 5 * 15 + 24 % 4 / 2 << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_05.txt",
    "content": "练习4.5：写出下列表达式的求值结果。\n\n(a) -30 * 3 + 21 / 5\t\t(b) -30 + 3 * 21 / 5\n(c) 30 / 3 * 21 % 5\t\t\t(d) -30 / 3 * 21 % 4\n\n---\n\n(a) -86\n(b) -18\n(c) 0\n(d) -2\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_06.txt",
    "content": "练习4.6：写出一条表达式用于确定一个整数是奇数还是偶数。\n\n(n % 2 == 0)\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_07.cpp",
    "content": "/*\n * 练习4.7：溢出是何含义？写出三条将导致溢出的表达式。\n */\n\n/*\n * 当计算的结果超出该类型所能表示的范围时就会产生溢出。\n */\n\n#include <iostream>\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tshort sval = 32767;\n\tsval += 1;\n\tcout << sval << endl; // -32768\n\n\tchar cval = 127;\n\tcval += 1;\n\tcout << (int)cval << endl; // -128\n\n\tunsigned uval = 0;\n\tuval -= 1;\n\tcout << uval << endl; // 4294967295\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_08.txt",
    "content": "练习4.8：说明在逻辑与、逻辑或及相等性运算符中运算对象求值的顺序。\n\n---\n\n逻辑与和逻辑或都是先求左侧运算对象的值再求右侧运算对象的值，且遵循短路求值策略。见p126。\n\n相等性运算符（似乎）没有要求求值顺序，当两侧的运算对象值相等时，返回true，否则返回false。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_09.txt",
    "content": "练习4.9：解释在下面的if语句中条件部分的判断过程。\n\nconst char *cp = \"Hello World\";\nif (cp && *cp)\n\n---\n\n先判断cp是否是空指针，如果不是，再判断*cp是否是空字符\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_10.cpp",
    "content": "/*\n * 练习4.10：为while循环写一个条件，使其从标准输入中读取整数，遇到42时停止。\n */\n\n#include <iostream>\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint iv;\n\twhile (cin >> iv && iv != 42);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_11.txt",
    "content": "练习4.11：书写一条表达式用于测试4个值a、b、c、d的关系，确保a大于b、b大于c、c大于d。\n\n(a > b && b > c && c > d)\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_12.txt",
    "content": "练习4.12：假设i、j和k是三个整数，说明表达式i != j<k的含义。\n\nj<k得到的是一个布尔值，与i做比较转换成整数0或者1，所以比较的结果是i是否等于0或者1。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_13.cpp",
    "content": "/*\n * 练习4.13：在下述语句中，当赋值完成后i和d的值分别是多少？\n *\n * int i; double d;\n * (a) d = i = 3.5;\n * (b) i = d = 3.5;\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tint i; double d;\n\n\td = i = 3.5;\n\tcout << d << \" \" << i << endl;\t// 3 3\n\n\ti = d = 3.5;\n\tcout << d << \" \" << i << endl;  // 3.5 3\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_14.txt",
    "content": "练习4.14：执行下述if语句后将发生什么？\n\nif (42 = i) // ...\nif (i = 42) // ...\n\n---\n\n第一条语句是错误的，赋值运算符的左侧必须是一个左值。\n\n第二条语句判断i是否为0，若不是（事实上也不是），就执行if语句块。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_15.txt",
    "content": "练习4.15：下面的赋值是非法的，为什么？应该如何修改？\n\ndouble dval; int ival; int *pi;\ndval = ival = pi = 0;\n\n---\n\n因为pi是指针，不能赋值给一个int。可以这样改：\npi = 0;\ndval = ival = 0;\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_16.cpp",
    "content": "/*\n * 练习4.16：尽管下面的语句合法，但它们实际执行的行为可能和预期的不一样，为什么？应该如何修改？\n * (a) if (p = getPtr() != 0)\t(b) if (i = 1024)\n */\n\n/*\n * (a) error:  can not convert‘bool’ to ‘int*’ in assignment, 应该改成if ((p = getPtr()) != 0)\n r\n * (b) 先给i赋值为1024，然后判断i是否等于0（结果永远为true），实际应该是if (i == 1024)\n */\n\nint *getPtr()\n{\n\treturn nullptr;\n}\n\nint main()\n{\n\tint *p = nullptr;\n\n\t//if (p = getPtr() != 0);\n\tif ((p = getPtr()) != 0);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_17.txt",
    "content": "练习4.17：说明前置递增运算符和后置递减运算符的区别。\n\n前置版本的求值结果是运算对象本身，是一个左值。后置版本的求值结果是运算对象的原始副本，是一个右值。因此后置版本会增加保留此副本的额外工作。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_18.txt",
    "content": "练习4.18：如果第132页那个输出vector对象元素的while循环使用前置递增运算符，将得到什么结果？\n\n如果vector里面没有负数，那么将会对尾后迭代器解引用，结果是未定义的。而且第一个元素会被跳过。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_19.txt",
    "content": "练习4.19：假设ptr的类型是指向int的指针、vec的类型是vector<int>、ival的类型是int，说明下面的表达式是何含义？如果有表达式不正确，为什么？应该如何修改？\n\n(a) ptr != 0 && *ptr++\t\t\t(b) ival++ && ival\n(c) vec[ival++] <= vec[ival]\n\n---\n\n(a) 先判断ptr是否为空，若不是，则对其执行后置递增运算，并返回其原始对象副本，对此副本解引用，判断解引用的结果是否不为0。\n\n(b) 先对ival执行后置递增运算，返回其原始对象副本，判断此副本是否不为0，如果是，再判断当前的ival是否不为0。\n\n(c) 是错误的，因为不能判断左侧的ival后置递增运算是否先于右侧的ival求值，其结果是未定义的。应该改成：\n\n    vec[ival] <= vec[ival + 1];\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_20.txt",
    "content": "练习4.20：假设iter的类型是vector<string>::iterator，说明下面的表达式是否合法。如果合法，表达式的含义是什么？如果不合法，错在何处？\n\n(a) *iter++;\t\t// 合法。此表达式先对iter执行后置递增运算，然后返回其原始副本，得到其解引用的值。\n\n(b) (*iter)++;\t\t// 不合法。*iter的结果是string类型，string没有后置递增操作。\n\n(c) *iter.empty();\t// 不合法，.运算符的优先级要高，所以会执行iter.empty()，但iter没有empty成员，故错误。\n\n(d) iter->empty();\t// 合法，调用了string的empty成员，判断其是否为空。\n\n(e) ++*iter;\t\t// 不合法，string没有前置递增操作。\n\n(f) iter++->empty();// 合法，执行iter的后置递增操作，然后返回其原始副本，对其使用箭头运算符，\n\t\t\t\t\t// 调用string的empty成员，判断其是否为空。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_21.cpp",
    "content": "/*\n * 练习4.21：编写一段程序，使用条件运算符从vector<int>中找到哪些元素的值是奇数，然后将这些奇数值翻倍。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\n\nint main()\n{\n\tvector<int> ivec = {1, 3, 4, 6, 7};\n\n\tfor (auto &i : ivec)\n\t\t(i % 2 != 0) ? i *= 2 : i;\t// 这样写没问题，一定先对i % 2求值\n\n\tfor (const auto i : ivec)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_22a.cpp",
    "content": "/*\n * 练习4.22：本节的示例程序将成绩划分成high pass、pass和fail三种，扩展该程序使其进一步将60分到75分之间的成绩设定为low pass。要求程序包含两个版本：一个版本只使用条件运算符；另外一个版本使用1个或多个if语句。哪个版本的程序更容易理解呢？为什么？\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\nint main()\n{\n\tunsigned grade = 0;\n\tcout << \"Enter grade: \";\n\tcin >> grade;\n\n\tif (grade > 100) {\n\t\tcout << \"Invalid grade\";\n\t\treturn 1;\n\t}\n\n\tstring finalgrade = (grade > 90) ? \"high pass\" : (grade > 75) ? \"pass\" : (grade >= 60) ? \"low pass\" : \"fail\";\n\tcout << finalgrade << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_22b.cpp",
    "content": "/*\n * 练习4.22：本节的示例程序将成绩划分成high pass、pass和fail三种，扩展该程序使其进一步将60分到75分之间的成绩设定为low pass。要求程序包含两个版本：一个版本只使用条件运算符；另外一个版本使用1个或多个if语句。哪个版本的程序更容易理解呢？为什么？\n */\n\n/*\n * 使用if-else的版本更容易理解，因为将逻辑代码分成多条语句，易于阅读。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\nint main()\n{\n\tunsigned grade = 0;\n\tcout << \"Enter grade: \";\n\tcin >> grade;\n\n\tif (grade > 100) {\n\t\tcout << \"Invalid grade\";\n\t\treturn 1;\n\t}\n\n\tstring finalgrade;\n\tif (grade > 90)\n\t\tfinalgrade = \"high pass\";\n\telse if (grade > 75)\n\t\tfinalgrade = \"pass\";\n\telse if (grade >= 60)\n\t\tfinalgrade = \"low pass\";\n\telse\n\t\tfinalgrade = \"fail\";\n\n\tcout << finalgrade << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_23.cpp",
    "content": "/*\n * 练习4.23：因为运算符的优先级问题，下面这条表达式无法通过编译。根据4.12节中的表（第147页）指出它的问题在哪里？应该如何修改？\n *\n * string s = \"word\";\n * string pl = s + s[s.size() - 1] == 's' ? \"\" : \"s\";\n */\n\n/*\n * 加法的优先级要高于条件运算符，因此这条表达式把 s + s[s.size() - 1] 结合在一起，结果是一个string，然后和's'做比较，但是string不能和一个字符做比较，所以报错。\n *\n * error: no match for ‘operator==’ (operand types are ‘std::basic_string<char>’ and ‘char’)\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\nint main()\n{\n\tstring s = \"word\";\n\t//string pl = s + s[s.size() - 1] == 's' ? \"\" : \"s\";\n\t\n\t// 修改如下\n\tstring pl = s + (s[s.size() - 1] == 's' ? \"\" : \"s\");\n\n\tcout << pl << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_24.txt",
    "content": "练习4.24：本节的示例程序将成绩划分成high pass、pass和fail三种，它的依据是条件运算符满足右结合律。假如条件运算符满足的是左结合律，求值过程将是怎样的？\n\nfinalgrade = (grade > 90) ? \"high pass\" : (grade < 60) ? \"fail\" : \"pass\";\n\n会把这个表达式结合在一起(grade > 90) ? \"high pass\" : (grade < 60)，然后再拿结果和后面的表达式组成条件表达式。如同：\n\nfinalgrade = ((grade > 90) ? \"high pass\" : (grade < 60)) ? \"fail\" : \"pass\"; \n\n因此，如果grade > 90，那么finalgrade恒为fail。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_25.cpp",
    "content": "/*\n * 练习4.25：如果一台机器上int占32位，用的是Latin-1字符集，其中字符'q'的二进制形式是01110001，那么\n * 表达式~'q' << 6的值是什么？\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\t// 先把'q'提升为int，然后对其取反，再左移6位\n\t// 0000 0000 0000 0000 0000 0000 0111 0001\n\t// 1111 1111 1111 1111 1111 1111 1000 1110\n\t// 1111 1111 1111 1111 1110 0011 1000 0000\n\t// 输出结果是：-7296\n\tcout << (~'q' << 6) << endl;\n\n\tcout << (int)0xFFFFE380 << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_26.txt",
    "content": "练习4.26：在本节关于测验成绩的例子中，如果使用unsigned int作为quizl的类型会发生什么情况？\n\n由于unsigned int可能是16位的，因此右侧的操作数如果是27，则超过了这个位数，那么结果是未定义的。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_27.cpp",
    "content": "/*\n * 练习4.27：下列表达式的结果是什么？\n *\n * unsigned long ul1 = 3, ul2 = 7;\n * (a) ul1 & ul2\t(b) ul1 | ul2\n * (c) ul1 && ul2\t(d) ul1 | ul2\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tunsigned long ul1 = 3, ul2 = 7;\n\n\tcout << (ul1 & ul2) << endl;\t// 0011 & 0111 --> 0011 --> 3\n\tcout << (ul1 | ul2) << endl;\t// 0011 | 0111 --> 0111 --> 7\n\tcout << (ul1 && ul2) << endl;\t// true --> 1\n\tcout << (ul1 || ul2) << endl;\t// true --> 1\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_28.cpp",
    "content": "/*\n * 4.28：编写一段程序，输出每一种内置类型所占空间的大小。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\n#define PRINT_BUILTIN_TYPES_SIZE(type) \\\ndo { \\\n\tcout << #type << \" size is: \"; \\\n\tcout << sizeof(type) << endl; \\\n} while(0) \\\n\nint main()\n{\n\tPRINT_BUILTIN_TYPES_SIZE(char);\n\tPRINT_BUILTIN_TYPES_SIZE(short);\n\tPRINT_BUILTIN_TYPES_SIZE(int);\n\tPRINT_BUILTIN_TYPES_SIZE(long);\n\tPRINT_BUILTIN_TYPES_SIZE(long long);\n\tPRINT_BUILTIN_TYPES_SIZE(bool);\n\tPRINT_BUILTIN_TYPES_SIZE(float);\n\tPRINT_BUILTIN_TYPES_SIZE(double);\n\tPRINT_BUILTIN_TYPES_SIZE(long double);\n\tPRINT_BUILTIN_TYPES_SIZE(void*);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_29.cpp",
    "content": "/*\n * 练习4.29：推断下面代码的输出结果并说明理由。实际运行这段程序，结果和你想象的一样吗？如果不一样，为什么？\n *  int x[10]; int *p = x;\n *  cout << sizeof(x)/sizeof(*x) << endl;\n *  cout << sizeof(p)/sizeof(*p) << endl;\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint x[10]; int *p = x;\n\tcout << sizeof(x)/sizeof(*x) << endl;\t// 10\n\tcout << sizeof(p)/sizeof(*p) << endl;\t// 8 / 4 --> 2，在我的CentOS 7上面，指针大小为8个字节\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_30.txt",
    "content": "\n练习4.30：根据4.12节中的表（第147页），在下述表达式中的适当位置加上括号，使得加上括号之后的表达式的含义与原来的含义相同。\n\n(a) sizeof x + y\t\t(b) sizeof p->mem[i]\n(c) sizeof a < b\t\t(d) sizeof f()\n\n---\n\n(a) sizeof (x) + y\n(b) sizeof (p->mem[i])\n(c) sizeof (a) < b\n(d) sizeof (f()) ps: 如果f()返回类型是void，这个操作是未定义的\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_31.cpp",
    "content": "/*\n * 练习4.31：本节的程序使用了前置版本的递增运算符和递减运算符，解释为什么要用前置版本而不用后置版本。要想使用后置版本的递增递减运算符需要做哪些改动？使用后置版本重写本节的程序。\n */\n\n/*\n * 因为后置版本需要保留一份原始对象的副本将其作为运算结果，而前置不用。见p132。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\n\nint main()\n{\n\tvector<int> ivec(10);\n\n\tvector<int>::size_type cnt = ivec.size();\n\t// 将把从size到1的值赋给ivec的元素\n\tfor (vector<int>::size_type ix = 0; ix != ivec.size(); ix++, cnt--) // 仅在这里做改动\n\t{\n\t\tivec[ix] = cnt;\n\t}\n\n\t//----------------------------------------\n\t\n\tfor (auto i : ivec)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_32.txt",
    "content": "练习4.32：解释下面这个循环的含义。\n\nconstexpr int size = 5;\nint ia[size] = {1, 2, 3, 4, 5};\nfor (int *ptr = ia, ix = 0; ix != size && ptr != ia + size; ++ix, ++ptr) { /* ... */ }\n\n---\n\n这个循环就是为了遍历一遍数组ia。其中ptr和ix的功能是一样的。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_33.txt",
    "content": "练习4.33：根据4.12节中的表（第147页）说明下面这条表达式的含义：\n\nsomeValue ? ++x, ++y : --x, --y\n\n---\n\n由于逗号运算符的优先级最低，因此表达式的结合性是：\n\n(someValue ? ++x, ++y : --x) , --y\n\n最后无论如何都会--y。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_34.txt",
    "content": "练习4.34：根据本节给出的变量定义，说明在下面的表达式中奖发生什么样的类型转换？\n\n(a) if (fval)\t(b) dval = fval + ival;\t(c) dval + ival * cval\n\n---\n\n(a) fval将转换成bool类型。\n(b) ival将转换成float类型，然后相加结果转换成double。\n(c) cval转换成int，然后ival * cval的结果转换成double。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_35.txt",
    "content": "练习4.35：假如有如下的定义：\n\nchar cval;\tint ival;\tunsigned int ui;\nflaot fval;\tdouble dval;\n\n请回答在下面的表达式中发生了隐式类型转换吗？如果有，指出来。\n\n(a) cval = 'a' + 3;\t\t\t// 'a'提升为int，然后加法结果再转换成char\n(b) fval = ui - ival * 1.0;\t// ival转换成double，ui也转换成double，结果再转换成float\n(c) dval = ui * fval;\t\t// ui转换成float，结果再转换成double\n(d) cval = ival + fval + dval;\t// ival转换成float，相加结果再转换成double，结果再转换成char\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_36.txt",
    "content": " 练习4.36：假设i是int类型，d是double类型，书写表达式i*=d使其执行整数类型的乘法而非浮点类型的乘法。\n\n i *= static_cast<int>(d);\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_37.txt",
    "content": "练习4.37：用命名的强制类型转换改写下列旧式的转换语句。\n\nint i; double d; const string *ps; char *pc; void *pv;\n\n(a) pv = (void*)ps;\t\t// pv = const_cast<string*>(ps);\n(b) i = int(*pc);\t\t// i = static_cast<int>(*pc);\n(c) pv = &d;\t\t\t// 无需修改\n(d) pc = (char*)pv;\t\t// pc = static_cast<char*>(pv);\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/exercise_4_38.txt",
    "content": "练习4.38：说明下面这条表达式的含义。\n\ndouble slope = static_cast<double>(j/i);\n\n把j/i的结果转换成double，实际上编译器可以自动进行转换。\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/test_bitwise_oparator.cpp",
    "content": "/*\n * 测试位运算\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tsigned char c = 1;\n\tcout << (c << 31) << endl;\t// 负数，signed char被提升为int\n\n\tunsigned char uc = 1;\n\tcout << (uc << 31) << endl;\t// 负数，unsigned char也被提升为int\n\n\tunsigned int ui = 1;\n\tcout << (ui << 31) << endl;\t// 正数\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch04_Expressions/test_sizeof.cpp",
    "content": "/*\n * 测试一下sizeof\n * sizeof 是一个运算符，返回存储对象所需的字节数，该对象的类型可能是某个给定类型的名字，也可能由表达式的返回结果确定。\n * sizeof 在编译时进行计算。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nvoid f()\n{\n}\n\nint f2()\n{\n\n}\n\nint main()\n{\n\t//cout << sizeof f() << endl; \t// 返回的是1，然而会报warning\n\n\tcout << sizeof f2() << endl;\t// 返回4\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/data/vowels.txt",
    "content": "  aaaAA\n\te\tEEEE\n\ti IIII\n ooOOO\nuuuuu\n\nff flaaaaa fi\nfi\nfl\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/example_do_while.cpp",
    "content": "// example: do while语句（p169）\n\n/*\n * 使用do while循环询问用户\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\nint main()\n{\n\t// 不断提示用户输入一对数，然后求其和\n\tstring rsp;\t// 作为循环的条件，不能定义在do的内部\n\tdo {\n\t\tcout << \"please enter two values: \";\n\t\tint val1 = 0, val2 = 0;\n\t\tcin >> val1 >> val2;\n\t\tcout << \"The sum is: \" << val1 + val2 << endl;\n\t\tcout << \"More? Enter yes or no: \";\n\t\tcin >> rsp;\n\t} while (!rsp.empty() && rsp[0] != 'n');\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/example_if_statement.cpp",
    "content": "// example: 嵌套if语句（p157）\n\n/*\n * 书中使用if else语句的案例，在代码格式上自己做了微调\n * 把数字形式表示的成绩转换成字母形式\n */\n\n#include <iostream>\n#include <vector>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\nusing std::string;\n\nint main()\n{\n\tconst vector<string> scores =  { \"F\", \"D\", \"C\", \"B\", \"A\", \"A++\" };\n\n\tint grade;\n\tcout << \"Enter grade: \";\n\tcin >> grade;\n\n\t// 如果grade小于60，对应的字母是F；否则计算其下标\n\tstring lettergrade;\n\tif (grade < 60) {\n\t\tlettergrade = scores[0];\n\t}\n\telse {\n\t\tlettergrade = scores[(grade - 50) / 10];\t// 获得字母形式的成绩\n\t\tif (grade != 100) { // 只要不是A++，就考虑添加加号或减号\n\t\t\tif (grade % 10 > 7) {\n\t\t\t\tlettergrade += '+';\n\t\t\t}\n\t\t\telse if (grade % 10 < 3) {\n\t\t\t\tlettergrade += '-';\n\t\t\t}\n\t\t}\n\t\t\n\t}\n\n\tcout << lettergrade << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_01.txt",
    "content": "练习5.1：什么是空语句？什么时候会用到空语句？\n\n只有一个分号的语句是空语句。如果在程序的某个地方，语法上需要一条语句但逻辑上不需要，此时应该使用空语句。\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_02.txt",
    "content": "练习5.2：什么是块？什么时候会使用块？\n\n复合语句就是块，它是用花括号括起来的语句和声明的序列。如果在程序的某个地方，语法上需要一条语句，但是逻辑上需要多条语句，则应该使用块把多条语句变成一条复合语句。\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_03.cpp",
    "content": "/*\n * 练习5.3：使用逗号运算符（参见4.10节，第140页）重写1.4.1节（第10页）的while循环，使它不再需要块，观察改写之后的代码的可读性提高了还是降低了。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint sum = 0, i = 50;\n\n\twhile (i <= 100) \n\t\tsum += i, ++i;\n\n\tstd::cout << \"Sum of 50 to 100 inclusive is \" << sum << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_04.txt",
    "content": "练习5.4：说明下列例子的含义，如果存在问题，试着修改它。\n\n(a) while (string::iterator iter != s.end()) { /* ... */ }\n\niter需要被初始化，应该在while语句前面做这个工作：\n\nstring::iterator iter = s.begin();\nwhile (iter != s.end) { /* ... */  }\n\n(b) while (bool status = find(word)) { /* ... * }\n\t\tif (!status) { /* ... */ }\n\nstatus只能在while语句内部使用，在if语句中已经失效了。可以把它定义到外面去。另外判断status是没有意义的，因为while已经做了判断了，!status一定是true。\n\nbool status = false;\nwhile (status = find(word)) { /* ... */ }\nif (!status) { /* ... */ } // 这个判断没有意义\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_05.cpp",
    "content": "/*\n * 练习5.5：写一段自己的程序，使用if else语句实现把数字成绩转换成字母成绩的要求。\n */\n\n/*\n * 这个练习和example_if_statement.cpp一模一样。\n */\n\n#include <iostream>\n#include <vector>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\nusing std::string;\n\nint main()\n{\n\tconst vector<string> scores =  { \"F\", \"D\", \"C\", \"B\", \"A\", \"A++\" };\n\n\tint grade;\n\tcout << \"Enter grade: \";\n\tcin >> grade;\n\n\t// 如果grade小于60，对应的字母是F；否则计算其下标\n\tstring lettergrade;\n\tif (grade < 60) {\n\t\tlettergrade = scores[0];\n\t}\n\telse {\n\t\tlettergrade = scores[(grade - 50) / 10];\t// 获得字母形式的成绩\n\t\tif (grade != 100) { // 只要不是A++，就考虑添加加号或减号\n\t\t\tif (grade % 10 > 7) {\n\t\t\t\tlettergrade += '+';\n\t\t\t}\n\t\t\telse if (grade % 10 < 3) {\n\t\t\t\tlettergrade += '-';\n\t\t\t}\n\t\t}\n\t\t\n\t}\n\n\tcout << lettergrade << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_06.cpp",
    "content": "/*\n * 练习5.6：改写上一题的程序，使用条件运算符（参见4.7节，第134页）代替if else语句。\n */\n\n#include <iostream>\n#include <vector>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\nusing std::string;\n\nint main()\n{\n\tconst vector<string> scores =  { \"F\", \"D\", \"C\", \"B\", \"A\", \"A++\" };\n\n\tint grade;\n\tcout << \"Enter grade: \";\n\tcin >> grade;\n\n\t// 如果grade小于60，对应的字母是F；否则计算其下标\n\tstring lettergrade;\n\tlettergrade = (grade < 60 ? scores[0] : scores[(grade - 50) / 10]);\n\tlettergrade += (grade != 100 && grade >= 60 ? \n\t\t\t\t   (grade % 10 > 7 ? \"+\" : (grade % 10 < 3 ? \"-\" : \"\")) : \"\");\n\n\tcout << lettergrade << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_07.txt",
    "content": "练习5.7：改正下列代码段中的错误。\n\n(a) if (ival1 != ival2)\n\t\tival1 = ival2\n\telse ival1 = ival2 = 0;\n\n(a 改) \n\tif (ival1 != ival2)\n\t\tival1 = ival2;\t// 加一个分号\n\telse ival1 = ival2 = 0;\n\n(b) if (ival < minval)\n\t\tminval = ival;\n\t\toccurs = 1;\n\n(b 改)\n\tif (ival < minval) { // 加上花括号\n\t\tminval = ival;\n\t\toccurs = 1;\n\t}\n\n(c) if (int ival = get_value())\n\t\tcout << \"ival = \" << ival << endl;\n\tif (!ival)\n\t\tcout << \"ival = 0 \\n\";\n\n(c 改)\n\tint ival; // 把ival定义在外面\n\tif (ival = get_value())\n\t\tcout << \"ival = \" << ival << endl;\n\tif (!ival)\n\t\tcout << \"ival = 0 \\n\";\n\n(d) if (ival = 0)\n\t\tival = get_value();\n\n(d 改)\n\tif (ival == 0)\t// 用==操作符，如果是用=，则条件恒为false\n\t\tival = get_value();\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_08.txt",
    "content": "练习5.8：什么是“悬垂else”？C++语言是如何处理else字句的？\n\n悬垂else是这样一个问题：当if分支多余else分支时，如何知道某个给定的else是和哪个if匹配。C++规定else与离它最近的尚未匹配的if匹配。如果想使else分支和外层的if语句匹配起来，可以在内层if语句的两端加上花括号，使其成为一个块。\n\nps：这样的描述感觉很晦涩，必须加以例子来理解，书本已经给予了详细的解释。见p158。\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_09.cpp",
    "content": "/*\n * 练习5.9：编写一段程序，使用一系列if语句统计从cin读入的文本中有多少元音字母。\n */\n\n/*\n * $ ./exercise_5_09 < data/vowels.txt\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint aCnt = 0;\n\tint eCnt = 0;\n\tint iCnt = 0;\n\tint oCnt = 0;\n\tint uCnt = 0;\n\tchar ch = '\\0';\n\n\twhile (cin >> ch) {\n\t\tif (ch == 'a')\n\t\t\t++aCnt;\n\t\telse if (ch == 'e')\n\t\t\t++eCnt;\n\t\telse if (ch == 'i')\n\t\t\t++iCnt;\n\t\telse if (ch == 'o')\n\t\t\t++oCnt;\n\t\telse if (ch == 'u')\n\t\t\t++uCnt;\n\t}\n\n\tcout << \"Number of vowel a: \" << aCnt << endl;\n\tcout << \"Number of vowel e: \" << eCnt << endl;\n\tcout << \"Number of vowel i: \" << iCnt << endl;\n\tcout << \"Number of vowel o: \" << oCnt << endl;\n\tcout << \"Number of vowel u: \" << uCnt << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_10.cpp",
    "content": "/*\n * 练习5.10：我们之前实现的统计元音字母的程序存在一个问题：如果元音字母以大写形式出现，不会被统计在内。编写一段程序，既统计元音字母的小写形式，也统计大写形式，也就是说，新程序遇到'a'和'A'都应该递增aCnt的值，以此类推。\n */\n\n// ./exercise_5_10 < data/vowels.txt\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;\n\tchar ch = 0;\n\n\twhile (cin >> ch) {\n\t\tswitch (ch) {\n\t\t\tcase 'a':\n\t\t\tcase 'A':\n\t\t\t\t++aCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'e':\n\t\t\tcase 'E':\n\t\t\t\t++eCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'i':\n\t\t\tcase 'I':\n\t\t\t\t++iCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'o':\n\t\t\tcase 'O':\n\t\t\t\t++oCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'u':\n\t\t\tcase 'U':\n\t\t\t\t++uCnt;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tcout << \"Number of vowel a: \" << aCnt << endl;\n\tcout << \"Number of vowel e: \" << eCnt << endl;\n\tcout << \"Number of vowel i: \" << iCnt << endl;\n\tcout << \"Number of vowel o: \" << oCnt << endl;\n\tcout << \"Number of vowel u: \" << uCnt << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_11.cpp",
    "content": "/*\n * 练习5.11：修改统计元音字母的程序，使其也能统计空格、制表符和换行符的数量。\n */\n\n// ./exercise_5_11 < data/vowels.txt\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;\n\tint spaceCnt = 0, tabCnt = 0, newlineCnt = 0;\n\tchar ch = 0;\n\n\twhile (cin >> std::noskipws >> ch) { // std::noskipws表示不忽略空白字符\n\t\tswitch (ch) {\n\t\t\tcase ' ':\n\t\t\t\t++spaceCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase '\\t':\n\t\t\t\t++tabCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase '\\n':\n\t\t\t\t++newlineCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'a':\n\t\t\tcase 'A':\n\t\t\t\t++aCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'e':\n\t\t\tcase 'E':\n\t\t\t\t++eCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'i':\n\t\t\tcase 'I':\n\t\t\t\t++iCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'o':\n\t\t\tcase 'O':\n\t\t\t\t++oCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'u':\n\t\t\tcase 'U':\n\t\t\t\t++uCnt;\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\tcout << \"Number of space: \" << spaceCnt << endl;\n\tcout << \"Number of tab: \" << tabCnt << endl;\n\tcout << \"Number of newline: \" << newlineCnt << endl;\n\tcout << \"Number of vowel a: \" << aCnt << endl;\n\tcout << \"Number of vowel e: \" << eCnt << endl;\n\tcout << \"Number of vowel i: \" << iCnt << endl;\n\tcout << \"Number of vowel o: \" << oCnt << endl;\n\tcout << \"Number of vowel u: \" << uCnt << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_12.cpp",
    "content": "/*\n * 练习5.12：修改统计元音字母的程序，使其能统计以下含有两个字符的字符序列的数量：ff、fl和fi。\n */\n\n// ./exercise_5_12 < data/vowels.txt\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0;\n\tchar ch = 0;\n\n\tchar prech = 0;\t// 上一次读取的字符\n\tint ffCnt = 0, flCnt = 0, fiCnt = 0;\n\n\twhile (cin >> std::noskipws >> ch) {\n\t\tswitch (ch) {\n\t\t\tcase 'a':\n\t\t\tcase 'A':\n\t\t\t\t++aCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'e':\n\t\t\tcase 'E':\n\t\t\t\t++eCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'i':\n\t\t\tcase 'I':\n\t\t\t\t++iCnt;\n\t\t\t\tif (prech == 'f') ++fiCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'o':\n\t\t\tcase 'O':\n\t\t\t\t++oCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'u':\n\t\t\tcase 'U':\n\t\t\t\t++uCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'l':\n\t\t\t\tif (prech == 'f') ++flCnt;\n\t\t\t\tbreak;\n\n\t\t\tcase 'f':\n\t\t\t\tif (prech == 'f') ++ffCnt;\n\t\t}\n\n\t\tprech = ch;\n\t}\n\n\tcout << \"Number of vowel a: \" << aCnt << endl;\n\tcout << \"Number of vowel e: \" << eCnt << endl;\n\tcout << \"Number of vowel i: \" << iCnt << endl;\n\tcout << \"Number of vowel o: \" << oCnt << endl;\n\tcout << \"Number of vowel u: \" << uCnt << endl;\n\tcout << \"Number of vowel ff: \" << ffCnt << endl;\n\tcout << \"Number of vowel fl: \" << flCnt << endl;\n\tcout << \"Number of vowel fi: \" << fiCnt << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_13.txt",
    "content": "练习5.13：下面显示的每个程序都含有一个常见的编程错误，指出错误在哪里，然后修改它们。\n\nPS: 此题代码略多，不写在这里了。直接简述答案。练习见书本p164。\n\n(a) case分支后没有加break。\n\n(b) ix不能这样初始化。可以在case分支里使用块，在块内初始化。\n\n(c) case分支只能有一个标签。\n\n(d) case分布的标签必须是整型常量。\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_14.cpp",
    "content": "/*\n * 练习5.14：编写一段程序，从标准输入中读取若干string对象并查找连续重复出现的单词。所谓连续重复出现的意思是：一个单词后面紧跟着这个单词本身。要求记录连续重复出现的最大次数以及对应的单词。如果这样的单词存在，输出重复出现的最大次数；如果不存在，输出一条信息说明任何单词都没有连续出现过。例如：如果输入是\n *\n * how now now now brown cow cow\n *\n * 那么输出应该表明单词now连续出现了3次。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\nint main()\n{\n\tstring cur_str, pre_str, max_repeat_count_str;\n\tint cur_repeat_count = 1, max_repeat_count = 1;\n\n\twhile (cin >> cur_str) {\n\t\tif (pre_str == cur_str) {\n\t\t\t++cur_repeat_count;\n\t\t\tif (cur_repeat_count > max_repeat_count) {\n\t\t\t\tmax_repeat_count = cur_repeat_count;\n\t\t\t\tmax_repeat_count_str = cur_str;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tcur_repeat_count = 1;\n\t\t}\n\n\t\tpre_str = cur_str;\n\t}\n\n\tif (max_repeat_count > 1) {\n\t\tcout << max_repeat_count_str << \" repeat \" << max_repeat_count << \" times\" << endl;\n\t}\n\telse {\n\t\tcout << \"No word repeat\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_15.txt",
    "content": "练习5.15：说明下列循环的含义并改正其中的错误。\n\n(a) for (int ix = 0; ix != sz; ++ix) { /* ... */ }\n    if (ix != sz)\n\t    // ...\n\n(a 改) ix只在for语句中有效，如果想在外部使用，需要提前定义。\n    int ix;\n\tfor (ix = 0; ix != sz; ++ix) { /* ... */ }\n\tif (ix != sz)\n\t    // ...\n\n(b) int ix;\n    for (ix != sz; ++ix) { /* ... */ }\n\n(b 改) for语句语法错误，没有init-statement，且ix没有初始化。\n    int ix;\n\tfor (ix = 0; ix != sz; ++ix) { /* ... */ }\n\n(c) for (int ix = 0; ix != sz; ++ix, ++sz) { /* ... */ }\n\n(c 改) sz在expression部分递增，可能会导致无线循环。\n    for (int ix = 0; ix != sz; ++ix) { /* ... */ }\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_16a.cpp",
    "content": "/*\n * 练习5.16：while循环特别适用于那种条件保持不变、反复执行操作的情况，例如，当未达到文件末尾时不断读取下一个值。for循环则更像是在按步骤迭代，它的索引值在某个范围内依次变化。根据每种循环的习惯用法各自编写一段程序，然后分别用另一种循环改写。如果只能使用一种循环，你倾向于使用哪种呢？为什么？\n */\n\n/*\n * while的习惯用法，使用while写。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint a;\n\twhile (cin >> a)\n\t\tcout << a << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_16b.cpp",
    "content": "/*\n * 练习5.16：while循环特别适用于那种条件保持不变、反复执行操作的情况，例如，当未达到文件末尾时不断读取下一个值。for循环则更像是在按步骤迭代，它的索引值在某个范围内依次变化。根据每种循环的习惯用法各自编写一段程序，然后分别用另一种循环改写。如果只能使用一种循环，你倾向于使用哪种呢？为什么？\n */\n\n/*\n * while的习惯用法，使用for写。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint a = 0;\n\tfor (; cin >> a;)\n\t\tcout << a << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_16c.cpp",
    "content": "/*\n * 练习5.16：while循环特别适用于那种条件保持不变、反复执行操作的情况，例如，当未达到文件末尾时不断读取下一个值。for循环则更像是在按步骤迭代，它的索引值在某个范围内依次变化。根据每种循环的习惯用法各自编写一段程序，然后分别用另一种循环改写。如果只能使用一种循环，你倾向于使用哪种呢？为什么？\n */\n\n/*\n * for的习惯用法，用for写。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint sum = 0;\n\tfor (int a = 0; a < 10; ++a)\n\t\tsum += a;\n\n\tcout << sum << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_16d.cpp",
    "content": "/*\n * 练习5.16：while循环特别适用于那种条件保持不变、反复执行操作的情况，例如，当未达到文件末尾时不断读取下一个值。for循环则更像是在按步骤迭代，它的索引值在某个范围内依次变化。根据每种循环的习惯用法各自编写一段程序，然后分别用另一种循环改写。如果只能使用一种循环，你倾向于使用哪种呢？为什么？\n */\n\n/*\n * for的习惯用法，用while写。\n *\n * 我更倾向于使用for循环，因为其循环次数可控。用for循环也能完全模拟while循环。缺陷是for循环稍微会难阅读一点。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint sum = 0;\n\tint a = 0;\n\twhile (a < 10) {\n\t\tsum += a;\n\t\t++a;\n\t}\n\n\tcout << sum << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_17.cpp",
    "content": "/*\n * 练习5.17：假设有两个包含整数的vector对象，编写一段程序，检验其中一个vector对象是否是另一个的前缀。为了实现这一目标，对于两个不等长的vector对象，只需挑出长度较短的那个，把它的所有元素和另一个vector对象比较即可。例如，如果两个vector对象的元素分别是0, 1, 1, 2和0, 1, 1, 2, 3, 5, 8，则程序返回的结果应该为真。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\n\nint main()\n{\n\tvector<int> v1 = {0, 1, 1, 2};\n\t//vector<int> v2 = {0, 1, 1, 2, 3, 5, 8};\n\tvector<int> v2 = {0, 1, 2, 2, 3, 5, 8};\n\n\tauto min_sz = v1.size() < v2.size() ? v1.size() : v2.size();\n\n\tbool is_prefix = true;\n\tfor (decltype(min_sz) i = 0; i < min_sz; ++i) {\n\t\tif (v1[i] != v2[i]) {\n\t\t\tis_prefix = false;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (is_prefix) {\n\t\tcout << \"True\" << endl;\n\t}\n\telse {\n\t\tcout << \"False\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_18.txt",
    "content": "练习5.18：说明下列循环的含义并改正其中的错误。\n\n原题代码略多，此处忽略。\n\n(a) do while语句中应该添加语句块。\n\n(b) condition中不能定义变量，ival必须定义在do while语句前。\n\n(c) condition中的变量必须定义在do while语句外面。\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_19.cpp",
    "content": "/*\n * 练习5.19：编写一段程序，使用do while循环重复地执行下述任务：首先提示\n * 用户输入两个string对象，然后挑出较短的那个并输出它。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\nint main()\n{\n\tdo {\n\t\tstring str1, str2;\n\t\tcout << \"Enter two string:\" << endl;\n\t\tcin >> str1 >> str2;\n\n\t\tif (cin) {\n\t\t\tif (str1.length() < str2.length())\n\t\t\t\tcout << str1 << \" is shorter\" << endl;\n\t\t\telse\n\t\t\t\tcout << str2 << \" is shorter\" << endl;\n\t\t}\n\t} while (cin); // 简单起见，输入CTRL+D结束循环\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_20.cpp",
    "content": "/*\n * 练习5.20：编写一段程序，从标准输入中读取string对象的序列直到连续出\n * 现两个相同的单词或者所有单词都读完为止。使用while循环一次读取一个单\n * 词，当一个单词连续出现两次时使用break语句终止循环。输出连续重复出现\n * 的单词，或者输出一个消息说明没有任何单词是连续重复出现的。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\nint main()\n{\n\tstring cur_str, pre_str;\n\tbool is_repeat = false;\n\n\twhile (cin >> cur_str) {\n\t\tif (pre_str == cur_str) {\n\t\t\tis_repeat = true;\n\t\t\tbreak;\n\t\t}\n\n\t\tpre_str = cur_str;\n\t}\n\n\tif (is_repeat)\n\t\tcout << cur_str << endl;\n\telse\n\t\tcout << \"no word repeat\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_21.cpp",
    "content": "/*\n * 练习5.21：修改5.5.1节（第171页）练习题的程序，使其找到的是重复单词必须以大写字母开头。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\nint main()\n{\n\tstring cur_str, pre_str;\n\tbool is_repeat = false;\n\n\twhile (cin >> cur_str) {\n\t\tif (islower(cur_str[0])) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (pre_str == cur_str) {\n\t\t\tis_repeat = true;\n\t\t\tbreak;\n\t\t}\n\t\telse {\n\t\t\tpre_str = cur_str;\n\t\t}\n\t}\n\n\tif (is_repeat)\n\t\tcout << cur_str << endl;\n\telse\n\t\tcout << \"no word repeat\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_22.txt",
    "content": "练习5.22：本节的最后一个例子跳到begin，其实使用循环能\n更好的完成该任务。重写这段代码，注意不再使用goto语句。\n\ndo {\n\tint sz = get_size();\n} while (sz <= 0);\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_23.cpp",
    "content": "/*\n * 练习5.23：编写一段程序，从标准输入读取两个整数，输出第一个数除以第二个数的结果。\n */\n\n#include <iostream>\n#include <stdexcept>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint a = 0, b = 0;\n\n\tcout << \"Enter two number(a/b): \";\n\tcin >> a >> b;\n\tcout << static_cast<double>(a) / b << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_24.cpp",
    "content": "/*\n * 练习5.24：修改你的程序，使得当第二个数是0时抛出异常。先不要设定catch子句，运行程序并真的为除数输入0，看看会发生什么？\n */\n\n/*\n * 程序异常结束，并输出一段文字：\n * terminate called after throwing an instance of 'std::runtime_error'\n *   what():  second number can not equal 0\n */\n\n#include <iostream>\n#include <stdexcept>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint a = 0, b = 0;\n\n\tcout << \"Enter two number(a/b): \";\n\tcin >> a >> b;\n\n\tif (b == 0)\n\t\tthrow std::runtime_error(\"second number can not equal 0\");\n\n\tcout << static_cast<double>(a) / b << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch05_Statements/exercise_5_25.cpp",
    "content": "/*\n * 练习5.25：修改上一题的程序，使用try语句块去捕获异常。catch子句应该为用户输出一条提示信息，询问其是否输出新数并重新执行try语句块的内容。\n */\n\n#include <iostream>\n#include <stdexcept>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint a = 0, b = 0;\n\n\twhile (true)\n\t{\n\t\ttry {\n\t\t\tcout << \"Enter two number(a/b): \";\n\t\t\tcin >> a >> b;\n\n\t\t\tif (b == 0)\n\t\t\t\tthrow std::runtime_error(\"second number can not equal 0\");\n\n\t\t\tcout << a * 1.0 / b << endl;\n\t\t\tbreak;\n\n\t\t} catch (std::runtime_error err) {\n\t\t\tcout << err.what() << endl;\n\t\t\t\n\t\t\tcout << \"Enter again[y or n]: \";\n\t\t\tchar c;\n\t\t\tcin >> c;\n\t\t\tif (!cin || c != 'y')\n\t\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/Chapter6.h",
    "content": "#ifndef CHAPTER6_H\n#define CHAPTER6_H\n\nint fact(int n); // 求n!\nint calc_fact_for_user(); // 输入一个数字，求其阶乘\nint my_abs(int n); // 求n的绝对值\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"exercise_6_30.cpp exercise_6_46.cpp\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/example_array_param.cpp",
    "content": "// example: 数组形参（p193）\n \n/*\n * 定义若干print函数，功能是打印数组内容，稍微做了微调\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\n// 使用标记指定数组长度\nvoid print(const char *cp)\n{\n\tif (cp)\t\t\t\t\t// 若cp非空\n\t\twhile (*cp)\t\t\t// 只要指针所指字符非空字符\n\t\t\tcout << *cp++;\t// 输出当前字符并将指针向前移动一个位置\n}\n\n// 使用标准库规范\nvoid print(const int *beg, const int *end)\n{\n\t// 输出beg到end之间（不含end）的所有元素\n\twhile (beg != end)\n\t\tcout << *beg++ << \" \";\t// 输出当前元素并将指针向前移动一个位置\n\tcout << endl;\n}\n\n// 显示传递一个表示数组大小的形参\n// const int ia[]等价于const int* ia\n// size表示数组的大小，将它显式地传给函数用于控制对ia元素的访问\nvoid print(const int ia[], size_t size)\n{\n\tfor (size_t i = 0; i != size; ++i) {\n\t\tcout << ia[i] << \" \";\n\t}\n\tcout << endl;\n}\n\n// 数组引用形参\nvoid print(int (&arr)[10])\n{\n\tfor (auto elem : arr)\n\t\tcout << elem << \" \";\n\tcout << endl;\n}\n\nint main()\n{\n\tprint(\"Hello World\\n\");\t\t\t\t// void print(const char *cp)\n\n\tint j[2] = {0, 1};\n\tprint(std::begin(j), std::end(j));\t// void print(const int *beg, const int *end)\n\n\tprint(j, 2);\t\t\t\t\t\t// void print(const int ia[], size_t size)\n\n\tint k[10] = {1, 2, 3, 4};\t\t\t// void print(int (&arr)[10])\n\tprint(k);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/example_const_cast_and_overloading.cpp",
    "content": "// example: const_cast和重载（p209）\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\n// 比较两个string对象的长度，返回较短的那个引用\nconst string &shorterString(const string &s1, const string &s2)\n{\n\tcout << \"Called this function: \" << \"const string &shorterString(const string &s1, const string &s2)\" << endl;\n\n\treturn s1.size() <= s2.size() ? s1 : s2;\n}\n\n// 重载\nstring &shorterString(string &s1, string &s2)\n{\n\tcout << \"Called this function: \" << \"string &shorterString(string &s1, string &s2)\" << endl;\n\n\tauto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));\n\treturn const_cast<string&>(r);\n}\n\nint main()\n{\n\tstring s1 = \"hello\";\n\tstring s2 = \"hi\";\n\n\tcout << shorterString(s1, s2) << endl; // 调用顺序 --> 非const版本的 --> const版本的\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/example_functions_with_no_return_value.cpp",
    "content": "// example: 无返回值函数（p200）\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\n\nvoid swap(int &v1, int &v2)\n{\n\t// 如果两个值是相等的，则不需要交换，直接退出\n\tif (v1 == v2)\n\t\treturn;\n\n\t// 如果函数执行到了这里，说明还需要继续完成某些功能\n\tint tmp = v2;\n\tv2 = v1;\n\tv1 = tmp;\n\t// 此处无须显式的return语句\n}\n\nint main()\n{\n\tint a = 1, b = 2;\n\tcout << a << \" \" << b << endl;\n\n\tswap(a, b);\n\tcout << a << \" \" << b << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/example_initializer_list_param.cpp",
    "content": "// example: initializer_list形参（可变形参，p198）\n\n#include <string>\n#include <iostream>\n#include <initializer_list>\n\nusing std::cout;\nusing std::cin;\nusing std::endl;\nusing std::string;\nusing std::initializer_list;\n\nvoid error_msg(int error_num, initializer_list<string> il)\n{\n\tcout << error_num << \": \";\n\tfor (auto beg = il.begin(); beg != il.end(); ++beg)\n\t\tcout << *beg << \" \";\n\tcout << endl;\n}\n\nint main()\n{\n\tstring actual = \"hello\";\n\tstring expected;\n\n\tcin >> expected;\n\n\tif (expected != actual)\n\t\terror_msg(42, {__FUNCTION__, expected, actual});\n\telse\n\t\terror_msg(0, {__FUNCTION__, \"okay\"});\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/example_inline_functions.cpp",
    "content": "// example: 内联函数可避免函数调用开销（p213）\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\n// 内联版本：寻找两个string对象中较短的那个\ninline const string &\nshorterString(const string &s1, const string &s2)\n{\n\treturn s1.size() <= s2.size() ? s1 : s2;\n}\n\nint main()\n{\n\tcout << shorterString(\"Hello\", \"Hi\") << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/example_passing_a_multidimensional_array.cpp",
    "content": "// example: 传递多维数组给形参（p195）\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nvoid print(int (*matrix)[10], int rowSize)\n{\n\tfor (int i = 0; i < rowSize; ++i) {\n\t\tfor (int j = 0; j < 10; ++j) {\n\t\t\tcout << matrix[i][j] << \" \";\n\t\t}\n\t\tcout << endl;\n\t}\n}\n\nint main()\n{\n\tint matrix[2][10] = \n\t{\n\t\t{1, 2, 3},\n\t\t{4, 5, 6}\n\t};\n\n\tprint(matrix, 2);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/example_preprocessor_variable.cpp",
    "content": "// example: 使用预处理变量输出错误信息（p216）\n\n#include <iostream>\n\nusing std::cerr;\nusing std::endl;\n\nvoid error_msg(const char *msg)\n{\n\tcerr << \"Error: \" << __FILE__\n\t     << \" : in function \" << __func__\n\t\t << \" at line \" << __LINE__ << endl\n\t\t << \"    Compiled on \" << __DATE__\n\t\t << \" at \" << __TIME__ << endl\n\t\t << \"    error_msg: \" << msg << endl;\n}\n\nint main()\n{\n\terror_msg(\"something wrong\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/example_recursion.cpp",
    "content": "// example: 递归（p204）\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\n// 计算val的阶乘，即1 * 2 * 3 ... * val\nint factorial(int val)\n{\n\tif (val > 1)\n\t\treturn factorial(val - 1) * val;\n\treturn 1;\n}\n\nint main()\n{\n\tcout << factorial(5) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/example_return_from_main.cpp",
    "content": "// example: 主函数main的返回值（p203）\n\n#include <cstdlib>\n\nint main()\n{\n\tbool some_failure = true;\n\tif (some_failure)\n\t\treturn EXIT_FAILURE;\n\telse\n\t\treturn EXIT_SUCCESS;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/example_returning_a_pointer_to_an_array.cpp",
    "content": "// example: 返回数组指针（使用decltype，p206）\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\n// 指向5个整数的数组的类型别名\nusing arrT = int[5];\n\nint odd[] = {1, 3, 5, 7, 9};\nint even[] = {0, 2, 4, 6, 8};\n\n// 返回一个指针，该指针指向含有5个整数的数组\ndecltype(odd) *arrPtr(int i)\n{\n\treturn (i % 2) ? &odd : &even;\n}\n\nint main()\n{\n\tarrT *arr = arrPtr(1);\n\n\tfor (auto i : *arr)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/example_using_ref_to_avoid_cp.cpp",
    "content": "// example: 使用引用传递避免拷贝（p189）\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\n// compare the length of two strings\nbool isShorter(const string &s1, const string &s2)\n{\n\treturn s1.size() < s2.size();\n}\n\nint main()\n{\n\tstring str1 = \"Hi\";\n\tstring str2 = \"Hello\";\n\tif (isShorter(str1, str2))\n\t\tcout << str1 << \" is shorter than \" << str2 << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/example_using_ref_to_return_info.cpp",
    "content": "// example: 使用引用形参返回额外信息（p189）\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\n// 返回s中c第一次出现的位置索引\n// 引用形参occurs负责统计c出现的总次数\nstring::size_type find_char(const string &s, char c, string::size_type &occurs)\n{\n\tauto ret = s.size();\t// 第一次出现的位置（如果有的话）\n\toccurs = 0;\n\tfor (decltype(ret) i = 0; i != s.size(); ++i) {\n\t\tif (s[i] == c) {\n\t\t\tif (ret == s.size())\n\t\t\t\tret = i;\t// 记录c第一次出现的位置\n\t\t\t++occurs;\n\t\t}\n\t}\n\treturn ret;\t// 出现次数通过occurs隐式地返回\n}\n\nint main()\n{\n\tstring::size_type occurs;\n\tauto index = find_char(\"hello world\", 'o', occurs);\n\n\tif (occurs > 0) {\n\t\tcout << \"o occurs \" << occurs << \" times\" << endl;\n\t\tcout << \"first occurs index: \" << index << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_01.txt",
    "content": "练习6.1：实参和形参的区别是什么？\n\n1. 实参和形参的类型可能不一致，但实参必须能转换成形参。\n2. 实参是形参的初始值，但实参和形参是不同的变量（PS：对于引用类型的形参这样讲应该不合适）。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_02.txt",
    "content": "练习6.2：请指出下列函数哪个有错误，为什么？应该如何修改这些错误呢？\n\n(a) int f() {\n\t\tstring s;\n\t\t// ...\n\t\treturn s;\n    }\n\n(a 改) 函数的返回类型错误，必须返回一个int类型的变量。\n\n(b) f2(int i) { /* ... */ }\n\n(b 改) 没有指明返回类型。\n\n(c) int calc(int v1, int v1) { /* ... */ }\n\n(c 改) 形参命名冲突。\n\n(d) double square(double x) return x * x;\n\n(d 改) 函数体必须用花括号括起来。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_03.cpp",
    "content": "/*\n * 练习6.3：编写你自己的fact函数，上机检查是否正确。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint fact(int n)\n{\n\tint ret = 1;\n\tfor (; n > 1; --n) {\n\t\tret *= n;\n\t}\n\treturn ret;\n}\n\nint main()\n{\n\tcout << fact(5) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_04.cpp",
    "content": "/*\n * 练习6.4：编写一个与用户交互的函数，要求用户输入一个数字，计算生成该数字的阶乘。\n * 在main函数中调用该函数。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint calc_fact_for_user()\n{\n\tint n = 0;\n\tcout << \"Enter a number: \";\n\tcin >> n;\n\n\tif (n <= 0 || n > 12) {\t// 超过此范围的结果将是非法的，简单起见，终止程序\n\t\tstd::cerr << \"Check valid range: [1, 12]\" << endl;\n\t\texit(1);\n\t}\n\n\tint ret = 1;\n\tfor (; n > 1; --n) {\n\t\tret *= n;\n\t}\n\treturn ret;\n}\n\nint main()\n{\n\tint ret = calc_fact_for_user();\n\tcout << ret << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_05.cpp",
    "content": "/*\n * 练习6.5：编写一个函数输出其实参的绝对值\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint my_abs(int n)\n{\n\tif (n > 0)\n\t\treturn n;\n\telse\n\t\treturn -n;\n}\n\nint main()\n{\n\tcout << my_abs(1) << endl;\n\tcout << my_abs(-1) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_06.cpp",
    "content": "/*\n * 练习6.6：说明形参、局部变量以及局部静态变量的区别。编写一个函数，同时用到这三种形式。\n */\n\n/*\n * 形参和普通局部变量都是自动变量，形参在函数执行时初始化（且必须使用实参初始化），\n * 并在函数结束时销毁。普通局部变量在第一次执行定义语句时初始化，在块结束时销毁。\n * 局部静态变量只在第一次执行定义语句时初始化一次，且生命周期伴随进程。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint foo(int param)\n{\n\tstatic int b = 0;\n\tb += param;\n\n\treturn b;\n}\n\nint main()\n{\n\tint n = 1;\n\tfoo(n);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_07.cpp",
    "content": "/*\n * 练习6.7：编写一个函数，当它第一次调用时返回0，以后每次被调用返回值加1。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint foo()\n{\n\tstatic int cnt = 0;\n\treturn cnt++;\n}\n\nint main()\n{\n\tcout << foo() << endl;\n\tcout << foo() << endl;\n\tcout << foo() << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_08.txt",
    "content": "练习6.8：编写一个名为Chapter6.h的头文件，令其包含6.1节中的函数声明。\n\n见Chapter.h\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_09/build.sh",
    "content": "#! /bin/bash\n\nbuild()\n{\n\tg++ -c factMain.cc fact.cc\n\tg++ factMain.o fact.o -o main\n}\n\nclean()\n{\n\trm -f *.o main\n}\n\nif [ \"$1\" = \"clean\" ]; then\n\tclean\nelse\n\tbuild\nfi\n\nexit 0\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_09/exercise_6_09.txt",
    "content": "练习6.9：编写你自己的fact.cc和factMain.cc，这两个文件都应该包含上\n一小节的练习中编写的Chapter6.h头文件。通过这些文件，理解你的编译\n器是如何支持分离式编译的。\n\n见本目录下的文件。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_09/fact.cc",
    "content": "#include \"../Chapter6.h\"\n\nint fact(int n)\n{\n\tint ret = 1;\n\tfor (; n > 1; --n)\n\t\tret *= n;\n\n\treturn ret;\n}\n\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_09/factMain.cc",
    "content": "/*\n * 调用fact函数\n */\n\n#include <iostream>\n#include \"../Chapter6.h\"\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tint n = 5;\n\tcout << n << \"! is \" << fact(n) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_10.cpp",
    "content": "/*\n * 练习6.10：编写一个函数，使用指针形参交换两个整数的值。在代码中调用\n * 该函数并输出交换后的结果，以此验证函数的正确性。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nvoid swap(int *a, int *b)\n{\n\tint tmp = *b;\n\t*b = *a;\n\t*a = tmp;\n}\n\nint main()\n{\n\tint a = 1, b = 2;\n\tswap(&a, &b);\n\n\tcout << a << \" \" << b << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_11.cpp",
    "content": "/*\n * 练习6.11：编写并验证你自己的reset函数，使其作用于引用类型的参数。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nvoid reset(int &a)\n{\n\ta = 0;\n}\n\nint main()\n{\n\tint a = 42;\n\treset(a);\n\n\tcout << a << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_12.cpp",
    "content": "/*\n * 练习6.12：改写6.2.1节中练习6.10（第188页）的程序，使用引用而非\n * 指针交换两个整数的值。你觉得哪种方法更易于使用呢？为什么？\n */\n\n/*\n * 引用会好一点，少写了一些符号，看上去易于理解。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nvoid swap(int &a, int &b)\n{\n\tint tmp = b;\n\tb = a;\n\ta = tmp;\n}\n\nint main()\n{\n\tint a = 1, b = 2;\n\tswap(a, b);\n\n\tcout << a << \" \" << b << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_13.txt",
    "content": "练习6.13：假设T是某种类型的名字，说明以下两个函数声明的区别：一个\n是void f(T)，另一个是void f(&T)。\n\n第一个是传值调用，第二个是传引用调用。传值会增加一个拷贝工作，且形参和实参是两个独立的对象。传引用的话，形参会绑定到形参上。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_14.txt",
    "content": "练习6.14：举一个形参应该是引用类型的例子，再举一个形参不能是引用类型的例子。\n\n# 形参应该是引用：\n\nvoid read(istream &is)\n{\n\t// ...\n}\n\n# 形参不能是引用：\n\n- 当形参会改变，且不应该影响实参时。\n\nint sum(int n)\n{\n\tint sum = 0;\n\twhile (n > 0) sum += n--;\n\treturn sum;\n}\n\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_15.txt",
    "content": "> 练习6.15：说明find_char函数中的三个形参为什么是现在的类型，\n特别说明为什么s是常量引用而occurs是普通引用？为什么s和occurs\n是引用类型而c不是？如果令s是普通引用会发生什么情况？如果令\noccurs是常量引用会发生什么情况？\n\ns可能是比较长的字符串，那么为了避免拷贝，应当使用引用，又\n因为函数只读取s而不修改它，所以应当使用常量引用；将occurs\n定义成普通引用是因为需要修改其值并作为一个返回结果；c只是一个\nchar，传值比较方便。s是普通引用的话也不会有错误，但没有必要；\noccurs如果是常量引用则无法通过编译，因为函数需要修改其绑定\n对象的值。\n\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_16.md",
    "content": "> 练习6.16：下面的这个函数虽然合法，但是不算特别有用。指出它的局限性并设法改善。\n\n```\nbool is_empty(string &s) { return s.empty(); }\n```\n\n---\n\ns由于不是常量引用，故而无法传入const string对象，所以可以这样修改：\n\n```\nbool is_empty(const string &s) { return s.empty(); }\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_17.cpp",
    "content": "/*\n * 练习6.17：编写一个函数，判断string对象中是否含有大写字母。编写另一个函数，把string对象全部\n * 改成小写形式。在这两个函数中你使用的形参类型相同吗？为什么？\n */\n\n/*\n * 第一个函数使用const string&，第二个string&，这是因为判断是否有大写字母不需要改写字符串。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\nbool has_upper_char(const string &s)\n{\n\tfor (auto c : s) {\n\t\tif (isupper(c))\n\t\t\treturn true;\n\t}\n\treturn false;\n}\n\nvoid str_tolower(string &s)\n{\n\tfor (auto &c : s)\n\t\tc = tolower(c);\n}\n\nint main()\n{\n\t// test\n\tstring str = \"Hello World\";\n\n\thas_upper_char(str) ? cout << \"Has upper character\" << endl : cout << \"Has not upper character\" << endl;\n\tstr_tolower(str);\n\t\n\tcout << str << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_18.md",
    "content": "> 练习6.18：为下面的函数编写函数声明，从给定的名字中推测函数具备的功能。\n\n (a) 名为compare的函数，返回布尔值，两个参数都是matrix类的引用。\n (b) 名为change_val的函数，返回vector<int>的迭代器，有两个参数：一个是int，另一个是vector<int>的迭代器。\n\n---\n\n(a)\n\n```\nbool compare(const matrix &rh, const matrix &lh); // 判断两个对象的大小\n```\n\n(b)\n\n```\nvector<int>::iterator change_val(int i, vector<int>::iterator it);\t// 把it绑定到的元素的值替换为i，返回替换后元素位置的迭代器（也许吧？）\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_19.md",
    "content": "练习6.19：假定有如下声明，判断哪个调用合法、哪个调用不合法。对于不合法的函数调用，说明原因。\n\n```\ndouble calc(double);\nint count(const string &, char);\nint sum(vector<int>::iterator, vector<int>::iterator, int);\nvector<int> vec(10);\n```\n\n(a) calc(23.4, 55.1);\t\t// 不合法，形参和实参数量不匹配\n\n(b) count(\"abcda\", 'a');\t// 合法\n\n(c) calc(66);\t\t\t\t// 合法\n\n(d) sum(vec.begin(), vec.end(), 3.8);\t// 合法\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_20.md",
    "content": "练习6.20：引用形参什么时候应该是常量引用？如果形参应该是常量引用，由我们将其设为了普通引用，会发生什么情况？\n\n不需要修改其绑定对象时应该设置成常量引用。\n\n如果应该为常量引用而实际用了普通引用，那么函数可能不经意间修改了引用绑定的对象。并且无法传递一个常量对象给\n形参。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_21.cpp",
    "content": "/*\n * 练习6.21：编写一个函数，令其接受两个参数：一个是int型的数，另一个是int指针。函数比较int的值和指针所指的值，返回较大\n * 的那个。在该函数中指针的类型应该是什么？\n */\n\n// 应该是const int*，从而可以传入const对象的地址\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint compare(int a, const int *b)\n{\n\treturn a > *b ? a : *b;\n}\n\nint main()\n{\n\tconst int n = 10;\n\tcout << compare(5, &n) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_22.cpp",
    "content": "/*\n * 练习6.22：编写一个函数，令其交换两个int指针。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nvoid swap(int* &a, int* &b)\n{\n\tint *tmp = b;\n\tb = a;\n\ta = tmp;\n}\n\nint main()\n{\n\tint a = 1, b = 2;\n\tint *pa = &a, *pb = &b;\n\n\tcout << *pa << \" \" << *pb << endl;\n\tswap(pa, pb);\n\tcout << *pa << \" \" << *pb << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_23.cpp",
    "content": "/*\n * 练习6.23：参考本节介绍的几个print函数，根据理解编写你自己的版本。以此调用每个函数使其输入\n * 下面定义的i和j。\n *\n * int i = 0, j[2] = {0, 1};\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\n// 使用标记指定数组长度\nvoid print(const char *cp)\n{\n\tif (cp)\t\t\t\t\t// 若cp非空\n\t\twhile (*cp)\t\t\t// 只要指针所指字符非空字符\n\t\t\tcout << *cp++;\t// 输出当前字符并将指针向前移动一个位置\n}\n\n// 使用标准库规范\nvoid print(const int *beg, const int *end)\n{\n\t// 输出beg到end之间（不含end）的所有元素\n\twhile (beg != end)\n\t\tcout << *beg++ << \" \";\t// 输出当前元素并将指针向前移动一个位置\n\tcout << endl;\n}\n\n// 显示传递一个表示数组大小的形参\n// const int ia[]等价于const int* ia\n// size表示数组的大小，将它显式地传给函数用于控制对ia元素的访问\nvoid print(const int ia[], size_t size)\n{\n\tfor (size_t i = 0; i != size; ++i) {\n\t\tcout << ia[i] << \" \";\n\t}\n\tcout << endl;\n}\n\nint main()\n{\n\tint i = 0, j[2] = {0, 1};\n\tprint(std::begin(j), std::end(j));\t// void print(const int *beg, const int *end)\n\n\tprint(&i, 1);\t\t\t\t\t\t// void print(const int ia[], size_t size)\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_24.cpp",
    "content": "/*\n * 练习6.24：描述下面这个函数的行为。如果代码中存在问题，请指出并改正。\n *\n * void print(const int ia[10])\n * {\n *     for (size_t i = 0; i != 10; ++i)\n *         cout << ia[i] << endl;\n * }\n */\n\n// ia的类型是const int*，如果指向的是数组首元素，那么此数组的长度不一定就是10。\n// 改成下面那样。\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nvoid print(const int (&ia)[10])\n{\n\tfor (size_t i = 0; i != 10; ++i)\n\t    cout << ia[i] << endl;\n}\n\nint main()\n{\n\tint a[10] = {1, 2, 3, 4, 5};\n\tprint(a);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_25.cpp",
    "content": "/*\n * 练习6.25：编写一个main函数，令其接受两个实参。把实参的内容连接成一个string对象并输出出来。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\nint main(int argc, char* argv[])\n{\n\tint i = 0;\n\tstring s;\n\twhile (argv[i])\n\t\ts += argv[i++];\n\n\tcout << s << endl;\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_26.cpp",
    "content": "/*\n * 练习6.26：编写一个程序，使其接受本节所示的选项：输出传递给main函数的实参的内容。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main(int argc, char* argv[])\n{\n\tfor (int i = 1; i < argc; ++i)\n\t\tcout << argv[i] << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_27.cpp",
    "content": "/*\n * 练习6.27：编写一个函数，它的参数是initializer_list<int>类型的对象，函数的功能是计算\n * 列表中所有元素的和。\n */\n\n#include <iostream>\n#include <initializer_list>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::initializer_list;\n\nint sum(initializer_list<int> il)\n{\n\tint sum = 0;\n\n\tfor (auto i : il)\n\t\tsum += i;\n\n\treturn sum;\n}\n\nint main()\n{\n\tcout << sum({1, 2, 3, 4, 5}) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_28.md",
    "content": "> 练习6.28：在error_msg函数的第二个版本中包含ErrCode类型的参数，其中循环内的elem是什么类型？\n\n---\n\nconst string&\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_29.md",
    "content": " > 练习6.29：在范围for循环中使用initializer_list对象时，应该将循环控制变量声明成引用类型吗？为什么？\n\n---\n\n应该，这样可以避免拷贝。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_30.cpp",
    "content": "/*\n * 练习6.30：编译第200页的str_subrange函数，看看你的编译器是如何处理函数中的错误的。\n */\n\n// $ g++ -std=c++11 ./exercise_6_30.cpp\n// error: return-statement with no value, in function returning ‘bool’\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\n// 因为含有不正确的返回值，所以这段代码无法通过编译\nbool str_subrange(const string &str1, const string &str2)\n{\n\t// 大小相同：此时用普通的相等性判断结果作为返回值\n\tif (str1.size() == str2.size())\n\t\treturn str1 == str2;\t// 正确：==运算符返回布尔值\n\t// 得到较短string对象的大小\n\tauto size = (str1.size() < str2.size()) ? str1.size() : str2.size();\n\t// 检查两个string对象的对应字符是否相等，以较短的字符串长度为限\n\tfor (decltype(size) i = 0; i != size; ++i) {\n\t\tif (str1[i] != str2[i])\n\t\t\treturn;\t// 错误 #1：没有返回值\n\t}\n\t// 错误 #2：控制流可能尚未返回任何值就结束了函数的执行\n\t// 编译器可能检查不出这一错误\n\n\treturn 0;\n}\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_31.md",
    "content": "> 练习6.31：什么情况下返回的引用无效？什么情况下返回常量的引用无效？\n\n---\n\n仅在函数执行过程中存在的对象，返回其引用就是无效的。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_32.md",
    "content": "> 练习6.32：下面的函数合法吗？如果合法，说明其功能；如果不合法，修改其中的错误并解释原因。\n\n```\nint &get(int *arry, int index) { return arry[index]; }\nint main()\n{\n\tint ia[10];\n\tfor (int i = 0; i != 10; ++i)\n\t\tget(ia, i) = i;\n}\n```\n\n---\n\n合法。目的是把0到9按顺序放到数组里。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_33.cpp",
    "content": "/*\n * 练习6.33：编写一个递归函数，输出vector对象的内容。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\n\nusing Iter = vector<int>::iterator;\n\nvoid print_vec(Iter beg, Iter end)\n{\n\tif (beg != end) {\n\t\tcout << *beg << endl;\n\t\tprint_vec(beg + 1, end);\n\t}\n\n}\n\nint main()\n{\n\tvector<int> ivec{1, 2, 3, 4, 5};\n\n\tprint_vec(ivec.begin(), ivec.end());\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_34.cpp",
    "content": "/*\n * 练习6.34：如果factorial函数的停止条件如下所示，将发生什么情况？\n * if (val != 0)\n */\n\n/*\n * 如果val是个负数，那么函数将永远递归下去，直到栈溢出。提示：Segmentation fault (core dumped)\n * 如果是正数，那么会多调用一次，即调用factorial(1) --> factorial(0) * 1 --> 1 * 1，这是多余的。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\n// 计算val的阶乘，即1 * 2 * 3 ... * val\nint factorial(int val)\n{\n\tif (val != 0)\n\t\treturn factorial(val - 1) * val;\n\treturn 1;\n}\n\nint main()\n{\n\t//cout << factorial(5) << endl;\n\tcout << factorial(-5) << endl;\n\t\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_35.md",
    "content": "> 练习6.35：在调用factorial函数时，为什么我们传入的值是val-1而非val--？\n\n---\n\n这样做的结果是未定义的，因为不知道乘法两边的值到底是多少。\n\n也就是 factorial(val--) * val 表达式中，不知道乘法两边的表达式谁先计算。\n\n而且val将永远作为factorial的参数（而不会改变），因此这将是一个无限递归。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_36.cpp",
    "content": "/*\n * 练习6.36：编写一个函数声明，使其返回数组的引用并且该数组包含10个string对象。不要使用尾置返回类型、\n * decltype或者类型别名。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\n/* Type (*function(parameter_list))[dimension]; */\nstring (&func())[10];\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_37.cpp",
    "content": "/*\n * 练习6.37：为上一题的函数再写三个声明，一个使用类型别名，另一个使用尾置返回类型，最后一个使用decltype\n * 关键字。你觉得哪种形式最好？为什么？\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\n/* Type (*function(parameter_list))[dimension]; */\nstring (&func())[10];\n\n// 使用类型别名\nusing StrArr = string[10];\nStrArr &func();\n\n// 使用尾置返回类型\nauto func() -> string(&)[10];\n\n// 使用decltype\nstring str_arr[10];\ndecltype(str_arr) &func();\n\n// 使用类型别名好点，因为看上去更清晰易懂。\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_38.cpp",
    "content": "/*\n * 练习6.38：修改arrPtr函数，使其返回数组的引用。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\n// 指向5个整数的数组的类型别名\nusing arrT = int[5];\n\nint odd[] = {1, 3, 5, 7, 9};\nint even[] = {0, 2, 4, 6, 8};\n\n// 返回一个引用，绑定到含有5个整数的数组\ndecltype(odd) &arrPtr(int i)\n{\n\treturn (i % 2) ? odd : even;\n}\n\nint main()\n{\n\tarrT &arr = arrPtr(1);\n\n\tfor (auto i : arr)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_39.md",
    "content": "> 练习6.39：说明在下面的每组声明中第二条声明语句是何含义。如果有非法的声明，请指出来。\n\n(a) int calc(int, int);\n    int calc(const int, const int);\n\n(b) int get();\n    double get();\n\n(c) int *reset(int *);\n    double *reset(double *);\n\n---\n\n(a) 传入顶层const版本的函数声明，本质上与第一个一样，所以是非法的。\n\n(b) 形参和第一个一样，是非法的。\n\n(c) 传入指向double类型的指针，与第一个明显不同，故是一个合法的重载函数。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_40.md",
    "content": "> 练习6.40：下面哪个声明是错误的，为什么？\n\n(a) int ff(int a, int b = 0, int c = 0);\n(b) char *init(int ht = 24, int wd, char bckgrnd);\n\n---\n\n(b) 错误，有默认值的形参后面的形参也必须有默认值。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_41.md",
    "content": "> 练习6.41：下面的哪个调用是非法的？为什么？哪个调用虽然合法但显然与程序员的初衷不符？为什么？\n\n```\nchar *init(int ht, int wd = 80, char bckgrnd = ' ');\n```\n\n(a) init(); (b) init(24, 10); (c) init(14, '*');\n\n---\n\n(a) 非法，ht必须指定初始值。\n(c) 虽然合法，但是却是将'*'隐式转换成int类型然后用于初始化wd，然而其初衷应该是用于初始化bckgrnd的。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_42.cpp",
    "content": "/*\n * 练习6.42：给make_plural函数的第二个形参赋予默认实参's'，利用新版本的函数输出单词success和\n * failure的单数和复数形式。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\n// 如果ctr的值大于1，返回word的复数形式\nstring make_plural(size_t ctr, const string &word, const string &ending = \"s\")\n{\n\treturn (ctr > 1) ? word + ending : word;\n}\n\nint main()\n{\n\tcout << make_plural(1, \"success\", \"es\") << endl;\n\tcout << make_plural(2, \"success\", \"es\") << endl;\n\n\tcout << make_plural(1, \"failure\") << endl;\n\tcout << make_plural(2, \"failure\") << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_43.md",
    "content": "> 练习6.43：你会把下面的哪个声明和定义放在头文件中？哪个放在源文件中？为什么？\n\n(a) inline bool eq(const BigInt&, const BigInt&) { ... }\n\n(b) void putValues(int *arr, int size);\n\n---\n\n(a) 放到头文件中，因为这是一个内联函数，它的多个定义必须完全一致，因此通常把内联函数定义在头文件中。\n\n(c) 声明在头文件，定义在源文件。因为这样可以实现分离式编译（改动了函数的实现，不需要重新编译其它引用了此函数的源文件，见书本p186）。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_44.cpp",
    "content": "/*\n * 练习6.44：将6.2.2节（第189页）的isShorter函数改写成内联函数。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\n// 比较两个string对象的长度\ninline\nbool isShorter(const string &s1, const string &s2)\n{\n\treturn s1.size() < s2.size();\n}\n\nint main()\n{\n\tcout << isShorter(\"Hello\", \"Hi\") << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_45.md",
    "content": "> 练习6.45：回顾在前面的练习中你编写的那些函数，它们应该是内联函数吗？如果是，将它们改写成内联函数；如果不是，说明原因。\n\n---\n\n可以使用正则表达式`grep 'return .*[<>=]' *`查询一些带有逻辑运算表达式的return语句，这些函数很可能很短。\n\n举例而言，可以是内联函数的有：\n\n- [练习6.21](./exercise_6_21.cpp)中的比较函数。\n\n- [练习6.42](./exercise_6_42.cpp)中的make_plural函数。\n\n而像下面这样的函数，由于内容比较多，不适合作为内联函数：\n\n- [练习6.30](./exercise_6_30.cpp)中的str_subrange函数。\n\n改写忽略。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_46.cpp",
    "content": "/*\n * 练习6.46：能把isShorter函数定义成constexpr函数吗？如果能，将他改写成constexpr函数；\n * 如果不能，说明原因。\n */\n\n// 不能，G++报错：error: call to non-constexpr function，因为其参数非字面值类型。\n// 关于此问题的一个讨论：https://github.com/ReadingLab/Discussion-for-Cpp/issues/22\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\n// 比较两个string对象的长度\nconstexpr\nbool isShorter(const string &s1, const string &s2)\n{\n\treturn s1.size() < s2.size();\n}\n\nint main()\n{\n\tcout << isShorter(\"Hello\", \"Hi\") << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_47.cpp",
    "content": "/*\n * 练习6.47：改写6.3.2节（第205页）练习中使用递归输出vector内容的程序，使其有条件地输出与执行过程有关的信息。\n * 例如，每次调用时输出vector对象的大小。分别在打开和关闭调试器的情况下编译并执行这个程序。\n */\n\n#include <iostream>\n#include <vector>\n\n//#define NDEBUG // 注释掉这句话，则会开启调试输出代码\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\n\nusing Iter = vector<int>::iterator;\n\nvoid print_vec(Iter beg, Iter end)\n{\n#ifndef NDEBUG\n\tcout << \"vector size: \" << end - beg << endl;\n#endif\n\n\tif (beg != end) {\n\t\tcout << *beg << endl;\n\t\tprint_vec(beg + 1, end);\n\t}\n\n}\n\nint main()\n{\n\tvector<int> ivec{1, 2, 3, 4, 5};\n\n\tprint_vec(ivec.begin(), ivec.end());\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_48.md",
    "content": "> 练习6.48：说明下面这个循环的含义，它对assert的使用合理吗？\n\n```\nstring s;\nwhile (cin >> s && s != sought) {} // 空函数体\nassert(cin);\n```\n---\n\n从标准输入循环读入数据，存入s，直到s等于sought。\n不合理，因为读到文件尾也会使得cin失效。这种属于正常情况，但assert会导致程序异常结束。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_49.md",
    "content": "> 练习6.49：什么是候选函数？什么是可行函数？\n\n---\n\n候选函数与被调用函数同名，并且其声明在调用点可见，是函数匹配第一步选定的重载函数集。\n\n可行函数的形参与本次调用的实参数量相等，且实参可以传递给形参（类型相同或可转换），是函数匹配第二步选出的函数。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_50.md",
    "content": "> 练习6.50：已知有第217页对函数f的声明，对于下面的每一个调用列出可行函数。其中哪个函数是最佳匹配？如果调用不合法，是因为没有可匹配的函数还是因为调用具有二义性？\n\n(a) f(2.56, 42) (b) f(42) (c) f(42, 0) (d) f(2.56, 3.14)\n\n---\n\n(a) 可行函数有：`void f(int, int);`, `void f(double, double = 3.14);`, 不合法，二义性调用。\n\n(b) 可行函数有：`void f(int);`, 最佳匹配是`void f(int);` 。\n\n(c) 可行函数有：`void f(int, int);`, `void f(double, double = 3.14);`, 最佳匹配是`void f(int, int);`。\n\n(d) 可行函数有：`void f(int, int);`, `void f(double, double = 3.14);`, 最佳匹配是`void f(double, double = 3.14);`。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_51.cpp",
    "content": "/*\n * 练习6.51：编写函数f的4个版本，令其各输出一条可以区分的消息。验证上\n * 一个练习的答案，如果你回答错了，反复研究本节的内容直到你弄清自己错\n * 在何处。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nvoid f()\n{\n\tcout << \"void f()\" << endl;\n}\n\nvoid f(int)\n{\n\tcout << \"void f(int)\" << endl;\n}\n\nvoid f(int, int)\n{\n\tcout << \"void f(int, int)\" << endl;\n}\n\nvoid f(double, double = 3.14)\n{\n\tcout << \"void f(double, double = 3.14)\" << endl;\n}\n\nint main()\n{\n\t//f(2.56, 42);\t// 二义性\n\tf(42);\t\t// void f(int)\n\tf(42, 0);\t// void f(int, int)\n\tf(2.56, 3.14);\t// void f(double, double = 3.14)\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_52.md",
    "content": "> 练习6.52：已知有如下声明：\n\n```\nvoid mainip(int, int);\ndouble dobj;\n```\n\n请指出下列调用中每个类型转换的等级（参见6,6,1节，第219页）。\n\n(a) mainip('a', 'z'); (b) mainip(55.4, dobj);\n\n---\n\n(a) 第3个等级，通过类型提升实现的匹配（char --> int）。\n\n(b) 第4个等级，通过算数类型转换实现的匹配（double --> int）。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_53.md",
    "content": "> 练习6.53：说明下列每组声明中的第二条语句会产生什么影响，并指出哪些不合法（如果有的话）。\n\n(a) int calc(int&, int&);\n    int calc(const int&, const int&);\n\n(b) int calc(char*, char*);\n    int calc(const char*, const char*);\n\n(c) int calc(char*, char*);\n    int calc(char* const, char* const);\n\n---\n\n(a) 合法，使得calc函数可以传递常量对象。\n\n(b) 合法，使得calc函数可以传递指向常量的指针。\n\n(c) 非法，重复定义，顶层const指针和普通指针作为形参没有本质区别。\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_54.cpp",
    "content": "/*\n * 练习6.54：编写函数声明，令其接受两个int形参并且返回类型也是int，然后\n * 声明一个vector对象，令其元素是指向该函数的指针。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\n\nusing pfunc = int (*)(int, int);\n\nint main()\n{\n\tvector<pfunc> pfunc_vec;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_55.cpp",
    "content": "/*\n * 练习6.55：编写4个函数，分别对两个int值执行加、减、乘、除运算；在上一题\n * 创建的vector对象中保存指向这些函数的指针。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\n\nusing pfunc = int (*)(int, int);\n\nint Addition(int a, int b)\n{\n\treturn a + b;\n}\n\nint Subtraction(int a, int b)\n{\n\treturn a - b;\n}\n\nint Multiplication(int a, int b)\n{\n\treturn a * b;\n}\n\nint Division(int a, int b)\n{\n\treturn a / b;\n}\n\nint main()\n{\n\tvector<pfunc> pfunc_vec;\n\tpfunc_vec.push_back(Addition);\n\tpfunc_vec.push_back(Subtraction);\n\tpfunc_vec.push_back(Multiplication);\n\tpfunc_vec.push_back(Division);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch06_Functions/exercise_6_56.cpp",
    "content": "/*\n * 练习6.56：调用上述vector中的每个元素并输出其结果。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\n\nusing pfunc = int (*)(int, int);\n\nint Addition(int a, int b)\n{\n\treturn a + b;\n}\n\nint Subtraction(int a, int b)\n{\n\treturn a - b;\n}\n\nint Multiplication(int a, int b)\n{\n\treturn a * b;\n}\n\nint Division(int a, int b)\n{\n\treturn a / b;\n}\n\nint main()\n{\n\tvector<pfunc> pfunc_vec;\n\tpfunc_vec.push_back(Addition);\n\tpfunc_vec.push_back(Subtraction);\n\tpfunc_vec.push_back(Multiplication);\n\tpfunc_vec.push_back(Division);\n\n\tint a = 10, b = 5;\n\tfor (auto f : pfunc_vec) {\n\t\tcout << f(a, b) << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/data/add_item",
    "content": "0-201-78345-X 3 20.00\n0-201-78345-X 2 25.00\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/data/book_sales",
    "content": "0-201-70353-X 4 24.99\n0-201-82470-1 4 45.39\n0-201-88954-4 2 15.00 \n0-201-88954-4 5 12.00 \n0-201-88954-4 7 12.00 \n0-201-88954-4 2 12.00 \n0-399-82477-1 2 45.39\n0-399-82477-1 3 45.39\n0-201-78345-X 3 20.00\n0-201-78345-X 2 25.00\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Account/Account.cpp",
    "content": "#include \"Account.h\"\n\ndouble Account::initRate()\n{\n\treturn 2.0;\n}\n\n// 即使一个常量静态数据成员在类内部被初始化了，通常情况下也应该在类的外部定义一下该成员（p271）\nconstexpr double Account::baseRate;\n\n// 定义并初始化一个静态成员\ndouble Account::interestRate = initRate();\n\nvoid Account::rate(double newRate)\n{\n\tinterestRate = newRate;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Account/Account.h",
    "content": "#ifndef ACCOUNT_H\n#define ACCOUNT_H\n\n#include <string>\n\n// 用于表示银行的账户记录\nclass Account\n{\npublic:\n\tvoid calculate() { amount += amount * interestRate; }\n\tstatic double rate() { return interestRate; }\n\tstatic void rate(double);\n\nprivate:\n\tstd::string owner;\n\tdouble amount;\n\tstatic double interestRate;\n\tstatic double initRate();\n\n\tstatic constexpr double baseRate = 1.0;\n};\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Account/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Account/main.cpp",
    "content": "// example: 类的静态成员（p269）\n\n#include \"Account.h\"\n#include <iostream>\n\nusing namespace std;\n\nint main()\n{\n\tdouble r = Account::rate();\t// 使用作用域运算符访问静态成员\n\tcout << \"rate: \" << r << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Person.cpp",
    "content": "// example: Person类的设计与实现\n// 由书本中的练习逐步完善而来\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nstruct Person\n{\n\tfriend std::istream& read_person(std::istream &is, Person &person);\n\tfriend std::ostream& print_person(std::ostream &os, const Person &person);\n\npublic: \n\t// 构造函数\n\tPerson() = default;\n\tPerson(const std::string &_name, const std::string &_addr) :\n\t       name(_name), addr(_addr) {}\n\texplicit Person(std::istream &is);\n\n\tstd::string GetName() const { return name; }\n\tstd::string GetAddr() const { return addr; }\n\nprivate:\n\tstd::string name;\n\tstd::string addr;\n};\n\nstd::istream& read_person(std::istream &is, Person &person);\nstd::ostream& print_person(std::ostream &os, const Person &person);\n\nPerson::Person(std::istream &is)\n{\n\tread_person(is, *this);\n}\n\nstd::istream& read_person(std::istream &is, Person &person)\n{\n\tis >> person.name >> person.addr;\n\treturn is;\n}\n\nstd::ostream& print_person(std::ostream &os, const Person &person)\n{\n\tos << person.name << \" \"\n\t   << person.addr;\n\n\treturn os;\n}\n\nint main()\n{\n\tPerson p1(\"liudiwen\", \"Guangzhou\");\n\tprint_person(cout, p1) << endl;\n\n\tPerson p2(cin);\n\tprint_person(cout, p2) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Sales_data/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Sales_data/Sales_data.h",
    "content": "#ifndef SALES_DATA_H\n#define SALES_DATA_H\n\n#include <iostream>\n#include <string>\n#include \"exceptions.h\"\n\n//--------------------------------------------------------------------------------\n\n// 类的定义\n\nclass Sales_data {\n\tfriend Sales_data add(const Sales_data&, const Sales_data&);\n\tfriend std::ostream &print(std::ostream&, const Sales_data&);\n\tfriend std::istream &read(std::istream&, Sales_data&);\n\tfriend struct std::hash<Sales_data>;\t\n\t\n\t// 重载的运算符（练习14.2, p493）\n\tfriend std::istream& operator>>(std::istream &is, Sales_data &sd);\n\tfriend std::ostream& operator<<(std::ostream &os, const Sales_data &sd);\n\tfriend Sales_data operator+(const Sales_data &sd1, const Sales_data &sd2);\n\tfriend bool operator==(const Sales_data &lhs, const Sales_data &rhs);\n\tfriend bool operator!=(const Sales_data &lhs, const Sales_data &rhs);\n\npublic:\n\t// 构造函数\n\tSales_data() = default;\n\tSales_data(const std::string &s) : bookNo(s) { }\n\tSales_data(const std::string &s, unsigned n, double p) :\n\t\t   bookNo(s), units_sold(n), revenue(p*n) { }\n\tSales_data(std::istream &);\n\n\tSales_data& operator=(const std::string &s);\n\tSales_data& operator+=(const Sales_data &rhs);\n\n\t// 其他成员函数\n\tstd::string isbn() const { return bookNo; }\n\tSales_data& combine(const Sales_data&);\n\n\t// 类型转换运算符（练习14.45）\n\texplicit operator std::string() const { \n\t\treturn isbn() + \" \" + std::to_string(units_sold) + \" \" + std::to_string(revenue) + \" \" + std::to_string(avg_price());\n\t}\n\n\texplicit operator double() const { return revenue; }\n\n\tstatic const std::string Sales_data::*pbookNo() { return &Sales_data::bookNo; } // 提供给练习19.13\n\n\tbool AvgPriceMoreThan(double price) const { return avg_price() > price; }\n\nprivate:\n\tdouble avg_price() const;\n\n\t// 成员函数类型别名，练习19.16\n\tusing AvgPrice = double (Sales_data::*)() const;\n\n\t// 数据成员\n\tstd::string bookNo;\n\tunsigned units_sold = 0;\n\tdouble revenue = 0.0;\n};\n\n//--------------------------------------------------------------------------------\n\n// Sales_data的非成员接口函数\nSales_data add(const Sales_data&, const Sales_data&);\nstd::ostream &print(std::ostream&, const Sales_data&);\nstd::istream &read(std::istream&, Sales_data&);\n\n//--------------------------------------------------------------------------------\n// 类模板特例化（p626）\n\n// 打开std命名空间，以便特例化std::hash\nnamespace std {\n\ntemplate <>\nstruct hash<Sales_data>\n{\n\t// 用来散列一个无序容器的类型必须要定义下列类型\n\ttypedef size_t result_type;\n\ttypedef Sales_data argument_type; // 默认情况下，此类型需要==\n\tsize_t operator()(const Sales_data& s) const;\n};\n\ninline\nsize_t hash<Sales_data>::operator()(const Sales_data& s) const\n{\n\treturn hash<string>()(s.bookNo) ^\n\t\thash<unsigned>()(s.units_sold) ^\n\t\thash<double>()(s.revenue);\n}\n\n}\n\n//--------------------------------------------------------------------------------成员函数定义BEG\n\ninline\ndouble Sales_data::avg_price() const\n{\n\tif (units_sold)\n\t\treturn revenue / units_sold;\n\telse\n\t\treturn 0;\n}\n\ninline\nSales_data& Sales_data::combine(const Sales_data &rhs)\n{\n\tif (isbn() != rhs.isbn())\n\t\tthrow isbn_mismatch(\"wrong isbns\", isbn(), rhs.isbn());\n\n\tunits_sold += rhs.units_sold;\n\trevenue += rhs.revenue;\n\treturn *this;\n}\n\n// 输入的交易信息包括ISBN、售出总数和售出价格\ninline\nstd::istream &read(std::istream &is, Sales_data &item)\n{\n\tdouble price = 0;\n\tis >> item.bookNo >> item.units_sold >> price;\n\titem.revenue = price * item.units_sold;\n\treturn is;\n}\n\ninline\nSales_data::Sales_data(std::istream &is)\n{\n\tread(is, *this);\n}\n\ninline\nstd::ostream &print(std::ostream &os, const Sales_data &item)\n{\n\tos << item.isbn() << \" \" << item.units_sold << \" \"\n\t   << item.revenue << \" \" << item.avg_price();\n\treturn os;\n}\n\ninline\nSales_data add(const Sales_data &lhs, const Sales_data &rhs)\n{\n\tSales_data sum = lhs;\n\tsum.combine(rhs);\n\treturn sum;\n}\n\ninline\nstd::istream& operator>>(std::istream &is, Sales_data &item)\n{\n\tdouble price = 0;\n\tis >> item.bookNo >> item.units_sold >> price;\n\tif (is)\n\t\titem.revenue = price * item.units_sold;\n\telse\n\t\titem = Sales_data();\n\treturn is;\n}\n\ninline\nstd::ostream& operator<<(std::ostream &os, const Sales_data &item)\n{\n\tos << item.isbn() << \" \" << item.units_sold << \" \"\n\t   << item.revenue << \" \" << item.avg_price();\n\treturn os;\n}\n\ninline\nSales_data operator+(const Sales_data &sd1, const Sales_data &sd2)\n{\n\tSales_data sum = sd1;\n\tsum.combine(sd2);\n\treturn sum;\n}\n\ninline\nSales_data& Sales_data::operator=(const std::string &s)\n{\n\t*this = Sales_data(s);\n\treturn *this;\n}\n\ninline\nSales_data& Sales_data::operator+=(const Sales_data &rhs)\n{\n\tcombine(rhs);\n\treturn *this;\n}\n\ninline\nbool operator==(const Sales_data &lhs, const Sales_data &rhs)\n{\n\treturn lhs.isbn() == rhs.isbn() &&\n\t\tlhs.units_sold == rhs.units_sold &&\n\t\tlhs.revenue == rhs.revenue;\n}\n\ninline\nbool operator!=(const Sales_data &lhs, const Sales_data &rhs)\n{\n\treturn !(lhs == rhs);\n}\n\n//--------------------------------------------------------------------------------成员函数定义END\n\n#endif // SALES_DATA_H\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Sales_data/exceptions.h",
    "content": "#ifndef EXCEPTIONS_H\n#define EXCEPTIONS_H\n\n#include <stdexcept>\n#include <iostream>\n\n// 为书店程序设定的异常类\nclass out_of_stock : public std::runtime_error {\npublic:\n\texplicit out_of_stock(const std::string &s) : std::runtime_error(s) {}\n};\n\nclass isbn_mismatch : public std::logic_error {\npublic:\n\texplicit isbn_mismatch(const std::string &s) : std::logic_error(s) {}\n\tisbn_mismatch(const std::string &s, const std::string &lhs, const std::string &rhs):\n\t\tstd::logic_error(s), left(lhs), right(rhs) {}\n\n\tconst char* what() const noexcept override { static const std::string res(\"isbn not equal: \" + left + \" != \" + right); return res.c_str(); }\n\n\tconst std::string left, right;\n};\n\n#endif // EXCEPTIONS_H\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Sales_data/main.cpp",
    "content": "// example: Sales_data案例（自p228始）\n\n// ./main < ../data/book_sales\n\n#include <iostream>\n#include <unordered_set>\n#include \"Sales_data.h\"\n\nusing namespace std;\n\nvoid test1()\n{\n\tSales_data total;\t// 保存下一条交易记录的变量\n\t// 读入第一条交易记录，并确保有数据可以处理\n\tif (read(cin, total)) {\n\t\tSales_data trans;\t// 保存和的变量\n\t\t// 读入并处理剩余交易记录\n\t\twhile (read(cin, trans)) {\n\t\t\t// 如果我们仍在处理相同的书\n\t\t\tif (total.isbn() == trans.isbn()) {\n\t\t\t\ttotal.combine(trans);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tprint(cout, total) << endl;\t// 输出结果\n\t\t\t\ttotal = trans;\t\t// 处理下一本书\n\t\t\t}\n\t\t}\n\t\tprint(cout, total) << endl;\t// 输出最后一条交易\n\t}\n\telse {\n\t\tcerr << \"No data?!\" << endl;\n\t}\n}\n\nvoid test2()\n{\n\t// 采用重载运算符的版本\n\tSales_data total;\n\tif (cin >> total) {\n\t\tSales_data trans;\t// 保存和的变量\n\t\t// 读入并处理剩余交易记录\n\t\twhile (cin >> trans) {\n\t\t\t// 如果我们仍在处理相同的书\n\t\t\tif (total.isbn() == trans.isbn()) {\n\t\t\t\ttotal += trans;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tcout << total << endl;\t// 输出结果\n\t\t\t\ttotal = trans;\t\t// 处理下一本书\n\t\t\t}\n\t\t}\n\t\tcout << total << endl;\t// 输出最后一条交易\n\t}\n\telse {\n\t\tcerr << \"No data?!\" << endl;\n\t}\n\t\n}\n\nvoid test3()\n{\n\t// 测试类型转换运算符\n\tSales_data sd(\"0-201-70353-X\", 4, 24.99);\n\tcout << static_cast<std::string>(sd) << endl;\n\tcout << static_cast<double>(sd) << endl;\n}\n\nvoid test4()\n{\n\t// 使用特例化版本的hash，p626\n\tunordered_set<Sales_data> SDset;\t\n\tSDset.insert({\"0-201-70353-X\", 4, 24.99});\n\tSDset.insert({\"0-201-70354-X\", 7, 34.99});\n\n\tfor (const auto &item : SDset)\n\t{\n\t\tcout << item << endl;\n\t}\n}\n\n// 测试自定义异常类，见p694\nvoid test5()\n{\n\tSales_data sd1(\"0-201-70353-X\", 4, 24.99);\n\tSales_data sd2(\"0-201-70354-X\", 7, 34.99);\n\tsd1 + sd2;\n}\n\nint main()\n{\n\t//test1();\n\t//test2();\n\t//test3();\n\t//test4();\n\ttest5();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Screen/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Screen/Screen.h",
    "content": "#ifndef SCREEN_H\n#define SCREEN_H\n\n#include <string>\n\nclass Screen\n{\n\tfriend class Window_mgr;\n\npublic:\n\t//typedef std::string::size_type pos;\n\tusing pos = std::string::size_type;\n\tScreen() = default;\t// 因为Screen有另一个构造函数，所以本函数是必须的\n\t// cursor被其类内初始值初始化为0\n\tScreen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}\n\n\tpos size() const;\n\t\n\tchar get() const { return contents[cursor]; }\t// 读取光标处的字符，隐式内联\n\tinline char get(pos ht, pos wd) const;\t\t\t// 显示内联\n\tScreen &move(pos r, pos c);\t\t\t\t\t\t// 可以在之后设置为内联\n\n\tScreen &set(char c);\n\tScreen &set(pos row, pos col, char c);\n\n\tScreen &display(std::ostream &os) { do_display(os); return *this; }\n\tconst Screen &display(std::ostream &os) const { do_display(os); return *this;  }\n\nprivate:\n\t// 该函数负责显示Screen的内容\n\tvoid do_display(std::ostream &os) const { os << contents; }\n\n\tpos cursor = 0;\n\tpos height = 0, width = 0;\n\tstd::string contents;\n\n\tmutable size_t access_get_ctr;\t// 即使在一个const对象内也能被修改\n};\n\ninline\nScreen::pos Screen::size() const\n{\n\treturn height * width;\n}\n\ninline\nScreen& Screen::move(pos r, pos c) \n{\n\tpos row = r * width;\t// 计算行的位置\n\tcursor = row + c;\t\t// 在行内将光标移动到指定的列\n\n\t++access_get_ctr;\n\treturn *this;\n}\n\nchar Screen::get(pos r, pos c) const\n{\n\tpos row = r * width;\t// 计算行的位置\n\treturn contents[row + c]; // 返回给定列的字符\n}\n\ninline\nScreen& Screen::set(char c)\n{\n\tcontents[cursor] = c;\n\treturn *this;\n}\n\ninline\nScreen& Screen::set(pos row, pos col, char c)\n{\n\tcontents[row * width + col] = c;\n\treturn *this;\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Screen/Window_mgr.h",
    "content": "#ifndef WINDOW_MGR_H\n#define WINDOW_MGR_H\n\n#include <vector>\n#include \"Screen.h\"\n\nclass Window_mgr\n{\npublic:\n\t// 窗口中每个屏幕的编号\n\tusing ScreenIndex = std::vector<Screen>::size_type;\n\n\t// 添加一个Screen，返回它的编号\n\tScreenIndex addScreen(const Screen&);\n\n\t// 按照编号将指定的Screen重置为空白\n\tvoid clear(ScreenIndex);\n\nprivate:\n\t// 默认情况下，一个Window_mgr包含一个标准尺寸的空白Screen\n\tstd::vector<Screen> screens{Screen(24, 80, ' ')};\n};\n\ninline\nvoid Window_mgr::clear(ScreenIndex i)\n{\n\tScreen &s = screens[i];\n\ts.contents = std::string(s.height * s.width, ' ');\n}\n\nWindow_mgr::ScreenIndex\nWindow_mgr::addScreen(const Screen& s)\n{\n\tscreens.push_back(s);\n\treturn screens.size() - 1;\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_Screen/main.cpp",
    "content": "// example: 窗口类的设计与实现(自p243始)\n\n#include <iostream>\n#include \"Window_mgr.h\"\n\nusing std::cout;\nusing std::endl;\n\nint main()\n{\n\tScreen screen(5, 3, 'A');\n\n\tscreen.set('#').display(cout);\n\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_literal_classes/Debug.h",
    "content": "#ifndef DEBUG_H\n#define DEBUG_H\n\nclass Debug\n{\npublic:\n\tconstexpr Debug(bool b = true) : hw(b), io(b), other(b) { }\n\tconstexpr Debug(bool h, bool i, bool o) : hw(h), io(i), other(o) { }\n\tconstexpr bool any() { return hw || io || other; }\n\tvoid set_io(bool b) { io = b; }\n\tvoid set_hw(bool b) { hw = b; }\n\tvoid set_other(bool b) { other = b; }\n\nprivate:\n\tbool hw;\t// 硬件错误\n\tbool io;\t// IO错误\n\tbool other;\t// 其他错误\n};\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_literal_classes/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/example_literal_classes/main.cpp",
    "content": "// example: 字面值常量类（p267）\n// 字面值常量类也是一种字面值类型，可以用于常量表达式\n\n#include <iostream>\n#include \"Debug.h\"\n\nusing namespace std;\n\nint main()\n{\n\tconstexpr Debug io_sub(false, true, false);\t\t// 调试IO\n\tif (io_sub.any()) {\t\t// 等价于if(true)\n\t\tcerr << \"print appropriate error message\" << endl;\n\t}\n\n\tconstexpr Debug prod(false);\n\tif (prod.any()) {\t\t// 等价于if(false)\n\t\tcerr << \"print an error message\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_01.cpp",
    "content": "/*\n * 练习7.1：使用2.6.1节练习定义的Sales_data类为1.6节（第21页）的交易处理程\n * 序编写一个新版本。\n */\n\n// ./exercise_7_01 < data/book_sales\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cerr;\nusing std::cin;\nusing std::string;\n\n// 定义在练习2.40\nstruct Sales_data\n{\n\tstd::string bookNo;\n\tunsigned int units_sold = {0};\n\tdouble revenue = {0.0};\n};\n\nstd::istream &read(std::istream &is, Sales_data &sd)\n{\n\tdouble price = 0.0;\n\tis >> sd.bookNo >> sd.units_sold >> price;\n\tsd.revenue = price * sd.units_sold;\n\treturn is;\n}\n\nstd::ostream &print(std::ostream &os, const Sales_data &sd)\n{\n\tdouble avg_price = 0.0;\n\tif (sd.units_sold > 0)\n\t\tavg_price = sd.revenue / sd.units_sold;\n\t\n\tos << sd.bookNo << \" \"\n\t   << sd.units_sold << \" \"\n\t   << sd.revenue << \" \"\n\t   << avg_price;\n\n\treturn os;\n}\n\nint main()\n{\n\tSales_data total;\t// 保存下一条交易记录的变量\n\t// 读入第一条交易记录，并确保有数据可以处理\n\tif (read(cin, total)) {\n\t\tSales_data trans;\t// 保存和的变量\n\t\t// 读入并处理剩余交易记录\n\t\twhile (read(cin, trans)) {\n\t\t\t// 如果我们仍在处理相同的书\n\t\t\tif (total.bookNo == trans.bookNo) {\n\t\t\t\ttotal.units_sold += trans.units_sold;\n\t\t\t\ttotal.revenue += trans.revenue;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tprint(cout, total) << endl;\t// 输出结果\n\t\t\t\ttotal = trans;\t\t// 处理下一本书\n\t\t\t}\n\t\t}\n\t\tprint(cout, total) << endl;\t// 输出最后一条交易\n\t}\n\telse {\n\t\tcerr << \"No data?!\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_02.cpp",
    "content": "/*\n * 练习7.2：曾在2.6.2的练习（第67页）中编写了一个Sales_data类，请向这个类\n * 添加combine和isbn成员。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nstruct Sales_data\n{\n\tSales_data &combine(const Sales_data &rhs);\n\tstd::string isbn() const { return bookNo; }\n\n\tstd::string bookNo;\n\tunsigned int units_sold = {0};\n\tdouble revenue = {0.0};\n};\n\nSales_data &Sales_data::combine(const Sales_data &rhs)\n{\n\tunits_sold += rhs.units_sold;\n\trevenue += rhs.revenue;\n\treturn *this;\n}\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_03.cpp",
    "content": "/*\n * 练习7.3：修改7.1.1节（第229页）的交易处理程序，令其使用这些成员。\n */\n\n// ./exercise_7_03 < data/book_sales\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cerr;\nusing std::cin;\nusing std::string;\n\n// 定义在练习2.40\nstruct Sales_data\n{\n\tSales_data &combine(const Sales_data &rhs);\n\tstd::string isbn() const { return bookNo; }\n\n\tstd::string bookNo;\n\tunsigned int units_sold = {0};\n\tdouble revenue = {0.0};\n};\n\nSales_data &Sales_data::combine(const Sales_data &rhs)\n{\n\tunits_sold += rhs.units_sold;\n\trevenue += rhs.revenue;\n\treturn *this;\n}\n\nstd::istream &read(std::istream &is, Sales_data &sd)\n{\n\tdouble price = 0.0;\n\tis >> sd.bookNo >> sd.units_sold >> price;\n\tsd.revenue = price * sd.units_sold;\n\treturn is;\n}\n\nstd::ostream &print(std::ostream &os, const Sales_data &sd)\n{\n\tdouble avg_price = 0.0;\n\tif (sd.units_sold > 0)\n\t\tavg_price = sd.revenue / sd.units_sold;\n\t\n\tos << sd.bookNo << \" \"\n\t   << sd.units_sold << \" \"\n\t   << sd.revenue << \" \"\n\t   << avg_price;\n\n\treturn os;\n}\n\nint main()\n{\n\tSales_data total;\t// 保存下一条交易记录的变量\n\t// 读入第一条交易记录，并确保有数据可以处理\n\tif (read(cin, total)) {\n\t\tSales_data trans;\t// 保存和的变量\n\t\t// 读入并处理剩余交易记录\n\t\twhile (read(cin, trans)) {\n\t\t\t// 如果我们仍在处理相同的书\n\t\t\tif (total.isbn() == trans.isbn()) {\n\t\t\t\ttotal.combine(trans);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tprint(cout, total) << endl;\t// 输出结果\n\t\t\t\ttotal = trans;\t\t// 处理下一本书\n\t\t\t}\n\t\t}\n\t\tprint(cout, total) << endl;\t// 输出最后一条交易\n\t}\n\telse {\n\t\tcerr << \"No data?!\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_04.cpp",
    "content": "/*\n * 练习7.4：编写一个名为Person的类，使其表示人员的姓名和住址。使用string对\n * 象存放这些元素，接下来的练习将不断充实这个类的其他特征。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nstruct Person\n{\n\tstd::string name;\n\tstd::string addr;\n};\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_05.cpp",
    "content": "/*\n * 练习7.5：在你的Person类中提供一些操作使其能够返回姓名和住址。这些函数是否\n * 应该是const的呢？解释原因。\n */\n\n// 应该是const的，因为这样就可以让常量对象调用此成员函数，否则会因为无法传入\n// 顶层const this指针导致无法调用。\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nstruct Person\n{\n\tstd::string GetName() const { return name; }\n\tstd::string GetAddr() const { return addr; }\n\n\tstd::string name;\n\tstd::string addr;\n};\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_06.cpp",
    "content": "/*\n * 练习7.6：对于函数add、read和print，定义你自己的版本。\n */\n\n// ./exercise_7_06 < data/book_sales\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cerr;\nusing std::cin;\nusing std::string;\n\nstruct Sales_data\n{\n\tSales_data &combine(const Sales_data &rhs);\n\tstd::string isbn() const { return bookNo; }\n\n\tstd::string bookNo;\n\tunsigned int units_sold = {0};\n\tdouble revenue = {0.0};\n};\n\nSales_data &Sales_data::combine(const Sales_data &rhs)\n{\n\tunits_sold += rhs.units_sold;\n\trevenue += rhs.revenue;\n\treturn *this;\n}\n\nstd::istream &read(std::istream &is, Sales_data &sd)\n{\n\tdouble price = 0.0;\n\tis >> sd.bookNo >> sd.units_sold >> price;\n\tsd.revenue = price * sd.units_sold;\n\treturn is;\n}\n\nstd::ostream &print(std::ostream &os, const Sales_data &sd)\n{\n\tdouble avg_price = 0.0;\n\tif (sd.units_sold > 0)\n\t\tavg_price = sd.revenue / sd.units_sold;\n\t\n\tos << sd.bookNo << \" \"\n\t   << sd.units_sold << \" \"\n\t   << sd.revenue << \" \"\n\t   << avg_price;\n\n\treturn os;\n}\n\nSales_data add(const Sales_data &lhs, const Sales_data &rhs)\n{\n\tSales_data tmp = lhs;\n\treturn tmp.combine(rhs);\n}\n\nint main()\n{\n\tSales_data total;\t// 保存下一条交易记录的变量\n\t// 读入第一条交易记录，并确保有数据可以处理\n\tif (read(cin, total)) {\n\t\tSales_data trans;\t// 保存和的变量\n\t\t// 读入并处理剩余交易记录\n\t\twhile (read(cin, trans)) {\n\t\t\t// 如果我们仍在处理相同的书\n\t\t\tif (total.isbn() == trans.isbn()) {\n\t\t\t\ttotal.combine(trans);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tprint(cout, total) << endl;\t// 输出结果\n\t\t\t\ttotal = trans;\t\t// 处理下一本书\n\t\t\t}\n\t\t}\n\t\tprint(cout, total) << endl;\t// 输出最后一条交易\n\t}\n\telse {\n\t\tcerr << \"No data?!\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_07.cpp",
    "content": "/*\n * 练习7.7：使用这些新函数重写7.1.2节（第233页）练习中的交易处理程序。\n */\n\n// ./exercise_7_07 < data/book_sales\n\n// PS: 代码和exercise_7_06.cpp一模一样\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cerr;\nusing std::cin;\nusing std::string;\n\nstruct Sales_data\n{\n\tSales_data &combine(const Sales_data &rhs);\n\tstd::string isbn() const { return bookNo; }\n\n\tstd::string bookNo;\n\tunsigned int units_sold = {0};\n\tdouble revenue = {0.0};\n};\n\nSales_data &Sales_data::combine(const Sales_data &rhs)\n{\n\tunits_sold += rhs.units_sold;\n\trevenue += rhs.revenue;\n\treturn *this;\n}\n\nstd::istream &read(std::istream &is, Sales_data &sd)\n{\n\tdouble price = 0.0;\n\tis >> sd.bookNo >> sd.units_sold >> price;\n\tsd.revenue = price * sd.units_sold;\n\treturn is;\n}\n\nstd::ostream &print(std::ostream &os, const Sales_data &sd)\n{\n\tdouble avg_price = 0.0;\n\tif (sd.units_sold > 0)\n\t\tavg_price = sd.revenue / sd.units_sold;\n\t\n\tos << sd.bookNo << \" \"\n\t   << sd.units_sold << \" \"\n\t   << sd.revenue << \" \"\n\t   << avg_price;\n\n\treturn os;\n}\n\nSales_data add(const Sales_data &lhs, const Sales_data &rhs)\n{\n\tSales_data tmp = lhs;\n\treturn tmp.combine(rhs);\n}\n\nint main()\n{\n\tSales_data total;\t// 保存下一条交易记录的变量\n\t// 读入第一条交易记录，并确保有数据可以处理\n\tif (read(cin, total)) {\n\t\tSales_data trans;\t// 保存和的变量\n\t\t// 读入并处理剩余交易记录\n\t\twhile (read(cin, trans)) {\n\t\t\t// 如果我们仍在处理相同的书\n\t\t\tif (total.isbn() == trans.isbn()) {\n\t\t\t\ttotal.combine(trans);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tprint(cout, total) << endl;\t// 输出结果\n\t\t\t\ttotal = trans;\t\t// 处理下一本书\n\t\t\t}\n\t\t}\n\t\tprint(cout, total) << endl;\t// 输出最后一条交易\n\t}\n\telse {\n\t\tcerr << \"No data?!\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_08.md",
    "content": "> 练习7.8：为什么read函数将其Sales_data参数定义成普通的引用，而print将其参数定义成常量引用？\n\n---\n\n因为read需要改变传入的Sales_data对象，print无需改变，为了使得print也能够处理常量对象，必须将其参数定义成常量引用。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_09.cpp",
    "content": "/*\n * 练习7.9：对于7.1.2节（第233页）练习中的代码，添加读取和打印Person对象的操作。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nstruct Person\n{\n\tstd::string GetName() const { return name; }\n\tstd::string GetAddr() const { return addr; }\n\n\tstd::string name;\n\tstd::string addr;\n};\n\nstd::istream& read_person(std::istream &is, Person &person);\nstd::ostream& print_person(std::ostream &os, const Person &person);\n\nstd::istream& read_person(std::istream &is, Person &person)\n{\n\tis >> person.name >> person.addr;\n\treturn is;\n}\n\nstd::ostream& print_person(std::ostream &os, const Person &person)\n{\n\tos << person.name << \" \"\n\t   << person.addr;\n\n\treturn os;\n}\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_10.md",
    "content": "> 练习7.10：在下面这条if语句中，条件部分的作用是什么？\n\n```c++\nif (read(read(cin, data1), data2))\n```\n\n---\n\n先从标准输入读取数据存入data1，然后继续从标准输入读取数据存入data2，判断此时标准输入cin的状态是否合法。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_11.cpp",
    "content": "/*\n * 练习7.11：在你的Sales_data类中添加构造函数，然后编写一段程序令其用到每个\n * 构造函数。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\n//--------------------------------------------------------------------------\n\nstruct Sales_data {\n\t// 构造函数\n\tSales_data() = default;\n\tSales_data(const std::string &s) : bookNo(s) { }\n\tSales_data(const std::string &s, unsigned n, double p) :\n\t\t   bookNo(s), units_sold(n), revenue(p*n) { }\n\tSales_data(std::istream &);\n\n\t// 其他成员函数\n\tstd::string isbn() const { return bookNo; }\n\tSales_data& combine(const Sales_data&);\n\tdouble avg_price() const;\n\n\t// 数据成员\n\tstd::string bookNo;\n\tunsigned units_sold = 0;\n\tdouble revenue = 0.0;\n};\n\n// Sales_data的非成员接口函数\nSales_data add(const Sales_data&, const Sales_data&);\nstd::ostream &print(std::ostream&, const Sales_data&);\nstd::istream &read(std::istream&, Sales_data&);\n\ndouble Sales_data::avg_price() const\n{\n\tif (units_sold)\n\t\treturn revenue / units_sold;\n\telse\n\t\treturn 0;\n}\n\nSales_data& Sales_data::combine(const Sales_data &rhs)\n{\n\tunits_sold += rhs.units_sold;\n\trevenue += rhs.revenue;\n\treturn *this;\n}\n\n// 输入的交易信息包括ISBN、售出总数和售出价格\nstd::istream &read(std::istream &is, Sales_data &item)\n{\n\tdouble price = 0;\n\tis >> item.bookNo >> item.units_sold >> price;\n\titem.revenue = price * item.units_sold;\n\treturn is;\n}\n\nSales_data::Sales_data(std::istream &is)\n{\n\tread(is, *this);\n}\n\nstd::ostream &print(std::ostream &os, const Sales_data &item)\n{\n\tos << item.isbn() << \" \" << item.units_sold << \" \"\n\t   << item.revenue << \" \" << item.avg_price();\n\treturn os;\n}\n\nSales_data add(const Sales_data &lhs, const Sales_data &rhs)\n{\n\tSales_data sum = lhs;\n\tsum.combine(rhs);\n\treturn sum;\n}\n\n//--------------------------------------------------------------------------\n\nint main()\n{\n\tSales_data sd1;\n\tprint(cout, sd1) << endl; //  0 0 0\n\n\tSales_data sd2(\"0-201-70353-X\");\n\tprint(cout, sd2) << endl; // 0-201-70353-X 0 0 0\n\n\tSales_data sd3(\"0-201-70353-X\", 2, 20);\n\tprint(cout, sd3) << endl; // 0-201-70353-X 2 40 20\n\n\tSales_data sd4(cin);\t  // 0-201-70353-X 2 20\n\tprint(cout, sd4) << endl; // 0-201-70353-X 2 40 20\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_12.cpp",
    "content": "/*\n * 练习7.12：把只接受一个istream作为参数的构造函数定义移到类的内部。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\n//--------------------------------------------------------------------------\n\nstruct Sales_data; // 前置声明\nstd::istream &read(std::istream&, Sales_data&);\n\nstruct Sales_data {\n\t// 构造函数\n\tSales_data() = default;\n\tSales_data(const std::string &s) : bookNo(s) { }\n\tSales_data(const std::string &s, unsigned n, double p) :\n\t\t   bookNo(s), units_sold(n), revenue(p*n) { }\n\tSales_data(std::istream &is)\n\t{\n\t\tread(is, *this);\n\t}\n\n\t// 其他成员函数\n\tstd::string isbn() const { return bookNo; }\n\tSales_data& combine(const Sales_data&);\n\tdouble avg_price() const;\n\n\t// 数据成员\n\tstd::string bookNo;\n\tunsigned units_sold = 0;\n\tdouble revenue = 0.0;\n};\n\n// Sales_data的非成员接口函数\nSales_data add(const Sales_data&, const Sales_data&);\nstd::ostream &print(std::ostream&, const Sales_data&);\n\ndouble Sales_data::avg_price() const\n{\n\tif (units_sold)\n\t\treturn revenue / units_sold;\n\telse\n\t\treturn 0;\n}\n\nSales_data& Sales_data::combine(const Sales_data &rhs)\n{\n\tunits_sold += rhs.units_sold;\n\trevenue += rhs.revenue;\n\treturn *this;\n}\n\n// 输入的交易信息包括ISBN、售出总数和售出价格\nstd::istream &read(std::istream &is, Sales_data &item)\n{\n\tdouble price = 0;\n\tis >> item.bookNo >> item.units_sold >> price;\n\titem.revenue = price * item.units_sold;\n\treturn is;\n}\n\nstd::ostream &print(std::ostream &os, const Sales_data &item)\n{\n\tos << item.isbn() << \" \" << item.units_sold << \" \"\n\t   << item.revenue << \" \" << item.avg_price();\n\treturn os;\n}\n\nSales_data add(const Sales_data &lhs, const Sales_data &rhs)\n{\n\tSales_data sum = lhs;\n\tsum.combine(rhs);\n\treturn sum;\n}\n\n//--------------------------------------------------------------------------\n\nint main()\n{\n\tSales_data sd(cin);\t  // 0-201-70353-X 2 20\n\tprint(cout, sd) << endl; // 0-201-70353-X 2 40 20\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_13.cpp",
    "content": "/*\n * 练习7.13：使用istream构造函数重写第229页的程序。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::cerr;\n\n//--------------------------------------------------------------------------\n\nstruct Sales_data {\n\t// 构造函数\n\tSales_data() = default;\n\tSales_data(const std::string &s) : bookNo(s) { }\n\tSales_data(const std::string &s, unsigned n, double p) :\n\t\t   bookNo(s), units_sold(n), revenue(p*n) { }\n\tSales_data(std::istream &);\n\n\t// 其他成员函数\n\tstd::string isbn() const { return bookNo; }\n\tSales_data& combine(const Sales_data&);\n\tdouble avg_price() const;\n\n\t// 数据成员\n\tstd::string bookNo;\n\tunsigned units_sold = 0;\n\tdouble revenue = 0.0;\n};\n\n// Sales_data的非成员接口函数\nSales_data add(const Sales_data&, const Sales_data&);\nstd::ostream &print(std::ostream&, const Sales_data&);\nstd::istream &read(std::istream&, Sales_data&);\n\ndouble Sales_data::avg_price() const\n{\n\tif (units_sold)\n\t\treturn revenue / units_sold;\n\telse\n\t\treturn 0;\n}\n\nSales_data& Sales_data::combine(const Sales_data &rhs)\n{\n\tunits_sold += rhs.units_sold;\n\trevenue += rhs.revenue;\n\treturn *this;\n}\n\n// 输入的交易信息包括ISBN、售出总数和售出价格\nstd::istream &read(std::istream &is, Sales_data &item)\n{\n\tdouble price = 0;\n\tis >> item.bookNo >> item.units_sold >> price;\n\titem.revenue = price * item.units_sold;\n\treturn is;\n}\n\nSales_data::Sales_data(std::istream &is)\n{\n\tread(is, *this);\n}\n\nstd::ostream &print(std::ostream &os, const Sales_data &item)\n{\n\tos << item.isbn() << \" \" << item.units_sold << \" \"\n\t   << item.revenue << \" \" << item.avg_price();\n\treturn os;\n}\n\nSales_data add(const Sales_data &lhs, const Sales_data &rhs)\n{\n\tSales_data sum = lhs;\n\tsum.combine(rhs);\n\treturn sum;\n}\n\n//--------------------------------------------------------------------------\n\nint main()\n{\n\tSales_data total(cin);\t// 保存下一条交易记录的变量\n\t// 读入第一条交易记录，并确保有数据可以处理\n\tif (cin) {\n\t\t// 读入并处理剩余交易记录\n\t\twhile (true) {\n\t\t\tSales_data trans(cin);\t// 保存和的变量\n\t\t\tif (!cin) break;\n\n\t\t\tif (total.isbn() == trans.isbn()) {\n\t\t\t\ttotal.combine(trans);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tprint(cout, total) << endl;\t// 输出结果\n\t\t\t\ttotal = trans;\t\t// 处理下一本书\n\t\t\t}\n\t\t}\n\t\tprint(cout, total) << endl;\t// 输出最后一条交易\n\t}\n\telse {\n\t\tcerr << \"No data?!\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_14.md",
    "content": "> 练习7.14：编写一个构造函数，令其用我们提供的类内初始值显式地初始化成员。\n\n---\n\n```\nSales_data() : units_sold(0), revenue(0) { }\n```\n\nPS: 这题的意思是让用构造函数初始值列表代替=default吧？\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_15.cpp",
    "content": "/*\n * 练习7.15：对于7.1.2节（第233页）练习中的代码，添加读取和打印Person对象的操作。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nstruct Person\n{\n\t// 构造函数\n\tPerson() = default;\n\tPerson(const std::string &_name, const std::string &_addr) :\n\t       name(_name), addr(_addr) {}\n\tPerson(std::istream &is);\n\n\tstd::string GetName() const { return name; }\n\tstd::string GetAddr() const { return addr; }\n\n\tstd::string name;\n\tstd::string addr;\n};\n\nstd::istream& read_person(std::istream &is, Person &person);\nstd::ostream& print_person(std::ostream &os, const Person &person);\n\nPerson::Person(std::istream &is)\n{\n\tread_person(is, *this);\n}\n\nstd::istream& read_person(std::istream &is, Person &person)\n{\n\tis >> person.name >> person.addr;\n\treturn is;\n}\n\nstd::ostream& print_person(std::ostream &os, const Person &person)\n{\n\tos << person.name << \" \"\n\t   << person.addr;\n\n\treturn os;\n}\n\nint main()\n{\n\tPerson p1(\"liudiwen\", \"Guangzhou\");\n\tprint_person(cout, p1) << endl;\n\n\tPerson p2(cin);\n\tprint_person(cout, p2) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_16.md",
    "content": "> 练习7.16：在类的定义中对于访问说明符出现的位置和次数有限定吗？如果有，是什么？什么样的成员应该定义在public说明符之后？什么样的成员应该定义在private说明符之后？\n\n---\n\n访问说明限定符的出现位置和次数没有限定，每个访问说明符指定了接下来的成员的访问级别，其有效范围直到出现下一个访问说明符或者到达类的结尾处为止。\n\n类的接口应该定义在public之后，类的实现细节（实现细节的成员函数及数据成员）应该定义在private说明符之后。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_17.md",
    "content": "> 练习7.17：使用class和struct时有区别吗？如果有，是什么？\n\n---\n\n它们的唯一区别是：struct的默认访问权限是public，而class是private。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_18.md",
    "content": "> 练习7.18：封装是何含义？它有什么用处？\n\n---\n\n使用户无法访问类的实现细节（被隐藏），强制用户使用类的接口。这样做有这样的好处：\n\n- 用户无需知道类的实现细节，使用起来如同内置类型一般。\n\n- 避免了用户代码错误修改了对象的内容。\n\n- 修改类的实现时而无需改变用户代码。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_19.cpp",
    "content": "/*\n * 练习7.19：在你的Person类中，你将把哪些成员声明成public的？\n * 解释你这样做的原因。\n */\n\n// 见下面修改的Person类。构造函数和Get函数都要被用户代码所使用，\n// 因此应该是public的。而数据成员属于实现细节，需要被隐藏，所以\n// 是private的。\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nstruct Person\n{\n//public: 如果使用了访问控制说明符，还需要声明相关友元函数\n\t// 构造函数\n\tPerson() = default;\n\tPerson(const std::string &_name, const std::string &_addr) :\n\t       name(_name), addr(_addr) {}\n\tPerson(std::istream &is);\n\n\tstd::string GetName() const { return name; }\n\tstd::string GetAddr() const { return addr; }\n\n//private:\n\tstd::string name;\n\tstd::string addr;\n};\n\nstd::istream& read_person(std::istream &is, Person &person);\nstd::ostream& print_person(std::ostream &os, const Person &person);\n\nPerson::Person(std::istream &is)\n{\n\tread_person(is, *this);\n}\n\nstd::istream& read_person(std::istream &is, Person &person)\n{\n\tis >> person.name >> person.addr;\n\treturn is;\n}\n\nstd::ostream& print_person(std::ostream &os, const Person &person)\n{\n\tos << person.name << \" \"\n\t   << person.addr;\n\n\treturn os;\n}\n\nint main()\n{\n\tPerson p1(\"liudiwen\", \"Guangzhou\");\n\tprint_person(cout, p1) << endl;\n\n\tPerson p2(cin);\n\tprint_person(cout, p2) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_20.md",
    "content": "练习7.20：友元在什么时候有用？请分别列举出使用友元的利弊。\n\n---\n\n当类的接口不是类的成员时，应当将其声明为友元，从而允许它们访问类的非公有成员。\n\n利：提高了灵活性，可以让非类的成员成为类的接口，即其使用起类来和类的成员一样。\n\n弊：如果设计不恰当，则破坏了封装性，从而使代码难以维护。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_21.md",
    "content": "> 练习7.21：修改你的Sales_data类使其隐藏实现的细节。你之前编写的关于Sales_data操作的程序应该继续使用，借助类的新定义重新编译该程序，确保其工作正常。\n\n---\n\n见[Sales_data类](./example_Sales_data/Sales_data.h)。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_22.cpp",
    "content": "/*\n * 练习7.22：修改你的Person类使其隐藏实现细节。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nstruct Person\n{\n\t// 将这两个函数声明成友元即可\n\tfriend std::istream& read_person(std::istream &is, Person &person);\n\tfriend std::ostream& print_person(std::ostream &os, const Person &person);\n\npublic: \n\t// 构造函数\n\tPerson() = default;\n\tPerson(const std::string &_name, const std::string &_addr) :\n\t       name(_name), addr(_addr) {}\n\tPerson(std::istream &is);\n\n\tstd::string GetName() const { return name; }\n\tstd::string GetAddr() const { return addr; }\n\nprivate:\n\tstd::string name;\n\tstd::string addr;\n};\n\nstd::istream& read_person(std::istream &is, Person &person);\nstd::ostream& print_person(std::ostream &os, const Person &person);\n\nPerson::Person(std::istream &is)\n{\n\tread_person(is, *this);\n}\n\nstd::istream& read_person(std::istream &is, Person &person)\n{\n\tis >> person.name >> person.addr;\n\treturn is;\n}\n\nstd::ostream& print_person(std::ostream &os, const Person &person)\n{\n\tos << person.name << \" \"\n\t   << person.addr;\n\n\treturn os;\n}\n\nint main()\n{\n\tPerson p1(\"liudiwen\", \"Guangzhou\");\n\tprint_person(cout, p1) << endl;\n\n\tPerson p2(cin);\n\tprint_person(cout, p2) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_23.cpp",
    "content": "/*\n * 练习7.23：编写你自己的Screen类。\n */\n\n#ifndef SCREEN_H\n#define SCREEN_H\n\n#include <string>\n#include <iostream>\n\nclass Screen\n{\npublic:\n\t//typedef std::string::size_type pos;\n\tusing pos = std::string::size_type;\n\tScreen() = default;\t// 因为Screen有另一个构造函数，所以本函数是必须的\n\t// cursor被其类内初始值初始化为0\n\tScreen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}\n\t\n\tchar get() const { return contents[cursor]; }\t// 读取光标处的字符，隐式内联\n\tinline char get(pos ht, pos wd) const;\t\t\t// 显示内联\n\tScreen &move(pos r, pos c);\t\t\t\t\t\t// 可以在之后设置为内联\n\nprivate:\n\tpos cursor = 0;\n\tpos height = 0, width = 0;\n\tstd::string contents;\n\n\tmutable size_t access_get_ctr;\t// 即使在一个const对象内也能被修改\n};\n\ninline\nScreen& Screen::move(pos r, pos c) \n{\n\tpos row = r * width;\t// 计算行的位置\n\tcursor = row + c;\t\t// 在行内将光标移动到指定的列\n\n\t++access_get_ctr;\n\treturn *this;\n}\n\nchar Screen::get(pos r, pos c) const\n{\n\tpos row = r * width;\t// 计算行的位置\n\treturn contents[row + c]; // 返回给定列的字符\n}\n\nint main()\n{\n\tScreen screen(24, 80, 'A');\n\tstd::cout << screen.get() << std::endl;\n\n\treturn 0;\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_24.cpp",
    "content": "/*\n * 练习7.24：给你的Screen类添加三个构造函数：一个默认构造函数；另一个构造函数\n * 接受宽和高的值，然后将contents初始化成给定数量的空白；第三个构造函数接受\n * 宽和高的值以及一个字符，该字符作为初始化之后屏幕的内容。\n */\n\n#ifndef SCREEN_H\n#define SCREEN_H\n\n#include <string>\n#include <iostream>\n\nclass Screen\n{\npublic:\n\t//typedef std::string::size_type pos;\n\tusing pos = std::string::size_type;\n\tScreen() = default;\t// 因为Screen有另一个构造函数，所以本函数是必须的\n\t// cursor被其类内初始值初始化为0\n\tScreen(pos ht, pos wd) : height(ht), width(wd), contents(ht * wd, ' ') {}\n\tScreen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}\n\t\n\tchar get() const { return contents[cursor]; }\t// 读取光标处的字符，隐式内联\n\tinline char get(pos ht, pos wd) const;\t\t\t// 显示内联\n\tScreen &move(pos r, pos c);\t\t\t\t\t\t// 可以在之后设置为内联\n\nprivate:\n\tpos cursor = 0;\n\tpos height = 0, width = 0;\n\tstd::string contents;\n\n\tmutable size_t access_get_ctr;\t// 即使在一个const对象内也能被修改\n};\n\ninline\nScreen& Screen::move(pos r, pos c) \n{\n\tpos row = r * width;\t// 计算行的位置\n\tcursor = row + c;\t\t// 在行内将光标移动到指定的列\n\n\t++access_get_ctr;\n\treturn *this;\n}\n\nchar Screen::get(pos r, pos c) const\n{\n\tpos row = r * width;\t// 计算行的位置\n\treturn contents[row + c]; // 返回给定列的字符\n}\n\nint main()\n{\n\tScreen screen(24, 80);\n\tstd::cout << screen.get() << std::endl;\n\n\treturn 0;\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_25.md",
    "content": "> 练习7.25：Screen能安全地依赖于拷贝和赋值操作的默认版本吗？如果能，为什么？如果不能，为什么？\n\n---\n\n可以，因为每一个成员都被拷贝，且拷贝的对象和原对象是独立的。\n\n见p239：当类需要分配类对象之外的资源时，合成的版本常常会失效。不过，使用vector或string对象，可以管理这些必要的存储空间。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_26.md",
    "content": "> 练习7.26：将Sales_data::avg_price定义成内联函数。\n\n---\n\n见[Sales_data类](./example_Sales_data/Sales_data.h)。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_27.cpp",
    "content": "/*\n * 练习7.27：给你自己的Screen类添加move、set和display函数，通过执行下面\n * 的代码检查你的类是否正确。\n */\n\n#include <iostream>\n#include \"example_Screen/Screen.h\"\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nint main()\n{\n\tScreen myScreen(5, 5, 'X');\n\tmyScreen.move(4, 0).set('#').display(cout);\n\tcout << \"\\n\";\n\tmyScreen.display(cout);\n\tcout << \"\\n\";\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_28.md",
    "content": "> 练习7.28：如果move、set和display函数返回类型不是Screen&而是Screen，则上一个练习中将会发生什么情况？\n\n---\n\n第一个条输出的结果是一样的。然而这些操作不会影响原对象（因为仅仅操作于临时副本），因此第二条输出的仍然是原对象的初始内容。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_29.cpp",
    "content": "/*\n * 练习7.29：修改你的Screen类，令move、set和display函数返回Screen并检\n * 查程序的运行结果，在上一个练习中你的推测正确吗？\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\n\nclass Screen\n{\npublic:\n\t//typedef std::string::size_type pos;\n\tusing pos = std::string::size_type;\n\tScreen() = default;\t// 因为Screen有另一个构造函数，所以本函数是必须的\n\t// cursor被其类内初始值初始化为0\n\tScreen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}\n\t\n\tchar get() const { return contents[cursor]; }\t// 读取光标处的字符，隐式内联\n\tinline char get(pos ht, pos wd) const;\t\t\t// 显示内联\n\tScreen move(pos r, pos c);\t\t\t\t\t\t// 可以在之后设置为内联\n\n\tScreen set(char c);\n\tScreen set(pos row, pos col, char c);\n\n\tScreen display(std::ostream &os) { do_display(os); return *this; }\n\tconst Screen display(std::ostream &os) const { do_display(os); return *this;  }\n\nprivate:\n\t// 该函数负责显示Screen的内容\n\tvoid do_display(std::ostream &os) const { os << contents; }\n\n\tpos cursor = 0;\n\tpos height = 0, width = 0;\n\tstd::string contents;\n\n\tmutable size_t access_get_ctr;\t// 即使在一个const对象内也能被修改\n};\n\ninline\nScreen Screen::move(pos r, pos c) \n{\n\tpos row = r * width;\t// 计算行的位置\n\tcursor = row + c;\t\t// 在行内将光标移动到指定的列\n\n\t++access_get_ctr;\n\treturn *this;\n}\n\nchar Screen::get(pos r, pos c) const\n{\n\tpos row = r * width;\t// 计算行的位置\n\treturn contents[row + c]; // 返回给定列的字符\n}\n\ninline\nScreen Screen::set(char c)\n{\n\tcontents[cursor] = c;\n\treturn *this;\n}\n\ninline\nScreen Screen::set(pos row, pos col, char c)\n{\n\tcontents[row * width + col] = c;\n\treturn *this;\n}\n\nint main()\n{\n\tScreen myScreen(5, 5, 'X');\n\tmyScreen.move(4, 0).set('#').display(cout);\n\tcout << \"\\n\";\n\tmyScreen.display(cout);\n\tcout << \"\\n\";\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_30.md",
    "content": "> 练习7.30：通过this指针使用成员的做法虽然合法，但是有点多余。讨论显示地使用指针访问成员的优缺点。\n\n---\n\n优点：明确知道调用的是本类的成员。\n\n缺点：有时候显得多余（比如给数据成员的前缀设置成m_，这样就已经足够明确了）。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_31.cpp",
    "content": "/*\n * 练习7.31：定义一对类X和Y，其中X包含一个指向Y的指针，而Y包含一个类型为\n * X的对象。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nclass Y;\n\nclass X\n{\n\tY *p;\n};\n\nclass Y\n{\n\tX x;\n};\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_32.cpp",
    "content": "/*\n * 练习7.32：定义你自己的Screen和Window_mgr，其中clear是Window_mgr的\n * 成员，是Screen的友元。\n */\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nclass Screen;\nclass Window_mgr\n{\npublic:\n\t// 窗口中每个屏幕的编号\n\tusing ScreenIndex = std::vector<Screen>::size_type;\n\n\t// 按照编号将指定的Screen重置为空白\n\tvoid clear(ScreenIndex);\n\nprivate:\n\tstd::vector<Screen> screens;\n};\n\n\nclass Screen\n{\n\tfriend void Window_mgr::clear(ScreenIndex);\n\npublic:\n\t//typedef std::string::size_type pos;\n\tusing pos = std::string::size_type;\n\tScreen() = default;\t// 因为Screen有另一个构造函数，所以本函数是必须的\n\t// cursor被其类内初始值初始化为0\n\tScreen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}\n\t\n\tchar get() const { return contents[cursor]; }\t// 读取光标处的字符，隐式内联\n\tinline char get(pos ht, pos wd) const;\t\t\t// 显示内联\n\tScreen &move(pos r, pos c);\t\t\t\t\t\t// 可以在之后设置为内联\n\n\tScreen &set(char c);\n\tScreen &set(pos row, pos col, char c);\n\n\tScreen &display(std::ostream &os) { do_display(os); return *this; }\n\tconst Screen &display(std::ostream &os) const { do_display(os); return *this;  }\n\nprivate:\n\t// 该函数负责显示Screen的内容\n\tvoid do_display(std::ostream &os) const { os << contents; }\n\n\tpos cursor = 0;\n\tpos height = 0, width = 0;\n\tstd::string contents;\n\n\tmutable size_t access_get_ctr;\t// 即使在一个const对象内也能被修改\n};\n\ninline\nScreen& Screen::move(pos r, pos c) \n{\n\tpos row = r * width;\t// 计算行的位置\n\tcursor = row + c;\t\t// 在行内将光标移动到指定的列\n\n\t++access_get_ctr;\n\treturn *this;\n}\n\nchar Screen::get(pos r, pos c) const\n{\n\tpos row = r * width;\t// 计算行的位置\n\treturn contents[row + c]; // 返回给定列的字符\n}\n\ninline\nScreen& Screen::set(char c)\n{\n\tcontents[cursor] = c;\n\treturn *this;\n}\n\ninline\nScreen& Screen::set(pos row, pos col, char c)\n{\n\tcontents[row * width + col] = c;\n\treturn *this;\n}\n\ninline\nvoid Window_mgr::clear(ScreenIndex i)\n{\n\tScreen &s = screens[i];\n\ts.contents = std::string(s.height * s.width, ' ');\n}\n\nint main()\n{\n\tWindow_mgr win;\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_33.md",
    "content": "> 练习7.33：如果我们给Screen添加一个如下所示的size成员将发生什么情况？如果出现了问题，请尝试修改它。\n\n```c++\npos Screen::size() const\n{\n\treturn height * width;\n}\n```\n\n---\n\n无法通过编译，因为在外部作用域内找不到pos这个名字。g++提示：error: ‘pos’ does not name a type\n\n改成：\n\n```c++\nScreen::pos Screen::size() const\n{\n\treturn height * width;\n}\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_34.md",
    "content": "> 练习7.34：如果我们把第256页Screen类的pos的typedef放在类的最后一行会发生什么情况？\n\n---\n\n由于找不到声明中的名字pos，所以编译器会报错。g++报错：pos’ does not name a type\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_35.cpp",
    "content": "/*\n * 练习7.35：解释下面代码的含义，说明其中的Type和initVal分别使用了哪个定\n * 义。如果代码存在错误，尝试修改它。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::string;\n\ntypedef string Type;\nType initVal();\t\t// string\nclass Exercise {\npublic:\n\ttypedef double Type;\n\tType setVal(Type);\t// double\n\tType initVal();\t\t// double\nprivate:\n\tint val;\n};\n\n/*\n * 返回类型Type是string，类没有这个声明，是错误的。\n * 参数Type是double，没问题\n * initVal缺少实现，有链接错误\n */\n/*\nType Exercise::setVal(Type parm) {\n\tval = parm + initVal();\n\treturn val;\n}\n*/\n\n// 改成下面这样\nExercise::Type Exercise::setVal(Type parm) {\n\tval = parm + initVal();\n\treturn val;\n}\n\ninline\nExercise::Type Exercise::initVal()\n{\n\treturn 1.0;\n}\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_36.cpp",
    "content": "/*\n * 练习7.36：下面的初始值是错误的，请找出问题所在并尝试修改它。\n */\n\n/*\n * 最好令构造函数初始值的顺序与成员声明的顺序保持一致。而且如果可能的\n * 话，尽量避免使用某些成员初始化其他成员。（p259）\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\n// rem先于base初始化，但却依赖于base的初始值，故而是错误的\n// 但g++只是报告了warning\n/*\nstruct X {\n\tX (int i, int j) : base(i), rem(base % j) {}\n\n\tint rem, base;\n};\n*/\n\n// 只需要调整数据成员的声明顺序即可\nstruct X {\n\tX (int i, int j) : base(i), rem(base % j) {}\n\n\tint base, rem;\n};\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_37.md",
    "content": "> 练习7.37：使用本节提供的Sales_data类，确定初始化下面的变量时分别使用了哪个构造函数，然后罗列出每个对象所有数据成员的值。\n\n```c++\nSales_data first_item(cin);\nint main() {\n\tSales_data next;\n\tSales_data last(\"9-999-99999-9\");\n}\n```\n\n---\n\n##### Sales_data first_item(cin);\n\n调用：Sales_data(std::istream &is)\n\n数据成员值：依赖于从标准输入读取的数据\n\n##### Sales_data next;\n\n调用：Sales_data(std::string s = \"\")\n\n数据成员值：\n\n- bookNo：空字符串\n- units_sold: 0\n- revenue: 0.0\n\n##### Sales_data last(\"9-999-99999-9\");\n\n调用：Sales_data(std::string s = \"\")\n\n数据成员值：\n\n- bookNo：字符串\"9-999-99999-9\"\n- units_sold: 0\n- revenue: 0.0\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_38.cpp",
    "content": "/*\n * 练习7.38：有些情况我们希望提供cin作为接受istream&参数的构造函数的默认\n * 实参，请声明这样的构造函数。\n */\n\n/*\n * 如下Foo类。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nclass Foo\n{\npublic:\n\tFoo(std::istream &is = std::cin) { is >> a; }\n\n\tvoid print() { std::cout << a << std::endl; }\n\nprivate:\n\tint a = 0;\n};\n\nint main()\n{\n\tFoo foo;\n\tfoo.print();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_39.md",
    "content": "> 练习7.39：如果接受string的构造函数和接受istream&的构造函数都使用默认实参，这种行为合法吗？如果不，为什么？\n\n---\n\n不合法，因为这样编译器就找不到合适的重载函数作为默认构造函数了（出现二义性调用）。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_40.cpp",
    "content": "/*\n * 练习7.40：从下面的抽象概念中选择一个（或者你自己指定一个），思考这样的类需要\n * 哪些数据成员，提供一组合理的构造函数并阐明这样做的原因。\n */\n\n/*\n * 比如选择Book\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nclass Book\n{\n\tfriend bool operator==(const Book &lhs, const Book &rhs);\n\tfriend bool operator!=(const Book &lhs, const Book &rhs);\n\npublic:\n\tBook() = default;\n\tBook(unsigned no, const std::string &name, const std::string &auther, const std::string &pubdate) : m_no(no), m_name(name), m_auther(auther), m_pubdate(pubdate) {}\n\tBook(std::istream &is) { is >> m_no >> m_name >> m_auther >> m_pubdate; }\n\n\tvoid print() { std::cout << m_no << \" \" << m_name << \" \" << m_auther << \" \" << m_pubdate << std::endl; }\n\nprivate:\n\tunsigned m_no = 0;\n\tstd::string m_name;\n\tstd::string m_auther;\n\tstd::string m_pubdate;\n};\n\nint main()\n{\n\tBook b1; // 默认构造函数，使用类内初始值初始化成员\n\tb1.print();\n\n\tBook b2(1, \"c--primer\", \"someone\", \"2017.11.8\"); // 需要使用一个构造函数来初始化所有成员\n\tb2.print();\n\n\tBook b3(std::cin); // 需要使用一个构造函数从标准输入读入数据来初始化类对象\n\tb3.print();\n\n\tif (b2 == b3)\n\t\tcout << \"b2 == b3\" << endl;\n\n\treturn 0;\n}\n\nbool operator==(const Book &lhs, const Book &rhs)\n{\n\treturn lhs.m_no == rhs.m_no &&\n\t       lhs.m_name == rhs.m_name &&\n\t\t   lhs.m_auther == rhs.m_auther &&\n\t\t   lhs.m_pubdate == rhs.m_pubdate;\n}\n\nbool operator!=(const Book &lhs, const Book &rhs)\n{\n\treturn !(lhs == rhs);\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_41.cpp",
    "content": "// 练习7.41：使用委托构造函数重新编写你的Sales_data类，给每个构造函数体添加\n// 一条语句，令其一旦执行就打印一条信息。用各种可能的方式分别创建Sales_data\n// 对象，认真研究每次输出的信息直到你确实理解了委托构造函数的执行顺序。\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::cerr;\n\n//--------------------------------------------------------------------------\n\nclass Sales_data {\n\tfriend Sales_data add(const Sales_data&, const Sales_data&);\n\tfriend std::ostream &print(std::ostream&, const Sales_data&);\n\tfriend std::istream &read(std::istream&, Sales_data&);\npublic:\n\t// 构造函数\n\tSales_data(const std::string &s, unsigned n, double p) :\n\t\t   bookNo(s), units_sold(n), revenue(p*n)\n\t{\n\t\tcout << \"Called constructor: \" << \"Sales_data(const std::string &s, unsigned n, double p)\" << endl;\n\t}\n\n\tSales_data() : Sales_data(\"\", 0, 0.0)\n\t{\n\t\tcout << \"Called constructor: \" << \"default\" << endl;\n\t}\n\n\tSales_data(const std::string &s) : Sales_data(s, 0, 0.0) { }\n\n\tSales_data(std::istream &is) : Sales_data()\n\t{\n\t\t//read(is, *this);  // 仅测试调用顺序，故屏蔽\n\t\tcout << \"Called constructor: \" << \"Sales_data(std::istream &is)\" << endl;\n\t}\n\n\t// 其他成员函数\n\tstd::string isbn() const { return bookNo; }\n\tSales_data& combine(const Sales_data&);\n\nprivate:\n\tdouble avg_price() const;\n\n\t// 数据成员\n\tstd::string bookNo;\n\tunsigned units_sold = 0;\n\tdouble revenue = 0.0;\n};\n\n// Sales_data的非成员接口函数\nSales_data add(const Sales_data&, const Sales_data&);\nstd::ostream &print(std::ostream&, const Sales_data&);\nstd::istream &read(std::istream&, Sales_data&);\n\ninline\ndouble Sales_data::avg_price() const\n{\n\tif (units_sold)\n\t\treturn revenue / units_sold;\n\telse\n\t\treturn 0;\n}\n\nSales_data& Sales_data::combine(const Sales_data &rhs)\n{\n\tunits_sold += rhs.units_sold;\n\trevenue += rhs.revenue;\n\treturn *this;\n}\n\n// 输入的交易信息包括ISBN、售出总数和售出价格\nstd::istream &read(std::istream &is, Sales_data &item)\n{\n\tdouble price = 0;\n\tis >> item.bookNo >> item.units_sold >> price;\n\titem.revenue = price * item.units_sold;\n\treturn is;\n}\n\nstd::ostream &print(std::ostream &os, const Sales_data &item)\n{\n\tos << item.isbn() << \" \" << item.units_sold << \" \"\n\t   << item.revenue << \" \" << item.avg_price();\n\treturn os;\n}\n\nSales_data add(const Sales_data &lhs, const Sales_data &rhs)\n{\n\tSales_data sum = lhs;\n\tsum.combine(rhs);\n\treturn sum;\n}\n\n//--------------------------------------------------------------------------\n\nint main()\n{\n\t//Sales_data sd1; \t// 调用顺序：三参数版本 -> 默认版本\n\n\tSales_data sd2(cin);\t// 调用顺序：三参数版本 -> 默认版本 -> istream参数版本\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_42.cpp",
    "content": "/*\n * 练习7.42：对于你在练习7.40（参见7.5.1节，第261页）中编写的类，确定哪些构造\n * 函数可以使用委托。如果可以的话，编写委托构造函数。如果不可以，从抽象概念列表\n * 中重新选择一个你认为可以使用委托构造函数的，为挑选出的这个概念编写类定义。\n */\n\n/*\n * 比如选择Book\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nclass Book\n{\npublic:\n\tBook(unsigned no, const std::string &name, const std::string &auther, const std::string &pubdate) : m_no(no), m_name(name), m_auther(auther), m_pubdate(pubdate) {}\n\tBook() : Book(0, \"\", \"\", \"\") {}\n\tBook(std::istream &is) : Book() { is >> m_no >> m_name >> m_auther >> m_pubdate; }\n\n\tvoid print() { std::cout << m_no << \" \" << m_name << \" \" << m_auther << \" \" << m_pubdate << std::endl; }\n\n\toperator bool() const { return m_no != 0; }\n\nprivate:\n\tunsigned m_no = 0;\n\tstd::string m_name;\n\tstd::string m_auther;\n\tstd::string m_pubdate;\n};\n\nint main()\n{\n\tBook b1; // 默认构造函数，使用类内初始值初始化成员\n\tb1.print();\n\n\tBook b2(1, \"c--primer\", \"someone\", \"2017.11.8\"); // 需要使用一个构造函数来初始化所有成员\n\tb2.print();\n\n\tBook b3(std::cin); // 需要使用一个构造函数从标准输入读入数据来初始化类对象\n\tb3.print();\n\tif (!b3)\n\t{\n\t\tcout << \"b3 is invalid\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_43.cpp",
    "content": "/*\n * 练习7.43：假定有一个名为NoDefault的类，它有一个接受int的构造函数，但是\n * 没有默认构造函数。定义类C，C有一个NoDefault类型的成员，定义C的默认构造\n * 函数。\n */\n\n#include <iostream>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nclass NoDefault\n{\npublic:\n\tNoDefault(int) {}\n};\n\nclass C\n{\npublic:\n\tC() : nd(0) {}\nprivate:\n\tNoDefault nd;\n};\n\nint main()\n{\n\tC c;\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_44.md",
    "content": "> 练习7.44：下面这条声明合法吗？如果不，为什么？\n\n```\nvector<NoDefault> vec(10);\n```\n\n---\n\n不合法，因为vector的10个元素将执行值初始化，因此会调用默认构造函数，而NoDefault没有默认构造函数，因此是错误的。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_45.md",
    "content": "> 练习7.45：如果在上一个练习中定义的vector的元素类型是C，则声明合法吗？为什么？\n\n---\n\n合法，因为C有默认构造函数。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_46.md",
    "content": "> 练习7.46：下面哪些论断是不正确的？为什么？\n\n(a) 一个类必须至少提供一个构造函数。\n\n不正确，不提供的话，编译器会自动合成一个。\n\n(b) 默认构造函数是参数列表为空的构造函数。\n\n不正确，还可以有默认实参的构造函数（所有形参都有一个默认实参），也可以作为默认构造函数。\n\n(c) 如果对于类来说不存在有意义的默认值，则类不应该提供默认构造函数。\n\n不正确，某些类不能依赖合成的默认构造函数（p236）。\n\n(d) 如果类没有定义默认构造函数，则编译器将为其生成一个并把每个数据成员初始化成相应类型的默认值。\n\n不正确。只有当类没有声明任何构造函数时，编译器才会自动生成默认构造函数（p236）。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_47.md",
    "content": "> 练习7.47：说明接受一个string参数的Sales_data构造函数是否应该是explicit的，并解释这样做的优缺点。\n\n---\n\n如果类的用户认为这样方便创建一个units_sold和revenue都为0的有意义的对象，那么就不需要是explicit的。\n\n优点：提供了一个隐式转换规则，从而方便书写，比如一个直接初始化式。\n\n缺点：只能应用于有一个参数的构造函数。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_48.md",
    "content": "> 练习7.48：假定Sales_data的构造函数不是explicit的，则下述定义将执行什么样的操作？\n\n```\nstring null_isbn(\"9-999-99999-9\");\nSales_data item1(null_isbn);\t\t// 直接使用null_isbn作为实参构造item1\nSales_data item2(\"9-999-99999-9\");\t// 通过创建一个临时的string，将其作为实参构造item2，因为形参是一个常量引用，所以合法\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_49.md",
    "content": "> 练习7.49：对于combine函数的三种不同声明，当我们调用`i.combine(s)`时分别发生什么情况？其中i是一个Sales_data，而s是一个string对象。\n\n(a) Sales_data &combine(Sales_data);\n\n(b) Sales_data &combine(Sales_data&);\n\n(c) Sales_data &combine(const Sales_data&) const;\n\n---\n\n(a) s隐式转换成一个临时的Sales_data对象，然后作为实参传递给成员函数。\n\n(b) 错误，临时对象没有办法初始化非常量引用。\n\n(c) 声明错误，传入const this的函数不能返回非常量的自身引用。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_50.md",
    "content": "> 练习7.50：确定在你的Person类中是否有一些构造函数应该是explicit的。\n\n---\n\n比如传入istream&的构造函数，避免隐式转换可以让意义更明确。\n\n见[Person类](./example_Person.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_51.md",
    "content": "> 练习7.51：vector将其单参数的构造函数定义成explicit的，而string则不是，\n你觉得原因何在？\n\n---\n\n如果vector可以有隐式转换，举个例子，这样的初始化语句是合法的：\n\n```\nvector<int> ivec = 10;\n```\n\n然而这样做显得不自然。\n\n但string却显得自然许多：\n\n```\nstring s = \"Hello\";\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_52.md",
    "content": "> 练习7.52：使用2.6.1节（第64页）的Sales_data类，解释下面的初始化过程。如果存在问题，尝试修改它。\n\n```\nSales_data item = {\"978-0590353403\", 25, 15.99};\n```\n\n---\n\nrevenue的初始化方式不正确，应该这样：\n\n```\nSales_data item = {\"978-0590353403\", 25, 15.99 * 25};\n```\n\n另外Sales_data也不需要提供类内初始值，这样才是一个聚合类型。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_53.md",
    "content": "> 练习7.53：定义你自己的Debug。\n\n---\n\n见[Debug类](./example_literal_classes/Debug.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_54.md",
    "content": "> 练习7.54：Debug中以set_开头的成员应该被声明成constexpr吗？如果不，为什么？\n\n---\n\n不应该，constexpr是用于常量表达式的函数，要遵顼如下的约定：\n\n- 函数的返回类型及其形参的类型都得是字面值类型。\n\n- 函数体中必须有且只有一条return语句。\n\n第二条不满足。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_55.md",
    "content": "> 练习7.55：7.5.5节（第266页）的Data类是字面值常量类吗？请解释原因。\n\n---\n\n不是，因为字面值常量类的数据成员都必须是字面值类型，而string不是。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_56.md",
    "content": "> 练习7.56：什么是类的静态成员？它有何优点？静态成员与普通成员有何区别？\n\n---\n\n静态成员是指：与类本身直接相关，而跟类的对象不保持关联的特殊成员。\n\n优点：\n\n- 有一些数据成员是所有类对象共享的，声明成static可以很方便做到共享。\n\n区别：\n\n- 静态成员函数不包含this指针。\n\n- 静态数据成员可以是不完全类型。\n\n- 可以使用静态成员作为默认实参。\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_57.md",
    "content": "> 练习7.57：编写你自己的Account类。\n\n---\n\n见[Account类](./example_Account)\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/exercise_7_58.md",
    "content": "> 练习7.58：下面的静态数据成员的声明和定义有错误吗？请解释原因。\n\n```\n// example.h\nclass Example {\npublic:\n\tstatic double rate = 6.5;\n\n\tstatic const int vecSize = 20;\n\tstatic vector<double> vec(vecSize);\n};\n// example.C\n#include \"example.h\"\ndouble Example::rate;\nvector<double> Example::vec;\n```\n\n---\n\n错误如下：\n\n```\nstatic double rate = 6.5;\n              ^\n              普通的静态成员不应该在类的内部初始化\n\nstatic vector<double> vec(vecSize);\n                          ^\n                          应该在类的外部对vec初始化\n```\n\n修改：\n\n```\nclass Example {\npublic:\n\tstatic constexpr double rate = 6.5;\n\n\tstatic const int vecSize = 20;\n\tstatic vector<double> vec;\n};\n// example.C\n#include \"example.h\"\nconstexpr double Example::rate;\nvector<double> Example::vec(vecSize);\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch07_Classes/test_incomplete_type.cpp",
    "content": "// test: 测试不完全类型（p250）\n\n// 不完全类型（incomplete type）：已经声明但是尚未定义的类型。不完全类型不能用于\n// 定义变量或者类的成员，但是用不完全类型定义指针或者引用是合法的。\n\n// My Note: 如下案例所示，定义一个空的包含不完全类型的容器也是合法的。\n\n#include <iostream>\n#include <vector>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\nusing std::vector;\n\nclass Child;\t// 前向声明一个不完全类型\nclass Parent\n{\n\tvector<Child> child_vec;\n};\n\nclass Child\n{\n\tint id;\n};\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/data/person_numbers.txt",
    "content": "morgan 2015552368 8625550123\ndrew 9735550130\nlee 6095550132 2015550175 8005550000\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/data/some_words.txt",
    "content": "What does the bee do\nBring home honey\nAnd what does Father do\nBring home money\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/example_fstream.cpp",
    "content": "// example: 使用fstream代替iostream&读取Sales_data（p284）\n\n// ./example_fstream ../ch07_Classes/data/book_sales /tmp/tmp_out.txt\n\n#include <iostream>\n#include <fstream>\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\nint main(int argc, char* argv[])\n{\n\tif (argc <= 2) return 1;\n\n\tifstream in(argv[1]); \t// 打开销售记录文件\n\tofstream out(argv[2]);\t// 打开输出文件\n\n\tSales_data total;\t// 保存下一条交易记录的变量\n\t// 读入第一条交易记录，并确保有数据可以处理\n\tif (read(in, total)) {\n\t\tSales_data trans;\t// 保存和的变量\n\t\t// 读入并处理剩余交易记录\n\t\twhile (read(in, trans)) {\n\t\t\t// 如果我们仍在处理相同的书\n\t\t\tif (total.isbn() == trans.isbn()) {\n\t\t\t\ttotal.combine(trans);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tprint(out, total) << endl;\t// 输出结果\n\t\t\t\ttotal = trans;\t\t// 处理下一本书\n\t\t\t}\n\t\t}\n\t\tprint(out, total) << endl;\t// 输出最后一条交易\n\t}\n\telse {\n\t\tcerr << \"No data?!\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/example_istringstream.cpp",
    "content": "// exmaple: 使用istringstream（p287）\n\n// ./example_istringstream < data/person_numbers.txt\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <sstream>\n\nusing namespace std;\n\n// 保存一个人的电话号码\nstruct PersonInfo {\n\tstring name;\n\tvector<string> phones;\n};\n\n// 输出记录\nvoid print(const PersonInfo &info)\n{\n\tcout << info.name << \" \";\n\tfor (const auto &number : info.phones)\n\t\tcout << number << \" \";\n\n\tcout << endl;\n}\n\nint main()\n{\n\tstring line, word;\t\t\t// 分别保存输入的一行和的单词\n\tvector<PersonInfo> people;\t// 保存来自输入的所有记录\n\n\t// 逐行从输入读取数据，直至cin遇到文件尾（或其他错误）\n\twhile (getline(cin, line)) {\n\t\tPersonInfo info;\t\t// 创建一个保存此记录的对象\n\t\tistringstream record(line);\t// 将记录绑定到刚读入的行\n\t\trecord >> info.name;\t// 读取名字\n\t\twhile (record >> word)\t// 读取电话号码\n\t\t\tinfo.phones.push_back(word);\t// 保存它们\n\t\tpeople.push_back(info);\t// 将此记录追加到people末尾\n\t}\n\n\t// 输出验证\n\tfor (const auto &person_info : people)\n\t\tprint(person_info);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_01.cpp",
    "content": "/*\n * 练习8.1：编写函数，接受一个istream&参数，返回值类型也是istream&。此函数须从给定流中读取数据，\n * 直至遇到文件结束符标识时停止。它将读取的数据打印在标准输出上。完成这些操作后，在返回流之前，\n * 对流进行复位，使其处于有效状态。\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cin;\nusing std::cout;\nusing std::endl;\nusing std::string;\n\nstd::istream& print(std::istream &is)\n{\n\tstring word;\n\twhile (is >> word)\n\t\tcout << word << endl;\n\n\tis.clear();\n\treturn is;\n}\n\nint main()\n{\n\tprint(cin);\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_02.md",
    "content": "> 练习8.2：测试函数，调用参数为cin。\n\n---\n\n见[练习8.1的main函数](./exercise_8_01.cpp)。\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_03.md",
    "content": "> 练习8.3：什么情况下，下面的while循环会终止？\n\n```\nwhile (cin >> i) /* ...  */\n```\n\n---\n\n当流崩溃、IO操作失败、流到达了文件尾时，检测流状态的条件会失败，while循环终止。\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_04.cpp",
    "content": "/*\n * 练习8.4：编写函数，以读模式打开一个文件，将其内容都入到一个string的vector中，\n * 将每一行作为一个独立的元素存于vector中。\n */\n\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <vector>\n\nusing namespace std;\n\nvoid read_func(const string &file, vector<string> &line_vec)\n{\n\tifstream in(file);\n\tif (!in) return;\n\n\tstring line;\n\twhile (getline(in, line))\n\t\tline_vec.push_back(line);\n\n\tin.close();\n}\n\nint main()\n{\n\tvector<string> line_vec;\n\tread_func(\"./exercise_8_04.cpp\", line_vec);\n\n\t// 输出验证\n\tfor (auto &l : line_vec)\n\t\tcout << l << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_05.cpp",
    "content": "/*\n * 练习8.5：重写上面的程序，将每个单词作为一个独立的元素进行存储。\n */\n\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <vector>\n\nusing namespace std;\n\nvoid read_func(const string &file, vector<string> &word_vec)\n{\n\tifstream in(file);\n\tif (!in) return;\n\n\tstring word;\n\twhile (in >> word)\n\t\tword_vec.push_back(word);\n\n\tin.close();\n}\n\nint main()\n{\n\tvector<string> word_vec;\n\tread_func(\"./exercise_8_04.cpp\", word_vec);\n\n\t// 输出验证\n\tfor (auto &w : word_vec)\n\t\tcout << w << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_06.cpp",
    "content": "/*\n * 练习8.6：重写7.1.1节的书店程序（第229页），从一个文件中读取交易记录。将文件名作为一个参数传\n * 递给main（参见6.2.5节，第196页）。\n */\n\n// ./exercise_8_06 ../ch07_Classes/data/book_sales\n\n#include <iostream>\n#include <fstream>\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\nint main(int argc, char* argv[])\n{\n\tif (argc <= 1) return 1;\n\n\tifstream in(argv[1]);\n\tif (!in) return 1;\n\n\tSales_data total;\t// 保存下一条交易记录的变量\n\t// 读入第一条交易记录，并确保有数据可以处理\n\tif (read(in, total)) {\n\t\tSales_data trans;\t// 保存和的变量\n\t\t// 读入并处理剩余交易记录\n\t\twhile (read(in, trans)) {\n\t\t\t// 如果我们仍在处理相同的书\n\t\t\tif (total.isbn() == trans.isbn()) {\n\t\t\t\ttotal.combine(trans);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tprint(cout, total) << endl;\t// 输出结果\n\t\t\t\ttotal = trans;\t\t// 处理下一本书\n\t\t\t}\n\t\t}\n\t\tprint(cout, total) << endl;\t// 输出最后一条交易\n\t}\n\telse {\n\t\tcerr << \"No data?!\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_07.cpp",
    "content": "// 练习8.7：修改上一节的书店程序，将结果保存到一个文件中。将输出文件名作为第二\n// 个参数传递给main函数。\n\n// ./exercise_8_07 ../ch07_Classes/data/book_sales /tmp/tmp_out.txt\n\n#include <iostream>\n#include <fstream>\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\nint main(int argc, char* argv[])\n{\n\tif (argc <= 2) return 1;\n\n\tifstream in(argv[1]);\n\tif (!in) return 1;\n\n\tofstream out(argv[2]);\n\tif (!out) return 2;\n\n\tSales_data total;\t// 保存下一条交易记录的变量\n\t// 读入第一条交易记录，并确保有数据可以处理\n\tif (read(in, total)) {\n\t\tSales_data trans;\t// 保存和的变量\n\t\t// 读入并处理剩余交易记录\n\t\twhile (read(in, trans)) {\n\t\t\t// 如果我们仍在处理相同的书\n\t\t\tif (total.isbn() == trans.isbn()) {\n\t\t\t\ttotal.combine(trans);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tprint(out, total) << endl;\t// 输出结果\n\t\t\t\ttotal = trans;\t\t// 处理下一本书\n\t\t\t}\n\t\t}\n\t\tprint(out, total) << endl;\t// 输出最后一条交易\n\t}\n\telse {\n\t\tcerr << \"No data?!\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_08.cpp",
    "content": "// 练习8.8：修改上一题的程序，将结果追加到给定文件末尾。对同一个输出文件，运行程序至少两次，检验数据是否得以保留。\n\n// ./exercise_8_08 ../ch07_Classes/data/book_sales /tmp/tmp_out.txt\n\n#include <iostream>\n#include <fstream>\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\nint main(int argc, char* argv[])\n{\n\tif (argc <= 2) return 1;\n\n\tifstream in(argv[1]);\n\tif (!in) return 1;\n\n\tofstream out(argv[2], ofstream::app);\n\tif (!out) return 2;\n\n\tSales_data total;\t// 保存下一条交易记录的变量\n\t// 读入第一条交易记录，并确保有数据可以处理\n\tif (read(in, total)) {\n\t\tSales_data trans;\t// 保存和的变量\n\t\t// 读入并处理剩余交易记录\n\t\twhile (read(in, trans)) {\n\t\t\t// 如果我们仍在处理相同的书\n\t\t\tif (total.isbn() == trans.isbn()) {\n\t\t\t\ttotal.combine(trans);\n\t\t\t}\n\t\t\telse {\n\t\t\t\tprint(out, total) << endl;\t// 输出结果\n\t\t\t\ttotal = trans;\t\t// 处理下一本书\n\t\t\t}\n\t\t}\n\t\tprint(out, total) << endl;\t// 输出最后一条交易\n\t}\n\telse {\n\t\tcerr << \"No data?!\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_09.cpp",
    "content": "/*\n * 练习8.9：使用你为8.1.2节（第281页）第一个练习所编写的函数打印一个\n * istringstream对象的内容。\n */\n\n#include <iostream>\n#include <string>\n#include <sstream>\n\nusing namespace std;\n\nstd::istream& print(std::istream &is)\n{\n\tstring word;\n\twhile (is >> word)\n\t\tcout << word << endl;\n\n\tis.clear();\n\treturn is;\n}\n\nint main()\n{\n\tstring s = \"hello world\";\n\tistringstream iss(s);\n\n\tprint(iss);\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_10.cpp",
    "content": "/*\n * 练习8.10：编写程序，将来自一个文件中的行保存在一个vector<string>中。然后\n * 使用一个istringstream从vector读取数据元素，每次读取一个单词。\n */\n\n// ./exercise_8_10 < data/some_words.txt\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <sstream>\n\nusing namespace std;\n\nint main()\n{\n\tvector<string> lines;\n\tstring line;\n\twhile (getline(cin, line))\n\t\tlines.push_back(line);\n\n\tstring word;\n\tfor (const auto &l : lines) {\n\t\tistringstream iss(l);\n\t\twhile (iss >> word)\n\t\t\tcout << word << \" \";\n\n\t\tcout << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_11.cpp",
    "content": "/*\n * 练习8.11：本节的程序在外层while循环中定义了istringstream对象。如果\n * record对象定义在循环之外，你需要对程序进行怎样的修改？重写程序，将\n * record的定义移到while循环之外，验证你设想的修改方法是否正确。\n */\n\n// ./exercise_8_11 < data/person_numbers.txt\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <sstream>\n\nusing namespace std;\n\n// 保存一个人的电话号码\nstruct PersonInfo {\n\tstring name;\n\tvector<string> phones;\n};\n\n// 输出记录\nvoid print(const PersonInfo &info)\n{\n\tcout << info.name << \" \";\n\tfor (const auto &number : info.phones)\n\t\tcout << number << \" \";\n\n\tcout << endl;\n}\n\nint main()\n{\n\tstring line, word;\t\t\t// 分别保存输入的一行和的单词\n\tvector<PersonInfo> people;\t// 保存来自输入的所有记录\n\n\tistringstream record;\t\n\t// 逐行从输入读取数据，直至cin遇到文件尾（或其他错误）\n\twhile (getline(cin, line)) {\n\t\tPersonInfo info;\t\t// 创建一个保存此记录的对象\n\t\trecord.str(line);\t\t// 将记录绑定到刚读入的行\n\t\trecord >> info.name;\t// 读取名字\n\t\twhile (record >> word)\t// 读取电话号码\n\t\t\tinfo.phones.push_back(word);\t// 保存它们\n\t\tpeople.push_back(info);\t// 将此记录追加到people末尾\n\n\t\t// 如果不复位条件状态位，那么将不能继续读取\n\t\trecord.clear();\n\t}\n\n\t// 输出验证\n\tfor (const auto &person_info : people)\n\t\tprint(person_info);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_12.md",
    "content": "> 练习8.12：我们为什么没有在PersonInfo中使用类内初始化？\n\n---\n\n因为不需要，string和vector将被默认初始化为空的容器。\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_13.cpp",
    "content": "// 练习8.13：重写本节的电话号码程序，从一个命名文件而非cin读取数据。\n\n#include <iostream>\n#include <fstream>\n#include <string>\n#include <vector>\n#include <sstream>\n\nusing namespace std;\n\n// 保存一个人的电话号码\nstruct PersonInfo {\n\tstring name;\n\tvector<string> phones;\n};\n\n// 输出记录\nvoid print(const PersonInfo &info)\n{\n\tcout << info.name << \" \";\n\tfor (const auto &number : info.phones)\n\t\tcout << number << \" \";\n\n\tcout << endl;\n}\n\nint main()\n{\n\tstring line, word;\t\t\t// 分别保存输入的一行和的单词\n\tvector<PersonInfo> people;\t// 保存来自输入的所有记录\n\n\tifstream ifs(\"./data/person_numbers.txt\");\n\tif (!ifs) return 1;\n\n\t// 逐行从输入读取数据，直至cin遇到文件尾（或其他错误）\n\twhile (getline(ifs, line)) {\n\t\tPersonInfo info;\t\t// 创建一个保存此记录的对象\n\t\tistringstream record(line);\t// 将记录绑定到刚读入的行\n\t\trecord >> info.name;\t// 读取名字\n\t\twhile (record >> word)\t// 读取电话号码\n\t\t\tinfo.phones.push_back(word);\t// 保存它们\n\t\tpeople.push_back(info);\t// 将此记录追加到people末尾\n\t}\n\n\t// 输出验证\n\tfor (const auto &person_info : people)\n\t\tprint(person_info);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch08_The_IO_Library/exercise_8_14.md",
    "content": "> 练习8.14：我们为什么将entry和nums定义为const auto&？\n\n---\n\n定义成引用是为了避免拷贝，定义成常量引用是因为不需要修改其值。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/data/letter.txt",
    "content": "hello cpp primer, I study you for a long time, wow ...\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/example_capacity_size.cpp",
    "content": "// capacity和size（p318）\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> ivec;\n\t// size应该为0，capacity的值依赖于具体实现\n\tcout << \"ivec: size: \" << ivec.size()\n\t     << \" capacity: \" << ivec.capacity() << endl;\n\t\n\t// 向ivec添加24个元素\n\tfor (int i = 0; i < 24; ++i)\n\t\tivec.push_back(i);\n\n\t// size应该为24，capacity应该大于等于24，具体值依赖于标准库实现\n\tcout << \"ivec: size: \" << ivec.size()\n\t     << \" capacity: \" << ivec.capacity() << endl;\n\tivec.reserve(50);\t// 将capacity至少设定为50，可能会更大\n\tcout << \"ivec: size: \" << ivec.size()\n\t     << \" capacity: \" << ivec.capacity() << endl;\n\n\t// 添加元素用光多余容量\n\twhile (ivec.size() != ivec.capacity())\n\t\tivec.push_back(0);\n\t// capacity应该未改变，size和capacity相等\n\tcout << \"ivec: size: \" << ivec.size()\n\t     << \" capacity: \" << ivec.capacity() << endl;\n\n\t// 如果再添加一个元素，vector不得不重新分配空间\n\tivec.push_back(42);\n\t// size应该为51，capacity至少为51，具体值依赖于标准库实现\n\tcout << \"ivec: size: \" << ivec.size()\n\t     << \" capacity: \" << ivec.capacity() << endl;\n\n\t// 可以调用shrink_to_fit要求vector将超出当前大小的多余内存退回给系统\n\tivec.shrink_to_fit();\n\t// size应该未改变，capacity的值依赖于具体实现\n\tcout << \"ivec: size: \" << ivec.size()\n\t     << \" capacity: \" << ivec.capacity() << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/example_change_container_in_loop.cpp",
    "content": "// example: 改变容器的循环程序（p316）\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\t// 傻瓜循环，删除偶数元素，复制每个奇数元素\n\tvector<int> vec = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};\n\n\tauto beg = vec.begin();\n\twhile (beg != vec.end()) {\n\t\tif (*beg % 2) {\n\t\t\tbeg = vec.insert(beg, *beg);\n\t\t\tbeg += 2; // 向前移动迭代器，跳过当前元素以及插入到它之前的元素\n\t\t}\n\t\telse\n\t\t\tbeg = vec.erase(beg);\n\t}\n\n\tfor (auto i : vec)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/example_erase.cpp",
    "content": "// example: 从容器内部删除一个元素（p312）\n\n#include <iostream>\n#include <list>\n\nusing namespace std;\n\nint main()\n{\n\t// 循环删除一个list中的所有奇数元素\n\n\tlist<int> lst = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};\n\tauto it = lst.begin();\n\twhile (it != lst.end()) {\n\t\tif (*it % 2)\n\t\t\tit = lst.erase(it);\n\t\telse\n\t\t\t++it;\n\t}\n\n\tfor (auto i : lst)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/example_forward_list.cpp",
    "content": "// example: forward_list操作（p313）\n\n#include <iostream>\n#include <forward_list>\n\nusing namespace std;\n\nint main()\n{\n\t// 删除forward_list中的奇数元素\n\t\n\tforward_list<int> flst = {0,1,2,3,4,5,6,7,8,9};\n\tauto prev = flst.before_begin();\t// 表示flst的首前元素\n\tauto curr = flst.begin();\t\t\t// 表示flst中的第一个元素\n\twhile (curr != flst.end()) {\n\t\tif (*curr % 2)\n\t\t\tcurr = flst.erase_after(prev);\n\t\telse {\n\t\t\tprev = curr;\t// 移动迭代器curr，指向下一个元素，prev指向curr之前的元素\n\t\t\t++curr;\n\t\t}\n\t}\n\n\tfor (auto i : flst)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/example_insert.cpp",
    "content": "// example: 使用insert的返回值（p308）\n\n#include <iostream>\n#include <string>\n#include <list>\n\nusing namespace std;\n\nint main()\n{\n\tlist<string> lst;\n\tstring word;\n\n\tauto it = lst.begin();\n\twhile (cin >> word)\n\t\tit = lst.insert(it, word);    // 等价于调用push_front\n\n\t// 输出验证（反序输出）\n\tfor (const auto &s : lst)\n\t\tcout << s << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/example_iterator_range.cpp",
    "content": "// example: 迭代器范围iterator range（p296）\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> vec = {1, 2, 3, 4, 5};\n\n\tauto beg = vec.begin();\n\tauto end = vec.end();\n\n\twhile (beg != end) {\n\t\tcout << *beg << endl; \t// 正确：范围非空，因此begin指向一个元素\n\t\t++beg;\t\t\t\t\t// 移动迭代器，获取下一个元素\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_01.md",
    "content": "> 练习9.1：对于下面的程序任务，vector、deque和list哪种容器最为适合？解释你的选择理由。如果没有哪一种容器优于其他容器，也请解释理由。\n\n(a) 读取固定数量的单词，将它们按照字典序插入到容器中。我们将在下一章看到，关联容器更适合这个问题。\n(b) 读取未知数量的单词，总是将新单词插入到末尾。删除操作在头部进行。\n(c) 从一个文件读取未知数量的整数。将这些数排序，然后将她们打印到标准输出。\n\n---\n\n(a) 使用list，因为很可能从中间插入数据，list的速度更快。\n(b) 使用deque，因为需要在头尾插入或者删除元素，deque的速度都很快。\n(c) 使用vector，没有什么很好的理由选择其他容器，就应该使用vector。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_02.cpp",
    "content": "/*\n * 练习9.2：定义一个list对象，其元素类型是int的deque。\n */\n\n#include <list>\n#include <deque>\n\nusing namespace std;\n\nint main()\n{\n\tlist<deque<int>> l;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_03.md",
    "content": "> 练习9.3：构成迭代器范围的迭代器有何限制？\n\n---\n\nbegin和end必须指向同一个容器中的元素，或者是容器最后一个元素之后的位置，且我们可以通过反复递增begin来到达end。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_04.cpp",
    "content": "/*\n * 练习9.4：编写函数，接受一对指向vector<int>的迭代器和一个int值。在两个迭代器\n * 指定的范围中查找给定的值，返回一个布尔值来指出是否找到。\n */\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nusing VecIt = vector<int>::const_iterator;\n\nbool find_val(VecIt beg, VecIt end, int i)\n{\n\twhile (beg != end) {\n\t\tif (*beg++ == i) return true;\n\t}\n\treturn false;\n}\n\nint main()\n{\n\tvector<int> vec = {1, 2, 3, 4, 5};\n\tif (find_val(vec.begin(), vec.end(), 3))\n\t\tcout << \"find\" << endl;\n\telse\n\t\tcout << \"not find\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_05.cpp",
    "content": "/*\n * 练习9.5：重写上一题的函数，返回一个迭代器指向找到的元素。注意，程序必须处理\n * 未找到给定值的情况。\n */\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nusing VecIt = vector<int>::const_iterator;\n\nVecIt find_val(VecIt beg, VecIt end, int i)\n{\n\twhile (beg != end) {\n\t\tif (*beg == i) return beg;\n\t\t++beg;\n\t}\n\treturn end;\n}\n\nint main()\n{\n\tvector<int> vec = {1, 2, 3, 4, 5};\n\tVecIt pos_it = find_val(vec.begin(), vec.end(), 3);\n\tif (pos_it != vec.end())\n\t\tcout << \"find, pos: \" << pos_it - vec.begin() << endl;\n\telse\n\t\tcout << \"not find\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_06.md",
    "content": "> 练习9.6：下面程序有何错误？你应该如何修改它？\n\n```\nlist<int> lst1;\nlist<int>::iterator iter1 = lst1.begin(),\n\t\t\t\t\titer2 = lst2.end();\n\nwhile (iter1 < iter2) /* ... */\n             ^\n\t\t\t list的迭代器不支持<运算符，需要使用通用的!=运算符\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_07.md",
    "content": "> 练习9.7：为了索引int的vector中的元素，应该使用什么类型？\n\n---\n\n使用：`vector<int>::size_type`\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_08.md",
    "content": "练习9.8：为了读取string的list中的元素，应该使用什么类型？如果写入list，又该使用什么类型？\n\n---\n\n- 读取：`list<string>::const_iterator`\n\n- 写入：`list<string>::iterator`\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_09.md",
    "content": "> 练习9.9：begin和cbegin两个函数有什么不同？\n\n---\n\nbegin返回迭代器类型依赖于容器的类型，如果容器是const的，则迭代器是const_iterator，否则是iterator；cbegin返回的是const_iterator。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_10.md",
    "content": "> 练习9.10：下面4个对象分别是什么类型？\n\n```\nvector<int> v1;\nconst vector<int> v2;\nauto it1 = v1.begin(), it2 = v2.begin();\nauto it3 = v1.cbegin(), it4 = v2.cbegin();\n```\n---\n\n- it1：vector<int>::iterator\n\n- it2: vector<int>::const_iterator\n\n- it3: vector<int>::const_iterator\n\n- it4: vector<int>::const_iterator\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_11.cpp",
    "content": "/*\n * 练习9.11：为6种创建和初始化vector对象的方法，每一种都给出一个实例。解释每个\n * vector包含什么值。\n */\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\n#define print_vec(vec)\\\ndo {\\\n\tcout << #vec << \": \";\\\n\tfor (auto i : vec)\\\n\t\tcout << i << \" \";\\\n\tcout << endl;\\\n} while (0)\n\nint main()\n{\n\tvector<int> ivec1;\t// 空vector\n\tprint_vec(ivec1);\n\n\tvector<int> ivec2 = {1, 2, 3};\t// 1 2 3\n\tprint_vec(ivec2);\n\n\tvector<int> ivec3 = ivec2;\t\t// 1 2 3\n\tprint_vec(ivec3);\n\n\tvector<int> ivec4(ivec3.begin(), ivec3.end());\t// 1 2 3\n\tprint_vec(ivec4);\n\n\tvector<int> ivec5(5);\t\t\t// 0 0 0 0 0\n\tprint_vec(ivec5);\n\n\tvector<int> ivec6(6, 6);\t\t// 6 6 6 6 6 6\n\tprint_vec(ivec6);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_12.md",
    "content": "> 练习9.12：对于接受一个容器创建其拷贝的构造函数，和接受两个迭代器创建拷贝的构造函数，解释它们的不同。\n\n---\n\n前者需要两个容器的类型相同，其元素的类型也相同。后者只需要迭代器指向的元素类型可以拷贝给容器的元素即可。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_13.cpp",
    "content": "// 练习9.13：如何从一个list<int>初始化一个vector<double>？从一个vector<int>\n// 又该如何创建？编写代码验证你的答案。\n\n#include <iostream>\n#include <list>\n#include <vector>\n\nusing namespace std;\n\n#define print_vec(vec)\\\ndo {\\\n\tcout << #vec << \": \";\\\n\tfor (auto i : vec)\\\n\t\tcout << i << \" \";\\\n\tcout << endl;\\\n} while (0)\n\nvoid func_list_to_vector()\n{\n\tlist<int> ilst = {1, 2, 3};\n\tvector<double> dvec(ilst.begin(), ilst.end());\n\tprint_vec(dvec);\n}\n\nvoid func_ivec_to_dvec()\n{\n\tvector<int> ivec = {1, 2, 3};\n\tvector<double> dvec(ivec.begin(), ivec.end());\n\tprint_vec(dvec);\n}\n\nint main()\n{\n\tfunc_list_to_vector();\n\tfunc_ivec_to_dvec();\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_14.cpp",
    "content": "// 练习9.14：编写程序，将一个list中的char*指针（指向C风格字符串）元素赋值\n// 给一个vector中的string。\n\n#include <iostream>\n#include <vector>\n#include <list>\n\nusing namespace std;\n\nint main()\n{\n\tlist<const char*> cstr_lst = {\"hi\", \"hello\", \"wow\"};\n\tvector<string> str_vec;\n\n\tstr_vec.assign(cstr_lst.begin(), cstr_lst.end());\n\tfor (const auto &s : str_vec)\n\t\tcout << s << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_15.cpp",
    "content": "// 练习9.15：编写程序，判定两个vector<int>是否相等。\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> v1 = {1, 2, 3};\n\tvector<int> v2 = {1, 2, 3};\n\n\tif (v1 == v2)\n\t\tcout << \"equal\" << endl;\n\telse\n\t\tcout << \"not equal\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_16.cpp",
    "content": "// 练习9.16：重写上一题的程序，比较一个list<int>中的元素和一个vector<int>\n// 中的元素。\n\n#include <iostream>\n#include <vector>\n#include <list>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> vec = {1, 2, 3};\n\tlist<int> lst = {1, 2, 3};\n\n\tvector<int> tmp_vec(lst.begin(), lst.end());\n\tif (vec == tmp_vec)\n\t\tcout << \"equal\" << endl;\n\telse\n\t\tcout << \"not equal\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_17.md",
    "content": "> 练习9.17：假定c1和c2是两个容器，下面的比较操作有何限制（如果有的话）？\n\n```\nif (c1 < c2)\n```\n\n---\n\nc1和c2必须是同种类型的容器，而且容器中的元素类型也要一致，而且元素类型支持<运算符。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_18.cpp",
    "content": "// 练习9.18：编写程序，从标准输入读取string序列，存入一个deque中。编写一个\n// 循环，用迭代器打印deque中的元素。\n\n#include <iostream>\n#include <string>\n#include <deque>\n\nusing namespace std;\n\nint main()\n{\n\tdeque<string> str_deq;\n\tstring word;\n\n\twhile (cin >> word)\n\t\tstr_deq.push_back(word);\n\n\tfor (auto it = str_deq.cbegin(); it != str_deq.cend(); ++it)\n\t\tcout << *it << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_19.cpp",
    "content": "// 练习9.19：重写上题的程序，用list替代lstue。列出程序要做出哪些改变。\n\n// 只需要改一下容器的名字就可以了\n\n#include <iostream>\n#include <string>\n#include <list>\n\nusing namespace std;\n\nint main()\n{\n\tlist<string> str_lst;\n\tstring word;\n\n\twhile (cin >> word)\n\t\tstr_lst.push_back(word);\n\n\tfor (auto it = str_lst.cbegin(); it != str_lst.cend(); ++it)\n\t\tcout << *it << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_20.cpp",
    "content": "// 编写程序，从一个list<int>拷贝元素到两个deque中。值为偶数的所有\n// 元素拷贝到一个deque中，而奇数值元素都拷贝到另一个deque中。\n\n#include <iostream>\n#include <list>\n#include <deque>\n\nusing namespace std;\n\n#define print_vec(vec)\\\ndo {\\\n\tcout << #vec << \": \";\\\n\tfor (auto i : vec)\\\n\t\tcout << i << \" \";\\\n\tcout << endl;\\\n} while (0)\n\nint main()\n{\n\tlist<int> ilst = {1, 2, 3, 4, 5, 6};\n\tdeque<int> even_deq;\n\tdeque<int> odd_deq;\n\n\tfor (auto i : ilst) {\n\t\tif (0 == i % 2)\n\t\t\teven_deq.push_back(i);\n\t\telse\n\t\t\todd_deq.push_back(i);\n\t}\n\n\tprint_vec(even_deq);\n\tprint_vec(odd_deq);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_21.md",
    "content": "> 练习9.21：如果我们将第308页中使用insert返回值将元素添加到list中的循环程序改写为将元素插入到vector中，分析循环将如何工作。\n\n---\n\n是一样的。但是每次插入都会移动容器中所以的元素，这样做可能很耗时。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_22.md",
    "content": "> 练习9.22：假定iv是一个int的vector，下面的程序存在什么错误？你将如何修改？\n\n```\nvector<int>::iterator iter = iv.begin(),\n                      mid = iv.begin() + iv.size()/2;\n\nwhile (iter != mid)\n\tif (*iter == some_val)\n\t\tiv.insert(iter, 2 * some_val);\n```\n\n---\n\n1. 插入操作会使vector中所有的迭代器失效，如果继续使用将出现未定义的错误。\n\n2. 没有递增iter，可能死循环。\n\n这样修改：\n\n```\nvector<int>::iterator iter = iv.begin(),\n                      mid = iv.begin() + iv.size()/2;\n\nwhile (iter != mid) {\n\tif (*iter == some_val) {\n\t\titer = iv.insert(iter, 2 * some_val);\n\t\t++iter;\n\t\tmid = iv.begin() + iv.size()/2;\n\t}\n\t++iter;\n}\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_23.md",
    "content": "> 练习9.23：在本节第一个程序（第309页）中，若c.size()为1，则val、val2、val3和val4的值会是什么？\n\n全部一样，都是第一个元素的拷贝。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_24.cpp",
    "content": "// 练习9.24：编写程序，分别使用at、下标运算符、front和begin提取一个vector中\n// 的第一个元素。在一个空的vector上测试你的程序。\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> vec;\n\n\t//vec.at(0);    // terminate called after throwing an instance of 'std::out_of_range'\n\n\t//cout << vec[0] << endl;    // Segmentation fault (core dumped)\n\n\t//cout << vec.front() << endl; // Segmentation fault (core dumped)\n\n\tcout << *vec.begin() << endl;  // Segmentation fault (core dumped)\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_25.cpp",
    "content": "// 练习9.25：对于第312页中删除一个范围内的元素的程序，如果elem1与elem2相等会发生什么？\n// 如果elem2是尾后迭代器，或者elem1和elem2皆为尾后迭代器，又会发生什么？\n\n// 如果elem1与elem2相等、或者都是尾后迭代器，那么什么都不会发生\n// 如果elem2是尾后迭代器，则删除[elem1, elem2)范围内的元素\n\n#include <iostream>\n#include <list>\n\nusing namespace std;\n\nint main()\n{\n\tlist<int> lst;\n\tauto end = lst.end();\n\n\tlst.erase(end, end); // nothing happened\n\n\t//lst.erase(end);\t// crash\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_26.cpp",
    "content": "// 使用下面代码定义的ia，将ia拷贝到一个vector和一个list中。使用\n// 单迭代器版本的erase从list中删除奇数元素，从vector中删除偶数元素。\n\n#include <iostream>\n#include <vector>\n#include <list>\n\nusing namespace std;\n\n#define print_container(c)\\\ndo {\\\n\tcout << #c << \": \";\\\n\tfor (auto i : c)\\\n\t\tcout << i << \" \";\\\n\tcout << endl;\\\n} while (0)\n\nvoid EraseEvenFromVector(vector<int> &vec)\n{\n\tauto it = vec.begin();\n\twhile (it != vec.end()) {\n\t\tif (*it % 2 == 0)\n\t\t\tit = vec.erase(it);\n\t\telse\n\t\t\t++it;\n\t}\n}\n\nvoid EraseOddFromList(list<int> &lst)\n{\n\tauto it = lst.begin();\n\twhile (it != lst.end()) {\n\t\tif (*it % 2)\n\t\t\tit = lst.erase(it);\n\t\telse\n\t\t\t++it;\n\t}\n}\n\nint main()\n{\n\tint ia[] = {0, 1, 1, 2, 3, 5, 8, 13, 21, 55, 89};\n\n\tvector<int> vec(begin(ia), end(ia));\n\tprint_container(vec);\n\n\tlist<int> lst(begin(ia), end(ia));\n\tprint_container(lst);\n\n\tEraseEvenFromVector(vec);\n\tprint_container(vec);\n\n\tEraseOddFromList(lst);\n\tprint_container(lst);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_27.cpp",
    "content": "// 练习9.27：编写程序，查找并删除forward_list<int>中的奇数元素。\n\n#include <iostream>\n#include <forward_list>\n\nusing namespace std;\n\nint main()\n{\n\t// 删除forward_list中的奇数元素\n\t\n\tforward_list<int> flst = {0,1,2,3,4,5,6,7,8,9};\n\tauto prev = flst.before_begin();\t// 表示flst的首前元素\n\tauto curr = flst.begin();\t\t\t// 表示flst中的第一个元素\n\twhile (curr != flst.end()) {\n\t\tif (*curr % 2)\n\t\t\tcurr = flst.erase_after(prev);\n\t\telse {\n\t\t\tprev = curr;\t// 移动迭代器curr，指向下一个元素，prev指向curr之前的元素\n\t\t\t++curr;\n\t\t}\n\t}\n\n\tfor (auto i : flst)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_28.cpp",
    "content": "// 练习9.28：编写函数，接受一个forward_list<string>和两个string共三个参数。\n// 函数应在链表中查找第一个string，并将第二个string插入到紧接着第一个string\n// 之后的位置。如第一个string未在链表中，则将第二个string插入到链表末尾。\n\n#include <iostream>\n#include <forward_list>\n#include <string>\n\nusing namespace std;\n\nvoid Insert(forward_list<string> &flst, const string &find_str, const string &insert_str)\n{\n\tauto prev = flst.before_begin();\n\tauto it = flst.begin();\n\twhile (it != flst.end()) {\n\t\tif (*it == find_str) {\n\t\t\tit = flst.insert_after(it, insert_str);\n\t\t\tbreak;\n\t\t}\n\t\telse {\n\t\t\tprev = it;\n\t\t\t++it;\n\t\t}\n\t}\n\n\tif (it == flst.end())\n\t\tflst.insert_after(prev, insert_str);\n}\n\nint main()\n{\n\tforward_list<string> flst{\"a\", \"b\", \"c\", \"d\"};\n\n\tInsert(flst, \"c\", \"X\");\n\tfor (const auto &s : flst)\n\t\tcout << s << \" \";\n\tcout << endl;\n\n\tInsert(flst, \"M\", \"U\");\n\tfor (const auto &s : flst)\n\t\tcout << s << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_29.md",
    "content": "> 练习9.29：假定vec包含25个元素，那么vec.resize(100)会做什么？如果接下来调用vec.resize(10)会做什么？\n\n---\n\n首先会将容器大小调整为100个元素，新加的元素全部值初始化；然后将容器大小调整为10，这会删除掉后面的90个元素。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_30.md",
    "content": "> 练习9.30：接受单个参数的resize版本对元素类型有什么限制（如果有的话）？\n\n---\n\n如果元素类型是类类型，那么它必须提供一个默认构造函数，以便进行值初始化。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_31.cpp",
    "content": "// 练习9.31：第316页中删除偶数值元素并复制奇数值元素的程序不能用于list或\n// forward_list。为什么？修改程序，使之也能用于这些类型。\n\n// list的迭代器不支持+=2这样的算术运算\n// forward_list并未定义insert和erase成员\n\n#include <iostream>\n#include <list>\n#include <forward_list>\n\nusing namespace std;\n\n// for list\nvoid func1()\n{\n\tlist<int> lst = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};\n\n\tauto beg = lst.begin();\n\twhile (beg != lst.end()) {\n\t\tif (*beg % 2) {\n\t\t\tbeg = lst.insert(beg, *beg);\n\t\t\t++beg, ++beg; // 向前移动迭代器，跳过当前元素以及插入到它之前的元素\n\t\t}\n\t\telse\n\t\t\tbeg = lst.erase(beg);\n\t}\n\n\tfor (auto i : lst)\n\t\tcout << i << \" \";\n\tcout << endl;\n}\n\n// for forward_list\nvoid func2()\n{\n\tforward_list<int> flst = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};\n\n\tauto beg = flst.begin();\n\tauto pre = flst.before_begin();\n\twhile (beg != flst.end()) {\n\t\tif (*beg % 2) {\n\t\t\tbeg = flst.insert_after(beg, *beg);\n\t\t\tpre = beg++;\n\t\t}\n\t\telse {\n\t\t\tbeg = flst.erase_after(pre);\n\t\t}\n\t}\n\n\tfor (auto i : flst)\n\t\tcout << i << \" \";\n\tcout << endl;\n}\n\nint main()\n{\n\tfunc1();\n\tfunc2();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_32.md",
    "content": "> 练习9.32：在第316页的程序中，向下面语句这样调用insert是否合法？如果不合法，为什么？\n\n```\niter = vi.insert(iter, *iter++);\n```\n\n---\n\n不合法，因为参数的求值顺序不能确定，如果*iter++先被执行了，那么iter就已经移动到下一个位置了，但也可能不先被执行。因此插入的位置难以确定。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_33.cpp",
    "content": "// 练习9.33：在本节最后一个例子中，如果不将insert的结果赋予begin，将会发生什么？\n// 编写程序，去掉此赋值语句，验证你的答案。\n\n// begin将失效，继续使用的结果是灾难性的。因为在begin之前插入了元素，其后的迭代器\n// （包括begin）都将失效。\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> v = {1, 2, 3};\n\tauto begin = v.begin();\n\n\twhile (begin != v.end()) {\n\t\t++begin;\n\t\tv.insert(begin,42);\n\t\t++begin;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_34.cpp",
    "content": "// 练习9.34：假定vi是一个保存int的容器，其中有偶数值也有奇数值，分析下面循环\n// 的行为，然后编写程序验证你的分析是否正确。\n\n/*\n * iter = vi.begin();\n * while (iter != vi.end())\n * \tif (*iter % 2)\n * \t\titer = vi.insert(iter, *iter);\n * \t++iter;\n */\n\n// 程序的目的就是复制一下奇数的值，但代码里语法都错了，而且会死循环。因此自己\n// 实现一个。\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> vi{1, 4, 5, 7, 8, 9};\n\n\tauto iter = vi.begin();\n\twhile (iter != vi.end()) {\n\t\tif (*iter % 2) {\n\t\t\titer = vi.insert(iter, *iter);\n\t\t\titer += 2;\n\t\t}\n\t\telse\n\t\t\t++iter;\n\t}\n\n\tfor (auto i : vi)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_35.md",
    "content": "> 练习9.35：解释一个vector的capacity和size有何区别。\n\n---\n\nsize表示容器已经保存的元素的数目；\n\ncapacity表示在不分配新的内存空间的前提下，它最多可以保存多少元素。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_36.md",
    "content": "> 练习9.36：一个容器的capacity可能小于它的size吗？\n\n---\n\n不可能。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_37.md",
    "content": "> 练习9.37：为什么list或array没有capacity成员函数？\n\n---\n\n为什么vector需要？vector需要兼顾性能，如果每添加一个元素，就执行一次内存分配和释放操作，性能会很慢。为了避免这种代价，vector会预留一些空间备用，可以用来保存更多的元素，capacity就是用来返回当前可用空间的函数。\n\n但是list的空间不是连续存储的，不需要有预留空间。\n\narray的空间不能改变，因此也没有必要有预留空间。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_38.cpp",
    "content": "// 练习9.38：编写程序，探究在你的标准库实现中，vector是如何增长的。\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> v;\n\n\t// GCC，capacity需要扩张时，每次都翻倍增长\n\tfor (int i = 0; i < 10; ++i) {\n\t\tv.push_back(i);\n\t\tcout << \"vector size: \" << v.size()\n\t\t     << \" capacity: \" << v.capacity() << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_39.md",
    "content": "> 练习9.39：解释下面程序片段做了什么？\n\n```\nvector<string> svec;\nsvec.reserve(1024);\nstring word;\nwhile (cin >> word)\n\tsvec.push_back(word);\nsvec.resize(svec.size() + svec.size()/2);\n```\n\n---\n\n```\nvector<string> svec;\t\t// 定义一个空的vector，存放string对象\nsvec.reserve(1024);\t\t\t// 将vector的capacity设置为1024，可能更大\nstring word;\t\t\t\t// 定义一个string变量\nwhile (cin >> word)\t\t\t// 循环读取字符串，然后将其存入容器，如果容器的capacity不足了，\n\tsvec.push_back(word);\t// 会扩张capacity\nsvec.resize(svec.size() + svec.size()/2);\t// 增加当前容器元素数量一般的元素，此时capacity可能\n\t\t\t\t\t\t\t\t\t\t\t// 不会增长，也可能会增长\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_40.cpp",
    "content": "// 练习9.40：如果上一题中的程序读入了256个词，在resize之后容器的capacity可能是多少？\n// 如果读入了512个，1000个或1048个词呢？\n\n#include <iostream>\n#include <vector>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tvector<string> svec;\n\tsvec.reserve(1024);\n\tstring word;\n\n\t/*\n\twhile (cin >> word)\n\t\tsvec.push_back(word);\n\t*/\n\n\t// 换一种模拟方法\n\tstatic constexpr int count = 1000;\n\tfor (int i = 0; i < count; ++i) {\n\t\tsvec.push_back(\"hello\");\n\t}\n\n\tsvec.resize(svec.size() + svec.size()/2);\n\tcout << \"capacity: \" << svec.capacity() << endl;\n\n\t// 256: 1024\n\t// 512: 1024\n\t// 1000: 2048 GCC上是2000\n\t// 1048: 2048\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_41.cpp",
    "content": "// 练习9.41：编写程序，从一个vector<char>初始化一个string。\n\n#include <iostream>\n#include <vector>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tvector<char> cvec = {'h', 'e', 'l', 'l', 'o'};\n\tstring s(cvec.begin(), cvec.end());\n\n\tcout << s << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_42.md",
    "content": "> 练习9.42：假定你希望每次读取一个字符存入string中，而且知道至少需要读取100个字符，应该如何提高程序的性能？\n\n---\n\n使用reserve(200)预留足够的空间。\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_43.cpp",
    "content": "// 练习9.43：编写一个函数，接受三个string参数s、oldVal和newVal。使用迭代器\n// 及insert和erase函数将s中所有oldVal替换为newVal。测试你的程序，用\n// 它替换通用的简写形式，如，将\"tho\"替换为\"though\"，将\"thru\"替换为\"through\"。\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nvoid replace(string &s, const string &oldVal, const string &newVal)\n{\n\tauto beg = s.begin();\n\t\n\twhile (beg != s.end() && s.end() - beg >= (int)oldVal.length()) {\n\t\tstring test_string(beg, beg + oldVal.length()); // 测试string\n\t\t// 可删除\n\t\tif (test_string == oldVal) {\n\t\t\tbeg = s.erase(beg, beg + oldVal.length());\n\n\t\t\tfor (auto c : newVal) {\n\t\t\t\tbeg = s.insert(beg, c);\n\t\t\t\t++beg;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\t// 测试下一个\n\t\t\t++beg;\n\t\t}\n\t}\n}\n\nint main()\n{\n\tstring s = \"hello tho thru\";\n\tcout << s << endl;\n\n\treplace(s, \"tho\", \"though\");\n\tcout << s << endl;\n\n\treplace(s, \"thru\", \"through\");\n\tcout << s << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_44.cpp",
    "content": "// 练习9.44：重写上一题的函数，这次使用一个下标和replace。\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nvoid replace(string &s, const string &oldVal, const string &newVal)\n{\n\tstring::size_type pos = 0;\n\n\twhile (pos + oldVal.length() <= s.length() ) {\n\t\tstring test_str = s.substr(pos, oldVal.length());\n\n\t\tif (test_str == oldVal) {\n\t\t\ts.replace(pos, oldVal.length(), newVal);\n\t\t\tpos += newVal.length();\n\t\t}\n\t\telse {\n\t\t\t++pos;\n\t\t}\n\t}\n}\n\nint main()\n{\n\tstring s = \"hello tho thru\";\n\tcout << s << endl;\n\n\treplace(s, \"tho\", \"though\");\n\tcout << s << endl;\n\n\treplace(s, \"thru\", \"through\");\n\tcout << s << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_45.cpp",
    "content": "// 练习9.45：编写一个函数，接受一个表示名字的string参数和两个分别表示前缀（如\n// Mr.或Ms.）和后缀（如Jr.或III）的字符串。使用迭代器及insert和append函数将\n// 前缀和后缀添加到给定的名字中，将生成新string返回。\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nstring func(const string &s, const string &prefix, const string &suffix)\n{\n\tstring str = s;\n\tstr.insert(str.begin(), prefix.begin(), prefix.end());\n\tstr.append(suffix.begin(), suffix.end());\n\n\treturn str;\n}\n\nint main()\n{\n\tstring s = \"Joe\";\n\n\tcout << func(s, \"Mr.\", \"III\") << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_46.cpp",
    "content": "// 练习9.46：重写上一题的函数，这次使用位置和长度来管理string，并只使用insert。\n\n#include <iostream>\n\nusing namespace std;\n\nstring func(const string &s, const string &prefix, const string &suffix)\n{\n\tstring str = s;\n\tstr.insert(0, prefix);\n\tstr.insert(str.size(), suffix);\n\n\treturn str;\n}\n\nint main()\n{\n\tstring s = \"Joe\";\n\n\tcout << func(s, \"Mr.\", \"III\") << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_47.cpp",
    "content": "// 练习9.47：编写程序，首先查找string\"ab2c3d7R4E6\"中的每个数字字符，然后查\n// 找其中每个字母字符。编写两个版本的程序，第一个要使用find_first_of，第二个\n// 要使用find_fisrt_not_of。\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nstring str = \"ab2c3d7R4E6\";\nstring numbers = \"0123456789\";\nstring alphas = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\nvoid find_num()\n{\n\tstring::size_type pos = 0;\n\tcout << \"numbers:\" << endl;\n\twhile ((pos = str.find_first_of(numbers, pos)) != string::npos) {\n\t\tcout << pos << \": \" << str[pos] << endl;\n\t\t++pos;\n\t}\n\n\tpos = 0;\n\tcout << \"alphas:\" << endl;\n\twhile ((pos = str.find_first_of(alphas, pos)) != string::npos) {\n\t\tcout << pos << \": \" << str[pos] << endl;\n\t\t++pos;\n\t}\n}\n\nvoid find_alpha()\n{\n\tstring::size_type pos = 0;\n\tcout << \"alphas:\" << endl;\n\twhile ((pos = str.find_first_not_of(numbers, pos)) != string::npos) {\n\t\tcout << pos << \": \" << str[pos] << endl;\n\t\t++pos;\n\t}\n\n\tpos = 0;\n\tcout << \"numbers:\" << endl;\n\twhile ((pos = str.find_first_not_of(alphas, pos)) != string::npos) {\n\t\tcout << pos << \": \" << str[pos] << endl;\n\t\t++pos;\n\t}\n}\n\nint main()\n{\n\t//find_num();\n\tfind_alpha();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_48.cpp",
    "content": "// 练习9.48：假定name和numbers的定义如325页所示，numbers.find(name)返回\n// 什么？\n\n// string::npos\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tstring numbers = \"0123456789\";\n\tstring name = \"r2d2\";\n\n\tif (string::npos == numbers.find(name))\n\t\tcout << \"it's npos\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_49.cpp",
    "content": "// 练习9.49：如果一个字母延伸到中线之上，如d或f，则称其有上出头部分(ascender)。\n// 如果一个字母延伸到中线之下，如p或q，则称其有下出头部分(descender)。编写程序，\n// 读入一个单词文件，输出最长的即不包含上出头部分，也不包含下出头部分的单词。\n\n#include <iostream>\n#include <string>\n#include <fstream>\n\nusing namespace std;\n\nint main()\n{\n\tifstream in(\"./data/letter.txt\");\n\tif (!in) {\n\t\tcerr << \"file open failed\" << endl;\n\t\treturn -1;\n\t}\n\n\tstring word;\n\tstring longest_word;\n\twhile (in >> word) {\n\t\tif (word.find_first_not_of(\"aceimnorsuvwxz\") == string::npos && word.size() > longest_word.size())\n\t\t\tlongest_word = word;\n\t}\n\n\tcout << longest_word << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_50.cpp",
    "content": "// 练习9.50：编写程序处理一个vector<string>，其元素表示整数值。计算vector\n// 中所有元素之和。修改程序，使之计算表示浮点值得string之和。\n\n#include <iostream>\n#include <string>\n#include <vector>\n\nusing namespace std;\n\nvoid func1()\n{\n\tvector<string> v{\"1\", \"2\", \"3\", \"4\", \"5\"}; // 15\n\tint sum = 0;\n\tfor (auto i : v)\n\t\tsum += stoi(i);\n\t\n\tcout << sum << endl;\n}\n\nvoid func2()\n{\n\tvector<string> v{\"1.2\", \"2.2\", \"3.2\", \"4.2\", \"5.3\"}; // 16.1\n\tdouble sum = 0;\n\tfor (auto i : v)\n\t\tsum += stod(i);\n\t\n\tcout << sum << endl;\n}\n\nint main()\n{\n\t//func1();\n\tfunc2();\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_51.cpp",
    "content": "// 练习9.51：设计一个类，它有三个unsigned成员，分别表示年、月、日。为其编写\n// 构造函数，接受一个表示日期的string参数。你的构造函数应该能处理不同数据格式，\n// 如January 1, 1990、1/1/1990、Jan 1 1990等。\n\n#include <iostream>\n#include <string>\n#include <vector>\n\nusing namespace std;\n\nclass Date\n{\npublic:\n\tDate() = default;\n\tDate(const string &d);\n\n\tvoid Print()\n\t{\n\t\tcout << \"year: \" << year << endl;\n\t\tcout << \"month: \" << month << endl;\n\t\tcout << \"day:\" << day << endl;\n\t}\n\nprivate:\n\tunsigned year = 0;\n\tunsigned month = 0;\n\tunsigned day = 0;\n};\n\nDate::Date(const string &d)\n{\n\tstatic string numbers = \"0123456789\";\n\tstatic vector<string> months_long = {\n\t\t\"January\", \"February\", \"March\", \"April\", \"May\", \"June\",\n\t\t\"July\", \"August\", \"September\", \"October\", \"November\",\n\t\t\"December\"\n\t};\n\n\tstatic vector<string> months_short = {\n\t\t\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\",\n\t\t\"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\",\n\t\t\"Dec\"\n\t};\n\n\t// Januery 1, 1990\n\tif (d.find(\",\") != string::npos) {\n\t\tfor (size_t i = 0; i < months_long.size(); ++i) {\n\t\t\tif (d.find(months_long[i]) != string::npos) {\n\t\t\t\tmonth = i + 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tauto day_pos = d.find_first_of(numbers);\n\t\tauto mark_pos = d.find(\",\");\n\t\tday = stoi(d.substr(day_pos, mark_pos - day_pos));\n\t\tyear = stoi(d.substr(mark_pos + 1));\n\t\t\n\t\treturn;\n\t}\n\t\n\t// 1/1/1990\n\tif (d.find(\"/\") != string::npos)\n\t{\n\t\tauto first_mark_pos = d.find(\"/\");\n\t\tday = stoi(d.substr(0, first_mark_pos));\n\t\tauto last_mark_pos = d.rfind(\"/\");\n\t\tmonth = stoi(d.substr(first_mark_pos + 1, last_mark_pos - first_mark_pos - 1));\n\t\tyear = stoi(d.substr(last_mark_pos + 1));\n\n\t\treturn;\n\t}\n\t\n\t// Jan 1 1990\n\t{\n\t\tfor (size_t i = 0; i < months_short.size(); ++i) {\n\t\t\tif (d.find(months_short[i]) != string::npos) {\n\t\t\t\tmonth = i + 1;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tauto first_mark_pos = d.find(\" \");\n\t\tauto last_mark_pos = d.rfind(\" \");\n\t\tday = stoi(d.substr(first_mark_pos + 1, last_mark_pos - first_mark_pos - 1));\n\t\tyear = stoi(d.substr(last_mark_pos + 1));\n\t}\n\t\n}\n\nint main(int argc, char *argv[])\n{\n\tif (argc != 2) {\n\t\tcerr << \"Please enter the date, example: \\\"January 1, 1990\\\"\" << endl;\n\t\treturn 1;\n\t}\n\n\tDate date(argv[1]);\n\tdate.Print();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch09_Sequential_Containers/exercise_9_52.cpp",
    "content": "// 练习9.52：使用stack处理括号化的表达式。当你看到一个左括号，将其记录下来。\n// 当你在一个左括号之后看到一个右括号，从stack中pop对象，直到遇到左括号，将\n// 左括号也一起弹出栈。然后将一个值（括号内的运算结果）push到栈中，表示一个\n// 括号化的（子）表达式处理完毕，被其运算结果所替代。\n\n#include <iostream>\n#include <string>\n#include <stack>\n\nusing namespace std;\n\nint main()\n{\n\tstring expr = \"This is (Mooophy(awesome)((((wooooooooo))))) and (ocxs) over\";\n\tchar repl = '#';\n\tint seen = 0;\n\n\tstack<char> stk;\n\tfor (auto c : expr) {\n\t\tstk.push(c);\n\t\tif (c == '(') ++seen;\n\t\tif (seen && c == ')') {\n\t\t\twhile (stk.top() != '(')\n\t\t\t\tstk.pop();\n\t\t\tstk.pop();\n\t\t\tstk.push(repl);\n\t\t\t--seen;\n\t\t}\n\t}\n\n\t// test\n\tstring output;\n\twhile (!stk.empty()) {\n\t\toutput.insert(output.begin(), stk.top());\n\t\tstk.pop();\n\t}\n\tcout << output << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tif [ \"$1\" != \"\" ]; then\n\t\tif [ \"$1\" != \"clear\" ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcpp_file=$1\n\t\t\texe_file=${cpp_file%%.cpp*}\n\t\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\t\t\treturn $?\n\t\tfi\n\tfi\n\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\trm -f out1 out2\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/example_back_inserter.cpp",
    "content": "// example: 介绍back_inserter(p341)\n\n#include <iostream>\n#include <iterator>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> vec; // 空向量\n\tauto it = back_inserter(vec); // 通过它赋值会将元素添加到vec中\n\t*it = 42; // vec中现在有一个元素，值为42\n\n\tfor(auto i : vec)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/example_istream_iterator.cpp",
    "content": "// example: istream_iterator操作（p359）\n\n#include <iostream>\n#include <iterator>\n#include <vector>\n\nusing namespace std;\n\nvoid test1()\n{\n\tistream_iterator<int> int_it(cin);\t// 从cin读取int\n\tistream_iterator<int> int_eof;\t\t// 尾后迭代器\n\tvector<int> vec;\n\n\twhile (int_it != int_eof)\n\t\t// 后置递增运算符读取流，返回迭代器的旧值\n\t\t// 解引用迭代器，获得从流读取的前一个值\n\t\tvec.push_back(*int_it++);\n\n\tfor (auto i : vec)\n\t\tcout << i << \" \";\n\tcout << endl;\n}\n\n// 使用 istream_iterator 快速地读取元素入容器\nvoid test2()\n{\n\tvector<int> vec {istream_iterator<int>(cin), istream_iterator<int>()};\n\n\tfor (auto i : vec)\n\t\tcout << i << \" \";\n\tcout << endl;\n}\n\nint main()\n{\n\t//test1();\n\ttest2();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/example_lambda.cpp",
    "content": "// example: 使用lambda案例（p349）\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n\nusing namespace std;\n\nvoid biggest(vector<string> &words, vector<string>::size_type sz)\n{\n\t// 按长度排序\n\tstable_sort(words.begin(), words.end(), [](const string &a, const string &b)\n\t\t\t\t{ return a.size() < b.size(); });\n\n\t// 获取一个迭代器，指向第一个满足size() >= sz的元素\n\tauto wc = find_if(words.begin(), words.end(), [sz](const string &a)\n\t\t\t\t\t\t{ return a.size() >= sz; });\n\t// 计算满足size >= sz的元素的数目\n\tauto count = words.end() - wc;\n\tcout << count << endl;\n\n\t// 打印长度大于等于sz的单词\n\tfor_each(wc, words.end(), [](const string &s) { cout << s << \" \"; });\n\tcout << endl;\n}\n\nint main()\n{\n\tvector<string> words = { \"hello\", \"father\", \"my\", \"ops\", \"yours\", \"everything\" };\n\tvector<string>::size_type sz = 5;\n\n\tbiggest(words, sz);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/example_ostream_iterator.cpp",
    "content": "// example: ostream_iterator操作（p361）\n\n#include <iostream>\n#include <vector>\n#include <iterator>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> vec{1, 2, 3, 4};\n\tostream_iterator<int> out_iter(cout, \" \");\n\tfor (auto i : vec)\n\t\t*out_iter++ = i;\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/example_stable_partition.cpp",
    "content": "// example: stable_partition，将序列分为两组，满足谓词的放在前面一组\n\n#include <algorithm>\n#include <vector>\n#include <iostream>\n\nint main()\n{\n\tstd::vector<int> vec{3, 5, 1, 100, 283, 9, 111, 8, 654, 4};\n\n\t// 从小到大排序，但大于等于100的放在前面\n\t\n\tstd::sort(vec.begin(), vec.end());\n\tstd::stable_partition(vec.begin(), vec.end(), [](int element) { return element >= 100; });\n\n\tfor (auto i : vec)\n\t{\n\t\tstd::cout << i << \" \";\n\t}\n\tstd::cout << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/example_stable_sort.cpp",
    "content": "// example: stable_sort（p345）\n// 另见：http://zh.cppreference.com/w/cpp/algorithm/stable_sort\n\n#include <algorithm>\n#include <iostream>\n#include <string>\n#include <vector>\n\nstruct Employee {\n\tEmployee(int age, std::string name) : age(age), name(name) {}\n\n\tint age;\n\tstd::string name; // 不参与比较\n};\n\n// 年龄小的放前面\nbool operator<(const Employee &lhs, const Employee &rhs) {\n\treturn lhs.age < rhs.age;\n}\n\nint main()\n{\n\tstd::vector<Employee> v = {\n\t\tEmployee(108, \"Zaphod\"),\n\t\tEmployee(32, \"Brthur\"),\n\t\tEmployee(32, \"Arthur\"),\n\t\tEmployee(32, \"Crthur\"),\n\t\tEmployee(108, \"Ford\"),\n\t};\n\n\t// 保证相等元素的（原有）顺序\n\tstd::stable_sort(v.begin(), v.end());\n\n\tfor (const auto &e : v) {\n\t\tstd::cout << e.age << \", \" << e.name << \"\\n\";\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/example_unique.cpp",
    "content": "// example: sort和unique函数，消除重复单词（p343）\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n\nusing namespace std;\n\nvoid elimDups(vector<string> &words)\n{\n\t// 按字典序排序words，以便查找重复单词\n\tsort(words.begin(), words.end());\n\t// unique重排输出范围，使得每个单词只出现一次\n\t// 排列在范围的前部，返回指向不重复区域之后一个位置的迭代器\n\tauto end_unique = unique(words.begin(), words.end());\n\t// 使用向量操作erase删除重复单词\n\twords.erase(end_unique, words.end());\n}\n\nint main()\n{\n\tvector<string> words = {\"the\", \"quick\", \"red\", \"fox\", \"jumps\", \"over\", \"the\", \"slow\", \"red\", \"turtle\"};\n\telimDups(words);\n\n\tfor (const auto &w : words)\n\t\tcout << w << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_01.cpp",
    "content": "// 练习10.1：头文件algorithm中定义了一个名为count的函数，它类似find，接受\n// 一对迭代器和一个值作为参数。count返回给定值在序列中出现的次数。编写程序，\n// 读取int序列存入vector中，打印有多少个元素的值等于给定值。\n\n#include <iostream>\n#include <vector>\n#include <algorithm>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> ivec;\n\tint i;\n\twhile (cin >> i)\n\t\tivec.push_back(i);\n\n\tint n = 5;\n\tcout << count(ivec.begin(), ivec.end(), n) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_02.cpp",
    "content": "// 练习10.2：重做上一题，但读取string序列存入list中。\n\n#include <iostream>\n#include <list>\n#include <string>\n#include <algorithm>\n\nusing namespace std;\n\nint main()\n{\n\tlist<string> slist;\n\tstring word;\n\twhile (cin >> word)\n\t\tslist.push_back(word);\n\n\tstring v = \"hi\";\n\tcout << count(slist.begin(), slist.end(), v) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_03.cpp",
    "content": "// 练习10.3：用accumulate求一个vector<int>中的元素之和。\n\n#include <iostream>\n#include <vector>\n#include <algorithm>\n#include <numeric>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> ivec = {1, 2, 3, 4, 5};\n\n\tcout << accumulate(ivec.cbegin(), ivec.cend(), 0) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_04.cpp",
    "content": "// 练习10.4：假定v是一个vector<double>，那么调用accumulate(v.cbegin(), v.cend(), 0)\n// 有何错误（如果存在的话）？\n\n// 没有编译错误，因为double可以转换成int。但结果却会丢失精度。\n\n#include <iostream>\n#include <vector>\n#include <algorithm>\n#include <numeric>\n\nusing namespace std;\n\nint main()\n{\n\tvector<double> ivec = {1.1, 2.2, 3.3, 4.4, 5.5};\n\n\tcout << accumulate(ivec.cbegin(), ivec.cend(), 0) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_05.md",
    "content": "> 练习10.5：在本节对名册（roster）调用equal的例子中，如果两个名册中保存的都是C风格字符串而不是string，会发生什么？\n\n---\n\n那么将会做指针的比较，而不是字符串内容的比较，显然是错误的。\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_06.cpp",
    "content": "// 练习10.6：编写程序，使用fill_n将一个序列中的int值都设置为0。\n\n#include <iostream>\n#include <vector>\n#include <algorithm>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> ivec{1, 2, 3, 4, 5};\n\n\tfill_n(ivec.begin(), ivec.size(), 0);\n\n\tfor (auto i : ivec)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_07.md",
    "content": "> 练习10.7：下面程序是否有错误？如果有，请改正。\n\n(a)\n```\nvector<int> vec; list<int> lst; int i;\nwhile (cin >> i)\n\tlst.push_back(i);\ncopy(lst.cbegin(), lst.cend(), vec.begin());\n                               ^\n\t\t\t\t\t\t\t   错误，vec为空，应该改写为back_inserter(vec)\n```\n\n(b)\n```\nvector<int> vec;\nvec.reserve(10);\nfill_n(vec.begin(), 10, 0);\n       ^\n\t   错误，vec为空，应该改写为back_inserter(vec)\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_08.md",
    "content": "> 练习10.8：本节提到过，标准库算法不会改变它们所操作的容器的大小。为什么使用back_insertor不会使这一断言失效？\n\n---\n\n当我们通过一个插入迭代器赋值时，一个与赋值号右侧值相等的元素被添加到容器中。\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_09.cpp",
    "content": "// 练习10.9：实现你自己的elimDups。测试你的程序，分别在读取输入后、调用unique\n// 后以及调用erase后打印vector的内容。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n\nusing namespace std;\n\nvoid print(vector<string> &vec)\n{\n\tfor (const auto &i : vec)\n\t\tcout << i << \" \";\n\tcout << endl;\n}\n\nvoid elimDups(vector<string> &words)\n{\n\t// 按字典序排序words，以便查找重复单词\n\tsort(words.begin(), words.end());\n\t// unique重排输出范围，使得每个单词只出现一次\n\t// 排列在范围的前部，返回指向不重复区域之后一个位置的迭代器\n\tauto end_unique = unique(words.begin(), words.end());\n\tprint(words);\n\t// 使用向量操作erase删除重复单词\n\twords.erase(end_unique, words.end());\n\tprint(words);\n}\n\nint main()\n{\n\tvector<string> words = {\"the\", \"quick\", \"red\", \"fox\", \"jumps\", \"over\", \"the\", \"slow\", \"red\", \"turtle\"};\n\tprint(words);\n\telimDups(words);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_10.md",
    "content": "> 练习10.10：你认为算法不改变容器大小的原因是什么？\n\n---\n\n算法只操作迭代器，一般迭代器没有办法改变容器的大小。\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_11.cpp",
    "content": "// 练习10.11：编写程序，使用stable_sort和isShorter将传递给你的elimDups\n// 版本的vector排序。打印vector的内容，验证你的程序的正确性。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n\nusing namespace std;\n\n// 比较函数，用来按长度排序单词\nbool isShorter(const string &s1, const string &s2)\n{\n\treturn s1.size() < s2.size();\n}\n\nvoid elimDups(vector<string> &words)\n{\n\t// 按字典序排序words，以便查找重复单词\n\tsort(words.begin(), words.end());\n\t// unique重排输出范围，使得每个单词只出现一次\n\t// 排列在范围的前部，返回指向不重复区域之后一个位置的迭代器\n\tauto end_unique = unique(words.begin(), words.end());\n\t// 使用向量操作erase删除重复单词\n\twords.erase(end_unique, words.end());\n\n\tstable_sort(words.begin(), words.end(), isShorter);\n}\n\nint main()\n{\n\tvector<string> words = {\"the\", \"quick\", \"red\", \"fox\", \"jumps\", \"over\", \"the\", \"slow\", \"red\", \"turtle\"};\n\telimDups(words);\n\n\tfor (const auto &w : words)\n\t\tcout << w << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_12.cpp",
    "content": "// 练习10.12：编写名为compareIsbn的函数，比较两个Sales_data对象的isbn()\n// 成员。使用这个函数排序一个保存Sales_data对象的vector。\n\n// ./exercise_10_12 < ../ch07_Classes/data/book_sales\n\n#include <iostream>\n#include <vector>\n#include <algorithm>\n\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\nbool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)\n{\n\treturn lhs.isbn() < rhs.isbn();\n}\n\nint main()\n{\n\tvector<Sales_data> vec;\n\tSales_data sd;\n\twhile (read(cin, sd))\n\t\tvec.push_back(sd);\n\n\tsort(vec.begin(), vec.end(), compareIsbn);\t\n\n\tfor (const auto &item : vec)\n\t\tprint(cout, item) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_13.cpp",
    "content": "// 练习10.13：标准库定义了名为partition的算法，它接受一个谓词，对容器内容进行\n// 划分，使得谓词为true的值会排在容器的前半部分，而使谓词为false的值会排在后\n// 半部分。算法返回一个迭代器，指向最后一个谓词为true的元素之后的位置。编写函数，\n// 接受一个string，返回一个bool值，指出string是否有5个或更多字符。使用此函数\n// 划分words。打印出长度大于等于5的元素。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n\nusing namespace std;\n\nbool charMoreThan(const string &str)\n{\n\treturn str.size() >= 5;\n}\n\nint main()\n{\n\tvector<string> words = {\"the\", \"quick\", \"red\", \"fox\", \"jumps\", \"over\", \"the\", \"slow\", \"red\", \"turtle\"};\n\n\tauto end = partition(words.begin(), words.end(), charMoreThan);\n\n\tfor (auto beg = words.begin(); beg != end; ++beg)\n\t{\n\t\tcout << *beg << \" \";\n\t}\n\tcout << endl;\n\t\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_14.cpp",
    "content": "// 练习10.14：编写一个lambda，接受两个int，返回它们的和。\n\n#include <iostream>\n\nusing namespace std;\n\nint main()\n{\n\tint a = 5, b = 4;\n\n\tauto func = [](int a, int b) -> int { return a + b; };\n\n\tcout << func(a, b) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_15.cpp",
    "content": "// 练习10.15：编写一个lambda，捕获它所在函数的int，并接受一个int参数。lambda应该\n// 返回捕获的int和int参数的和。\n\n#include <iostream>\n\nusing namespace std;\n\nint main()\n{\n\tint base = 10;\n\n\tauto func = [base](int a) -> int { return base + a; };\n\n\tcout << func(5) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_16.cpp",
    "content": "// 练习10.16：使用lambda编写你自己版本的biggies。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n\nusing namespace std;\n\nvoid biggest(vector<string> &words, vector<string>::size_type sz)\n{\n\t// 按长度排序\n\tstable_sort(words.begin(), words.end(), [](const string &a, const string &b)\n\t\t\t\t{ return a.size() < b.size(); });\n\n\t// 获取一个迭代器，指向第一个满足size() >= sz的元素\n\tauto wc = find_if(words.begin(), words.end(), [sz](const string &a)\n\t\t\t\t\t\t{ return a.size() >= sz; });\n\t// 计算满足size >= sz的元素的数目\n\tauto count = words.end() - wc;\n\tcout << count << endl;\n\n\t// 打印长度大于等于sz的单词\n\tfor_each(wc, words.end(), [](const string &s) { cout << s << \" \"; });\n\tcout << endl;\n}\n\nint main()\n{\n\tvector<string> words = { \"hello\", \"father\", \"my\", \"ops\", \"yours\", \"everything\" };\n\tvector<string>::size_type sz = 5;\n\n\tbiggest(words, sz);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_17.cpp",
    "content": "// 练习10.17：重写10.3.1节练习10.12（第345页）的程序，在对sort的调用中使用lambda\n// 来替代函数compareIsbn。\n\n// 练习10.12：编写名为compareIsbn的函数，比较两个Sales_data对象的isbn()\n// 成员。使用这个函数排序一个保存Sales_data对象的vector。\n\n// ./exercise_10_17 < ../ch07_Classes/data/book_sales\n\n#include <iostream>\n#include <vector>\n#include <algorithm>\n\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\nint main()\n{\n\tvector<Sales_data> vec;\n\tSales_data sd;\n\twhile (read(cin, sd))\n\t\tvec.push_back(sd);\n\n\tsort(vec.begin(), vec.end(), [](const Sales_data &lhs, const Sales_data &rhs)\n\t\t{ return lhs.isbn() < rhs.isbn(); });\n\n\tfor (const auto &item : vec)\n\t\tprint(cout, item) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_18.cpp",
    "content": "// 练习10.18：重写biggies，用partition代替find_if。我们在10.3.1节练习10.13\n// （第345页）中介绍了partition算法。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n\nusing namespace std;\n\nvoid biggest(vector<string> &words, vector<string>::size_type sz)\n{\n\t// 获取一个迭代器，指向第一个满足size() >= sz的元素\n\tauto wc = partition(words.begin(), words.end(), [sz](const string &a)\n\t\t\t\t\t\t{ return a.size() < sz; } );\n\n\t// 计算满足size >= sz的元素的数目\n\tauto count = words.end() - wc;\n\tcout << count << endl;\n\n\t// 打印长度大于等于sz的单词\n\tfor_each(wc, words.end(), [](const string &s) { cout << s << \" \"; });\n\tcout << endl;\n}\n\nint main()\n{\n\tvector<string> words = { \"hello\", \"father\", \"my\", \"ops\", \"yours\", \"everything\" };\n\tvector<string>::size_type sz = 5;\n\n\tbiggest(words, sz);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_19.cpp",
    "content": "// 练习10.19：用stable_partition重写前一题的程序，与stable_sort类似，在\n// 划分后的序列中维持原有元素的顺序。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n\nusing namespace std;\n\nvoid biggest(vector<string> &words, vector<string>::size_type sz)\n{\n\t// 获取一个迭代器，指向第一个满足size() >= sz的元素\n\tauto wc = stable_partition(words.begin(), words.end(), [sz](const string &a)\n\t\t\t\t\t\t{ return a.size() < sz; } );\n\n\t// 计算满足size >= sz的元素的数目\n\tauto count = words.end() - wc;\n\tcout << count << endl;\n\n\t// 打印长度大于等于sz的单词\n\tfor_each(wc, words.end(), [](const string &s) { cout << s << \" \"; });\n\tcout << endl;\n}\n\nint main()\n{\n\tvector<string> words = { \"hello\", \"father\", \"my\", \"ops\", \"yours\", \"everything\" };\n\tvector<string>::size_type sz = 5;\n\n\tbiggest(words, sz);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_20.cpp",
    "content": "// 练习10.20：标准库定义了一个名为count_if的算法。类似find_if，此函数接受\n// 一对迭代器，表示一个输入范围，还接受一个谓词，会对输入范围中每个元素执行。\n// count_if返回一个计数值，表示谓词有多少次为真。使用count_if重写我们程序中\n// 统计有多少单词长度超过6的部分。\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <algorithm>\n\nusing namespace std;\n\nint main()\n{\n\tvector<string> words = {\"hello\", \"programmer\", \"project\", \"mom\", \"daddy\"};\n\n\tauto cnt = count_if(words.begin(), words.end(), [](const string &w) { return w.size() > 6; });\n\n\tcout << cnt << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_21.cpp",
    "content": "// 练习10.21：编写一个lambda，捕获一个局部int变量，并递减变量值，直至它变为0。\n// 一旦变量变为0，再调用lambda应该不再递减变量。lambda应该返回一个bool值，指\n// 出捕获的变量是否为0。\n\n#include <iostream>\n\nusing namespace std;\n\nint main()\n{\n\tint n = 5;\n\tauto func = [&n]() -> bool { if (n > 0) --n; return 0 == n; };\n\n\twhile (!func())\n\t{\n\t\tcout << \"func called\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_22.cpp",
    "content": "// 练习10.22：重写统计长度小于等于6的单词数量的程序，使用函数代替lambda。\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <algorithm>\n#include <functional>\n\nusing namespace std;\nusing namespace std::placeholders;\n\nbool func(const string &w, size_t n)\n{\n\treturn w.size() > n;\n}\n\nint main()\n{\n\tvector<string> words = {\"hello\", \"programmer\", \"project\", \"mom\", \"daddy\"};\n\n\tauto cnt = count_if(words.begin(), words.end(), bind(func, _1, 6));\n\n\tcout << cnt << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_23.md",
    "content": "> 练习10.23：bind接受几个参数？\n\n---\n\n假如bind的第一个参数，一个可调用对象的参数的个数为n，那么bind的参数数量为：n + 1\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_24.cpp",
    "content": "// 练习10.24：给定一个string，使用bind和check_size在一个int的vector中\n// 查找第一个大于string长度的值。\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <algorithm>\n#include <functional>\n\nusing namespace std;\nusing namespace std::placeholders;\n\nbool check_size(const string &s, string::size_type sz)\n{\n\treturn s.size() < sz;\n}\n\nint main()\n{\n\tstring s = \"www\";\n\tvector<int> v = {1, 2, 3, 4, 5};\n\n\tauto it = find_if(v.begin(), v.end(), bind(check_size, s, _1));\n\t\n\tif (it != v.end())\n\t\tcout << *it << endl;\n\telse\n\t\tcout << \"not find\" << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_25.cpp",
    "content": "// 练习10.25：在10.3.2节（第349页）的练习中，编写了一个使用partition的biggies\n// 版本。使用check_size和bind重写此函数。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n#include <functional>\n\nusing namespace std;\nusing namespace std::placeholders;\n\nbool check_size(const string &s, string::size_type sz)\n{\n\treturn s.size() >= sz;\n}\n\nvoid biggest(vector<string> &words, vector<string>::size_type sz)\n{\n\t// 获取一个迭代器，指向第一个满足size() >= sz的元素\n\tauto wc = partition(words.begin(), words.end(), bind(check_size, _1, sz) );\n\n\t// 计算满足size >= sz的元素的数目\n\tauto count = wc - words.begin();\n\tcout << count << endl;\n\n\t// 打印长度大于等于sz的单词\n\tfor_each(words.begin(), wc, [](const string &s) { cout << s << \" \"; });\n\tcout << endl;\n}\n\nint main()\n{\n\tvector<string> words = { \"hello\", \"father\", \"my\", \"ops\", \"yours\", \"everything\" };\n\tvector<string>::size_type sz = 5;\n\n\tbiggest(words, sz);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_26.md",
    "content": "> 练习10.26：解释三种插入迭代器的不同之处。\n\n---\n\nback_inserter调用push_back把元素插入到容器尾部。\n\nfront_inserter调用push_front把元素插入到容器首部。\n\ninserter调用insert将元素插入到其绑定到的迭代器之前的位置。\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_27.cpp",
    "content": "//  练习10.27：除了unique（参见10.2.3节，第343页）之外，标准库还定义了名为\n//  unique_copy的函数，它接受第三个迭代器，表示拷贝不重复元素的目的位置。编写\n//  一个程序，使用unique_copy将一个vector中不重复的元素拷贝到一个初始为空的list中。\n\n#include <iostream>\n#include <vector>\n#include <list>\n#include <algorithm>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> v{1, 5, 3, 4, 3, 9, 5};\n\tlist<int> lst;\n\n\tsort(v.begin(), v.end());\n\tunique_copy(v.begin(), v.end(), back_inserter(lst));\n\n\tfor_each(lst.begin(), lst.end(), [](int i) { cout << i << \" \"; });\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_28.cpp",
    "content": "// 练习10.28：一个vector中保存1到9，将其拷贝到三个其他容器中。分别使用\n// inserter、back_inserter和front_inserter将元素添加到三个容器中。对每\n// 种inserter，估计输出序列是怎样的，运行程序验证你的估计是否正确。\n\n#include <iostream>\n#include <vector>\n#include <list>\n#include <algorithm>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};\n\n\tvector<int> v1, v2;\n\tlist<int> v3;\n\n\tcopy(v.begin(), v.end(), inserter(v1, v1.begin())); // 1 2 3 ...\n\tcopy(v.begin(), v.end(), back_inserter(v2)); \t\t// 1 2 3 ...\n\tcopy(v.begin(), v.end(), front_inserter(v3)); \t\t// 9 8 7 ...\n\n\tfor_each(v1.begin(), v1.end(), [](int i) { cout << i << \" \"; }); cout << endl;\n\tfor_each(v2.begin(), v2.end(), [](int i) { cout << i << \" \"; }); cout << endl;\n\tfor_each(v3.begin(), v3.end(), [](int i) { cout << i << \" \"; }); cout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_29.cpp",
    "content": "// 练习10.29：编写程序，使用流迭代器读取一个文本文件，存入一个vector中的string里。\n\n#include <iostream>\n#include <fstream>\n#include <iterator>\n#include <vector>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tfstream in(\"../data/some_words.txt\");\n\tif (!in) {\n\t\tcerr << \"open file fail\" << endl;\n\t\treturn 1;\n\t}\n\n\tistream_iterator<string> iter(in);\n\tistream_iterator<string> eof;\n\n\tvector<string> vec(iter, eof);\n\n\tfor (const auto &w : vec)\n\t\tcout << w << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_30.cpp",
    "content": "// 练习10.30：使用流迭代器、sort和copy从标准输入读取一个整数序列，将其排序，\n// 并将结果写到标准输出。\n\n#include <iostream>\n#include <iterator>\n#include <algorithm>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tistream_iterator<int> in_iter(cin), in_eof;\n\tostream_iterator<int> out_iter(cout, \" \");\n\tvector<int> v(in_iter, in_eof);\n\n\tsort(v.begin(), v.end());\n\tcopy(v.begin(), v.end(), out_iter);\n\tcout << endl;\t\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_31.cpp",
    "content": "// 练习10.31：修改前一题的程序，使其只打印不重复的元素。你的程序应使用\n// unique_copy（参见10.4.1节，第359页）。\n\n#include <iostream>\n#include <iterator>\n#include <algorithm>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tistream_iterator<int> in_iter(cin), in_eof;\n\tostream_iterator<int> out_iter(cout, \" \");\n\tvector<int> v(in_iter, in_eof);\n\n\tsort(v.begin(), v.end());\n\tunique_copy(v.begin(), v.end(), out_iter);\n\tcout << endl;\t\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_32.cpp",
    "content": "// 练习10.32：重写1.6节（第21页）的书店程序，使用一个vector保存交易记录，\n// 使用不同的算法完成处理。使用sort和10.3.1节（第345页）中的compareIsbn\n// 函数来排序交易记录，然后使用find和accumulate求和。\n\n/*\n * 练习1.25：借助网站上的Sales_item.h头文件，编译并运行本节给出的书店程序。\n */\n\n// ./exercise_10_32 < ../data/book_sales\n\n#include <iostream>\n#include <vector>\n#include <algorithm>\n#include <numeric>\n#include <iterator>\n\n#include \"../ch01_Getting_Started/Sales_item.h\"\n\nusing namespace std;\n\nint main()\n{\n\tistream_iterator<Sales_item> in_iter(cin), in_eof;\n\tvector<Sales_item> vec(in_iter, in_eof);\n\n\tsort(vec.begin(), vec.end(), [](const Sales_item &lhs, const Sales_item &rhs) { return lhs.isbn() < rhs.isbn(); });\n\n\tauto end = vec.begin();\n\twhile (end != vec.end()) {\n\t\tauto beg = end;\n\t\tend = find_if(beg, vec.end(), [beg](const Sales_item &item) { return beg->isbn() != item.isbn(); } );\n\t\t\n\t\tSales_item sum(beg->isbn());\n\t\tcout << accumulate(beg, end, sum) << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_33.cpp",
    "content": "// 练习10.33：编写程序，接受三个参数：一个输入文件和两个输出文件的文件名。输入\n// 文件保存的应该是整数。使用istream_iterator读取输入文件。使用ostream_iterator\n// 将奇数写入第一个输出文件，每个值之后都跟一个空格。将偶数写入第二个输出文件，\n// 每个值都独占一行。\n\n// ./exercise_10_33 ../data/numbers.txt out1 out2\n\n#include <iostream>\n#include <fstream>\n#include <iterator>\n#include <vector>\n#include <algorithm>\n\nusing namespace std;\n\nint main(int argc, char *argv[])\n{\n\tif (argc != 4) {\n\t\tcout << \"usage: ./exercise_10_33 in_file out_file1 out_file2\";\n\t\treturn 1;\n\t}\n\n\tifstream in(argv[1]);\n\tofstream out1(argv[2]), out2(argv[3]);\n\tif (!in || !out1 || !out2) {\n\t\tcout << \"open file fail\" << endl;\n\t\treturn 1;\n\t}\n\n\tistream_iterator<int> in_iter(in), in_eof;\n\tostream_iterator<int> out1_iter(out1, \" \"), out2_iter(out2, \" \");\n\tvector<int> v(in_iter, in_eof);\n\tcopy_if(v.begin(), v.end(), out1_iter, [](int i) { return i % 2 != 0; });\n\tcopy_if(v.begin(), v.end(), out2_iter, [](int i) { return i % 2 == 0; });\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_34.cpp",
    "content": "// 练习10.34：使用reverse_iterator逆序打印一个vector。\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> v{1, 2, 3, 4, 5};\n\n\tfor (auto it = v.crbegin(); it != v.crend(); ++it)\n\t\tcout << *it << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_35.cpp",
    "content": "// 练习10.35：使用普通迭代器逆序打印一个vector。\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> v{1, 2, 3, 4, 5};\n\n\tfor (auto it = v.cend(); it != v.cbegin();)\n\t\tcout << *--it << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_36.cpp",
    "content": "// 练习10.36：使用find在一个int的list中查找最后一个值为0的元素。\n\n#include <iostream>\n#include <list>\n#include <algorithm>\n\nusing namespace std;\n\nint main()\n{\n\tlist<int> lst{1, 0, 3, 0, 9};\n\n\tauto it = find(lst.crbegin(), lst.crend(), 0);\n\n\tif (it == lst.crend())\n\t\tcout << \"no find\" << endl;\n\telse {\n\t\tauto normal_it = it.base();\n\t\tif (normal_it != lst.end())\n\t\t\tcout << \"find, next is: \" << *it.base() << endl;\n\t\telse\n\t\t\tcout << \"find, it is last number\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_37.cpp",
    "content": "// 练习10.37：给定一个包含10个元素的vector，将位置3到7之间的元素按逆序拷贝\n// 到一个list中。\n\n#include <iostream>\n#include <vector>\n#include <list>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> v;\n\tfor (int i = 1; i <= 10; ++i)\n\t\tv.push_back(i);\n\n\tauto beg = v.crbegin() + 3;\n\tauto end = v.crbegin() + 8;\n\n\tlist<int> lst(beg, end);\n\tfor (auto i : lst)\n\t\tcout << i << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_38.md",
    "content": "> 练习10.38：列出5个迭代器类别，以及每类迭代器所支持的操作。\n\n---\n\n- 输入迭代器，可以读取序列中的元素。它支持的操作有：==, !=, ++, 解引用（只能出现在赋值运算符的右侧）, ->\n\n- 输出迭代器，只写而不读元素。它支持的操作有：++, 解引用（只出现在赋值运算符的左侧）\n\n- 前向迭代器，可以单向读写元素。它支持的操作有：所有的输入输出迭代器的操作，而且可以多次读写同一个元素\n\n- 双向迭代器，可以正向/反向读写序列中的元素。支持所有的前向迭代器的操作，还支持前置和后置递减运算符\n\n- 随机访问迭代器，提供在常量时间内访问序列中任意元素的能力，支持双向迭代器的所有功能，还支持：<, <=, >, >=, +, +=, -, -=, 下标运算符。\n\n详细内容见p366 - p377。\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_39.md",
    "content": "> 练习10.39：list的迭代器属于哪类？vector呢？\n\n---\n\nlist属于双向迭代器，vector属于随机访问迭代器。\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_40.md",
    "content": "> 练习10.40：你认为copy要求哪类迭代器？reverse和unique呢？\n\n---\n\n- copy，第一对迭代器要求输入迭代器，后面的迭代器要求输出迭代器。\n\n- reverse，要求双向迭代器\n\n- unique，要求前向迭代器\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_41.md",
    "content": "> 练习10.41：仅根据算法和参数的名字，描述下面每个标准库算法执行什么操作：\n\n```\nreplace(beg, end, old_val, new_val); // 将序列中的元素为old_val的替换为new_val\nreplace_if(beg, end, pred, new_val); // 将序列中的元素满足pred条件的替换为new_val\nreplace_copy(beg, end, dest, old_val, new_val); // 将序列中的old_val替换成new_val，存储到dest所起始的序列中，原序列不变\nreplace_copy_if(beg, end, dest, pred, new_val); // 将序列中的满足pred条件的元素替换成new_val，存储到dest所起始的序列中，原序列不变\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch10_Generic_Algorithms/exercise_10_42.cpp",
    "content": "// 练习10.42：使用list代替vector重新实现10.2.3节（第343页）中的去除重复\n// 单词的程序。\n\n#include <iostream>\n#include <string>\n#include <list>\n#include <algorithm>\n\nusing namespace std;\n\nvoid elimDups(list<string> &words)\n{\n\twords.sort();\n\twords.unique();\n}\n\nint main()\n{\n\tlist<string> words{\"the\", \"quick\", \"red\", \"fox\", \"jumps\", \"over\", \"the\", \"slow\", \"red\", \"turtle\"};\n\n\telimDups(words);\n\n\tfor (const auto &w : words)\n\t\tcout << w << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\trm -f out1 out2\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/data/test_rules.txt",
    "content": "where r u\ny dont u send me a pic\nk thk 18r\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/data/trans_rules.txt",
    "content": "brb be right back\nk okay?\ny why\nr are\nu you\npic picture\nthk thanks!\n18r later\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/example_init_multiset.cpp",
    "content": "// example: 初始化multiset(p377)\n\n#include <iostream>\n#include <set>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\t// 定义一个有20个元素的vector，保存0到9每个整数的两个拷贝\n\tvector<int> ivec;\n\tfor (vector<int>::size_type i = 0; i != 10; ++i) {\n\t\tivec.push_back(i);\n\t\tivec.push_back(i); // 每个数重复保存一次\n\t}\n\n\t// iset包含来自ivec的不重复元素；miset包含所有20个元素\n\tset<int> iset(ivec.cbegin(), ivec.cend());\n\tmultiset<int> miset(ivec.cbegin(), ivec.cend());\n\tcout << ivec.size() << endl;\t// 打印出20\n\tcout << iset.size() << endl;\t// 打印出10\n\tcout << miset.size() << endl;\t// 打印出20\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/example_make_pair.cpp",
    "content": "// example: 创建pair对象的函数\n\n#include <iostream>\n#include <utility> // pair类型定义在此头文件\n#include <string>\n#include <vector>\n\nusing namespace std;\n\npair<string, int>\nprocess(vector<string> &v)\n{\n\t// 处理v\n\tif (!v.empty())\n\t\treturn {v.back(), v.back().size()}; // 列表初始化\n\telse\n\t\treturn pair<string, int>();\t// 隐式构造返回值\n}\n\nint main()\n{\n\tvector<string> v{\"a\", \"b\", \"ccc\"};\n\tauto p = process(v);\n\n\tcout << p.first << \" \" << p.second << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/example_map.cpp",
    "content": "// example: 使用map（p375）\n\n#include <iostream>\n#include <string>\n#include <map>\n\nusing namespace std;\n\nint main()\n{\n\t// 统计每个单词在输入中出现的次数\n\tmap<string, size_t> word_count;\t// string到size_t的空map\n\tstring word;\n\twhile (cin >> word)\n\t\t++word_count[word];\t// 提取word的计数器并将其加1\n\t\n\tfor (const auto &w : word_count) // 对map中的每个元素\n\t\t// 打印结果\n\t\tcout << w.first << \" occurs time: \" << w.second << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/example_multimap_find.cpp",
    "content": "// 在multimap中查找元素(p389)\n\n#include <iostream>\n#include <map>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tmultimap<string, string> authors = {{\"Liudiwen\", \"CppNoob\"}, \n\t\t{\"Liudiwen\", \"GameDesignNoob\"}, \n\t\t{\"Liumiemie\", \"Smiling\"}};\n\n\tstring search_item(\"Liudiwen\");\t\t\t\t// 要查找的作者\n\tauto entries = authors.count(search_item);\t// 元素的数量\n\tauto iter = authors.find(search_item);\t\t// 此作者的第一本书\n\n\t// 用一个循环查找此作者的所有著作\n\twhile (entries) {\n\t\tcout << iter->second << endl;\t\t\t// 打印每个题目\n\t\t++iter;\t\t\t\t\t\t\t\t\t// 前进到下一本书\n\t\t--entries;\t\t\t\t\t\t\t\t// 记录已经打印了多少本书\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/example_set.cpp",
    "content": "// example: 使用set（p375）\n\n#include <iostream>\n#include <string>\n#include <set>\n#include <map>\n\nusing namespace std;\n\nint main()\n{\n\t// 统计每个单词在输入中出现的次数\n\tmap<string, size_t> word_count;\t// string到size_t的空map\n\tset<string> exclude = {\"The\", \"the\", \"And\", \"and\"};\n\n\tstring word;\n\twhile (cin >> word) {\n\t\t// 只统计不在exclude中的单词\n\t\tif (exclude.find(word) == exclude.end())\n\t\t\t++word_count[word];\t// 提取word的计数器并将其加1\n\t}\n\t\n\tfor (const auto &w : word_count) // 对map中的每个元素\n\t\t// 打印结果\n\t\tcout << w.first << \" occurs time: \" << w.second << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/example_unordered_set.cpp",
    "content": "// example: 无序容器对关键字的要求（p396）\n\n#include <iostream>\n#include <unordered_set>\n\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\nsize_t hasher(const Sales_data &sd)\n{\n\treturn hash<string>()(sd.isbn());\n}\n\nbool eqOp(const Sales_data &lhs, const Sales_data &rhs)\n{\n\treturn lhs.isbn() == rhs.isbn();\n}\n\nint main()\n{\n\tusing SD_multiset = unordered_multiset<Sales_data, decltype(hasher)*, decltype(eqOp)*>;\n\n\tSD_multiset bookstore(0, hasher, eqOp);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/example_word_transform.cpp",
    "content": "// example: 单词转换程序（p392）\n\n#include <iostream>\n#include <fstream>\n#include <map>\n#include <string>\n#include <stdexcept>\n#include <sstream>\n\nusing namespace std;\n\nmap<string, string> buildMap(ifstream &map_file)\n{\n\tmap<string, string> trans_map;\t// 保存转换规则\n\tstring key;\t\t// 要转换的单词\n\tstring value;\t// 替换后的内容\n\n\t// 读取第一个单词存入key中，行中剩余内容存入value\n\twhile (map_file >> key && getline(map_file, value))\n\t\tif (value.size() > 1) // 检查是否有转换规则\n\t\t\ttrans_map[key] = value.substr(1);\t// 跳过前导空格\n\t\telse\n\t\t\tthrow runtime_error(\"no rule for \" + key);\n\treturn trans_map;\n}\n\nconst string& transform(const string &s, const map<string, string> &m)\n{\n\t// 实际的转换工作；此部分是程序的核心\n\tauto map_it = m.find(s);\n\n\t// 如果单词在转换规则中map中\n\tif (map_it != m.cend())\n\t\treturn map_it->second;\t// 使用替换短语\n\telse\n\t\treturn s;\t\t\t\t// 否则返回原string\n}\n\nvoid word_transform(ifstream &map_file, ifstream &input)\n{\n\tauto trans_map = buildMap(map_file);\t// 保存转换规则\n\tstring text;\t\t\t\t\t\t\t// 保存输入中的每一行\n\twhile (getline(input, text)) {\t\t\t// 读取一行输入\n\t\tistringstream stream(text);\t\t\t// 读取每个单词\n\t\tstring word;\n\t\tbool firstword = true;\t\t\t\t// 控制是否打印空格\n\t\twhile (stream >> word) {\n\t\t\tif (firstword)\n\t\t\t\tfirstword = false;\n\t\t\telse\n\t\t\t\tcout << \" \";\t\t\t\t// 在单词间打印一个空格\n\t\t\t// transform返回它的第一个参数或其转换之后的形式\n\t\t\tcout << transform(word, trans_map); // 打印输出\n\t\t}\n\t\tcout << endl;\t\t\t\t\t\t// 完成一行的转换\n\t}\n}\n\nint main()\n{\n\tifstream rule_ifs(\"data/trans_rules.txt\");\n\tifstream input_ifs(\"data/test_rules.txt\");\n\tif (!rule_ifs || !input_ifs)\n\t{\n\t\tcerr << \"fail open fail\" << endl;\n\t\treturn 1;\n\t}\n\n\tword_transform(rule_ifs, input_ifs);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_01.md",
    "content": "练习11.1：描述map和vector的不同。\n\n---\n\nmap的元素是按照关键字来保存和访问，vector是按照其在容器中的位置来顺序保存和访问。\n\nmap的下标（关键字）不必是整数。\n\nmap中保存的元素是一个key-value对。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_02.md",
    "content": "练习11.2：分别给出最适合使用list, vector, deque, map以及set的例子。\n\n---\n\n- list: to-do list，经常会在任意位置添加、删除元素\n\n- vector: 保存需要用下标进行快速访问的元素\n\n- deque: message handle. FIFO\n\n- map: 字典\n\n- set: bad_checks\n\n参考：https://github.com/pezy/CppPrimer/tree/master/ch11\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_03.cpp",
    "content": "// 练习11.3：编写你自己的单词计数程序\n\n#include <iostream>\n#include <string>\n#include <map>\n\nusing namespace std;\n\nint main()\n{\n\t// 统计每个单词在输入中出现的次数\n\tmap<string, size_t> word_count;\t// string到size_t的空map\n\tstring word;\n\twhile (cin >> word)\n\t\t++word_count[word];\t// 提取word的计数器并将其加1\n\t\n\tfor (const auto &w : word_count) // 对map中的每个元素\n\t\t// 打印结果\n\t\tcout << w.first << \" occurs time: \" << w.second << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_04.cpp",
    "content": "// 练习11.4：扩展你的程序，忽略大小写和标点。例如，\"example.\", \"example,\"和\"Example\"\n// 应该递增相同的计数器。\n\n#include <cctype>\n\n#include <iostream>\n#include <string>\n#include <map>\n\nusing namespace std;\n\nvoid fix(string &word)\n{\n\tstring s;\n\tfor (auto c : word)\n\t{\n\t\tif (!ispunct(c))\n\t\t{\n\t\t\ts += tolower(c);\n\t\t}\n\t}\n\tword = s;\n}\n\nint main()\n{\n\t// 统计每个单词在输入中出现的次数\n\tmap<string, size_t> word_count;\t// string到size_t的空map\n\tstring word;\n\twhile (cin >> word) {\n\t\tfix(word);\n\t\t++word_count[word];\t// 提取word的计数器并将其加1\n\t}\n\t\n\tfor (const auto &w : word_count) // 对map中的每个元素\n\t\t// 打印结果\n\t\tcout << w.first << \" occurs time: \" << w.second << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_05.md",
    "content": "练习11.5：解释map和set的区别。你如何选择使用哪个？\n\n---\n\nmap除了保存关键字，还会保存其对应的键值。而set只保存关键字。\n\n所以如果不需要保存键值，就可以使用set。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_06.md",
    "content": "练习11.6：解释set和list的区别。你如何选择使用哪个？\n\n---\n\nlist是顺序容器，可以保存相同的元素。而set是关联容器，按照关键字来排序，且只能存储唯一的关键字。如果需要在中间插入、删除元素，并存储可能相同的元素，可使用list。如果想保存唯一的元素，可以使用set。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_07.cpp",
    "content": "// 练习11.7：定义一个map，关键字是家庭的姓，值是一个vector，保存家中孩子们的名。\n// 编写代码，实现添加新的家庭以及向已有家庭中添加新的孩子。\n\n#include <iostream>\n#include <map>\n#include <vector>\n#include <string>\n\nusing namespace std;\n\nusing Family = map<string, vector<string>>;\n\nvoid add_family(const string &family_name, Family &family_map)\n{\n\tfamily_map[family_name].clear();\n}\n\nvoid add_child(const string &family_name, const string &name, Family &family_map)\n{\n\tauto it = family_map.find(family_name);\n\tif (it != family_map.end())\n\t{\n\t\tit->second.push_back(name);\n\t}\n}\n\nint main()\n{\n\tFamily family_map;\n\n\tadd_family(\"Liu\", family_map);\n\tadd_family(\"Li\", family_map);\n\n\tadd_child(\"Liu\", \"Diwen\", family_map);\n\tadd_child(\"Liu\", \"Miemie\", family_map);\n\tadd_child(\"Li\", \"Meili\", family_map);\n\n\tfor (const auto &pair : family_map)\n\t{\n\t\tconst auto &family_name = pair.first;\n\t\tcout << \"family: \" << family_name << endl;\n\t\tfor (const auto &name : pair.second)\n\t\t{\n\t\t\tcout << name << endl;\n\t\t}\n\t\tcout << \"---------\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_08.cpp",
    "content": "// 练习11.8：编写一个程序，在一个vector而不是一个set中保存不重复的单词。使用\n// set的优点是什么？\n\n// 用set代码更简单，执行效率更高\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n\nusing namespace std;\n\nint main()\n{\n\tvector<string> v;\n\tstring word;\n\n\twhile (cin >> word)\n\t{\n\t\tif (find(v.begin(), v.end(), word) == v.end())\n\t\t{\n\t\t\tv.push_back(word);\n\t\t}\n\t}\n\n\tfor (const auto &w : v)\n\t{\n\t\tcout << w << \" \";\n\t}\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_09.cpp",
    "content": "// 练习11.9：定义一个map，将单词与一个行号的list关联，list中保存的是单词所出现的行号。\n\n#include <iostream>\n#include <map>\n#include <string>\n#include <list>\n#include <sstream>\n\nusing namespace std;\n\nusing WordLine = map<string, list<int>>;\n\nint main()\n{\n\tWordLine word_line;\n\tstring line;\n\n\tint line_no = 1;\n\twhile (getline(cin, line))\n\t{\n\t\tistringstream iss(line);\n\t\tstring word;\n\n\t\twhile (iss >> word)\n\t\t{\n\t\t\tword_line[word].push_back(line_no);\n\t\t}\n\n\t\t++line_no;\n\t}\n\n\tfor (const auto &pair : word_line)\n\t{\n\t\tcout << pair.first << \":\";\n\t\tfor (auto l : pair.second)\n\t\t{\n\t\t\tcout << \" \" << l;\n\t\t}\n\t\tcout << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_10.md",
    "content": "练习11.10：可以定义一个vector<int>::iterator到int的map吗？\nlist<int>::iterator到int的map呢？对于两种情况，如果不能，解释为什么。\n\n---\n\nvector版本的可以，list的不能。因为list的迭代器不支持<运算符。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_11.cpp",
    "content": "// 练习11.11：不使用decltype重新定义bookstore\n\n#include <iostream>\n#include <set>\n\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\ntypedef bool (*compare_func)(const Sales_data &lhs, const Sales_data &rhs);\n\nbool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)\n{\n\treturn lhs.isbn() < rhs.isbn();\n}\n\nint main()\n{\n\tmultiset<Sales_data, compare_func> bookstore(compareIsbn);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_12.cpp",
    "content": "// 练习11.12：编写程序，读入string和int的序列，将每个string和int存入一个\n// pair中，pair保存在一个vector中。\n\n#include <iostream>\n#include <utility>\n#include <string>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tvector<pair<string, int>> v;\n\tstring w;\n\tint n;\n\n\twhile (cin >> w >> n)\n\t{\n\t\tv.push_back({w, n});\n\t}\n\n\tfor (const auto &p : v)\n\t{\n\t\tcout << p.first << \"****\" << p.second << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_13.cpp",
    "content": "// 练习11.13：在上一题的程序中，至少有三种创建pair的方法。编写此程序的三\n// 个版本，分别采用不同的方法创建pair。解释你认为哪种形式最易于编写和理解，\n// 为什么？\n\n// 练习11.12：编写程序，读入string和int的序列，将每个string和int存入一个\n// pair中，pair保存在一个vector中。\n\n#include <iostream>\n#include <utility>\n#include <string>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tvector<pair<string, int>> v;\n\tstring w;\n\tint n;\n\n\twhile (cin >> w >> n)\n\t{\n\t\t//v.push_back({w, n}); // 列表初始化\n\t\t//v.push_back(pair<string, int>(w, n)); // 默认构造函数\n\t\t//v.push_back(make_pair(w, n)); // 使用make_pair，可能更好，向下兼容，可读性好\n\t\tv.emplace_back(w, n); // 可以避免拷贝，效率更好\n\t}\n\n\tfor (const auto &p : v)\n\t{\n\t\tcout << p.first << \"****\" << p.second << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_14.cpp",
    "content": "// 练习11.14：扩展你在11.2.1节练习（第378页）中编写的孩子姓到名的map，添加\n// 一个pair的vector，保存孩子的名和生日\n\n// 练习11.7：定义一个map，关键字是家庭的姓，值是一个vector，保存家中孩子们的名。\n// 编写代码，实现添加新的家庭以及向已有家庭中添加新的孩子。\n\n#include <iostream>\n#include <map>\n#include <vector>\n#include <string>\n\nusing namespace std;\n\nusing Family = map<string, vector<pair<string, string>>>; // 姓 - （名，生日）\n\nvoid add_family(const string &family_name, Family &family_map)\n{\n\tfamily_map[family_name].clear();\n}\n\nvoid add_child(const string &family_name, \n\t\t\tconst string &name, const string &birthday, \n\t\t\tFamily &family_map)\n{\n\tauto it = family_map.find(family_name);\n\tif (it != family_map.end())\n\t{\n\t\tit->second.emplace_back(name, birthday);\n\t}\n}\n\nint main()\n{\n\tFamily family_map;\n\n\tadd_family(\"Liu\", family_map);\n\tadd_family(\"Li\", family_map);\n\n\tadd_child(\"Liu\", \"Diwen\", \"1990-9-16\", family_map);\n\tadd_child(\"Liu\", \"Miemie\", \"2015-9-19\", family_map);\n\tadd_child(\"Li\", \"Meili\", \"1987-5-24\", family_map);\n\n\tfor (const auto &pair : family_map)\n\t{\n\t\tconst auto &family_name = pair.first;\n\t\tcout << \"family: \" << family_name << endl;\n\t\tfor (const auto &child : pair.second)\n\t\t{\n\t\t\tcout << child.first << \" \" << child.second << endl;\n\t\t}\n\t\tcout << \"---------\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_15.md",
    "content": "练习11.15：对一个int到vector<int>的map，其mapped_type、key_type和\nvalue_type分别是什么？\n\n---\n\nmap<int, vector<int>>\n\n- mapped_type: vector<int>\n\n- key_type: int\n\n- value_type: pair<const int, vector<int>>\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_16.md",
    "content": "练习11.16：使用一个map迭代器编写一个表达式，将一个值赋予一个元素。\n\n---\n\n```\nit->second = v;\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_17.cpp",
    "content": "// 练习11.17：假定c是一个string的multiset，v是一个string的vector，\n// 解释下面的调用。指出每个调用是否合法。\n\n#include <iostream>\n#include <string>\n#include <set>\n#include <vector>\n#include <algorithm>\n\nusing namespace std;\n\nint main()\n{\n\tvector<string> v{\"h\", \"a\", \"u\"};\n\tmultiset<string> c;\n\n\tcopy(v.begin(), v.end(), inserter(c, c.end())); // 合法\n\t//copy(v.begin(), v.end(), back_inserter(c)); // 非法，multiset没有push_back操作\n\tcopy(c.begin(), c.end(), inserter(v, v.end())); // 合法\n\tcopy(c.begin(), c.end(), back_inserter(v));\t// 合法\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_18.md",
    "content": "练习11.18：写出第382页循环中map_it的类型，不要使用auto或decltype。\n\n---\n\nmap<string, size_t>::const_iterator\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_19.cpp",
    "content": "// 练习11.19：定义一个变量，通过对11.2.2节（第378页）中的名为bookstore的multiset\n// 调用begin()来初始化这个变量。写出变量的类型，不要使用auto或decltype。\n\n// 练习11.11：不使用decltype重新定义bookstore\n\n#include <iostream>\n#include <set>\n\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\ntypedef bool (*compare_func)(const Sales_data &lhs, const Sales_data &rhs);\n\nbool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)\n{\n\treturn lhs.isbn() < rhs.isbn();\n}\n\nint main()\n{\n\tmultiset<Sales_data, compare_func> bookstore(compareIsbn);\n\n\tmultiset<Sales_data, compare_func>::iterator it = bookstore.begin();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_20.cpp",
    "content": "// 练习11.20：重写11.1节练习（第376页）的单词计数程序，使用insert代替下标\n// 操作。你认为哪个程序更容易编写和阅读？解释原因。\n\n// 使用下标的版本更容易编写和阅读，原因在于返回的ret类型过于复杂。\n\n#include <iostream>\n#include <string>\n#include <map>\n\nusing namespace std;\n\nint main()\n{\n\t// 统计每个单词在输入中出现的次数\n\tmap<string, size_t> word_count;\t// string到size_t的空map\n\tstring word;\n\twhile (cin >> word)\n\t{\n\t\tauto ret = word_count.insert({word, 1});\n\t\tif (!ret.second)\n\t\t{\n\t\t\t// 插入失败，则累加\n\t\t\t++ret.first->second;\n\t\t}\n\t}\n\t\n\tfor (const auto &w : word_count) // 对map中的每个元素\n\t\t// 打印结果\n\t\tcout << w.first << \" occurs time: \" << w.second << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_21.md",
    "content": "练习11.21：假定word_count是一个string到size_t的map，word是一个string到size_t的map，word是一个string，解释下面循环的作用：\n\n```\nwhile (cin >> word)\n\t++word_count.insert({word, 0}).first->second;\n```\n\n---\n\n统计word出现的数量信息，存放到word_count里。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_22.md",
    "content": "练习11.22：给定一个map<string, vector<int>>，对此容器的插入一个元素的insert版本，写出其参数类型和返回类型。\n\n---\n\n- 参数类型：pair<string, vector<int>>\n\n- 返回类型：pair<map<string, vector<int>>::iterator, bool>\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_23.cpp",
    "content": "// 练习11.23：11.2.1节练习（第378页）中的map以孩子的姓为关键字，保存他们的名的\n// vector，用multimap重写此map。\n\n// 练习11.7：定义一个map，关键字是家庭的姓，值是一个vector，保存家中孩子们的名。\n// 编写代码，实现添加新的家庭以及向已有家庭中添加新的孩子。\n\n#include <iostream>\n#include <map>\n#include <vector>\n#include <string>\n\nusing namespace std;\n\nusing Family = multimap<string, string>;\n\nvoid add_child(const string &family_name, const string &name, Family &family_map)\n{\n\tfamily_map.insert({family_name, name});\n}\n\nint main()\n{\n\tFamily family_map;\n\n\tadd_child(\"Liu\", \"Diwen\", family_map);\n\tadd_child(\"Liu\", \"Miemie\", family_map);\n\tadd_child(\"Li\", \"Meili\", family_map);\n\n\tfor (const auto &pair : family_map)\n\t{\n\t\tcout << \"family: \" << pair.first << \" \" << pair.second << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_24.md",
    "content": "练习11.24：下面的程序完成什么功能？\n\n```\nmap<int, int> m;\nm[0] = 1;\n```\n\n---\n\n1. 在m中查找key为0的元素\n\n2. 找不到，创建一个元素插入到m中，key为0，value为0（值初始化）\n\n3. 提取出新插入的元素，并将1赋给value\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_25.md",
    "content": "练习11.25：对比下面程序与上一题程序\n\n```\nvector<int> v;\nv[0] = 1;\n```\n\n---\n\n错误的代码，试图访问vector中一个不存在的元素，结果未定义。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_26.md",
    "content": "练习11.26：可以用什么类型来对一个map进行下标操作？下标运算符返回的类型是什么？请给出一个具体例子——即定义一个map，然后写出一个可以用来对map进行下标操作的类型以及下标运算符将会返回的类型。\n\n---\n\n只要这个类型可以作为map的关键字，就可以将其作为下标。\n\n下标运算符返回的类型是map中对应键的值(value_type对象)。\n\n```\nmap<string, size_t> word_cnt;\n\nstring key = \"hello\";\nsize_t cnt = word_cnt[key];\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_27.md",
    "content": "练习11.27：对于什么问题你会使用count来解决？什么时候你又会选择find呢？\n\n---\n\n如果我们所关心的只不过是一个特定的元素是否已在容器中，可能find是最佳选择。对于不允许重复关键字的容器，find和count可能没有什么区别。对于允许重复关键字的容器，count会统计有多少个元素有相同的关键字，这些相同的元素会相邻存储。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_28.cpp",
    "content": "// 练习11.28：对一个string到int的vector的map，定义并初始化一个变量来保存\n// 在其上调用find所返回的结果。\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <map>\n\nusing namespace std;\n\nint main()\n{\n\tmap<string, int> m;\n\n\tmap<string, int>::iterator it = m.find(\"a\");\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_29.md",
    "content": "练习11.29：如果给定的关键字不在容器中，upper_bound, lower_bound和equal_range分别会返回什么？\n\n---\n\nupper_bound和lower_bound返回相同的迭代器——指向一个不影响排序的关键字插入位置。\n\nequal_range中的两个迭代器都指向可以插入的位置。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_30.md",
    "content": "练习11.30：对于本节最后一个程序中的输出表达式，解释运算对象pos.first->second的含义。\n\n---\n\npos是一个pair，first一开始是找到的第一个不小于给定元素的迭代器，此迭代器指向的也是一个pair，second是作者的书名。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_31.cpp",
    "content": "// 练习11.31：编写程序，定义一个作者及其作品的multimap。使用find在multimap\n// 中查找一个元素并用erase删除它。确保你的程序在元素不在map中时也能正常运行。\n\n#include <iostream>\n#include <map>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tmultimap<string, string> authors = {\n\t\t{\"Liudiwen\", \"CppNoob\"},\n\t\t{\"Liudiwen\", \"GameNoob\"},\n\t\t{\"Lee\", \"Hehe\"}\n\t};\n\n\tconst char *key = \"Liudiwen\";\n\n\tauto it = authors.find(key);\n\n\twhile (it != authors.end())\n\t{\n\t\tit = authors.erase(it);\n\t}\n\n\tfor (const auto &pair : authors)\n\t{\n\t\tcout << pair.first << \" \" << pair.second << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_32.cpp",
    "content": "// 练习11.32：使用上一题定义的multimap编写一个程序，按字典序打印作者列表和\n// 他们的作品。\n\n// 练习11.31：编写程序，定义一个作者及其作品的multimap。使用find在multimap\n// 中查找一个元素并用erase删除它。确保你的程序在元素不在map中时也能正常运行。\n\n#include <iostream>\n#include <map>\n#include <set>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tmultimap<string, string> authors = {\n\t\t{\"Liudiwen\", \"GameNoob\"},\n\t\t{\"Liudiwen\", \"CppNoob\"},\n\t\t{\"Lee\", \"Hehe\"}\n\t};\n\n\tmap<string, set<string>> ordered_authors;\n\n\tfor (const auto &pair : authors)\n\t{\n\t\tordered_authors[pair.first].insert(pair.second);\n\t}\n\n\tfor (const auto &pair : ordered_authors)\n\t{\n\t\tcout << pair.first << \":\";\n\t\tfor (const auto &books : pair.second)\n\t\t{\n\t\t\tcout << \" \" << books;\n\t\t}\n\t\tcout << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_33.cpp",
    "content": "// 练习11.33：实现你自己版本的单词转换程序。\n\n#include <iostream>\n#include <fstream>\n#include <map>\n#include <string>\n#include <stdexcept>\n#include <sstream>\n\nusing namespace std;\n\nmap<string, string> buildMap(ifstream &map_file)\n{\n\tmap<string, string> trans_map;\t// 保存转换规则\n\tstring key;\t\t// 要转换的单词\n\tstring value;\t// 替换后的内容\n\n\t// 读取第一个单词存入key中，行中剩余内容存入value\n\twhile (map_file >> key && getline(map_file, value))\n\t\tif (value.size() > 1) // 检查是否有转换规则\n\t\t\ttrans_map[key] = value.substr(1);\t// 跳过前导空格\n\t\telse\n\t\t\tthrow runtime_error(\"no rule for \" + key);\n\treturn trans_map;\n}\n\nconst string& transform(const string &s, const map<string, string> &m)\n{\n\t// 实际的转换工作；此部分是程序的核心\n\tauto map_it = m.find(s);\n\n\t// 如果单词在转换规则中map中\n\tif (map_it != m.cend())\n\t\treturn map_it->second;\t// 使用替换短语\n\telse\n\t\treturn s;\t\t\t\t// 否则返回原string\n}\n\nvoid word_transform(ifstream &map_file, ifstream &input)\n{\n\tauto trans_map = buildMap(map_file);\t// 保存转换规则\n\tstring text;\t\t\t\t\t\t\t// 保存输入中的每一行\n\twhile (getline(input, text)) {\t\t\t// 读取一行输入\n\t\tistringstream stream(text);\t\t\t// 读取每个单词\n\t\tstring word;\n\t\tbool firstword = true;\t\t\t\t// 控制是否打印空格\n\t\twhile (stream >> word) {\n\t\t\tif (firstword)\n\t\t\t\tfirstword = false;\n\t\t\telse\n\t\t\t\tcout << \" \";\t\t\t\t// 在单词间打印一个空格\n\t\t\t// transform返回它的第一个参数或其转换之后的形式\n\t\t\tcout << transform(word, trans_map); // 打印输出\n\t\t}\n\t\tcout << endl;\t\t\t\t\t\t// 完成一行的转换\n\t}\n}\n\nint main()\n{\n\tifstream rule_ifs(\"data/trans_rules.txt\");\n\tifstream input_ifs(\"data/test_rules.txt\");\n\tif (!rule_ifs || !input_ifs)\n\t{\n\t\tcerr << \"fail open fail\" << endl;\n\t\treturn 1;\n\t}\n\n\tword_transform(rule_ifs, input_ifs);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_34.md",
    "content": "练习11.34：如果你将transform函数中的find替换为下标运算符，会发生什么情况？\n\n---\n\n当前使用的GCC版本会编译报错，因为传入的map是一个const的，无法使用下标运算符进行插入元素的操作。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_35.md",
    "content": "练习11.35：在buildMap中，如果进行如下改写，会有什么效果？\n\ntrans_map[key] = value.substr(1);\n\n改为trans_map.insert({key, value.substr(1)})\n\n---\n\n下标版本的，更新最后一次操作的元素。\n\n插入版本的，如果后续插入相同键值的元素，则不会改变原有的元素。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_36.md",
    "content": "练习11.36：我们的程序并没有检查输入文件的合法性。特别是，它假定转换规则文件中的规则都是有意义的。如果文件中的某一行包含一个关键字、一个空格，然后就结束了，会发生什么？预测程序的行为并进行验证，再与你的程序进行比较。\n\n---\n\n只有一个空格的版本没有影响。\n\n只有一个关键字的版本抛出异常并崩溃。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_37.md",
    "content": "练习11.37：一个无序容器与其有序版本相比有何优势？有序版本有何优势？\n\n---\n\n- 无序容器：通过哈希函数和==运算符来组织元素。在关键字类型的元素没有明显的序关系的情况下，无序容器是非常有用的。在某些应用中，维护元素的序代价非常高，此时无序容器也很有用。使用无序容器通常会有更好的性能。\n\n- 有序容器：通过<运算符来组织元素。定义起来比较简单。\n"
  },
  {
    "path": "codes/CppPrimer/ch11_Associative_Containers/exercise_11_38.cpp",
    "content": "// 练习11.38：用unordered_map重写单词计数程序（参见11.1节，第375页）和单词\n// 转换程序（参见11.3.6节，第391页）。\n\n#include <iostream>\n#include <string>\n#include <unordered_map>\n\nusing namespace std;\n\nint main()\n{\n\t// 统计每个单词在输入中出现的次数\n\tunordered_map<string, size_t> word_count;\t// string到size_t的空map\n\tstring word;\n\twhile (cin >> word)\n\t\t++word_count[word];\t// 提取word的计数器并将其加1\n\t\n\tfor (const auto &w : word_count) // 对map中的每个元素\n\t\t// 打印结果\n\t\tcout << w.first << \" occurs time: \" << w.second << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"exercise_12_20.cpp\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\trm -f out1 out2\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_StrBlob/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_StrBlob/StrBlob.cpp",
    "content": "#include \"StrBlob.h\"\n\nbool operator==(const StrBlob &rhs, const StrBlob &lhs)\n{\n\treturn *rhs.data == *lhs.data;\t\n}\n\nbool operator!=(const StrBlob &rhs, const StrBlob &lhs)\n{\n\treturn !(rhs == lhs);\n}\n\nbool operator<(const StrBlob &rhs, const StrBlob &lhs)\n{\n\treturn *rhs.data < *lhs.data;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_StrBlob/StrBlob.h",
    "content": "#ifndef __STR_BLOB_H__\n#define __STR_BLOB_H__\n\n#include <vector>\n#include <string>\n#include <initializer_list>\n#include <memory>\n\nclass StrBlob {\n\tfriend bool operator==(const StrBlob &rhs, const StrBlob &lhs);\n\tfriend bool operator!=(const StrBlob &rhs, const StrBlob &lhs);\n\tfriend bool operator<(const StrBlob &rhs, const StrBlob &lhs);\n\npublic:\n\ttypedef std::vector<std::string>::size_type size_type;\n\n\tStrBlob() : data(std::make_shared<std::vector<std::string>>()) {}\n\tStrBlob(std::initializer_list<std::string> il) :\n\t\t\tdata(std::make_shared<std::vector<std::string>>(il)) {}\n\n\tinline size_type size() const { return data->size(); }\n\tinline bool empty() const { return data->empty(); }\n\n\t// 添加和删除元素\n\tinline void push_back(const std::string &t) { data->push_back(t); }\n\tinline void push_back(std::string &&t) { data->push_back(std::move(t)); } // 右值引用版本，13.55练习\n\tinline void pop_back();\n\n\t// 元素访问\n\tinline std::string& front();\n\tinline std::string& back();\n\tinline const std::string& front() const;\n\tinline const std::string& back() const;\n\n\tstd::string& operator[](size_type i) { return (*data)[i]; } // 省略了check\n\tconst std::string& operator[](size_type i) const { return (*data)[i]; } // 省略了check\n\nprivate:\n\tstd::shared_ptr<std::vector<std::string>> data;\n\n\t// 如果data[i]不合法，抛出一个异常\n\tinline void check(size_type i, const std::string &msg) const;\n};\n\nvoid StrBlob::check(size_type i, const std::string &msg) const\n{\n\tif (i >= data->size())\n\t\tthrow std::out_of_range(msg);\n}\n\nstd::string& StrBlob::front()\n{\n\t// 如果vector为空，check会抛出一个异常\n\tcheck(0, \"front on empty StrBlob\");\n\treturn data->front();\n}\n\nstd::string& StrBlob::back()\n{\n\tcheck(0, \"back on empty StrBlob\");\n\treturn data->back();\n}\n\nvoid StrBlob::pop_back()\n{\n\tcheck(0, \"pop_back on empty StrBlob\");\n\treturn data->pop_back();\n}\n\nconst std::string& StrBlob::front() const\n{\n\tcheck(0, \"front on empty StrBlob\");\n\treturn data->front();\n}\n\nconst std::string& StrBlob::back() const\n{\n\tcheck(0, \"back on empty StrBlob\");\n\treturn data->back();\n}\n\n#endif // __STR_BLOB_H__\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_StrBlob/main.cpp",
    "content": "// example: 使用智能指针共享底层数据(p405)\n\n#include <iostream>\n\n#include \"StrBlob.h\"\n\nusing namespace std;\n\nint main()\n{\n\tStrBlob b1({\"a\", \"b\", \"c\"});\n\tStrBlob b2({\"a\", \"b\", \"c\"});\n\tStrBlob b3({\"b\"});\n\n\tif (b1 == b2)\n\t\tcout << \"b1 == b2\" << endl;\n\n\tif (b2 < b3)\n\t\tcout << \"b2 < b3\" << endl;\n\t\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_StrBlobPtr/ConstStrBlobPtr.cpp",
    "content": "#include \"ConstStrBlobPtr.h\"\n#include \"StrBlob.h\"\n\nConstStrBlobPtr::ConstStrBlobPtr(const StrBlob &a, size_t sz) : wptr(a.data), curr(sz) {}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_StrBlobPtr/ConstStrBlobPtr.h",
    "content": "#ifndef __CONST_STR_BLOB_PTR_H__\n#define __CONST_STR_BLOB_PTR_H__\n\n#include <memory>\n#include <vector>\n#include <string>\n\nclass StrBlob;\n\nclass ConstStrBlobPtr\n{\npublic:\n\tConstStrBlobPtr() : curr(0) {}\n\tConstStrBlobPtr(const StrBlob &a, size_t sz = 0); \n\n\tconst std::string &deref() const;\n\tConstStrBlobPtr &incr(); // 前缀递增\n\n\tconst std::string& operator*() const;\n\tconst std::string* operator->() const;\n\nprivate:\n\t// 若检查成功，check返回一个指向vector的shared_ptr\n\tstd::shared_ptr<std::vector<std::string>>\n\tcheck(std::size_t, const std::string&) const;\n\n\tstd::weak_ptr<std::vector<std::string>> wptr; // 保存一个weak_ptr，意味着底层vector可能会被销毁\n\tstd::size_t curr; // 在数组中的当前位置\n};\n\n\ninline std::shared_ptr<std::vector<std::string>>\nConstStrBlobPtr::check(std::size_t i, const std::string& msg) const\n{\n\tauto ret = wptr.lock();\t// vector还存在吗？\n\n\tif (!ret)\n\t{\n\t\tthrow std::runtime_error(\"unbound ConstStrBlobPtr\");\n\t}\n\n\tif (i >= ret->size())\n\t{\n\t\tthrow std::out_of_range(msg);\n\t}\n\n\treturn ret;\n}\n\ninline const std::string & ConstStrBlobPtr::deref() const\n{\n\tauto p = check(curr, \"deference past end\");\n\treturn (*p)[curr]; // (*p)是对象所指向的vector\n}\n\n// 前缀递增，返回递增后的对象的引用\ninline ConstStrBlobPtr & ConstStrBlobPtr::incr()\n{\n\tcheck(curr, \"increment past end of ConstStrBlobPtr\");\n\t++curr;\n\treturn *this;\n}\n\ninline const std::string& ConstStrBlobPtr::operator*() const\n{\n\tauto p = check(curr, \"deference past end\");\n\treturn (*p)[curr];\n}\n\ninline const std::string* ConstStrBlobPtr::operator->() const\n{\n\treturn & operator*();\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_StrBlobPtr/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_StrBlobPtr/StrBlob.cpp",
    "content": "#include \"StrBlob.h\"\n\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_StrBlobPtr/StrBlob.h",
    "content": "#ifndef __STR_BLOB_H__\n#define __STR_BLOB_H__\n\n#include <vector>\n#include <string>\n#include <initializer_list>\n#include <memory>\n\n#include \"StrBlobPtr.h\"\n#include \"ConstStrBlobPtr.h\"\n\nclass StrBlob {\n\tfriend class StrBlobPtr;\n\tfriend class ConstStrBlobPtr;\n\npublic:\n\ttypedef std::vector<std::string>::size_type size_type;\n\n\tStrBlob() : data(std::make_shared<std::vector<std::string>>()) {}\n\tStrBlob(std::initializer_list<std::string> il) :\n\t\t\tdata(std::make_shared<std::vector<std::string>>(il)) {}\n\n\tinline size_type size() const { return data->size(); }\n\tinline bool empty() const { return data->empty(); }\n\n\t// 添加和删除元素\n\tinline void push_back(const std::string &t) { data->push_back(t); }\n\tinline void pop_back();\n\n\t// 元素访问\n\tinline std::string& front();\n\tinline std::string& back();\n\tinline const std::string& front() const;\n\tinline const std::string& back() const;\n\n\t// 返回指向首元素和尾后元素的StrBlobPtr\n\tinline StrBlobPtr begin() { return StrBlobPtr(*this); }\n\tinline StrBlobPtr end() { return StrBlobPtr(*this, data->size()); }\n\n\tinline ConstStrBlobPtr begin() const { return ConstStrBlobPtr(*this); }\n\tinline ConstStrBlobPtr end() const { return ConstStrBlobPtr(*this, data->size()); }\n\nprivate:\n\tstd::shared_ptr<std::vector<std::string>> data;\n\n\t// 如果data[i]不合法，抛出一个异常\n\tinline void check(size_type i, const std::string &msg) const;\n};\n\nvoid StrBlob::check(size_type i, const std::string &msg) const\n{\n\tif (i >= data->size())\n\t\tthrow std::out_of_range(msg);\n}\n\nstd::string& StrBlob::front()\n{\n\t// 如果vector为空，check会抛出一个异常\n\tcheck(0, \"front on empty StrBlob\");\n\treturn data->front();\n}\n\nstd::string& StrBlob::back()\n{\n\tcheck(0, \"back on empty StrBlob\");\n\treturn data->back();\n}\n\nvoid StrBlob::pop_back()\n{\n\tcheck(0, \"pop_back on empty StrBlob\");\n\treturn data->pop_back();\n}\n\nconst std::string& StrBlob::front() const\n{\n\tcheck(0, \"front on empty StrBlob\");\n\treturn data->front();\n}\n\nconst std::string& StrBlob::back() const\n{\n\tcheck(0, \"back on empty StrBlob\");\n\treturn data->back();\n}\n\n#endif // __STR_BLOB_H__\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_StrBlobPtr/StrBlobPtr.cpp",
    "content": "#include \"StrBlobPtr.h\"\n#include \"StrBlob.h\"\n\nStrBlobPtr::StrBlobPtr(StrBlob &a, size_t sz) : wptr(a.data), curr(sz) {}\n\nbool operator==(const StrBlobPtr &rhs, const StrBlobPtr &lhs)\n{\n\treturn rhs.curr == lhs.curr;\n}\n\nbool operator!=(const StrBlobPtr &rhs, const StrBlobPtr &lhs)\n{\n\treturn !(rhs == lhs);\n}\n\nbool operator<(const StrBlobPtr &rhs, const StrBlobPtr &lhs)\n{\n\treturn rhs.curr < lhs.curr;\n}\n\nStrBlobPtr operator+(const StrBlobPtr &rhs, size_t n)\n{\n\tauto pos = rhs.curr + n;\n\trhs.check(pos, \"out of range\");\n\tStrBlobPtr ret = rhs;\n\tret.curr = pos;\n\treturn ret;\n}\n\nStrBlobPtr operator-(const StrBlobPtr &rhs, size_t n)\n{\n\tauto pos = rhs.curr - n;\n\trhs.check(pos, \"out of range\");\n\tStrBlobPtr ret = rhs;\n\tret.curr = pos;\n\treturn ret;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_StrBlobPtr/StrBlobPtr.h",
    "content": "#ifndef __STR_BLOB_PTR_H__\n#define __STR_BLOB_PTR_H__\n\n#include <memory>\n#include <vector>\n#include <string>\n\nclass StrBlob;\n\nclass StrBlobPtr\n{\n\tfriend bool operator==(const StrBlobPtr &rhs, const StrBlobPtr &lhs);\n\tfriend bool operator!=(const StrBlobPtr &rhs, const StrBlobPtr &lhs);\n\tfriend bool operator<(const StrBlobPtr &rhs, const StrBlobPtr &lhs);\n\tfriend StrBlobPtr operator+(const StrBlobPtr &rhs, size_t n);\n\tfriend StrBlobPtr operator-(const StrBlobPtr &rhs, size_t n);\n\npublic:\n\tStrBlobPtr() : curr(0) {}\n\tStrBlobPtr(StrBlob &a, size_t sz = 0); \n\n\tstd::string &deref() const;\n\tStrBlobPtr &incr(); // 前缀递增\n\n\tstd::string& operator[](size_t);\n\tconst std::string& operator[](size_t) const;\n\n\tStrBlobPtr& operator++();\n\tStrBlobPtr& operator--();\n\tStrBlobPtr operator++(int);\n\tStrBlobPtr operator--(int);\n\tstd::string& operator*() const;\n\tstd::string* operator->() const;\n\nprivate:\n\t// 若检查成功，check返回一个指向vector的shared_ptr\n\tstd::shared_ptr<std::vector<std::string>>\n\tcheck(std::size_t, const std::string&) const;\n\n\tstd::weak_ptr<std::vector<std::string>> wptr; // 保存一个weak_ptr，意味着底层vector可能会被销毁\n\tstd::size_t curr; // 在数组中的当前位置\n};\n\n\ninline std::shared_ptr<std::vector<std::string>>\nStrBlobPtr::check(std::size_t i, const std::string& msg) const\n{\n\tauto ret = wptr.lock();\t// vector还存在吗？\n\n\tif (!ret)\n\t{\n\t\tthrow std::runtime_error(\"unbound StrBlobPtr\");\n\t}\n\n\tif (i >= ret->size())\n\t{\n\t\tthrow std::out_of_range(msg);\n\t}\n\n\treturn ret;\n}\n\ninline std::string & StrBlobPtr::deref() const\n{\n\tauto p = check(curr, \"deference past end\");\n\treturn (*p)[curr]; // (*p)是对象所指向的vector\n}\n\ninline std::string& StrBlobPtr::operator[](size_t n)\n{\n\tauto p = check(n, \"out of range\");\n\treturn (*p)[n];\n}\n\ninline const std::string& StrBlobPtr::operator[](size_t n) const\n{\n\tauto p = check(n, \"out of range\");\n\treturn (*p)[n];\n}\n\ninline StrBlobPtr& StrBlobPtr::operator++()\n{\n\t// 如果curr已经指向了尾后位置，则无法递增它\n\tcheck(curr, \"increment past end of StrBlobPtr\");\n\t++curr;\t\t\t// 将curr在当前状态下向前移动一个元素\n\treturn *this;\n}\n\ninline StrBlobPtr& StrBlobPtr::operator--()\n{\n\t// 如果curr是0，则继续递减它将产生一个无效下标\n\t--curr;\t\t\t// 将curr在当前状态下向后移动一个元素\n\tcheck(curr, \"decrement past begin of StrBlobPtr\");\n\treturn *this;\n}\n\ninline StrBlobPtr StrBlobPtr::operator++(int)\n{\n\t// 此处无须检查有效性，调用前置递增运算时才需要检查\n\tStrBlobPtr ret = *this;\t\t// 记录当前的值\n\t++*this;\t\t\t\t\t// 向前移动一个元素，前置++需要检查递增的有效性\n\treturn ret;\t\t\t\t\t// 返回之前记录的状态\n}\n\ninline StrBlobPtr StrBlobPtr::operator--(int)\n{\n\t// 此处无需检查有效性，调用前置递减运算时才需要检查\n\tStrBlobPtr ret = *this;\t\t// 记录当前的值\n\t--*this;\t\t\t\t\t// 向后移动一个元素，前置--需要检查递减的有效性\n\treturn ret;\t\t\t\t\t// 返回之前记录的状态\n}\n\ninline std::string& StrBlobPtr::operator*() const\n{\n\tauto p = check(curr, \"dereference past end\");\n\treturn (*p)[curr];\n}\n\ninline std::string* StrBlobPtr::operator->() const\n{\n\t// 将实际工作委托给解引用运算符\n\treturn & this->operator*();\n}\n\n// 前缀递增，返回递增后的对象的引用\ninline StrBlobPtr & StrBlobPtr::incr()\n{\n\tcheck(curr, \"increment past end of StrBlobPtr\");\n\t++curr;\n\treturn *this;\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_StrBlobPtr/main.cpp",
    "content": "// example: weak_ptr的一个例子(p421)\n\n#include <iostream>\n\n#include \"StrBlob.h\"\n\nusing namespace std;\n\nvoid func1()\n{\n\tStrBlob a1 = {\"hi\", \"bye\", \"now\"};\n\tStrBlobPtr p(a1);\n\t*p = \"okay\";\n\tcout << p->size() << endl; // 4，okey长度是4\n}\n\nvoid func2()\n{\n\t// 调用const版本的front\n\tconst StrBlob a = {\"hi\", \"bye\", \"now\"};\n\tconst auto &s = a.front();\n\n\tcout << s << endl;\n}\n\nint main()\n{\n\t//func1();\n\tfunc2();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery/QueryResult.cpp",
    "content": "#include \"QueryResult.h\"\n\nusing namespace std;\n\nostream &print(ostream &os, const QueryResult &qr)\n{\n\t// 如果找到了单词，打印出现次数和所有出现的位置\n\tos << qr.sought << \" occurs \" << qr.lines->size() << \" time(s)\" << endl;\n\n\t// 打印单词出现的每一行\n\tfor (auto num : *qr.lines) // 对set中每个单词\n\t\t// 避免行号从0开始给用户带来的困惑\n\t\tos << \"\\t(line \" << num + 1 << \") \"\n\t\t\t<< *(qr.file->begin() + num) << endl;\n\treturn os;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery/QueryResult.h",
    "content": "#ifndef QUERY_RESULT_H\n#define QUERY_RESULT_H\n\n#include <iostream>\n#include <string>\n#include <set>\n#include <vector>\n#include <memory>\n\nclass QueryResult {\n\tfriend std::ostream& print(std::ostream&, const QueryResult&);\npublic:\n\tusing line_no = std::vector<std::string>::size_type;\n\tQueryResult(std::string s,\n\t\t\t\tstd::shared_ptr<std::set<line_no>> p,\n\t\t\t\tstd::shared_ptr<std::vector<std::string>> f):\n\t\tsought(s), lines(p), file(f) {}\n\n\tstd::set<line_no>::iterator begin() { return lines->begin(); }\n\tstd::set<line_no>::iterator end()   { return lines->end(); }\n\n\tstd::shared_ptr<std::vector<std::string>> get_file() const { return file; }\n\nprivate:\n\tstd::string sought;\t// 查询单词\n\tstd::shared_ptr<std::set<line_no>> lines;\t// 出现的行号\n\tstd::shared_ptr<std::vector<std::string>> file;\t// 输入文件\n};\n\nextern std::ostream& print(std::ostream&, const QueryResult&);\n\n#endif // QUERY_RESULT_H\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery/TextQuery.cpp",
    "content": "#include <sstream>\n\n#include \"TextQuery.h\"\n#include \"QueryResult.h\"\n\nusing namespace std;\n\nTextQuery::TextQuery(ifstream &is): file(new vector<string>)\n//TextQuery::TextQuery(ifstream &is): file(new vector<string>, DebugDelete())\n{\n\tstring text;\n\twhile (getline(is, text)) {\t\t// 对文件中的每一行\n\t\tfile->push_back(text);\t\t// 保存此行文本\n\t\tint n = file->size() - 1;\t// 当前行号\n\t\tistringstream line(text);\t// 将行文本分解为单词\n\t\tstring word;\n\t\twhile (line >> word) {\t\t// 对行中每个单词\n\t\t\t// 如果单词不在wm中，以之为下标在wm中添加一项\n\t\t\tauto &lines = wm[word];\t// lines是一个shared_ptr\n\t\t\tif (!lines)\t\t\t\t// 在我们第一次遇到这个单词时，此指针为空\n\t\t\t\tlines.reset(new set<line_no>);\t// 分配一个新的set\n\t\t\tlines->insert(n);\t\t// 将此行号插入set中\n\t\t}\n\t}\n}\n\nQueryResult\nTextQuery::query(const string &sought) const\n{\n\t// 如果未找到sought，我们将返回一个指向此set的指针\n\tstatic shared_ptr<set<line_no>> nodata(new set<line_no>);\n\n\t// 使用find而不是下标运算符来查找单词，避免将单词添加到wm中！\n\tauto loc = wm.find(sought);\n\tif (loc == wm.end())\n\t\treturn QueryResult(sought, nodata, file);\t// 未找到\n\telse\n\t\treturn QueryResult(sought, loc->second, file);\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery/TextQuery.h",
    "content": "#ifndef TEXT_QUERY_H\n#define TEXT_QUERY_H\n\n#include <vector>\n#include <string>\n#include <fstream>\n#include <memory>\n#include <map>\n#include <set>\n#include \"../../ch16_Templates_and_GenericProgramming/example_member_template/DebugDelete.h\" // 练习16.22（p597）\n\nclass QueryResult; // 为了定义函数query的返回类型，这个定义是必须的\nclass TextQuery {\npublic:\n\tusing line_no = std::vector<std::string>::size_type;\n\tTextQuery(std::ifstream&);\n\tQueryResult query(const std::string&) const;\n\nprivate:\n\tstd::shared_ptr<std::vector<std::string>> file; // 输入文件\n\t//std::shared_ptr<std::vector<std::string>, DebugDelete> file; // 输入文件\n\t\n\t// 每个单词到它所在的行号的集合的映射\n\tstd::map<std::string, std::shared_ptr<std::set<line_no>>> wm;\n};\n\n#endif // TEXT_QUERY_H\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery/main.cpp",
    "content": "// example: 文本查询程序(p430始)\n\n#include <fstream>\n#include <iostream>\n#include <string>\n\n#include \"TextQuery.h\"\n#include \"QueryResult.h\"\n\nusing namespace std;\n\nvoid runQueries(ifstream &infile)\n{\n\t// infile是一个ifstream，指向我们要处理的文件\n\tTextQuery tq(infile); // 保存文件并建立查询map\n\n\t// 与用户交互，提示用户输入要查询的单词，完成查询并打印结果\n\twhile (true)\n\t{\n\t\tcout << \"Enter word to look for, or q to quit: \";\n\t\tstring s;\n\t\t// 若遇到了文件尾或用户输入了q时循环终止\n\t\tif (!(cin >> s) || s == \"q\") break;\n\n\t\t// 指向查询并打印结果\n\t\tprint(cout, tq.query(s)) << endl;\n\t}\n}\n\nint main()\n{\n\tifstream infile(\"../../data/some_words.txt\");\n\tif (!infile)\n\t{\n\t\tcout << \"Cant open file\" << endl;\n\t\treturn 1;\n\t}\n\n\trunQueries(infile);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery2/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery2/QueryResult.cpp",
    "content": "#include \"QueryResult.h\"\n\nusing namespace std;\n\nostream &print(ostream &os, const TextQuery::QueryResult &qr)\n{\n\t// 如果找到了单词，打印出现次数和所有出现的位置\n\tos << qr.sought << \" occurs \" << qr.lines->size() << \" time(s)\" << endl;\n\n\t// 打印单词出现的每一行\n\tfor (auto num : *qr.lines) // 对set中每个单词\n\t\t// 避免行号从0开始给用户带来的困惑\n\t\tos << \"\\t(line \" << num + 1 << \") \"\n\t\t\t<< *(qr.file->begin() + num) << endl;\n\treturn os;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery2/QueryResult.h",
    "content": "#ifndef QUERY_RESULT_H\n#define QUERY_RESULT_H\n\n#include <iostream>\n#include <string>\n#include <set>\n#include <vector>\n#include <memory>\n#include \"TextQuery.h\"\n\nclass TextQuery::QueryResult {\n\tfriend std::ostream& print(std::ostream&, const QueryResult&);\npublic:\n\tQueryResult(std::string s,\n\t\t\t\tstd::shared_ptr<std::set<line_no>> p,\n\t\t\t\tstd::shared_ptr<std::vector<std::string>> f):\n\t\tsought(s), lines(p), file(f) {}\n\n\tstd::set<line_no>::iterator begin() { return lines->begin(); }\n\tstd::set<line_no>::iterator end()   { return lines->end(); }\n\n\tstd::shared_ptr<std::vector<std::string>> get_file() const { return file; }\n\nprivate:\n\tstd::string sought;\t// 查询单词\n\tstd::shared_ptr<std::set<line_no>> lines;\t// 出现的行号\n\tstd::shared_ptr<std::vector<std::string>> file;\t// 输入文件\n};\n\nextern std::ostream& print(std::ostream&, const TextQuery::QueryResult&);\n\n#endif // QUERY_RESULT_H\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery2/TextQuery.cpp",
    "content": "#include <sstream>\n\n#include \"TextQuery.h\"\n#include \"QueryResult.h\"\n\nusing namespace std;\n\nTextQuery::TextQuery(ifstream &is): file(new vector<string>)\n//TextQuery::TextQuery(ifstream &is): file(new vector<string>, DebugDelete())\n{\n\tstring text;\n\twhile (getline(is, text)) {\t\t// 对文件中的每一行\n\t\tfile->push_back(text);\t\t// 保存此行文本\n\t\tint n = file->size() - 1;\t// 当前行号\n\t\tistringstream line(text);\t// 将行文本分解为单词\n\t\tstring word;\n\t\twhile (line >> word) {\t\t// 对行中每个单词\n\t\t\t// 如果单词不在wm中，以之为下标在wm中添加一项\n\t\t\tauto &lines = wm[word];\t// lines是一个shared_ptr\n\t\t\tif (!lines)\t\t\t\t// 在我们第一次遇到这个单词时，此指针为空\n\t\t\t\tlines.reset(new set<line_no>);\t// 分配一个新的set\n\t\t\tlines->insert(n);\t\t// 将此行号插入set中\n\t\t}\n\t}\n}\n\nTextQuery::QueryResult\nTextQuery::query(const string &sought) const\n{\n\t// 如果未找到sought，我们将返回一个指向此set的指针\n\tstatic shared_ptr<set<line_no>> nodata(new set<line_no>);\n\n\t// 使用find而不是下标运算符来查找单词，避免将单词添加到wm中！\n\tauto loc = wm.find(sought);\n\tif (loc == wm.end())\n\t\treturn QueryResult(sought, nodata, file);\t// 未找到\n\telse\n\t\treturn QueryResult(sought, loc->second, file);\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery2/TextQuery.h",
    "content": "#ifndef TEXT_QUERY_H\n#define TEXT_QUERY_H\n\n#include <vector>\n#include <string>\n#include <fstream>\n#include <memory>\n#include <map>\n#include <set>\n\nclass TextQuery {\npublic:\n\tclass QueryResult; // 为了定义函数query的返回类型，这个定义是必须的\n\n\tusing line_no = std::vector<std::string>::size_type;\n\tTextQuery(std::ifstream&);\n\tQueryResult query(const std::string&) const;\n\nprivate:\n\tstd::shared_ptr<std::vector<std::string>> file; // 输入文件\n\t\n\t// 每个单词到它所在的行号的集合的映射\n\tstd::map<std::string, std::shared_ptr<std::set<line_no>>> wm;\n};\n\n#endif // TEXT_QUERY_H\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery2/main.cpp",
    "content": "// 练习19.20：将QueryResult类型嵌套在TextQuery中。\n\n#include <fstream>\n#include <iostream>\n#include <string>\n\n#include \"TextQuery.h\"\n#include \"QueryResult.h\"\n\nusing namespace std;\n\nvoid runQueries(ifstream &infile)\n{\n\t// infile是一个ifstream，指向我们要处理的文件\n\tTextQuery tq(infile); // 保存文件并建立查询map\n\n\t// 与用户交互，提示用户输入要查询的单词，完成查询并打印结果\n\twhile (true)\n\t{\n\t\tcout << \"Enter word to look for, or q to quit: \";\n\t\tstring s;\n\t\t// 若遇到了文件尾或用户输入了q时循环终止\n\t\tif (!(cin >> s) || s == \"q\") break;\n\n\t\t// 指向查询并打印结果\n\t\tprint(cout, tq.query(s)) << endl;\n\t}\n}\n\nint main()\n{\n\tifstream infile(\"../../data/some_words.txt\");\n\tif (!infile)\n\t{\n\t\tcout << \"Cant open file\" << endl;\n\t\treturn 1;\n\t}\n\n\trunQueries(infile);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery_use_StrVec/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery_use_StrVec/QueryResult.cpp",
    "content": "#include \"QueryResult.h\"\n\nusing namespace std;\n\nostream &print(ostream &os, const QueryResult &qr)\n{\n\t// 如果找到了单词，打印出现次数和所有出现的位置\n\tos << qr.sought << \" occurs \" << qr.lines->size() << \" time(s)\" << endl;\n\n\t// 打印单词出现的每一行\n\tfor (auto num : *qr.lines) // 对set中每个单词\n\t\t// 避免行号从0开始给用户带来的困惑\n\t\tos << \"\\t(line \" << num + 1 << \") \"\n\t\t\t<< *(qr.file->begin() + num) << endl;\n\treturn os;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery_use_StrVec/QueryResult.h",
    "content": "#ifndef QUERY_RESULT_H\n#define QUERY_RESULT_H\n\n#include <iostream>\n#include <string>\n#include <set>\n#include <vector>\n#include <memory>\n\n#include \"StrVec.h\"\n\nclass QueryResult {\n\tfriend std::ostream& print(std::ostream&, const QueryResult&);\npublic:\n\tusing line_no = size_t;\n\tQueryResult(std::string s,\n\t\t\t\tstd::shared_ptr<std::set<line_no>> p,\n\t\t\t\tstd::shared_ptr<StrVec> f):\n\t\tsought(s), lines(p), file(f) {}\n\n\tstd::set<line_no>::iterator begin() { return lines->begin(); }\n\tstd::set<line_no>::iterator end()   { return lines->end(); }\n\n\tstd::shared_ptr<StrVec> get_file() const { return file; }\n\nprivate:\n\tstd::string sought;\t// 查询单词\n\tstd::shared_ptr<std::set<line_no>> lines;\t// 出现的行号\n\tstd::shared_ptr<StrVec> file;\t// 输入文件\n};\n\nextern std::ostream& print(std::ostream&, const QueryResult&);\n\n#endif // QUERY_RESULT_H\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery_use_StrVec/StrVec.cpp",
    "content": "#include <memory>\n#include <utility>\n\n#include \"StrVec.h\"\n\nusing namespace std;\n\nallocator<std::string> StrVec::alloc;\n\nStrVec::StrVec(const initializer_list<string> &lst)\n{\n\tauto newdata = alloc_n_copy(lst.begin(), lst.end());\n\telements = newdata.first;\n\tfirst_free = cap = newdata.second;\n}\n\nStrVec::StrVec(const StrVec &s)\n{\n\t// 调用alloc_n_copy分配空间以容纳与s中一样多的元素\n\tauto newdata = alloc_n_copy(s.begin(), s.end());\n\telements = newdata.first;\n\tfirst_free = cap = newdata.second;\n}\n\nStrVec::~StrVec()\n{\n\tfree();\n}\n\nStrVec &StrVec::operator=(const StrVec &rhs)\n{\n\t// 调用alloc_n_copy分配内存，大小与rhs中元素占用空间一样多\n\tauto data = alloc_n_copy(rhs.begin(), rhs.end());\n\tfree();\n\telements = data.first;\n\tfirst_free = cap = data.second;\n\treturn *this;\n}\n\nvoid StrVec::push_back(const string &s)\n{\n\tchk_n_alloc(); // 确保有空间容纳新元素\n\t// 在first_free指向的元素中构造s的副本\n\talloc.construct(first_free++, s);\n}\n\npair<string*, string*>\nStrVec::alloc_n_copy(const string *b, const string *e)\n{\n\t// 分配空间保存给定范围中的元素\n\tauto data = alloc.allocate(e - b);\n\n\t// 初始化并返回一个pair，该pair由data和uninitialized_copy的返回值构成\n\treturn {data, uninitialized_copy(b, e, data)};\n}\n\nvoid StrVec::free()\n{\n\t// 不能传递给deallocate一个空指针，如果elements为0，函数什么也不做\n\tif (elements) {\n\t\t// 逆序销毁旧元素\n\t\tfor (auto p = first_free; p != elements; /* empty */)\n\t\t\talloc.destroy(--p);\n\t\talloc.deallocate(elements, cap - elements);\n\t}\n}\n\nvoid StrVec::reallocate()\n{\n\t// 我们将分配当前大小两倍的内存空间\n\tauto newcapacity = size() ? 2 * size() : 1;\n\t// 分配新内存\n\tauto newdata = alloc.allocate(newcapacity);\n\t// 将数据从旧内存移动到新内存\n\tauto dest = newdata;  // 指向新数组中下一个空闲位置\n\tauto elem = elements; // 指向旧数组中下一个元素\n\tfor (size_t i = 0; i != size(); ++i)\n\t\talloc.construct(dest++, std::move(*elem++));\n\tfree(); // 一旦我们移动完元素就释放旧内存空间\n\t// 更新我们的数据结构，执行新元素\n\telements = newdata;\n\tfirst_free = dest;\n\tcap = elements + newcapacity;\n}\n\nvoid StrVec::reserve(size_t n)\n{\n\tif (n <= capacity()) return;\n\n\tauto newdata = alloc.allocate(n);\n\n\t// copy old data\n\tauto dest = newdata;\n\tauto elem = elements;\n\tfor (size_t i = 0; i != size(); ++i)\n\t\talloc.construct(dest++, std::move(*elem++));\n\tfree();\n\n\telements = newdata;\n\tfirst_free = dest;\n\tcap = elements + n;\n}\n\nvoid StrVec::resize(size_t n)\n{\n\tif (n == size()) return;\n\n\tif (n < size())\n\t{\n\t\tdo {\n\t\t\talloc.destroy(--first_free);\n\t\t} while (first_free != elements + n);\n\n\t\treturn;\n\t}\n\n\tif (n > size())\n\t{\n\t\tdo {\n\t\t\tchk_n_alloc();\n\n\t\t\talloc.construct(first_free++);\n\t\t} while (first_free != elements + n);\n\n\t\treturn;\n\t}\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery_use_StrVec/StrVec.h",
    "content": "#ifndef STR_VEC_H\n#define STR_VEC_H\n\n#include <string>\n\nclass StrVec\n{\npublic:\n\tStrVec() :\t// allocator成员进行默认初始化\n\t\telements(nullptr), first_free(nullptr), cap(nullptr) {}\n\tStrVec(const std::initializer_list<std::string>&);\n\tStrVec(const StrVec&);\t\t\t\t\t\t// 拷贝构造函数\n\tStrVec& operator=(const StrVec&);\t\t\t// 拷贝赋值运算符\n\t~StrVec();\t\t\t\t\t\t\t\t\t// 析构函数\n\tvoid push_back(const std::string&);\t\t\t// 拷贝元素\n\tsize_t size() const { return first_free - elements; }\n\tsize_t capacity() const { return cap - elements; }\n\tvoid reserve(size_t n);\n\tvoid resize(size_t n);\n\tstd::string *begin() const { return elements; }\n\tstd::string *end() const { return first_free; }\n\nprivate:\n\tstatic std::allocator<std::string> alloc;\t// 分配元素\n\t// 被添加元素的函数所使用\n\tvoid chk_n_alloc()\n\t\t{ if (size() == capacity()) reallocate(); }\n\t// 工具函数，被拷贝构造函数、赋值运算符和析构函数所使用\n\tstd::pair<std::string*, std::string*> alloc_n_copy\n\t\t(const std::string*, const std::string*);\n\tvoid free();\t\t\t\t\t\t\t\t// 销毁元素并释放内存\n\tvoid reallocate();\t\t\t\t\t\t\t// 获得更多内存并拷贝已有元素\n\t\n\tstd::string *elements;\t\t\t\t\t\t// 指向数组首元素的指针\n\tstd::string *first_free;\t\t\t\t\t// 指向数组第一个空闲元素的指针\n\tstd::string *cap;\t\t\t\t\t\t\t// 指向数组尾后位置的指针\n\n};\n\n#endif // STR_VEC_H\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery_use_StrVec/TextQuery.cpp",
    "content": "#include <sstream>\n\n#include \"TextQuery.h\"\n#include \"QueryResult.h\"\n\nusing namespace std;\n\nTextQuery::TextQuery(ifstream &is): file(new StrVec)\n{\n\tstring text;\n\twhile (getline(is, text)) {\t\t// 对文件中的每一行\n\t\tfile->push_back(text);\t\t// 保存此行文本\n\t\tint n = file->size() - 1;\t// 当前行号\n\t\tistringstream line(text);\t// 将行文本分解为单词\n\t\tstring word;\n\t\twhile (line >> word) {\t\t// 对行中每个单词\n\t\t\t// 如果单词不在wm中，以之为下标在wm中添加一项\n\t\t\tauto &lines = wm[word];\t// lines是一个shared_ptr\n\t\t\tif (!lines)\t\t\t\t// 在我们第一次遇到这个单词时，此指针为空\n\t\t\t\tlines.reset(new set<line_no>);\t// 分配一个新的set\n\t\t\tlines->insert(n);\t\t// 将此行号插入set中\n\t\t}\n\t}\n}\n\nQueryResult\nTextQuery::query(const string &sought) const\n{\n\t// 如果未找到sought，我们将返回一个指向此set的指针\n\tstatic shared_ptr<set<line_no>> nodata(new set<line_no>);\n\n\t// 使用find而不是下标运算符来查找单词，避免将单词添加到wm中！\n\tauto loc = wm.find(sought);\n\tif (loc == wm.end())\n\t\treturn QueryResult(sought, nodata, file);\t// 未找到\n\telse\n\t\treturn QueryResult(sought, loc->second, file);\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery_use_StrVec/TextQuery.h",
    "content": "#ifndef TEXT_QUERY_H\n#define TEXT_QUERY_H\n\n#include <vector>\n#include <string>\n#include <fstream>\n#include <memory>\n#include <map>\n#include <set>\n\n#include \"StrVec.h\"\n\nclass QueryResult; // 为了定义函数query的返回类型，这个定义是必须的\nclass TextQuery {\npublic:\n\tusing line_no = size_t;\n\tTextQuery(std::ifstream&);\n\tQueryResult query(const std::string&) const;\n\nprivate:\n\tstd::shared_ptr<StrVec> file; // 输入文件\n\t// 每个单词到它所在的行号的集合的映射\n\tstd::map<std::string, std::shared_ptr<std::set<line_no>>> wm;\n};\n\n#endif // TEXT_QUERY_H\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_TextQuery_use_StrVec/main.cpp",
    "content": "#include <fstream>\n#include <iostream>\n#include <string>\n\n#include \"TextQuery.h\"\n#include \"QueryResult.h\"\n\nusing namespace std;\n\nvoid runQueries(ifstream &infile)\n{\n\t// infile是一个ifstream，指向我们要处理的文件\n\tTextQuery tq(infile); // 保存文件并建立查询map\n\n\t// 与用户交互，提示用户输入要查询的单词，完成查询并打印结果\n\twhile (true)\n\t{\n\t\tcout << \"Enter word to look for, or q to quit: \";\n\t\tstring s;\n\t\t// 若遇到了文件尾或用户输入了q时循环终止\n\t\tif (!(cin >> s) || s == \"q\") break;\n\n\t\t// 指向查询并打印结果\n\t\tprint(cout, tq.query(s)) << endl;\n\t}\n}\n\nint main()\n{\n\tifstream infile(\"../../data/some_words.txt\");\n\tif (!infile)\n\t{\n\t\tcout << \"Cant open file\" << endl;\n\t\treturn 1;\n\t}\n\n\trunQueries(infile);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_allocator.cpp",
    "content": "// example: allocator类（427）\n\n#include <iostream>\n#include <memory>\n#include <vector>\n\nusing namespace std;\n\nclass Bar\n{\npublic:\n\tBar() { cout << \"Bar()\" << endl; }\n\tBar(const Bar&) { cout << \"Bar(const Bar&)\" << endl; }\n\t~Bar() { cout << \"~Bar()\" << endl; }\n};\n\nint main()\n{\n\tstatic constexpr size_t N = 3;\n\n\tvector<Bar> vec;\n\tvec.resize(N); // 调用构造函数\n\n\tallocator<Bar> alloc;\n\n\t// allocate: 分配适当字节对齐并有Bar类型的N个对象的内存块，返回内存块首地址\n\tauto const p = alloc.allocate(N);\n\n\t// uninitialized_copy: 复制来自[first, last)的元素到始于p的未初始化内存\n\tauto const q = uninitialized_copy(vec.begin(), vec.end(), p); // 调用拷贝构造函数\n\n\t// destroy: 销毁对象，即对对象执行析构函数\n\t{\n\t\tauto beg = p, end = q;\n\t\twhile (beg != end)\n\t\t\talloc.destroy(--end); // 调用析构函数\n\t}\n\n\t// deallocate: 释放从p开始的内存\n\talloc.deallocate(p, N);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/example_using_own_deletion.cpp",
    "content": "// example: 使用我们自己的释放操作（p416）\n\n#include <iostream>\n#include <memory>\n#include <string>\n\nusing namespace std;\n\n// 表示正在连接什么\nstruct destination\n{\n\tstd::string addr;\n};\n\n// 使用连接所需信息\nstruct connection\n{\n\tstd::string *addr;\n};\n\n// 打开连接\nconnection connect(destination *d)\n{\n\tcout << \"[DEBUG] connect: \" << d->addr << endl;\n\n\tconnection c;\n\tc.addr = new std::string(d->addr);\n\n\treturn c;\n}\n\n// 关闭给定连接\nvoid disconnect(connection c)\n{\n\tcout << \"[DEBUG] disconnect: \" << *c.addr << endl;\n\n\tdelete c.addr;\n}\n\n// 删除器\nvoid end_connection(connection *p)\n{\n\tdisconnect(*p);\n}\n\nvoid f(destination &d)\n{\n\t// 获得一个连接；记住使用完后关闭它\n\tconnection c = connect(&d);\n\tshared_ptr<connection> p(&c, end_connection);\n\n\t// 使用连接，无需显示调用disconnect，智能指针会调用\n\tcout << \"[DEBUG] using connection: \" << *p->addr << endl;\n}\n\nint main()\n{\n\tdestination d;\n\td.addr = \"mysql\";\n\n\tf(d);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_01.cpp",
    "content": "// 练习12.1：在此代码的结尾，b1和b2各包含多少个元素？\n\n#include <iostream>\n\n#include \"example_StrBlob/StrBlob.h\"\n\nusing namespace std;\n\nint main()\n{\n\tStrBlob b1;\n\t{\n\t\tStrBlob b2 = {\"a\", \"an\", \"the\"};\n\t\tb1 = b2;\n\t\tb2.push_back(\"about\");\n\t\t\n\t\t// b1和b2共享底层数据，都包含4个元素\n\t\tcout << \"b1 size: \" << b1.size() << endl;\n\t\tcout << \"b2 size: \" << b2.size() << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_02.md",
    "content": "练习12.2：编写你自己的StrBlob类，包含const版本的front和back。\n\n---\n\n见[StrBlob](example_StrBlob/StrBlob.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_03.md",
    "content": "练习12.3：StrBlob需要const版本的push_back和pop_back吗？如果需要，添加进去。否则，解释为什么不需要。\n\n---\n\n不需要，因为push_back和pop_back都要修改本对象的数据成员，因此不能传入const版本的this指针。\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_04.md",
    "content": "练习12.4：在我们的check函数中，没有检查i是否大于0.为什么可以忽略这个检查？\n\n---\n\n因为>=0已经包含了>0了。。且必须判断是否等于0.\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_05.md",
    "content": "练习12.5：我们未编写接受一个initializer_list explicit（参见7.5.4节，第264页）参数的构造函数。讨论这个设计策略的优点和缺点。\n\n---\n\nMy: 优点是可以直接使用花括号列表来隐式转换成StrBlob（隐式转换机制）。\n\nPros：\n\n- The compiler will not use this constructor in an automatic conversion.\n\n- We can realize clearly which class we have used.\n\nCons:\n\n- We always uses the constructor to construct a temporary StrBlob object.\n\n- cannot use the copy form of initialization with an explicit constructor. not easy to use.\n\n参见：https://github.com/pezy/CppPrimer/tree/master/ch12\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_06.cpp",
    "content": "// 练习12.6：编写函数，返回一个动态分配的int的vector。将此vector传递给\n// 另一个函数，这个函数读取标准输入，将读入的值保存在vector元素中。再将\n// vector传递给另一个函数，打印读入的值。记得在恰当的时刻delete vector。\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nvector<int>* create_vec()\n{\n\treturn new vector<int>();\n}\n\nvoid read_for_vec(vector<int> *vec)\n{\n\tint n = 0;\n\twhile (cin >> n)\n\t{\n\t\tvec->push_back(n);\n\t}\n}\n\nvoid print_vec(vector<int> *vec)\n{\n\tfor (auto i : *vec)\n\t{\n\t\tcout << i << \" \";\n\t}\n\tcout << endl;\n}\n\nint main()\n{\n\tauto vec = create_vec();\n\tread_for_vec(vec);\n\tprint_vec(vec);\n\n\tdelete vec;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_07.cpp",
    "content": "// 练习12.7：重做上一题，这次使用shared_ptr而不是内置指针。\n\n// 练习12.6：编写函数，返回一个动态分配的int的vector。将此vector传递给\n// 另一个函数，这个函数读取标准输入，将读入的值保存在vector元素中。再将\n// vector传递给另一个函数，打印读入的值。记得在恰当的时刻delete vector。\n\n#include <iostream>\n#include <vector>\n#include <memory>\n\nusing namespace std;\n\nshared_ptr<vector<int>> create_vec()\n{\n\treturn make_shared<vector<int>>();\n}\n\nvoid read_for_vec(shared_ptr<vector<int>> vec)\n{\n\tint n = 0;\n\twhile (cin >> n)\n\t{\n\t\tvec->push_back(n);\n\t}\n}\n\nvoid print_vec(shared_ptr<vector<int>> vec)\n{\n\tfor (auto i : *vec)\n\t{\n\t\tcout << i << \" \";\n\t}\n\tcout << endl;\n}\n\nint main()\n{\n\tauto vec = create_vec();\n\tread_for_vec(vec);\n\tprint_vec(vec);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_08.md",
    "content": "练习12.8：下面的函数是否有错误？如果有，解释错误原因。\n\n```\nbool b() {\n\tint *p = new int;\n\t// ...\n\treturn p;\n}\n```\n\n---\n\n错误，返回类型是bool，但实际返回的是一个int\\*，不匹配，如果强制转换，那么就没有机会释放这个动态对象了。\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_09.md",
    "content": "练习12.9：解释下面代码执行的结果：\n\n```\nint *q = new int(42), *r = new int(100);\nr = q;\t// 出现内存泄漏，r指向的动态对象无法被释放了\n\nauto q2 = make_shared<int>(42), r2 = make_shared<int>(100);\nr2 = q2; // 不会有问题，r2指向的动态对象被释放\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_10.md",
    "content": "练习12.10：下面的代码调用了第413页中定义的process函数，解释此调用是否正确。如果不正确，应如何修改？\n\n```\nshared_ptr<int> p(new int(42));\nprocess(shared_ptr<int>(p));\n```\n\n---\n\n正确。p是一个智能指针，其指向的动态内存会被正确释放。\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_11.md",
    "content": "练习12.11：如果我们像下面这样调用process，会发生什么？\n\n```\nprocess(shared_ptr<int>(p.get()));\n```\n\n---\n\n由于使用了另一个智能指针的底层数据创建了一个智能指针，当原智能指针被销毁（或解引用）的时候，会造成未定义的错误，因为其指向的动态内存已经被释放过了。\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_12.md",
    "content": "练习12.12：p和q的定义如下，对于接下来的对process的每个调用，如果合法，解释它做了什么，如果不合法，解释错误的原因：\n\n```\nauto p = new int();\nauto sp = make_shared<int>();\n```\n\n(a) process(sp); // 合法，正确拷贝、销毁形参\n\n(b) process(new int()); // 错误，不能隐式初始化一个智能指针\n\n(c) process(p); // 错误，不能隐式初始化一个智能指针\n\n(d) process(shared_ptr<int>(p)); // 编译正确，但使用错误，这个操作会将p指向的内存释放掉，从而将p变成悬空指针。\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_13.md",
    "content": "练习12.13：如果执行下面的代码，会发生什么？\n\n```\nauto sp = make_shared<int>();\nauto p = sp.get();\ndelete p;\n```\n\n---\n\n当sp被销毁(或解引用)的时候，由于其指向的动态内存已经被销毁过了，因此会出现未定义的错误。\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_14.md",
    "content": "练习12。14：编写你自己版本的用shared_ptr管理connection的函数。\n\n---\n\n见[例子](./example_using_own_deletion.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_15.cpp",
    "content": "// 练习12.15：重写第一题的程序，用lambda（参见10.3.2节，第346页）代替end_connection函数。\n\n#include <iostream>\n#include <memory>\n#include <string>\n\nusing namespace std;\n\n// 表示正在连接什么\nstruct destination\n{\n\tstd::string addr;\n};\n\n// 使用连接所需信息\nstruct connection\n{\n\tstd::string *addr;\n};\n\n// 打开连接\nconnection connect(destination *d)\n{\n\tcout << \"[DEBUG] connect: \" << d->addr << endl;\n\n\tconnection c;\n\tc.addr = new std::string(d->addr);\n\n\treturn c;\n}\n\n// 关闭给定连接\nvoid disconnect(connection c)\n{\n\tcout << \"[DEBUG] disconnect: \" << *c.addr << endl;\n\n\tdelete c.addr;\n}\n\nvoid f(destination &d)\n{\n\t// 获得一个连接；记住使用完后关闭它\n\tconnection c = connect(&d);\n\tshared_ptr<connection> p(&c, [](connection *p) { disconnect(*p); } );\n\n\t// 使用连接，无需显示调用disconnect，智能指针会调用\n\tcout << \"[DEBUG] using connection: \" << *p->addr << endl;\n}\n\nint main()\n{\n\tdestination d;\n\td.addr = \"mysql\";\n\n\tf(d);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_16.cpp",
    "content": "// 练习12.16：如果你试图拷贝或赋值unique_ptr，编译器并不总是能给出易于理解的错误信息。\n// 编写包含这种错误的程序，观察编译器如何诊断这种错误。\n\n#include <iostream>\n#include <memory>\n\nusing namespace std;\n\nint main()\n{\n\tunique_ptr<int> p1(new int(1));\n\t//unique_ptr<int> p2(p1); // 错误：unique_ptr不支持拷贝，打开注释->编译，观察错误\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_17.md",
    "content": "练习12.7：下面的unique_ptr声明中，哪些是合法的，哪些可能导致后续的程序错误？解释每个错误的问题在哪里。\n\n```\nint ix = 1024, *pi = &ix, *pi2 = new int(1024);\ntypedef unique_ptr<int> IntP;\n```\n\n(a) IntP p0(ix); // 错误，unique_ptr只能指向一块动态内存\n\n(b) IntP p1(pi); // 错误，pi指向的不是一块动态内存\n\n(c) IntP p2(pi2); // 正确，但是pi2不能再手动释放\n\n(d) IntP p3(&ix); // 错误，unique_ptr只能指向一块动态内存\n\n(e) IntP p4(new int(2048)); // 正确\n\n(f) IntP p5(p2.get()); // 错误，p2.get()获得的指针不可以初始化智能指针\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_18.md",
    "content": "练习12.18：shared_ptr为什么没有release成员？\n\n---\n\nrelease让unique_ptr放弃对动态内存的管理，但shared_ptr不需要放弃，因为还有其他的shared_ptr共享着动态内存。\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_19.md",
    "content": "练习12.19：定义你自己版本的StrBlobPtr，更新StrBlob类，加入恰当的friend声明及begin和end成员。\n\n---\n\n见[案例](./example_StrBlobPtr)\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_20.cpp",
    "content": "// 练习12.20：编写程序，逐行读入一个输入文件，将内容存入一个StrBlob中，用\n// 一个StrBlobPtr打印出StrBlob中的每个元素。\n\n// g++ -g -Wall -std=c++11 exercise_12_20.cpp ./example_StrBlobPtr/StrBlobPtr.cpp -o exercise_12_20\n\n#include <iostream>\n#include <string>\n\n#include \"./example_StrBlobPtr/StrBlob.h\"\n\nusing namespace std;\n\nint main()\n{\n\tStrBlob sb;\n\tstring word;\n\twhile (cin >> word)\n\t{\n\t\tsb.push_back(word);\n\t}\n\n\tauto beg = sb.begin();\n\tfor (size_t i = 0; i < sb.size(); ++i)\n\t{\n\t\tcout << beg.deref() << endl;\n\t\tbeg.incr();\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_21.md",
    "content": "练习12.21：也可以这样编写StrBlobPtr的deref成员：\n\n```\nstd::string & deref() const\n{\n\treturn (*check(curr, \"deference past end\"))[curr];\n}\n```\n\n你认为哪个版本更好？为什么？\n\n---\n\n原版本更好，因为代码更清晰。\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_22.md",
    "content": "练习12.22：为了能让StrBlobPtr使用const StrBlob，你觉得应该如何修改？定义一个名为ConstStrBlobPtr的类，使其能够指向const StrBlob。\n\n---\n\n见[案例](./example_StrBlobPtr)\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_23.cpp",
    "content": "// 练习12.23：编写一个程序，连接两个字符串字面量，将结果保存在一个动态分配的\n// char数组中。重写这个程序，连接两个标准库string对象。\n\n#include <cstring>\n\n#include <iostream>\n#include <memory>\n#include <string>\n\nusing namespace std;\n\nvoid func1()\n{\n\tconst char *str1 = \"hello\";\n\tconst char *str2 = \" world\";\n\n\tunique_ptr<char[]> up(new char[strlen(str1) + strlen(str2)]());\n\t\n\tfor (size_t i = 0; i < strlen(str1); ++i)\n\t{\n\t\tup[i] = str1[i];\n\t}\n\n\tsize_t offset = strlen(str1);\n\tfor (size_t i = 0; i < strlen(str2); ++i)\n\t{\n\t\tup[i + offset] = str2[i];\n\t}\n\n\n\t// print\n\tfor (size_t i = 0; i < strlen(str1) + strlen(str2); ++i)\n\t{\n\t\tcout << up[i];\n\t}\n\tcout << endl;\n}\n\nvoid func2()\n{\n\tstring str1 = \"hello\";\n\tstring str2 = \" world\";\n\n\tunique_ptr<char[]> up(new char[str1.length() + str2.length()]());\n\t\n\tfor (size_t i = 0; i < str1.length(); ++i)\n\t{\n\t\tup[i] = str1[i];\n\t}\n\n\tsize_t offset = str1.length();\n\tfor (size_t i = 0; i < str2.length(); ++i)\n\t{\n\t\tup[i + offset] = str2[i];\n\t}\n\n\n\t// print\n\tfor (size_t i = 0; i < str1.length() + str2.length(); ++i)\n\t{\n\t\tcout << up[i];\n\t}\n\tcout << endl;\n\t\n}\n\nint main()\n{\n\t//func1();\n\tfunc2();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_24.cpp",
    "content": "// 练习12.24：编写一个程序，从标准输入读取一个字符串，存入一个动态分配的字符数组\n// 中。描述你的程序如何处理变长输入。测试你的程序，输入一个超出你分配的数组长度\n// 的字符串。\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tsize_t length = 0; // 分配的动态数组的长度\n\tstring word;\n\n\tcout << \"Enter length: \";\n\tcin >> length;\n\n\tcout << \"Enter word: \";\n\tcin >> word;\n\n\tif (length < word.length() + 1)\n\t{\n\t\tcout << \"length not enough!\";\n\t\treturn 1;\n\t}\n\n\tchar *str = new char[length]();\n\tfor (size_t i = 0; i < length; ++i)\n\t{\n\t\tstr[i] = word[i];\n\t}\n\n\t// print\n\tcout << str << endl;\n\n\tdelete []str;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_25.md",
    "content": "练习12.25：给定下面的new表达式，你应该如何释放pa？\n\n```\nint *pa = new int[10];\n```\n\n---\n\n```\ndelete []pa;\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_26.cpp",
    "content": "// 练习12.26：用allocator重写第427页的程序。\n\n#include <iostream>\n#include <string>\n#include <memory>\n\nusing namespace std;\n\nint main()\n{\n\tsize_t n = 4;\n\tsize_t real_n = 0;\n\n\tallocator<string> alloc;\n\tauto const p = alloc.allocate(n);\n\n\tstring s;\n\tstring *q = p;\n\twhile (cin >> s && q != p + n)\n\t{\n\t\talloc.construct(q++, s);\n\t\t++real_n;\n\t}\n\n\t// print test\n\t{\n\t\tstring *q = p;\n\t\twhile (q != p + real_n)\n\t\t\tcout << *q++ << \" \";\n\t\tcout << endl;\n\t}\n\n\t// destroy\n\t{\n\t\tstring *q = p;\n\t\twhile (q != p + real_n)\n\t\t\talloc.destroy(q++);\n\t}\n\n\talloc.deallocate(p, n);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_27.cpp",
    "content": "// 练习12.27：TextQuery和QueryResult类只使用了我们已介绍过的语言和标准\n// 库特性。不要提前看后续章节内容，只用已经学到的知识编写你自己的版本。\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <map>\n#include <set>\n#include <fstream>\n#include <sstream>\n\nusing namespace std;\n\nclass QueryResult\n{\n\tfriend ostream& print(ostream&, const QueryResult&);\n\npublic:\n\tQueryResult(const string &word) : m_lines(nullptr), m_line_numbers(nullptr), m_word(word) {}\n\n\tbool IsValid() const { return m_lines && m_line_numbers; }\n\n\tvoid SetRes(vector<string> *lines, set<int> *line_numbers)\n\t{\n\t\tm_lines = lines;\n\t\tm_line_numbers = line_numbers;\n\t}\n\nprivate:\n\tvector<string> *m_lines;\n\tset<int> *m_line_numbers;\n\tstring m_word;\n};\n\nclass TextQuery\n{\npublic:\n\tTextQuery(ifstream &ifs);\n\n\tQueryResult query(const string &word);\n\nprivate:\n\tvector<string> m_lines;\n\tmap<string, set<int>> m_word_to_line_map;\n};\n\n//------------------------------------------\n\n// TextQuery implement\n\nTextQuery::TextQuery(ifstream &ifs)\n{\n\tstring line;\n\tsize_t line_no = 0;\n\n\twhile (getline(ifs, line))\n\t{\n\t\tm_lines.push_back(line);\n\n\t\tistringstream iss(line);\n\t\tstring word;\n\n\t\twhile (iss >> word)\n\t\t{\n\t\t\tm_word_to_line_map[word].insert(line_no);\n\t\t}\n\n\t\t++line_no;\n\t}\n}\n\nQueryResult TextQuery::query(const string &word)\n{\n\tQueryResult query_res(word);\n\n\tif (m_word_to_line_map.find(word) != m_word_to_line_map.end())\n\t{\n\t\tquery_res.SetRes(&m_lines, &m_word_to_line_map[word]);\n\t}\n\n\treturn query_res;\n}\n\n//------------------------------------------\n\nostream& print(ostream &os, const QueryResult &query_res)\n{\n\tif (query_res.IsValid())\n\t{\n\t\tcout << query_res.m_word << \" occurs \" << query_res.m_line_numbers->size() << \" times\" << endl;\n\n\t\tfor (auto line : *query_res.m_line_numbers)\n\t\t{\n\t\t\tcout << \"(line \" << line << \") \" << (*query_res.m_lines)[line] << endl;\n\t\t}\n\t}\n\telse\n\t{\n\t\tcout << \"Cant find \" << query_res.m_word << endl;\n\t}\n\n\treturn os;\n}\n\nvoid runQueries(ifstream &infile)\n{\n\t// infile是一个ifstream，指向我们要处理的文件\n\tTextQuery tq(infile); // 保存文件并建立查询map\n\n\t// 与用户交互，提示用户输入要查询的单词，完成查询并打印结果\n\twhile (true)\n\t{\n\t\tcout << \"Enter word to look for, or q to quit: \";\n\t\tstring s;\n\t\t// 若遇到了文件尾或用户输入了q时循环终止\n\t\tif (!(cin >> s) || s == \"q\") break;\n\n\t\t// 指向查询并打印结果\n\t\tprint(cout, tq.query(s)) << endl;\n\t}\n}\n\nint main()\n{\n\tifstream infile(\"../data/some_words.txt\");\n\tif (!infile)\n\t{\n\t\tcout << \"Cant open file\" << endl;\n\t\treturn 1;\n\t}\n\n\trunQueries(infile);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_28.cpp",
    "content": "// 练习12.28：编写程序实现文本查询，不要定义类来管理数据。你的程序应该接受一个\n// 文件，并与用户交互来查询单词。使用vector、map和set容器来保存来自文件的数据并\n// 生成查询结果\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <map>\n#include <set>\n#include <fstream>\n#include <sstream>\n\nusing namespace std;\n\nint main()\n{\n\t// 读入文件\n\tifstream infile(\"../data/some_words.txt\");\n\tif (!infile)\n\t{\n\t\tcout << \"Cant open file\" << endl;\n\t\treturn 1;\n\t}\n\n\tvector<string> lines;\n\tmap<string, set<int>> word_to_line_map;\n\n\t// 存储数据\n\t{\n\t\tstring line;\n\t\tsize_t line_no = 0;\n\n\t\twhile (getline(infile, line))\n\t\t{\n\t\t\tlines.push_back(line);\n\n\t\t\tistringstream iss(line);\n\t\t\tstring word;\n\n\t\t\twhile (iss >> word)\n\t\t\t{\n\t\t\t\tword_to_line_map[word].insert(line_no);\n\t\t\t}\n\n\t\t\t++line_no;\n\t\t}\n\t}\n\n\t// 与用户交互\n\tdo\n\t{\n\t\tcout << \"Enter word to look for, or q to quit: \";\n\t\tstring s;\n\t\tif (!(cin >> s) || s == \"q\") break;\n\n\t\t// 查询\n\t\tif (word_to_line_map.find(s) != word_to_line_map.end())\n\t\t{\n\t\t\tcout << s << \" occurs \" << word_to_line_map[s].size() << \" times\" << endl;\n\n\t\t\tfor (auto l : word_to_line_map[s])\n\t\t\t{\n\t\t\t\tcout << \"(line \" << l << \") \" << lines[l] << endl;\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcout << \"Cant find \" << s << endl;\n\t\t}\n\t} while (true);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_29.md",
    "content": "练习12.29：我们曾用do while循环来编写管理用户交互的循环（参见5.4.4节，第169页）。用do while重写本节程序，解释你倾向于哪个版本，为什么？\n\n---\n\n见[练习12.28](./exercise_12_28.cpp)\n\n我更倾向于while循环，因为习惯。\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_30.md",
    "content": "练习12.30：定义你自己版本的TextQuery和QueryResult类，并执行12.3.1节（第431页）中的runQueries函数。\n\n---\n\n见[example_TextQuery](./example_TextQuery)\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_31.md",
    "content": "练习12.31：如果用vector代替set保存行号，会有什么差别？哪种方法更好？为什么？\n\n---\n\n- set能保证升序\n\n- set保存唯一的行号，vector可能重复\n\n使用set更好。\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_32.cpp",
    "content": "// 重写TextQuery和QueryResult类，用StrBlob代替vector<string>保存输入文件。\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <map>\n#include <set>\n#include <fstream>\n#include <sstream>\n\n#include \"./example_StrBlob/StrBlob.h\"\n\nusing namespace std;\n\nclass QueryResult\n{\n\tfriend ostream& print(ostream&, const QueryResult&);\n\npublic:\n\tQueryResult(const string &word) : m_lines(nullptr), m_line_numbers(nullptr), m_word(word) {}\n\n\tbool IsValid() const { return m_lines && m_line_numbers; }\n\n\tvoid SetRes(StrBlob *lines, set<int> *line_numbers)\n\t{\n\t\tm_lines = lines;\n\t\tm_line_numbers = line_numbers;\n\t}\n\nprivate:\n\tStrBlob *m_lines;\n\tset<int> *m_line_numbers;\n\tstring m_word;\n};\n\nclass TextQuery\n{\npublic:\n\tTextQuery(ifstream &ifs);\n\n\tQueryResult query(const string &word);\n\nprivate:\n\tStrBlob m_lines;\n\tmap<string, set<int>> m_word_to_line_map;\n};\n\n//------------------------------------------\n\n// TextQuery implement\n\nTextQuery::TextQuery(ifstream &ifs)\n{\n\tstring line;\n\tsize_t line_no = 0;\n\n\twhile (getline(ifs, line))\n\t{\n\t\tm_lines.push_back(line);\n\n\t\tistringstream iss(line);\n\t\tstring word;\n\n\t\twhile (iss >> word)\n\t\t{\n\t\t\tm_word_to_line_map[word].insert(line_no);\n\t\t}\n\n\t\t++line_no;\n\t}\n}\n\nQueryResult TextQuery::query(const string &word)\n{\n\tQueryResult query_res(word);\n\n\tif (m_word_to_line_map.find(word) != m_word_to_line_map.end())\n\t{\n\t\tquery_res.SetRes(&m_lines, &m_word_to_line_map[word]);\n\t}\n\n\treturn query_res;\n}\n\n//------------------------------------------\n\nostream& print(ostream &os, const QueryResult &query_res)\n{\n\tif (query_res.IsValid())\n\t{\n\t\tcout << query_res.m_word << \" occurs \" << query_res.m_line_numbers->size() << \" times\" << endl;\n\n\t\tfor (auto line : *query_res.m_line_numbers)\n\t\t{\n\t\t\tcout << \"(line \" << line << \") \" << (*query_res.m_lines)[line] << endl;\n\t\t}\n\t}\n\telse\n\t{\n\t\tcout << \"Cant find \" << query_res.m_word << endl;\n\t}\n\n\treturn os;\n}\n\nvoid runQueries(ifstream &infile)\n{\n\t// infile是一个ifstream，指向我们要处理的文件\n\tTextQuery tq(infile); // 保存文件并建立查询map\n\n\t// 与用户交互，提示用户输入要查询的单词，完成查询并打印结果\n\twhile (true)\n\t{\n\t\tcout << \"Enter word to look for, or q to quit: \";\n\t\tstring s;\n\t\t// 若遇到了文件尾或用户输入了q时循环终止\n\t\tif (!(cin >> s) || s == \"q\") break;\n\n\t\t// 指向查询并打印结果\n\t\tprint(cout, tq.query(s)) << endl;\n\t}\n}\n\nint main()\n{\n\tifstream infile(\"../data/some_words.txt\");\n\tif (!infile)\n\t{\n\t\tcout << \"Cant open file\" << endl;\n\t\treturn 1;\n\t}\n\n\trunQueries(infile);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch12_Dynamic_Memory/exercise_12_33.md",
    "content": "练习12.33：在第15章中我们将扩展查询系统，在QueryResult类中将会需要一些额外的成员。添加名为begin和end的成员，返回一个迭代器，指向一个给定查询返回的行号的set中的位置。再添加一个名为get_file的成员，返回一个shared_ptr，指向QueryResult对象中的文件。\n\n---\n\n见[class QueryResult](./example_TextQuery/QueryResult.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\trm -f out1 out2\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_Copy_Control/Folder.cpp",
    "content": "#include \"Folder.h\"\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_Copy_Control/Folder.h",
    "content": "#ifndef FOLDER_H\n#define FOLDER_H\n\n#include <iostream>\n#include <set>\n#include <string>\n#include <memory>\n\n#include \"Message.h\"\n\nclass Folder\n{\npublic:\n\tFolder(const std::string &str) : name(str), messages(std::make_shared<std::set<Message*>>()) {}\n\t~Folder()\n\t{ \n\t\tfor (auto m : *messages)\n\t\t\tm->remove(*this);\n\t}\n\n\tvoid addMsg(Message *m) { messages->insert(m); }\n\tvoid remMsg(Message *m) { messages->erase(m); }\n\n\tconst std::string &getName() const { return name; }\n\tvoid print() const;\n\nprivate:\n\tstd::string name;\n\tstd::shared_ptr<std::set<Message*>> messages; // 使用智能指针共享底层对象\n};\n\ninline\nvoid Folder::print() const\n{\n\tusing namespace std;\n\n\tcout << \"Folder [\" << name << \"]:\" << endl;\n\n\tsize_t number = 0;\n\tfor (auto m : *messages)\n\t\tcout << \"\\t\" << number++ << \": \" << m->contents << endl;\n}\n\n#endif // FOLDER_H\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_Copy_Control/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_Copy_Control/Message.cpp",
    "content": "#include \"Message.h\"\n#include \"Folder.h\"\n\nvoid Message::add_to_Folders(const Message &m)\n{\n\tfor (auto f : m.folders) // 对每个包含m的folder\n\t\tf->addMsg(this);\t // 向该Folder添加一个指向本Message的指针\n}\n\nvoid Message::remove_from_Folders()\n{\n\tfor (auto f : folders)\t// 对folders中每个指针\n\t\tf->remMsg(this);\t// 从该Folder中删除本Message\n}\n\nvoid Message::move_Folders(Message *m)\n{\n\tfolders = std::move(m->folders); // 使用set的移动赋值运算符\n\tfor (auto f : folders) {\t// 对于每个Folder\n\t\tf->remMsg(m);\t\t\t// 从Folder中删除旧Message\n\t\tf->addMsg(this);\t\t// 将本Message添加到Folder中\n\t}\n\tm->folders.clear();\t\t\t// 确保销毁m是无害的\n}\n\nvoid Message::save(Folder &f)\n{\n\tfolders.insert(&f);\t\t// 将给定Folder添加到我们的Folder列表中\n\tf.addMsg(this);\t\t\t// 将本Message添加到f的Message集合中\n}\n\nvoid Message::remove(Folder &f)\n{\n\tfolders.erase(&f);\t\t// 将给定Folder从我们的Folder列表中删除\n\tf.remMsg(this);\t\t\t// 将本Message从f的Message集合中删除\n}\n\n//----------------------------------------------\n\n// implement 拷贝控制成员\n\nMessage::Message(const Message &m) :\n\tcontents(m.contents), folders(m.folders)\n{\n\tadd_to_Folders(m);\t\t// 将本消息添加到指向m的Folder中\n}\n\nMessage::Message(Message &&m) : contents(std::move(m.contents))\n{\n\tmove_Folders(&m);\t\t// 移动Folders并更新Folder指针\n}\n\nMessage::~Message()\n{\n\tremove_from_Folders();\n}\n\nMessage& Message::operator=(const Message &rhs)\n{\n\t// 通过先删除指针再插入它们来处理自赋值的情况\n\tremove_from_Folders();\t// 更新已有Folder\n\tcontents = rhs.contents;// 从rhs拷贝消息内容\n\tfolders = rhs.folders;\t// 从rhs拷贝Folder指针\n\tadd_to_Folders(rhs);\t// 将本Message添加到那些Folder中\n\treturn *this;\n}\n\nMessage& Message::operator=(Message &&rhs)\n{\n\tif (this != &rhs) {\t\t// 直接检查自赋值情况\n\t\tremove_from_Folders();\n\t\tcontents = std::move(rhs.contents); // 移动赋值运算符\n\t\tmove_Folders(&rhs); // 重置Folders指向本Message\n\t}\n\treturn *this;\n}\n\n//----------------------------------------------\n\nvoid swap(Message &lhs, Message &rhs)\n{\n\tusing std::swap;\t\t// 在本例中严格来说并不重要，但这是一个好习惯\n\t// 将每个消息的指针从它（原来）所在Folder中删除\n\tlhs.remove_from_Folders();\n\trhs.remove_from_Folders();\n\n\t// 交换contents和Folder指针set\n\tswap(lhs.folders, rhs.folders);\n\t//swap(lhs.contents, rhs.contents); // 交换contents就看不出变化了。。\n\n\t// 将每个Message的指针添加到新的Folder中\n\tlhs.add_to_Folders(lhs);\n\trhs.add_to_Folders(rhs);\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_Copy_Control/Message.h",
    "content": "#ifndef MESSAGE_H\n#define MESSAGE_H\n\n#include <string>\n#include <set>\n\nclass Folder;\n\nclass Message\n{\n\tfriend class Folder;\n\tfriend void swap(Message&, Message&);\n\npublic:\n\t// folders被隐式初始化为空集合\n\texplicit Message(const std::string &str = \"\") :\n\t\tcontents(str) {}\n\n\t// 拷贝控制成员，用来管理指向本Message的指针\n\tMessage(const Message&);\t\t\t// 拷贝构造函数\n\tMessage(Message&&);\t\t\t\t\t// 移动构造函数\n\tMessage& operator=(const Message&);\t// 拷贝赋值运算符\n\tMessage& operator=(Message&&);\t\t// 移动赋值运算符\n\t~Message();\t\t\t\t\t\t\t// 析构函数\n\n\t// 从给定Folder集合中添加/删除本Message\n\tvoid save(Folder&);\n\tvoid remove(Folder&);\n\t\n\nprivate:\n\tstd::string contents;\t\t// 实际消息文本\n\tstd::set<Folder*> folders;\t// 包含本Message的Folder\n\n\t// 拷贝构造函数、拷贝赋值运算符和析构函数所使用的工具函数\n\t// 将本Message添加到指向参数的Folder中\n\tvoid add_to_Folders(const Message&);\n\n\t// 从folders中的每个Folder中删除本Message\n\tvoid remove_from_Folders();\n\n\t// 从本Message移动Folder指针\n\tvoid move_Folders(Message *m);\n};\n\n#endif // MESSAGE_H\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_Copy_Control/main.cpp",
    "content": "// example: 拷贝控制示例（p460）\n\n#include <iostream>\n#include \"Folder.h\"\n\nusing namespace std;\n\nint main()\n{\n\tFolder folder1(\"folder1\");\n\tFolder folder2(\"folder2\");\n\tFolder folder3(\"folder3\");\n\n\t{\n\t\tMessage msg1(\"Hi there\");\n\t\tMessage msg2(\"Hello world\");\n\t\tMessage msg3(\"Have a nice day\");\n\n\t\tmsg1.save(folder1);\n\t\tmsg2.save(folder2);\n\t\tmsg3.save(folder3);\n\n\t\t// Copy\n\t\tauto msg4(msg2);\n\t\tMessage msg5;\n\t\tmsg5 = msg2;\n\n\t\tfolder1.print();\n\t\tfolder2.print();\n\t\tfolder3.print();\n\n\t\tcout << \"\\nmsg swap ...\" << endl;\n\t\tswap(msg1, msg3);\n\t\tfolder1.print();\n\t\tfolder2.print();\n\t\tfolder3.print();\n\t}\n\n\tcout << \"\\nmsg destruct ...\" << endl;\n\tfolder1.print();\n\tfolder2.print();\n\tfolder3.print();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_Reference_Count/HasPtr.cpp",
    "content": "#include \"HasPtr.h\"\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_Reference_Count/HasPtr.h",
    "content": "#ifndef HAS_PTR_H\n#define HAS_PTR_H\n\n#include <string>\n\nclass HasPtr {\n\tfriend void swap(HasPtr&, HasPtr&);\n\npublic:\n\t// 构造函数分配新的string和新的计数器，将计数器置为1\n\tHasPtr(const std::string &s = std::string()) :\n\t\tps(new std::string(s)), i(0), use(new std::size_t(1)) {}\n\n\t// 拷贝构造函数拷贝所有三个数据成员，并递增计数器\n\tHasPtr(const HasPtr &p) :\n\t\tps(p.ps), i(p.i), use(p.use) { ++*use; }\n\n\tHasPtr& operator=(const HasPtr&);\n\t~HasPtr();\n\nprivate:\n\tstd::string *ps;\n\tint i;\n\tstd::size_t *use;\t// 用来记录有多少个对象共享*ps的成员\n};\n\nHasPtr::~HasPtr()\n{\n\tif (--*use == 0) {\t// 如果引用计数变为0\n\t\tdelete ps;\t\t// 释放string内存\n\t\tdelete use;\t\t// 释放计数器内存\n\t}\n}\n\nHasPtr& HasPtr::operator=(const HasPtr &rhs)\n{\n\t++*rhs.use;\t\t\t// 递增右侧运算对象的引用计数\n\tif (--*use == 0) {\t// 然后递减本对象的引用计数\n\t\tdelete ps;\t\t// 如果没有其他用户\n\t\tdelete use;\t\t// 释放本对象分配的成员\n\t}\n\tps = rhs.ps;\t\t// 将数据从rhs拷贝到本对象\n\ti = rhs.i;\n\tuse = rhs.use;\n\treturn *this;\t\t// 返回本对象\n}\n\ninline\nvoid swap(HasPtr &lhs, HasPtr &rhs)\n{\n\tusing std::swap;\n\tswap(lhs.ps, rhs.ps);\t// 交换指针，而不是string数据\n\tswap(lhs.i, rhs.i);\t\t// 交换int成员\n}\n\n#endif // HAS_PTR_H\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_Reference_Count/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_Reference_Count/main.cpp",
    "content": "// example: 使用引用计数的类（p456）\n\n#include <iostream>\n\nusing namespace std;\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_Reference_Qualifier/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_Reference_Qualifier/main.cpp",
    "content": "// example: 引用限定符（p483）\n\n#include <iostream>\n#include <vector>\n#include <algorithm>\n\nusing namespace std;\n\nclass Foo {\npublic:\n\tFoo &operator=(const Foo&) &; \t// 只能向可修改的左值赋值\n\n\tFoo someMem() const &;\t\t\t// 正确，const限定符在前\n\n\tFoo sorted() &&;\t\t\t\t// 可用于可改变的右值\n\tFoo sorted() const &;\t\t\t// 可用于任何类型的Foo\n\nprivate:\n\tint a = 0;\n\tvector<int> data;\n};\n\nFoo &Foo::operator=(const Foo &rhs) &\n{\n\ta = rhs.a;\n\treturn *this;\n}\n\n// 本对象为右值，因此可以原址排序\nFoo Foo::sorted() &&\n{\n\tcout << \"Foo Foo::sorted() &&\" << endl;\n\n\tsort(data.begin(), data.end());\n\treturn *this;\n}\n\n// 本对象是const或是一个左值，哪种情况我们都不能对原址排序\nFoo Foo::sorted() const &\n{\n\tcout << \"Foo Foo::sorted() const &\" << endl;\n\n\tFoo ret(*this);\t// 拷贝一个副本\n\tsort(ret.data.begin(), ret.data.end()); // 排序副本\n\treturn ret;\t\t// 返回副本\n}\n\n// 返回一个引用；retFoo1调用是一个左值\nFoo &retFoo1()\n{\n\tstatic Foo foo;\n\treturn foo;\n}\n\n// 返回一个值；retFoo2调用是一个右值\nFoo retFoo2()\n{\n\treturn Foo();\n}\n\nint main()\n{\n\t// tests\n\t\n\tFoo i, j;\t\t// i,j是左值\n\ti = j;\t\t\t// 正确，i是左值\n\tretFoo1() = j;\t// 正确，retFoo1()返回一个左值\n\t//retFoo2() = j;\t// 错误，retFoo2()返回一个右值\n\ti = retFoo2();\t// 正确，右值可以放在赋值符号的右边\n\n\tretFoo1().sorted(); // retFoo1()是一个左值，调用Foo::sorted() const &\n\tretFoo2().sorted(); // retFoo2()是一个右值，调用Foo::sorted() &&\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_StrVec/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_StrVec/StrVec.cpp",
    "content": "#include <memory>\n#include <utility>\n#include <algorithm>\n\n#include \"StrVec.h\"\n\nusing namespace std;\n\nallocator<std::string> StrVec::alloc;\n\nStrVec::StrVec(const initializer_list<string> &lst)\n{\n\tauto newdata = alloc_n_copy(lst.begin(), lst.end());\n\telements = newdata.first;\n\tfirst_free = cap = newdata.second;\n}\n\nStrVec::StrVec(const StrVec &s)\n{\n\t// 调用alloc_n_copy分配空间以容纳与s中一样多的元素\n\tauto newdata = alloc_n_copy(s.begin(), s.end());\n\telements = newdata.first;\n\tfirst_free = cap = newdata.second;\n}\n\nStrVec::StrVec(StrVec &&s) noexcept\n\t// 成员初始化器接管s中的资源\n\t: elements(s.elements), first_free(s.first_free), cap(s.cap)\n{\n\t// 令s进入这样的状态：对其进行析构函数是安全的\n\ts.elements = s.first_free = s.cap = nullptr;\n}\n\nStrVec& StrVec::operator=(StrVec &&rhs) noexcept\n{\n\t// 直接检测自赋值\n\tif (this != &rhs) {\n\t\tfree();\t// 释放已有元素\n\t\telements = rhs.elements; // 从rhs接管资源\n\t\tfirst_free = rhs.first_free;\n\t\tcap = rhs.cap;\n\n\t\t// 将rhs置于可析构状态\n\t\trhs.elements = rhs.first_free = rhs.cap = nullptr;\n\t}\n\treturn *this;\n}\n\nbool operator==(const StrVec &lhs, const StrVec &rhs)\n{\n\tauto lhs_beg = lhs.elements;\n\tauto rhs_beg = rhs.elements;\n\n\twhile (lhs_beg != lhs.first_free && rhs_beg != rhs.first_free) {\n\t\tif (*lhs_beg != *rhs_beg)\n\t\t\treturn false;\n\n\t\t++lhs_beg;\n\t\t++rhs_beg;\n\t}\n\n\tif (lhs_beg == lhs.first_free && rhs_beg == rhs.first_free)\n\t\treturn true;\n\n\treturn false;\n}\n\nbool operator!=(const StrVec &lhs, const StrVec &rhs)\n{\n\treturn !(lhs == rhs);\n}\n\nbool operator<(const StrVec &lhs, const StrVec &rhs)\n{\n\tauto lhs_beg = lhs.elements;\n\tauto rhs_beg = rhs.elements;\n\n\twhile (lhs_beg != lhs.first_free && rhs_beg != rhs.first_free) {\n\t\tif (*lhs_beg > *rhs_beg)\n\t\t\treturn false;\n\t\telse if (*lhs_beg < *rhs_beg)\n\t\t\treturn true;\n\n\t\t++lhs_beg;\n\t\t++rhs_beg;\n\t}\n\n\tif (lhs_beg == lhs.first_free && rhs_beg <= rhs.first_free)\n\t\treturn true;\n\n\treturn false;\n}\n\nStrVec::~StrVec()\n{\n\tfree();\n}\n\nStrVec &StrVec::operator=(const StrVec &rhs)\n{\n\t// 调用alloc_n_copy分配内存，大小与rhs中元素占用空间一样多\n\tauto data = alloc_n_copy(rhs.begin(), rhs.end());\n\tfree();\n\telements = data.first;\n\tfirst_free = cap = data.second;\n\treturn *this;\n}\n\nStrVec &StrVec::operator=(initializer_list<string> il)\n{\n\t// alloc_n_copy分配内存空间并从给定范围内拷贝元素\n\tauto data = alloc_n_copy(il.begin(), il.end());\n\tfree();\t\t\t\t\t// 销毁对象中的元素并释放内存空间\n\telements = data.first;\t// 更新数据成员使其指向新空间\n\tfirst_free = cap = data.second;\n\treturn *this;\n}\n\nvoid StrVec::push_back(const string &s)\n{\n\tchk_n_alloc(); // 确保有空间容纳新元素\n\t// 在first_free指向的元素中构造s的副本\n\talloc.construct(first_free++, s);\n}\n\npair<string*, string*>\nStrVec::alloc_n_copy(const string *b, const string *e)\n{\n\t// 分配空间保存给定范围中的元素\n\tauto data = alloc.allocate(e - b);\n\n\t// 初始化并返回一个pair，该pair由data和uninitialized_copy的返回值构成\n\treturn {data, uninitialized_copy(b, e, data)};\n}\n\nvoid StrVec::free()\n{\n\t// 不能传递给deallocate一个空指针，如果elements为0，函数什么也不做\n\tif (elements) {\n\t\t// 逆序销毁旧元素\n\t\t\n\t\t//for (auto p = first_free; p != elements;) alloc.destroy(--p);\n\n\t\tfor_each(elements, first_free, [this](string &s){ alloc.destroy(&s); } );\n\n\t\talloc.deallocate(elements, cap - elements);\n\t}\n}\n\nvoid StrVec::reallocate()\n{\n\t// 我们将分配当前大小两倍的内存空间\n\tauto newcapacity = size() ? 2 * size() : 1;\n\t// 分配新内存\n\tauto newdata = alloc.allocate(newcapacity);\n\t// 将数据从旧内存移动到新内存\n\tauto dest = newdata;  // 指向新数组中下一个空闲位置\n\tauto elem = elements; // 指向旧数组中下一个元素\n\tfor (size_t i = 0; i != size(); ++i)\n\t\talloc.construct(dest++, std::move(*elem++));\n\tfree(); // 一旦我们移动完元素就释放旧内存空间\n\t// 更新我们的数据结构，执行新元素\n\telements = newdata;\n\tfirst_free = dest;\n\tcap = elements + newcapacity;\n}\n\nvoid StrVec::reserve(size_t n)\n{\n\tif (n <= capacity()) return;\n\n\tauto newdata = alloc.allocate(n);\n\n\t// copy old data\n\tauto dest = newdata;\n\tauto elem = elements;\n\tfor (size_t i = 0; i != size(); ++i)\n\t\talloc.construct(dest++, std::move(*elem++));\n\tfree();\n\n\telements = newdata;\n\tfirst_free = dest;\n\tcap = elements + n;\n}\n\nvoid StrVec::resize(size_t n)\n{\n\tif (n == size()) return;\n\n\tif (n < size())\n\t{\n\t\tdo {\n\t\t\talloc.destroy(--first_free);\n\t\t} while (first_free != elements + n);\n\n\t\treturn;\n\t}\n\n\tif (n > size())\n\t{\n\t\tdo {\n\t\t\tchk_n_alloc();\n\n\t\t\talloc.construct(first_free++);\n\t\t} while (first_free != elements + n);\n\n\t\treturn;\n\t}\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_StrVec/StrVec.h",
    "content": "#ifndef STR_VEC_H\n#define STR_VEC_H\n\n#include <string>\n\n/*\n#include \"../../ch19_Specialized_Tools_and_Techniques/example_operator_new_delete.h\" // 使用自定义的new和delete表达式，提供给allocator，ex19.2\n*/\n\nclass StrVec\n{\n\tfriend bool operator==(const StrVec &lhs, const StrVec &rhs);\n\tfriend bool operator!=(const StrVec &lhs, const StrVec &rhs);\n\tfriend bool operator<(const StrVec &lhs, const StrVec &rhs);\n\npublic:\n\tStrVec() :\t// allocator成员进行默认初始化\n\t\telements(nullptr), first_free(nullptr), cap(nullptr) {}\n\tStrVec(const std::initializer_list<std::string>&);\n\t\n\tStrVec(const StrVec&);\t\t\t\t\t\t// 拷贝构造函数\n\tStrVec(StrVec&&) noexcept;\t\t\t\t\t// 移动构造函数\n\tStrVec& operator=(const StrVec&);\t\t\t// 拷贝赋值运算符\n\tStrVec& operator=(StrVec&&) noexcept;\t\t// 移动赋值运算符\n\t~StrVec();\t\t\t\t\t\t\t\t\t// 析构函数\n\n\tStrVec& operator=(std::initializer_list<std::string> il);\n\tstd::string& operator[](std::size_t n) { return elements[n]; }\n\tconst std::string& operator[](std::size_t n) const { return elements[n]; }\n\n\tvoid push_back(const std::string&);\t\t\t// 拷贝元素\n\tsize_t size() const { return first_free - elements; }\n\tsize_t capacity() const { return cap - elements; }\n\tvoid reserve(size_t n);\n\tvoid resize(size_t n);\n\tstd::string *begin() const { return elements; }\n\tstd::string *end() const { return first_free; }\n\n\ttemplate <class... Args> void emplace_back(Args&&... args);\n\nprivate:\n\tstatic std::allocator<std::string> alloc;\t// 分配元素\n\t// 被添加元素的函数所使用\n\tvoid chk_n_alloc()\n\t\t{ if (size() == capacity()) reallocate(); }\n\t// 工具函数，被拷贝构造函数、赋值运算符和析构函数所使用\n\tstd::pair<std::string*, std::string*> alloc_n_copy\n\t\t(const std::string*, const std::string*);\n\tvoid free();\t\t\t\t\t\t\t\t// 销毁元素并释放内存\n\tvoid reallocate();\t\t\t\t\t\t\t// 获得更多内存并拷贝已有元素\n\t\n\tstd::string *elements;\t\t\t\t\t\t// 指向数组首元素的指针\n\tstd::string *first_free;\t\t\t\t\t// 指向数组第一个空闲元素的指针\n\tstd::string *cap;\t\t\t\t\t\t\t// 指向数组尾后位置的指针\n\n};\n\ntemplate <class... Args> inline\nvoid StrVec::emplace_back(Args&&... args)\n{\n\tchk_n_alloc();\t// 如果需要的话重新分配StrVec内存空间\n\talloc.construct(first_free++, std::forward<Args>(args)...);\n}\n\n#endif // STR_VEC_H\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_StrVec/main.cpp",
    "content": "// example: 动态内存管理类（p464）\n\n#include <iostream>\n\n#include \"StrVec.h\"\n\nusing namespace std;\n\nvoid func1()\n{\n\tStrVec v;\n\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tcout << \"reserve 4 ...\" << endl;\n\tv.reserve(4);\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tcout << \"push_back 2 string ...\" << endl;\n\tv.push_back(\"a\");\n\tv.push_back(\"b\");\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tcout << \"print ...\" << endl;\n\tfor (auto p = v.begin(); p != v.end(); ++p)\n\t\tcout << *p << \" \";\n\tcout << endl;\n\n\tcout << \"resize to 0 ...\" << endl;\n\tv.resize(0);\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tcout << \"resize to 100 ...\" << endl;\n\tv.resize(100);\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n}\n\nvoid func2()\n{\n\tStrVec v{\"a\", \"b\", \"Hello\", \"World\"};\n\n\tfor (auto p = v.begin(); p != v.end(); ++p)\n\t\tcout << *p << \" \";\n\tcout << endl;\n\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n}\n\nvoid func3()\n{\n\tStrVec v1{\"a\", \"b\", \"Hi\"};\n\tStrVec v2{\"a\", \"n\", \"Hi\"};\n\tStrVec v3{\"a\", \"n\", \"Hi\"};\n\tStrVec v4{\"a\", \"n\", \"Hi\", \"Hello\"};\n\n\tif (v1 != v2)\n\t\tcout << \"v1 != v2\" << endl;\n\n\tif (v2 == v3)\n\t\tcout << \"v2 == v3\" << endl;\n\n\tif (v3 < v4)\n\t\tcout << \"v3 < v4\" << endl;\n}\n\n// 测试emplace_back，p623\nvoid func4()\n{\n\tStrVec v;\n\tv.emplace_back(10, 'c');\n\tv.emplace_back(\"Hello\");\n\n\tfor (auto p = v.begin(); p != v.end(); ++p)\n\t\tcout << *p << \" \";\n\tcout << endl;\n}\n\nint main()\n{\n\tfunc1();\n\t//func2();\n\t//func3();\n\t//func4();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_String/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_String/String.cpp",
    "content": "#include <cstring>\n\n#include <iostream>\n#include <memory>\n#include <utility>\n#include <algorithm>\n\n#include \"String.h\"\n\nusing namespace std;\n\nstd::ostream& operator<<(std::ostream &os, String &rhs)\n{\n\tauto str = unique_ptr<char[]>(new char[rhs.size() + 1]());\n\tmemcpy(str.get(), rhs.elements, rhs.size());\n\tos << str.get();\n\treturn os;\n}\n\nallocator<char> String::alloc;\n\nString::String(const initializer_list<char> &lst)\n{\n\tauto newdata = alloc_n_copy(lst.begin(), lst.end());\n\telements = newdata.first;\n\tfirst_free = cap = newdata.second;\n}\n\nString::String(const char *str)\n{\n\tif (!str) return;\n\n\tauto newdata = alloc_n_copy(str, str + strlen(str));\n\telements = newdata.first;\n\tfirst_free = cap = newdata.second;\n}\n\nString::String(const String &s)\n{\n\t// 调用alloc_n_copy分配空间以容纳与s中一样多的元素\n\tauto newdata = alloc_n_copy(s.begin(), s.end());\n\telements = newdata.first;\n\tfirst_free = cap = newdata.second;\n\n\tcout << \"String::String(const String &s)\" << endl;\n}\n\nString::String(String &&s) noexcept\n\t: elements(s.elements), first_free(s.first_free), cap(s.cap)\n{\n\ts.elements = s.first_free = s.cap = nullptr;\n\n\tcout << \"String::String(String &&s) noexcept\" << endl;\n}\n\nString::~String()\n{\n\tfree();\n}\n\nString &String::operator=(String &&rhs) noexcept\n{\n\tif (this != &rhs) {\n\t\telements = rhs.elements;\n\t\tfirst_free = rhs.first_free;\n\t\tcap = rhs.cap;\n\n\t\trhs.elements = rhs.first_free = rhs.cap = nullptr;\n\t}\n\treturn *this;\n}\n\nbool operator==(const String &lhs, const String &rhs)\n{\n\tauto lhs_beg = lhs.elements;\n\tauto rhs_beg = rhs.elements;\n\n\twhile (lhs_beg != lhs.first_free && rhs_beg != rhs.first_free) {\n\t\tif (*lhs_beg != *rhs_beg)\n\t\t\treturn false;\n\n\t\t++lhs_beg;\n\t\t++rhs_beg;\n\t}\n\n\tif (lhs_beg == lhs.first_free && rhs_beg == rhs.first_free)\n\t\treturn true;\n\n\treturn false;\n}\n\nbool operator!=(const String &lhs, const String &rhs)\n{\n\treturn !(lhs == rhs);\n}\n\nString &String::operator=(const String &rhs)\n{\n\t// 调用alloc_n_copy分配内存，大小与rhs中元素占用空间一样多\n\tauto data = alloc_n_copy(rhs.begin(), rhs.end());\n\tfree();\n\telements = data.first;\n\tfirst_free = cap = data.second;\n\n\tcout << \"String &String::operator=(const String &rhs)\" << endl;\n\treturn *this;\n}\n\nString& String::operator=(const char *str)\n{\n\tif (str) {\n\n\t\tauto data = alloc_n_copy(str, str + strlen(str));\n\t\tfree();\n\t\telements = data.first;\n\t\tfirst_free = cap = data.second;\n\t}\n\treturn *this;\n}\n\nbool operator<(const String &lhs, const String &rhs)\n{\n\tauto lhs_beg = lhs.elements;\n\tauto rhs_beg = rhs.elements;\n\n\twhile (lhs_beg != lhs.first_free && rhs_beg != rhs.first_free) {\n\t\tif (*lhs_beg > *rhs_beg)\n\t\t\treturn false;\n\t\telse if (*lhs_beg < *rhs_beg)\n\t\t\treturn true;\n\n\t\t++lhs_beg;\n\t\t++rhs_beg;\n\t}\n\n\tif (lhs_beg == lhs.first_free && rhs_beg <= rhs.first_free)\n\t\treturn true;\n\n\treturn false;\n}\n\nvoid String::push_back(const char &s)\n{\n\tchk_n_alloc(); // 确保有空间容纳新元素\n\t// 在first_free指向的元素中构造s的副本\n\talloc.construct(first_free++, s);\n}\n\npair<char*, char*>\nString::alloc_n_copy(const char *b, const char *e)\n{\n\t// 分配空间保存给定范围中的元素\n\tauto data = alloc.allocate(e - b);\n\n\t// 初始化并返回一个pair，该pair由data和uninitialized_copy的返回值构成\n\treturn {data, uninitialized_copy(b, e, data)};\n}\n\nvoid String::free()\n{\n\t// 不能传递给deallocate一个空指针，如果elements为0，函数什么也不做\n\tif (elements) {\n\t\t// 逆序销毁旧元素\n\t\t\n\t\t//for (auto p = first_free; p != elements;) alloc.destroy(--p);\n\n\t\tfor_each(elements, first_free, [this](char &s){ alloc.destroy(&s); } );\n\n\t\talloc.deallocate(elements, cap - elements);\n\t}\n}\n\nvoid String::reallocate()\n{\n\t// 我们将分配当前大小两倍的内存空间\n\tauto newcapacity = size() ? 2 * size() : 1;\n\t// 分配新内存\n\tauto newdata = alloc.allocate(newcapacity);\n\t// 将数据从旧内存移动到新内存\n\tauto dest = newdata;  // 指向新数组中下一个空闲位置\n\tauto elem = elements; // 指向旧数组中下一个元素\n\tfor (size_t i = 0; i != size(); ++i)\n\t\talloc.construct(dest++, std::move(*elem++));\n\tfree(); // 一旦我们移动完元素就释放旧内存空间\n\t// 更新我们的数据结构，执行新元素\n\telements = newdata;\n\tfirst_free = dest;\n\tcap = elements + newcapacity;\n}\n\nvoid String::reserve(size_t n)\n{\n\tif (n <= capacity()) return;\n\n\tauto newdata = alloc.allocate(n);\n\n\t// copy old data\n\tauto dest = newdata;\n\tauto elem = elements;\n\tfor (size_t i = 0; i != size(); ++i)\n\t\talloc.construct(dest++, std::move(*elem++));\n\tfree();\n\n\telements = newdata;\n\tfirst_free = dest;\n\tcap = elements + n;\n}\n\nvoid String::resize(size_t n)\n{\n\tif (n == size()) return;\n\n\tif (n < size())\n\t{\n\t\tdo {\n\t\t\talloc.destroy(--first_free);\n\t\t} while (first_free != elements + n);\n\n\t\treturn;\n\t}\n\n\tif (n > size())\n\t{\n\t\tdo {\n\t\t\tchk_n_alloc();\n\n\t\t\talloc.construct(first_free++);\n\t\t} while (first_free != elements + n);\n\n\t\treturn;\n\t}\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_String/String.h",
    "content": "#ifndef STRING_H\n#define STRING_H\n\nclass String\n{\n\tfriend std::ostream& operator<<(std::ostream &os, String &rhs);\n\tfriend bool operator==(const String &lhs, const String &rhs);\n\tfriend bool operator!=(const String &lhs, const String &rhs);\n\tfriend bool operator<(const String &lhs, const String &rhs);\n\npublic:\n\tString() :\t// allocator成员进行默认初始化\n\t\telements(nullptr), first_free(nullptr), cap(nullptr) {}\n\tString(const std::initializer_list<char>&);\n\tString(const char*);\n\tString& operator=(const char*);\n\tchar& operator[](size_t n) { return elements[n]; }\n\tconst char& operator[](size_t n) const { return elements[n]; }\n\n\tString(const String&);\t\t\t\t\t\t// 拷贝构造函数\n\tString(String&&) noexcept;\t\t\t\t\t// 移动构造函数\n\tString& operator=(const String&);\t\t\t// 拷贝赋值运算符\n\tString& operator=(String&&) noexcept;\t\t// 移动赋值运算符\n\t~String();\t\t\t\t\t\t\t\t\t// 析构函数\n\n\tvoid push_back(const char&);\t\t\t\t// 拷贝元素\n\tsize_t size() const { return first_free - elements; }\n\tsize_t capacity() const { return cap - elements; }\n\tvoid reserve(size_t n);\n\tvoid resize(size_t n);\n\tchar *begin() const { return elements; }\n\tchar *end() const { return first_free; }\n\nprivate:\n\tstatic std::allocator<char> alloc;\t// 分配元素\n\t// 被添加元素的函数所使用\n\tvoid chk_n_alloc()\n\t\t{ if (size() == capacity()) reallocate(); }\n\t// 工具函数，被拷贝构造函数、赋值运算符和析构函数所使用\n\tstd::pair<char*, char*> alloc_n_copy\n\t\t(const char*, const char*);\n\tvoid free();\t\t\t\t\t\t\t\t// 销毁元素并释放内存\n\tvoid reallocate();\t\t\t\t\t\t\t// 获得更多内存并拷贝已有元素\n\t\n\tchar *elements;\t\t\t\t\t\t// 指向数组首元素的指针\n\tchar *first_free;\t\t\t\t\t// 指向数组第一个空闲元素的指针\n\tchar *cap;\t\t\t\t\t\t\t// 指向数组尾后位置的指针\n\n};\n\n#endif // STRING_H\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/example_String/main.cpp",
    "content": "// example: 自定义String简化版本（p470练习13.44）\n\n#include <iostream>\n#include <vector>\n\n#include \"String.h\"\n\nusing namespace std;\n\nvoid func1()\n{\n\tString v;\n\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tcout << \"reserve 4 ...\" << endl;\n\tv.reserve(4);\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tcout << \"push_back 2 char ...\" << endl;\n\tv.push_back('a');\n\tv.push_back('b');\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tcout << \"print ...\" << endl;\n\tfor (auto p = v.begin(); p != v.end(); ++p)\n\t\tcout << *p << \" \";\n\tcout << endl;\n\n\tcout << \"resize to 0 ...\" << endl;\n\tv.resize(0);\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tcout << \"resize to 97 ...\" << endl;\n\tv.resize(97);\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n}\n\nvoid func2()\n{\n\tString v{'a', 'b', 'c', 'd'};\n\n\tfor (auto p = v.begin(); p != v.end(); ++p)\n\t\tcout << *p << \" \";\n\tcout << endl;\n\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n}\n\nvoid func3()\n{\n\tString v;\n\tcout << v << endl;\n\n\tv = \"Hello world\";\n\tcout << v << endl;\n}\n\nvoid func4()\n{\n\tvector<String> v;\n\tv.push_back(\"Hello World\");\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tv.push_back(\"Hi\");\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tv.push_back(\"WoW\");\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n}\n\nvoid func5()\n{\n\tString s1 = \"Hello\";\n\tString s2 = \"Hillo\";\n\tString s3 = \"Hillo\";\n\tString s4 = \"Him\";\n\n\tif (s1 != s2)\n\t\tcout << \"s1 != s2\" << endl;\n\n\tif (s2 == s3)\n\t\tcout << \"s2 == s3\" << endl;\n\n\tif (s3 < s4)\n\t\tcout << \"s3 < s4\" << endl;\n}\n\nint main()\n{\n\tfunc1();\n\t//func2();\n\t//func3();\n\t//func4();\n\t//func5();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_01.md",
    "content": "练习13.1：拷贝构造函数是什么？什么时候使用它？\n\n---\n\n一种构造函数，将新对象初始化为同类型另一个对象的副本。当向函数传递对象，或以传值方式从函数返回对象时，会隐式使用拷贝构造函数。如果我们未提供拷贝构造函数，编译器会为我们合成一个。\n\n当使用拷贝初始化时，也会调用拷贝构造函数：\n\n```\nT a = b;\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_02.md",
    "content": "练习13.2：解释为什么下面的声明是非法的：\n\n```\nSales_data::Sales_data(Sales_data rhs);\n```\n\n---\n\n拷贝构造函数的参数应该是一个本类型的引用，如果不是引用，那么对参数的初始化还会调用拷贝构造函数，如此则无限循环。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_03.md",
    "content": "练习13.3：当我们拷贝一个StrBlob时，会发生什么？拷贝一个StrBlobPtr呢？\n\n---\n\n- 拷贝StrBlob，会拷贝shared_ptr，从而使其引用计数递增。\n\n- 拷贝StrBlobPtr，不会增加其引用计数\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_04.md",
    "content": "练习13.4：假定Point是一个类类型，它有一个public的拷贝构造函数，指出下面程序片段中哪些地方使用了拷贝构造函数：\n\n```\nPoint global;\nPoint foo_bar(Point arg)\n{\n\tPoint local = arg, *heap = new Point(global);\n\t*heap = local;\n\tPoint pa[4] = {local, *heap};\n\treturn *heap;\n}\n```\n\n---\n\n- Point arg，调用函数时用于初始化参数\n\n- Point local = arg\n\n- *heap = new Point(global)\n\n- Point pa[4] = {local, *heap}; 使用了两次拷贝构造函数构造数组元素\n\n- return *heap，用于初始化返回值\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_05.cpp",
    "content": "// 练习13.5：给定下面的类框架，编写一个拷贝构造函数，拷贝所有成员\n// 你的构造函数应该动态分配一个新的string（参见12.1.2节，第407页\n// ），并将对象拷贝到ps指向的位置，而不是ps本身的位置。\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nclass HasPtr {\npublic:\n\tHasPtr(const std::string &s = std::string()) :\n\t\tps(new std::string(s)), i(0) {}\n\n\tHasPtr(const HasPtr &hp) :\n\t\tps(new std::string(*hp.ps)), i(hp.i) {}\nprivate:\n\tstd::string *ps;\n\tint i;\n};\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_06.md",
    "content": "练习13.6：拷贝赋值运算符是什么？什么时候使用它？合成拷贝赋值运算符完成什么工作？什么时候会生成合成拷贝赋值运算符？\n\n---\n\n- 拷贝赋值运算符是什么：拷贝赋值运算符接受一个本类型对象的赋值运算符版本。通常，拷贝赋值运算符的参数是一个const的引用，并返回指向本对象的引用。如果类未显式定义拷贝赋值运算符，编译器会为它合成一个。\n\n- 什么时候使用：当发生赋值操作的时候会使用。\n\n- 合成拷贝赋值运算符完成什么工作：将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员。\n\n- 什么时候会生成合成拷贝赋值运算符：如果一个类未定义自己的拷贝赋值运算符，编译器会为它生成一个。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_07.md",
    "content": "练习13.7：当我们将一个StrBlob赋值给另一个StrBlob时，会发生什么？赋值StrBlobPtr呢？\n\n---\n\n- 赋值StrBlob时，把右侧的底层智能指针赋值给左侧的，左侧的底层智能指针引用计数递减，右侧的递增。\n\n- 赋值StrBlobPtr时，把右侧的底层weak_ptr赋值给左侧的，不影响原双方智能指针的引用计数。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_08.cpp",
    "content": "// 练习13.8：为13.1.1节（第443页）练习13.5中的HasPtr类编写赋值运算符。类似\n// 拷贝构造函数，你的赋值运算符应该将对象拷贝到ps指向的位置。\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nclass HasPtr {\npublic:\n\tHasPtr(const std::string &s = std::string()) :\n\t\tps(new std::string(s)), i(0) {}\n\n\tHasPtr(const HasPtr &hp) :\n\t\tps(new std::string(*hp.ps)), i(hp.i) {}\n\n\tHasPtr& operator=(const HasPtr &hp) {\n\t\tauto new_ps = new std::string(*hp.ps);\n\t\tdelete ps;\n\t\tps = new_ps;\n\t\ti = hp.i;\n\t\treturn *this;\n\t}\nprivate:\n\tstd::string *ps;\n\tint i;\n};\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_09.md",
    "content": "练习13.9：析构函数是什么？合成析构函数完成什么工作？什么时候会生成合成析构函数？\n\n---\n\n析构函数是什么：特殊的成员函数，当对象离开作用域或被释放时进行清理工作。编译器会自动销毁每个数据成员。类类型的成员通过其析构函数来销毁；而内置类型或复合类型的成员的销毁则不需要做任何工作。特别是，析构函数不会释放指针成员指向的对象。\n\n合成析构函数完成什么工作：函数体为空。\n\n什么时候会生成合成析构函数：当一个类未定义自己的析构函数时，编译器会为它定义一个合成析构函数。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_10.md",
    "content": "练习13.10：当一个StrBlob对象销毁时会发生什么？一个StrBlobPtr对象销毁时呢？\n\n---\n\nStrBlob销毁时，其智能指针成员会被销毁，如果其引用计数为0，则会把其指向的对象也销毁。\n\nStrBlobPtr销毁时，其weak_ptr成员会被销毁，不会影响其绑定的智能指针的引用计数。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_11.cpp",
    "content": "// 练习13.11：为前面练习中的HasPtr类添加一个析构函数。\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nclass HasPtr {\npublic:\n\tHasPtr(const std::string &s = std::string()) :\n\t\tps(new std::string(s)), i(0) {}\n\n\tHasPtr(const HasPtr &hp) :\n\t\tps(new std::string(*hp.ps)), i(hp.i) {}\n\n\tHasPtr& operator=(const HasPtr &hp) {\n\t\tauto new_ps = new std::string(*hp.ps);\n\t\tdelete ps;\n\t\tps = new_ps;\n\t\ti = hp.i;\n\t\treturn *this;\n\t}\n\n\t~HasPtr() { delete ps; }\nprivate:\n\tstd::string *ps;\n\tint i;\n};\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_12.md",
    "content": "练习13.12：在下面的代码片段中会发生几次析构函数调用？\n\n```\nbool fcn(const Sales_data *trans, Sales_data accum)\n{\n\tSales_data item1(*trans), item2(accum);\n\treturn item1.isbn() != item2.isbn();\n}\n```\n\n---\n\n- accum，函数结束时析构\n\n- item1，函数结束时析构\n\n- item2，函数结束时析构\n\n- item1.isbn() != item2.isbn()表达式产生了两个临时的string对象，它们在表达式结束时析构\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_13.cpp",
    "content": "// 练习13.13：理解拷贝控制成员和构造函数的一个好方法是定义一个简单的类，\n// 为该类定义这些成员，每个成员都打印出自己的名字\n// 给X添加拷贝赋值运算符和析构函数，并编写一个程序以不同方式使用X的对象；\n// 将它们作为非引用和引用参数传递；动态分配它们；将它们放于容器中；\n// 诸如此类。观察程序的输出，直到你确认理解了什么时候会使用拷贝控制成员，\n// 以及为什么会使用它们。当你观察程序输出时，记住编译器可以略过对拷贝构\n// 造函数的调用。\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nstruct X {\n\tX() { std::cout << \"X()\" << std::endl; }\n\tX(const X&) { std::cout << \"X(const X&)\" << std::endl; }\n\tX& operator=(const X&) { std::cout << \"X& operator=(const X&)\" << std::endl; return *this; }\n\t~X() { std::cout << \"~X()\" << std::endl; }\n};\n\nvoid f1(X)\n{\n}\n\nvoid f2(X&)\n{\n}\n\nint main()\n{\n\t//X x;\n\t//f1(x);\n\t//f2(x);\n\t//X x2;\n\t//x2 = x;\n\t\n\t//auto px = new X();\n\t//delete px;\n\t\n\tstd::vector<X> x_vec;\n\t//x_vec.emplace_back();\n\tx_vec.push_back(X());\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_14.md",
    "content": "练习13.14：假定numbered是一个类，它有一个默认构造函数，能为每个对象生成一个唯一的序号，保存在名为mysn的数据成员中。假定numbered使用合成的拷贝控制成员，并给定如下函数：\n\n```\nvoid f(numbered s) { cout << s.mysn << endl; }\n```\n\n则下面代码输出什么内容？\n\n```\nnumbered a, b = a, c = b;\nf(a); f(b); f(c);\n```\n\n---\n\n输出一样的内容。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_15.md",
    "content": "练习13.15：假定numbered定义了一个拷贝构造函数，能生成一个新的序号。这会改变上一题中调用的输出结果吗？如果会改变，为什么？新的输出结果是什么？\n\n---\n\n会改变，因为mysn成员的内容是新的唯一的序号。新的输出结果将是3个不同的序号，并且有别于a, b, c的序号。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_16.md",
    "content": "练习13.16：如果f中的参数是const numbered&，将会怎样？这会改变输出结果吗？如果会改变，为什么？新的输出结果是什么？\n\n---\n\n会改变，因为不会创建新的序号了。新的输出结果是三个不同的序号，它们就是a, b, c的序号。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_17.cpp",
    "content": "// 练习13.17：分别编写前三题所描述的numbered和f， 验证你是否正确预测了输出结果。\n\n#include <iostream>\n\nusing namespace std;\n\nint gen_number()\n{\n\tstatic int n = 0;\n\treturn ++n;\n}\n\nnamespace exercise1\n{\n\tstruct numbered\n\t{\n\t\tnumbered() : mysn(gen_number()) {}\n\t\tint mysn;\n\t};\n\n\tvoid f(numbered s) { cout << s.mysn << endl; }\n}\n\nnamespace exercise2\n{\n\tstruct numbered\n\t{\n\t\tnumbered() : mysn(gen_number()) {}\n\t\tnumbered(numbered &n) : mysn(gen_number()) {}\n\t\tint mysn;\n\t};\n\n\tvoid f(numbered s) { cout << s.mysn << endl; }\n}\n\nnamespace exercise3\n{\n\tstruct numbered\n\t{\n\t\tnumbered() : mysn(gen_number()) {}\n\t\tnumbered(numbered &n) : mysn(gen_number()) {}\n\t\tint mysn;\n\t};\n\n\tvoid f(const numbered &s) { cout << s.mysn << endl; }\n}\n\nint main()\n{\n\t// 练习13.14\n\tcout << \"exercise 13.14:\" << endl;\n\t{\n\t\tusing namespace exercise1;\n\t\tnumbered a, b = a, c = b;\n\t\tf(a); f(b); f(c); // 完全一样\n\n\t\tcout << \"a: \" << a.mysn << endl\n\t\t\t << \"b: \" << b.mysn << endl\n\t\t\t << \"c: \" << c.mysn << endl;\n\t}\n\n\t// 练习13.15\n\tcout << \"exercise 13.15:\" << endl;\n\t{\n\t\tusing namespace exercise2;\n\t\tnumbered a, b = a, c = b;\n\t\tf(a); f(b); f(c); // 都不一样，且有别于a,b,c\n\n\t\tcout << \"a: \" << a.mysn << endl\n\t\t\t << \"b: \" << b.mysn << endl\n\t\t\t << \"c: \" << c.mysn << endl;\n\t}\n\n\t// 练习13.16\n\tcout << \"exercise 13.16:\" << endl;\n\t{\n\t\tusing namespace exercise3;\n\t\tnumbered a, b = a, c = b;\n\t\tf(a); f(b); f(c); // 都不一样，且等于a,b,c\n\n\t\tcout << \"a: \" << a.mysn << endl\n\t\t\t << \"b: \" << b.mysn << endl\n\t\t\t << \"c: \" << c.mysn << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_18.cpp",
    "content": "// 练习13.18：定义一个Employee类，它包含雇员的姓名和唯一的雇员编号。为这个类\n// 定义默认构造函数，以及接受一个表示雇员姓名的string的构造函数。每个构造函\n// 数应该通过递增一个static数据成员来生成一个唯一的证号。\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nclass Employee\n{\npublic:\n\tEmployee() : m_id(id_auto) { gen_id(); }\n\tEmployee(string &name) : m_id(id_auto), m_name(name) { gen_id(); }\n\n\tvoid Print() const { cout << \"id: \" << m_id << \"\\tname: \" << m_name << endl; }\n\nprivate:\n\tstatic void gen_id() { ++id_auto; }\n\n\tstatic int id_auto;\n\tint m_id;\n\tstring m_name;\n};\n\nint Employee::id_auto = 0;\n\nint main()\n{\n\tstring name = \"liudiwen\";\n\tEmployee e(name);\n\te.Print();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_19.cpp",
    "content": "// 练习13.19：你的Employee类需要定义它自己的拷贝控制成员吗？如果需要，为什么？\n// 如果不需要，为什么？实现你认为Employee需要的拷贝控制成员。\n\n// 我认为不需要，认为一个员工实体应该是唯一的。\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nclass Employee\n{\npublic:\n\tEmployee() : m_id(id_auto) { gen_id(); }\n\tEmployee(string &name) : m_id(id_auto), m_name(name) { gen_id(); }\n\n\tvoid Print() const { cout << \"id: \" << m_id << \"\\tname: \" << m_name << endl; }\n\nprivate:\n\tEmployee(const Employee&) = delete;\n\tEmployee& operator=(const Employee&) = delete;\n\tstatic void gen_id() { ++id_auto; }\n\n\tstatic int id_auto;\n\tint m_id;\n\tstring m_name;\n};\n\nint Employee::id_auto = 0;\n\nint main()\n{\n\tstring name = \"liudiwen\";\n\tEmployee e(name);\n\te.Print();\n\n\t//Employee e2(e); // 编译不通过\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_20.md",
    "content": "练习13.20：解释当我们拷贝、赋值或销毁TextQuery和QueryResult类（参见12.3节，第430页）对象时会发生什么？\n\n---\n\n当执行合成的拷贝构造函数、拷贝赋值运算符、析构函数。会将成员变量都拷贝一份过去。由于使用了智能指针，因此析构的时候会减少引用计数并在正确的时机释放掉其对象。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_21.md",
    "content": "练习13.21：你认为TextQuery和QueryResult类需要定义它们自己版本的拷贝控制成员吗？如果需要，为什么？如果不需要，为什么？实现你认为这两个类需要的拷贝控制操作。\n\n---\n\n不需要，因为使用了智能指针绑定动态对象。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_22.md",
    "content": "练习13.22：假定我们希望HasPtr的行为像一个值。即，对于对象所指向的string成员，每个对象都有一份自己的拷贝。我们将在下一节介绍拷贝控制成员的定义。但是，你已经学习了定义这些成员所需的所有知识。在继续学习下一节之前，为HasPtr编写拷贝构造函数和拷贝赋值运算符。\n\n---\n\n见[HasPtr](./exercise_13_11.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_23.md",
    "content": "练习13.23：比较上一节练习中你编写的拷贝控制成员和这一节中的代码。确定你理解了你的代码和我们的代码之间的差异（如果有的话）。\n\n---\n\n略。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_24.md",
    "content": "练习13.24：如果本节中的HasPtr版本未定义析构函数，将会发生什么？如果未定义拷贝构造函数，将会发生什么？\n\n---\n\n如果没有析构函数，编译器会合成一个，但是并不会释放指针指向的对象，从而导致内存泄漏。\n\n如果未定义拷贝构造函数，编译器会合成一个，但是只是浅拷贝了指针，可能会造成悬空指针的问题。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_25.md",
    "content": "练习13.25：假定希望定义StrBlob的类值版本，而且希望继续使用shared_ptr，这样我们的StrBlobPtr类就仍能使用指向vector的weak_ptr了。你修改后的类将需要一个拷贝构造函数和一个拷贝赋值运算符，但不需要析构函数。解释拷贝构造函数和拷贝赋值运算符必须要做什么。解释为什么不需要析构函数。\n\n---\n\n必须创建一个新的动态对象，绑定到StrBlob的智能指针上面。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_26.cpp",
    "content": "// 练习13.26：对上一题中描述的StrBlob类，编写你自己的版本。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <initializer_list>\n#include <memory>\n\nclass StrBlob {\npublic:\n\ttypedef std::vector<std::string>::size_type size_type;\n\n\tStrBlob() : data(std::make_shared<std::vector<std::string>>()) {}\n\tStrBlob(std::initializer_list<std::string> il) :\n\t\t\tdata(std::make_shared<std::vector<std::string>>(il)) {}\n\n\tStrBlob(const StrBlob &sb) : data(std::make_shared<std::vector<std::string>>(*sb.data)) {}\n\n\tStrBlob& operator=(const StrBlob &sb)\n\t{\n\t\tdata = std::make_shared<std::vector<std::string>>(*sb.data);\n\t\treturn *this;\n\t}\n\n\tinline size_type size() const { return data->size(); }\n\tinline bool empty() const { return data->empty(); }\n\n\t// 添加和删除元素\n\tinline void push_back(const std::string &t) { data->push_back(t); }\n\tinline void pop_back();\n\n\t// 元素访问\n\tinline std::string& front();\n\tinline std::string& back();\n\tinline std::string& front() const;\n\tinline std::string& back() const;\n\n\tstd::string& operator[](size_type i) { return (*data)[i]; } // 省略了check\n\nprivate:\n\tstd::shared_ptr<std::vector<std::string>> data;\n\n\t// 如果data[i]不合法，抛出一个异常\n\tinline void check(size_type i, const std::string &msg) const;\n};\n\nvoid StrBlob::check(size_type i, const std::string &msg) const\n{\n\tif (i >= data->size())\n\t\tthrow std::out_of_range(msg);\n}\n\nstd::string& StrBlob::front()\n{\n\t// 如果vector为空，check会抛出一个异常\n\tcheck(0, \"front on empty StrBlob\");\n\treturn data->front();\n}\n\nstd::string& StrBlob::back()\n{\n\tcheck(0, \"back on empty StrBlob\");\n\treturn data->back();\n}\n\nvoid StrBlob::pop_back()\n{\n\tcheck(0, \"pop_back on empty StrBlob\");\n\treturn data->pop_back();\n}\n\nstd::string& StrBlob::front() const\n{\n\tcheck(0, \"front on empty StrBlob\");\n\treturn data->front();\n}\n\nstd::string& StrBlob::back() const\n{\n\tcheck(0, \"back on empty StrBlob\");\n\treturn data->back();\n}\n\nint main()\n{\n\tusing namespace std;\n\n\tStrBlob sb1;\n\tsb1.push_back(\"hi\");\n\tcout << \"sb1[0]: \" << sb1[0] << endl;\n\n\tStrBlob sb2 = sb1;\n\tcout << \"sb2[0]: \" << sb2[0] << endl;\n\t\n\tsb2[0] = \"wow\";\n\n\tcout << \"sb1[0]: \" << sb1[0] << endl;\n\tcout << \"sb2[0]: \" << sb2[0] << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_27.md",
    "content": "练习13.27：定义你自己的使用引用计数版本的HasPtr。\n\n---\n\n见[HasPtr](./example_Reference_Count/HasPtr.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_28.cpp",
    "content": "// 练习13.28：给定下面的类，为其实现一个默认构造函数和必要的拷贝控制成员。\n\n// TreeNode采用行为像指针的设计，拷贝的对象共享底层数据\n// BinStrTree阻止其拷贝\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nclass TreeNode {\npublic:\n\tTreeNode(const std::string &s) : value(s), count(0), left(nullptr), right(nullptr), use(new size_t(1)) { cout << \"TreeNode(const std::string &s): \" << s << endl;}\n\n\t~TreeNode()\n\t{\n\t\tcout << \"~TreeNode()\" << endl;\n\n\t\tif (--*use == 0)\n\t\t{\n\t\t\tdelete use;\n\t\t\tif (left) delete left;\n\t\t\tif (right) delete right;\n\n\t\t\tcout << \"~TreeNode() real delete\" << endl;\n\t\t}\n\t}\n\n\tTreeNode(const TreeNode &node) : value(node.value), count(node.count), left(node.left), right(node.right), use(node.use)\n\t{\n\t\t++*use;\n\n\t\tcout << \"TreeNode(const TreeNode &node): \" << value << endl;\n\t}\n\n\tTreeNode& operator=(const TreeNode &node)\n\t{\n\t\tcout << \"TreeNode& operator=(const TreeNode &node): \" << node.value << endl;\n\n\t\t++*node.use;\n\t\tif (--*use == 0)\n\t\t{\n\t\t\tdelete use;\n\t\t\tif (left) delete left;\n\t\t\tif (right) delete right;\n\t\t}\n\n\t\tvalue = node.value;\n\t\tcount = node.count;\n\t\tleft = node.left;\n\t\tright = node.right;\n\t\treturn *this;\n\t}\n\nprivate:\n\tstd::string value;\n\tint count;\n\tTreeNode *left;\n\tTreeNode *right;\n\n\tsize_t *use;\n};\n\nclass BinStrTree {\npublic:\n\tBinStrTree() : root(new TreeNode(\"root\")) { cout << \"BinStrTree()\" << endl; }\n\n\t~BinStrTree() { cout << \"~BinStrTree()\" << endl; delete root; }\n\n\tBinStrTree(const BinStrTree&) = delete;\n\tBinStrTree& operator=(const BinStrTree&) = delete;\n\n\tTreeNode Root() const { return *root; }\n\nprivate:\n\tTreeNode *root;\n};\n\nint main()\n{\n\tBinStrTree tree;\n\t\n\tauto root = tree.Root();\n\n\t//BinStrTree tree2 = tree; // cant copy tree\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_29.md",
    "content": "练习13.29：解释swap(HasPtr&, HasPtr&)中对swap的调用不会导致递归循环。\n\n---\n\n因为函数内调用的swap版本不是主调函数的swap版本。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_30.cpp",
    "content": "// 练习13.30：为你的类值版本的HasPtr编写swap函数，并测试它。为你的swap函数\n// 添加一个打印语句，指出函数什么时候执行。\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nclass HasPtr {\n\tfriend void swap(HasPtr&, HasPtr&);\n\npublic:\n\tHasPtr(const std::string &s = std::string()) :\n\t\tps(new std::string(s)), i(0) {}\n\n\tHasPtr(const HasPtr &hp) :\n\t\tps(new std::string(*hp.ps)), i(hp.i) {}\n\n\tHasPtr& operator=(const HasPtr &hp) {\n\t\tauto new_ps = new std::string(*hp.ps);\n\t\tdelete ps;\n\t\tps = new_ps;\n\t\ti = hp.i;\n\t\treturn *this;\n\t}\n\n\t~HasPtr() { delete ps; }\n\n\tstd::string Value() const { return *ps; }\nprivate:\n\tstd::string *ps;\n\tint i;\n};\n\nvoid swap(HasPtr &lhs, HasPtr &rhs)\n{\n\tcout << \"swap(HasPtr &lhs, HasPtr &rhs)\" << endl;\n\n\tusing std::swap;\n\tswap(lhs.ps, rhs.ps);\n\tswap(lhs.i, rhs.i);\n}\n\nint main()\n{\n\tHasPtr p1(\"p1\"), p2(\"p2\");\n\tswap(p1, p2);\n\n\tcout << \"p1: \" << p1.Value() << endl;\n\tcout << \"p2: \" << p2.Value() << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_31.cpp",
    "content": "// 练习13.31：为你的HasPtr类定义一个<运算符，并定义一个HasPtr的vector。\n// 为这个vector添加一些元素，并对它执行sort。注意何时会调用swap。\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <algorithm>\n\nusing namespace std;\n\nclass HasPtr {\n\tfriend void swap(HasPtr&, HasPtr&);\n\npublic:\n\tHasPtr(const std::string &s = std::string()) :\n\t\tps(new std::string(s)), i(0) {}\n\n\tHasPtr(const HasPtr &hp) :\n\t\tps(new std::string(*hp.ps)), i(hp.i) {}\n\n\tHasPtr& operator=(HasPtr hp);\n\n\tbool operator<(const HasPtr &rhs) const { return *ps > *rhs.ps; }\n\n\t~HasPtr() { delete ps; }\n\n\tstd::string Value() const { return *ps; }\nprivate:\n\tstd::string *ps;\n\tint i;\n};\n\nvoid swap(HasPtr &lhs, HasPtr &rhs)\n{\n\tcout << \"swap(HasPtr &lhs, HasPtr &rhs)\" << endl;\n\n\tusing std::swap;\n\tswap(lhs.ps, rhs.ps);\n\tswap(lhs.i, rhs.i);\n}\n\nHasPtr& HasPtr::operator=(HasPtr hp)\n{\n\tswap(*this, hp);\n\treturn *this;\n}\n\nint main()\n{\n\tstd::vector<HasPtr> vec;\n\tvec.emplace_back(\"A\");\n\tvec.emplace_back(\"B\");\n\tvec.emplace_back(\"C\");\n\n\tcout << \"before sort ...\" << endl;\n\tfor (const auto &i : vec)\n\t\tcout << i.Value() << \" \";\n\tcout << endl;\n\n\tsort(vec.begin(), vec.end());\n\tcout << \"after sort ...\" << endl;\n\tfor (const auto &i : vec)\n\t\tcout << i.Value() << \" \";\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_32.md",
    "content": "练习13.32：类指针的HasPtr版本会从swap函数受益吗？如果会，得到了什么益处？如果不是，为什么？\n\n---\n\n不会。因为类指针的版本共享底层数据，不需要分配额外的内存——这正是swap可以优化的地方。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_33.md",
    "content": "练习13.33：为什么Message的成员save和remove的参数是一个Folder&？为什么我们不将参数定义为Folder或是const Folder&？\n\n---\n\n因为在里面要修改实参Folder。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_34.md",
    "content": "练习13.34：编写本节所描述的Message。\n\n见\n\n- [Message.h](./example_Copy_Control/Message.h)\n\n- [Message.cpp](./example_Copy_Control/Message.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_35.md",
    "content": "练习13.35：如果Message使用合成的拷贝控制成员，将会发生什么？\n\n---\n\n将无法把自己加入到Folder里面。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_36.md",
    "content": "练习13.36：设计并实现对应的Folder类。此类应该保存一个指向Folder中包含的Message的set。\n\n---\n\n见[案例](./example_Copy_Control)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_37.md",
    "content": "练习13.37：为Message类添加成员，实现向folders添加或删除一个给定的Folder\\*。这两个成员类似Folder类的addMsg和remMsg操作。\n\n---\n\nsave和remove就满足了如上要求。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_38.md",
    "content": "练习13.38：我们并未使用拷贝和交换方式来设计Message的赋值运算符。你认为其原因是什么？\n\n---\n\n拷贝和交换的方式适用于动态分配资源的场景，这样可以提高效率。而Message不是动态分配的，不适用这种场景。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_39.md",
    "content": "练习13.39：编写你自己版本的StrVec，包括自己版本的reserve、capacity（参见9.4节，第318页）和resize（参见9.3.5节，第314页）。\n\n---\n\n见[StrVec](./example_StrVec)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_40.md",
    "content": "练习13.40：为你的StrVec类添加一个构造函数，它接受一个`initializer_list<string>`参数。\n\n---\n\n见[StrVec](./example_StrVec)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_41.md",
    "content": "练习13.41：在push_back中，我们为什么在construct调用中使用后置递增运算？如果使用前置递增运算的话，会发生什么？\n\n---\n\n因为first_free是数组中首个可构造的内存，如果使用前置递增，将返回递增后的副本，那么构造的内存就错了。\n\nPS：中文版本的书题目有误。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_42.md",
    "content": "练习13.42：在你的TextQuery和QueryResult类（参加12.3节，第431页）中用你的StrVec类代替vector<string>，以此来测试你的StrVec类。\n\n---\n\n见[案例代码](../ch12_Dynamic_Memory/example_TextQuery_use_StrVec)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_43.md",
    "content": "练习13.43：重写free成员，用for_each和lambda（参加10.3.2节，第346页）来代替for循环destroy元素。你更倾向于哪种实现，为什么？\n\n---\n\n见[案例代码](./example_StrVec/StrVec.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_44.md",
    "content": "练习13.44：编写标准库string类的简化版本，命名为String。你的类应该至少有一个默认构造函数和一个接受C风格字符串指针参数的构造函数。使用allocator为你的String类分配所需内存。\n\n---\n\n见[String](./example_String)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_45.md",
    "content": "练习13.45：解释右值引用和左值引用的区别。\n\n---\n\n右值引用：只能绑定到右值。\n\n左值引用：只能绑定到左值。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_46.md",
    "content": "练习13.46：什么类型的引用可以绑定到下面的初始化器上？\n\n```\nint f();\nvector<int> vi(100);\nint? r1 = f(); // &&\nint? r2 = vi[0]; // &\nint? r3 = r1; // &\nint? r4 = vi[0] * f(); // &&\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_47.md",
    "content": "练习13.47：对你在练习13.44（13.5节，第470页）中定义的String类，为它的拷贝构造函数和拷贝赋值运算符添加一条语句，在每次函数执行时打印一条信息。\n\n---\n\n见[String](./example_String)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_48.md",
    "content": "练习13.48：定义一个vector<String>并在其上多次调用push_back。运行你的程序，并观察String被拷贝了多少次。\n\n---\n\n见[String案例func4](./example_String/main.cpp)\n\n观察结果：当vector重新分配空间时，原来的对象也会被拷贝到新的空间。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_49.md",
    "content": "练习13.49：为你的StrVec、String和Message类添加一个移动构造函数和一个移动赋值运算符。\n\n---\n\n见：\n\n- [StrVec](./example_StrVec/StrVec.h)\n\n- [String](./example_String/String.h)\n\n- [Message](./example_Copy_Control/Message.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_50.md",
    "content": "练习13.50：在你的String类的移动操作中添加打印语句，并重新运行13.6.1节（第473页）的练习13.48程序，它使用了一个vector<String>，观察什么时候会避免拷贝。\n\n---\n\n当重新分配空间时会避免调用拷贝构造函数，而使用移动构造函数。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_51.md",
    "content": "练习13.51：虽然unique_ptr不能拷贝，但我们在12.1.5节（第418页）中编写了一个clone函数，它以值方式返回一个unique_ptr。解释为什么函数是合法的，以及为什么它能正确工作。\n\n---\n\n因为这里的源unique_ptr即将销毁，是一个右值引用，执行的是移动操作。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_52.md",
    "content": "练习13.52：详细解释第478页中的HasPtr对象的赋值发生了什么？特别是，一步一步描述hp、hp2以及HasPtr的赋值运算符中的参数rhs的值发生了什么变化。\n\n---\n\nhp = hp2;\n\n由于hp2是一个左值，因此调用拷贝构造函数初始化operator=函数的形参，hp2本身不会发生变化。\n\nhp = std::move(hp2);\n\nhp2经过move得到一个右值引用，因此会调用移动构造函数初始化operator=函数的形参，移动构造函数会修改hp2，使其处于无效状态。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_53.cpp",
    "content": "/*\n * 练习13.53：从底层效率的角度看，HasPtr的赋值运算符并不理想，解释为什么。\n * 为HasPtr实现一个拷贝赋值运算符和一个移动赋值运算符，并比较你的新的移动\n * 赋值运算符中执行的操作和拷贝并交换版本中执行的操作。\n */\n\n\n// 因为operator=函数中，初始化形参会调用一次拷贝构造函数。\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <algorithm>\n\nusing namespace std;\n\nclass HasPtr {\n\tfriend void swap(HasPtr&, HasPtr&);\n\npublic:\n\tHasPtr(const std::string &s = std::string()) :\n\t\tps(new std::string(s)), i(0) {}\n\n\tHasPtr(const HasPtr &hp) :\n\t\tps(new std::string(*hp.ps)), i(hp.i) {}\n\n\t//HasPtr& operator=(HasPtr hp);\n\tHasPtr& operator=(const HasPtr &hp);\n\tHasPtr& operator=(HasPtr &&hp) noexcept;\n\n\tbool operator<(const HasPtr &rhs) const { return *ps > *rhs.ps; }\n\n\t~HasPtr() { if (ps) delete ps; }\n\n\tstd::string Value() const { return *ps; }\nprivate:\n\tstd::string *ps;\n\tint i;\n};\n\nvoid swap(HasPtr &lhs, HasPtr &rhs)\n{\n\tcout << \"swap(HasPtr &lhs, HasPtr &rhs)\" << endl;\n\n\tusing std::swap;\n\tswap(lhs.ps, rhs.ps);\n\tswap(lhs.i, rhs.i);\n}\n\n/*\nHasPtr& HasPtr::operator=(HasPtr hp)\n{\n\tswap(*this, hp);\n\treturn *this;\n}\n*/\n\nHasPtr& HasPtr::operator=(const HasPtr &hp)\n{\n\tcout << \"HasPtr& HasPtr::operator=(const HasPtr &hp)\" << endl;\n\n\tif (ps) {\n\t\tdelete ps;\n\t}\n\tps = new std::string(*hp.ps);\n\ti = hp.i;\n\treturn *this;\n}\n\nHasPtr& HasPtr::operator=(HasPtr &&hp) noexcept\n{\n\tcout << \"HasPtr& HasPtr::operator(HasPtr &&hp)\" << endl;\n\n\tif (ps) {\n\t\tdelete ps;\n\t}\n\tps = hp.ps;\n\ti = hp.i;\n\thp.ps = nullptr;\n\treturn *this;\n}\n\nint main()\n{\n\tHasPtr a(\"A\");\n\tHasPtr b(\"B\");\n\tHasPtr c(\"C\");\n\n\tb = a;\n\tcout << b.Value() << endl;\n\n\tb = std::move(c);\n\tcout << b.Value() << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_54.md",
    "content": "练习13.54：如果我们为HasPtr定义了移动赋值运算符，但未改变拷贝并交换运算符，会发生什么？编写代码验证你的答案。\n\n---\n\nerror: ambiguous overload for ‘operator=’ (operand types are ‘HasPtr’ and ‘std::remove_reference<HasPtr&>::type {aka HasPtr}’)\n\nPS: 不是很懂。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_55.md",
    "content": "练习13.55：为你的StrBlob添加一个右值引用版本的push_back。\n\n---\n\n见[StrBlob](../ch12_Dynamic_Memory/example_StrBlob/StrBlob.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_56.md",
    "content": "练习13.56：如果sorted定义如下，会发生什么：\n\n```\nFoo Foo::sorted() const & {\n\tFoo ret(*this);\n\treturn ret.sorted();\n}\n```\n\n---\n\n由于ret是一个左值，那么将会出现递归调用（无限递归）。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_57.md",
    "content": "练习13.57：如果sorted定义如下，会发生什么：\n\n```\nFoo Foo::sorted() const & { return Foo(*this).sorted(); }\n```\n\n---\n\nwork fine, 由于'Foo(*this)'得到一个临时对象，它是一个右值，因此接下来会调用右值引用版本的sorted。\n"
  },
  {
    "path": "codes/CppPrimer/ch13_Copy_Control/exercise_13_58.cpp",
    "content": "// 练习13.58：编写新版本的Foo类，其sorted函数中有打印语句，测试这个类，来\n// 验证你对前两题的答案是否正确。\n\n#include <iostream>\n#include <vector>\n#include <algorithm>\n\nusing namespace std;\n\nclass Foo {\npublic:\n\tFoo &operator=(const Foo&) &; \t// 只能向可修改的左值赋值\n\n\tFoo someMem() const &;\t\t\t// 正确，const限定符在前\n\n\tFoo sorted() &&;\t\t\t\t// 可用于可改变的右值\n\tFoo sorted() const &;\t\t\t// 可用于任何类型的Foo\n\nprivate:\n\tint a = 0;\n\tvector<int> data;\n};\n\nFoo &Foo::operator=(const Foo &rhs) &\n{\n\ta = rhs.a;\n\treturn *this;\n}\n\n// 本对象为右值，因此可以原址排序\nFoo Foo::sorted() &&\n{\n\tcout << \"Foo Foo::sorted() &&\" << endl;\n\n\tsort(data.begin(), data.end());\n\treturn *this;\n}\n\n// 练习13.56，导致无限递归的版本\n/*\nFoo Foo::sorted() const &\n{\n\tcout << \"Foo Foo::sorted() const &\" << endl;\n\n\tFoo ret(*this);\n\treturn ret.sorted();\n}\n*/\n\n// 练习13.57，调用右值引用sorted的版本\nFoo Foo::sorted() const &\n{\n\tcout << \"Foo Foo::sorted() const &\" << endl;\n\n\treturn Foo(*this).sorted();\n}\n\n// 返回一个引用；retFoo1调用是一个左值\nFoo &retFoo1()\n{\n\tstatic Foo foo;\n\treturn foo;\n}\n\n// 返回一个值；retFoo2调用是一个右值\nFoo retFoo2()\n{\n\treturn Foo();\n}\n\nint main()\n{\n\t// tests\n\n\tFoo foo;\n\tfoo.sorted();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"exercise_14_32.cpp\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\trm -f out1 out2\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/example_conversion_operators.cpp",
    "content": "// example: 定义含有类型转换运算符的类（p514）\n\n#include <iostream>\n#include <stdexcept>\n\nusing namespace std;\n\nclass SmallInt\n{\npublic:\n\tSmallInt(int i = 0) : val(i)\n\t{\n\t\tif (i < 0 || i > 255)\n\t\t\tthrow std::out_of_range(\"Bad SamllInt value\");\n\t}\n\toperator int() const { return val; }\n\n\t// 编译器不会自动执行这一类型的转换（拒绝隐式转换）\n\texplicit operator double() const { return val; }\n\n\texplicit operator bool() const { return val == 0; }\n\nprivate:\n\tstd::size_t val;\n};\n\nint main()\n{\n\tSmallInt si;\n\tsi = 4;\t\t// 首先将4隐式地转换成SmallInt，然后调用SmallInt::operator=\n\n\tcout << si + 3 << endl; // 首先将si隐式地转换成int，然后执行整数的加法\n\tcout << static_cast<double>(si) + 3.14 << endl;\n\n\tsi = 0;\n\tif (si)\n\t{\n\t\tcout << si << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/example_function-call_operator.cpp",
    "content": "// example: 函数调用运算符（p506）\n\n#include <iostream>\n\nusing namespace std;\n\nstruct AbsInt\n{\n\tint operator()(int val) const {\n\t\treturn val < 0 ? -val : val;\n\t}\n};\n\nint main()\n{\n\tint i = -42;\n\tAbsInt abs_obj;\n\tcout << abs_obj(i) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/example_function.cpp",
    "content": "// example: 可调用对象与function（p511）\n\n// function 是一个模板类型, 它有一个调用模式的额外信息, 比如 int(int, int) 代表参数是 (int, int) 返回值是 int 的可调用对象\n// 可以使用这样的可调用对象的实例来初始化 function 对象\n\n#include <iostream>\n#include <map>\n#include <functional>\n\n// 几种不同类型的可调用对象：\n\nint add(int i, int j) { return i + j; }\n\nauto mod = [](int i, int j) { return i % j; };\n\nstruct divide {\n\tint operator()(int denominator, int divisor) {\n\t\treturn denominator / divisor;\n\t}\n};\n\nusing namespace std;\n\nint main()\n{\n\t// 构建从运算符到函数指针的映射关系，其中函数接受两个int、返回一个int\n\tmap<string, function<int(int, int)>> binops = {\n\t\t{\"+\", add},\t\t\t\t\t// 函数指针\n\t\t{\"-\", std::minus<int>()},\t// 标准库函数对象\n\t\t{\"/\", divide()},\t\t\t// 用户定义的函数对象\n\t\t{\"*\", [](int i, int j) { return i * j; }}, // 未命名的lambda\n\t\t{\"%\", mod}\t\t\t\t\t// 命名了的lambda对象\t\t\n\t};\n\n\tcout << binops[\"+\"](1, 2) << endl;\n\tcout << binops[\"-\"](1, 2) << endl;\n\tcout << binops[\"/\"](1, 2) << endl;\n\tcout << binops[\"*\"](1, 2) << endl;\n\tcout << binops[\"%\"](1, 2) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/example_std_funcobj.cpp",
    "content": "// example: 标准库定义的函数对象（p509）\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n#include <functional>\n\nusing namespace std;\n\nint main()\n{\n\tvector<string> vec = {\"A\", \"BB\", \"CCC\"};\n\tsort(vec.begin(), vec.end(), greater<string>());\n\n\tfor_each(vec.begin(), vec.end(), [](const string &s) { cout << s << \" \"; });\n\tcout << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_01.md",
    "content": "练习14.1：在什么情况下重载的运算符与内置运算符有所区别？在什么情况下重载的运算符又与内置运算符一样？\n\n---\n\n不同点：\n\n- 我们可以用函数调用的方式使用重载运算符。\n\n- 重载的版本必须是类的成员，或者参数拥有类类型。\n\n- 某些运算符（逗号，逻辑与，逻辑或）指定了运算对象的求值顺序，但无法应用到重载的版本上面，因为使用重载的运算符本质上是一次函数调用。\n\n相同点：\n\n- 重载运算符和内置运算符拥有相同的优先级和结合律。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_02.md",
    "content": "练习14.2：为Sales_data编写重载的输入、输出、加法和复合赋值运算符。\n\n---\n\n见[Sales_data](../ch07_Classes/example_Sales_data/Sales_data.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_03.md",
    "content": "练习14.3：string和vector都定义了重载的==以比较各自的对象，假设svec1和svec2是存放string的vector，确定在下面的表达式中分别使用了哪个版本的==？\n\n---\n\n(a) \"cobble\" == \"stone\"\n\n使用了内置类型`const char*`的比较操作，比较的是两个字符串的地址。\n\n(b) svec1[0] == svec2[0]\n\n使用的是string的比较操作。\n\n(c) svec1 = svec2\n\n使用的是vector<string>的比较操作。\n\n(d) svec1[0] == \"stone\"\n\n比较的是string，stone会转换成string类型。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_04.md",
    "content": "练习14.4：如何确定下列运算符是否应该是类的成员？\n\n(a) % 对称性运算符，非成员函数。\n\n(b) %= 复合赋值运算符，成员函数。\n\n(c) ++ 改变对象状态的运算符，成员函数。\n\n(d) -> 必须是成员函数。\n\n(e) << 输出运算符，非成员函数。\n\n(f) && 重载版本无法保证求值顺序，一般不重载它，非成员函数。\n\n(g) == 对称性运算符，非成员函数。\n\n(h) () 必须是成员函数。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_05.cpp",
    "content": "/*\n * 练习14.5：在7.5.1节的练习7.40（第261页）中，编写了下列类中某一个的框架，\n * 请问在这个类中应该定义重载的运算符吗？如果是，请写出来。\n */\n\n\n/*\n * 练习7.40：从下面的抽象概念中选择一个（或者你自己指定一个），思考这样的类需要\n * 哪些数据成员，提供一组合理的构造函数并阐明这样做的原因。\n */\n\n/*\n * 比如选择Book，这里可以定义输入输出运算符\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nclass Book\n{\n\tfriend std::ostream& operator<<(std::ostream &os, const Book &rhs);\n\tfriend std::istream& operator>>(std::istream &is, Book &rhs);\n\npublic:\n\tBook() = default;\n\tBook(unsigned no, const std::string &name, const std::string &auther, const std::string &pubdate) : m_no(no), m_name(name), m_auther(auther), m_pubdate(pubdate) {}\n\tBook(std::istream &is) { is >> m_no >> m_name >> m_auther >> m_pubdate; }\n\n\tvoid print() { std::cout << m_no << \" \" << m_name << \" \" << m_auther << \" \" << m_pubdate << std::endl; }\n\nprivate:\n\tunsigned m_no = 0;\n\tstd::string m_name;\n\tstd::string m_auther;\n\tstd::string m_pubdate;\n};\n\nstd::ostream& operator<<(std::ostream &os, const Book &rhs)\n{\n\tos << rhs.m_no << \" \" << rhs.m_name << \" \" << rhs.m_auther << \" \" << rhs.m_pubdate;\n\treturn os;\n}\n\nstd::istream& operator>>(std::istream &is, Book &rhs)\n{\n\tis >> rhs.m_no >> rhs.m_name >> rhs.m_auther >> rhs.m_pubdate;\n\tif (!is)\n\t\trhs = Book();\n\n\treturn is;\n}\n\nint main()\n{\n\tBook b1; // 默认构造函数，使用类内初始值初始化成员\n\tcout << b1 << endl;\n\n\tBook b2(1, \"c--primer\", \"someone\", \"2017.11.8\"); // 需要使用一个构造函数来初始化所有成员\n\tcout << b2 << endl;\n\n\tBook b3;\n\tcin >> b3;\n\tcout << b3 << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_06.md",
    "content": "练习14.6：为你的Sales_data类定义输出运算符。\n\n---\n\n见[Sales_data](../ch07_Classes/example_Sales_data/Sales_data.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_07.md",
    "content": "练习14.7：你在13.5节的练习（第470页）中曾经编写了一个String类，为它定义要给输出运算符。\n\n---\n\n见[String](../ch13_Copy_Control/example_String/String.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_08.md",
    "content": "练习14.8：你在7.5.1节的练习7.40（第261页）中曾经选择并编写了一个类，为它定义一个输出运算符。\n\n---\n\n见[练习14.5](./exercise_14_05.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_09.md",
    "content": "练习14.9：为你的Sales_data类定义输入运算符。\n\n---\n\n见[Sales_data](../ch07_Classes/example_Sales_data/Sales_data.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_10.md",
    "content": "练习14.10：对于Sales_data的输入运算符来说，如果给定了下面的输入将发生什么情况？\n\n(a) 0-201-99999-9 10 24.95\n\n正常输入\n\n(b) 10 24.95 0-210-99999-9\n\n读取bookNo时读取失败，对象被设置成默认状态。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_11.md",
    "content": "练习14.11：下面的Sales_data输入运算符存在错误码？如果有，请指出来。对于这个输入运算符如果仍然给定上个练习的输入将发生什么情况？\n\n```\nistream& operator>>(istream& in, Sales_data& s)\n{\n\tdouble price;\n\tin >> s.bookNo >> s.units_sold >> price;\n\ts.revenue = s.units_sold * price;\n\treturn in;\n}\n```\n\n---\n\n有错误，因为可能出现IO错误，从而导致后续的读取失败。由于没有考虑失败的情况，源数据可能会被修改成错误的状态。\n\n上个练习中的b就会出现这种情况。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_12.md",
    "content": "练习14.12：你在7.5.1节的练习7.40（第261页）中曾经选择并编写了一个类，为它定义一个输入运算符并确保该运算符可以处理输入错误。\n\n---\n\n见[练习14.5](./exercise_14_05.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_13.md",
    "content": "练习14。13：你认为Sales_data类还应该支持哪些其他算术运算符（参见表4.1，第124页）？如果有的话，请给出它们的定义。\n\n---\n\n恐怕没有。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_14.md",
    "content": "练习14.14：你觉得为什么调用operator+=来定义operator+比其他方法更有效？\n\n---\n\n据说更高效（更少的拷贝次数）：https://stackoverflow.com/questions/21071167/why-is-it-more-efficient-to-define-operator-to-call-operator-rather-than-the\n\n但我尚未能明白，假如我写成这样：\n\n```\nSales_data operator+(const Sales_data &lhs, const Sales_data &rhs)\n{\n\tSales_data sum;\n\tsum.units_sold = lhs.units_sold + rhs.units_sold;\n\tsum.revenue = lhs.revenue + rhs.revenue;\n\treturn sum;\n}\n```\n\n看上去效率是一样的。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_15.md",
    "content": "练习14.15：你在7.5.1节的练习7.40（第261页）中曾经选择并编写了一个类，你认为它应该含有其他算术运算符吗？如果是，请实现它们；如果不是，解释原因。\n\n---\n\n我实现的是Book类，它看上去不需要算术运算符，至多需要==运算符。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_16.md",
    "content": "练习14.16：为你的StrBlob类（参见12.1.1节，第405页）、StrBlobPtr类（参加12.1.6节，第421页）、StrVec类（参加13.5节，第465页）和String类（参加13.5节，第470页）分别定义相等运算符和不相等运算符。\n\n---\n\n见：\n\n[StrBlob](../ch12_Dynamic_Memory/example_StrBlob/StrBlob.cpp)\n\n[StrBlobPtr](../ch12_Dynamic_Memory/example_StrBlobPtr/StrBlobPtr.cpp)\n\n[StrVec](../ch13_Copy_Control/example_StrVec/StrVec.cpp)\n\n[String](../ch13_Copy_Control/example_String/String.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_17.md",
    "content": "练习14.17：你在7.5.1节的练习7.40（第261页）中曾经选择并编写了一个类，你认为它应该含有相等运算符吗？如果是，请实现它；如果不是，解释原因。\n\n---\n\n可以实现一个。见：\n\n[Book](../ch07_Classes/exercise_7_40.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_18.md",
    "content": "练习14.18：为你的StrBlob类、StrBlobPtr类、StrVec类和String类定义关系运算符。\n\n---\n\n见：\n\n[StrBlob](../ch12_Dynamic_Memory/example_StrBlob/StrBlob.cpp)\n\n[StrBlobPtr](../ch12_Dynamic_Memory/example_StrBlobPtr/StrBlobPtr.cpp)\n\n[StrVec](../ch13_Copy_Control/example_StrVec/StrVec.cpp)\n\n[String](../ch13_Copy_Control/example_String/String.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_19.md",
    "content": "练习14.19：你在7.5.1节的练习7.40（第261页）中曾经选择并编写了一个类，你认为它应该含有关系运算符吗？如果是，请实现它；如果不是，解释原因。\n\n---\n\n实现的是Book类，它不需要关系运算符。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_20.md",
    "content": "练习14.20：为你的Sales_data类定义加法和复合赋值运算符。\n\n---\n\n见[Sales_data](../ch07_Classes/example_Sales_data/Sales_data.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_21.md",
    "content": "练习14.21：编写Sales_data类的+和+=运算符，使得+执行实际的加法操作而+=调用+。相比于14.3节（第500页）对这两个运算符的定义，本题的定义有何缺点？试讨论之。\n\n---\n\n忽略编写。\n\n本题的定义需要定义一个临时变量，得到加法的结果，然后再把临时变量赋值给左侧对象。\n\n实际上可以避免使用临时变量，直接修改左侧对象即可。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_22.md",
    "content": "练习14.22：定义赋值运算符的一个新版本，使得我们能把一个表示ISBN的string赋给一个Sales_data对象。\n\n---\n\n见[Sales_data](../ch07_Classes/example_Sales_data/Sales_data.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_23.md",
    "content": "练习14.23：为你的StrVec类定义一个initializer_list赋值运算符。\n\n---\n\n见[StrVec](../ch13_Copy_Control/example_StrVec)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_24.cpp",
    "content": "/*\n * 练习14.24：你在7.5.1节的练习7.40（第261页）中曾经选择并编写了一个类，你认为它\n * 应该含有拷贝赋值和移动赋值运算符吗？如果是，请实现它们。\n */\n\n// 应该实现。\n\n/*\n * 练习7.40：从下面的抽象概念中选择一个（或者你自己指定一个），思考这样的类需要\n * 哪些数据成员，提供一组合理的构造函数并阐明这样做的原因。\n */\n\n/*\n * 比如选择Book\n */\n\n#include <iostream>\n#include <string>\n\nusing std::cout;\nusing std::endl;\nusing std::cin;\n\nclass Book\n{\n\tfriend bool operator==(const Book &lhs, const Book &rhs);\n\tfriend bool operator!=(const Book &lhs, const Book &rhs);\n\npublic:\n\tBook() = default;\n\tBook(unsigned no, const std::string &name, const std::string &auther, const std::string &pubdate) : m_no(no), m_name(name), m_auther(auther), m_pubdate(pubdate) {}\n\tBook(std::istream &is) { is >> m_no >> m_name >> m_auther >> m_pubdate; }\n\n\tBook& operator=(const Book &lhs)\n\t{\n\t\tm_no = lhs.m_no;\n\t\tm_name = lhs.m_name;\n\t\tm_auther = lhs.m_auther;\n\t\tm_pubdate = lhs.m_pubdate;\n\t\treturn *this;\n\t}\n\n\tBook& operator=(Book &&lhs)\n\t{\n\t\tstd::cout << \"Book& operator=(Book &&lhs)\" << std::endl;\n\n\t\tm_no = lhs.m_no;\n\t\tm_name = std::move(lhs.m_name);\n\t\tm_auther = std::move(lhs.m_auther);\n\t\tm_pubdate = std::move(lhs.m_pubdate);\n\t\treturn *this;\n\t}\n\n\tvoid print() { std::cout << m_no << \" \" << m_name << \" \" << m_auther << \" \" << m_pubdate << std::endl; }\n\nprivate:\n\tunsigned m_no = 0;\n\tstd::string m_name;\n\tstd::string m_auther;\n\tstd::string m_pubdate;\n};\n\nint main()\n{\n\tBook b1; // 默认构造函数，使用类内初始值初始化成员\n\tb1.print();\n\n\tBook b2(1, \"c--primer\", \"someone\", \"2017.11.8\"); // 需要使用一个构造函数来初始化所有成员\n\tb2.print();\n\n\tb1 = b2;\n\tb1.print();\n\n\tBook b3;\n\tb3 = std::move(b1);\n\tb3.print();\n\n\treturn 0;\n}\n\nbool operator==(const Book &lhs, const Book &rhs)\n{\n\treturn lhs.m_no == rhs.m_no &&\n\t       lhs.m_name == rhs.m_name &&\n\t\t   lhs.m_auther == rhs.m_auther &&\n\t\t   lhs.m_pubdate == rhs.m_pubdate;\n}\n\nbool operator!=(const Book &lhs, const Book &rhs)\n{\n\treturn !(lhs == rhs);\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_25.md",
    "content": "练习14.25：上题的这个类还需要定义其他赋值运算符吗？如果是，请实现它们；同时说明运算对象应该是什么类型并解释原因。\n\n---\n\n不用了。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_26.md",
    "content": "练习14.26：为你的StrBlob类、StrBlobPtr类、StrVec类和String类定义下标运算符。\n\n---\n\n见：\n\n[StrBlob](../ch12_Dynamic_Memory/example_StrBlob/)\n\n[StrBlobPtr](../ch12_Dynamic_Memory/example_StrBlobPtr/)\n\n[StrVec](../ch13_Copy_Control/example_StrVec/)\n\n[String](../ch13_Copy_Control/example_String/)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_27.md",
    "content": "练习14.27：为你的StrBlobPtr类添加递增和递减运算符。\n\n---\n\n见[StrBlobPtr](../ch12_Dynamic_Memory/example_StrBlobPtr/)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_28.md",
    "content": "练习14.28：为你的StrBlobPtr类添加加法和减法运算符，使其可以实现指针和算术运算（参见3.5.3节，第106页）。\n\n---\n\n见[StrBlobPtr](../ch12_Dynamic_Memory/example_StrBlobPtr/)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_29.md",
    "content": "练习14.29：为什么不定义const版本的递增和递减运算符？\n\n---\n\n因为这些运算符修改对象，和const版本的成员函数矛盾。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_30.md",
    "content": "练习14.30：为你的StrBlobPtr类和在12.1.6节练习12.22（第423页）中定义的ConstStrBlobPtr类分别添加解引用运算符和箭头运算符。注意：因为ConstStrBlobPtr的数据成员指向const vector，所以ConstStrBlobPtr中的运算符必须返回常量引用。\n\n---\n\n[StrBlobPtr](../ch12_Dynamic_Memory/example_StrBlobPtr/StrBlobPtr.h)\n\n[ConstStrBlobPtr](../ch12_Dynamic_Memory/example_StrBlobPtr/ConstStrBlobPtr.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_31.md",
    "content": "练习14.31：我们的StrBlobPtr类没有定义拷贝构造函数、赋值运算符及析构函数，为什么？\n\n---\n\n因为这个类不需要这些拷贝控制成员，它只用于访问资源，不用于管理资源。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_32.cpp",
    "content": "// 练习14.32：定义一个类令其有指向StrBlobPtr对象的指针，为这个类定义重载的箭头运算符。\n\n// g++ -g -Wall -std=c++11 -o exercise_14_32 exercise_14_32.cpp ../ch12_Dynamic_Memory/example_StrBlobPtr/StrBlobPtr.cpp\n\n#include <iostream>\n\n#include \"../ch12_Dynamic_Memory/example_StrBlobPtr/StrBlob.h\"\n#include \"../ch12_Dynamic_Memory/example_StrBlobPtr/StrBlobPtr.h\"\n\nusing namespace std;\n\nclass Foo\n{\npublic:\n\tFoo(StrBlobPtr *p) : m_p(p) {}\n\n\tStrBlobPtr *operator->()\n\t{\n\t\treturn m_p;\n\t}\n\nprivate:\n\tStrBlobPtr *m_p;\n};\n\nint main()\n{\n\tStrBlob b{\"Hi\", \"Hello\", \"World\"};\n\tStrBlobPtr bp(b);\n\n\tFoo foo(&bp);\n\tcout << foo->deref() << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_33.md",
    "content": "练习14.33：一个重载的函数调用运算符应该接受几个运算对象？\n\n---\n\n不受限制。见p120。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_34.cpp",
    "content": "// 练习14.34：定义一个函数对象类，令其执行if-then-else的操作；该类的调用\n// 运算符接受三个形参：它首先检查第一个形参，如果成功返回第二个形参的值；\n// 如果不成功返回第三个形参的值。\n\n#include <iostream>\n\nusing namespace std;\n\nstruct Foo\n{\n\tint operator()(bool a, int b, int c) const\n\t{\n\t\treturn a ? b : c;\n\t}\n};\n\nint main()\n{\n\tFoo f1;\n\tcout << f1(true, 1, 10) << endl;\n\tcout << f1(false, 1, 10) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_35.cpp",
    "content": "// 练习14.35：编写一个类似于PrintString的类，令其从istream中读取一行输入，\n// 然后返回一个表示我们所读内容的string，如果读取失败，返回空string。\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\nclass ReadString\n{\npublic:\n\tReadString(istream &is) : m_is(is) {}\n\tstring operator()() const\n\t{\n\t\tstring line;\n\t\tif (getline(m_is, line))\n\t\t\treturn line;\n\t\treturn \"\";\n\t}\n\nprivate:\n\tistream &m_is;\n};\n\nint main()\n{\n\tReadString reader(cin);\n\tcout << reader() << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_36.cpp",
    "content": "// 练习14.36：使用前一个练习定义的类读取标准输入，将每一行保存为vector的一个\n// 元素。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n\nusing namespace std;\n\nclass ReadString\n{\npublic:\n\tReadString(istream &is) : m_is(is) {}\n\tstring operator()() const\n\t{\n\t\tstring line;\n\t\tif (getline(m_is, line))\n\t\t\treturn line;\n\t\treturn \"\";\n\t}\n\nprivate:\n\tistream &m_is;\n};\n\nint main()\n{\n\tvector<string> vec;\n\tReadString reader(cin);\n\tstring line;\n\twhile ((line = reader()).length() > 0) {\n\t\tvec.emplace_back(line);\n\t}\n\n\tfor_each(vec.cbegin(), vec.cend(), [](const string &line) { cout << line << \"\\n\"; });\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_37.cpp",
    "content": "// 练习14.37：编写一个类令其检查两个值是否相等。使用该对象及标准库算法编写程序，\n// 令其替换某个序列中具有给定值的所有实例。\n\n#include <iostream>\n#include <vector>\n#include <algorithm>\n\nusing namespace std;\n\nclass Equal\n{\npublic:\n\tEqual(int cmp) : m_cmp(cmp) {}\n\tbool operator()(int t) const { return m_cmp == t; }\n\nprivate:\n\tint m_cmp;\n};\n\nint main()\n{\n\tvector<int> vec = {1, 2, 3, 2, 2, 5};\n\n\t//replace(vec.begin(), vec.end(), 2, 20);\n\t\n\treplace_if(vec.begin(), vec.end(), Equal(2), 20);\n\n\tfor_each(vec.cbegin(), vec.cend(), [](int i) { cout << i << \" \"; });\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_38.cpp",
    "content": "// 练习14.38：编写一个类令其检查某个给定的string对象的长度是否与一个阙值相等。\n// 使用该对象编写程序，统计并报告在输入文件中长度为1的单词有多少个、长度为2\n// 的单词有多少个、....、长度为10的单词又有多少个。\n\n#include <iostream>\n#include <string>\n#include <fstream>\n#include <sstream>\n\nusing namespace std;\n\nclass SizeCmp\n{\npublic:\n\tSizeCmp(size_t size = 0) : m_size(size) {}\n\tbool operator()(size_t sz) const { return m_size == sz; }\n\n\tvoid SetSize(size_t size) { m_size = size; }\n\nprivate:\n\tsize_t m_size;\n};\n\nint main()\n{\n\tstring file_name;\n\tcin >> file_name;\n\n\tifstream ifs(file_name);\n\tif (!ifs.is_open())\n\t{\n\t\tcerr << \"open file fail\" << endl;\n\t\treturn -1;\n\t}\n\n\tconstexpr int SZ_LIST_COUNT = 10;\n\tsize_t sz_count_list[SZ_LIST_COUNT] = {0};\n\n\tstring line;\n\tstring word;\n\tSizeCmp size_cmper;\n\n\twhile (getline(ifs, line))\n\t{\n\t\tistringstream iss(line);\n\t\twhile (iss >> word)\n\t\t{\n\t\t\tfor (size_t sz = 0; sz < SZ_LIST_COUNT; ++sz)\n\t\t\t{\n\t\t\t\tsize_cmper.SetSize(sz + 1);\n\t\t\t\tif (size_cmper(word.length()))\n\t\t\t\t{\n\t\t\t\t\t++sz_count_list[sz];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tfor (size_t sz = 0; sz < SZ_LIST_COUNT; ++sz)\n\t{\n\t\tcout << sz + 1 << \" count: \" << sz_count_list[sz] << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_39.cpp",
    "content": "// 练习14.39：修改上一题的程序令其报告长度在1至9之间的单词有多少个、长度在10\n// 以上的单词又有多少个？\n\n// 练习14.38：编写一个类令其检查某个给定的string对象的长度是否与一个阙值相等。\n// 使用该对象编写程序，统计并报告在输入文件中长度为1的单词有多少个、长度为2\n// 的单词有多少个、....、长度为10的单词又有多少个。\n\n#include <iostream>\n#include <string>\n#include <fstream>\n#include <sstream>\n\nusing namespace std;\n\nclass SizeCmp1\n{\npublic:\n\tSizeCmp1(size_t lower, size_t upper) : m_lower(lower), m_upper(upper) {}\n\tbool operator()(size_t len) const { return len >= m_lower && len < m_upper; }\n\nprivate:\n\tsize_t m_lower;\n\tsize_t m_upper;\n};\n\nclass SizeCmp2\n{\npublic:\n\tSizeCmp2(size_t lower) : m_lower(lower) {}\n\tbool operator()(size_t len) const { return len >= m_lower; }\n\nprivate:\n\tsize_t m_lower;\n};\n\nint main()\n{\n\tstring file_name;\n\tcin >> file_name;\n\n\tifstream ifs(file_name);\n\tif (!ifs.is_open())\n\t{\n\t\tcerr << \"open file fail\" << endl;\n\t\treturn -1;\n\t}\n\n\tstring line;\n\tstring word;\n\tSizeCmp1 cmper1(1, 9);\n\tSizeCmp2 cmper2(10);\n\tsize_t n1_9 = 0;\n\tsize_t n10 = 0;\n\n\twhile (getline(ifs, line))\n\t{\n\t\tistringstream iss(line);\n\t\twhile (iss >> word)\n\t\t{\n\t\t\tif (cmper1(word.length()))\n\t\t\t{\n\t\t\t\t++n1_9;\n\t\t\t}\n\n\t\t\tif (cmper2(word.length()))\n\t\t\t{\n\t\t\t\t++n10;\n\t\t\t}\n\t\t}\n\t}\n\n\tcout << \"number of len in 1-9: \" << n1_9 << endl;\n\tcout << \"number of len >= 10 : \" << n10 << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_40.cpp",
    "content": "// 练习14.40：重新白那些10.3.2节（第349页）的biggiest函数，使用函数对象类替换其中的\n// lambda表达式。\n\n// 练习10.16：使用lambda编写你自己版本的biggies。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n\nusing namespace std;\n\nclass Cmp1\n{\npublic:\n\tbool operator()(const string &a, const string &b) const\n\t{\n\t\treturn a.size() < b.size();\n\t}\n};\n\nclass Cmp2\n{\npublic:\n\tCmp2(size_t sz) : m_sz(sz) {}\n\tbool operator()(const string &a) const\n\t{\n\t\treturn a.size() >= m_sz;\n\t}\n\nprivate:\n\tsize_t m_sz;\n};\n\nclass Printer\n{\npublic:\n\tvoid operator()(const string &s) const\n\t{\n\t\tcout << s << \" \";\n\t}\n};\n\nvoid biggest(vector<string> &words, vector<string>::size_type sz)\n{\n\t// 按长度排序\n\tstable_sort(words.begin(), words.end(), Cmp1());\n\n\t// 获取一个迭代器，指向第一个满足size() >= sz的元素\n\tauto wc = find_if(words.begin(), words.end(), Cmp2(sz));\n\n\t// 计算满足size >= sz的元素的数目\n\tauto count = words.end() - wc;\n\tcout << count << endl;\n\n\t// 打印长度大于等于sz的单词\n\tfor_each(wc, words.end(), Printer());\n\tcout << endl;\n}\n\nint main()\n{\n\tvector<string> words = { \"hello\", \"father\", \"my\", \"ops\", \"yours\", \"everything\" };\n\tvector<string>::size_type sz = 5;\n\n\tbiggest(words, sz);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_41.md",
    "content": "练习14.41：你认为C++11新标准为什么要增加lambda？对于你自己来说，什么情况下会使用lambda，什么情况下会使用类？\n\n---\n\nlambda可以定义一个匿名的函数对象类并生成一个匿名的对象，省去了定义具名类并创建对象的过程，比较适合于一次性使用算法的场景。\n\n如果一个函数对象需要复用，那么可以选择定义一个函数对象的具名类。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_42.cpp",
    "content": "// 练习14.42：使用标准库函数对象及适配器定义一条表达式，令其\n// (a) 统计大于1024的值有多少个。\n// (b) 找到第一个不等于pooh的字符串。\n// (c) 将所有的值乘以2。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <algorithm>\n#include <functional>\n\nusing namespace std;\n\nvoid func_a()\n{\n\tvector<int> vec = {1, 2, 1025, 2000, 1024};\n\n\tauto adapter = bind(greater<int>(), placeholders::_1, 1024);\n\tauto cnt = count_if(vec.cbegin(), vec.cend(), adapter);\n\n\tcout << cnt << endl;\n}\n\nvoid func_b()\n{\n\tvector<string> vec = {\"pooh\", \"Hi\", \"A\"};\n\n\tauto adapter = bind(not_equal_to<string>(), placeholders::_1, \"pooh\");\n\tauto it = find_if(vec.cbegin(), vec.cend(), adapter);\n\tif (it != vec.cend())\n\t{\n\t\tcout << *it << endl;\n\t}\n\telse\n\t{\n\t\tcout << \"Not found\" << endl;\n\t}\n}\n\nvoid func_c()\n{\n\tvector<int> vec = {1, 2, 3, 4};\n\n\tauto adapter = bind(multiplies<int>(), placeholders::_1, 2);\n\ttransform(vec.cbegin(), vec.cend(), vec.begin(), adapter);\n\n\tfor (auto i : vec)\n\t{\n\t\tcout << i << \" \";\n\t}\n\tcout << endl;\n}\n\nint main()\n{\n\t//func_a();\n\t//func_b();\n\tfunc_c();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_43.cpp",
    "content": "// 练习14.43：使用标准库函数对象判断一个给定的int值是否能被int容器中的所有元素整除。\n\n#include <iostream>\n#include <vector>\n#include <functional>\n\nusing namespace std;\n\nint main()\n{\n\tvector<int> vec = {4, 6, 10};\n\tint n = 0;\n\tcin >> n;\n\n\tauto check_func = modulus<int>();\n\n\tbool can_modulus = true;\n\tfor (auto i : vec)\n\t{\n\t\tif (check_func(i, n) != 0)\n\t\t{\n\t\t\tcan_modulus = false;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tcout << \"Can modulus: \" << can_modulus << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_44.cpp",
    "content": "// 练习14.44：编写一个简单的桌面计算器使其能处理二元运算。\n\n// ./exercise_14_44 2 - 2\n\n#include <iostream>\n#include <map>\n#include <functional>\n#include <string>\n\nusing namespace std;\n\nvoid Calc(string ops, int a, int b)\n{\n\tstatic map<string, function<int(int, int)>> calc_f_map = {\n\t\t{\"+\", [](int i, int j) { return i + j; }},\n\t\t{\"-\", [](int i, int j) { return i - j; }},\n\t\t{\"*\", [](int i, int j) { return i * j; }},\n\t\t{\"/\", [](int i, int j) { return i / j; }},\n\t};\n\n\tif (calc_f_map.count(ops) == 0)\n\t\tcerr << \"cant find ops: \" << ops << endl;\n\telse\n\t\tcout << calc_f_map[ops](a, b) << endl;\n}\n\nint main(int args, char *argv[])\n{\n\tif (args != 4) {\n\t\tcerr << \"args != 4, return\" << endl;\n\t\treturn -1;\n\t}\n\n\tint lhs = stoi(argv[1]);\n\tstring ops = argv[2];\n\tint rhs = stoi(argv[3]);\n\t\n\tCalc(ops, lhs, rhs);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_45.md",
    "content": "练习14.45：编写类型转换运算符将一个Sales_data对象分别转换成string和double，你认为这些运算符的返回值应该是什么？\n\n---\n\n见[Sales_data](../ch07_Classes/example_Sales_data/Sales_data.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_46.md",
    "content": "练习14.46：你认为应该为Sales_data类定义上面两种类型转换符吗？应该把它们声明成explicit的吗？为什么？\n\n---\n\n似乎必要性都不大，因为使用更明显的成员函数更合适。\n\n应该声明成explicit，以拒绝隐式转换，这样可以让避免代码具有误导性。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_47.md",
    "content": "练习14.47：说明下面这两个类型转换运算符的区别。\n\n```\nstruct Integral {\n\toperator const int();\n\toperator int() const;\n}:\n```\n\n---\n\n- 第一个说明实例对象不能是const的（因为传入普通的this指针），类型转换得到一个const int的值。\n\n- 第二个说明实例对象可以是const的（因为传入const的this指针），类型转换得到一个int的值。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_48.md",
    "content": "练习14.48：你在7.5.1节的练习7.40（第261页）中曾经选择并编写了一个类，你认为它应该含有向bool的类型转换运算符吗？如果是，解释原因并说明该运算符是否应该是explicit的；如果不是，也请解释原因。\n\n---\n\n是可以的，如果Book的m_no是0，那么可以让其表示false。\n\n应该是explicit的，因为向bool的类型转换通常用在条件部分，这个时候仍然会被隐式地转换，而其余情况必须显示转换。\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_49.md",
    "content": "练习14.49：为上一题提到的类定义一个转换目标是bool的类型转换运算符，先不用在意这么做是否应该。\n\n---\n\n见[Book](../ch07_Classes/exercise_7_42.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_50.cpp",
    "content": "// 练习14.50：在初始化ex1和ex2的过程中，可能用到哪些类类型的转换序列呢？说明\n// 初始化是否正确并解释原因。\n\n#include <iostream>\n\nusing namespace std;\n\nstruct LongDouble {\n\tLongDouble(double _v = 0.0) : v(_v) {}\n\toperator double() { cout << \"operator double\" << endl; return v; }\n\toperator float() { cout << \"operator float\" << endl; return (float)v; }\n\n\tdouble v;\n};\n\nint main()\n{\n\tLongDouble ldObj;\n\t//int ex1 = ldObj; // error: conversion from ‘LongDouble’ to ‘int’ is ambiguous\n\t\n\tfloat ex2 = ldObj;\n\tcout << ex2 << endl; // 正确，调用float版本的\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_51.cpp",
    "content": "// 练习14.51：在调用calc的过程中，可能用到哪些类型转换序列呢？说明最佳可行函数\n// 是如何被选出来的。\n\n#include <iostream>\n\nusing namespace std;\n\nstruct LongDouble {\n\tLongDouble(double _v = 0.0) : v(_v) {}\n\toperator double() { cout << \"operator double\" << endl; return v; }\n\toperator float() { cout << \"operator float\" << endl; return (float)v; }\n\n\tdouble v;\n};\n\nvoid calc(int) { cout << \"calc(int)\" << endl; }\nvoid calc(LongDouble) { cout << \"calc(LongDouble)\" << endl; }\n\nint main()\n{\n\tdouble dval = 0;\n\tcalc(dval);\t\t// 哪个calc？ -> calc(int)\n\n\t// class-type conversion is the lowest ranked\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_52.md",
    "content": "练习14.52：在下面的加法表达式中分别选用了哪个operator+？列出候选函数、可行函数及为每个可行函数的实参执行的类型转换：\n\n```\nstruct LongDouble {\n\tLongDouble operator+(const SmallInt&);\n};\n\nLongDouble operator+(LongDouble&, double);\nSmallInt si;\nLongDouble ld;\nld = si + ld;\nld = ld + si;\n```\n\n---\n\n**对于`ld = si + ld;`，都是可行函数，但有二义性。**\n\n对于operator+(LongDouble&, double)，实参转换路径是：\n\n- si -> int -> double -> LongDouble\n\n- ld -> double\n\n对于LongDouble::operator+(const SmallInt&)，实参转换路径是：\n\n- si -> int -> double -> LongDouble\n\n- ld -> double -> int -> SmallInt\n\n**对于`ld = ld + si;`，都是可行函数，但LongDouble::operator+(const SmallInt&)更佳。**\n\n对于operator+(LongDouble&, double)，实参转换路径是：\n\n- ld无需转换\n\n- si -> int -> double\n\n对于LongDouble::operator+(const SmallInt&)，实参转换路径是：\n\n- ld无需转换\n\n- si无需转换\n"
  },
  {
    "path": "codes/CppPrimer/ch14_Overloaded_Operations_and_Conversions/exercise_14_53.cpp",
    "content": "// 练习14.53：假设我们已经定义了如522页所示的SmallInt，判断下面的加法表达式是否合法。\n// 如果合法，使用了哪个加法运算符？如果不合法，应该怎样修改代码才能使其合法。\n\n#include <iostream>\n\nusing namespace std;\n\nclass SmallInt {\n\tfriend SmallInt operator+(const SmallInt&, const SmallInt&);\n\npublic:\n\tSmallInt(int _v = 0) : val(_v) {}\n\toperator int() const { return val; }\n\nprivate:\n\tstd::size_t val;\n};\n\nSmallInt operator+(const SmallInt &lhs, const SmallInt &rhs)\n{\n\tcout << \"operator+(const SmallInt &lhs, const SmallInt &rhs)\" << endl;\n\n\tSmallInt ret;\n\tret.val = lhs.val + rhs.val;\n\treturn ret;\n}\n\nint main()\n{\n\tSmallInt s1;\n\t//double d = s1 + 3.14; // error: ambiguous overload for ‘operator+’\n\t\n\tdouble d = static_cast<int>(s1) + 3.14;\n\n\tcout << d << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\trm -f out1 out2\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_Access_Control_and_Inheritance/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_Access_Control_and_Inheritance/classes.hpp",
    "content": "#ifndef __CLASSES_HPP__\n#define __CLASSES_HPP__\n\nclass Base {\n\t// 添加friend声明，其他成员与之前的版本一致\n\tfriend class Pal;\t\t\t\t// Pal在访问Base的派生类时不具有特殊性\npublic:\n\tvoid pub_mem() {}\t\t\t\t// public成员\n\nprotected:\n\tint prot_mem;\t\t\t\t\t// protected成员\n\nprivate:\n\tchar priv_mem;\t\t\t\t\t// private成员\n};\n\nclass Sneaky : public Base {\n\tfriend void clobber(Sneaky&);\t// 能访问Sneaky::prot_mem\n\tfriend void clobber(Base&);\t\t// 不能访问Base::prot_mem\n\tint j;\t\t\t\t\t\t\t// j默认是private\n};\n\nclass Pal {\npublic:\n\tint f(Base b) { return b.prot_mem; }\t// 正确：Pal是Base的友元\n\t//int f2(Sneaky s) { return s.j; }\t\t// 错误：Pal不是Sneaky的友元\n\n\t// 对基类的访问权限由基类本身控制，即使对于派生类的基类部分也是如此\n\tint f3(Sneaky s) { return s.prot_mem; }\t// 正确：Pal是Base的友元\n};\n\nclass Pub_Derv : public Base {\n\t// 正确：派生类能访问protected成员\n\tint f() { return prot_mem; }\n\t\n\t// 错误：private成员对于派生类来说是不可访问的\n\t//char g() { return priv_mem; }\n};\n\nclass Priv_Derv : private Base {\n\t// private不影响派生类的访问权限\n\tint f1() const { return prot_mem; }\n};\n\nclass Prot_Derv : protected Base {\n\t// private不影响派生类的访问权限\n\tint f1() const { return prot_mem; }\n};\n\nstruct Derived_from_Public : public Pub_Derv {\n\t// 正确：Base::prot_mem在Pub_Derv中仍然是protected的\n\tint use_base() { return prot_mem; }\n};\n\nstruct Derived_from_Private : public Priv_Derv {\n\t// 错误：Base::prot_mem在Priv_Derv中是private的\n\t//int use_base() { return prot_mem; }\n};\n\nstruct Derived_from_Protected : public Prot_Derv {\n\t// 正确：Base::prot_mem在Prot_Derv中仍然是protected的\n\tint use_base() { return prot_mem; }\n};\n\n// 正确：clobber能访问Sneaky对象的private和protected成员\nvoid clobber(Sneaky &s) { s.j = s.prot_mem = 0; }\n\n// 错误：clobber不能访问Base的protected成员\n//void clobber(Base &b) { b.prot_mem = 0; }\n\n#endif // __CLASSES_HPP__\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_Access_Control_and_Inheritance/main.cpp",
    "content": "// exmaple: 访问控制与继承（p543）\n\n#include <iostream>\n#include \"classes.hpp\"\n\nusing namespace std;\n\n#define USE_VAR(x) ((void)&(x))\n\nint main()\n{\n\tPub_Derv d1;\t\t// 继承自Base的成员是public的\n\tPriv_Derv d2;\t\t// 继承自Base的成员是private的\n\td1.pub_mem();\t\t// 正确：pub_mem在派生类中是public的\n\t//d2.pub_mem();\t\t// 错误：pub_mem在派生类中是private的\n\n\tUSE_VAR(d2);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_TextQuery/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_TextQuery/Query.cpp",
    "content": "#include \"Query.h\"\n#include <algorithm>\n\n// 返回运算对象查询结果set的并集\nQueryResult\nOrQuery::eval(const TextQuery &text) const\n{\n\t// 通过Query成员lhs和rhs进行虚调用\n\t// 调用eval返回每个运算对象的QueryResult\n\tauto right = rhs.eval(text), left = lhs.eval(text);\n\n\t// 将左侧运算对象的行号拷贝到结果set中\n\tauto ret_lines = std::make_shared<std::set<line_no>>(left.begin(), left.end());\n\n\t// 插入右侧运算对象所得的行号\n\tret_lines->insert(right.begin(), right.end());\n\n\t// 返回一个新的QueryResult，它表示lhs和rhs的并集\n\treturn QueryResult(rep(), ret_lines, left.get_file());\n}\n\n// 返回运算对象查询结果set的交集\nQueryResult\nAndQuery::eval(const TextQuery &text) const\n{\n\t// 通过Query运算对象进行的虚调用，以获得运算对象的查询结果set\n\tauto left = lhs.eval(text), right = rhs.eval(text);\n\n\t// 保存left和right交集的set\n\tauto ret_lines = std::make_shared<std::set<line_no>>();\n\n\t// 将两个范围的交集写入一个目的迭代器中\n\t// 本次调用的目的迭代器向ret添加元素\n\tstd::set_intersection(left.begin(), left.end(), right.begin(), right.end(), std::inserter(*ret_lines, ret_lines->begin()));\n\n\treturn QueryResult(rep(), ret_lines, left.get_file());\n}\n\n// 返回运算对象的结果set中不存在的行\nQueryResult\nNotQuery::eval(const TextQuery &text) const\n{\n\t// 通过Query运算对象对eval进行虚调用\n\tauto result = query.eval(text);\n\n\t// 开始时结果为空\n\tauto ret_lines = std::make_shared<std::set<line_no>>();\n\n\t// 我们必须在运算对象出现的所有行中进行迭代\n\tauto beg = result.begin(), end = result.end();\n\n\t// 对于输入文件的每一行，如果该行不在result当中，则将其添加到ret_lines\n\tauto sz = result.get_file()->size();\n\tfor (size_t n = 0; n != sz; ++n)\n\t{\n\t\t// 如果我们还没有处理完result的所有行\n\t\t// 检查当前行是否存在\n\t\tif (beg == end || *beg != n)\n\t\t\tret_lines->insert(n);\t// 如果不在result中，添加这一行\n\t\telse if (beg != end)\n\t\t\t++beg;\t// 否则继续获取result的下一行（如果有的话）\n\t}\n\n\treturn QueryResult(rep(), ret_lines, result.get_file());\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_TextQuery/Query.h",
    "content": "#ifndef QUERY_H\n#define QUERY_H\n\n#include <iostream>\n#include <string>\n#include <memory>\n\n#include \"QueryResult.h\"\n#include \"TextQuery.h\"\n\nstatic constexpr bool open_debug = false;\ninline void PrintDebug(const char *msg)\n{\n\tif (open_debug)\n\t\tstd::cout << \"[Debug] \" << msg << \"\\n\";\n}\n\nstatic QueryResult invalid_qr(\"\", 0, 0);\n\n// 这是一个抽象基类，具体的查询类型从中派生，所有成员都是private的\nclass Query_base {\n\tfriend class Query;\n\nprotected:\n\tusing line_no = TextQuery::line_no;\n\tvirtual ~Query_base() = default;\n\nprivate:\n\t// eval返回与当前Query匹配的QueryResult\n\tvirtual QueryResult eval(const TextQuery&) const = 0;\n\n\t// rep是表示查询的一个string\n\tvirtual std::string rep() const = 0;\n};\n\nclass WordQuery : public Query_base {\n\tfriend class Query;\t\t// Query使用WordQuery构造函数\n\n\tWordQuery(const std::string &s) : query_word(s)\n\t{ PrintDebug(\"WordQuery::WordQuery(const std::string &s)\"); }\n\t\n\t// 具体的类：WordQuery将定义所有继承而来的纯虚函数\n\tQueryResult eval(const TextQuery &t) const override\n\t{ return t.query(query_word); }\n\n\tstd::string rep() const override\n\t{ \n\t\tPrintDebug(\"WordQuery::rep\");\n\t\treturn query_word; \n\t}\n\tstd::string query_word;\t// 要查找的单词\n};\n\n// 这是一个管理Query_base继承体系的接口类\nclass Query {\n\t// 这些运算符需要访问接受shared_ptr的构造函数，而该函数是私有的\n\tfriend Query operator~(const Query &);\n\tfriend Query operator|(const Query&, const Query&);\n\tfriend Query operator&(const Query&, const Query&);\n\npublic:\n\tQuery(const std::string &s) : q(new WordQuery(s))\t// 构建一个新的WordQuery\n\t{ PrintDebug(\"Query::Query(const std::string &s)\"); }\n\n\t// 接口函数：调用对应的Query_base操作\n\tQueryResult eval(const TextQuery &t) const { return q->eval(t); }\n\tstd::string rep() const\n\t{ \n\t\tPrintDebug(\"Query::rep\");\n\t\treturn q->rep(); \n\t}\n\nprivate:\n\tQuery(std::shared_ptr<Query_base> query) : q(query)\n\t{ PrintDebug(\"Query::Query(std::shared_ptr<Query_base> query)\"); }\n\n\tstd::shared_ptr<Query_base> q;\n};\n\ninline std::ostream& operator<<(std::ostream &os, const Query &query)\n{\n\t// Query::rep通过它的Query_base指针对rep()进行了虚调用\n\treturn os << query.rep();\n}\n\nclass NotQuery : public Query_base {\n\tfriend Query operator~(const Query&);\n\t\n\tNotQuery(const Query &q) : query(q)\n\t{ PrintDebug(\"NotQuery::NotQuery(const Query &q)\"); }\n\n\t// 具体的类：NotQuery将定义所有继承而来的纯虚函数\n\tstd::string rep() const\n\t{ \n\t\tPrintDebug(\"NotQuery::rep\");\n\t\treturn \"~(\" + query.rep() + \")\"; \n\t}\n\n\tQueryResult eval(const TextQuery&) const;\n\tQuery query;\n};\n\ninline Query operator~(const Query &operand)\n{\n\treturn std::shared_ptr<Query_base>(new NotQuery(operand));\n}\n\nclass BinaryQuery : public Query_base {\nprotected:\n\tBinaryQuery(const Query &l, const Query &r, std::string s) :\n\t\tlhs(l), rhs(r), opSym(s)\n\t{ PrintDebug(\"BinaryQuery::BinaryQuery(const Query &l, const Query &r, std::string s)\"); }\n\n\t// 抽象类：BinaryQuery不定义eval\n\tstd::string rep() const\n\t{\n\t\tPrintDebug(\"BinaryQuery::rep\");\n\t\treturn \"(\" + lhs.rep() + \" \" + opSym + \" \" + rhs.rep() + \")\";\n\t}\n\n\tQuery lhs, rhs;\t\t// 左侧和右侧的运算对象\n\tstd::string opSym;\t// 运算符的名字\n};\n\nclass AndQuery : public BinaryQuery {\n\tfriend Query operator&(const Query&, const Query&);\n\n\tAndQuery(const Query &left, const Query &right) : BinaryQuery(left, right, \"&\")\n\t{ PrintDebug(\"AndQuery::AndQuery(const Query &left, const Query &right)\"); }\n\n\t// 具体的类：AndQuery继承了rep并且定义了其他纯虚函数\n\tQueryResult eval(const TextQuery&) const override;\n};\n\ninline Query operator&(const Query &lhs, const Query &rhs)\n{\n\treturn std::shared_ptr<Query_base>(new AndQuery(lhs, rhs));\n};\n\nclass OrQuery : public BinaryQuery {\n\tfriend Query operator|(const Query&, const Query&);\n\n\tOrQuery(const Query &left, const Query &right) : BinaryQuery(left, right, \"|\")\n\t{ PrintDebug(\"OrQuery::OrQuery(const Query &left, const Query &right)\"); }\n\n\tQueryResult eval(const TextQuery&) const override;\n};\n\ninline Query operator|(const Query &lhs, const Query &rhs)\n{\n\treturn std::shared_ptr<Query_base>(new OrQuery(lhs, rhs));\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_TextQuery/QueryResult.cpp",
    "content": "#include \"QueryResult.h\"\n\nusing namespace std;\n\nostream &print(ostream &os, const QueryResult &qr, std::pair<size_t, size_t> *line_range)\n{\n\t// 如果找到了单词，打印出现次数和所有出现的位置\n\tos << qr.sought << \" occurs \" << qr.lines->size() << \" time(s)\" << endl;\n\n\t// 打印单词出现的每一行\n\tfor (auto num : *qr.lines) // 对set中每个单词\n\t\t// 避免行号从0开始给用户带来的困惑\n\t{\n\t\tsize_t line_num = num + 1;\n\t\tif (line_range && (line_num < line_range->first || line_num > line_range->second))\n\t\t\tcontinue;\n\n\t\tos << \"\\t(line \" << num + 1 << \") \"\n\t\t\t<< *(qr.file->begin() + num) << endl;\n\t}\n\treturn os;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_TextQuery/QueryResult.h",
    "content": "#ifndef QUERY_RESULT_H\n#define QUERY_RESULT_H\n\n#include <iostream>\n#include <string>\n#include <set>\n#include <vector>\n#include <memory>\n\nclass QueryResult {\n\tfriend std::ostream& print(std::ostream&, const QueryResult&, std::pair<size_t, size_t>*);\npublic:\n\tusing line_no = std::vector<std::string>::size_type;\n\tQueryResult(std::string s,\n\t\t\t\tstd::shared_ptr<std::set<line_no>> p,\n\t\t\t\tstd::shared_ptr<std::vector<std::string>> f):\n\t\tsought(s), lines(p), file(f) {}\n\n\tstd::set<line_no>::iterator begin() { return lines->begin(); }\n\tstd::set<line_no>::iterator end()   { return lines->end(); }\n\n\tstd::shared_ptr<std::vector<std::string>> get_file() const { return file; }\n\nprivate:\n\tstd::string sought;\t// 查询单词\n\tstd::shared_ptr<std::set<line_no>> lines;\t// 出现的行号\n\tstd::shared_ptr<std::vector<std::string>> file;\t// 输入文件\n};\n\nextern std::ostream& print(std::ostream&, const QueryResult&, std::pair<size_t, size_t>* line_range = nullptr);\n\n#endif // QUERY_RESULT_H\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_TextQuery/Query_ex15_37.h",
    "content": "// 专门为练习15.37定义的文件\n\n#ifndef QUERY_H\n#define QUERY_H\n\n#include <iostream>\n#include <string>\n#include <memory>\n\n#include \"QueryResult.h\"\n#include \"TextQuery.h\"\n\nstatic constexpr bool open_debug = true;\ninline void PrintDebug(const char *msg)\n{\n\tif (open_debug)\n\t\tstd::cout << \"[Debug] \" << msg << \"\\n\";\n}\n\nstatic QueryResult invalid_qr(\"\", 0, 0);\n\n// 这是一个抽象基类，具体的查询类型从中派生，所有成员都是private的\nclass Query_base {\n\tfriend class Query;\n\tfriend class NotQuery;\n\tfriend class AndQuery;\n\tfriend class OrQuery;\n\tfriend class BinaryQuery;\n\nprotected:\n\tusing line_no = TextQuery::line_no;\n\tvirtual ~Query_base() = default;\n\nprivate:\n\t// eval返回与当前Query匹配的QueryResult\n\tvirtual QueryResult eval(const TextQuery&) const = 0;\n\n\t// rep是表示查询的一个string\n\tvirtual std::string rep() const = 0;\n};\n\nclass WordQuery : public Query_base {\n\tfriend class Query;\t\t// Query使用WordQuery构造函数\n\n\tWordQuery(const std::string &s) : query_word(s)\n\t{ PrintDebug(\"WordQuery::WordQuery(const std::string &s)\"); }\n\t\n\t// 具体的类：WordQuery将定义所有继承而来的纯虚函数\n\tQueryResult eval(const TextQuery &t) const override\n\t{ return t.query(query_word); }\n\n\tstd::string rep() const override\n\t{ \n\t\tPrintDebug(\"WordQuery::rep\");\n\t\treturn query_word; \n\t}\n\tstd::string query_word;\t// 要查找的单词\n};\n\n// 这是一个管理Query_base继承体系的接口类\nclass Query {\n\t// 这些运算符需要访问接受shared_ptr的构造函数，而该函数是私有的\n\tfriend Query operator~(const Query &);\n\tfriend Query operator|(const Query&, const Query&);\n\tfriend Query operator&(const Query&, const Query&);\n\n\tfriend class NotQuery;\n\tfriend class AndQuery;\n\tfriend class OrQuery;\n\tfriend class BinaryQuery;\n\npublic:\n\tQuery(const std::string &s) : q(new WordQuery(s))\t// 构建一个新的WordQuery\n\t{ PrintDebug(\"Query::Query(const std::string &s)\"); }\n\n\t// 接口函数：调用对应的Query_base操作\n\tQueryResult eval(const TextQuery &t) const { return q->eval(t); }\n\tstd::string rep() const\n\t{ \n\t\tPrintDebug(\"Query::rep\");\n\t\treturn q->rep(); \n\t}\n\nprivate:\n\tQuery(std::shared_ptr<Query_base> query) : q(query)\n\t{ PrintDebug(\"Query::Query(std::shared_ptr<Query_base> query)\"); }\n\n\tstd::shared_ptr<Query_base> q;\n};\n\ninline std::ostream& operator<<(std::ostream &os, const Query &query)\n{\n\t// Query::rep通过它的Query_base指针对rep()进行了虚调用\n\treturn os << query.rep();\n}\n\nclass NotQuery : public Query_base {\n\tfriend Query operator~(const Query&);\n\t\n\tNotQuery(const Query &q) : query(q.q)\n\t{ PrintDebug(\"NotQuery::NotQuery(const Query &q)\"); }\n\n\t// 具体的类：NotQuery将定义所有继承而来的纯虚函数\n\tstd::string rep() const\n\t{ \n\t\tPrintDebug(\"NotQuery::rep\");\n\t\treturn \"~\" + query->rep() + \")\"; \n\t}\n\n\tQueryResult eval(const TextQuery&) const;\n\tstd::shared_ptr<Query_base> query;\n};\n\ninline Query operator~(const Query &operand)\n{\n\treturn std::shared_ptr<Query_base>(new NotQuery(operand));\n}\n\nclass BinaryQuery : public Query_base {\nprotected:\n\tBinaryQuery(const Query &l, const Query &r, std::string s) :\n\t\tlhs(l.q), rhs(r.q), opSym(s)\n\t{ PrintDebug(\"BinaryQuery::BinaryQuery(const Query &l, const Query &r, std::string s)\"); }\n\n\t// 抽象类：BinaryQuery不定义eval\n\tstd::string rep() const\n\t{\n\t\tPrintDebug(\"BinaryQuery::rep\");\n\t\treturn \"(\" + lhs->rep() + \" \" + opSym + \" \" + rhs->rep() + \")\";\n\t}\n\n\tstd::shared_ptr<Query_base> lhs, rhs;\t\t// 左侧和右侧的运算对象\n\tstd::string opSym;\t// 运算符的名字\n};\n\nclass AndQuery : public BinaryQuery {\n\tfriend Query operator&(const Query&, const Query&);\n\n\tAndQuery(const Query &left, const Query &right) : BinaryQuery(left, right, \"&\")\n\t{ PrintDebug(\"AndQuery::AndQuery(const Query &left, const Query &right)\"); }\n\n\t// 具体的类：AndQuery继承了rep并且定义了其他纯虚函数\n\tQueryResult eval(const TextQuery&) const { return invalid_qr; }\n};\n\ninline Query operator&(const Query &lhs, const Query &rhs)\n{\n\treturn std::shared_ptr<Query_base>(new AndQuery(lhs, rhs));\n};\n\nclass OrQuery : public BinaryQuery {\n\tfriend Query operator|(const Query&, const Query&);\n\n\tOrQuery(const Query &left, const Query &right) : BinaryQuery(left, right, \"|\")\n\t{ PrintDebug(\"OrQuery::OrQuery(const Query &left, const Query &right)\"); }\n\n\tQueryResult eval(const TextQuery&) const { return invalid_qr; }\n};\n\ninline Query operator|(const Query &lhs, const Query &rhs)\n{\n\treturn std::shared_ptr<Query_base>(new OrQuery(lhs, rhs));\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_TextQuery/Query_ex15_41.cpp.bak",
    "content": "#include \"Query.h\"\n#include <algorithm>\n\n// 返回运算对象查询结果set的并集\nQueryResult\nOrQuery::eval(const TextQuery &text) const\n{\n\t// 通过Query成员lhs和rhs进行虚调用\n\t// 调用eval返回每个运算对象的QueryResult\n\tauto right = rhs.eval(text), left = lhs.eval(text);\n\n\t// 将左侧运算对象的行号拷贝到结果set中\n\tauto ret_lines = std::make_shared<std::set<line_no>>(left.begin(), left.end());\n\n\t// 插入右侧运算对象所得的行号\n\tret_lines->insert(right.begin(), right.end());\n\n\t// 返回一个新的QueryResult，它表示lhs和rhs的并集\n\treturn QueryResult(rep(), ret_lines, left.get_file());\n}\n\n// 返回运算对象查询结果set的交集\nQueryResult\nAndQuery::eval(const TextQuery &text) const\n{\n\t// 通过Query运算对象进行的虚调用，以获得运算对象的查询结果set\n\tauto left = lhs.eval(text), right = rhs.eval(text);\n\n\t// 保存left和right交集的set\n\tauto ret_lines = std::make_shared<std::set<line_no>>();\n\n\t// 将两个范围的交集写入一个目的迭代器中\n\t// 本次调用的目的迭代器向ret添加元素\n\tstd::set_intersection(left.begin(), left.end(), right.begin(), right.end(), std::inserter(*ret_lines, ret_lines->begin()));\n\n\treturn QueryResult(rep(), ret_lines, left.get_file());\n}\n\n// 返回运算对象的结果set中不存在的行\nQueryResult\nNotQuery::eval(const TextQuery &text) const\n{\n\t// 通过Query运算对象对eval进行虚调用\n\tauto result = query.eval(text);\n\n\t// 开始时结果为空\n\tauto ret_lines = std::make_shared<std::set<line_no>>();\n\n\t// 我们必须在运算对象出现的所有行中进行迭代\n\tauto beg = result.begin(), end = result.end();\n\n\t// 对于输入文件的每一行，如果该行不在result当中，则将其添加到ret_lines\n\tauto sz = result.get_file()->size();\n\tfor (size_t n = 0; n != sz; ++n)\n\t{\n\t\t// 如果我们还没有处理完result的所有行\n\t\t// 检查当前行是否存在\n\t\tif (beg == end || *beg != n)\n\t\t\tret_lines->insert(n);\t// 如果不在result中，添加这一行\n\t\telse if (beg != end)\n\t\t\t++beg;\t// 否则继续获取result的下一行（如果有的话）\n\t}\n\n\treturn QueryResult(rep(), ret_lines, result.get_file());\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_TextQuery/Query_ex15_41.h",
    "content": "// 为练习15.41专门定义的文件\n\n#ifndef QUERY_H\n#define QUERY_H\n\n#include <iostream>\n#include <string>\n#include <memory>\n\n#include \"QueryResult.h\"\n#include \"TextQuery.h\"\n\nstatic constexpr bool open_debug = false;\ninline void PrintDebug(const char *msg)\n{\n\tif (open_debug)\n\t\tstd::cout << \"[Debug] \" << msg << \"\\n\";\n}\n\nstatic QueryResult invalid_qr(\"\", 0, 0);\n\n// 这是一个抽象基类，具体的查询类型从中派生，所有成员都是private的\nclass Query_base {\n\tfriend class Query;\n\nprotected:\n\tusing line_no = TextQuery::line_no;\n\tvirtual ~Query_base() = default;\n\nprivate:\n\t// eval返回与当前Query匹配的QueryResult\n\tvirtual QueryResult eval(const TextQuery&) const = 0;\n\n\t// rep是表示查询的一个string\n\tvirtual std::string rep() const = 0;\n};\n\nclass WordQuery : public Query_base {\n\tfriend class Query;\t\t// Query使用WordQuery构造函数\n\n\tWordQuery(const std::string &s) : query_word(s)\n\t{ PrintDebug(\"WordQuery::WordQuery(const std::string &s)\"); }\n\t\n\t// 具体的类：WordQuery将定义所有继承而来的纯虚函数\n\tQueryResult eval(const TextQuery &t) const override\n\t{ return t.query(query_word); }\n\n\tstd::string rep() const override\n\t{ \n\t\tPrintDebug(\"WordQuery::rep\");\n\t\treturn query_word; \n\t}\n\tstd::string query_word;\t// 要查找的单词\n};\n\n// 这是一个管理Query_base继承体系的接口类\nclass Query {\n\t// 这些运算符需要访问接受shared_ptr的构造函数，而该函数是私有的\n\tfriend Query operator~(const Query &);\n\tfriend Query operator|(const Query&, const Query&);\n\tfriend Query operator&(const Query&, const Query&);\n\npublic:\n\tQuery(const std::string &s) : q(new WordQuery(s)), use(new size_t(1))\t// 构建一个新的WordQuery\n\t{ PrintDebug(\"Query::Query(const std::string &s)\"); }\n\n\tQuery(const Query &query) : q(query.q), use(query.use) { ++*use; }\n\tQuery& operator=(const Query &rhs)\n\t{\n\t\t++*rhs.use;\n\t\tif (--*use == 0) {\n\t\t\tdelete q;\n\t\t\tdelete use;\n\t\t}\n\n\t\tq = rhs.q;\n\t\tuse = rhs.use;\n\t\treturn *this;\n\t}\n\n\t// 接口函数：调用对应的Query_base操作\n\tQueryResult eval(const TextQuery &t) const { return q->eval(t); }\n\tstd::string rep() const\n\t{ \n\t\tPrintDebug(\"Query::rep\");\n\t\treturn q->rep(); \n\t}\n\nprivate:\n\tQuery(Query_base *query) : q(query)\n\t{ PrintDebug(\"Query::Query(std::shared_ptr<Query_base> query)\"); }\n\n\tsize_t *use;\t// 引用计数\n\tQuery_base *q;\n};\n\ninline std::ostream& operator<<(std::ostream &os, const Query &query)\n{\n\t// Query::rep通过它的Query_base指针对rep()进行了虚调用\n\treturn os << query.rep();\n}\n\nclass NotQuery : public Query_base {\n\tfriend Query operator~(const Query&);\n\t\n\tNotQuery(const Query &q) : query(q)\n\t{ PrintDebug(\"NotQuery::NotQuery(const Query &q)\"); }\n\n\t// 具体的类：NotQuery将定义所有继承而来的纯虚函数\n\tstd::string rep() const\n\t{ \n\t\tPrintDebug(\"NotQuery::rep\");\n\t\treturn \"~\" + query.rep() + \")\"; \n\t}\n\n\tQueryResult eval(const TextQuery&) const;\n\tQuery query;\n};\n\ninline Query operator~(const Query &operand)\n{\n\treturn new NotQuery(operand);\n}\n\nclass BinaryQuery : public Query_base {\nprotected:\n\tBinaryQuery(const Query &l, const Query &r, std::string s) :\n\t\tlhs(l), rhs(r), opSym(s)\n\t{ PrintDebug(\"BinaryQuery::BinaryQuery(const Query &l, const Query &r, std::string s)\"); }\n\n\t// 抽象类：BinaryQuery不定义eval\n\tstd::string rep() const\n\t{\n\t\tPrintDebug(\"BinaryQuery::rep\");\n\t\treturn \"(\" + lhs.rep() + \" \" + opSym + \" \" + rhs.rep() + \")\";\n\t}\n\n\tQuery lhs, rhs;\t\t// 左侧和右侧的运算对象\n\tstd::string opSym;\t// 运算符的名字\n};\n\nclass AndQuery : public BinaryQuery {\n\tfriend Query operator&(const Query&, const Query&);\n\n\tAndQuery(const Query &left, const Query &right) : BinaryQuery(left, right, \"&\")\n\t{ PrintDebug(\"AndQuery::AndQuery(const Query &left, const Query &right)\"); }\n\n\t// 具体的类：AndQuery继承了rep并且定义了其他纯虚函数\n\tQueryResult eval(const TextQuery&) const override;\n};\n\ninline Query operator&(const Query &lhs, const Query &rhs)\n{\n\treturn new AndQuery(lhs, rhs);\n};\n\nclass OrQuery : public BinaryQuery {\n\tfriend Query operator|(const Query&, const Query&);\n\n\tOrQuery(const Query &left, const Query &right) : BinaryQuery(left, right, \"|\")\n\t{ PrintDebug(\"OrQuery::OrQuery(const Query &left, const Query &right)\"); }\n\n\tQueryResult eval(const TextQuery&) const override;\n};\n\ninline Query operator|(const Query &lhs, const Query &rhs)\n{\n\treturn new OrQuery(lhs, rhs);\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_TextQuery/TextQuery.cpp",
    "content": "#include <sstream>\n#include <string>\n\n#include \"TextQuery.h\"\n#include \"QueryResult.h\"\n\nusing namespace std;\n\n// 把word里面不属于字母的内容剔除掉\ninline void FixWord(string &word)\n{\n\tstatic string s;\n\ts.clear();\n\tfor (auto c : word)\t{\n\t\tif (isalpha(c))\n\t\t\ts.push_back(c);\n\t}\n\tword = s;\n}\n\nTextQuery::TextQuery(ifstream &is): file(new vector<string>)\n{\n\tstring text;\n\twhile (getline(is, text)) {\t\t// 对文件中的每一行\n\t\tfile->push_back(text);\t\t// 保存此行文本\n\t\tint n = file->size() - 1;\t// 当前行号\n\t\tistringstream line(text);\t// 将行文本分解为单词\n\t\tstring word;\n\t\twhile (line >> word) {\t\t// 对行中每个单词\n\t\t\tFixWord(word);\n\t\t\t// 如果单词不在wm中，以之为下标在wm中添加一项\n\t\t\tauto &lines = wm[word];\t// lines是一个shared_ptr\n\t\t\tif (!lines)\t\t\t\t// 在我们第一次遇到这个单词时，此指针为空\n\t\t\t\tlines.reset(new set<line_no>);\t// 分配一个新的set\n\t\t\tlines->insert(n);\t\t// 将此行号插入set中\n\t\t}\n\t}\n}\n\nQueryResult\nTextQuery::query(const string &sought) const\n{\n\t// 如果未找到sought，我们将返回一个指向此set的指针\n\tstatic shared_ptr<set<line_no>> nodata(new set<line_no>);\n\n\t// 使用find而不是下标运算符来查找单词，避免将单词添加到wm中！\n\tauto loc = wm.find(sought);\n\tif (loc == wm.end())\n\t\treturn QueryResult(sought, nodata, file);\t// 未找到\n\telse\n\t\treturn QueryResult(sought, loc->second, file);\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_TextQuery/TextQuery.h",
    "content": "#ifndef TEXT_QUERY_H\n#define TEXT_QUERY_H\n\n#include <vector>\n#include <string>\n#include <fstream>\n#include <memory>\n#include <map>\n#include <set>\n\nclass QueryResult; // 为了定义函数query的返回类型，这个定义是必须的\nclass TextQuery {\npublic:\n\tusing line_no = std::vector<std::string>::size_type;\n\tTextQuery(std::ifstream&);\n\tQueryResult query(const std::string&) const;\n\nprivate:\n\tstd::shared_ptr<std::vector<std::string>> file; // 输入文件\n\t// 每个单词到它所在的行号的集合的映射\n\tstd::map<std::string, std::shared_ptr<std::set<line_no>>> wm;\n};\n\n#endif // TEXT_QUERY_H\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_TextQuery/main.cpp",
    "content": "// example: 文本查询程序再探(p562)\n\n#include <fstream>\n#include <iostream>\n#include <string>\n\n#include \"TextQuery.h\"\n#include \"QueryResult.h\"\n\n#define DEFAULT_EXAMPLE\n//#define TEST_EX15_37\n//#define TEST_EX15_41\n\n#ifdef TEST_EX15_37\n#include \"Query_ex15_37.h\"\n#endif\n\n#ifdef TEST_EX15_41\n#include \"Query_ex15_41.h\"\n#endif\n\n#ifdef DEFAULT_EXAMPLE\n#include \"Query.h\"\n#endif\n\nusing namespace std;\n\nvoid runQueries(ifstream &infile)\n{\n\t// infile是一个ifstream，指向我们要处理的文件\n\tTextQuery tq(infile); // 保存文件并建立查询map\n\n\t// 与用户交互，提示用户输入要查询的单词，完成查询并打印结果\n\twhile (true)\n\t{\n\t\tcout << \"Enter word to look for, or q to quit: \";\n\t\tstring s;\n\t\t// 若遇到了文件尾或用户输入了q时循环终止\n\t\tif (!(cin >> s) || s == \"q\") break;\n\n\t\t// 指向查询并打印结果\n\t\tprint(cout, tq.query(s)) << endl;\n\t}\n}\n\nvoid func1()\n{\n\tifstream infile(\"../../data/little_story.txt\");\n\tif (!infile)\n\t{\n\t\tcout << \"Cant open file\" << endl;\n\t\treturn;\n\t}\n\n\trunQueries(infile);\n}\n\n// 练习15.36\nvoid func2()\n{\n\tQuery q = (Query(\"fiery\") & Query(\"bird\")) | Query(\"wind\");\n\tcout << q << endl;\n}\n\n// 练习15.39\nvoid func3()\n{\n\tQuery q0 = Query(\"Daddy\");\n\tQuery q1 = ~Query(\"Alice\");\n\tQuery q2 = Query(\"hair\") | Query(\"Alice\");\n\tQuery q3 = Query(\"hair\") & Query(\"Alice\");\n\tQuery q4 = (Query(\"fiery\") & Query(\"bird\")) | Query(\"wind\");\n\n\tifstream infile(\"../../data/little_story.txt\");\n\tif (!infile)\n\t{\n\t\tcout << \"Cant open file\" << endl;\n\t\treturn;\n\t}\n\n\tTextQuery tq(infile);\n\n\tprint(cout, q0.eval(tq));\n\tprint(cout, q1.eval(tq));\n\tprint(cout, q2.eval(tq));\n\tprint(cout, q3.eval(tq));\n\tprint(cout, q4.eval(tq));\n}\n\n// 练习15.42（c）\nvoid func4()\n{\n\tQuery q = ~Query(\"Alice\");\n\n\tifstream infile(\"../../data/little_story.txt\");\n\tif (!infile)\n\t{\n\t\tcout << \"Cant open file\" << endl;\n\t\treturn;\n\t}\n\n\tTextQuery tq(infile);\n\n\tstd::pair<size_t, size_t> line_range{6, 10};\n\tprint(cout, q.eval(tq), &line_range);\n}\n\nint main()\n{\n\t//func1();\n\t//func2();\n\t//func3();\n\tfunc4();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_abstract_base_class.cpp",
    "content": "// example: 抽象基类（p540）\n\n#include <iostream>\n\nusing namespace std;\n\nclass Quote {\npublic:\n\tQuote() = default;\n\tQuote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) {}\n\n\tstd::string isbn() const { return bookNo; }\n\n\t// 返回给定数量的书籍的销售额\n\t// 派生类负责改写并使用不同的折扣计算算法\n\tvirtual double net_price(std::size_t n) const\n\t{ return n * price; }\n\n\tvirtual ~Quote() = default;\t// 对析构函数进行动态绑定\n\nprivate:\n\tstd::string bookNo;\n\nprotected:\n\tdouble price = 0.0;\t\t// 代表普通状态下不打折的价格\n};\n\n// 用于保存折扣值和购买量的类，派生类使用这些数据可以实现不同的价格策略\nclass Disc_quote : public Quote {\npublic:\n\tDisc_quote() = default;\n\tDisc_quote(const std::string& book, double p, std::size_t qty, double disc) :\n\t\tQuote(book, p), quantity(qty), discount(disc) {}\n\n\tdouble net_price(std::size_t) const = 0;\n\nprotected:\n\tstd::size_t quantity = 0;\t\t// 折扣适用的购买量\n\tdouble discount = 0.0;\t\t\t// 表示折扣的小数值\n};\n\nclass Bulk_quote : public Disc_quote\n{\npublic:\n\tBulk_quote() = default;\n\tBulk_quote(const std::string &book, double p, std::size_t qty, double disc)\n\t    : Disc_quote(book, p, qty, disc) {}\n\n\t// 覆盖基类的函数版本以实现基于大量购买的折扣政策\n\tdouble net_price(std::size_t cnt) const override\n\t{\n\t\tif (cnt >= quantity)\n\t\t\treturn cnt * (1 - discount) * price;\n\t\telse\n\t\t\treturn cnt * price;\n\t}\n};\n\nint main()\n{\n\t//Disc_quote obj; // 练习15.17：cannot declare variable ‘obj’ to be of abstract type ‘Disc_quote’\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_base_class.cpp",
    "content": "// example: 定义基类（p528）\n\n#include <iostream>\n#include <string>\n\nclass Quote {\npublic:\n\tQuote() = default;\n\tQuote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) {}\n\n\tstd::string isbn() const { return bookNo; }\n\n\t// 返回给定数量的书籍的销售额\n\t// 派生类负责改写并使用不同的折扣计算算法\n\tvirtual double net_price(std::size_t n) const\n\t{ return n * price; }\n\n\tvirtual ~Quote() = default;\t// 对析构函数进行动态绑定\n\nprivate:\n\tstd::string bookNo;\n\nprotected:\n\tdouble price = 0.0;\t\t// 代表普通状态下不打折的价格\n};\n\nusing namespace std;\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_basket.cpp",
    "content": "// example: 编写Basket类（容器与继承案例，p559）\n\n#include <iostream>\n#include <memory>\n#include <set>\n\nusing namespace std;\n\nclass Quote\n{\npublic:\n\tQuote() = default;\n\tQuote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) {}\n\n\tstd::string isbn() const { return bookNo; }\n\n\t// 返回给定数量的书籍的销售额\n\t// 派生类负责改写并使用不同的折扣计算算法\n\tvirtual double net_price(std::size_t n) const\n\t{ return n * price; }\n\n\tvirtual ~Quote() = default;\t// 对析构函数进行动态绑定\n\n\tvirtual void debug() const\n\t{\n\t\tcout << \"bookNo: \" << bookNo << endl\n\t\t     << \"price: \" << price << endl;\n\t}\n\n\tvirtual Quote* clone() const & { return new Quote(*this); }\n\tvirtual Quote* clone() && { return new Quote(std::move(*this)); }\n\nprivate:\n\tstd::string bookNo;\n\nprotected:\n\tdouble price = 0.0;\t\t// 代表普通状态下不打折的价格\n};\n\nclass Bulk_quote : public Quote // Bulk继承自Quote\n{\npublic:\n\tBulk_quote() = default;\n\tBulk_quote(const std::string &book, double p, std::size_t qty, double disc)\n\t    : Quote(book, p), min_qty(qty), discount(disc) {}\n\n\t// 覆盖基类的函数版本以实现基于大量购买的折扣政策\n\tdouble net_price(std::size_t cnt) const override\n\t{\n\t\tif (cnt >= min_qty)\n\t\t\treturn cnt * (1 - discount) * price;\n\t\telse\n\t\t\treturn cnt * price;\n\t}\n\n\tvoid debug() const override\n\t{\n\t\tQuote::debug();\n\t\tcout << \"min_qty: \" << min_qty << endl\n\t\t     << \"discount: \" << discount << endl;\n\t}\n\n\tBulk_quote* clone() const & override { return new Bulk_quote(*this); }\n\tBulk_quote* clone() && override { return new Bulk_quote(std::move(*this)); }\n\nprivate:\n\tstd::size_t min_qty = 0;\t// 适用折扣政策的最低购买量\n\tdouble discount = 0.0;\t\t// 以小数表示的折扣额\n};\n\n// 计算并打印销售给定数量的某种书籍所得的费用\ndouble print_total(ostream &os, const Quote &item, size_t n)\n{\n\t// 根据传入item形参的对象类型调用Quote::net_price\n\t// 或者Bulk_quote::net_price\n\tdouble ret = item.net_price(n);\n\tos << \"ISBN: \" << item.isbn() // 调用Quote::isbn\n\t   << \" # sold: \" << n << \" total due: \" << ret << endl;\n\n\treturn ret;\n}\n\nclass Basket\n{\npublic:\n\t// Basket使用合成的默认构造函数和拷贝控制成员\n\tvoid add_item(const std::shared_ptr<Quote> &sale) { items.insert(sale); }\n\n\tvoid add_item(const Quote &sale)\n\t{ items.insert(std::shared_ptr<Quote>(sale.clone())); }\n\n\tvoid add_item(Quote &&sale)\n\t{ items.insert(std::shared_ptr<Quote>(std::move(sale).clone())); }\n\n\t// 打印每本书的总价和购物篮中所有书的总价\n\tdouble total_receipt(std::ostream&) const;\n\nprivate:\n\t// 该函数用于比较shared_ptr, multiset成员会用到它\n\tstatic bool compare(const std::shared_ptr<Quote> &lhs, const std::shared_ptr<Quote> &rhs)\n\t{ return lhs->isbn() < rhs->isbn(); }\n\n\tstd::multiset<std::shared_ptr<Quote>, decltype(compare)*> items{compare};\n};\n\ndouble Basket::total_receipt(std::ostream &os) const\n{\n\tdouble sum = 0.0;\t// 保存实时计算出的总价格\n\t// iter指向ISBN相同的一批元素中的第一个\n\t// upper_bound返回一个迭代器，该迭代器指向这批元素的尾后位置\n\tfor (auto iter = items.cbegin(); iter != items.cend(); iter = items.upper_bound(*iter)) {\n\t\t// 我们知道在当前的Basket中至少有一个关键字元素\n\t\t// 打印该书籍对应的项目\n\t\tsum += print_total(os, **iter, items.count(*iter));\n\t}\n\tos << \"Total Sale: \" << sum << endl; // 打印最终的总价格\n\treturn sum;\n}\n\nvoid func1()\n{\n\t// 直接使用智能指针插入\n\tBasket bsk;\n\tbsk.add_item(make_shared<Quote>(\"0-201-70353-X\", 10));\n\tbsk.add_item(make_shared<Bulk_quote>(\"0-201-70354-X\", 10, 2, 0.5));\n\tbsk.add_item(make_shared<Bulk_quote>(\"0-201-70354-X\", 10, 2, 0.5));\n\n\tbsk.total_receipt(cout);\n}\n\nvoid func2()\n{\n\t// 使用拷贝/移动的版本插入\n\tBasket bsk;\n\tbsk.add_item(Quote(\"0-201-70353-X\", 10));\n\tbsk.add_item(Bulk_quote(\"0-201-70354-X\", 10, 2, 0.5));\n\tbsk.add_item(Bulk_quote(\"0-201-70354-X\", 10, 2, 0.5));\n\n\tbsk.total_receipt(cout);\n}\n\nint main()\n{\n\t//func1();\n\tfunc2();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_derived_class.cpp",
    "content": "// example: 定义派生类（p529）\n\n#include <iostream>\n#include <vector>\n#include <memory>\n\nusing namespace std;\n\nclass Quote {\npublic:\n\tQuote() = default;\n\tQuote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) {}\n\n\tstd::string isbn() const { return bookNo; }\n\n\t// 返回给定数量的书籍的销售额\n\t// 派生类负责改写并使用不同的折扣计算算法\n\tvirtual double net_price(std::size_t n) const\n\t{ return n * price; }\n\n\tvirtual ~Quote() = default;\t// 对析构函数进行动态绑定\n\n\tvirtual void debug() const\n\t{\n\t\tcout << \"bookNo: \" << bookNo << endl\n\t\t     << \"price: \" << price << endl;\n\t}\n\nprivate:\n\tstd::string bookNo;\n\nprotected:\n\tdouble price = 0.0;\t\t// 代表普通状态下不打折的价格\n};\n\nclass Bulk_quote : public Quote // Bulk继承自Quote\n{\npublic:\n\tBulk_quote() = default;\n\tBulk_quote(const std::string &book, double p, std::size_t qty, double disc)\n\t    : Quote(book, p), min_qty(qty), discount(disc) {}\n\n\t// 覆盖基类的函数版本以实现基于大量购买的折扣政策\n\tdouble net_price(std::size_t cnt) const override\n\t{\n\t\tif (cnt >= min_qty)\n\t\t\treturn cnt * (1 - discount) * price;\n\t\telse\n\t\t\treturn cnt * price;\n\t}\n\n\tvoid debug() const override\n\t{\n\t\tQuote::debug();\n\t\tcout << \"min_qty: \" << min_qty << endl\n\t\t     << \"discount: \" << discount << endl;\n\t}\n\nprivate:\n\tstd::size_t min_qty = 0;\t// 适用折扣政策的最低购买量\n\tdouble discount = 0.0;\t\t// 以小数表示的折扣额\n};\n\n// 练习15.11\nvoid func1()\n{\n\tQuote *quote = nullptr;\n\tQuote base(\"0-201-70353-X\", 5);\n\tBulk_quote derived(\"0-201-70353-X\", 5, 5, 0.5);\n\n\tquote = &base;\n\tquote->debug();\n\n\tquote = &derived;\n\tquote->debug();\n}\n\n// 练习15.28\nvoid func2()\n{\n\tvector<Quote> basket = {\n\t\tBulk_quote(\"0-201-70353-X\", 5, 5, 0.5),\n\t\tBulk_quote(\"0-201-70354-X\", 6, 5, 0.5),\n\t\tBulk_quote(\"0-201-70355-X\", 7, 5, 0.5),\n\t};\n\n\tdouble total = 0;\n\tfor (const auto &item : basket)\n\t\ttotal += item.net_price(10);\n\n\tcout << \"total: \" << total << endl;\n}\n\n// 练习15.29\nvoid func3()\n{\n\tvector<shared_ptr<Quote>> basket = {\n\t\tmake_shared<Bulk_quote>(\"0-201-70353-X\", 5, 5, 0.5),\n\t\tmake_shared<Bulk_quote>(\"0-201-70354-X\", 6, 5, 0.5),\n\t\tmake_shared<Bulk_quote>(\"0-201-70355-X\", 7, 5, 0.5),\n\t};\n\n\tdouble total = 0;\n\tfor (const auto &item : basket)\n\t\ttotal += item->net_price(10);\n\n\tcout << \"total: \" << total << endl;\n}\n\nint main()\n{\n\t//func1();\n\t//func2();\n\tfunc3();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/example_derived_class_copy_control_members.cpp",
    "content": "// example: 派生类的拷贝控制成员（p554）\n\n#include <iostream>\n\nusing namespace std;\n\nclass Quote {\npublic:\n\tQuote() { cout << \"Quote::Quote()\" << endl; }\n\tQuote(const std::string &book, double sales_price) : bookNo(book), price(sales_price)\n\t{ cout << \"Quote::Quote(const std::string &book, double sales_price)\" << endl; }\n\n\tQuote(const Quote &q) : bookNo(q.bookNo), price(q.price)\n\t{ cout << \"Quote::Quote(const Quote &q)\" << endl; }\n\n\tQuote(Quote &&q) : bookNo(std::move(q.bookNo)), price(q.price)\n\t{ cout << \"Quote::Quote(Quote &&q)\" << endl; }\n\n\tQuote& operator=(const Quote &q)\n\t{\n\t\tbookNo = q.bookNo;\n\t\tprice = q.price;\n\t\tcout << \"Quote::operator=(const Quote &q)\" << endl;\n\t\treturn *this;\n\t}\n\n\tstd::string isbn() const { return bookNo; }\n\n\t// 返回给定数量的书籍的销售额\n\t// 派生类负责改写并使用不同的折扣计算算法\n\tvirtual double net_price(std::size_t n) const\n\t{ return n * price; }\n\n\tvirtual ~Quote()\t// 对析构函数进行动态绑定\n\t{\n\t\tcout << \"Quote::~Quote()\" << endl;\n\t}\n\nprivate:\n\tstd::string bookNo;\n\nprotected:\n\tdouble price = 0.0;\t\t// 代表普通状态下不打折的价格\n};\n\nclass Bulk_quote : public Quote // Bulk继承自Quote\n{\npublic:\n\tBulk_quote() { cout << \"Bulk_quote::Bulk_quote()\" << endl; }\n\tBulk_quote(const std::string &book, double p, std::size_t qty, double disc)\n\t    : Quote(book, p), min_qty(qty), discount(disc)\n\t{ cout << \"Bulk_quote::Bulk_quote(const std::string &book, double p, std::size_t qty, double disc)\" << endl; }\n\n\tBulk_quote(const Bulk_quote &bq) : Quote(bq), min_qty(bq.min_qty), discount(bq.discount)\n\t{ cout << \"Bulk_quote::Bulk_quote(const Bulk_quote &bq)\" << endl; }\n\n\tBulk_quote(Bulk_quote &&bq) : Quote(std::move(bq)), min_qty(bq.min_qty), discount(bq.discount)\n\t{ cout << \"Bulk_quote::Bulk_quote(Bulk_quote &&bq)\" << endl; }\n\n\tBulk_quote& operator=(const Bulk_quote &bq)\n\t{\n\t\tQuote::operator=(bq);\n\t\tmin_qty = bq.min_qty;\n\t\tdiscount = bq.discount;\n\t\tcout << \"Bulk_quote::operator=(const Bulk_quote &bq)\" << endl;\n\t\treturn *this;\n\t}\n\n\t~Bulk_quote()\n\t{\n\t\tcout << \"Bulk_quote::Bulk_quote()\" << endl;\n\t}\n\n\t// 覆盖基类的函数版本以实现基于大量购买的折扣政策\n\tdouble net_price(std::size_t cnt) const override\n\t{\n\t\tif (cnt >= min_qty)\n\t\t\treturn cnt * (1 - discount) * price;\n\t\telse\n\t\t\treturn cnt * price;\n\t}\n\nprivate:\n\tstd::size_t min_qty = 0;\t// 适用折扣政策的最低购买量\n\tdouble discount = 0.0;\t\t// 以小数表示的折扣额\n};\n\n// 练习15.26\nvoid func()\n{\n\tcout << \"1 ===>\" << endl;\n\tBulk_quote bq;\t// 先调用基类的默认构造函数，然后调用派生类的\n\n\tcout << \"2 ===>\" << endl;\n\tBulk_quote bq2(\"0-201-70353-X\", 10, 5, .5); // 先调用基类的构造函数，然后调用派生类的\n\n\tcout << \"3 ===>\" << endl;\n\tBulk_quote bq3(bq2);\t// 先调用基类的拷贝构造函数，然后调用派生类的\n\t\n\tcout << \"4 ===>\" << endl;\n\tBulk_quote bq4(std::move(bq2));\t// 先调用基类的移动构造函数，然后调用派生类的\n\n\tcout << \"5 ===>\" << endl;\n\tbq4 = bq3;\t// 先调用基类的赋值运算符，再调用派生类的\n\n\tcout << \"6 Destructors calling ===>\" << endl;\n}\n\nint main()\n{\n\tfunc();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_01.md",
    "content": "练习15.1：什么是虚成员？\n\n---\n\n虚函数是这样一种函数：它的基类希望其派生类对它进行覆盖。当使用指针或引用调用虚函数时，该调用将被动态绑定。根据引用或指针所绑定的对象类型不同，该调用可能执行基类的版本，也可能执行某个派生类的版本。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_02.md",
    "content": "练习15.2：protected访问说明符与private有何区别？\n\n---\n\n被protected说明的成员，可以使其在它的派生类中被访问，而禁止其他用户访问。被private说明的成员，既不能在其派生类中被访问，也不能被其他用户访问。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_03.md",
    "content": "练习15.3：定义你自己的Quote类和print_total函数。\n\n---\n\n见[案例](./example_base_class.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_04.md",
    "content": "练习15.4：下面哪条声明语句使不正确的？请解释原因。\n\nclass Base { ... };\n\n(a) class Derived : public Derived { ... }; // 错误，一个类不能派生它本身\n\n(b) class Derived : private Base { ... }; // 正确\n\n(c) class Derived : public Base; // 错误，派生列表以及与定义有关的其他细节必须与类的主体一起出现。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_05.md",
    "content": "练习15.5：定义你自己的Bulk_quote类。\n\n---\n\n见[案例](./example_derived_class.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_06.cpp",
    "content": "// 练习15.6：将Quote和Bulk_quote的对象传递给15.2.1节（第529页）练习中的\n// print_total函数，检查该函数是否正确。\n\n#include <iostream>\n\nusing namespace std;\n\nclass Quote {\npublic:\n\tQuote() = default;\n\tQuote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) {}\n\n\tstd::string isbn() const { return bookNo; }\n\n\t// 返回给定数量的书籍的销售额\n\t// 派生类负责改写并使用不同的折扣计算算法\n\tvirtual double net_price(std::size_t n) const\n\t{ return n * price; }\n\n\tvirtual ~Quote() = default;\t// 对析构函数进行动态绑定\n\nprivate:\n\tstd::string bookNo;\n\nprotected:\n\tdouble price = 0.0;\t\t// 代表普通状态下不打折的价格\n};\n\nclass Bulk_quote : public Quote // Bulk继承自Quote\n{\npublic:\n\tBulk_quote() = default;\n\tBulk_quote(const std::string &book, double p, std::size_t qty, double disc)\n\t    : Quote(book, p), min_qty(qty), discount(disc) {}\n\n\t// 覆盖基类的函数版本以实现基于大量购买的折扣政策\n\tdouble net_price(std::size_t cnt) const override\n\t{\n\t\tif (cnt >= min_qty)\n\t\t\treturn cnt * (1 - discount) * price;\n\t\telse\n\t\t\treturn cnt * price;\n\t}\n\nprivate:\n\tstd::size_t min_qty = 0;\t// 适用折扣政策的最低购买量\n\tdouble discount = 0.0;\t\t// 以小数表示的折扣额\n};\n\n// 计算并打印销售给定数量的某种书籍所得的费用\ndouble print_total(ostream &os, const Quote &item, size_t n)\n{\n\t// 根据传入item形参的对象类型调用Quote::net_price\n\t// 或者Bulk_quote::net_price\n\tdouble ret = item.net_price(n);\n\tos << \"ISBN: \" << item.isbn() // 调用Quote::isbn\n\t   << \" # sold: \" << n << \" total due: \" << ret << endl;\n\n\treturn ret;\n}\n\nint main()\n{\n\tQuote base(\"0-201-70353-X\", 10);\n\tBulk_quote derived(\"0-201-70353-X\", 10, 5, 0.5);\n\n\tprint_total(cout, base, 5); // 50\n\tprint_total(cout, derived, 5); // 25\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_07.cpp",
    "content": "// 练习15.7：定义一个类使其实现一种数量受限的折扣策略，具体策略是：当购买书籍\n// 的数量不超过一个给定的限量时享受折扣，如果购买量一旦超过了限量，则超出的部分\n// 将以原价销售。\n\n#include <iostream>\n\nusing namespace std;\n\nclass Quote {\npublic:\n\tQuote() = default;\n\tQuote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) {}\n\n\tstd::string isbn() const { return bookNo; }\n\n\t// 返回给定数量的书籍的销售额\n\t// 派生类负责改写并使用不同的折扣计算算法\n\tvirtual double net_price(std::size_t n) const\n\t{ return n * price; }\n\n\tvirtual ~Quote() = default;\t// 对析构函数进行动态绑定\n\nprivate:\n\tstd::string bookNo;\n\nprotected:\n\tdouble price = 0.0;\t\t// 代表普通状态下不打折的价格\n};\n\nclass Bulk_quote : public Quote // Bulk继承自Quote\n{\npublic:\n\tBulk_quote() = default;\n\tBulk_quote(const std::string &book, double p, std::size_t qty, double disc)\n\t    : Quote(book, p), min_qty(qty), discount(disc) {}\n\n\t// 覆盖基类的函数版本以实现基于大量购买的折扣政策\n\tdouble net_price(std::size_t cnt) const override\n\t{\n\t\tif (cnt <= min_qty)\n\t\t\treturn cnt * (1 - discount) * price;\n\t\telse {\n\t\t\tauto nodiscount_cnt = cnt - min_qty;\n\t\t\treturn nodiscount_cnt * price + (min_qty * (1 - discount) * price);\n\t\t}\n\t}\n\nprivate:\n\tstd::size_t min_qty = 0;\t// 适用折扣政策的最低购买量\n\tdouble discount = 0.0;\t\t// 以小数表示的折扣额\n};\n\n// 计算并打印销售给定数量的某种书籍所得的费用\ndouble print_total(ostream &os, const Quote &item, size_t n)\n{\n\t// 根据传入item形参的对象类型调用Quote::net_price\n\t// 或者Bulk_quote::net_price\n\tdouble ret = item.net_price(n);\n\tos << \"ISBN: \" << item.isbn() // 调用Quote::isbn\n\t   << \" # sold: \" << n << \" total due: \" << ret << endl;\n\n\treturn ret;\n}\n\nint main()\n{\n\tQuote base(\"0-201-70353-X\", 10);\n\tBulk_quote derived(\"0-201-70353-X\", 10, 5, 0.5);\n\n\tprint_total(cout, base, 10); // 100\n\tprint_total(cout, derived, 10); // 5本享有折扣，75\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_08.md",
    "content": "练习15.8：给出静态类型和动态类型的定义。\n\n---\n\n静态类型：对象被定义的类型或表达式产生的类型。静态类型在编译时是已知的。\n\n动态类型：对象在运行时的类型。引用所引对象或指针所指对象的动态类型可能与该引用或指针的静态类型不同。基类的指针或引用可以指向一个派生类对象。在这样的情况中，静态类型是基类的引用（或指针），而动态类型是派生类的引用（或指针）。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_09.md",
    "content": "练习15.9：在什么清空下表达式的静态类型可能与动态类型不同？请给出三个静态类型与动态类型不同的例子。\n\n```\n\n当使用一个基类的引用或指针，实际绑定到的是一个派生类对象，并调用其虚函数时。\n\n比如：\n\n```\nprint(ostream &os); // os绑定到的实参可能是一个ofstream对象，也可能是一个cout\n\nOnDie(Obj *obj); // obj绑定到的是派生类的对象，具体是哪个派生类直到运行时才知道\n\nm_logic->Update();\t// m_logic是基类的指针，它可能绑定到了派生类的对象\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_10.md",
    "content": "练习15.10：回忆我们在8.1节（第279页）进行的讨论，解释第284页中将ifstream传递给Sales_data的read函数的程序是如何工作的。\n\n---\n\nread函数接受的是istream&，它是一个基类的引用，可以动态绑定到派生类的对象（比如ifstream实例）上面，从而令派生类执行自己实现的虚函数版本。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_11.md",
    "content": "练习15.11：为你的Quote类体系添加一个名为debug的虚函数，令其分别显示每个类的数据成员。\n\n---\n\n见[案例](./example_derived_class.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_12.md",
    "content": "练习15.12：有必要将一个成员函数同时声明成override和final吗？为什么？\n\n---\n\n是可以的，表示其后续的派生类无法再重写这个虚函数了。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_13.md",
    "content": "练习15.13：给定下面的类，解释每个print函数的机理：\n\n```\nclass base\n{\npublic:\n\tstring name() { return basename; }\n\tvirtual void print(ostream &os) { os << basename; }\nprivate:\n\tstring basename;\n};\n\nclass derived : base\n{\npublic:\n\tvoid print(ostream &os) { print(os); os << \" \" << i; }\nprivate:\n\tint i;\n};\n```\n\n上述代码中存在问题吗？如果有，你该如何修改它？\n\n---\n\nderived的print函数有问题，由于调用了自身的print，会造成递归调用。应该调用基类的：base::print。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_14.md",
    "content": "练习15.14：给定上一题中的类以及下面这些对象，说明在运行时调用哪个函数？\n\n```\nbase bobj;\nbase *bp1 = &bobj;\nbase &br1 = bobj;\n\nderived dboj;\nbase *bp2 = &dobj;\nbase &br2 = dobj;\n```\n\n---\n\n(a) bobj.print(); // base::print\n\n(b) dboj.print(); // derived::print\n\n(c) bp1->name(); // base::name\n\n(d) bp2->name(); // base::name\n\n(e) br1.print(); // base::print\n\n(f) br2.print(); // derived::print\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_15.md",
    "content": "练习15.15：定义你自己的Disc_quote和Bulk_quote。\n\n---\n\n见[案例](./example_abstract_base_class.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_16.cpp",
    "content": "// 练习15.16：改写你在15.2.2节（第533页）练习中编写的数量受限的折扣策略，令其\n// 继承Disc_quote。\n\n#include <iostream>\n\nusing namespace std;\n\nclass Quote {\npublic:\n\tQuote() = default;\n\tQuote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) {}\n\n\tstd::string isbn() const { return bookNo; }\n\n\t// 返回给定数量的书籍的销售额\n\t// 派生类负责改写并使用不同的折扣计算算法\n\tvirtual double net_price(std::size_t n) const\n\t{ return n * price; }\n\n\tvirtual ~Quote() = default;\t// 对析构函数进行动态绑定\n\nprivate:\n\tstd::string bookNo;\n\nprotected:\n\tdouble price = 0.0;\t\t// 代表普通状态下不打折的价格\n};\n\n// 用于保存折扣值和购买量的类，派生类使用这些数据可以实现不同的价格策略\nclass Disc_quote : public Quote {\npublic:\n\tDisc_quote() = default;\n\tDisc_quote(const std::string& book, double p, std::size_t qty, double disc) :\n\t\tQuote(book, p), quantity(qty), discount(disc)\n\t{}\n\n\tdouble net_price(std::size_t) const = 0;\n\nprotected:\n\tstd::size_t quantity = 0;\t\t// 折扣适用的购买量\n\tdouble discount = 0.0;\t\t\t// 表示折扣的小数值\n};\n\nclass Bulk_quote : public Disc_quote\n{\npublic:\n\tBulk_quote() = default;\n\tBulk_quote(const std::string &book, double p, std::size_t qty, double disc)\n\t    : Disc_quote(book, p, qty, disc)\n\t{}\n\n\t// 覆盖基类的函数版本以实现基于大量购买的折扣政策\n\tdouble net_price(std::size_t cnt) const override\n\t{\n\t\tif (cnt <= quantity)\n\t\t\treturn cnt * (1 - discount) * price;\n\t\telse {\n\t\t\tauto nodiscount_cnt = cnt - quantity;\n\t\t\treturn nodiscount_cnt * price + (quantity * (1 - discount) * price);\n\t\t}\n\t}\n};\n\n// 计算并打印销售给定数量的某种书籍所得的费用\ndouble print_total(ostream &os, const Quote &item, size_t n)\n{\n\t// 根据传入item形参的对象类型调用Quote::net_price\n\t// 或者Bulk_quote::net_price\n\tdouble ret = item.net_price(n);\n\tos << \"ISBN: \" << item.isbn() // 调用Quote::isbn\n\t   << \" # sold: \" << n << \" total due: \" << ret << endl;\n\n\treturn ret;\n}\n\nint main()\n{\n\tQuote base(\"0-201-70353-X\", 10);\n\tBulk_quote derived(\"0-201-70353-X\", 10, 5, 0.5);\n\n\tprint_total(cout, base, 10); // 100\n\tprint_total(cout, derived, 10); // 5本享有折扣，75\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_17.md",
    "content": "练习15.17：尝试定义一个Disc_quote的对象，看看编译器给出的错误信息是什么？\n\n---\n\n提示：cannot declare variable ‘obj’ to be of abstract type ‘Disc_quote’\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_18.cpp",
    "content": "// 练习15.18：假设给定了第543页和第544页的类，同时已知每个对象的类型如注释所示，判断下面的哪些赋值语句是合法的。解释那些不合法的语句为什么不被允许：\n\n/*\nBase *p = &d1;\t// d1的类型是Pub_Derv，合法\np = &d2;\t\t// d2的类型是Priv_Derv，非法，非公有继承\np = &d3;\t\t// d3的类型是Prot_Derv，非法，非公有继承\np = &dd1;\t\t// dd1的类型是Derived_from_Public，合法\np = &dd2;\t\t// dd2的类型是Derived_from_Private，非法，非公有继承\np = &dd3;\t\t// dd3的类型是Derived_from_Protected，非法，非公有继承\n*/\n\n#include <iostream>\n#include \"./example_Access_Control_and_Inheritance/classes.hpp\"\n\n#define USE_VAR(x) ((void)&(x))\n\nint main()\n{\n\tPub_Derv d1;\n\tPriv_Derv d2;\n\tProt_Derv d3;\n\tDerived_from_Public dd1;\n\tDerived_from_Private dd2;\n\tDerived_from_Protected dd3;\n\n\tBase *p = &d1;\n\t//p = &d2;\n\t//p = &d3;\n\tp = &dd1;\n\t//p = &dd2;\n\t//p = &dd3;\n\t\n\n\n\t// ignore warning\n\tUSE_VAR(d2);\n\tUSE_VAR(d3);\n\tUSE_VAR(dd2);\n\tUSE_VAR(dd3);\n\tUSE_VAR(p);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_19.cpp",
    "content": "// 练习15.19：假设543页和544页的每个类都有如下形式的成员函数u：\n/*\nvoid memfcn(Base &b) { b = *this; }\n\n对于每个类，分别判断上面的函数是否合法。\n\nBase: 合法\n\nSneaky: 合法，但只会拷贝其基类部分\n\nPal: 非法，不存在类型转换规则\n\nPub_Derv: 合法\n\nPriv_Derv: 合法\n\nDerived_from_Public: 合法\n\nDerived_from_Private: 非法，Base::Base is inaccessible\n*/\n\n#include <iostream>\n\nclass Base {\n\t// 添加friend声明，其他成员与之前的版本一致\n\tfriend class Pal;\t\t\t\t// Pal在访问Base的派生类时不具有特殊性\npublic:\n\tvoid pub_mem() {}\t\t\t\t// public成员\n\nprotected:\n\tint prot_mem;\t\t\t\t\t// protected成员\n\nprivate:\n\tchar priv_mem;\t\t\t\t\t// private成员\n\n\tvoid memfcn(Base &b) { b = *this; }\n};\n\nclass Sneaky : public Base {\n\tfriend void clobber(Sneaky&);\t// 能访问Sneaky::prot_mem\n\tfriend void clobber(Base&);\t\t// 不能访问Base::prot_mem\n\tint j;\t\t\t\t\t\t\t// j默认是private\n\tvoid memfcn(Base &b) { b = *this; }\n};\n\nclass Pal {\npublic:\n\tint f(Base b) { return b.prot_mem; }\t// 正确：Pal是Base的友元\n\t//int f2(Sneaky s) { return s.j; }\t\t// 错误：Pal不是Sneaky的友元\n\n\t// 对基类的访问权限由基类本身控制，即使对于派生类的基类部分也是如此\n\tint f3(Sneaky s) { return s.prot_mem; }\t// 正确：Pal是Base的友元\n\t//void memfcn(Base &b) { b = *this; }\n};\n\nclass Pub_Derv : public Base {\n\t// 正确：派生类能访问protected成员\n\tint f() { return prot_mem; }\n\t\n\t// 错误：private成员对于派生类来说是不可访问的\n\t//char g() { return priv_mem; }\n\tvoid memfcn(Base &b) { b = *this; }\n};\n\nclass Priv_Derv : private Base {\n\t// private不影响派生类的访问权限\n\tint f1() const { return prot_mem; }\n\tvoid memfcn(Base &b) { b = *this; }\n};\n\nstruct Derived_from_Public : public Pub_Derv {\n\t// 正确：Base::prot_mem在Pub_Derv中仍然是protected的\n\tint use_base() { return prot_mem; }\n\tvoid memfcn(Base &b) { b = *this; }\n};\n\nstruct Derive_from_Private : public Priv_Derv {\n\t// 错误：Base::prot_mem在Priv_Derv中是private的\n\t//int use_base() { return prot_mem; }\n\n\t//void memfcn(Base &b) { b = *this; }\n};\n\n// 正确：clobber能访问Sneaky对象的private和protected成员\nvoid clobber(Sneaky &s) { s.j = s.prot_mem = 0; }\n\n// 错误：clobber不能访问Base的protected成员\n//void clobber(Base &b) { b.prot_mem = 0; }\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_20.md",
    "content": "练习15.20：编写代码检验你对前面两题的回答是否正确。\n\n---\n\n见这两个练习。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_21.cpp",
    "content": "// 练习15.21：从下面这些一般性抽象概念中任选一个（或者选一个你自己的），将其对应\n// 的一组类型组织成一个继承体系：\n/*\n(a) 图形文件格式（如git、tiff、jpeg、bmp）\n\n(b) 图形基元（如方格、圆、球、圆锥）\n\n(c) C++语言中的类型（如类、函数、成员函数）\n */\n\n#include <iostream>\n#include <cstdio>\n\nusing namespace std;\n\nenum SHAPE_TYPE\n{\n\tSHAPE_TYPE_INVALID = 0,\n\tSHAPE_TYPE_SQUARE = 1,\n\tSHAPE_TYPE_CIRCLE = 2,\n};\n\nclass Shape\n{\npublic:\n\tShape(int type) : m_type(type) {}\n\t\n\tvirtual void Print() const = 0;\n\nprotected:\n\tint m_type;\n};\n\nclass Square : public Shape\n{\npublic:\n\tSquare() : Shape(SHAPE_TYPE_SQUARE), m_height(1), m_width(1) {}\n\n\tvoid Print() const override { printf(\"[Square] height: %g, width: %g\\n\", m_height, m_width); }\n\nprivate:\n\tdouble m_height;\n\tdouble m_width;\n};\n\nclass Circle : public Shape\n{\npublic:\n\tCircle() : Shape(SHAPE_TYPE_CIRCLE), m_radius(1) {}\n\t\n\tvoid Print() const override { printf(\"[Circle] radius: %g\", m_radius); }\n\nprivate:\n\tdouble m_radius; \n};\n\nint main()\n{\n\tSquare square;\n\tsquare.Print();\n\n\tCircle circle;\n\tcircle.Print();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_22.md",
    "content": "练习15.22：对于你在上一题中选择的类，为其添加合适的虚函数及公有成员和受保护的成员。\n\n---\n\n见[练习15.21](./exercise_15_21.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_23.cpp",
    "content": "// 练习15.23：假设第550页的D1类需要覆盖它继承而来的fcn函数，你应该如何对其\n// 进行修改？如果你修改之后fcn匹配了Base中的定义，则该节的那些调用语句将如\n// 何解析？\n\n#include <iostream>\n\n#define USE_VAR(x) ((void)&(x))\n\nusing namespace std;\n\nclass Base {\npublic:\n\tvirtual int fcn() { cout << \"Base::fcn()\" << endl; return 0; }\n};\n\nclass D1 : public Base {\npublic:\n\t// 隐藏基类的fcn，这个fcn不是虚函数\n\t// D1继承了Base::fcn()的定义\n\tint fcn(int) { cout << \"D1::fcn(int)\" << endl; return 0; }\t\t\t// 形参列表与Base中的fcn不一致\n\tvirtual void f2() { cout << \"D1::f2()\" << endl; }\t\t\t\t\t// 是一个新的虚函数，在Base中不存在\n\n\tint fcn() override { cout << \"D1::fcn()\" << endl; return 0; };\t\t// 练习\n};\n\nclass D2 : public D1 {\npublic:\n\tint fcn(int) { cout << \"D2::fcn(int)\" << endl; return 0; }\t\t\t// 是一个非虚函数，隐藏了D1::fcn(int);\n\tint fcn() { cout << \"D2::fcn()\" << endl; return 0; }\t\t\t\t// 覆盖了Base的虚函数fcn\n\tvoid f2() { cout << \"D2::f2()\" << endl; }\t\t\t\t\t\t\t// 覆盖了D1的虚函数f2\n};\n\nint main()\n{\n\tBase bobj; D1 d1obj; D2 d2obj;\n\n\tBase *bp1 = &bobj, *bp2 = &d1obj, *bp3 = &d2obj;\n\tbp1->fcn();\t\t// 虚调用，将在运行时调用Base::fcn\n\tbp2->fcn();\t\t// 虚调用，将在运行时调用D1::fcn\n\tbp3->fcn();\t\t// 虚调用，将在运行时调用D2::fcn\n\n\tD1 *d1p = &d1obj; D2 *d2p = &d2obj;\n\t//bp2->f2();\t// 错误：Base没有名为f2的成员\n\td1p->f2();\t\t// 虚调用，将在运行时调用D1::f2()\t\n\td2p->f2();\t\t// 虚调用，将在运行时调用D2::f2()\n\n\tBase *p1 = &d2obj; D1 *p2 = &d2obj; D2 *p3 = &d2obj;\n\t//p1->fcn(42);\t// 错误：Base中没有接受一个int的fcn\n\tp2->fcn(42);\t// 静态绑定，调用D1::fcn(int)\n\tp3->fcn(42);\t// 静态绑定，调用D2::fcn(int)\n\n\t// ignore for warning\n\tUSE_VAR(p1);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_24.md",
    "content": "练习15.24：哪种类需要虚析构函数？虚析构函数必须执行什么样的操作？\n\n---\n\n基类通常需要虚析构函数。基类的虚析构函数内容应该为空，而子类负责实现自己的析构操作。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_25.md",
    "content": "练习15.25：我们为什么为Disc_quote定义一个默认构造函数？如果去除该构造函数的话会对Bulk_quote的行为产生什么影响？\n\n---\n\n\n由于Disc_quote定义了另一个版本的构造函数，合成版本的默认构造函数将不会被定义，这样，Bulk_quote就无法默认初始化，因为其基类没有默认构造函数。\n\n当基类的默认构造函数是被删除的，则派生类中的默认构造函数也是被删除的。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_26.md",
    "content": "练习15.26：定义Quote和Bulk_quote的拷贝控制成员，令其与合成的版本行为一致。为这些成员以及其他构造函数添加打印状态的语句，使得我们能够知道正在运行哪个程序。使用这些类编写程序，预测程序将创建和销毁哪些对象。重复实验，不断比较你的预测和实际输出结果是否相同，直到预测完全准确再结束。\n\n---\n\n见[案例](./example_derived_class_copy_control_members.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_27.cpp",
    "content": "// 练习15.27：重新定义你的Bulk_quote类，令其继承构造函数。\n\n#include <iostream>\n\nusing namespace std;\n\nclass Quote {\npublic:\n\tQuote() = default;\n\tQuote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) {}\n\n\tstd::string isbn() const { return bookNo; }\n\n\t// 返回给定数量的书籍的销售额\n\t// 派生类负责改写并使用不同的折扣计算算法\n\tvirtual double net_price(std::size_t n) const\n\t{ return n * price; }\n\n\tvirtual ~Quote() = default;\t// 对析构函数进行动态绑定\n\nprivate:\n\tstd::string bookNo;\n\nprotected:\n\tdouble price = 0.0;\t\t// 代表普通状态下不打折的价格\n};\n\n// 用于保存折扣值和购买量的类，派生类使用这些数据可以实现不同的价格策略\nclass Disc_quote : public Quote {\npublic:\n\tDisc_quote() = default;\n\tDisc_quote(const std::string& book, double p, std::size_t qty, double disc) :\n\t\tQuote(book, p), quantity(qty), discount(disc) {}\n\n\tdouble net_price(std::size_t) const = 0;\n\nprotected:\n\tstd::size_t quantity = 0;\t\t// 折扣适用的购买量\n\tdouble discount = 0.0;\t\t\t// 表示折扣的小数值\n};\n\nclass Bulk_quote : public Disc_quote\n{\npublic:\n\tBulk_quote() = default;\n\tusing Disc_quote::Disc_quote;\n\n\t// 覆盖基类的函数版本以实现基于大量购买的折扣政策\n\tdouble net_price(std::size_t cnt) const override\n\t{\n\t\tif (cnt >= quantity)\n\t\t\treturn cnt * (1 - discount) * price;\n\t\telse\n\t\t\treturn cnt * price;\n\t}\n};\n\nint main()\n{\n\tBulk_quote bq(\"0-201-70353-X\", 10, 5, 0.5);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_28.md",
    "content": "练习15.28：定义一个存放Quote对象的vector，将Bulk_quote对象传入其中，计算vector中所有元素总的net_price。\n\n---\n\n见[案例](./example_derived_class.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_29.md",
    "content": "练习15.29：再运行一次你的程序，这次传入Quote对象的shared_ptr。如果这次计算出的总额与之前的程序不一致，解释为什么；如果一致，也请说明原因。\n\n---\n\n见[案例](./example_derived_class.cpp)\n\n这次的计算结果不一致。因为上个版本虽然传入的是Bulk_quote，但实际上vector里面插入的仍然是Quote对象；而这个版本，vector中存放的是Quote的智能指针，它可以动态绑定到Bulk_quote类型的对象上面。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_30.md",
    "content": "练习15.30：编写你自己的Basket类，用它计算上一个练习中交易记录的总价格。\n\n---\n\n见[案例](./example_basket.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_31.md",
    "content": "练习15.31：已知s1、s2、s3和s4都是string，判断下面的表达式分别创建了什么样的对象：\n\n(a) Query(s1) | Query(s2) & ~Query(s3);\n\n(b) Query(s1) | (Query(s2) & ~Query(s3));\n\n(c) (Query(s1) & (Query(s2)) | (Query(s3) & Query(s4)));\n\n---\n\nQuery(s)创建了一个WordQuery对象。\n\n|操作创建了OrQuery对象，~操作创建了NotQuery对象，&操作创建了AndQuery对象。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_32.md",
    "content": "练习15.32：当一个Query类型的对象被拷贝、移动、赋值或销毁时，将分别发生什么？\n\n---\n\n这些操作本质上操作的是它的指针指针成员：\n\n拷贝：智能指针的引用增加1。\n\n移动：窃取指针指针的资源。\n\n赋值：右侧智能指针的引用加1，左侧原智能指针的引用减1，如果引用次数为0，销毁其管理的资源。\n\n销毁：智能指针的引用减1，如果为0，销毁其管理的资源。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_33.md",
    "content": "练习15.33：当一个Query_base类型的对象被拷贝、移动、赋值或销毁时，将分别发生什么？\n\n---\n\n由其子类的实现决定。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_34.md",
    "content": "练习15.34：针对图15.3（第565页）构建的表达式：\n\n(a) 列举出在处理表达式的过程中执行的所有构造函数。\n\n(b) 列举出cout<<q所调用的rep。\n\n(c) 列举出q.eval()所调用的eval。\n\n---\n\n**(a)**\n\n- Query::Query(const std::string &s)\n\n- WordQuery::WordQuery(const std::string &s)\n\n- AndQuery::AndQuery(const Query &left, const Query &right)\n\n- BinaryQuery::BinaryQuery(const Query &l, const Query &r, std::string s)\n\n- Query::Query(std::shared_ptr<Query_base> query)\n\n- Query::Query(const Query&)（Query合成的拷贝构造函数）\n\n- OrQuery::OrQuery(const Query &left, const Query &right)\n\n**(b)**\n\nQuery::rep -> Query_base::rep（动态绑定到派生类对象的rep上面）\n\n**(c)**\n\nQuery_base::eval（动态绑定到派生类对象的rep上面）\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_35.md",
    "content": "练习15.35：实现Query类和Query_base类，其中需要定义rep而无须定义eval。\n\n---\n\n见[案例](./example_TextQuery/Query.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_36.md",
    "content": "练习15.36：在构造函数和rep成员中添加打印语句，允许你的代码以检查你对本节第一个练习中(a)、(b)两小题的回答是否正确。\n\n---\n\n见[案例](./example_TextQuery/Query.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_37.md",
    "content": "练习15.37：如果在派生类中含有shared_ptr<Query_base>类型的成员而非Query类型的成员，则你的类需要做出怎样的改变？\n\n---\n\n见[案例代码](./example_TextQuery/Query_ex15_37.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_38.md",
    "content": "练习15.38：下面的声明合法吗？如果不合法，请解释原因；如果合法，请指出含义。\n\n```\nBinaryQuery a = Query(\"fiery\") & Query(\"bird\");\t// 不合法，BinaryQuery是一个抽象类，无法实例化\n\nAndQuery a = Query(\"fiery\") & Query(\"bird\");\t// 合法，生成一个AndQuery\n\nOrQuery a = Query(\"fiery\") & Query(\"bird\");\t\t// 不合法，类型不一致\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_39.md",
    "content": "练习15.39：实现Query类和Query_base类，求图15.3（第565页）中表达式的值并打印相关信息，验证你的程序是否正确。\n\n---\n\n见[案例](./example_TextQuery)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_40.md",
    "content": "练习15.40：在OrQuery的eval函数中，如果rhs成员返回的是空集将发生什么？如果lhs是空集呢？如果lhs和rhs都是空集又将发生什么？\n\n---\n\n程序正常运转，空集时，迭代器范围为空。\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_41.md",
    "content": "练习15.41：重新实现你的类，这次使用指向Query_base的内置指针而非shared_ptr。请注意，做出上述改动后你的类将不能再使用合成的拷贝控制成员。\n\n---\n\n见[案例](./example_TextQuery/Query_ex15_41.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch15_Object-Oriented_Programming/exercise_15_42.md",
    "content": "练习15.42：从下面的几种改进中选择一种，设计并实现它：\n\n(a) 按句子查询并打印单词，而不再是按行打印。\n\n(b) 引入一个历史系统，用户可以按编号查阅之前的某个查询，并可以在其中增加内容或者将其与其他查询组合。\n\n(c) 允许用户对结果做出限制，比如从给定范围的行中挑出匹配的进行显示。\n\n---\n\n选择C。见：\n\n- [print](./example_TextQuery/QueryResult.cpp)\n\n- [func4](./example_TextQuery/main.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"exercise_16_55.cpp\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\trm -f out1 out2\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_Screen/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_Screen/Screen.h",
    "content": "#ifndef SCREEN_H\n#define SCREEN_H\n\n#include <string>\n#include <iostream>\n\nusing pos = std::string::size_type;\n\ntemplate<pos heigth, pos width>\nclass Screen;\n\ntemplate<pos height, pos width>\nstd::istream& operator>>(std::istream&, Screen<height, width>&);\n\ntemplate<pos height, pos width>\nstd::ostream& operator<<(std::ostream&, const Screen<height, width>&);\n\ntemplate<pos height, pos width>\nclass Screen\n{\n\tfriend std::istream& operator>><height, width>(std::istream&, Screen<height, width>&);\n\npublic:\n\tScreen() = default;\t// 因为Screen有另一个构造函数，所以本函数是必须的\n\t// cursor被其类内初始值初始化为0\n\tScreen(char c) : contents(height * width, c) {}\n\n\tpos size() const;\n\t\n\tchar get() const { return contents[cursor]; }\t// 读取光标处的字符，隐式内联\n\tchar get(pos ht, pos wd) const;\n\tScreen &move(pos r, pos c);\t\t\t\t\t\t// 可以在之后设置为内联\n\n\tScreen &set(char c);\n\tScreen &set(pos row, pos col, char c);\n\n\tScreen &display(std::ostream &os) { do_display(os); return *this; }\n\tconst Screen &display(std::ostream &os) const { do_display(os); return *this;  }\n\nprivate:\n\t// 该函数负责显示Screen的内容\n\tvoid do_display(std::ostream &os) const { os << contents; }\n\n\tpos cursor = 0;\n\tstd::string contents;\n\n\tmutable size_t access_get_ctr;\t// 即使在一个const对象内也能被修改\n};\n\ntemplate<pos height, pos width>\ninline\npos Screen<height, width>::size() const\n{\n\treturn height * width;\n}\n\ntemplate<pos height, pos width>\ninline\nScreen<height, width>& Screen<height, width>::move(pos r, pos c) \n{\n\tpos row = r * width;\t// 计算行的位置\n\tcursor = row + c;\t\t// 在行内将光标移动到指定的列\n\n\t++access_get_ctr;\n\treturn *this;\n}\n\ntemplate<pos height, pos width>\ninline char Screen<height, width>::get(pos r, pos c) const\n{\n\tpos row = r * width;\t// 计算行的位置\n\treturn contents[row + c]; // 返回给定列的字符\n}\n\ntemplate<pos height, pos width>\ninline\nScreen<height, width>& Screen<height, width>::set(char c)\n{\n\tcontents[cursor] = c;\n\treturn *this;\n}\n\ntemplate<pos height, pos width>\ninline\nScreen<height, width>& Screen<height, width>::set(pos row, pos col, char c)\n{\n\tcontents[row * width + col] = c;\n\treturn *this;\n}\n\ntemplate<pos height, pos width>\nstd::istream& operator>>(std::istream &is, Screen<height, width> &screen)\n{\n\tstd::string str;\n\tif (getline(is, str)) {\n\t\tfor (auto c : str)\n\t\t{\n\t\t\tscreen.set(c);\n\t\t\tscreen.cursor = (screen.cursor + 1) % screen.contents.size();\n\t\t}\n\t}\n\treturn is;\n}\n\ntemplate<pos height, pos width>\nstd::ostream& operator<<(std::ostream &os, const Screen<height, width> &screen)\n{\n\tfor (size_t l = 0; l < height; ++l) {\n\t\tfor (size_t r = 0; r < width; ++r) {\n\t\t\tos << screen.get(l, r);\n\t\t}\n\t\tstd::cout << \"\\n\";\n\t}\n\treturn os;\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_Screen/main.cpp",
    "content": "// example: 窗口类的设计与实现(练习16.14, 16.15, p592)\n\n#include <iostream>\n#include \"Screen.h\"\n\nusing std::cout;\nusing std::cin;\nusing std::endl;\n\nvoid func1()\n{\n\tScreen<5, 3> screen('A');\n\n\tscreen.set('#').display(cout);\n\n\tcout << endl;\n}\n\nvoid func2()\n{\n\tScreen<3, 5> screen('0');\n\tcout << screen;\n\n\tcout << \"enter some words: \";\n\n\tcin >> screen;\n\tcout << screen;\n}\n\nint main()\n{\n\t//func1();\n\tfunc2();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_SmartPointer/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_SmartPointer/SharedPtr.h",
    "content": "#ifndef SHARED_PTR_H\n#define SHARED_PTR_H\n\n#include <functional>\n\ntemplate<typename T>\nclass SharedPtr\n{\n\tusing DelFunc = std::function<void(T*)>;\n\npublic:\n\tSharedPtr(T *p = nullptr, DelFunc del = nullptr) : m_use_count(new size_t(1)), m_p(p), m_del(del) {}\n\t~SharedPtr();\n\tSharedPtr(const SharedPtr&);\n\tSharedPtr& operator=(const SharedPtr&);\n\n\tT& operator*() const;\n\tT* operator->() const\n\t{ return &this->operator*(); }\n\nprivate:\n\tsize_t *m_use_count = nullptr;\n\tT *m_p = nullptr;\n\n\tDelFunc m_del;\n};\n\ntemplate<typename T> inline\nSharedPtr<T>::~SharedPtr()\n{\n\tif (m_use_count && --*m_use_count == 0)\n\t{\n\t\tif (m_del)\n\t\t\tm_del(m_p);\n\t\telse\n\t\t\tdelete m_p;\n\t\n\t\tdelete m_use_count;\n\t}\n}\n\ntemplate<typename T> inline\nSharedPtr<T>::SharedPtr(const SharedPtr &s) : m_use_count(s.m_use_count), m_p(s.m_p), m_del(s.m_del)\n{\n\t++*m_use_count;\n}\n\ntemplate<typename T> inline\nSharedPtr<T>& SharedPtr<T>::operator=(const SharedPtr &s)\n{\n\tif (this != &s)\n\t{\n\t\tif (m_use_count && --*m_use_count == 0)\n\t\t{\n\t\t\tif (m_del)\n\t\t\t\tm_del(m_p);\n\t\t\telse\n\t\t\t\tdelete m_p;\n\n\t\t\tdelete m_use_count;\n\t\t}\n\n\t\tm_use_count = s.m_use_count;\n\t\t++*m_use_count;\n\t\tm_p = s.m_p;\n\t\tm_del = s.m_del;\n\t}\n\n\treturn *this;\n}\n\ntemplate<typename T> inline\nT& SharedPtr<T>::operator*() const\n{\n\treturn *m_p;\n}\n\n#endif // SHARED_PTR_H\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_SmartPointer/UniquePtr.h",
    "content": "#ifndef UNIQUE_PTR_H\n#define UNIQUE_PTR_H\n\nclass Delete\n{\npublic:\n\ttemplate<typename T> void operator()(T *p) const { delete p; }\n};\n\ntemplate<typename T, typename D = Delete>\nclass UniquePtr\n{\npublic:\n\tUniquePtr(T *p = nullptr, const D &d = D()) : m_p(p), m_del(d) {}\n\t~UniquePtr()\n\t{\n\t\tif (m_p) {\n\t\t\tm_del(m_p);\n\t\t}\n\t}\n\tUniquePtr(const UniquePtr&) = delete;\n\tUniquePtr& operator=(const UniquePtr&) = delete;\n\n\tT& operator*() const { return *m_p; }\n\tT* operator->() const { return &this->operator*(); }\n\nprivate:\n\tT *m_p = nullptr;\n\tD m_del;\n};\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_SmartPointer/main.cpp",
    "content": "// example: 自己实现智能指针（p600, 练习16.28）\n\n#include <iostream>\n#include \"SharedPtr.h\"\n#include \"UniquePtr.h\"\n\nusing namespace std;\n\nclass Boo\n{\npublic:\n\t~Boo() { cout << \"~Boo()\" << endl; }\n\tvoid Print() const { cout << \"I am Boo\" << endl; }\n};\n\nvoid Test_SharedPtr()\n{\n\t{\n\t\tSharedPtr<int> sp;\n\t}\n\n\t{\n\t\tSharedPtr<int> sp(new int(5));\n\t\tcout << *sp << endl;\n\t}\n\n\t{\n\t\tSharedPtr<int> sp(new int(5), [](int *p) { \n\t\t cout << \"Delete int\" << endl; delete p;});\n\t}\n\n\t{\n\t\tSharedPtr<int> sp(new int(5));\n\t\tSharedPtr<int> sp2 = sp;\n\t\t*sp2 = 10;\n\t\tcout << *sp << endl;\n\t}\n\n\t{\n\t\tSharedPtr<int> sp(new int(5));\n\n\t\tsp = sp; // 检测自赋值\n\n\t\tSharedPtr<int> sp2;\n\t\tsp2 = sp;\n\t\t*sp2 = 10;\n\t\tcout << *sp << endl;\n\t}\n\n\t{\n\t\tSharedPtr<Boo> sp(new Boo());\n\t\tSharedPtr<Boo> sp2 = sp;\n\t\tsp2->Print();\n\t\t// delete only once\n\t}\n}\n\nvoid Test_UniquePtr()\n{\n\t{\n\t\tUniquePtr<int> sp;\n\t}\n\n\t{\n\t\tUniquePtr<int> sp(new int(5));\n\t\tcout << *sp << endl;\n\t}\n\n\t{\n\t\tUniquePtr<int, std::function<void(int*)>> sp(new int(5), [](int *p) {\n\t\t\tcout << \"Delete int\" << endl;\n\t\t\tdelete p;\n\t\t});\n\t}\n\n\t{\n\t\t// can not copy\n\t\tUniquePtr<int> sp(new int(5));\n\t\t//UniquePtr<int> sp2 = sp;\n\t}\n\n\t{\n\t\tUniquePtr<Boo> sp(new Boo());\n\t\tsp->Print();\n\t}\n}\n\nint main()\n{\n\t//Test_SharedPtr();\n\tTest_UniquePtr();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_Vec/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_Vec/Vec.h",
    "content": "#ifndef VEC_H\n#define VEC_H\n\n#include <memory>\n#include <initializer_list>\n#include <algorithm>\n\ntemplate<typename elemType> class Vec;\n\ntemplate<typename elemType>\nbool operator==(const Vec<elemType>&, const Vec<elemType>&);\n\ntemplate<typename elemType>\nbool operator!=(const Vec<elemType>&, const Vec<elemType>&);\n\ntemplate<typename elemType>\nbool operator<(const Vec<elemType>&, const Vec<elemType>&);\n\ntemplate<typename elemType>\nclass Vec\n{\n\tfriend bool operator==<elemType>(const Vec<elemType> &lhs, const Vec<elemType> &rhs);\n\tfriend bool operator!=<elemType>(const Vec<elemType> &lhs, const Vec<elemType> &rhs);\n\tfriend bool operator< <elemType>(const Vec<elemType> &lhs, const Vec<elemType> &rhs);\n\npublic:\n\tinline Vec() :\t// allocator成员进行默认初始化\n\t\telements(nullptr), first_free(nullptr), cap(nullptr) {}\n\tinline Vec(const std::initializer_list<elemType>&);\n\t\n\tinline Vec(const Vec&);\t\t\t\t\t\t// 拷贝构造函数\n\tinline Vec(Vec&&) noexcept;\t\t\t\t\t// 移动构造函数\n\tinline Vec& operator=(const Vec&);\t\t\t// 拷贝赋值运算符\n\tinline Vec& operator=(Vec&&) noexcept;\t\t// 移动赋值运算符\n\tinline ~Vec();\t\t\t\t\t\t\t\t\t// 析构函数\n\n\tinline Vec& operator=(std::initializer_list<elemType> il);\n\tinline elemType& operator[](std::size_t n) { return elements[n]; }\n\tinline const elemType& operator[](std::size_t n) const { return elements[n]; }\n\n\tinline void push_back(const elemType&);\t\t\t// 拷贝元素\n\ttemplate<typename... Args> inline void emplace_back(Args&&... args);\n\tinline size_t size() const { return first_free - elements; }\n\tinline size_t capacity() const { return cap - elements; }\n\tinline void reserve(size_t n);\n\tinline void resize(size_t n);\n\tinline elemType *begin() const { return elements; }\n\tinline elemType *end() const { return first_free; }\n\nprivate:\n\tstatic std::allocator<elemType> alloc;\t// 分配元素\n\t// 被添加元素的函数所使用\n\tinline void chk_n_alloc()\n\t\t{ if (size() == capacity()) reallocate(); }\n\t// 工具函数，被拷贝构造函数、赋值运算符和析构函数所使用\n\tinline std::pair<elemType*, elemType*> alloc_n_copy\n\t\t(const elemType*, const elemType*);\n\tinline void free();\t\t\t\t\t\t\t\t// 销毁元素并释放内存\n\tinline void reallocate();\t\t\t\t\t\t\t// 获得更多内存并拷贝已有元素\n\t\n\telemType *elements;\t\t\t\t\t\t// 指向数组首元素的指针\n\telemType *first_free;\t\t\t\t\t// 指向数组第一个空闲元素的指针\n\telemType *cap;\t\t\t\t\t\t\t// 指向数组尾后位置的指针\n};\n\ntemplate<typename elemType>\nstd::allocator<elemType> Vec<elemType>::alloc;\n\ntemplate<typename elemType>\nVec<elemType>::Vec(const std::initializer_list<elemType> &lst)\n{\n\tauto newdata = alloc_n_copy(lst.begin(), lst.end());\n\telements = newdata.first;\n\tfirst_free = cap = newdata.second;\n}\n\ntemplate<typename elemType>\nVec<elemType>::Vec(const Vec<elemType> &s)\n{\n\t// 调用alloc_n_copy分配空间以容纳与s中一样多的元素\n\tauto newdata = alloc_n_copy(s.begin(), s.end());\n\telements = newdata.first;\n\tfirst_free = cap = newdata.second;\n}\n\ntemplate<typename elemType>\nVec<elemType>::Vec(Vec<elemType> &&s) noexcept\n\t// 成员初始化器接管s中的资源\n\t: elements(s.elements), first_free(s.first_free), cap(s.cap)\n{\n\t// 令s进入这样的状态：对其进行析构函数是安全的\n\ts.elements = s.first_free = s.cap = nullptr;\n}\n\ntemplate<typename elemType>\nVec<elemType>& Vec<elemType>::operator=(Vec<elemType> &&rhs) noexcept\n{\n\t// 直接检测自赋值\n\tif (this != &rhs) {\n\t\tfree();\t// 释放已有元素\n\t\telements = rhs.elements; // 从rhs接管资源\n\t\tfirst_free = rhs.first_free;\n\t\tcap = rhs.cap;\n\n\t\t// 将rhs置于可析构状态\n\t\trhs.elements = rhs.first_free = rhs.cap = nullptr;\n\t}\n\treturn *this;\n}\n\ntemplate<typename elemType>\nbool operator==(const Vec<elemType> &lhs, const Vec<elemType> &rhs)\n{\n\tauto lhs_beg = lhs.elements;\n\tauto rhs_beg = rhs.elements;\n\n\twhile (lhs_beg != lhs.first_free && rhs_beg != rhs.first_free) {\n\t\tif (*lhs_beg != *rhs_beg)\n\t\t\treturn false;\n\n\t\t++lhs_beg;\n\t\t++rhs_beg;\n\t}\n\n\tif (lhs_beg == lhs.first_free && rhs_beg == rhs.first_free)\n\t\treturn true;\n\n\treturn false;\n}\n\ntemplate<typename elemType>\nbool operator!=(const Vec<elemType> &lhs, const Vec<elemType> &rhs)\n{\n\treturn !(lhs == rhs);\n}\n\ntemplate<typename elemType>\nbool operator<(const Vec<elemType> &lhs, const Vec<elemType> &rhs)\n{\n\tauto lhs_beg = lhs.elements;\n\tauto rhs_beg = rhs.elements;\n\n\twhile (lhs_beg != lhs.first_free && rhs_beg != rhs.first_free) {\n\t\tif (*lhs_beg > *rhs_beg)\n\t\t\treturn false;\n\t\telse if (*lhs_beg < *rhs_beg)\n\t\t\treturn true;\n\n\t\t++lhs_beg;\n\t\t++rhs_beg;\n\t}\n\n\tif (lhs_beg == lhs.first_free && rhs_beg <= rhs.first_free)\n\t\treturn true;\n\n\treturn false;\n}\n\ntemplate<typename elemType>\nVec<elemType>::~Vec()\n{\n\tfree();\n}\n\ntemplate<typename elemType>\nVec<elemType> &Vec<elemType>::operator=(const Vec<elemType> &rhs)\n{\n\t// 调用alloc_n_copy分配内存，大小与rhs中元素占用空间一样多\n\tauto data = alloc_n_copy(rhs.begin(), rhs.end());\n\tfree();\n\telements = data.first;\n\tfirst_free = cap = data.second;\n\treturn *this;\n}\n\ntemplate<typename elemType>\nVec<elemType> &Vec<elemType>::operator=(std::initializer_list<elemType> il)\n{\n\t// alloc_n_copy分配内存空间并从给定范围内拷贝元素\n\tauto data = alloc_n_copy(il.begin(), il.end());\n\tfree();\t\t\t\t\t// 销毁对象中的元素并释放内存空间\n\telements = data.first;\t// 更新数据成员使其指向新空间\n\tfirst_free = cap = data.second;\n\treturn *this;\n}\n\ntemplate<typename elemType>\nvoid Vec<elemType>::push_back(const elemType &s)\n{\n\tchk_n_alloc(); // 确保有空间容纳新元素\n\t// 在first_free指向的元素中构造s的副本\n\talloc.construct(first_free++, s);\n}\n\ntemplate<typename elemType>\ntemplate<typename... Args>\nvoid Vec<elemType>::emplace_back(Args&&... args)\n{\n\tchk_n_alloc();\n\talloc.construct(first_free++, std::forward<Args>(args)...);\n}\n\ntemplate<typename elemType>\nstd::pair<elemType*, elemType*>\nVec<elemType>::alloc_n_copy(const elemType *b, const elemType *e)\n{\n\t// 分配空间保存给定范围中的元素\n\tauto data = alloc.allocate(e - b);\n\n\t// 初始化并返回一个pair，该pair由data和uninitialized_copy的返回值构成\n\treturn {data, uninitialized_copy(b, e, data)};\n}\n\ntemplate<typename elemType>\nvoid Vec<elemType>::free()\n{\n\t// 不能传递给deallocate一个空指针，如果elements为0，函数什么也不做\n\tif (elements) {\n\t\t// 逆序销毁旧元素\n\t\t\n\t\t//for (auto p = first_free; p != elements;) alloc.destroy(--p);\n\n\t\tstd::for_each(elements, first_free, [this](elemType &s){ alloc.destroy(&s); } );\n\n\t\talloc.deallocate(elements, cap - elements);\n\t}\n}\n\ntemplate<typename elemType>\nvoid Vec<elemType>::reallocate()\n{\n\t// 我们将分配当前大小两倍的内存空间\n\tauto newcapacity = size() ? 2 * size() : 1;\n\t// 分配新内存\n\tauto newdata = alloc.allocate(newcapacity);\n\t// 将数据从旧内存移动到新内存\n\tauto dest = newdata;  // 指向新数组中下一个空闲位置\n\tauto elem = elements; // 指向旧数组中下一个元素\n\tfor (size_t i = 0; i != size(); ++i)\n\t\talloc.construct(dest++, std::move(*elem++));\n\tfree(); // 一旦我们移动完元素就释放旧内存空间\n\t// 更新我们的数据结构，执行新元素\n\telements = newdata;\n\tfirst_free = dest;\n\tcap = elements + newcapacity;\n}\n\ntemplate<typename elemType>\nvoid Vec<elemType>::reserve(size_t n)\n{\n\tif (n <= capacity()) return;\n\n\tauto newdata = alloc.allocate(n);\n\n\t// copy old data\n\tauto dest = newdata;\n\tauto elem = elements;\n\tfor (size_t i = 0; i != size(); ++i)\n\t\talloc.construct(dest++, std::move(*elem++));\n\tfree();\n\n\telements = newdata;\n\tfirst_free = dest;\n\tcap = elements + n;\n}\n\ntemplate<typename elemType>\nvoid Vec<elemType>::resize(size_t n)\n{\n\tif (n == size()) return;\n\n\tif (n < size())\n\t{\n\t\tdo {\n\t\t\talloc.destroy(--first_free);\n\t\t} while (first_free != elements + n);\n\n\t\treturn;\n\t}\n\n\tif (n > size())\n\t{\n\t\treallocate();\n\n\t\tdo {\n\t\t\tchk_n_alloc();\n\n\t\t\talloc.construct(first_free++);\n\t\t} while (first_free != elements + n);\n\n\t\treturn;\n\t}\n}\n\n#endif // VEC_H\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_Vec/main.cpp",
    "content": "// example: 动态内存管理类（练习16.16，p592）\n\n#include <iostream>\n#include <string>\n\n#include \"Vec.h\"\n\nusing namespace std;\n\nvoid func1()\n{\n\tVec<string> v;\n\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tcout << \"reserve 4 ...\" << endl;\n\tv.reserve(4);\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tcout << \"push_back 2 string ...\" << endl;\n\tv.push_back(\"a\");\n\tv.push_back(\"b\");\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tcout << \"print ...\" << endl;\n\tfor (auto p = v.begin(); p != v.end(); ++p)\n\t\tcout << *p << \" \";\n\tcout << endl;\n\n\tcout << \"resize to 0 ...\" << endl;\n\tv.resize(0);\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n\n\tcout << \"resize to 100 ...\" << endl;\n\tv.resize(100);\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n}\n\nvoid func2()\n{\n\tVec<string> v{\"a\", \"b\", \"Hello\", \"World\"};\n\n\tfor (auto p = v.begin(); p != v.end(); ++p)\n\t\tcout << *p << \" \";\n\tcout << endl;\n\n\tcout << \"v.size(): \" << v.size() << endl;\n\tcout << \"v.capacity(): \" << v.capacity() << endl;\n}\n\nvoid func3()\n{\n\tVec<string> v1{\"a\", \"b\", \"Hi\"};\n\tVec<string> v2{\"a\", \"n\", \"Hi\"};\n\tVec<string> v3{\"a\", \"n\", \"Hi\"};\n\tVec<string> v4{\"a\", \"n\", \"Hi\", \"Hello\"};\n\n\tif (v1 != v2)\n\t\tcout << \"v1 != v2\" << endl;\n\n\tif (v2 == v3)\n\t\tcout << \"v2 == v3\" << endl;\n\n\tif (v3 < v4)\n\t\tcout << \"v3 < v4\" << endl;\n}\n\n// test emplace_back\nvoid func4()\n{\n\tVec<string> v;\n\tv.emplace_back(10, 'c');\n\tv.emplace_back(\"Hi\");\n\n\tfor (auto p = v.begin(); p != v.end(); ++p)\n\t\tcout << *p << \" \";\n\tcout << endl;\n}\n\nint main()\n{\n\tfunc1();\n\t//func2();\n\t//func3();\n\t//func4();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_customer_MakeShared/MakeShared.h",
    "content": "#ifndef MAKE_SHARED_H\n#define MAKE_SHARED_H\n\n#include <memory>\n\ntemplate <typename T, typename... Args> inline\nstd::shared_ptr<T> MakeShared(Args&&... args)\n{\n\tauto p = new T(std::forward<Args>(args)...);\n\treturn std::shared_ptr<T>(p);\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_customer_MakeShared/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_customer_MakeShared/main.cpp",
    "content": "// example: 自定义的make_shared，练习16.61\n\n#include <iostream>\n#include \"MakeShared.h\"\n\nclass Foo\n{\npublic:\n\tFoo() { std::cout << \"Foo()\" << std::endl; }\n\tFoo(int, int) { std::cout << \"Foo(int, int)\" << std::endl; }\n\t~Foo() { std::cout << \"~Foo()\" << std::endl; }\n};\n\nint main()\n{\n\t//auto p = MakeShared<Foo>();\n\tauto p2 = MakeShared<Foo>(1, 2);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_explicit_instantiation/Application.cpp",
    "content": "#include \"Application.h\"\n#include <iostream>\n\nvoid func()\n{\n\tint ret = compare(5, 10);\t\t\t// 已经实例化声明, 因此不会被实例化, 必须在程序的其他地方显式实例化\n\tstd::cout << ret << std::endl;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_explicit_instantiation/Application.h",
    "content": "#ifndef APPLICATION_H\n#define APPLICATION_H\n\n#include \"TemplateBuild.h\"\nextern template int compare(const int&, const int&);            // 模板实例化声明\n\nextern void func();\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_explicit_instantiation/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.c.o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_explicit_instantiation/TemplateBuild.cpp",
    "content": "#include \"TemplateBuild.h\"\n\ntemplate int compare(const int&, const int&); // 模板实例化定义, 显式地实例化\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_explicit_instantiation/TemplateBuild.h",
    "content": "#ifndef TEMPLATE_BUILD\n#define TEMPLATE_BUILD\n\ntemplate<typename T>\nint compare(const T &left, const T &right)\n{\n\tif (left < right) return -1;\n\telse if (left > right) return 1;\n\telse return 0;\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_explicit_instantiation/main.cpp",
    "content": "// example: 控制实例化（597）\n\n/*\n * make后，使用nm命令观察compare符号信息\n * nm Application.o -> U，表明compare没有定义过，需要链接\n * nm TemplateBuild.o -> W，弱链接符号，具有一个默认的定义\n */\n\n#include <iostream>\n#include \"Application.h\"\n\nusing namespace std;\n\nint main()\n{\n\tfunc();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_function_template.cpp",
    "content": "// example: 函数模板（p578）\n\n#include <cstring>\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\ntemplate<typename T>\nint compare(const T &v1, const T &v2)\n{\n\tif (v1 < v2) return -1;\n\tif (v2 < v1) return 1;\n\treturn 0;\n}\n\n// 非类型模板参数\ntemplate<unsigned N>\nint compare(const char (&p1)[N], const char (&p2)[N])\n{\n\treturn strcmp(p1, p2);\n}\n\n// 默认模板实参（p594）\ntemplate<typename T, typename F = std::less<T>>\nint compare2(const T &v1, const T &v2, F f = F()) // 重载会造成二义性调用\n{\n\tif (f(v1, v2)) return -1;\n\tif (f(v2, v1)) return 1;\n\n\treturn 0;\n}\n\n// 模板特例化（p625）\ntemplate <>\nint compare(const char* const &p1, const char* const &p2)\n{\n\tint ret = strcmp(p1, p2);\n\tif (ret < 0) return -1;\n\tif (ret == 0) return 0;\n\t\n\treturn 1;\n}\n\nint main()\n{\n\tcout << compare(1, 0) << endl; // T为int\n\n\tvector<int> vec1{1, 2, 3}, vec2{4, 5, 6};\n\tcout << compare(vec1, vec2) << endl; // T为vector<int>\n\n\tcout << compare(\"aaa\", \"aaa\") << endl;\n\n\tcout << compare2(5, 6) << endl;\n\n\t// 练习16.39，使用显式模板实参，注意这里用的是compare2\n\tcout << compare2<string>(\"hello\", \"z\") << endl;\n\n\t{\n\t\tconst char* p1 = \"hello\";\n\t\tconst char* p2 = \"z\";\n\t\tcout << compare(p1, p2) << endl;\n\t}\n\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_member_template/DebugDelete.h",
    "content": "#include <iostream>\n#include <memory>\n\nusing namespace std;\n\n// 函数对象类，对给定指针执行delete\nclass DebugDelete {\npublic:\n\tDebugDelete(std::ostream &s = std::cerr) : os(s) {}\n\t// 与任何函数模板相同，T的类型由编译器推断\n\ttemplate<typename T> void operator()(T *p) const\n\t{ os << \"deleting unique_ptr\" << std::endl; delete p; }\nprivate:\n\tstd::ostream &os;\n};\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_member_template/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_member_template/main.cpp",
    "content": "// example: 成员模板（p595）\n\n#include <iostream>\n#include \"DebugDelete.h\"\n\nusing namespace std;\n\nvoid func1()\n{\n\tdouble *p = new double;\n\tDebugDelete d; // 可像delete表达式一样使用的对象\n\td(p); // 调用DebugDelete::operator()(double*)，释放p\n}\n\nvoid func2()\n{\n\t// 销毁p指向的对象\n\t// 实例化DebugDelete::operator()<int>(int*)\n\tunique_ptr<int, DebugDelete> p(new int, DebugDelete());\n}\n\nint main()\n{\n\t//func1();\n\tfunc2();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_overload_and_template.cpp",
    "content": "// example: 重载与模板(p615)\n\n#include <iostream>\n#include <sstream>\n\nusing namespace std;\n\n// 打印任何我们不能处理的类型\ntemplate <typename T> string debug_rep(const T &t)\n{\n\tostringstream ret;\n\tret << t;\t\t\t// 使用T的输出运算符打印t的一个表示形式\n\treturn ret.str();\t// 返回ret绑定的string的一个副本\n}\n\n// 打印指针的值，后跟指针指向的对象\n// 注意：此函数不能用于char*\ntemplate <typename T> string debug_rep(T *p)\n{\n\tostringstream ret;\n\tret << \"pointer: \" << p; // 打印指针本身的值\n\tif (p)\n\t\tret << \" \" << debug_rep(*p);\t// 打印p指向的值\n\telse\n\t\tret << \" null pointer\";\t\t\t// 或指出p为空\n\n\treturn ret.str();\t// 返回ret绑定的string的一个副本\n}\n\n// 非模板\nstring debug_rep(const string &s)\n{\n\treturn '\"' + s + '\"';\n}\n\n// 将字符指针转换为string，并调用string版本的debug_rep\nstring debug_rep(char *p)\n{\n\treturn debug_rep(string(p));\n}\n\nstring debug_rep(const char *p)\n{\n\treturn debug_rep(string(p));\n}\n\nvoid func1()\n{\n\tint i = 42;\n\tcout << debug_rep(i) << endl;\n}\n\nvoid func2()\n{\n\tint i = 42;\n\tcout << debug_rep(&i) << endl;\n}\n\nvoid func3()\n{\n\tstring s = \"hi\";\n\tcout << debug_rep(s) << endl;\n}\n\nvoid func4()\n{\n\tcout << debug_rep(\"Wow\") << endl;\n}\n\nint main()\n{\n\t//func1();\n\t//func2();\n\t//func3();\n\tfunc4();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_template_class/Blob.h",
    "content": "#ifndef BLOB_H\n#define BLOB_H\n\n#include <vector>\n#include <memory>\n#include <stdexcept>\n#include \"BlobPtr.h\"\n\n// 前置声明，在Blob中声明友元所需要的\ntemplate<typename> class BlobPtr;\ntemplate<typename> class Blob;\ntemplate<typename T>\nbool operator==(const Blob<T>&, const Blob<T>&);\n\ntemplate<typename T>\nclass Blob {\n\t// 每个Blob实例将访问权限授予用相同类型实例化的BlobPtr和相等运算符\n\tfriend class BlobPtr<T>;\n\tfriend bool operator==<T>(const Blob<T>&, const Blob<T>&);\n\npublic:\n\ttypedef T value_type;\n\ttypedef typename std::vector<T>::size_type size_type;\n\n\t// 构造函数\n\tBlob() : data(new std::vector<T>()) {}\n\tBlob(std::initializer_list<T> il) : data(new std::vector<T>(il)) {}\n\ttemplate<typename It> Blob(It beg, It end);\n\n\t// Blob中的元素数目\n\tsize_type size() const { return data->size(); }\n\tbool empty() const { return data->empty(); }\n\n\t// 添加和删除元素\n\tvoid push_back(const T &t) { data->push_back(t); }\n\tvoid pop_back();\n\n\t// 移动版本\n\tvoid push_back(T &&t) { data->push_back(std::move(t)); }\n\n\t// 元素访问\n\tconst T& back() const;\t\n\tconst T& front() const;\t\n\tconst T& operator[](size_type i) const;\n\tT& back();\n\tT& front();\n\tT& operator[](size_type i);\n\n\tBlobPtr<T> begin() { return BlobPtr<T>(*this); }\n\tBlobPtr<T> end() { return BlobPtr<T>(*this, data->size()); }\n\nprivate:\n\tstd::shared_ptr<std::vector<T>> data;\n\n\t// 若data[i]无效，则抛出异常\n\tvoid check(size_type i, const std::string &msg) const;\n};\n\ntemplate<typename T>\ntemplate<typename It>\ninline Blob<T>::Blob(It beg, It end) try : data(std::make_shared<std::vector<T>>(beg, end))\n{\n} catch (const std::bad_alloc &e) { \n\t// 构造函数异常处理，见p689\n\t// 简单处理，直接异常退出\n\tstd::cout << \"bad_alloc: \" << e.what() << std::endl;\n\tabort();\n}\n\ntemplate<typename T>\ninline void Blob<T>::check(size_type i, const std::string &msg) const\n{\n\tif (i >= data->size())\n\t\tthrow std::out_of_range(msg);\n}\n\ntemplate<typename T>\ninline T& Blob<T>::back()\n{\n\tcheck(0, \"back on empty Blob\");\n\treturn data->back();\n}\n\ntemplate<typename T>\ninline T& Blob<T>::front()\n{\n\tcheck(0, \"front on empty Blob\");\n\treturn data->front();\n}\n\ntemplate<typename T>\ninline const T& Blob<T>::back() const\n{\n\tcheck(0, \"back on empty Blob\");\n\treturn data->back();\n}\n\ntemplate<typename T>\ninline const T& Blob<T>::front() const\n{\n\tcheck(0, \"front on empty Blob\");\n\treturn data->front();\n}\n\ntemplate<typename T>\ninline T& Blob<T>::operator[](size_type i)\n{\n\t// 如果i太大，check会抛出异常，阻止访问一个不存在的元素\n\tcheck(i, \"subscript out of range\");\n\treturn (*data)[i];\n}\n\ntemplate<typename T>\ninline const T& Blob<T>::operator[](size_type i) const\n{\n\t// 如果i太大，check会抛出异常，阻止访问一个不存在的元素\n\tcheck(i, \"subscript out of range\");\n\treturn (*data)[i];\n}\n\ntemplate<typename T>\ninline void Blob<T>::pop_back()\n{\n\tcheck(0, \"pop_back on empty Blob\");\n\tdata->pop_back();\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_template_class/BlobPtr.h",
    "content": "#ifndef BLOB_PTR_H\n#define BLOB_PTR_H\n\ntemplate<typename> class Blob;\n\n// 若试图访问一个不存在的元素，BlobPtr抛出一个异常\ntemplate<typename T> class BlobPtr {\npublic:\n\tBlobPtr() : curr(0) {}\n\tBlobPtr(Blob<T> &a, size_t sz = 0) :\n\t\twptr(a.data), curr(sz) {}\n\n\tT& operator*() const\n\t{\n\t\tauto p = check(curr, \"dereference past end\");\n\t\treturn (*p)[curr]; // (*p)为指向本对象的vector\n\t}\n\n\tBlobPtr& operator++();\n\tBlobPtr& operator--();\n\nprivate:\n\t// 若检查成功，check返回一个指向vector的shared_ptr\n\tstd::shared_ptr<std::vector<T>> check(std::size_t, const std::string&) const;\n\n\t// 保存一个weak_ptr, 表示底层vector可能被销毁\n\tstd::weak_ptr<std::vector<T>> wptr;\n\tstd::size_t curr;\t// 数组中的当前位置\n};\n\ntemplate<typename T>\nstd::shared_ptr<std::vector<T>> BlobPtr<T>::check(std::size_t i, const std::string &msg) const\n{\n\tauto ret = wptr.lock();\n\tif (!ret)\n\t{\n\t\tthrow std::runtime_error(\"unbound BlobPtr\");\n\t}\n\n\tif (i >= ret->size())\n\t{\n\t\tthrow std::out_of_range(msg);\n\t}\n\n\treturn ret;\n}\n\n#endif\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_template_class/Makefile",
    "content": "EXEC=main\nCXXFLAGS=-g -Wall -std=c++11\n\nSRCS=$(wildcard *.cpp)\nOBJS=$(SRCS:.cpp=.o)\n\nall:$(OBJS)\n\tg++ -o $(EXEC) $(OBJS)\n\t@echo build succ\n.cpp .o:\n\tg++ -o $@ -c $<\nclean:\n\t@rm -f $(OBJS) $(EXEC)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_template_class/main.cpp",
    "content": "// example: 类模板（p583）\n\n#include <string>\n#include <iostream>\n#include \"Blob.h\"\n\nusing namespace std;\n\nvoid func1()\n{\n\tBlob<int> ia;\n\tBlob<int> ia2 = {0, 1, 2, 3, 4};\n}\n\nvoid func2()\n{\n\tBlob<string> str_blob = {\"Hello\", \"World\"};\n\tauto beg_ptr = str_blob.begin();\n\n\t*beg_ptr = \"Hi\";\n\n\tfor (size_t i = 0; i < str_blob.size(); ++i)\n\t\tcout << str_blob[i] << \" \";\n\tcout << endl;\n}\n\n// 实例化与成员模板\nvoid func3()\n{\n\tint ia[] = {0, 1, 2, 3};\n\tBlob<int> a1(begin(ia), end(ia));\t\n\tfor (size_t i = 0; i < a1.size(); ++i)\n\t\tcout << a1[i] << \" \";\n\tcout << endl;\n}\n\nint main()\n{\n\t//func2();\n\tfunc3();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_variadic_template.cpp",
    "content": "// example: 可变参数模板（p618）\n\n#include <string>\n#include <iostream>\n\nusing namespace std;\n\n// Args是一个模板参数包；rest是一个函数参数包\n// Args表示零个或多个模板类型参数\n// rest表示零个或多个函数参数\ntemplate <typename T, typename... Args>\nvoid foo(const T &t, const Args& ... rest)\n{\n\tcout << sizeof...(Args) << endl;\t// 类型参数的数目\n\tcout << sizeof...(rest) << endl;\t// 函数参数的数目\n\tcout << \"\\n\";\n}\n\nint main()\n{\n\tint i = 0; double d = 3.14; string s = \"how now brown cow\";\n\n\tfoo(i, s, 42, d);\t\t// 包中有三个参数\n\tfoo(s, 42, \"hi\");\t\t// 包中有两个参数\n\tfoo(d, s);\t\t\t\t// 包中有一个参数\n\tfoo(\"hi\");\t\t\t\t// 空包\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/example_variadic_template2.cpp",
    "content": "// example: 编写可变参数函数模板（p620）\n\n#include <iostream>\n#include <string>\n#include <sstream>\n\nusing namespace std;\n\n// 用来终止递归并打印最后一个元素的函数\n// 此函数必须在可变参数版本的print定义之前声明\ntemplate <typename T>\nostream &print(ostream &os, const T &t)\n{\n\treturn os << t;\t// 包中最后一个元素之后不打印分隔符\n}\n\n// 包中除了最后一个元素之外的其他元素都会调用这个版本的print\ntemplate <typename T, typename... Args>\nostream &print(ostream &os, const T &t, const Args&... rest)\n{\n\tos << t << \", \";\t\t\t// 打印第一个实参\n\treturn print(os, rest...);\t// 递归调用，打印其他实参\n}\n\n//-----------------------------------------------------------\n\n// 打印任何我们不能处理的类型\ntemplate <typename T> string debug_rep(const T &t)\n{\n\tostringstream ret;\n\tret << t;\t\t\t// 使用T的输出运算符打印t的一个表示形式\n\treturn ret.str();\t// 返回ret绑定的string的一个副本\n}\n\n// 打印指针的值，后跟指针指向的对象\n// 注意：此函数不能用于char*\ntemplate <typename T> string debug_rep(T *p)\n{\n\tostringstream ret;\n\tret << \"pointer: \" << p; // 打印指针本身的值\n\tif (p)\n\t\tret << \" \" << debug_rep(*p);\t// 打印p指向的值\n\telse\n\t\tret << \" null pointer\";\t\t\t// 或指出p为空\n\n\treturn ret.str();\t// 返回ret绑定的string的一个副本\n}\n\n// 非模板\nstring debug_rep(const string &s)\n{\n\treturn '\"' + s + '\"';\n}\n\n// 将字符指针转换为string，并调用string版本的debug_rep\nstring debug_rep(char *p)\n{\n\treturn debug_rep(string(p));\n}\n\nstring debug_rep(const char *p)\n{\n\treturn debug_rep(string(p));\n}\n\n// 在print调用中对每个实参调用debug_rep\ntemplate <typename... Args>\nostream &errorMsg(ostream &os, const Args&... rest)\n{\n\t// print(os, debug_rep(a1), debug_rep(a2), ..., debug_rep(an))\n\treturn print(os, debug_rep(rest)...);\n}\n\n//-----------------------------------------------------------\n\nvoid func1()\n{\n\tint i = 88;\n\tstring s = \"Hi\";\n\tprint(cout, i, s, 42);\n}\n\n// 练习16.53\nvoid func2()\n{\n\tint i = 88;\n\tstring s = \"Hi\";\n\tchar c1 = 'x';\n\tchar c2 = 'd';\n\tdouble pi = 3.14;\n\n\tprint(cout, i) << endl;\n\tprint(cout, i, s) << endl;\n\tprint(cout, i, s, c1, c2, pi) << endl;\n}\n\n// 练习16.54\nvoid func3()\n{\n\tstruct NoOutOp\n\t{\n\t};\n\n\tNoOutOp obj;\n\t//print(cout, obj); // 编译报错\n\t\n\n\t(void)obj; // ignore warning\n}\n\n// 理解包扩展（p622）\nvoid func4()\n{\n\tint a = 42;\n\tconst char* str = \"Hello\";\n\tdouble pi = 3.14;\n\n\terrorMsg(cerr, a, &a, str, pi);\n}\n\nint main()\n{\n\t//func1();\n\t//func2();\n\tfunc4();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_01.md",
    "content": "练习16.1：给出实例化的定义。\n\n---\n\n当我们调用一个函数模板时，编译器（通常）用函数实参来为我们推断模板实参。\n\n编译器用推断出的模板参数来为我们实例化一个特定版本的函数。当编译器实例化一个模板时，它使用实际的模板实参代替对应的模板参数来创建出模板的一个新“实例”。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_02.md",
    "content": "练习16.2：编写并测试你自己版本的compare函数。\n\n---\n\n见[案例](./example_function_template.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_03.cpp",
    "content": "// 练习16.3：对两个Sales_data对象调用你的compare函数，观察编译器在实例化\n// 过程中如何处理错误。\n\n// 提示：error: no match for ‘operator<’ (operand types are ‘const Sales_data’ and ‘const Sales_data’)\n\n#include <iostream>\n\nusing namespace std;\n\ntemplate<typename T>\nint compare(const T &v1, const T &v2)\n{\n\tif (v1 < v2) return -1;\n\tif (v2 < v1) return 1;\n\treturn 0;\n}\n\nclass Sales_data\n{\n};\n\nint main()\n{\n\t/*\n\tSales_data sd1, sd2;\n\tcompare(sd1, sd2);\n\t*/\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_04.cpp",
    "content": "// 练习16.4：编写行为类似标准库find算法的模板。函数需要两个模板类型参数，一个\n// 表示函数的迭代器参数，另一个表示值的类型。使用你的函数在一个vector<int>和\n// 一个list<string>中查找给定值。\n\n#include <iostream>\n#include <vector>\n#include <list>\n\nusing namespace std;\n\ntemplate<typename It, typename S>\nIt my_find(It beg, It end, S sought)\n{\n\twhile (beg != end) {\n\t\tif (*beg == sought)\n\t\t\treturn beg;\n\t\t++beg;\n\t}\n\n\treturn end;\n}\n\nint main()\n{\n\tvector<int> vec = {1, 2, 5, 8, 3};\n\tif (my_find(vec.begin(), vec.end(), 8) != vec.end())\n\t{\n\t\tcout << \"vec find!\" << endl;\n\t}\n\n\tlist<int> lst = {1, 2, 5, 8, 3};\n\tif (my_find(lst.begin(), lst.end(), 10) == lst.end())\n\t{\n\t\tcout << \"list not find!\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_05.cpp",
    "content": "// 练习16.5：为6.2.4节（第195页）中的print函数编写模板版本，它接受一个数组的引用，\n// 能处理任意大小、任意元素类型的数组。\n\n#include <iostream>\n\nusing namespace std;\n\ntemplate<typename T, unsigned N>\nvoid print(T (&arr)[N])\n{\n\tfor (const auto &elem : arr)\n\t\tcout << elem << endl;\n}\n\nint main()\n{\n\tint arr1[] = {1, 2, 3, 4, 5};\n\tprint(arr1);\n\n\tcout << endl;\n\tdouble arr2[] = {1.1, 2.2, 3.3, 4.4, 5.5};\n\tprint(arr2);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_06.cpp",
    "content": "// 练习16.6：你认为接受一个数组实参的标准库函数begin和end是如何工作的？定义\n// 你自己版本的begin和end。\n\n// begin返回数组首元素的地址，end返回数组最后一个元素后一个位置的地址\n\n#include <iostream>\n\nusing namespace std;\n\ntemplate<typename T, unsigned N>\nT* arr_beg(T (&arr)[N])\n{\n\treturn arr;\n}\n\ntemplate<typename T, unsigned N>\nT* arr_end(T (&arr)[N])\n{\n\treturn arr + N;\n}\n\nint main()\n{\n\tint arr[] = {1, 2, 3, 4, 5};\n\tauto beg = arr_beg(arr), end = arr_end(arr);\n\n\twhile (beg != end)\n\t{\n\t\tcout << *beg++ << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_07.cpp",
    "content": "// 编写一个constexpr模板，返回给定数组的大小。\n\n#include <iostream>\n\nusing namespace std;\n\ntemplate<typename T, unsigned N>\nconstexpr unsigned arr_size(T (&arr)[N])\n{\n\treturn N;\n}\n\nint main()\n{\n\tint a[10] = {0};\n\n\tcout << arr_size(a) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_08.md",
    "content": "练习16.8：在第97页的“关键概念”中，我们注意到，C++程序员喜欢使用!=而不喜欢<。解释这个习惯的原因。\n\n---\n\n因为定义了!=操作的类型通常比定义了<操作的类型要普遍，等于是降低了模板的类型的要求。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_09.md",
    "content": "练习16.9：什么是函数模板？什么是类模板？\n\n---\n\n函数模板是用来生成类的公式。\n\n类模板是用来生成类的蓝图。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_10.md",
    "content": "练习16.10：当一个类模板被实例化时，会发生什么？\n\n---\n\n当实例化一个类时，编译器根据我们提供的模板实参，绑定到模板的参数，然后用这些模板实参实例化出特定的类。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_11.cpp",
    "content": "// 练习16.11：下面List的定义是错误的。应如何修正它？\n/*\n * template<typename elemType> class ListItem;\n * template<typename elemType> class List {\n * public:\n *     List<elemType>();\n *     List<elemType>(const List<elemType> &);\n *     List<elemType>& operator=(const List<elemType> &);\n *     ~List();\n *     void insert(ListItem *ptr, elemType value);\n * private:\n *     ListItem *fron, *end;\n * };\n */\n\n#include <iostream>\n\nusing namespace std;\n\n// 修正如下\ntemplate<typename elemType> class ListItem;\ntemplate<typename elemType> class List {\npublic:\n    List<elemType>() {}\n    List<elemType>(const List<elemType> &);\n    List<elemType>& operator=(const List<elemType> &);\n    ~List() {}\n    void insert(ListItem<elemType> *ptr, elemType value);\nprivate:\n    ListItem<elemType> *fron, *end;\n};\n\nint main()\n{\n\tList<int> list;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_12.md",
    "content": "练习16.12：编写你自己版本的Blob和BlobPtr模板，包含书中未定义的多个const成员。\n\n---\n\n见[案例](./example_template_class)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_13.md",
    "content": "练习16.13：解释你为BlobPtr的相等和关系运算符选择哪种类型的友好关系？\n\n---\n\n这里的相等关系运算符是模板，是一对一的友好关系。见p588。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_14.md",
    "content": "练习16.14：编写Screen类模板，用非类型参数定义Screen的高和宽。\n\n---\n\n见[案例](./example_Screen)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_15.md",
    "content": "练习16.15：为你的Screen模板实现输入和输出运算符。Screen类需要哪些友元（如果需要的话）来令输入和输出运算符正确工作？解释每个友元声明（如果有的话）为什么是必要的。\n\n---\n\n在我实现的版本中，输入运算符是需要声明成友元的，因为它需要改变Screen的cursor成员。\n\n见[案例](./example_Screen)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_16.md",
    "content": "练习16.16：将StrVec类（参见13.5节，第465页）重写为模板，命名为Vec。\n\n---\n\n见[案例](./example_Vec)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_17.md",
    "content": "练习16.17：声明为typename的类型参数和声明为class的类型参数有什么不同（如果有的话？）什么时候必须使用typename？\n\n---\n\n如果是作为模板参数，二者没有区别。\n\n如果是为了使用模板参数的类型成员，就必须使用typename，告诉编译器该名字是一个类型：\n\n```c++\nreturn typename T::value_type();\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_18.cpp",
    "content": "// 练习16.18：解释下面每个函数模板声明并指出它们是否非法。更正你发现的每个错误。\n/*\n * (a) template<typename T, U, typename V> void f1(T, U, V);\n * (b) template<typename T> T f2(int &T);\n * (c) inline template<typename T> T foo(T, unsigned int*);\n * (d) template<typename T> f4(T, T);\n * (e) typedef char Ctype;\n *     template<typename Ctype> Ctype f5(Ctype a);\n */\n\n// (a) 错误：error: ‘U’ has not been declared\n// template<typename T, U, typename V> void f1(T, U, V);\ntemplate<typename T, typename U, typename V> void f1(T, U, V);\n\n// (b) 错误，T是一个类型，而非参数名\n//template<typename T> T f2(int &T);\ntemplate<typename T> T f2(int&);\n\n// (c) 错误，template必须放在开头\n//inline template<typename T> T foo(T, unsigned int*);\ntemplate<typename T> inline T foo(T, unsigned int*);\n\n// (d) 错误，函数没有返回值\n//template<typename T> f4(T, T); \ntemplate<typename T> void f4(T, T); \n\n// (e) 正确，模板参数隐藏了类型别名\ntypedef char Ctype;\ntemplate<typename Ctype> Ctype f5(Ctype a);\n\n#include <iostream>\n\nusing namespace std;\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_19.cpp",
    "content": "// 练习16.19：编写函数，接受一个容器的引用，打印容器中的元素。使用容器的size_type\n// 和size成员来控制打印元素的循环。\n\n#include <iostream>\n#include <vector>\n\ntemplate<typename Container>\nvoid print_container(const Container &container)\n{\n\tfor (typename Container::size_type i = 0; i < container.size(); ++i) {\n\t\tstd::cout << container[i];\n\t\tif (i != container.size() - 1)\n\t\t\tstd::cout << \" \";\n\t}\n\tstd::cout << std::endl;\n}\n\nint main()\n{\n\tstd::vector<int> vec = {1, 2, 3, 4};\n\tprint_container(vec);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_20.cpp",
    "content": "// 练习16.20：重写上一题的函数，使用begin和end返回的迭代器来控制循环。\n\n// 练习16.19：编写函数，接受一个容器的引用，打印容器中的元素。使用容器的size_type\n// 和size成员来控制打印元素的循环。\n\n#include <iostream>\n#include <vector>\n#include <set>\n\ntemplate<typename Container>\nvoid print_container(const Container &container)\n{\n\tfor (auto it = container.begin(); it != container.end(); ++it)\n\t\tstd::cout << *it << \" \";\n\tstd::cout << std::endl;\n}\n\nint main()\n{\n\tstd::vector<int> vec = {1, 2, 3, 4};\n\tprint_container(vec);\n\n\tstd::set<int> set1 = {1, 2, 3, 4};\n\tprint_container(vec);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_21.md",
    "content": "练习16.21：编写你自己的DebugDelete版本。\n\n---\n\n见[案例](./example_member_template/DebugDelete.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_22.md",
    "content": "练习16.22：修改12.3节（第430页）中你的TextQuery程序，令shared_ptr成员使用DebugDelete作为它们的删除器（参加12.1.4节，第415页）。\n\n---\n\n见[案例](../ch12_Dynamic_Memory/example_TextQuery)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_23.md",
    "content": "练习16.23：预测在你的查询主程序中何时会执行调用运算符。如果你的预测和实际不符，确认你理解了原因。\n\n---\n\n当runQueries运行完毕的时候（用户输入q），TextQuery析构，shared_ptr的引用数量变为0，此时调用析构器的调用运算符。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_24.md",
    "content": "练习16.24：为你的Blob模板添加一个构造函数，它接受两个迭代器。\n\n---\n\n见[案例](./example_template_class/Blob.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_25.md",
    "content": "练习16.25：解释下面这些声明的含义：\n\n```c++\nextern template class vector<string>;\ntemplate class vector<Sales_data>;\n```\n\n---\n\n对于带extern的声明，编译器不会在本文件将中生成模板的实例代码。将一个实例化声明为extern就表示承诺在程序其他位置有该实例化的一个非extern声明（定义）。\n\n对于不带extern的声明，编译器会进行模板的实例化。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_26.cpp",
    "content": "// 练习16.26：假设NoDefault是一个没有默认构造函数的类，我们可以显式实例化\n// vector<NoDefault>吗？如果不可以，解释为什么？\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\nclass NoDefault\n{\npublic:\n\texplicit NoDefault(int a) : m_a(a) {}\n\nprivate:\n\tint m_a = 0;\n};\n\n//template class vector<NoDefault>; // ERROR: 显式实例化会定义实例化所有成员\n\nint main()\n{\n\tvector<NoDefault> vec; // OK, 因为没有使用到默认构造函数，无须实例化这个成员\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_27.md",
    "content": "练习16.27：对下面每条带标签的语句，解释发生了什么样的实例化（如果有的话）。如果一个模板被实例化，解释为什么；如果未实例化，解释为什么没有。\n\n```c++\n#include <iostream>\n\nusing namespace std;\n\ntemplate <typename T> class Stack {};\n\nvoid f1(Stack<char>);\t\t\t\t\t// (a) 不会被实例化，这里是函数声明，没有用到模板的实例\n\nclass Exercise {\n\tStack<double> &rsd;\t\t\t\t\t// (b) 不会被实例化，因为成员是引用\n\tStack<int> si;\t\t\t\t\t\t// (c) 实例化Stack<int>\n};\n\nint main()\n{\n\tStack<char> *sc;\t\t\t\t\t// (d) 不会被实例化，这里只是定义了一个指针类型\n\tf1(*sc);\t\t\t\t\t\t\t// (e) 实例化Stack<char>\n\tint iObj = sizeof(Stack<string>);\t// (f) 实例化Stack<string>\n\n\treturn 0;\n}\n```\n\n---\n\n这里都是普通的实例化，即当用到了模板的实例和模板实例的成员，才会实例化它们。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_28.md",
    "content": "练习16.28：编写你自己版本的shared_ptr和unique_ptr。\n\n---\n\n见[案例](./example_SmartPointer)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_29.cpp",
    "content": "// 练习16.29：修改你的Blob类，用你自己的shared_ptr代替标准库中的版本。\n\n#include <iostream>\n#include <vector>\n#include \"./example_SmartPointer/SharedPtr.h\"\n\nusing namespace std;\n\ntemplate<typename> class Blob;\n\ntemplate<typename T>\nbool operator==(const Blob<T>&, const Blob<T>&);\n\ntemplate<typename T>\nclass Blob {\n\tfriend bool operator==<T>(const Blob<T>&, const Blob<T>&);\n\npublic:\n\ttypedef T value_type;\n\ttypedef typename std::vector<T>::size_type size_type;\n\n\t// 构造函数\n\tBlob() : data(new std::vector<T>()) {}\n\tBlob(std::initializer_list<T> il) : data(new std::vector<T>(il)) {}\n\n\t// Blob中的元素数目\n\tsize_type size() const { return data->size(); }\n\tbool empty() const { return data->empty(); }\n\n\t// 添加和删除元素\n\tvoid push_back(const T &t) { data->push_back(t); }\n\tvoid pop_back();\n\n\t// 移动版本\n\tvoid push_back(T &&t) { data->push_back(std::move(t)); }\n\n\t// 元素访问\n\tconst T& back() const;\t\n\tconst T& front() const;\t\n\tconst T& operator[](size_type i) const;\n\tT& back();\n\tT& front();\n\tT& operator[](size_type i);\n\nprivate:\n\tSharedPtr<std::vector<T>> data;\n\n\t// 若data[i]无效，则抛出异常\n\tvoid check(size_type i, const std::string &msg) const;\n};\n\ntemplate<typename T>\ninline void Blob<T>::check(size_type i, const std::string &msg) const\n{\n\tif (i >= data->size())\n\t\tthrow std::out_of_range(msg);\n}\n\ntemplate<typename T>\ninline T& Blob<T>::back()\n{\n\tcheck(0, \"back on empty Blob\");\n\treturn data->back();\n}\n\ntemplate<typename T>\ninline T& Blob<T>::front()\n{\n\tcheck(0, \"front on empty Blob\");\n\treturn data->front();\n}\n\ntemplate<typename T>\ninline const T& Blob<T>::back() const\n{\n\tcheck(0, \"back on empty Blob\");\n\treturn data->back();\n}\n\ntemplate<typename T>\ninline const T& Blob<T>::front() const\n{\n\tcheck(0, \"front on empty Blob\");\n\treturn data->front();\n}\n\ntemplate<typename T>\ninline T& Blob<T>::operator[](size_type i)\n{\n\t// 如果i太大，check会抛出异常，阻止访问一个不存在的元素\n\tcheck(i, \"subscript out of range\");\n\treturn (*data)[i];\n}\n\ntemplate<typename T>\ninline const T& Blob<T>::operator[](size_type i) const\n{\n\t// 如果i太大，check会抛出异常，阻止访问一个不存在的元素\n\tcheck(i, \"subscript out of range\");\n\treturn (*data)[i];\n}\n\ntemplate<typename T>\ninline void Blob<T>::pop_back()\n{\n\tcheck(0, \"pop_back on empty Blob\");\n\tdata->pop_back();\n}\n\nint main()\n{\n\tBlob<int> blob{1, 2, 3, 4, 5};\n\tBlob<int> blob2(blob);\n\tblob2.push_back(100);\n\n\tfor (size_t i = 0; i < blob.size(); ++i)\n\t\tcout << blob[i] << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_30.md",
    "content": "练习16.30：重新运行你的一些程序，验证你的shared_ptr类和修改后的Blob类。（注意：实现weak_ptr类型超出了本书范围，因此你不能将BlobPtr类与你修改后的Blob一起使用。）\n\n---\n\n见[上一个练习](./exercise_16_29.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_31.md",
    "content": "练习16.31：如果我们将DebugDelete与unique_ptr一起使用，解释编译器将删除器处理为内联形式的可能方式。\n\n---\n\n在编译阶段确定unique_ptr的删除器为DebugDelete类型的对象。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_32.md",
    "content": "练习16.32：在模板实参推断过程中发生了什么？\n\n---\n\n从函数实参来确定模板实参的过程称为模板实参推断。在模板实参推断过程中，编译器使用函数调用中的实参类型来寻找模板实参，用这些模板实参生成的函数版本与给定的函数调用最为匹配。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_33.md",
    "content": "练习16.33：指出在模板实参推断过程中允许对函数实参进行的两种类型转换。\n\n---\n\n1. const转换：将一个非const对象的引用（或指针）传递给一个const的引用（或指针）形参。\n\n2. 数组或函数到指针的转换：如果函数形参不是引用类型，则可以对数组或函数类型的实参应用正常的指针转换。一个数组实参可以转换为一个指向其首元素的指针。类似的，一个函数实参可以转换为一个该函数类型的指针。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_34.md",
    "content": "练习16.34：对下面的代码解释每个调用是否合法。如果合法，T的类型是什么？如果不合法，为什么？\n\n```c++\ntemplate <class T> int compare(const T&, const T&);\n```\n\n(a) compare(\"hi\", \"world\");\n\n(b) compare(\"bye\", \"dad\");\n\n---\n\na非法，因为无法推断出可以绑定到`const T&`的模板实参类型。\n\nb合法，可以推断出这样的模板实参类型：`const char (&)[4]`，它可以绑定到`const T&`。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_35.md",
    "content": "练习16.35：下面调用中哪些是错误的（如果有的话）？如果调用合法，T的类型是什么？如果调用不合法，问题何在？\n\n```c++\ntemplate <typename T> T calc(T, int);\ntemplate <typename T> T fcn(T, T);\n\ndouble d; float f; char c;\n```\n(a) calc(c, 'c');\n\n(b) calc(d, f);\n\n(c) fcn(c, 'c');\n\n(d) fcn(d, f);\n\n---\n\n(a) 合法，T的类型是char。\n\n(b) 合法，T的类型是double。\n\n(c) 合法，T的类型是char。\n\n(d) 不合法，函数实参类型不一致，无法推断出模板实参。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_36.md",
    "content": "练习16.36：进行下面的调用会发生什么：\n\n```c++\ntemplate <typename T> void f1(T, T);\ntempalte <typename T1, typename T2> void f2(T1, T2);\n\nint i = 0, j = 42, *p1 = &i, *p2 = &j;\nconst int *cp1 = &i, *cp2 = &j;\n```\n\n(a) f1(p1, p2);\t\t// f1(int*, int*)\n\n(b) f2(p1, p2);\t\t// f2(int*, int*)\n\n(c) f1(cp1, cp2);\t// f1(const int*, const int*)\n\n(d) f2(cp1, cp2);\t// f2(const int*, const int*)\n\n(e) f1(p1, cp1);\t// 错误，p1和cp1类型不一致\n\n(f) f2(p1, cp1);\t// f2(int*, const int*);\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_37.cpp",
    "content": "// 练习16.37：标准库max函数有两个参数，它返回实参中的较大者。此函数有一个模板\n// 类型参数。你能在调用max时传递给它一个int和一个double吗？如果可以，如何做？\n// 如果不可以，为什么？\n\n#include <iostream>\n\nusing namespace std;\n\nint main()\n{\n\tint i = 10;\n\tdouble pi = 3.14;\n\n\t//cout << std::max(i, pi) << endl; // error: no matching function for call to ‘max(int, double)’\n\tcout << std::max<double>(i, pi) << endl; // OK\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_38.md",
    "content": "练习16.38：当我们调用make_shared(参加12.1.1节，第401页)时，必须提供一个显式模板实参。解释为什么需要显式模板实参以及它是如何使用的。\n\n---\n\nmake_shared的参数是构造类型对象的参数列表（可为空），不能由此推断出对象的类型，因此要提供显式模板实参表明返回值。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_39.md",
    "content": "练习16.39：对16.1.1节（第578页）中的原始版本的compare函数，使用一个显式\n模板实参，使得可以向函数传递两个字符串字面常量。\n\n---\n\n见[案例](./example_function_template.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_40.cpp",
    "content": "// 练习16.40：下面的函数是否合法？如果不合法，为什么？如果合法，对可以传递的\n// 实参类型有什么限制（如果有的话）？返回类型是什么？\n\n// 合法，但是必须用于能和整数做加法的类型，返回beg绑定对象的类型\n\n#include <iostream>\n#include <string>\n#include <vector>\n\nusing namespace std;\n\ntemplate <typename It>\nauto fcn3(It beg, It end) -> decltype(*beg + 0)\n{\n\t// 处理序列\n\treturn *beg; // 返回序列中一个元素的拷贝\n}\n\nint main()\n{\n\tstring s;\n\tfcn3(s.begin(), s.end()); // OK，char可以和整数做加法\n\n\tvector<string> vec;\n\t//fcn3(vec.begin(), vec.end()); // 非法，string不能和整数做加法\n\t\n\t(void)vec; // ignore unuse warning\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_41.cpp",
    "content": "// 练习16.41：编写一个新的sum版本，它的返回类型保证足够大，足以容纳加法的结果。\n\n#include <iostream>\n#include <initializer_list>\n#include <string>\n\nusing namespace std;\n\ntemplate<typename T>\nlong long sum(initializer_list<T> il)\n{\n\tlong long sum = 0;\n\tfor (auto i : il)\n\t\tsum += i;\n\n\treturn sum;\n}\n\nint main()\n{\n\tcout << sum({1, 2, 3, 4, 5}) << endl;\n\tcout << sum({(short)30000, (short)30000}) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_42.md",
    "content": "练习16.42：对下面的每个调用，确定T和val的类型：\n\n```c++\ntemplate<typename T> void g(T&& val);\nint i = 0; const int ci = i;\n```\n\n---\n\n(a) g(i);\t\t\t// T类型是 int&, val类型是 int& &&，折叠成 int&\n\n(b) g(ci);\t\t\t// T类型是 const int&，val类型是 const int& &&，折叠成 const int&\n\n(c) g(i * ci);\t\t// T类型是 int，val类型是int&&\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_43.md",
    "content": "练习16.43：使用上一题定义的函数，如果我们调用g(i == ci)，g的模板参数将是什么？\n\n---\n\ni = ci返回的是一个int&，它是一个左值，模板参数将推断为引用类型，即int& 。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_44.md",
    "content": "练习16.44：使用与第一题中相同的三个调用，如果g的函数参数声明为T（而不是T&&），确定T的类型。如果g的函数参数是const T&呢？\n\n练习16.42：对下面的每个调用，确定T和val的类型：\n\n```c++\ntemplate<typename T> void g(T&& val);\nint i = 0; const int ci = i;\n```\n\n---\n\n**如果是g(T val)**\n\n(a) g(i);\t\t\t// int\n\n(b) g(ci);\t\t\t// int\n\n(c) g(i * ci);\t\t// int\n\n**如果是g(const T&)**\n\n(a) g(i);\t\t\t// int\n\n(b) g(ci);\t\t\t// int\n\n(c) g(i * ci);\t\t// int\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_45.cpp",
    "content": "// 练习16.45：给定下面的模板，如果我们对一个像42这样的字面常量调用g，解释会发生\n// 什么？如果我们对一个int类型的变量调用g呢？\n\n#include <iostream>\n#include <vector>\n\nusing namespace std;\n\ntemplate<typename T> void g(T&& val) { vector<T> v; }\n\nint main()\n{\n\tg(42); // OK，T被推导为int，可以定义一个vector<int>\n\t\n\t//int i = 0;\n\t//g(i);  // ERROR，T被推导为int&，不能定义一个vector<int&>\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_46.md",
    "content": "练习16.46：解释下面的循环，它来自13.5节（第469页）中的StrVec::reallocate:\n\n```c++\nfor (size_t i = 0; i != size(); ++i)\n\talloc.construct(dest++, std::move(*elem++));\n```\n\n---\n\n先将eleme往后递增，返回原来的elem所指对象(一个左值)，通过move得到其右值引用，然后调用construct的右值引用版本，从而窃取其中的资源，避免了资源的拷贝。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_47.cpp",
    "content": "// 练习16.47：编写你自己版本的翻转函数，通过调用接受左值和右值引用参数的函数\n// 来测试它。\n\n#include <iostream>\n\nusing namespace std;\n\ntemplate <typename F, typename T1, typename T2>\nvoid flip2(F f, T1 &&t1, T2 &&t2)\n{\n\tf(t2, t1);\n}\n\ntemplate<typename F, typename T1, typename T2>\nvoid flip(F f, T1 &&t1, T2 &&t2)\n{\n\tf(std::forward<T2>(t2), std::forward<T1>(t1));\n}\n\nvoid g1(int &&i, int &j)\n{\n\tj = i;\n}\n\nint main()\n{\n\tint a = 0;\n\t//flip2(g1, a, 42); // cannot bind ‘int’ lvalue to ‘int&&’\n\tflip(g1, a, 42);\n\tcout << a << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_48.md",
    "content": "练习16.48：编写你自己版本的debug_rep函数。\n\n---\n\n见[案例](./example_overload_and_template.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_49.cpp",
    "content": "// 练习16.49：解释下面每个调用会发生什么。\n\n#include <iostream>\n\nusing namespace std;\n\ntemplate <typename T> void f(T) { cout << \"f(T)\" << endl; }\n\ntemplate <typename T> void f(const T*) { cout << \"f(const T*)\" << endl; }\n\ntemplate <typename T> void g(T) { cout << \"g(T)\" << endl; }\n\ntemplate <typename T> void g(T*) { cout << \"g(T*)\" << endl; }\n\nint main()\n{\n\tint i = 42, *p = &i;\n\tconst int ci = 0, *p2 = &ci;\n\n\tg(42);\t\t// g(T)\n\tg(p);\t\t// g(T*)\n\tg(ci);\t\t// g(T)\n\tg(p2);\t\t// g(T*)\n\n\tf(42);\t\t// f(T)\n\tf(p);\t\t// f(T)\n\tf(ci);\t\t// f(T)\n\tf(p2);\t\t// f(const T*)\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_50.md",
    "content": "练习16.50：定义上一个练习中的函数，令它们打印一条身份信息。运行该练习中的代码。如果函数调用的行为与你的预期不符，确定你了解原因。\n\n---\n\n见上一个练习。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_51.md",
    "content": "练习16.51：调用本节中的每个foo，确定sizeof...(Args)和sizeof...(rest)分别返回什么。\n\n---\n\n见[案例](./example_variadic_template.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_52.md",
    "content": "练习16.52：编写一个程序验证上一题的答案。\n\n---\n\n见[案例](./example_variadic_template.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_53.md",
    "content": "练习16.53：编写你自己版本的print函数，并打印一个、两个及五个实参来测试它，要打印的每个实参都应有不同的类型。\n\n---\n\n见[案例](./example_variadic_template2.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_54.md",
    "content": "练习16.54：如果我们对一个没有<<运算符的类型调用print，会发生什么？\n\n---\n\n编译报错。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_55.cpp",
    "content": "// 练习16.55：如果我们的可变参数版本print的定义之后声明非可变阐述版本，解释\n// 可变参数的版本会如何执行。\n\n// error: no matching function for call to ‘print(std::ostream&)’\n\n#include <iostream>\n#include <string>\n\nusing namespace std;\n\n// 包中除了最后一个元素之外的其他元素都会调用这个版本的print\ntemplate <typename T, typename... Args>\nostream &print(ostream &os, const T &t, const Args&... rest)\n{\n\tos << t << \", \";\t\t\t// 打印第一个实参\n\treturn print(os, rest...);\t// 递归调用，打印其他实参\n}\n\n// 用来终止递归并打印最后一个元素的函数\n// 此函数必须在可变参数版本的print定义之前声明\ntemplate <typename T>\nostream &print(ostream &os, const T &t)\n{\n\treturn os << t;\t// 包中最后一个元素之后不打印分隔符\n}\n\nvoid func1()\n{\n\tint i = 88;\n\tstring s = \"Hi\";\n\tprint(cout, i, s, 42);\n}\n\nint main()\n{\n\tfunc1(); \n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_56.md",
    "content": "练习16.56：编写并测试可变参数版本的errorMsg。\n\n---\n\n见[案例](./example_variadic_template2.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_57.md",
    "content": "练习16.57：比较你的可变参数版本的errorMsg和6.2.6节（198页）中的error_msg函数。两种方法的优点和确定各是什么？\n\n---\n\n- errorMsg, 更加灵活，适用于各种类型，只要它有<<运算符，但实现起来不太容易\n\n- error_msg, 使用起来更简单，但只适用于一种类型\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_58.md",
    "content": "练习16.58：为你的StrVec类及你为16.1.2节（第591页）练习中编写的Vec类添加emplace_back函数。\n\n---\n\n见：\n\n- [StrVec::emplace_back](../ch13_Copy_Control/example_StrVec)\n\n- [Vec::emplace_back](./example_Vec)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_59.md",
    "content": "练习16.59：假定s是一个string，解释调用svec.emplace_back(s)会发生什么。\n\n---\n\nconstruct调用中的模式会扩展出：\n\nstd::forward<string&>(s) -> string& s\n\n从而会调用：alloc.construct(first_free++, s);\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_60.md",
    "content": "练习16.60：解释make_shared(参见12.1.1节，第401页)是如何工作的。\n\n---\n\n使用可变长参数作为要构造的类型（也是一个模板参数）的构造函数参数，构造一个对象，用于初始化它的shared_ptr。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_61.md",
    "content": "练习16.61：定义你自己版本的make_shared。\n\n---\n\n[MakeShared](./example_customer_MakeShared)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_62.md",
    "content": "练习16.62：定义你自己版本的hash<Sales_data>，并定义一个Sales_data对象的unordered_multiset。将多条交易记录保存到容器中，并打印其内容。\n\n---\n\n见[Sales_data](../ch07_Classes/example_Sales_data)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_63.cpp",
    "content": "// 练习16.63：定义一个函数模板，统计一个给定值在一个vector中出现的次数。\n// 测试你的函数，分别传递给它一个double的vector，一个int的vector以及一个\n// string的vector。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <cstring>\n\nusing namespace std;\n\ntemplate <typename T>\nint GetOccurTimes(const T &value, const std::vector<T> &vec)\n{\n\tint times = 0;\n\n\tfor (const auto &item : vec)\n\t{\n\t\tif (item == value)\n\t\t{\n\t\t\t++ times;\n\t\t}\n\t}\n\n\treturn times;\n}\n\n// 特例化版本，练习16.64\ntemplate <>\nint GetOccurTimes(const char* const &value, const std::vector<const char*> &vec)\n{\n\tint times = 0;\n\n\tfor (auto item : vec)\n\t{\n\t\tif (strcmp(value, item) == 0)\n\t\t{\n\t\t\t++ times;\n\t\t}\n\t}\n\n\treturn times;\n}\n\nint main()\n{\n\tvector<int> ivec{1, 2, 3, 3, 3, 5, 2};\n\tcout << GetOccurTimes(2, ivec) << endl;\n\n\tvector<double> dvec{1.1, 2.2, 3.3, 4.4, 2.2};\n\tcout << GetOccurTimes(2.2, dvec) << endl;\n\n\tvector<string> svec{\"hello\", \"hi\", \"hi\", \"wow\"};\n\tcout << GetOccurTimes(string(\"hi\"), svec) << endl;\n\n\tvector<const char*> cstr_vec{\"hello\", \"hi\", \"hi\", \"wow\"};\n\tcout << GetOccurTimes(string(\"hi\"), svec) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_64.md",
    "content": "练习16.64：为上一题中的模板编写特例化版本来处理vector<const char\\*>。编写程序使用这个特例化的版本。\n\n---\n\n见[练习16.63](./exercise_16_63.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_65.cpp",
    "content": "// 练习16.65\n\n#include <iostream>\n#include <sstream>\n\nusing namespace std;\n\n// 打印任何我们不能处理的类型\ntemplate <typename T> string debug_rep(const T &t)\n{\n\tostringstream ret;\n\tret << t;\t\t\t// 使用T的输出运算符打印t的一个表示形式\n\treturn ret.str();\t// 返回ret绑定的string的一个副本\n}\n\n// 打印指针的值，后跟指针指向的对象\n// 注意：此函数不能用于char*\ntemplate <typename T> string debug_rep(T *p)\n{\n\tostringstream ret;\n\tret << \"pointer: \" << p; // 打印指针本身的值\n\tif (p)\n\t\tret << \" \" << debug_rep(*p);\t// 打印p指向的值\n\telse\n\t\tret << \" null pointer\";\t\t\t// 或指出p为空\n\n\treturn ret.str();\t// 返回ret绑定的string的一个副本\n}\n\n// 非模板\nstring debug_rep(const string &s)\n{\n\treturn '\"' + s + '\"';\n}\n\n// 将字符指针转换为string，并调用string版本的debug_rep\ntemplate <>\nstring debug_rep(char *p)\n{\n\treturn debug_rep(string(p));\n}\n\ntemplate <>\nstring debug_rep(const char *p)\n{\n\treturn debug_rep(string(p));\n}\n\nvoid func1()\n{\n\tint i = 42;\n\tcout << debug_rep(i) << endl;\n}\n\nvoid func2()\n{\n\tint i = 42;\n\tcout << debug_rep(&i) << endl;\n}\n\nvoid func3()\n{\n\tstring s = \"hi\";\n\tcout << debug_rep(s) << endl;\n}\n\nvoid func4()\n{\n\tcout << debug_rep(\"Wow\") << endl;\n\n\tchar str[] = \"Hello\";\n\tcout << debug_rep(str) << endl;\n}\n\nint main()\n{\n\t//func1();\n\t//func2();\n\t//func3();\n\tfunc4();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_66.md",
    "content": "练习16.66：重载debug_rep函数与特例化它的相比，有何优点和缺点？\n\n---\n\n特例化版本只是一个实例，而非重载函数，不会影响原有的函数匹配规则。\n\n而重载的版本会比模板实例有更高的匹配优先级。\n"
  },
  {
    "path": "codes/CppPrimer/ch16_Templates_and_GenericProgramming/exercise_16_67.md",
    "content": "练习16.67：定义特例化版本会影响debug_rep的函数匹配吗？如果不影响，为什么？\n\n---\n\n不会。特例化的版本只是原有模板的一个实例。\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"example_phone_number.cpp example_sregex_iterator.cpp exercise_17_18.cpp exercise_17_21.cpp exercise_17_22.cpp exercise_17_23.cpp exercise_17_26.cpp exercise_17_35.cpp exercise_17_36.cpp\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\trm -f out1 out2\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/data/test_rules.txt",
    "content": "where r u\ny dont u send me a pic\nk thk 18r\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/data/trans_rules.txt",
    "content": "brb be right back\nk okay?:ok?:OK?\ny why:Why:WHY\nr are:ARE:Are\nu you:YOU:You\npic picture:PIC\nthk thanks!:THX\n18r later:Later\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/example_formatted_io.cpp",
    "content": "// example: 格式化输入与输出（p666）\n\n#include <iostream>\n#include <iomanip>\n#include <cmath>\n\nusing namespace std;\n\n// 控制布尔值的格式\nvoid func1()\n{\n\tcout << \"default bool values: \" << true << \" \" << false << endl;\n\n\tcout << \"alpha bool values: \" << boolalpha << true << \" \" << false << endl;\n\n\tcout << noboolalpha << endl; // 将内部状态恢复为默认格式\n}\n\n// 指定整型值的进制\nvoid func2()\n{\n\tcout << showbase; // 打印整型值时显示进制\n\n\tcout << \"default: \" << 20 << \" \" << 1024 << endl;\n\n\tcout << \"octal: \" << oct << 20 << \" \" << 1024 << endl;\n\n\tcout << \"hex: \" << hex << 20 << \" \" << 1024 << endl;\n\n\tcout << \"decimal: \" << dec << 20 << \" \" << 1024 << endl;\n\n\tcout << noshowbase; // 恢复流状态\n}\n\n// 指定打印精度\nvoid func3()\n{\n\t// 返回当前精度值\n\tcout << \"Precision: \" << cout.precision() << \", Value: \" << sqrt(2.0) << endl;\n\n\t// 设置精度\n\tcout << setprecision(3) << \"Precision: \" << cout.precision() << \", Value: \" << sqrt(2.0) << endl;\n}\n\n// 输出补白\nvoid func4()\n{\n\tint i = -16;\n\tdouble d = 3.14159;\n\t\n\t// 补白第一列，使用输出中最小12个位置\n\tcout << \"i: \" << setw(12) << i << endl\n\t     << \"d: \" << setw(12) << d << endl;\n\n\t// 补白第一列，左对齐所有列\n\tcout << left\n\t     << \"i: \" << setw(12) << i << endl\n\t     << \"d: \" << setw(12) << d << endl;\n\t\n\t// 补白第一列，右对齐所有列\n\tcout << right\n\t     << \"i: \" << setw(12) << i << endl\n\t     << \"d: \" << setw(12) << d << endl;\n\t\n\t// 补白第一列，但补在域的内部\n\tcout << internal\n\t     << \"i: \" << setw(12) << i << endl\n\t     << \"d: \" << setw(12) << d << endl;\n\t\n\t// 补白第一列，用#作为补白字符\n\tcout << setfill('#')\n\t     << \"i: \" << setw(12) << i << endl\n\t     << \"d: \" << setw(12) << d << endl\n\t\t << setfill(' '); // 恢复正常的补白字符\n}\n\nint main()\n{\n\t//func1();\n\t//func2();\n\t//func3();\n\tfunc4();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/example_phone_number.cpp",
    "content": "// example: 使用子表达式验证电话号码（p654）\n\n#include <iostream>\n#include <regex>\n#include <string>\n\nusing namespace std;\n\nbool valid(const smatch &m)\n{\n\t// 在一个合法的电话号码中，区号要么是完整括号包围的，要么完全没有括号。\n\t// 如果区号前有一个左括号\n\tif (m[1].matched)\n\t{\n\t\t// 则区号后必须有一个右括号，之后紧跟剩余号码或一个空格\n\t\treturn m[3].matched && (m[4].matched == 0 || m[4].str() == \" \");\n\t}\n\telse\n\t{\n\t\t// 否则，区号后不能有右括号\n\t\t// 另外两个组成部分间的分隔符必须匹配\n\t\treturn !m[3].matched && m[4].str() == m[6].str();\n\t}\n\n\treturn false;\n}\n\nint main()\n{\n\tstring phone = \"(\\\\()?(\\\\d{3})(\\\\))?([-. ])?(\\\\d{3})([-. ])?(\\\\d{4})\";\n\n\t/*\n\t * e.g. (555)1234567\n\t *      ^^  ^^  ^\n\t *      12  35  7\n\t *\n\t * 1. (\\\\()? 表示区号部分可选的左括号\n\t * 2. (\\\\d{3}) 表示区号\n\t * 3. (\\\\))? 表示区号部分可选的右括号\n\t * 4. ([-. ])? 表示区号部分可选的分隔符\n\t * 5. (\\\\d{3}) 表示号码的下三位数字\n\t * 6. ([-. ])? 表示可选的分隔符\n\t * 7. (\\\\d{4}) 表示号码的最后四位数字\n\t */\n\n\tregex r(phone);\t// regex对象，用于查找我们的模式\n\tsmatch m;\n\tstring s;\n\n\t// 从输入文件中读取每条记录\n\twhile (getline(cin, s))\n\t{\n\t\tbool matched = false;\n\n\t\t// 对每个匹配的电话号码\n\t\tfor (sregex_iterator it(s.begin(), s.end(), r), end_it; it != end_it; ++it)\n\t\t{\n\t\t\t// 检查号码格式是否合法\n\t\t\tif (valid(*it))\n\t\t\t\tcout << \"valid: \" << it->str() << endl;\n\t\t\telse\n\t\t\t\tcout << \"not valid: \" << it->str() << endl;\n\n\t\t\tmatched = true;\n\t\t}\n\n\t\tif (!matched)\n\t\t{\n\t\t\tcerr << \"your input can not match phone number!\" << endl;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/example_random.cpp",
    "content": "// example: 随机数（p659）\n\n// PS: 出于演示的目的，实用性有待确认。\n\n#include <iostream>\n#include <random>\n#include <vector>\n\nusing namespace std;\n\n// 通过随机数引擎对象生成原始随机数\nvoid func1()\n{\n\tdefault_random_engine e;\n\tfor (size_t i = 0; i < 10; ++i)\n\t{\n\t\tcout << e() << \" \";\n\t}\n\tcout << endl;\n}\n\n// 生成0到9之间（包含）均匀分布的随机数\nvoid func2()\n{\n\tuniform_int_distribution<unsigned> u(0, 9);\n\tdefault_random_engine e;\n\tfor (size_t i = 0; i < 10; ++i)\n\t{\n\t\t// 将u作为随机数源\n\t\t// 每个调用返回指定范围内并服从均匀分布的值\n\t\tcout << u(e) << \" \";\n\t}\n}\n\n// 正确的方法：将引擎和关联的分布对象定义为static的\n// 返回一个vector，包含100个均匀分布的随机数\nvector<unsigned> good_randVec()\n{\n\t// 由于我们希望引擎和分布对象保持状态，因此应该将它们\n\t// 定义为static的，从而每次调用都生成新的数\n\tstatic default_random_engine e;\n\tstatic uniform_int_distribution<unsigned> u(0, 9);\n\tvector<unsigned> ret;\n\tfor (size_t i = 0; i < 100; ++i)\n\t{\n\t\tret.push_back(u(e));\n\t}\n\n\treturn ret;\n}\n\n// 设置随机数种子\nvoid func3()\n{\n\tstatic uniform_int_distribution<unsigned> u(0, 9);\n\tstatic default_random_engine e(time(0));\n\n\tfor (size_t i = 0; i != 10; ++i)\n\t{\n\t\tcout << u(e) << \" \";\n\t}\n\tcout << endl;\n}\n\n// 正态分布的随机数\nvoid func4()\n{\n\tdefault_random_engine e;\n\tnormal_distribution<> n(4, 1.5);\n\n\tvector<unsigned> vals(9);\n\n\tfor (size_t i = 0; i != 200; ++i) {\n\t\tunsigned v = lround(n(e));\t// 舍入到最接近的整数\n\t\tif (v < vals.size())\t\t// 如果结果在范围内\n\t\t\t++vals[v];\t\t\t\t// 统计每个数出现了多少次\n\t}\n\n\tfor (size_t j = 0; j != vals.size(); ++j)\n\t\tcout << j << \": \" << string(vals[j], '*') << endl;\n}\n\nint main()\n{\n\t//func1();\n\t//func2();\n\t\n\t/*\n\tif (good_randVec() != good_randVec())\n\t{\n\t\tcout << \"different random result\" << endl;\n\t}\n\t*/\n\n\t/*\n\tfor (int i = 0; i < 3; ++i)\n\t{\n\t\tfunc3();\n\t}\n\t*/\n\n\tfunc4();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/example_regex_error.cpp",
    "content": "// example: 指定或使用正则表达式时的错误（p648）\n\n#include <iostream>\n#include <regex>\n\nusing namespace std;\n\nint main()\n{\n\ttry {\n\t\t// 错误，alnum漏掉了右括号，构造函数会抛出异常\n\t\tregex r(\"[[:alnum:]\", regex::icase);\n\t}\n\tcatch (regex_error e)\n\t{\n\t\tcout << e.what() << \"\\ncode: \" << e.code() << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/example_regex_replace.cpp",
    "content": "// example: 使用regex_replace（p657）\n\n// ./example_regex_replace < ../data/person_numbers.txt\n\n#include <iostream>\n#include <regex>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tstring phone = \"(\\\\()?(\\\\d{3})(\\\\))?([-. ])?(\\\\d{3})([-. ])?(\\\\d{4})\";\n\tregex r(phone);\t// 寻找模式所用的regex对象\n\n\tstring s;\n\tstring fmt = \"$2.$5.$7\";\t// 将号码格式改为ddd.ddd.dddd\n\n\t// 从输入文件中读取每条记录\n\twhile (getline(cin , s))\n\t{\n\t\tcout << regex_replace(s, r, fmt) << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/example_seek_tell.cpp",
    "content": "// example: seek和tell函数（p676）\n\n#include <iostream>\n#include <fstream>\n#include <string>\n\nusing namespace std;\n\n/*\n * 给定一个文件，在文件尾写入新的一行，包含文件中每行的相对起始位置。\n */\n\nint main()\n{\n\t// 以读写方式打开文件，并定位到文件尾\n\tfstream inOut(\"../data/some_words.txt\", fstream::ate | fstream::in | fstream::out);\n\tif (!inOut)\n\t{\n\t\tcerr << \"Unable to open file!\" << endl;\n\t\treturn EXIT_FAILURE;\n\t}\n\n\t// inOut以ate模式打开，因此一开始就定义到其文件尾\n\tauto end_mark = inOut.tellg();\t\t// 记住原文件尾位置\n\tinOut.seekg(0, fstream::beg);\t\t// 重定位到文件开始\n\tsize_t cnt = 0;\t\t\t\t\t\t// 字节数累加器\n\tstring line;\t\t\t\t\t\t// 保存输入中的行\n\n\t// 继续读取的条件：还未遇到错误且还在读取原数据\n\twhile (inOut && inOut.tellg() != end_mark && getline(inOut, line))\n\t{\n\t\tcnt += line.size() + 1;\t\t\t// +1表示换行符\n\t\tauto mark = inOut.tellg();\t\t// 记住读取位置\n\t\tinOut.seekp(0, fstream::end);\t// 将写标记移动到文件尾\n\t\tinOut << cnt;\t\t\t\t\t// 输出累计长度\n\n\t\t// 如果不是最后一行，打印一个分隔符\n\t\tif (mark != end_mark) inOut << \" \";\n\t\tinOut.seekg(mark);\t\t\t\t// 恢复读位置\n\t}\n\tinOut.seekp(0, fstream::end);\t\t// 定位到文件尾\n\tinOut << \"\\n\";\t\t\t\t\t\t// 在文件尾输出一个换行符\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/example_sregex_iterator.cpp",
    "content": "// example: 匹配与Regex迭代器类型（p650）\n\n#include <iostream>\n#include <string>\n#include <regex>\n\nusing namespace std;\n\n// 使用sregex_iterator\nvoid func1()\n{\n\tstring test_str = \"receipt freind theif receive\";\n\n\t// 查找前一个字符不是c的字符串ci\n\tstring pattern(\"[^c]ei\");\n\n\t// 我们想要包含pattern的单词的全部内容\n\tpattern = \"[[:alpha:]]*\" + pattern + \"[[:alpha:]]*\";\n\tregex r(pattern, regex::icase);\t// 在进行匹配时将忽略大小写\n\n\t// 它将反复调用regex_search来寻找文件中的所有匹配\n\tfor (sregex_iterator it(test_str.begin(), test_str.end(), r), end_it; it != end_it; ++it)\n\t{\n\t\tcout << it->str() << endl;\t// 匹配的单词\n\t}\n}\n\n// 使用匹配数据\nvoid func2()\n{\n\tstring test_str = \"receipt freind theif receive\";\n\n\t// 查找前一个字符不是c的字符串ci\n\tstring pattern(\"[^c]ei\");\n\n\t// 我们想要包含pattern的单词的全部内容\n\tpattern = \"[[:alpha:]]*\" + pattern + \"[[:alpha:]]*\";\n\tregex r(pattern, regex::icase);\t// 在进行匹配时将忽略大小写\n\n\tfor (sregex_iterator it(test_str.begin(), test_str.end(), r), end_it; it != end_it; ++it)\n\t{\n\t\tauto pos = it->prefix().length();\t// 前缀的大小\n\t\tpos = pos > 40 ? pos - 40 : 0;\t\t// 我们想要最多40个字符\n\t\tcout << it->prefix().str().substr(pos) // 前缀最后一部分\n\t\t     << \"\\n\\t\\t>>> \" << it->str() << \" <<<\\n\"\t// 匹配的单词\n\t\t\t << it->suffix().str().substr(0, 40)\t// 后缀的第一部分\n\t\t\t << endl;\n\t}\n}\n\nint main()\n{\n\t//func1();\n\tfunc2();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/example_tuple.cpp",
    "content": "// example: 使用tuple返回多个值（p638）\n\n#include <iostream>\n#include <fstream>\n#include <vector>\n#include <algorithm>\n#include <numeric>\n#include <tuple>\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\n// matches有三个成员：一家书店的索引和两个指向书店vector中元素的迭代器 \n\ntypedef tuple<vector<Sales_data>::size_type, \n              vector<Sales_data>::const_iterator,\n\t\t\t  vector<Sales_data>::const_iterator> matches;\n\nbool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)\n{\n\treturn lhs.isbn() < rhs.isbn();\n}\n\n// file保存每家书店的销售记录\n// findBook返回一个vector，每家销售了给定书籍的书店在其中都有一项\nvector<matches>\nfindBook(const vector<vector<Sales_data>> &files, const string &book)\n{\n\tvector<matches> res;\n\n\t// 对每家书店，查找与给定书籍匹配的记录范围（如果存在的话）\n\tfor (auto it = files.cbegin(); it != files.cend(); ++it) {\n\t\t// 查找具有相同ISBN的Sales_data范围（book隐式转换成一个Sales_data对象）\n\t\tauto found = equal_range(it->cbegin(), it->cend(), book, compareIsbn);\n\n\t\tif (found.first != found.second) // 此书店销售了给定书籍\n\t\t\t// 记住此书店的索引及匹配的范围\n\t\t\tres.push_back(make_tuple(it - files.cbegin(), found.first, found.second));\n\t}\n\n\treturn res;\n}\n\nvoid reportResult(istream &in, ostream &os, const vector<vector<Sales_data>> &files)\n{\n\tstring s; // 要找的书\n\twhile (in >> s) {\n\t\tauto trans = findBook(files, s);\t// 销售了这本书的书店\n\t\tif (trans.empty()) {\n\t\t\tcout << s << \" not found in any stores\" << endl;\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (const auto &store : trans)\t\t// 对每家销售了给定书籍的书店\n\t\t\t// get<n>返回store中tuple的指定的成员\n\t\t\tos << \"store \" << get<0>(store) << \" sales: \"\n\t\t\t   << accumulate(get<1>(store), get<2>(store), Sales_data(s))\n\t\t\t   << endl;\n\t}\n}\n\nint main()\n{\n\tifstream ifs(\"../data/book_sales\");\n\tif (!ifs)\n\t{\n\t\tcerr << \"open file failed\" << endl;\n\t\treturn -1;\n\t}\n\n\t// 就测试一个书店\n\tvector<vector<Sales_data>> files;\n\tfiles.resize(1);\n\n\t{\n\t\tSales_data tmp;\n\t\twhile (ifs >> tmp)\n\t\t{\n\t\t\tfiles[0].push_back(tmp);\n\t\t}\n\n\t\t// 给书店的销售记录排序\n\t\tsort(files[0].begin(), files[0].end(), compareIsbn);\n\t}\n\n\treportResult(cin, cout, files);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/example_unformatted_io.cpp",
    "content": "// example: 未格式化IO（p673）\n\n#include <iostream>\n\nusing namespace std;\n\n// 单字节操作\nvoid func1()\n{\n\tchar ch;\n\twhile (cin.get(ch))\n\t\tcout.put(ch);\n}\n\nint main()\n{\n\tfunc1();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/example_using_regex.cpp",
    "content": "// example: 使用正则表达式库（p646）\n\n#include <iostream>\n#include <regex>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\t// 查找不在字符c之后的字符串ei\n\tstring pattern(\"[^c]ei\");\n\n\t// 我们需要包含pattern的整个单词\n\tpattern = \"[[:alpha:]]*\" + pattern + \"[[:alpha:]]*\";\n\n\tregex r(pattern);\t// 构造一个用于查找模式的regex\n\tsmatch results;\t\t// 定义一个对象保存结果\n\n\t// 定义一个string保存与模式匹配和不匹配的文本\n\tstring test_str = \"receipt freind theif receive\";\n\n\t// 用r在test_str中查找与pattern匹配的子串\n\tif (regex_search(test_str, results, r))\t\t// 如果有匹配子串\n\t\tcout << results.str() << endl;\t\t\t// 打印匹配的单词\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/example_using_subexpression.cpp",
    "content": "// example: 使用子表达式（p653）\n\n#include <iostream>\n#include <regex>\n\nusing namespace std;\n\nint main()\n{\n\tstd::string filename(\"foo.cpp\");\n\n\t// r有两个子表达式：第一个是点之前表示文件名的部分，第二个表示文件扩展名\n\tregex r(\"([[:alnum:]]+)\\\\.(cpp|cxx|cc)$\", regex::icase);\n\n\tsmatch results;\n\n\tif (regex_search(filename, results, r))\n\t{\n\t\tcout << results.str(1) << endl;// 打印第一个子表达式\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_01.cpp",
    "content": "// 练习17.1：定义一个保存三个int值的tuple，并将其成员分别初始化为10、20和30。\n\n#include <iostream>\n#include <tuple>\n\nusing namespace std;\n\nint main()\n{\n\ttuple<int, int, int> t(10, 20, 30);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_02.cpp",
    "content": "// 练习17.2：定义一个tuple，保存一个string、一个vector<string>和一个\n// pair<string, int>。\n\n#include <iostream>\n#include <tuple>\n#include <string>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\ttuple<string, vector<string>, pair<string, int>> t;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_03.cpp",
    "content": "// 练习17.3：重写12.3节（第430页）中的TextQuery程序，使用tuple代替QueryResult类。\n// 你认为哪种设计更好？为什么？\n\n// 使用tuple可以降低代码量，但是对于扩展功能不友好。所以我更愿意使用QueryResult。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <fstream>\n#include <memory>\n#include <map>\n#include <set>\n#include <sstream>\n#include <tuple>\n\ntypedef std::tuple<std::string, std::shared_ptr<std::set<size_t>>, std::shared_ptr<std::vector<std::string>>> QueryResult;\n\nclass TextQuery {\npublic:\n\tusing line_no = std::vector<std::string>::size_type;\n\tTextQuery(std::ifstream&);\n\tQueryResult query(const std::string&) const;\n\nprivate:\n\tstd::shared_ptr<std::vector<std::string>> file; // 输入文件\n\t\n\t// 每个单词到它所在的行号的集合的映射\n\tstd::map<std::string, std::shared_ptr<std::set<line_no>>> wm;\n};\n\nTextQuery::TextQuery(std::ifstream &is): file(new std::vector<std::string>)\n{\n\tstd::string text;\n\twhile (getline(is, text)) {\t\t// 对文件中的每一行\n\t\tfile->push_back(text);\t\t// 保存此行文本\n\t\tint n = file->size() - 1;\t// 当前行号\n\t\tstd::istringstream line(text);\t// 将行文本分解为单词\n\t\tstd::string word;\n\t\twhile (line >> word) {\t\t// 对行中每个单词\n\t\t\t// 如果单词不在wm中，以之为下标在wm中添加一项\n\t\t\tauto &lines = wm[word];\t// lines是一个shared_ptr\n\t\t\tif (!lines)\t\t\t\t// 在我们第一次遇到这个单词时，此指针为空\n\t\t\t\tlines.reset(new std::set<line_no>);\t// 分配一个新的set\n\t\t\tlines->insert(n);\t\t// 将此行号插入set中\n\t\t}\n\t}\n}\n\nQueryResult\nTextQuery::query(const std::string &sought) const\n{\n\t// 如果未找到sought，我们将返回一个指向此set的指针\n\tstatic std::shared_ptr<std::set<line_no>> nodata(new std::set<line_no>);\n\n\t// 使用find而不是下标运算符来查找单词，避免将单词添加到wm中！\n\tauto loc = wm.find(sought);\n\tif (loc == wm.end())\n\t\treturn QueryResult(sought, nodata, file);\t// 未找到\n\telse\n\t\treturn QueryResult(sought, loc->second, file);\n}\n\nstd::ostream &print(std::ostream &os, const QueryResult &qr)\n{\n\t// 如果找到了单词，打印出现次数和所有出现的位置\n\tos << std::get<0>(qr) << \" occurs \" << std::get<1>(qr)->size() << \" time(s)\" << std::endl;\n\n\t// 打印单词出现的每一行\n\tfor (auto num : *std::get<1>(qr)) // 对set中每个单词\n\t\t// 避免行号从0开始给用户带来的困惑\n\t\tos << \"\\t(line \" << num + 1 << \") \"\n\t\t\t<< *(std::get<2>(qr)->begin() + num) << std::endl;\n\treturn os;\n}\n\nvoid runQueries(std::ifstream &infile)\n{\n\t// infile是一个ifstream，指向我们要处理的文件\n\tTextQuery tq(infile); // 保存文件并建立查询map\n\n\t// 与用户交互，提示用户输入要查询的单词，完成查询并打印结果\n\twhile (true)\n\t{\n\t\tstd::cout << \"Enter word to look for, or q to quit: \";\n\t\tstd::string s;\n\t\t// 若遇到了文件尾或用户输入了q时循环终止\n\t\tif (!(std::cin >> s) || s == \"q\") break;\n\n\t\t// 指向查询并打印结果\n\t\tprint(std::cout, tq.query(s)) << std::endl;\n\t}\n}\n\nint main()\n{\n\tstd::ifstream infile(\"../data/some_words.txt\");\n\tif (!infile)\n\t{\n\t\tstd::cout << \"Cant open file\" << std::endl;\n\t\treturn 1;\n\t}\n\n\trunQueries(infile);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_04.md",
    "content": "练习17.4：编写并测试你自己版本的findBook函数。\n\n---\n\n见[案例](./example_tuple.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_05.cpp",
    "content": "// 练习17.5：重写findBook，令其返回一个pair，包含一个索引和一个迭代器pair。\n\n#include <iostream>\n#include <fstream>\n#include <vector>\n#include <algorithm>\n#include <numeric>\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\n// matches有三个成员：一家书店的索引和两个指向书店vector中元素的迭代器 \n\ntypedef pair<vector<Sales_data>::size_type, \n              pair<vector<Sales_data>::const_iterator, vector<Sales_data>::const_iterator>>\n\t\t\t  matches;\n\nbool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)\n{\n\treturn lhs.isbn() < rhs.isbn();\n}\n\n// file保存每家书店的销售记录\n// findBook返回一个vector，每家销售了给定书籍的书店在其中都有一项\nvector<matches>\nfindBook(const vector<vector<Sales_data>> &files, const string &book)\n{\n\tvector<matches> res;\n\n\t// 对每家书店，查找与给定书籍匹配的记录范围（如果存在的话）\n\tfor (auto it = files.cbegin(); it != files.cend(); ++it) {\n\t\t// 查找具有相同ISBN的Sales_data范围（book隐式转换成一个Sales_data对象）\n\t\tauto found = equal_range(it->cbegin(), it->cend(), book, compareIsbn);\n\n\t\tif (found.first != found.second) // 此书店销售了给定书籍\n\t\t\t// 记住此书店的索引及匹配的范围\n\t\t\tres.push_back(make_pair(it - files.cbegin(), make_pair(found.first, found.second)));\n\t}\n\n\treturn res;\n}\n\nvoid reportResult(istream &in, ostream &os, const vector<vector<Sales_data>> &files)\n{\n\tstring s; // 要找的书\n\twhile (in >> s) {\n\t\tauto trans = findBook(files, s);\t// 销售了这本书的书店\n\t\tif (trans.empty()) {\n\t\t\tcout << s << \" not found in any stores\" << endl;\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (const auto &store : trans)\t\t// 对每家销售了给定书籍的书店\n\t\t\t// get<n>返回store中tuple的指定的成员\n\t\t\tos << \"store \" << store.first << \" sales: \"\n\t\t\t   << accumulate(store.second.first, store.second.second, Sales_data(s))\n\t\t\t   << endl;\n\t}\n}\n\nint main()\n{\n\tifstream ifs(\"../data/book_sales\");\n\tif (!ifs)\n\t{\n\t\tcerr << \"open file failed\" << endl;\n\t\treturn -1;\n\t}\n\n\t// 就测试一个书店\n\tvector<vector<Sales_data>> files;\n\tfiles.resize(1);\n\n\t{\n\t\tSales_data tmp;\n\t\twhile (ifs >> tmp)\n\t\t{\n\t\t\tfiles[0].push_back(tmp);\n\t\t}\n\n\t\tsort(files[0].begin(), files[0].end(), compareIsbn);\n\t}\n\n\treportResult(cin, cout, files);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_06.cpp",
    "content": "// 练习17.6：重写findBook, 不使用tuple或pair。\n\n#include <iostream>\n#include <fstream>\n#include <vector>\n#include <algorithm>\n#include <numeric>\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\n// matches有三个成员：一家书店的索引和两个指向书店vector中元素的迭代器 \n\nstruct matches\n{\n\tvector<Sales_data>::size_type index;\n\tvector<Sales_data>::const_iterator it_beg;\n\tvector<Sales_data>::const_iterator it_end;\n};\n\nbool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)\n{\n\treturn lhs.isbn() < rhs.isbn();\n}\n\n// file保存每家书店的销售记录\n// findBook返回一个vector，每家销售了给定书籍的书店在其中都有一项\nvector<matches>\nfindBook(const vector<vector<Sales_data>> &files, const string &book)\n{\n\tvector<matches> res;\n\n\t// 对每家书店，查找与给定书籍匹配的记录范围（如果存在的话）\n\tfor (auto it = files.cbegin(); it != files.cend(); ++it) {\n\t\t// 查找具有相同ISBN的Sales_data范围（book隐式转换成一个Sales_data对象）\n\t\tauto found = equal_range(it->cbegin(), it->cend(), book, compareIsbn);\n\n\t\tif (found.first != found.second) // 此书店销售了给定书籍\n\t\t\t// 记住此书店的索引及匹配的范围\n\t\t{\n\t\t\tauto index = (size_t)(it - files.cbegin()); // convert to size_t to ignore warning\n\t\t\tmatches m = {index, found.first, found.second};\n\t\t\tres.push_back(m);\n\t\t}\n\t}\n\n\treturn res;\n}\n\nvoid reportResult(istream &in, ostream &os, const vector<vector<Sales_data>> &files)\n{\n\tstring s; // 要找的书\n\twhile (in >> s) {\n\t\tauto trans = findBook(files, s);\t// 销售了这本书的书店\n\t\tif (trans.empty()) {\n\t\t\tcout << s << \" not found in any stores\" << endl;\n\t\t\tcontinue;\n\t\t}\n\n\t\tfor (const auto &store : trans)\t\t// 对每家销售了给定书籍的书店\n\t\t\t// get<n>返回store中tuple的指定的成员\n\t\t\tos << \"store \" << store.index << \" sales: \"\n\t\t\t   << accumulate(store.it_beg, store.it_end, Sales_data(s))\n\t\t\t   << endl;\n\t}\n}\n\nint main()\n{\n\tifstream ifs(\"../data/book_sales\");\n\tif (!ifs)\n\t{\n\t\tcerr << \"open file failed\" << endl;\n\t\treturn -1;\n\t}\n\n\t// 就测试一个书店\n\tvector<vector<Sales_data>> files;\n\tfiles.resize(1);\n\n\t{\n\t\tSales_data tmp;\n\t\twhile (ifs >> tmp)\n\t\t{\n\t\t\tfiles[0].push_back(tmp);\n\t\t}\n\n\t\tsort(files[0].begin(), files[0].end(), compareIsbn);\n\t}\n\n\treportResult(cin, cout, files);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_07.md",
    "content": "练习17.7：解释你更倾向于哪个版本的findBook，为什么。\n\n---\n\n使用struct版本的，也就是17.6练习中的，我觉得这样写更加清晰，容易扩展。\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_08.md",
    "content": "练习17.8：在本节最后一段代码中，如果我们将Sales_data()作为第三个参数传递给accumulate，会发生什么？\n\n---\n\n由于我的Sales_data的加法操作没有检查bookNo，因此会把bookNo从结果中抹除。\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_09.cpp",
    "content": "// 练习17.9：解释下列每个bitset对象所包含的位模式。\n\n#include <iostream>\n#include <bitset>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tbitset<64> bitvec(32); // 共64位，低位为100000，高位全为0\n\tcout << bitvec.to_string() << endl;\n\n\tbitset<32> bv(1010101);\t// 共32位，低位为11110110100110110101，高位全为0，用于初始化bv的参数应当看成是10进制数 \n\tcout << bv.to_string() << endl;\n\n\tstring bstr; cin >> bstr; bitset<8> bv2(bstr); // 共8位，根据bstr来初始化，总共可以使用8个字符，下标大的初始化低位，超出的被丢弃\n\tcout << bv2.to_string() << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_10.cpp",
    "content": "// 练习17.10：使用序列1、2、3、5、8、13、21初始化一个bitset，将这些位置置位。\n// 对另一个bitset进行默认初始化，并编写一小段程序将其恰当的位置位。\n\n#include <iostream>\n#include <bitset>\n#include <string>\n#include <set>\n#include <vector>\n\nusing namespace std;\n\nstatic const int MAX_BIT_CNT = 32;\n\nvoid func1()\n{\n\tstd::string str;\n\tstd::set<int> hit_bits{1, 2, 3, 5, 8, 13, 21};\n\tfor (int i = 0; i < MAX_BIT_CNT; ++i)\n\t{\n\t\tif (hit_bits.count(MAX_BIT_CNT - 1 - i) > 0) // 下标高的对应低位\n\t\t{\n\t\t\tstr += '1';\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstr += '0';\n\t\t}\n\t}\n\n\tbitset<MAX_BIT_CNT> bv(str);\n\n\tcout << bv.to_string() << endl;\n}\n\nvoid func2()\n{\n\tstd::vector<int> hit_bits{1, 2, 3, 5, 8, 13, 21};\n\tbitset<MAX_BIT_CNT> bv;\n\n\tfor (auto bit : hit_bits)\n\t{\n\t\tbv.set(bit);\n\t}\n\n\tcout << bv.to_string() << endl;\n}\n\nint main()\n{\n\t//func1();\n\tfunc2();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_11.cpp",
    "content": "// 练习17.11：定义一个数据结构，包含一个整型对象，记录一个包含10个问题的真假\n// 测验的解答。如果测验包含100道题，你需要对数据结构做出什么改变（如果需要的话）？\n\n// 直接修改bitset的二进制位的个数即可。或者把结构体定义成一个模板，模板参数是一个\n// 非类型参数，指出问题的个数，同时用于指出bitset的二进制位的个数。\n\n#include <iostream>\n#include <bitset>\n#include <string>\n\nusing namespace std;\n\nstruct QA\n{\n\tbitset<128> result_bitset;\n};\n\ntemplate <unsigned N>\nstruct QA2\n{\n\tQA2() {}\n\tQA2(const char *bits) : result_bitset(bits) {}\n\n\tbitset<N> result_bitset;\n};\n\nint main()\n{\n\tQA2<10> qa(\"0000011111\");\n\tcout << qa.result_bitset.to_string() << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_12.cpp",
    "content": "// 练习17.12：使用前一题中的数据结构，编写一个函数，它接受一个问题编号和一个表示\n// 真假解答的值，函数根据这两个参数更新测验的解答。\n\n#include <iostream>\n#include <bitset>\n#include <string>\n\nusing namespace std;\n\ntemplate <unsigned N>\nstruct QA\n{\n\tQA() {}\n\tQA(const char *bits) : result_bitset(bits) {}\n\n\tvoid Answer(unsigned i, bool is_right);\n\n\tbitset<N> result_bitset;\n};\n\ntemplate <unsigned N> inline\nvoid QA<N>::Answer(unsigned i, bool is_right)\n{\n\tif (i >= 0 && i < N)\n\t\tresult_bitset[i] = is_right;\n}\n\nint main()\n{\n\tQA<10> qa;\n\tqa.Answer(0, true);\n\n\tcout << qa.result_bitset.to_string() << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_13.cpp",
    "content": "// 练习17.13：编写一个整型对象，包含真假测验的正确答案。使用它来为前两题中的\n// 数据结构生成测验成绩。\n\n#include <iostream>\n#include <bitset>\n#include <string>\n\nusing namespace std;\n\ntemplate <unsigned N>\nstruct QA\n{\n\tQA() {}\n\tQA(const char *bits) : result_bitset(bits) {}\n\n\tvoid Answer(unsigned i, bool is_right);\n\n\tbitset<N> result_bitset;\n};\n\ntemplate <unsigned N> inline\nvoid QA<N>::Answer(unsigned i, bool is_right)\n{\n\tif (i >= 0 && i < N)\n\t\tresult_bitset[i] = is_right;\n}\n\nint main()\n{\n\tconstexpr int Q_COUNT = 10;\n\n\tQA<Q_COUNT> qa(\"1111100000\");\n\n\t// 假定一题10分\n\tint score = 0;\n\n\tbitset<Q_COUNT> real_answer(0xFFFFFFFF);\t// 这个整型对象就是0xFFFFFFFF，多余的位被舍弃\n\n\tfor (int i = 0; i < Q_COUNT; ++i)\n\t{\n\t\tif (qa.result_bitset[i] == real_answer[i])\n\t\t{\n\t\t\tscore += 10;\n\t\t}\n\t}\n\n\tcout << \"got score: \" << score << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_14.cpp",
    "content": "// 练习17.14：编写几个正则表达式，分别触发不同错误。运行你的程序，观察编译器\n// 对每个错误的输出。\n\n#include <iostream>\n#include <regex>\n\nusing namespace std;\n\nvoid error1()\n{\n\t// 运行时错误，错误的正则表达式，构造时抛出异常\n\tregex r(\"[[:alnum:]\");\n}\n\nvoid error2()\n{\n\tregex r(\"cpp\");\n\t//smatch results;\t// 输入序列与match参数类型不匹配，编译报错\n\tcmatch results;\t\t// OK\n\t\n\tif (regex_search(\"cc\", results, r))\n\t{\n\t}\n}\n\nint main()\n{\n\terror1();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_15.cpp",
    "content": "// 练习17.15：编写程序，使用模式查找违反“i在e之前，除非在c之后”规则的单词。\n// 你的程序应该提示用户输入一个单词，然后指出此单词是否符合要求。用一些违反\n// 和未违反的单词测试你的程序。\n\n#include <iostream>\n#include <regex>\n#include <string>\n\nusing namespace std;\n\nbool InValid(const string &word)\n{\n\tstring pattern(\"[^c]ei\");\n\tpattern = \"[[:alpha:]]*\" + pattern + \"[[:alpha:]]*\";\n\n\t// 练习17.16\n\t//regex r(\"[[:alpha:]]*[^c]ei[[:alpha:]]*\");\n\t//regex r(\"[^c]ei\");\n\t\n\tconst static regex r(pattern);\n\tsmatch results;\n\n\treturn regex_search(word, results, r);\n}\n\nint main()\n{\n\tstring word;\n\tdo\n\t{\n\t\tcout << \"enter a word to test: \";\n\t\tif (!(cin >> word))\n\t\t{\n\t\t\tbreak;\n\t\t}\n\n\t\tif (InValid(word))\n\t\t{\n\t\t\tcout << \"InValid word\" << endl;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcout << \"Valid word\" << endl;\n\t\t}\n\n\t} while (true);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_16.md",
    "content": "练习17.16：如果前一题程序中的regex对象用“[^c]ei”进行初始化，将会发生什么？用此模式测试你的程序，检查你的答案是否正确。\n\n---\n\n如果题目意思是，用字符串替换string，那么一切正常。\n\n如果是用`\"[^cei]\"`替换匹配模式，那么这就不是一个正确的匹配模式，因为两边可以有数字，这就是一个非法的单词了，但输出合法。\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_17.md",
    "content": "练习17.17：更新你的程序，令它查找输入序列中所有违反“ei”语法规则的单词。\n\n---\n\n见[案例](./example_sregex_iterator.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_18.cpp",
    "content": "// 练习17.18：修改你的程序，忽略包含“ei”但并非拼写错误的单词，如“albeit”和\n// \"neighbor\"\n\n// my note：如何才能判断并非拼写错误呢？我尚不知，为了方便起见，就用一个set保存这样的单词了。\n\n#include <iostream>\n#include <regex>\n#include <set>\n#include <string>\n\nusing namespace std;\n\nset<string> valid_string_set{\"albeit\", \"neighbor\"};\n\nint main()\n{\n\tstring pattern(\"[^c]ei\");\n\n\t// 我们需要包含pattern的整个单词\n\tpattern = \"[[:alpha:]]*\" + pattern + \"[[:alpha:]]*\";\n\n\tregex r(pattern);\t// 构造一个用于查找模式的regex\n\tsmatch results;\t\t// 定义一个对象保存结果\n\n\t// 定义一个string保存与模式匹配和不匹配的文本\n\tstring test_str = \"receipt freind theif receive albeit neighbor\";\n\n\tfor (sregex_iterator it(test_str.begin(), test_str.end(), r), it_end; it != it_end; ++it)\n\t{\n\t\tif (valid_string_set.count(it->str()) == 0)\n\t\t\tcout << it->str() << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_19.md",
    "content": "练习17.19：为什么可以不先检查m[4]是否匹配了就直接调用m[4].str()？\n\n---\n\n因为这只是为了检查第4和第6个子表达式是否一致而已，即要么都匹配且内容一样，要么都不匹配。如果都不匹配，str()就会返回空字符串，那么两者也会一样。使用表达式`m[4].str() == m[6].str()`来判断这种情况是合理的。\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_20.md",
    "content": "练习17.20：编写你自己版本的验证电话号码的程序。\n\n---\n\n见[案例](./example_phone_number.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_21.cpp",
    "content": "// 练习17.21：使用本节中定义的valid函数重写8.3.2节（第289页）中的电话号码程序。\n\n// ./exercise_17_21 < ../data/person_numbers.txt\n\n#include <iostream>\n#include <string>\n#include <vector>\n#include <sstream>\n#include <regex>\n\nusing namespace std;\n\nbool valid(const smatch &m)\n{\n\t// 在一个合法的电话号码中，区号要么是完整括号包围的，要么完全没有括号。\n\t// 如果区号前有一个左括号\n\tif (m[1].matched)\n\t{\n\t\t// 则区号后必须有一个右括号，之后紧跟剩余号码或一个空格\n\t\treturn m[3].matched && (m[4].matched == 0 || m[4].str() == \" \");\n\t}\n\telse\n\t{\n\t\t// 否则，区号后不能有右括号\n\t\t// 另外两个组成部分间的分隔符必须匹配\n\t\treturn !m[3].matched && m[4].str() == m[6].str();\n\t}\n\n\treturn false;\n}\n\n\n// 检查电话号码是否合法\nbool CheckPhoneNumber(const string &phone_number)\n{\n\tstring phone = \"(\\\\()?(\\\\d{3})(\\\\))?([-. ])?(\\\\d{3})([-. ])?(\\\\d{4})\";\n\n\t/*\n\t * e.g. (555)1234567\n\t *      ^^  ^^  ^\n\t *      12  35  7\n\t *\n\t * 1. (\\\\()? 表示区号部分可选的左括号\n\t * 2. (\\\\d{3}) 表示区号\n\t * 3. (\\\\))? 表示区号部分可选的右括号\n\t * 4. ([-. ])? 表示区号部分可选的分隔符\n\t * 5. (\\\\d{3}) 表示号码的下三位数字\n\t * 6. ([-. ])? 表示可选的分隔符\n\t * 7. (\\\\d{4}) 表示号码的最后四位数字\n\t */\n\n\tregex r(phone);\t// regex对象，用于查找我们的模式\n\tsmatch m;\n\n\t// 对每个匹配的电话号码\n\tfor (sregex_iterator it(phone_number.begin(), phone_number.end(), r), end_it; it != end_it; ++it)\n\t{\n\t\t// 检查号码格式是否合法\n\t\treturn valid(*it);\n\t}\n\n\treturn false;\n}\n\n\n// 保存一个人的电话号码\nstruct PersonInfo {\n\tstring name;\n\tvector<string> phones;\n};\n\n// 输出记录\nvoid print(const PersonInfo &info)\n{\n\tcout << info.name << \" \";\n\tfor (const auto &number : info.phones)\n\t\tcout << number << \"(\" << (CheckPhoneNumber(number) ? \"valid\" : \"invalid\") << \")\" << \" \";\n\n\tcout << endl;\n}\n\nint main()\n{\n\tstring line, word;\t\t\t// 分别保存输入的一行和的单词\n\tvector<PersonInfo> people;\t// 保存来自输入的所有记录\n\n\t// 逐行从输入读取数据，直至cin遇到文件尾（或其他错误）\n\twhile (getline(cin, line)) {\n\t\tPersonInfo info;\t\t// 创建一个保存此记录的对象\n\t\tistringstream record(line);\t// 将记录绑定到刚读入的行\n\t\trecord >> info.name;\t// 读取名字\n\t\twhile (record >> word)\t// 读取电话号码\n\t\t\tinfo.phones.push_back(word);\t// 保存它们\n\t\tpeople.push_back(info);\t// 将此记录追加到people末尾\n\t}\n\n\t// 输出验证\n\tfor (const auto &person_info : people)\n\t\tprint(person_info);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_22.cpp",
    "content": "// 练习17.22：重写你的电话号码程序，使之允许在号码的三个部分之间放置任意多个空白符。\n\n// \\s* 表示零个或多个空格，只需要修改匹配模式（即phone）即可。\n\n#include <iostream>\n#include <regex>\n#include <string>\n\nusing namespace std;\n\nbool valid(const smatch &m)\n{\n\t// 在一个合法的电话号码中，区号要么是完整括号包围的，要么完全没有括号。\n\t// 如果区号前有一个左括号\n\tif (m[1].matched)\n\t{\n\t\t// 则区号后必须有一个右括号，之后紧跟剩余号码或一个空格\n\t\treturn m[3].matched && (m[4].matched == 0 || m[4].str() == \" \");\n\t}\n\telse\n\t{\n\t\t// 否则，区号后不能有右括号\n\t\t// 另外两个组成部分间的分隔符必须匹配\n\t\treturn !m[3].matched && m[4].str() == m[6].str();\n\t}\n\n\treturn false;\n}\n\nint main()\n{\n\tstring phone = \"(\\\\()?(\\\\d{3})(\\\\))?\\\\s*([-. ])?(\\\\d{3})\\\\s*([-. ])?(\\\\d{4})\\\\s*\";\n\n\t/*\n\t * e.g. (555)1234567\n\t *      ^^  ^^  ^\n\t *      12  35  7\n\t *\n\t * 1. (\\\\()? 表示区号部分可选的左括号\n\t * 2. (\\\\d{3}) 表示区号\n\t * 3. (\\\\))? 表示区号部分可选的右括号\n\t * 4. ([-. ])? 表示区号部分可选的分隔符\n\t * 5. (\\\\d{3}) 表示号码的下三位数字\n\t * 6. ([-. ])? 表示可选的分隔符\n\t * 7. (\\\\d{4}) 表示号码的最后四位数字\n\t */\n\n\tregex r(phone);\t// regex对象，用于查找我们的模式\n\tsmatch m;\n\tstring s;\n\n\t// 从输入文件中读取每条记录\n\twhile (getline(cin, s))\n\t{\n\t\tbool matched = false;\n\n\t\t// 对每个匹配的电话号码\n\t\tfor (sregex_iterator it(s.begin(), s.end(), r), end_it; it != end_it; ++it)\n\t\t{\n\t\t\t// 检查号码格式是否合法\n\t\t\tif (valid(*it))\n\t\t\t\tcout << \"valid: \" << it->str() << endl;\n\t\t\telse\n\t\t\t\tcout << \"not valid: \" << it->str() << endl;\n\n\t\t\tmatched = true;\n\t\t}\n\n\t\tif (!matched)\n\t\t{\n\t\t\tcerr << \"your input can not match phone number!\" << endl;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_23.cpp",
    "content": "// 练习17.23：编写查找邮政编码的正则表达式。一个美国邮政编码可以由五位或九位数\n// 字组成。前五位数字和后四位数字之间可以用一个短横线分隔。\n\n#include <iostream>\n#include <regex>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tstring pattern = \"[0-9]{5}(-[0-9]{4})?\"; // 参考；https://www.ibm.com/developerworks/cn/java/j-validating/\n\tregex r(pattern);\n\n\tcout << \"input number: \" << endl;\n\n\tstring number;\n\tif (cin >> number)\n\t{\n\t\tfor (sregex_iterator it(number.begin(), number.end(), r), end_it; it != end_it; ++it)\n\t\t{\n\t\t\tcout << it->str() << endl;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_24.md",
    "content": "练习17.24：编写你自己版本的重排电话号码格式的程序。\n\n---\n\n见[案例](./example_regex_replace.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_25.cpp",
    "content": "// 练习17.25：重写你的电话号码程序，使之只输出每个人的第一个电话号码。\n\n// ./exercise_17_25 < ../data/person_numbers.txt\n\n#include <iostream>\n#include <regex>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tstring phone = \"(\\\\()?(\\\\d{3})(\\\\))?([-. ])?(\\\\d{3})([-. ])?(\\\\d{4})\";\n\tregex r(phone);\t// 寻找模式所用的regex对象\n\n\tsmatch result;\n\tstring s;\n\n\twhile (getline(cin, s))\n\t{\n\t\tif (regex_search(s, result, r))\n\t\t{\n\t\t\tcout << result.str() << endl;\n\t\t}\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_26.cpp",
    "content": "// 练习17.26：重写你的电话号码程序，使之对多于一个电话号码的人只输出第二个和后续电话号码。\n\n// ./exercise_17_26 < ../data/person_numbers.txt\n\n#include <iostream>\n#include <regex>\n#include <string>\n#include <vector>\n\nusing namespace std;\n\nint main()\n{\n\tstring phone = \"(\\\\()?(\\\\d{3})(\\\\))?([-. ])?(\\\\d{3})([-. ])?(\\\\d{4})\";\n\tregex r(phone);\t// 寻找模式所用的regex对象\n\n\tsmatch result;\n\tstring s;\n\n\tvector<vector<string>> person_numbers;\n\n\twhile (getline(cin, s))\n\t{\n\t\tvector<string> numbers;\n\n\t\tfor (sregex_iterator it(s.begin(), s.end(), r), end_it; it != end_it; ++it)\n\t\t{\n\t\t\tnumbers.push_back(it->str()); \n\t\t}\n\n\t\tperson_numbers.push_back(numbers);\n\t}\n\n\tfor (const auto &numbers : person_numbers)\n\t{\n\t\tif (numbers.empty()) continue;\n\n\t\tif (numbers.size() > 1)\n\t\t{\n\t\t\tfor (size_t i = 1; i < numbers.size(); ++i)\n\t\t\t{\n\t\t\t\tcout << numbers[i] << \" \";\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tcout << numbers[0]; \n\t\t}\n\n\t\tcout << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_27.cpp",
    "content": "// 练习17.27：编写程序，将九位数字邮政编码的格式转换为ddddd-dddd。\n\n#include <iostream>\n#include <regex>\n#include <string>\n\nusing namespace std;\n\nint main()\n{\n\tstring pattern = \"([0-9]{5})-([0-9]{4})?\"; // 参考；https://www.ibm.com/developerworks/cn/java/j-validating/\n\tregex r(pattern);\t// 寻找模式所用的regex对象\n\n\tstring s;\n\tstring fmt = \"$1-$2\";\t\n\n\tcout << \"input number: \";\n\tcin >> s;\n\tcout << regex_replace(s, r, fmt) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_28.cpp",
    "content": "// 练习17.28：编写函数，每次调用生成并返回一个均匀分布的随机unsigned int。\n\n#include <iostream>\n#include <random>\n\nusing namespace std;\n\nunsigned int Random()\n{\n\t//static uniform_int_distribution<unsigned> u(0, 9);\n\tstatic default_random_engine e;\n\n\treturn e();\n}\n\nint main()\n{\n\tcout << Random() << endl;\n\tcout << Random() << endl;\n\tcout << Random() << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_29.cpp",
    "content": "// 练习17.29：修改上一题中编写的函数，允许用户提供一个种子作为可选参数。\n\n#include <iostream>\n#include <random>\n\nusing namespace std;\n\nunsigned int Random(int seed)\n{\n\t//static uniform_int_distribution<unsigned> u(0, 9);\n\tstatic default_random_engine e;\n\te.seed(seed);\n\n\treturn e();\n}\n\nint main()\n{\n\tcout << Random(1) << endl;\n\tcout << Random(2) << endl;\n\tcout << Random(3) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_30.cpp",
    "content": "// 练习17.30：再次修改你的程序，此次再增加两个参数，表示函数允许返回的最小值和\n// 最大值。\n\n#include <iostream>\n#include <random>\n\nusing namespace std;\n\nunsigned int Random(int seed, std::pair<int, int> range)\n{\n\tstatic uniform_int_distribution<unsigned> u(range.first, range.second);\n\tstatic default_random_engine e(seed);\n\n\treturn u(e);\n}\n\nint main()\n{\n\tcout << Random(1, {1, 10}) << endl;\n\tcout << Random(1, {1, 10}) << endl;\n\tcout << Random(1, {1, 10}) << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_31.md",
    "content": "练习17.31：对于本节中的游戏程序，如果在do循环内定义b和e，会发生什么？\n\n---\n\n每次循环都会创建一个新引擎，从而每步循环都会生成相同的值。分布对象也要保持状态，也应该在循环外定义。\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_32.md",
    "content": "练习17.32：如果我们在循环内定义resp，会发生什么？\n\n---\n\n不会发生什么，至多是每次循环重新创建一个string对象，不影响游戏进行。\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_33.cpp",
    "content": "// 练习17.33：修改11.3.6节（第392页）中的单词转换程序，允许对一个给定单词有多种\n// 转换方式，每次随机选择一种进行实际转换。\n\n#include <iostream>\n#include <fstream>\n#include <map>\n#include <string>\n#include <stdexcept>\n#include <sstream>\n#include <vector>\n#include <cstring>\n#include <random>\n\nusing namespace std;\n\n// 格式：k okay?:ok?\n// 即用符号:进行分隔\n\nvoid ParseParam(const char *str, std::vector<std::string> &result, const char *delim)\n{\n\tchar *temp = (char*)malloc(strlen(str) + 1);\n\t\n\tstrncpy(temp, str, strlen(str) + 1);\n\tresult.clear();\n\n\tchar *token = strtok(temp, delim);\n\twhile (token)\n\t{\n\t\tresult.push_back(token);\n\t\ttoken = strtok(NULL, delim);\n\t}\n\n\tfree(temp);\n}\n\nmap<string, std::vector<string>> buildMap(ifstream &map_file)\n{\n\tmap<string, std::vector<string>> trans_map;\t// 保存转换规则\n\tstring key;\t\t// 要转换的单词\n\tstring value;\t// 替换后的内容\n\n\t// 读取第一个单词存入key中，行中剩余内容存入value\n\twhile (map_file >> key && getline(map_file, value))\n\t{\n\t\tchar buffer[1024] = {0};\n\t\tstrncpy(buffer, value.substr(1).c_str(), sizeof(buffer)); // substr(1)用于跳过前导空格\n\t\tstd::vector<std::string> result;\n\n\t\tParseParam(buffer, result, \":\");\n\t\tif (result.empty())\n\t\t{\n\t\t\tthrow runtime_error(\"no rule for \" + key);\n\t\t}\n\n\t\ttrans_map[key] = result;\n\t}\n\n\treturn trans_map;\n}\n\n// 获取替换文本序列中的一个文本\nconst string& Random(const std::vector<std::string> &trans_word)\n{\n\tif (trans_word.empty())\n\t{\n\t\tthrow runtime_error(\"trans_word is empty\");\n\t}\n\n\tstatic default_random_engine e(time(0));\n\n\treturn trans_word[e() % trans_word.size()];\n}\n\nconst string& transform(const string &s, const map<string, std::vector<string>> &m)\n{\n\t// 实际的转换工作；此部分是程序的核心\n\tauto map_it = m.find(s);\n\n\t// 如果单词在转换规则中map中\n\tif (map_it != m.cend())\n\t\treturn Random(map_it->second);\t// 使用替换短语\n\telse\n\t\treturn s;\t\t\t\t// 否则返回原string\n}\n\nvoid word_transform(ifstream &map_file, ifstream &input)\n{\n\tauto trans_map = buildMap(map_file);\t// 保存转换规则\n\tstring text;\t\t\t\t\t\t\t// 保存输入中的每一行\n\twhile (getline(input, text)) {\t\t\t// 读取一行输入\n\t\tistringstream stream(text);\t\t\t// 读取每个单词\n\t\tstring word;\n\t\tbool firstword = true;\t\t\t\t// 控制是否打印空格\n\t\twhile (stream >> word) {\n\t\t\tif (firstword)\n\t\t\t\tfirstword = false;\n\t\t\telse\n\t\t\t\tcout << \" \";\t\t\t\t// 在单词间打印一个空格\n\t\t\t// transform返回它的第一个参数或其转换之后的形式\n\t\t\tcout << transform(word, trans_map); // 打印输出\n\t\t}\n\t\tcout << endl;\t\t\t\t\t\t// 完成一行的转换\n\t}\n}\n\nint main()\n{\n\tifstream rule_ifs(\"data/trans_rules.txt\");\n\tifstream input_ifs(\"data/test_rules.txt\");\n\tif (!rule_ifs || !input_ifs)\n\t{\n\t\tcerr << \"fail open fail\" << endl;\n\t\treturn 1;\n\t}\n\n\tword_transform(rule_ifs, input_ifs);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_34.md",
    "content": "练习17.34：编写一个程序，展示如何使用表17.17和表17.18中的每个操纵符。\n\n---\n\n见[案例](./example_formatted_io.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_35.cpp",
    "content": "// 练习17.35：修改第670页中的程序，打印2的平方根，但这次打印十六进制数字的大写形式。\n\n#include <iostream>\n#include <cmath>\n#include <iomanip>\n\nusing namespace std;\n\nvoid func()\n{\n\t// PS: 使用我当前的编译器(4.8.5)无法正确编译，采用devtoolset-4的编译器又无法正常运行\n\t// 采用最简单的方法，直接去在线编译：http://coliru.stacked-crooked.com/\n\tcout << hexfloat << uppercase << sqrt(2.0) << endl;\n}\n\nint main()\n{\n\tfunc();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_36.cpp",
    "content": "// 练习17.36：修改上一题中的程序，打印不同的浮点数，使它们排成一列。\n\n// 练习17.35：修改第670页中的程序，打印2的平方根，但这次打印十六进制数字的大写形式。\n\n#include <iostream>\n#include <cmath>\n#include <iomanip>\n\nusing namespace std;\n\nvoid func(float v)\n{\n\t// PS: 使用我当前的编译器(4.8.5)无法正确编译，采用devtoolset-4的编译器又无法正常运行\n\t// 采用最简单的方法，直接去在线编译：http://coliru.stacked-crooked.com/\n\tcout << hexfloat << uppercase << v << endl;\n}\n\nint main()\n{\n\tfunc(3.14);\n\tfunc(sqrt(2));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_37.cpp",
    "content": "// 练习17.37：用未格式化版本的getline逐行读取一个文件。测试你的程序，给它一个\n// 文件，既包含空行又包含长度超过你传递给getline的字符数组大小的行。\n\n#include <iostream>\n#include <fstream>\n\nusing namespace std;\n\nint main()\n{\n\tifstream ifs(\"../data/little_story.txt\");\n\tif (!ifs)\n\t{\n\t\tcerr << \"open file failed\" << endl;\n\t\treturn -1;\n\t}\n\n\tchar buffer[40] = {0};\n\twhile (ifs.getline(buffer, sizeof(buffer), '\\n')) // 如果buffer长度不够，并还未遇到分隔符，那么流会处于failbit状态\n\t{\n\t\tcout << buffer << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_38.cpp",
    "content": "// 练习17.38：扩展上一题中你的程序，将读入的每个单词打印到它所在的行。\n\n#include <iostream>\n#include <iomanip>\n#include <fstream>\n#include <map>\n#include <set>\n#include <sstream>\n\nusing namespace std;\n\nint main()\n{\n\tifstream ifs(\"../data/some_words.txt\");\n\tif (!ifs)\n\t{\n\t\tcerr << \"open file failed\" << endl;\n\t\treturn -1;\n\t}\n\n\tmap<string, set<size_t>> word_to_lines_map;\n\n\tsize_t line = 1;\n\tchar buffer[128] = {0};\n\twhile (ifs.getline(buffer, sizeof(buffer), '\\n')) // 如果buffer长度不够，并还未遇到分隔符，那么流会处于failbit状态\n\t{\n\t\tistringstream iss(buffer);\n\t\tstring word;\n\n\t\twhile (iss >> word)\n\t\t\tword_to_lines_map[word].insert(line);\n\n\t\t++line;\n\t}\n\n\tfor (const auto &p : word_to_lines_map)\n\t{\n\t\tcout << setw(6) << p.first << \": \" << setw(1);\n\t\tfor (const auto &l : p.second)\n\t\t{\n\t\t\tcout << l << \" \";\n\t\t}\n\t\tcout << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch17_Specialized_Library_Facilities/exercise_17_39.md",
    "content": "练习17.39：对本节给出的seek程序，编写你自己的版本。\n\n---\n\n见[案例](./example_seek_tell.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"exercise_18_29.cpp exercise_18_30.cpp\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\trm -f out1 out2\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_01.cpp",
    "content": "// 练习18.1：在下列throw语句中异常对象的类型是什么？如果将（b）中的throw语句写成了throw p将发生什么情况？\n\n// 抛出的异常类型是表达式的静态类型。因此a是range_error，b是exception\n\n// 抛出一个局部对象的指针，是错误的行为。见func_bad。\n\n#include <iostream>\n#include <stdexcept>\n\nusing namespace std;\n\nvoid func_a()\n{\n\trange_error r(\"error\");\n\tthrow r;\n}\n\nvoid func_b()\n{\n\trange_error r(\"error\");\n\texception *p = &r;\n\tthrow *p;\n}\n\nvoid func_bad()\n{\n\trange_error r(\"error\");\n\texception *p = &r;\n\tthrow p; \n}\n\nint main()\n{\n\t//func_a();\n\t//func_b();\n\t\n\ttry {\n\t\tfunc_bad();\n\t} catch (exception *p)\n\t{\n\t\tcerr << p->what() << endl; // Segmentation fault (core dumped)\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_02.md",
    "content": "练习18.2：当在指定的位置发生了异常时将出现什么情况？\n\n```c++\nvoid exercise(int *b, int *e)\n{\n\tvector<int> v(b, e);\n\tint *p = new int[v.size()];\n\tifstream in(\"ints\");\n\t// 此处发生异常\n}\n```\n\n---\n\np所指的内存发生泄漏。\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_03.cpp",
    "content": "// 练习18.3：要想让上面的代码在发生异常时能正常工作，有两种解决方案。请描述这两种\n// 方法并实现它们。\n\n#include <iostream>\n#include <stdexcept>\n#include <vector>\n#include <fstream>\n#include <memory>\n\nusing namespace std;\n\nvoid exercise1(int *b, int *e)\n{\n\tclass IntList\n\t{\n\tpublic:\n\t\tIntList(size_t sz) : lst(new int[sz]()) {}\n\t\t~IntList() { cout << \"~IntList()\" << endl; delete [] lst; }\n\n\tprivate:\n\t\tint *lst = nullptr;\n\t};\n\n\tvector<int> v(b, e);\n\tIntList il(v.size()); // 改成用类来管理\n\tifstream in(\"ints\");\n\n\tthrow runtime_error(\"test exception\");\n}\n\nvoid exercise2(int *b, int *e)\n{\n\tvector<int> v(b, e);\n\tunique_ptr<int[]> up(new int[v.size()]()); // 使用智能指针管理\n\tifstream in(\"ints\");\n\n\tthrow runtime_error(\"test exception\");\n}\n\nint main()\n{\n\tint arr[] = {1, 2, 3};\n\texercise1(begin(arr), end(arr));\n\t//exercise2(begin(arr), end(arr));\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_04.md",
    "content": "练习18.4：查看图18.1（第693页）所示的继承体系，说明下面的try块有何错误并修改它。\n\n```c++\ntry {\n\t// 使用C++标准库\n} catch(exception) {\n\t// ...\n} catch (const runtime_error &re) {\n\t// ...\n} catch (overflow_error eobj) { /* ... */ }\n```\n\n---\n\n在这些异常类型中，exception是继承链最顶层的类型，所以它可以匹配所有的异常类型，它会首先被捕获，后续的异常类型就没有机会匹配了。因此我们应该把继承链最底端的类放在前面，而最顶端的类放在后面。\n\n改成下面这样：\n\n```c++\ntry {\n\t// 使用C++标准库\n} catch(overflow_error eobj) {\n\t// ...\n} catch (const runtime_error &re) {\n\t// ...\n} catch (exception) { /* ... */ }\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_05.cpp",
    "content": "// 练习18.5：修改下面的main函数，使其能捕获图18.1（第693页）所示的任何异常类型：\n// int main() { // 使用C++标准库 }\n// 处理代码应该首先打印异常相关的错误信息，然后调用abort（定义在cstdlib头文件中）\n// 终止main函数。\n\n#include <iostream>\n#include <vector>\n#include <stdexcept>\n#include <bitset>\n#include <string>\n\nusing namespace std;\n\nvoid func_overflow_error()\n{\n\tstring str(128,'1');\n\tbitset<128> bs(str);\n\tbs.to_ulong();\n}\n\nvoid func_runtime_error()\n{\n\tthrow runtime_error(\"oop!!\");\n}\n\nvoid func_exception()\n{\n\tvector<int> vec;\n\tvec.at(0);\n}\n\nint main()\n{\n\ttry {\n\t\t// 使用C++标准库\n\t\t//func_overflow_error();\n\t\tfunc_runtime_error();\n\t\tfunc_exception();\n\n\t} catch(overflow_error eobj) {\n\t\tcout << \"overflow_error: \" << eobj.what() << endl;\n\t\tabort();\n\t} catch (const runtime_error &re) {\n\t\tcout << \"runtime_error: \" << re.what() << endl;\n\t\tabort();\n\t} catch (exception) {\n\t\tcout << \"catch exception\" << endl;\n\t\tabort();\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_06.cpp",
    "content": "// 练习18.6：已知下面的异常类型和catch语句，书写一个throw表达式使其创建的\n// 异常对象能被这些catch语句捕获。\n\n#include <iostream>\n#include <stdexcept>\n\nusing namespace std;\n\nclass exceptionType {};\ntypedef int EXCPTYPE;\n\nint main()\n{\n\ttry {\n\t\t//static exceptionType pet;\n\t\t//throw &pet;\n\n\t\t//throw 42;\n\n\t\tthrow runtime_error(\"oops!!\");\n\n\t} catch (exceptionType *pet) {\n\t\tcout << \"catch exceptionType\" << endl;\n\t} catch (EXCPTYPE) {\n\t\tcout << \"catch EXCPTYPE\" << endl;\n\t} catch (...) {\n\t\tcout << \"catch other exception\" << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_07.md",
    "content": "练习18.7：根据第16章的介绍定义你自己的Blob和BlobPtr，注意将构造函数写成函数try语句块。\n\n---\n\n见[Blob类定义](../ch16_Templates_and_GenericProgramming/example_template_class/Blob.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_08.md",
    "content": "练习18.8：回顾你之前编写的各个类，为它们的构造函数和析构函数添加正确的异常说明。如果你认为某个析构函数可能抛出异常，尝试修改代码使得该析构函数不会抛出异常。\n\n---\n\n略。析构函数不应该抛出异常。\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_09.md",
    "content": "练习18.9：定义本节描述的书店程序异常类，然后为Sales_data类重新编写一个复合赋值运算符并令其抛出一个异常。\n\n---\n\n见[案例](../ch07_Classes/example_Sales_data)\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_10.md",
    "content": "练习18.10：编写程序令其对两个ISBN编号不相同的对象执行Sales_data的加法运算。为该程序编写两个不同的版本：一个处理异常，另一个不处理异常。观察并比较这两个程序的行为，用心体会当出现了一个未被捕获的异常时程序会发生什么情况。\n\n---\n\n编写略。如果未捕获异常，那么显然会把两个isbn不相同的Sales_data相加，导致逻辑上的错误结果。\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_11.md",
    "content": "练习18.11：为什么what函数不应该抛出异常？\n\n---\n\n如果what抛出异常，那么在异常处理代码里就不应该调用what了，而这和what函数的初衷相违背。\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_12.md",
    "content": "练习18.12：将你之前各章练习编写的程序放置在各自的命名空间中。也就是说，命名空间chapter15包含Query程序的代码，命名空间chapter10包含TextQuery的代码；使用这种结构重新编译Query代码示例。\n\n---\n\n略。\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_13.md",
    "content": "练习18.13：什么时候应该使用未命名的命名空间？\n\n---\n\n当需要使用静态声明时，可以采用在未命名的命名空间中定义。\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_14.cpp",
    "content": "// 练习18.14：假设下面的operator*声明的是嵌套的命名空间mathLib::MatrixLib的一个成员，\n// 请问你应该如何在全局作用域中声明该运算符？\n\n#include <iostream>\n\nnamespace mathLib {\n\tnamespace MatrixLib {\n\t\tclass matrix { /* ... */ };\n\t\tmatrix operator* (const matrix &, const matrix &);\n\t}\n}\n\n// 声明\nmathLib::MatrixLib::matrix mathLib::MatrixLib::operator*(const matrix&, const matrix&);\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_15.md",
    "content": "练习18.15：说明using指示与using声明的区别。\n\n---\n\n- using指示将命名空间中的所有名字可见。\n\n- using声明只是将命名空间中的一个名字可见。\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_16.cpp",
    "content": "// 练习18.16：假定在下面的代码中标记为“位置1”的地方是对于命名空间Excercise\n// 中所有成员的using声明，请解释代码的含义。如果这些using声明出现在“位置2”\n// 又会怎样呢？将using声明变为using指示，重新回答之前的问题。\n\n#include <iostream>\n\nnamespace Excercise {\n\tint ivar = 0;\n\tdouble dvar = 0;\n\tconst int limit = 1000;\n}\nint ivar = 0;\n// 位置1\n//using Excercise::ivar; // error: ‘ivar’ is already declared in this scope\n//using Excercise::dvar;\n//using Excercise::limit;\n\nvoid manip() {\n\t// 位置2\n\tusing namespace Excercise;\n\n\tdouble dvar = 3.1416; // 与命名空间Excercise的dvar无关\n\tint iobj = limit + 1;\n\t//++ivar; // 如果使用using指示，则有error: reference to ‘ivar’ is ambiguous\n\t// 改成：\n\t++Excercise::ivar;\n\t++::ivar; // 使用的是全局空间的ivar\n\n\t// ignore warning\n\t(void)dvar;\n\t(void)iobj;\n}\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_17.md",
    "content": "练习18.17：实际编写代码检验你对上一题的回答是否正确。\n\n---\n\n见[上一题](./exercise_18_16.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_18.cpp",
    "content": "// 练习18.18：已知有下面的swap的典型定义（参加13.3节，第457页），当mem1是\n// 一个string时程序使用swap的哪个版本？如果mem1是int呢？说明在这两种情况下\n// 名字查找的过程。\n\n// 查找过程：在std命名空间中查找，找到。\n\n#include <iostream>\n#include <string>\n\nstruct Boo\n{\n\tstd::string mem1;\t// 使用：void std::swap(std::string&, std::string&);\n};\n\nstruct Goo\n{\n\tint mem1 = 0;\t\t// 使用：void std::swap(int&, int&);\n};\n\ntemplate<typename T>\nvoid swap(T v1, T v2)\n{\n\tusing std::swap;\n\tswap(v1.mem1, v2.mem1);\n}\n\nint main()\n{\n\t{\n\t\tBoo v1, v2;\n\t\tswap(v1, v2);\n\t}\n\n\t{\n\t\tGoo v1, v2;\n\t\tswap(v1, v2);\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_19.md",
    "content": "练习18.19：如果对swap的调用形如std::swap(v1.mem1, v2.mem2)将发生什么情况？\n\n---\n\n和上一题一样。\n\n但据题意，应该有更确切的解答，但我尚不能知晓。\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_20.cpp",
    "content": "// 练习18.20：在下面的代码中，确定哪个函数与compute调用匹配。列出所有候选函数\n// 和可行函数，对于每个可行函数的实参与形参的匹配过程来说，发生了哪种类型转换？\n\n// 如果将using声明置于main函数中compute的调用点之前将发生什么情况？重新回答之前\n// 的那些问题。\n\n// answer：这里面定义的所有compute都是候选函数，除了compute()，其他都是可行函数，\n// 都是把int类型转换成对应的形参类型，只有compute(int)最为匹配。\n// 如果把using声明放到main中compute的调用点之前，那么候选函数中就没有primerLib中\n// 的compute函数，候选函数则全部是可行函数，但最匹配的还是compute(int)。\n\n#include <iostream>\n\nnamespace primerLib {\n\tvoid compute();\n\tvoid compute(const void *);\n}\n\n//using primerLib::compute;\nvoid compute(int) { std::cout << \"compute(int)\" << std::endl; } // 最为匹配的函数\nvoid compute(double, double = 3.4);\nvoid compute(char*, char* = 0);\n\nvoid f()\n{\n\tcompute(0);\n}\n\nint main()\n{\n\tusing primerLib::compute;\n\tf();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_21.cpp",
    "content": "// 练习18.21：解释下列声明的含义，在它们当中存在错误吗？如果有，请指出来并说明\n// 错误的原因。\n\n// 为了通过编译，手动添加基类的定义\nclass CAD {};\nclass Vehicle {};\nclass List {};\nclass istream {};\nclass ostream {};\n\n// (a) 正确，Vehicle默认使用private访问说明符\nclass CADVehicle : public CAD, Vehicle { };\n\n// (b) 错误，在派生列表中，同一个基类只能出现一次\n//class DblList : public List, public List { };\n\n// (c) 正确\nclass iostream : public istream, public ostream {};\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_22.cpp",
    "content": "// 练习18.22：已知存在如下所示的类的继承体系，每个类都定义了一个默认构造函数。\n// 对于下面的定义来说，构造函数的执行顺序是怎样的？MI mi;\n\n// 为了验证结果，采用自定义的默认构造函数，打印一下。\n\n#include <iostream>\n\nusing namespace std;\n\nclass A { public: A() { cout << \"A()\" << endl; } };\nclass B : public A { public: B() { cout << \"B()\" << endl; } };\nclass C : public B { public: C() { cout << \"C()\" << endl; } };\nclass X { public: X() { cout << \"X()\" << endl; } };\nclass Y { public: Y() { cout << \"Y()\" << endl; } };\nclass Z : public X, public Y { public: Z() { cout << \"Z()\" << endl; } };\nclass MI : public C, public Z { public: MI() { cout << \"MI()\" << endl; } };\n\nint main()\n{\n\t// 推导构造顺序：\n\t// C, Z, MI\n\t// B -> C, X, Y, Z, MI\n\t// A -> B -> C, X, Y, Z, MI\n\n\tMI mi;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_23.cpp",
    "content": "// 练习18.23：使用练习18.22的继承体系以及下面定义的类D，同时假定每个类都定义\n// 了默认构造函数，请问下面的哪些类型转换是不被允许的？\n\n#include <iostream>\n\n#define USE_VAR(v) (void)(v)\n\nusing namespace std;\n\nclass A { public: A() { cout << \"A()\" << endl; } };\nclass B : public A { public: B() { cout << \"B()\" << endl; } };\nclass C : public B { public: C() { cout << \"C()\" << endl; } };\nclass X { public: X() { cout << \"X()\" << endl; } };\nclass Y { public: Y() { cout << \"Y()\" << endl; } };\nclass Z : public X, public Y { public: Z() { cout << \"Z()\" << endl; } };\nclass MI : public C, public Z { public: MI() { cout << \"MI()\" << endl; } };\n\nclass D : public X, public C {};\n\nint main()\n{\n\tD *pd = new D;\n\tUSE_VAR(pd);\n\n\t// (a) \n\tX *px = pd; // OK\n\tUSE_VAR(px);\n\n\t// (b)\n\tA *pa = pd; // OK\n\tUSE_VAR(pa);\n\n\t// (c)\n\tB *pb = pd; // OK\n\tUSE_VAR(pb);\n\n\t// (d)\n\tC *pc = pd; // OK\n\tUSE_VAR(pc);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_24.md",
    "content": "练习18.24：在第714页，我们使用一个指向Panda对象的Bear指针进行了一系列调用，假设我们使用的是一个指向Panda对象的ZooAnimal指针将发生什么情况，请对这些调用语句逐一进行说明。\n\n---\n\n```c++\nZooAnimal *pz = new Panda(\"yang_yang\");\npz->print();\t\t// 正确：Panda::print()\npz->toes();\t\t// 错误：不属于ZooAnimal接口\npz->cuddle();\t\t// 错误：不属于ZooAnimal接口\npz->highlight();\t// 错误：不属于ZooAnimal接口\ndelete pz;\t\t// 正确：Panda::~Panda\n```\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_25.cpp",
    "content": "// 练习18.25：假设我们有两个基类Base1和Base2，它们各自定义了一个名为print\n// 的虚成员和一个虚析构函数。从这两个基类中我们派生出下面的类，它们都重新\n// 定义了print函数。\n\n// 通过下面的指针，指出在每个调用中分别使用了哪个函数。\n\n#include <iostream>\n#include <memory>\n\nusing namespace std;\n\nclass Base1\n{\npublic:\n\tvirtual ~Base1() { cout << \"~Base1()\" << endl; }\n\tvirtual void print() = 0;\n};\n\nclass Base2\n{\npublic:\n\tvirtual ~Base2() { cout << \"~Base2()\" << endl; }\n\tvirtual void print() = 0;\n};\n\nclass D1 : public Base1\n{\npublic:\n\t~D1() { cout << \"~D1()\" << endl; }\n\tvoid print() override { cout << \"D1::print\" << endl; }\n};\n\nclass D2 : public Base2\n{\npublic:\n\t~D2() { cout << \"~D2()\" << endl; }\n\tvoid print() override { cout << \"D2::print\" << endl; }\n};\n\nclass MI : public D1, public D2\n{\npublic:\n\t~MI() { cout << \"~MI()\" << endl; }\n\tvoid print() override { cout << \"MI::print\" << endl; }\n};\n\nint main()\n{\n\tBase1 *pb1 = new MI;\n\tBase2 *pb2 = new MI;\n\tD1 *pd1 = new MI;\n\tD2 *pd2 = new MI;\n\n\t// (a)\n\tpb1->print(); \t// MI::print\n\n\t// (b)\n\tpd1->print();\t// MI::print\n\n\t// (c)\n\tpd2->print();\t// MI::print\n\n\t// 析构的过程都一样\n\n\t// (d)\n\tcout << \"d---------------------->\\n\";\n\tdelete pb2;\t// ~MI() -> ~D2() -> ~Base2() -> ~D1() -> ~Base1()\n\tcout << \"<----------------------d\\n\";\n\n\t// (e)\n\tcout << \"e---------------------->\\n\";\n\tdelete pd1;\t// ~MI() -> ~D2() -> ~Base2() -> ~D1() -> ~Base1()\n\tcout << \"<----------------------e\\n\";\n\n\t// (df)\n\tcout << \"f---------------------->\\n\";\n\tdelete pd2;\t// ~MI() -> ~D2() -> ~Base2() -> ~D1() -> ~Base1()\n\tcout << \"<----------------------f\\n\";\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_26.cpp",
    "content": "// 练习18.26：已知如上所示的继承体系，下面对print的调用为什么是错误的？适当\n// 修改MI，令其对print的调用可以编译通过并正确执行。\n\n// MI mi; mi.print(42);\n\n#include <iostream>\n#include <string>\n#include <vector>\n\nusing namespace std;\n\nstruct Base1 {\n\tvoid print(int) const {}\n};\n\nstruct Base2 {\n\tvoid print(double) const {}\n};\n\nstruct Derived : public Base1 {\n\tvoid print(std::string) const {}\n};\n\nstruct MI : public Derived, public Base2 {\n\tvoid print(std::vector<double>) {}\n};\n\nint main()\n{\n\tMI mi;\n\t//mi.print(42); // 非法，派生类的print函数隐藏了基类的，然而这个print函数不是可行函数\n\t\n\n\t// 可以明确指定要调用的版本\n\tmi.Base1::print(42);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_27.cpp",
    "content": "// 练习18.27：已知如上所示的继承体系，同时假定为MI添加了一个名为foo的函数：\n\n/* \n\n(a) 列出在MI::foo中可见的所有名字。\n    ival（全局），dval（全局），cval（形参），dval（局部），MI::ival，MI::dvec，Derived::sval, Derived::dval, Base1::ival, Base1::dval, Base1::cval, Base1::id（但不可访问），Base2::fval, Base2::dval, 所有print函数。\n\n(b) 是否存在某个可见的名字是继承自多个基类的？\n    有，比如dval。\n\n(c) 将Base1的dval成员与Derived的dval成员求和后赋给dval的局部实例。\n\n(d) 将MI::dvec的最后一个元素的值赋给Base2::fval。\n\n(e) 将从Base1继承的cval赋给从Derived继承的sval的第一个字符。\n\n*/\n\n#include <iostream>\n#include <string>\n#include <vector>\n\nusing namespace std;\n\nstruct Base1 {\n\tvoid print(int) const {}\n\nprotected:\n\tint ival;\n\tdouble dval;\n\tchar cval;\n\nprivate:\n\tint *id;\n};\n\nstruct Base2 {\n\tvoid print(double) const {}\n\nprotected:\n\tdouble fval;\n\tdouble dval;\n};\n\nstruct Derived : public Base1 {\n\tvoid print(std::string) const {}\nprotected:\n\tstd::string sval;\n\tdouble dval;\n};\n\nstruct MI : public Derived, public Base2 {\n\tvoid print(std::vector<double>) {}\n\n\tvoid foo(double);\n\nprotected:\n\tint *ival;\n\tstd::vector<double> dvec;\n};\n\nint ival;\ndouble dval;\nvoid MI::foo(double cval)\n{\n\tint dval;\n\t(void)dval;\n\n\t// 练习中的问题发生在此处\n\t\n\t// (c)\n\tdval = Derived::dval + Derived::Base1::dval;\n\n\t// (d) 假定不是空容器\n\tfval = dvec.back();\n\n\t// (e) 假定不是空字符串\n\tDerived::sval[0] = Base1::cval; \n}\n\nint main()\n{\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_28.cpp",
    "content": "// 练习18.28：已知存在如下的继承体系，在VMI类的内部哪些继承而来的成员无须前缀\n// 限定符就能直接访问？哪些必须有限定符才能访问？说明你的原因。\n\nstruct Base {\n\tvoid bar(int);\nprotected:\n\tint ival;\n};\n\nstruct Derived1 : virtual public Base {\n\tvoid bar(char);\n\tvoid foo(char);\nprotected:\n\tchar cval;\n};\n\nstruct Derived2 : virtual public Base {\n\tvoid foo(int);\nprotected:\n\tint ival;\n\tchar cval;\n};\n\nclass VMI : public Derived1, public Derived2 {\npublic:\n\tvoid test()\n\t{\n\t\t// ival可以直接访问，Derived2的优先级更高\n\t\t(void)ival;\n\n\t\t// cval不能直接访问，会产生二义性\n\t\t//(void)cval;\n\n\t\t// foo不能直接访问，会产生二义性\n\t\t//foo(42);\n\t\t\n\t\t// bar可以直接访问，Derived1的优先级更高\n\t\tbar(42);\n\t}\n};\n\n//---------------------------------------------------------\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_29.cpp",
    "content": "// 练习18.29：已知有如下所示的继承关系：\n// PS: 该程序会报warning\n\n#include <iostream>\nusing namespace std;\n\nclass Class {\npublic:\n\tClass() { cout << \"Class()\" << endl; }\n};\n\nclass Base : public Class {\npublic:\n\tBase() { cout << \"Base()\" << endl; }\n};\n\nclass D1 : virtual public Base {\npublic:\n\tD1() { cout << \"D1()\" << endl; }\n};\n\nclass D2 : virtual public Base {\npublic:\n\tD2() { cout << \"D2()\" << endl; }\n};\n\nclass MI : public D1, public D2 {\npublic:\n\tMI() { cout << \"MI()\" << endl; }\n};\n\nclass Final : public MI, public Class {\npublic:\n\tFinal() { cout << \"Final()\" << endl; }\n};\n\n// (a) 当作用于一个Final对象时，构造函数和析构函数的执行次序分别是什么？\n// Class -> Base -> D1 -> D2 -> MI -> Class -> Final\n\n// (b) 在一个Final对象中有几个Base部分？几个Class部分？\n// 一个Base部分，两个Class部分\n\n// (c) 下面哪些赋值运算符将造成编译错误？\nvoid func()\n{\n\tFinal f;\n\n\tBase *pb = 0;\n\tClass *pc = 0;\n\tMI *pmi = 0;\n\tD2 *pd2 = 0;\n \n\t// (c) (a)\n\t//pb = new Class(); // 错误，不能把基类指针转换成派生类指针\n\t// (c) (b)\n\t//pc = new Final();\t// 错误，‘Class’ is an ambiguous base of ‘Final’\n\t\n\t// (c) (c)\n\t//pmi = pb; // 错误，能把基类指针转换成派生类指针\n\t\n\t// (c) (d)\n\tpd2 = pmi; // 正确\n\n\tif (pb) { delete pb; pb = 0; }\n\tif (pc) { delete pc; pc = 0; }\n\tif (pmi) { delete pmi; pmi = 0; }\n\tif (pd2) { delete pd2; pd2 = 0; }\n}\n\nint main()\n{\n\tfunc();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch18_Tools_for_Large_Programs/exercise_18_30.cpp",
    "content": "// 练习18.30：在Base中定义一个默认构造函数、一个拷贝构造函数和一个接受int形参的构造函数。在每个派生类中分别定义这三种构造函数，每个构造函数应该使用它的实参初始化其Base部分。\n\n#include <iostream>\nusing namespace std;\n\nclass Class {\npublic:\n\tClass() { cout << \"Class()\" << endl; }\n};\n\nclass Base : public Class {\npublic:\n\tBase() { cout << \"Base()\" << endl; }\n\tBase(const Base&) {}\n\tBase(int) {}\n};\n\nclass D1 : virtual public Base {\npublic:\n\tD1() { cout << \"D1()\" << endl; }\n\tD1(const D1 &rhs) : Base(rhs) {}\n\tD1(int i) : Base(i) {}\n};\n\nclass D2 : virtual public Base {\npublic:\n\tD2() { cout << \"D2()\" << endl; }\n\tD2(const D2 &rhs) : Base(rhs) {}\n\tD2(int i) : Base(i) {}\n};\n\nclass MI : public D1, public D2 {\npublic:\n\tMI() { cout << \"MI()\" << endl; }\n\tMI(const MI &rhs) : Base(rhs), D1(rhs), D2(rhs) {}\n\tMI(int i) : Base(i), D1(i), D2(i) {}\n};\n\nclass Final : public MI, public Class {\npublic:\n\tFinal() { cout << \"Final()\" << endl; }\n\tFinal(const Final &rhs) : Base(rhs), MI(rhs) {}\n\tFinal(int i) : Base(i), MI(i) {}\n};\n\nvoid func()\n{\n\tFinal f;\n\n}\n\nint main()\n{\n\tfunc();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/build.sh",
    "content": "#! /bin/bash\n\n# 自动编译源文件的脚本，使用方法：sh build.sh [rebuild | clear]\n\nall_cpp_files=`ls *.cpp`\nexclude_files=\"\"\n\nis_exclude_file() {\n\tfor file in $exclude_files; do\n\t\tif [ \"$file\" = \"$1\" ]; then\n\t\t\treturn 0\n\t\tfi\n\tdone\n\n\treturn 1\n}\n\nmain() {\n\tif [ \"$1\" != \"\" ]; then\n\t\tif [ \"$1\" != \"clear\" ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcpp_file=$1\n\t\t\texe_file=${cpp_file%%.cpp*}\n\t\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\t\t\treturn $?\n\t\tfi\n\tfi\n\n\tfor cpp_file in $all_cpp_files; do\n\t\texe_file=${cpp_file%%.cpp*}\n\n\t\tif [ \"$1\" == \"clear\" ]; then\n\t\t\trm -f $exe_file\n\t\t\trm -f out1 out2\n\t\t\tcontinue\n\t\tfi\n\n\t\tif is_exclude_file $cpp_file; then\n\t\t\tcontinue\n\t\tfi\n\n\t\tif [ $exe_file -nt $cpp_file ] && [ \"$1\" != \"rebuild\" ]; then\n\t\t\tcontinue\n\t\tfi\n\n\t\techo \"[BUILDING] $cpp_file -> $exe_file\"\n\t\tg++ -g -Wall -std=c++11 $cpp_file -o $exe_file\n\n\t\tif [ \"$?\" != \"0\" ]; then\n\t\t\treturn 1\n\t\tfi\n\tdone\n\n\treturn 0\n}\n\nmain $1\n\nexit $?\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/example_function_table.cpp",
    "content": "// example: 成员指针函数表（p743）\n\n#include <iostream>\n\nusing namespace std;\n\nclass Screen\n{\npublic:\n\tScreen& home() { cout << __FUNCTION__ << endl; return *this; }\n\tScreen& forward() { cout << __FUNCTION__ << endl; return *this; }\n\tScreen& back() { cout << __FUNCTION__ << endl; return *this; }\n\tScreen& up() { cout << __FUNCTION__ << endl; return *this; }\n\tScreen& down() { cout << __FUNCTION__ << endl; return *this; }\n\n\t// Action是一个指针，可以用任意一个光标移动函数进行赋值\n\tusing Action = Screen& (Screen::*)();\n\n\tenum Directions { HOME, FORWARD, BACK, UP, DOWN };\n\tScreen& move(Directions);\n\nprivate:\n\tstatic Action Menu[];\t\t// 函数表\n};\n\nScreen::Action Screen::Menu[] = {\n\t&Screen::home,\n\t&Screen::forward,\n\t&Screen::back,\n\t&Screen::up,\n\t&Screen::down\n};\n\nScreen& Screen::move(Directions dir)\n{\n\treturn (this->*Menu[dir])();\n}\n\nint main()\n{\n\tScreen screen;\n\tscreen.move(Screen::HOME).move(Screen::FORWARD).move(Screen::BACK).move(Screen::UP).move(Screen::DOWN);\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/example_memfunc_as_callable_obj.cpp",
    "content": "// example: 将成员函数用作可调用对象（p744）\n\n#include <functional>\n#include <string>\n#include <algorithm>\n#include <vector>\n#include <iostream>\n\n// 使用function\nvoid method1()\n{\n\tstd::vector<std::string> vec{\"Hello\", \"World\", \"\"};\n\n\tstd::function<bool(const std::string&)> fcn = &std::string::empty;\n\tauto it = std::find_if(vec.begin(),  vec.end(), fcn);\n\tif (it == vec.end())\n\t{\n\t\tstd::cout << \"no empty string\" << std::endl;\n\t}\n\telse\n\t{\n\t\tstd::cout << \"has empty string\" << std::endl;\n\t}\n}\n\n// 使用mem_fn\nvoid method2()\n{\n\tstd::vector<std::string> vec{\"Hello\", \"World\"};\n\n\tauto it = std::find_if(vec.begin(),  vec.end(), std::mem_fn(&std::string::empty));\n\tif (it == vec.end())\n\t{\n\t\tstd::cout << \"no empty string\" << std::endl;\n\t}\n\telse\n\t{\n\t\tstd::cout << \"has empty string\" << std::endl;\n\t}\n}\n\n// 使用bind\nvoid method3()\n{\n\tstd::vector<std::string> vec{\"Hello\", \"World\"};\n\n\tauto func = std::bind(&std::string::empty, std::placeholders::_1);\n\n\tauto it = std::find_if(vec.begin(),  vec.end(), func);\n\tif (it == vec.end())\n\t{\n\t\tstd::cout << \"no empty string\" << std::endl;\n\t}\n\telse\n\t{\n\t\tstd::cout << \"has empty string\" << std::endl;\n\t}\n}\n\nint main()\n{\n\tmethod1();\n\tmethod2();\n\tmethod3();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/example_placement_new.cpp",
    "content": "// example: 定位new表达式（p729）\n\n#include <iostream>\n\nstruct Foo\n{\n\tFoo(int _a, int _b) : a(_a), b(_b) { std::cout << \"Foo::Foo()\\n\"; }\n\t~Foo() { std::cout << \"Foo::~Foo()\\n\"; }\n\tint a, b;\n};\n\nint main()\n{\n\tchar *buffer = new char[sizeof(Foo)];\n\n\tauto foo = new (buffer) Foo(2, 3); // 定位new不分配内存，只在预先分配的内存地址上构造对象\n\tstd::cout << foo->a << \" \" << foo->b << std::endl;\n\n\tstd::cout << \"foo address: \" << foo << std::endl;\n\tstd::cout << \"buffer address: \" << (void*)buffer << std::endl;\n\n\tfoo->~Foo();\t\t\t\t\t  // 手动调用析构函数\n\n\tdelete []buffer;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/example_pointer_to_member.cpp",
    "content": "// example: 类成员指针（p739）\n\n#include <string>\n#include <iostream>\n\nclass Screen\n{\npublic:\n\ttypedef std::string::size_type pos;\n\n\tScreen(const std::string &_contents) : contents(_contents), cursor(0), height(4), weight(6) {}\n\n\tchar get_cursor() const { return contents[cursor]; }\n\tchar get() const;\n\tchar get(pos ht, pos wd) const;\n\n\t// data是一个静态成员，返回一个成员指针\n\tstatic const std::string Screen::*data()\n\t{ return &Screen::contents; }\n\nprivate:\n\tstd::string contents;\n\tpos cursor;\n\tpos height, weight;\n};\n\nint main()\n{\n\t// 数据成员指针\n\t{\n\t\t// pdata可以指向一个Screen对象的string成员\n\t\t// 使用类的成员初始化\n\t\tconst std::string Screen::*pdata = Screen::data();\n\n\t\t// 使用\n\t\tScreen myscreen(\"hello\"), *pScreen = &myscreen;\n\t\tstd::cout << myscreen.*pdata << std::endl;\n\t\tstd::cout << pScreen->*pdata << std::endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/example_type_info.cpp",
    "content": "// example: type_info类（p735）\n\n#include <typeinfo>\n#include <iostream>\n\nint main()\n{\n\tint arr[10];\n\tint i_val;\n\tdouble d_val;\n\n\tstd::cout << typeid(arr).name() << \"\\n\"\n\t\t<< typeid(i_val).name() << \"\\n\"\n\t\t<< typeid(d_val).name() << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/example_union_with_class.cpp",
    "content": "// example: 含有类类型成员的union（p750）\n\n#include <iostream>\n#include <string>\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nusing namespace std;\n\nclass Token {\n\tfriend ostream& operator<<(ostream &os, Token &t);\n\npublic:\n\t// 因为union定义了一个string成员，所以Token必须定义拷贝控制成员\n\tToken() : tok(INT), ival(0) {}\n\tToken(const Token& t) : tok(t.tok) { copyUnion(t); }\n\tToken& operator=(const Token&);\n\tToken(Token &&t) : tok(t.tok) { moveCopyUnion(std::move(t)); }\n\tToken& operator=(Token&&);\n\n\t// 如果union含有一个string成员，则我们必须销毁它\n\t~Token()\n\t{ \n\t\tif (tok == STR) sval.~string(); \n\t\telse if(tok == SALES_DATA) sdval.~Sales_data();\n\t}\n\n\t// 下面的赋值运算符负责设置union的不同成员\n\tToken &operator=(const std::string&);\n\tToken &operator=(char);\n\tToken &operator=(int);\n\tToken &operator=(double);\n\tToken &operator=(const Sales_data&);\n\t\nprivate:\n\tenum { INT, CHAR, DBL, STR, SALES_DATA } tok; // 判别式\n\tunion {\n\t\tchar cval;\n\t\tint ival;\n\t\tdouble dval;\n\t\tstd::string sval;\n\t\tSales_data sdval;\n\t}; // 每个Token对象含有一个该未命名union类型的未命名成员\n\t\n\t// 检查判别式，然后酌情拷贝union成员\n\tvoid copyUnion(const Token&);\n\tvoid moveCopyUnion(Token&&);\n};\n\nToken& Token::operator=(int i)\n{\n\tif (tok == STR) sval.~string();\n\telse if (tok == SALES_DATA) sdval.~Sales_data();\n\n\tival = i;\n\ttok = INT;\n\treturn *this;\n}\n\nToken& Token::operator=(char c)\n{\n\tif (tok == STR) sval.~string();\n\telse if (tok == SALES_DATA) sdval.~Sales_data();\n\tcval = c;\n\ttok = CHAR;\n\treturn *this;\n}\n\nToken& Token::operator=(double d)\n{\n\tif (tok == STR) sval.~string();\n\telse if (tok == SALES_DATA) sdval.~Sales_data();\n\tdval = d;\n\ttok = DBL;\n\treturn *this;\n}\n\nToken& Token::operator=(const std::string &s)\n{\n\tif (tok == SALES_DATA) sdval.~Sales_data();\n\n\tif (tok == STR) sval = s;  // 如果当前存储的是string，则直接赋值\n\telse\n\t\tnew (&sval) string(s);   // 否则需要先构造一个string\n\ttok = STR;\n\treturn *this;\n}\n\nToken& Token::operator=(const Sales_data &sd)\n{\n\tif (tok == STR) sval.~string();\n\n\tif (tok == SALES_DATA) sdval = sd;\n\telse\n\t\tnew (&sdval) Sales_data(sd);\n\n\ttok = SALES_DATA;\n\treturn *this;\n}\n\nvoid Token::copyUnion(const Token &t)\n{\n\tswitch (t.tok)\n\t{\n\tcase INT: ival = t.ival;break;\n\tcase CHAR: cval = t.cval;break;\n\tcase DBL: dval = t.dval;break;\n\tcase STR: new (&sval) string(t.sval);break;\n\tcase SALES_DATA: new (&sdval) Sales_data(t.sdval);break;\n\t}\n}\n\nvoid Token::moveCopyUnion(Token &&t)\n{\n\tswitch (t.tok)\n\t{\n\tcase INT: ival = t.ival;break;\n\tcase CHAR: cval = t.cval;break;\n\tcase DBL: dval = t.dval;break;\n\tcase STR: new (&sval) string(std::move(t.sval));break;\n\tcase SALES_DATA: new (&sdval) Sales_data(std::move(t.sdval));break;\n\t}\n}\n\nToken& Token::operator=(const Token &t)\n{\n\t// 如果此对象是STR而t不是，则必须释放原来的string\n\tif (tok == STR && t.tok != STR) sval.~string();\n\tif (tok == SALES_DATA && t.tok != SALES_DATA) sdval.~Sales_data();\n\n\tif (tok == STR && t.tok == STR)\n\t\tsval = t.sval;\n\telse if(tok == SALES_DATA && t.tok == SALES_DATA)\n\t\tsdval = t.sdval;\n\telse\n\t\tcopyUnion(t);\n\n\ttok = t.tok;\n\treturn *this;\n}\n\nToken& Token::operator=(Token &&t)\n{\n\t// 如果此对象是STR而t不是，则必须释放原来的string\n\tif (tok == STR && t.tok != STR) sval.~string();\n\tif (tok == SALES_DATA && t.tok != SALES_DATA) sdval.~Sales_data();\n\n\tif (tok == STR && t.tok == STR)\n\t\tsval = t.sval;\n\telse if(tok == SALES_DATA && t.tok == SALES_DATA)\n\t\tsdval = t.sdval;\n\telse\n\t\tmoveCopyUnion(std::move(t));\n\n\ttok = t.tok;\n\treturn *this;\n}\n\nostream& operator<<(ostream &os, Token &t)\n{\n\tswitch (t.tok)\n\t{\n\tcase Token::INT: os << t.ival;break;\n\tcase Token::CHAR: os << t.cval;break;\n\tcase Token::DBL: os << t.dval;break;\n\tcase Token::STR: os << t.sval;break;\n\tcase Token::SALES_DATA: os << t.sdval;break;\n\t}\n\n\treturn os;\n}\n\nint main()\n{\n\tToken t;\n\tt = 'c';\n\tt = 42;\n\tt = 3.14;\n\tt = \"hello\";\n\tt = Sales_data(\"0-201-70353-X\", 4, 24.99);\n\n\tt = t;\n\tcout << t << endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/example_use_RTTI.cpp",
    "content": "// example: 使用RTTI（p733）\n\n#include <typeinfo>\n#include <iostream>\n\nclass Base {\n\tfriend bool operator==(const Base&, const Base&);\npublic:\n\t// Base的接口成员\n\nprotected:\n\tvirtual bool equal(const Base& rhs) const\n\t{\n\t\t// 执行Base对象的操作\n\t\treturn m_type == rhs.m_type;\n\t}\n\nprivate:\n\tint m_type = 0;\n};\n\nclass Derived : public Base {\npublic:\n\t// Derived的其他接口成员\n\nprotected:\n\tbool equal(const Base& rhs) const override\n\t{\n\t\t// 我们清除这两个类型是相等的，所以转换过程不会抛出异常\n\t\tauto r = dynamic_cast<const Derived&>(rhs);\n\t\treturn m_id == r.m_id;\n\t}\n\nprivate:\n\tint m_id = 0;\n};\n\n// 类型敏感的相等运算符\nbool operator==(const Base &lhs, const Base &rhs)\n{\n\t// 如果typeid不相同，返回false，否则虚调用equal\n\treturn typeid(lhs) == typeid(rhs) && lhs.equal(rhs);\n}\n\nint main()\n{\n\tDerived d1, d2;\n\tBase b;\n\n\tif (b == d1)\n\t{\n\t\tstd::cout << \"b == di\" << std::endl;\n\t}\n\n\tif (d1 == d2)\n\t{\n\t\tstd::cout << \"d1 == d2\" << std::endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_01.cpp",
    "content": "// 练习19.1：使用malloc编写你自己的operator new(size_t)函数，使用free编写\n// operator delete(void*)函数。\n\n#include <cstdlib>\n#include <new>\n#include <iostream>\n\nusing namespace std;\n\nvoid *operator new(size_t sz) {\n\tif (void *mem = malloc(sz))\n\t\treturn mem;\n\telse\n\t\tthrow bad_alloc();\n}\n\nvoid operator delete(void *mem) noexcept\n{\n\tfree(mem);\n}\n\nint main()\n{\n\tint *ip = new int;\n\tcout << ip << endl;\n\tdelete ip;\n\tip = 0;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_02.md",
    "content": "练习19.2：默认情况下，allocator类使用operator new获取存储空间，然后使用operator delete释放它。利用上一题中的两个函数重新编译并运行你的StrVec程序（参见13.5节，第465页）。\n\n---\n\n见：\n\n- [StrVec](../ch13_Copy_Control/example_StrVec/StrVec.h)\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_03.cpp",
    "content": "// 练习19.3：已知存在如下的继承体系，其中每个类分别定义了一个公有的默认构造函数\n// 和一个虚析构函数。哪一个dynamic_cast将失败？\n\n#include <iostream>\n\nclass A { public: virtual ~A() {} };\nclass B : public A {};\nclass C : public B {};\nclass D : public A {}; // ps: 书本中是如这样写的：class D : public B, public A {}; 然而这样无法通过编译\n\nint main()\n{\n\t// a succ\n\t{\n\t\tA *pa = new C;\n\t\tB *pb = dynamic_cast<B*>(pa);\n\t\tstd::cout << (pb ? \"succ\" : \"failed\") << std::endl;\n\n\t\tdelete pa;\n\t}\n\n\t// b 失败\n\t{\n\t\tB *pb = new B;\n\t\tC *pc = dynamic_cast<C*>(pb);\n\t\tstd::cout << (pc ? \"succ\" : \"failed\") << std::endl;\n\n\t\tdelete pb;\n\t}\n\n\t// c 失败\n\t{\n\t\tA *pa = new D;\n\t\tB *pb = dynamic_cast<B*>(pa);\n\t\tstd::cout << (pb ? \"succ\" : \"failed\") << std::endl;\n\n\t\tdelete pa;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_04.cpp",
    "content": "// 练习19.4：使用上一个练习定义的类改写下面的代码，将表达式*pa转换成类型C&。\n\n#include <iostream>\n#include <typeinfo>\n\nclass A { public: virtual ~A() {} };\nclass B : public A {};\nclass C : public B {};\nclass D : public A {};\n\nint main()\n{\n\tA *pa = new B;\n\n\t/*\n\tif (C *pc = dynamic_cast<C*>(pa))\n\t{\n\t\t// 使用C的成员\n\t\t(void)pc;\n\t}\n\telse\n\t{\n\t\t// 使用A的成员\n\t}\n\t*/\n\n\ttry {\n\t\tC& c = dynamic_cast<C&>(*pa);\n\t\t(void)c;\n\t} catch (std::bad_cast) {\n\t\t// 转换失败\n\t\tstd::cout << \"dynamic_cast failed\" << std::endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_05.md",
    "content": "练习19.5：在什么情况下你应该使用dynamic_cast替代虚函数？\n\n---\n\n当我们想使用基类对象的指针或引用执行某个派生类操作并且该操作不是虚函数时，就需要用dynamic_cast将基类的指针或引用转换成派生类的。\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_06.cpp",
    "content": "// 练习19.6：编写一条表达式将Query_base指针动态转换为AndQuery指针（参见15.9.1节，第564页）。\n// 分别使用AndQuery的对象以及其他类型的对象测试转换是否有效。打印一条表示类型转换是否成功\n// 的信息，确保实际输出的结果与期望的一致。\n\n#include <iostream>\n#include <typeinfo>\n\nclass Query_base\n{\npublic:\n\tvirtual ~Query_base() {}\n};\n\nclass AndQuery : public Query_base\n{\n};\n\nint main()\n{\n\tQuery_base *base_ptr = new Query_base;\n\n\t{\n\t\tAndQuery cmp_obj;\n\t\tAndQuery *derived_ptr = dynamic_cast<AndQuery*>(base_ptr);\t\n\t\tif (derived_ptr && typeid(*derived_ptr) == typeid(cmp_obj))\n\t\t{\n\t\t\tstd::cout << \"Query_base* -> AndQuery* succ\" << std::endl;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstd::cout << \"Query_base* -> AndQuery* failed\" << std::endl;\n\t\t}\t\n\t}\n\n\t{\n\t\tQuery_base cmp_obj;\n\t\tQuery_base *ptr = dynamic_cast<Query_base*>(base_ptr);\t\n\t\tif (ptr && typeid(*ptr) == typeid(cmp_obj))\n\t\t{\n\t\t\tstd::cout << \"Query_base* -> Query_base* succ\" << std::endl;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tstd::cout << \"Query_base* -> Query_base* failed\" << std::endl;\n\t\t}\t\n\t}\n\n\tdelete base_ptr;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_07.cpp",
    "content": "// 练习19.7：编写与上一个练习类似的转换，这一次将Query_base对象转换为\n// AndQuery的引用。重复上面的测试过程，确保转换能正常工作。\n\n#include <iostream>\n#include <typeinfo>\n\nclass Query_base\n{\npublic:\n\tvirtual ~Query_base() {}\n};\n\nclass AndQuery : public Query_base\n{\n};\n\nint main()\n{\n\tQuery_base *base_ptr = new Query_base;\n\n\t{\n\t\tAndQuery cmp_obj;\n\n\t\ttry {\n\t\t\tAndQuery &derived_ref = dynamic_cast<AndQuery&>(*base_ptr);\n\t\t\tif (typeid(derived_ref) == typeid(cmp_obj))\n\t\t\t{\n\t\t\t\tstd::cout << \"Query_base& -> AndQuery& succ\" << std::endl;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tstd::cout << \"Query_base& -> AndQuery& failed\" << std::endl;\n\t\t\t}\t\n\t\t} catch (std::bad_cast e) {\n\t\t\tstd::cout << \"Query_base& -> AndQuery& failed: \" << e.what() << std::endl;\n\t\t}\n\t}\n\n\t{\n\t\tQuery_base cmp_obj;\n\n\t\ttry {\n\t\t\tQuery_base &ref = dynamic_cast<Query_base&>(*base_ptr);\t\n\t\t\tif (typeid(ref) == typeid(cmp_obj))\n\t\t\t{\n\t\t\t\tstd::cout << \"Query_base& -> Query_base& succ\" << std::endl;\n\t\t\t}\n\t\t\telse\n\t\t\t{\n\t\t\t\tstd::cout << \"Query_base& -> Query_base& failed\" << std::endl;\n\t\t\t}\t\n\t\t} catch (std::bad_cast e) {\n\t\t\tstd::cout << \"Query_base& -> Query_base& failed: \" << e.what() << std::endl;\n\t\t}\n\t}\n\n\tdelete base_ptr;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_08.cpp",
    "content": "// 练习19.8：编写一条typeid表达式检查两个Query_base对象是否指向同一种类型。\n// 再检查该类型是否是AndQuery。\n\n#include <iostream>\n#include <typeinfo>\n\nclass Query_base\n{\npublic:\n\tvirtual ~Query_base() {}\n};\n\nclass AndQuery : public Query_base\n{\n};\n\nint main()\n{\n\tAndQuery aq1, aq2;\n\tQuery_base *p1 = &aq1, *p2 = &aq2;\n\n\tif (typeid(*p1) == typeid(*p2))\n\t\tstd::cout << \"same type\" << std::endl;\n\n\tif (typeid(*p1) == typeid(AndQuery))\n\t\tstd::cout << \"type is AndQuery\" << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_09.md",
    "content": "练习19.09：编写本节最后一个程序类似的代码，令其打印你的编译器为一些常见类型所起的名字。如果你得到的输出结果与本书类似，尝试编写一个函数将这些字符串翻译成人们更易读懂的形式。\n\n---\n\n见[案例](./example_type_info.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_10.cpp",
    "content": "// 练习19.10：已知存在如下的类继承体系，其中每个类定义了一个默认公有的构造函数\n// 和一个虚析构函数。下面的语句将打印哪些类的名字？\n\n#include <iostream>\n#include <typeinfo>\n\nusing namespace std;\n\nclass A { public: virtual ~A() {} };\nclass B : public A {};\nclass C : public B {};\n\nint main()\n{\n\t// (a) 指向A的指针类型\n\t{\n\t\tA *pa = new C;\n\t\tcout << typeid(pa).name() << endl;\n\t}\n\n\t// (b) 指向A的指针类型\n\t{\n\t\tC cobj;\n\t\tA &ra = cobj;\n\t\tcout << typeid(&ra).name() << endl;\n\t}\n\n\t// (c) B类型\n\t{\n\t\tB *px = new B;\n\t\tA &ra = *px;\n\t\tcout << typeid(ra).name() << endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_11.md",
    "content": "练习19.11：普通的数据指针与指向成员的指针有何区别？\n\n---\n\n指向成员的指针指向类的成员而非实际数据，如果想使用它，就必须把它绑定到类型的对象上。\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_12.cpp",
    "content": "// 练习19.12：定义一个成员指针，令其可以指向Screen类的cursor成员。\n// 通过该指针获得Scene::cursor的值。\n\n#include <string>\n#include <iostream>\n\nclass Screen\n{\npublic:\n\ttypedef std::string::size_type pos;\n\n\tScreen(const std::string &_contents) : contents(_contents), cursor(0), height(4), weight(6) {}\n\n\tchar get_cursor() const { return contents[cursor]; }\n\tchar get() const;\n\tchar get(pos ht, pos wd) const;\n\n\t// data是一个静态成员，返回一个成员指针\n\tstatic const std::string Screen::*data()\n\t{ return &Screen::contents; }\n\n\tstatic const pos Screen::*pcursor()\n\t{ return &Screen::cursor; }\n\nprivate:\n\tstd::string contents;\n\tpos cursor;\n\tpos height, weight;\n};\n\nint main()\n{\n\t// 数据成员指针\n\t{\n\t\tauto pcursor = Screen::pcursor();\n\n\t\t// 使用\n\t\tScreen myscreen(\"hello\");\n\t\tstd::cout << myscreen.*pcursor << std::endl;\n\t}\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_13.cpp",
    "content": "// 练习19.13：定义一个类型，使其可以表示指向Sales_data类的bookNo成员的指针。\n\n#include <iostream>\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nint main()\n{\n\tauto pbookNo = Sales_data::pbookNo();\n\tSales_data sd(\"0-201-88954-4\");\n\n\tstd::cout << sd.*pbookNo << std::endl;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_14.cpp",
    "content": "//练习19.14：下面的代码合法吗？如果合法，代码的含义是什么？\n//如果不合法，解释原因。\n\n\n/*\nauto pmf = &Screen::get_cursor;\npmf = &Screen::get;\n*/\n\n// 不合法，函数类型不一致。\n\nclass Screen\n{\npublic:\n\tchar get_cursor() const { return 0; }\n\tchar get(unsigned w, unsigned h) const { return 0; }\n};\n\nint main()\n{\n\tauto pmf = &Screen::get_cursor;\n\t//pmf = &Screen::get;\n\t\n\t(void)pmf;\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_15.md",
    "content": "练习19.15：普通函数指针和指向成员的函数指针有何区别？\n\n---\n\n1. 成员函数的函数指针不绑定至类型的对象，直到调用时才会使用对象。\n\n2. 成员函数和指向该成员的指针之间不存在自动转换规则。\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_16.md",
    "content": "练习19.16：声明一个类型别名，令其作为指向Sales_data的avg_price成员的指针的同义词。\n\n---\n\nusing AvgPrice = double (Sales_data::*)() const;\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_17.md",
    "content": "练习19.17：为Screen的所有成员函数类型各定义一个类型别名。\n\n---\n\n见[案例](./example_function_table.cpp)\n\n未定义所有成员的类型别名，略。\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_18.cpp",
    "content": "// 练习19.18：编写一个函数，使用count_if统计在给定的vector中有多少个空\n// string。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <functional>\n#include <algorithm>\n\nvoid func()\n{\n\tusing namespace std;\n\n\tvector<string> vec{\"\", \"aaa\", \"bbb\", \"\"};\n\tint cnt = count_if(vec.begin(), vec.end(), bind(&string::empty, placeholders::_1));\n\n\tcout << \"empty string count: \" << cnt << endl;\n}\n\nint main()\n{\n\tfunc();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_19.cpp",
    "content": "// 练习19.19：编写一个函数，令其接受vector<Sales_data>并查找平均价格高于\n// 某个值的第一个元素。\n\n#include <iostream>\n#include <vector>\n#include <string>\n#include <functional>\n#include <algorithm>\n#include \"../ch07_Classes/example_Sales_data/Sales_data.h\"\n\nvoid func()\n{\n\tusing namespace std;\n\n\tvector<Sales_data> vec;\n\tvec.emplace_back(\"0-201-70353-X\", 4, 24.99);\n\n\tdouble avg_price = 0;\n\tcout << \"enter avg_price: \";\n\tcin >> avg_price;\n\n\tauto it = find_if(vec.begin(), vec.end(), bind(&Sales_data::AvgPriceMoreThan, placeholders::_1, avg_price));\n\tif (it != vec.end())\n\t{\n\t\tcout << \"find, info: \" << *it << endl;\n\t}\n\telse\n\t{\n\t\tcout << \"no find\" << endl;\n\t}\n}\n\nint main()\n{\n\tfunc();\n\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_20.md",
    "content": "练习19.20：将你的QueryResult类嵌套在TextQuery中，然后重新运行12.3.2节（第435页）中使用了TextQuery的程序。\n\n---\n\n见：[新的TextQuery例程](../ch12_Dynamic_Memory/example_TextQuery2)\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_21.md",
    "content": "练习19.21：编写你自己的Token类。\n\n---\n\n见[Token](./example_union_with_class.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_22.md",
    "content": "练习19.22：为你的Token类添加一个Sales_data类型的成员。\n\n---\n\n见[Token](./example_union_with_class.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_23.md",
    "content": "练习19.23：为你的Token类添加移动构造函数和移动赋值运算符。\n\n---\n\n见[Token](./example_union_with_class.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_24.md",
    "content": "练习19.24：如果我们将一个Token对象赋值给它自己将发生什么情况？\n\n---\n\n依赖它的成员的赋值操作正确完成自赋值。\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_25.md",
    "content": "练习19.25：编写一系列赋值运算符，令其分别接受union中各种类型的值。\n\n---\n\n见[Token](./example_union_with_class.cpp)\n"
  },
  {
    "path": "codes/CppPrimer/ch19_Specialized_Tools_and_Techniques/exercise_19_26.cpp",
    "content": "// 练习19.26：说明下列声明语句的含义并判断它们是否合法。\n\n// 声明这个函数的定义是用C语言编写的。\n// 不合法，因为C语言不支持函数重载，而这两个函数是一组重载函数。\n\nextern \"C\" int compute(int*, int);\n//extern \"C\" double compute(double*, double);\n\nint main()\n{\n\treturn 0;\n}\n"
  },
  {
    "path": "codes/CppPrimer/data/book_sales",
    "content": "0-201-70353-X 4 24.99\n0-201-82470-1 4 45.39\n0-201-88954-4 2 15.00 \n0-201-88954-4 5 12.00 \n0-201-88954-4 7 12.00 \n0-201-88954-4 2 12.00 \n0-399-82477-1 2 45.39\n0-399-82477-1 3 45.39\n0-201-78345-X 3 20.00\n0-201-78345-X 2 25.00\n"
  },
  {
    "path": "codes/CppPrimer/data/little_story.txt",
    "content": "Alice Emma has long flowing red hair.\n\nHer Daddy sayes when the wind blows\nthrough her hair, it looks almost alive,\nlike a fiery bird in flight.\nA beautiful fiery bird, he tells her,\n\nmagical but untamed.\n\"Daddy, shush, there is no such thing,\"\nshe tells him, at the same time wanting\nhim to tell her more.\nShyly, she asks, \"I mean, Daddy, is there?\"\n"
  },
  {
    "path": "codes/CppPrimer/data/numbers.txt",
    "content": "2 4 3 18 9 7\n"
  },
  {
    "path": "codes/CppPrimer/data/person_numbers.txt",
    "content": "morgan 2015552368 8625550123\ndrew 9735550130\nlee 6095550132 2015550175 8005550000\n"
  },
  {
    "path": "codes/CppPrimer/data/some_words.txt",
    "content": "What does the bee do\nBring home honey\nAnd what does Father do\nBring home money\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/argument.md",
    "content": "参数（实参，argument）向函数传递的值。\n\n见p21。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/assignment.md",
    "content": "赋值（assignment）抹去一个对象的当前值，用一个新值取代之。\n\n见p11。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/block.md",
    "content": "程序块（block）零条或多条语句的序列，用花括号包围。\n\n见p2。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/buffer.md",
    "content": "缓冲区（buffer）一个存储区域，用于保存数据。IO设备通常将输入（或输出）数据保存在一个缓冲区中，读写缓冲区的动作与程序中的动作是无关的。我们可以显示地刷新输出缓冲，以便强制将缓冲区中的数据写入输出设备。默认情况下，读[cin](./cin.md)会刷新[cout](./cout.md)；程序非正常终止时也会刷新cout。\n\n见p6。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/built-in_type.md",
    "content": "内置类型（built-in type）由语言定义的类型，如int。\n\n见p2。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/cerr.md",
    "content": "cerr 一个[ostream](./ostream.md)对象，关联到[标准错误](./standard_error.md)，通常写入到与标准输出相同的设备。默认情况下，写到cerr的数据是不缓冲的。cerr通常用于输出错误信息或其他不属于程序正常逻辑的输出内容。\n\n见p5。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/cin.md",
    "content": "cin 一个[istream](./istream.md)对象，用来从[标准输入](./standard_input.md)读取数据。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/class.md",
    "content": "类（class）一种用于定义自己的数据结构及其相关操作的机制。类是C++中最基本的特性之一。[标准库](./standard_library.md)类型中，如istream和ostream都是类。\n\n见p17。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/class_type.md",
    "content": "类类型（class type）类定义的类型。类名即为类型名。\n\n见p17。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/clog.md",
    "content": "clog 一个ostream对象，关联到[标准错误](./standard_error.md)。默认情况下，写到clog的数据是被缓冲的。clog通常用于报告程序的执行信息，存入一个日志文件中。\n\n见p5。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/comment.md",
    "content": "注释（comment）被编译器忽略的程序文本。C++有两种类型的注释：单行注释和界定符对注释。单行注释以//开始，从//到行尾的所有内容都是注释。界定符对注释以`/*`开始，其后所有内容都是注释，直到遇到`*/`为止。\n\n见p8。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/condition.md",
    "content": "条件（condition）求值结果为真或假的表达式。通常用值0表示假，用非零值表示真。\n\n见p10。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/cout.md",
    "content": "cout 一个[ostream](./ostream.md)对象，用于将数据写入[标准输出](./standard_output.md)。通常用于程序的正常输出内容。\n\n见p5。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/data_structure.md",
    "content": "数据结构（data structure）数据及其上所允许的操作的一种逻辑组合。\n\n见p17。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/edit-compile-debug.md",
    "content": "编辑-编译-调试（edit-compile-debug）使程序能正确执行的开发过程。\n\n见p14。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/end-of-file.md",
    "content": "文件结束符（end-of-file）系统特定的标识，指出文件中无更多数据了。\n\n见p13。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/expression.md",
    "content": "表达式（expression）最小的计算单元。一个表达式包含一个或多个运算对象，通常还包含一个或多个运算符。表达式求值会产生一个结果。例如，假设i和j是int对象，则i+j是一个表达式，它产生两个int值的和。\n\n见p6。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/for_statement.md",
    "content": "for语句（for statement）迭代语句，提供重复执行能力。通常用来将一个计算反复执行指定次数。\n\n见p11。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/function.md",
    "content": "函数（function）具名的计算单元。\n\n见p2。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/function_body.md",
    "content": "函数体（function body）语句块，定义了函数所执行的动作。\n\n见p2。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/function_name.md",
    "content": "函数名（function_name）函数为人所知的名字，也用来进行函数调用。\n\n见p2。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/header.md",
    "content": "头文件（header）使类或其他名字的定义可被多个程序使用的一种机制。程序通过#include指令使用头文件。\n\n见p6。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/if_statement.md",
    "content": "if语句（if statement）根据一个特定条件的值进行条件执行的语句。如果条件为真，执行if语句体。否则，执行else语句体（如果存在的话）。\n\n见p15。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/initialize.md",
    "content": "初始化（initialize）在一个对象创建的时候赋予它一个值。\n\n见p7。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/iostream.md",
    "content": "iostream [头文件](./header.md)，提供了面向流的输入输出的[标准库](./standard_library.md)类型。\n\n见p5。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/istream.md",
    "content": "istream 提供了面向流的输入的库类型。\n\n见p5。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/main.md",
    "content": "main 操作系统执行一个C++程序时所调用的函数。每个程序必须有且只有一个命名为main的函数。\n\n见p2。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/manipulator.md",
    "content": "操纵符（manipulator）对象，如std::endl，在读写流的时候用来“操纵”流本身。\n\n见p6。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/member_function.md",
    "content": "成员函数（member function）类定义的操作。通常通过调用成员函数来操作特定对象。\n\n见p20。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/namespace.md",
    "content": "命名空间（namespace）将库定义的名字放在一个单一位置的机制。命名空间可以帮助避免不经意的名字冲突。C++标准库定义的名字在命名空间std中。\n\n见p7。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/ostream.md",
    "content": "ostream [标准库](./standard_library.md)类型，提供面向流的输出。\n\n见p5。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/parameter_list.md",
    "content": "形参列表（parameter list）函数定义的一部分，指出调用函数时可以使用什么样的实参，可能为空列表。\n\n见p2。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/return_type.md",
    "content": "返回类型（return type）函数返回值的类型。\n\n见p2。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/source_file.md",
    "content": "源文件（source file）包含C++程序的文件。\n\n见p3。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/standard_error.md",
    "content": "标准错误（standard error）输出流，用于报告错误。标准输出和标准错误通常关联到程序执行所在的窗口。\n\n见p5。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/standard_input.md",
    "content": "标准输入（standard input）输入流，通常与程序执行所在窗口相关联。\n\n见p5。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/standard_library.md",
    "content": "标准库（standard library）一个类型和函数的集合，每个C++编译器都必须支持。标准库提供了支持IO操作的类型。C++程序员倾向于用”库”指代整个标准库，还倾向于用库类型表示标准库的特定部分，例如用”iostream库”表示标准库中定义IO类的部分。\n\n见p5。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/standard_output.md",
    "content": "标准输出（standard output）输出流，通常与程序执行所在窗口相关联。\n\n见p5。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/statement.md",
    "content": "语句（statement）程序的一部分，指定了当程序执行时进行什么动作。一个表达式接一个分号就是一条语句；其他类型的语句包括语句块、if语句、for语句和while语句，所有这些语句内部都包含其他语句。\n\n见p2。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/string_literal.md",
    "content": "字符串常量（string literal）零或多个字符组成的序列，用双引号包围(\"a string literal\")。\n\n见p6。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/uninitialized_variable.md",
    "content": "未初始化的变量（uninitialized variable）未赋予初值的变量。类类型的变量如果未指定初值，则按类定义指定的方式进行初始化。定义在函数内部的内置类型变量默认是不初始化的，除非有显示的初始化语句。试图使用一个未初始化变量的值是错误的。未初始化变量是bug的常见成因。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/variable.md",
    "content": "变量（variable）具名对象。\n\n见p7。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/while_statement.md",
    "content": "while语句（while statement）迭代语句，提供重复执行直至一个特定条件为假的机制。循环体会执行零次或多次，依赖于循环条件求值结果。\n\n见p10。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/不等运算符.md",
    "content": "!=运算符（!=operator）不等运算符。检测左侧运算对象是否不等于右侧运算对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/作用域运算符.md",
    "content": "::运算符（::operator）作用域运算符。其用处之一是访问命名空间中的名字。例如std::cout表示命名空间std中的名字cout。\n\n见p7。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/复合赋值运算符.md",
    "content": "+=运算符（+=operator）复合赋值运算符，将右侧运算对象加到左侧运算对上上；a+=b等价于a=a+b。\n\n见p11。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/大于等于运算符.md",
    "content": "`>=运算符`（>=operator）大于等于运算符。检测左侧运算对象是否大于等于右侧运算对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/大于运算符.md",
    "content": "`>运算符`（>operator）大于运算符。检测左侧运算对象是否大于右侧运算对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/头文件包含指令.md",
    "content": "#include 头文件包含指令，使头文件中代码可被程序使用。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/小于等于运算符.md",
    "content": "<=运算符（<=operator）小于等于运算符。检测左侧运算对象是否小于等于右侧运算对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/小于运算符.md",
    "content": "<运算符（<operator）小于运算符。检测左侧运算对象是否小于右侧运算对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/点运算符.md",
    "content": ".运算符（.operator）点运算符。左侧运算对象必须是一个类类型对象，右侧运算对象必须是此对象的一个成员的名字。运算结果即为该对象的这个成员。\n\n见p20。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/相等运算符.md",
    "content": "==运算符（==operator）相等运算符。检测左侧运算对象是否等于右侧运算对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/等号运算符.md",
    "content": "=运算符（=operator）将右侧运算对象的值赋予左侧运算对象所表示的对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/调用运算符.md",
    "content": "()运算符（()operator）调用运算符。跟随在函数名之后的一对括号()，起到调用函数的效果。传递给函数的实参放置在括号内。\n\n见p21。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/输入运算符.md",
    "content": "`>>运算符`（>>operator）输入运算符。从左侧运算对象所指定的输入流读取数据，存入右侧运算对象中：cin>>i表示从标准输入读取下一个值，存入i中。输入运算符可以连接：cin>>i>>j表示先读取一个值存入i，再读取一个值存入j。\n\n见p7。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/输出运算符.md",
    "content": "<<运算符（<<operator）输出运算符。将右侧运算对象的值写到左侧运算对象表示的输出流：cout << \"hi\"表示将hi写到标准输出。输出运算符可以连接：cout << \"hi\" << \"bye\"表示将输出hibye。\n\n见p6。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/递减运算符.md",
    "content": "--运算符（--operator）递减运算符。将运算对象的值减1，--i等价于i=i-1。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch01/递增运算符.md",
    "content": "++运算符（++operator）递增运算符。将运算对象的值加1，++i等价于i=i+1。\n\n见p11。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/address.md",
    "content": "地址（address）是一个数字，根据它可以找到内存中的一个字节。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/alias_declaration.md",
    "content": "别名声明（alias declaration）为另一种类型定义一个同义词，使用\"名字=类型\"的格式将名字作为该类型的同义词。\n\n如：\n\n```\nusing SI = Sales_item;\t// SI是Sales_item的同义词\n```\n\n见p60。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/arithmetic_type.md",
    "content": "算术类型（arithmetic type）布尔值、字符、整数、浮点数等内置类型。\n\n见p30。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/array.md",
    "content": "数组（array）是一种数据结构，存放着一组未命名的对象，可以通过索引来访问这些对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/auto.md",
    "content": "auto 是一种类型说明符，通过变量的初始值来推断变量的类型。\n\n见p61。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/base_type.md",
    "content": "基本类型（base type）是类型说明符，可以用const修饰，在声明语句中位于声明符之前。基本类型提供了最常见的数据类型，以此为基础构建声明符。\n\n一条声明语句由一个基本数据类型和紧随其后的一个声明符列表组成。每个声明符命名了一个变量并指定该变量为基本数据类型有关的某种类型。\n\n如（个人理解）：\n\n```\nSales_data      sd;\n^               ^\n基本类型        声明符\n类型说明符\n```\n\n见p45。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/bind.md",
    "content": "绑定（bind）令某个名字与给定的实体关联在一起，使用该名字也就是使用该实体。例如，引用就是将某个名字与某个对象绑定在一起。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/byte.md",
    "content": "字节（byte）内存中可寻址的最小单元，大多数机器的字节占8位。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/class_member.md",
    "content": "类成员（class member）类的组成部分。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/compound_type.md",
    "content": "复合类型（compound type）是一种类型，它的定义以其他类型为基础。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/const.md",
    "content": "const 是一种类型修饰符，用于说明永不改变的对象。const对象一旦定义就无法再赋新值，所以必须初始化。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/const_expression.md",
    "content": "常量表达式（const expression）能在编译时计算并获取结果的表达式。\n\nC++11新标准规定，允许将变量声明为constexpr类型以便编译器来验证变量的值是否是一个常量表达式。\n\n见p58。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/const_pointer.md",
    "content": "常量指针（const pointer）是一种指针，它的值永不改变。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/const_reference.md",
    "content": "常量引用（const reference）是一种习惯叫法，含义是指向常量的引用。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/conversion.md",
    "content": "转换（conversion）一种类型的值转变成另外一种类型值的过程。C++语言支持内置类型之间的转换。\n\n见p32。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/data_member.md",
    "content": "数据成员（data member）组成对象的数据元素，类的每个对象都有类的数据成员的一份拷贝。数据成员可以在类内部声明的同时初始化。\n\nmy note: 所谓声明的同时初始化，应该是值类内初始值吧？\n\n见p65。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/declaration.md",
    "content": "声明（declaration）声称存在一个变量、函数或者别处定义的类型。名字必须在定义或声明之后才能使用。\n\n为了支持分离式编译，C++语言将声明和定义区分开来。声明使得名字为程序所知，一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义负责创建与名字关联的实体。\n\n见p41。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/declarator.md",
    "content": "声明符（declarator）是声明的一部分，包括被定义的名字和类型修饰符，其中类型修饰符可以有也可以没有。\n\n见p45。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/decltype.md",
    "content": "decltype 是一个类型说明符，从变量或表达式推断得到类型。\n\ndeletype的作用是选择并返回操作数的数据类型。在此过程中，编译器分析表达式并得到它的类型，却不实际计算表达式的值：\n\n```\ndecltype(f()) sum = x; // sum的类型就是函数f的返回类型\n```\n\n见p62。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/default_initialization.md",
    "content": "默认初始化（default initialization）当对象未被显式地赋予初始值时执行的初始化行为。由类本身负责执行的类对象的初始化行为。全局作用域的内置类型对象初始化为0；局部作用域的对象未被初始化即拥有未定义的值。\n\n见p40。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/definition.md",
    "content": "定义（definition）为某一特定类型的变量申请存储空间，可以选择初始化该变量。名字必须在定义或声明之后才能使用。\n\n变量能且只能被定义一次，但是可以被多次声明。\n\n见p41。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/escape_sequence.md",
    "content": "转义序列（escape sequence）字符特别是那些不可打印字符的替代形式。转义以反斜线开头，后面紧跟一个字符，或者不多于3个八进制数字，或者字母x加上1个十六进制数。\n\n见p36。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/global_scope.md",
    "content": "全局作用域（global scope）位于其他所有作用域之外的作用域。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/header_guard.md",
    "content": "头文件保护符（header guard）使用预处理变量以防止头文件被某个文件重复包含。\n\n见p68。\n\nmy note: 还可以使用另一种方法，即在文件开头使用`#pragma once`令编译器保证同一个文件不被包含多次。其功能和header guard只有一点细微的区别：\n\n- 可以避免因header guard重复所导致的奇怪编译错误\n\n- 不是所有编译器都支持\n\n参考资料：http://www.cnblogs.com/Braveliu/archive/2012/12/29/2838726.html\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/identifier.md",
    "content": "标识符（identifier）组成名字的字符序列，标识符对大小写敏感。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/in-class_initializer.md",
    "content": "类内初始值（in-class initializer）在声明类的[数据成员](./data_member.md)时同时提供的初始值，必须置于等号右侧或花括号内。\n\n见p65。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/list_initialization.md",
    "content": "列表初始化（list initialization）利用花括号把一个或多个初始值放在一起的初始化形式。\n\n对于内置类型，这种初始化形式有一个重要特点：如果我们使用列表初始化且初始值存在丢失信息的风险，编译器将报错。\n\n见p39。\n\n**其他笔记**\n\n数组，可以对数组进行列表初始化，如果没有忽略维度，且初始值数量小于维度，剩下的元素被初始化成默认值（执行值初始化），见p102。\n\n结构体，对于聚合类，可以提供一个花括号括起来的成员初始值列表，如果初始值列表中的元素个数少于成员数量，靠后的成员被值初始化，见p267。\n\n容器类型也支持列表初始化，如：`C c = {a,b,c...}`，c初始化为列表中元素的拷贝，任何遗漏的元素都进行值初始化，见p299。\n\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/literal.md",
    "content": "字面值（literal）是一个不能改变的值，如数字、字符、字符串等。单引号内的是字符字面值，双引号内的是[字符串字面值](../ch01/string_literal.md)。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/low-level_const.md",
    "content": "底层const（low-level const）一个不属于顶层的const，类型如果由底层常量定义，则不能忽略。\n\nmy note: 即指针所指的对象，或者引用所绑定的对象是一个常量，那么这个指针就是底层const的。\n\n见p57。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/nonprintable_character.md",
    "content": "不可打印字符（nonprintable character）不具有可见形式的字符，如控制符、退格、换行符等。\n\n可以使用函数`isprint(c)`判断一个字符是否是可打印的字符。见p82。\n\n见p36。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/null_pointer.md",
    "content": "空指针（null pointer）值为0的指针，空指针合法但是不指向任何对象。\n\n见p48。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/nullptr.md",
    "content": "nullptr 是表示空指针的字面值常量。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/object.md",
    "content": "对象（object）是内存的一块区域，具有某种类型，变量是命名了的对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/pointer.md",
    "content": "指针（pointer）是一个对象，存放着某个对象的地址，或者某个对象存储区域之后的下一个地址，或者0。\n\n指针的值（即地址）应属下列4种状态之一：\n\n1. 指向一个对象。\n\n2. 指向紧邻对象所占空间的下一个位置。\n\n3. 空指针。\n\n4. 无效指针，也就是上述情况之外的其他值。\n\n见p47。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/pointer_to_const.md",
    "content": "指向常量的指针（pointer to const）是一个指针，存放着某个常量对象的地址。指向常量的指针不能用来改变它所指对象的值。\n\n见p56。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/preprocessor.md",
    "content": "预处理器（preprocessor）在C++编译过程中执行的一段程序。\n\n见p68。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/preprocessor_variable.md",
    "content": "预处理器变量（preprocessor variable）由预处理器管理的变量。在程序编译之前，预处理器负责将程序中的预处理变量替换成它的真实值。\n\nmy note: 比如NULL就是一个预处理器变量，它的值为0。\n\n见p49。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/reference.md",
    "content": "引用（reference）是某个对象的别名。\n\n见p45。\n\n**其他笔记**\n\n引用绑定到一个对象意味着它可能失效，即这个对象可能已经不再存在，比如返回局部变量的引用。见p201。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/reference_to_const.md",
    "content": "对常量的引用（reference to const）是一个引用，不能用来改变它所绑定对象的值。对常量的引用可以绑定常量对象，或者非常量对象，或者表达式的结果。\n\n见p54。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/scope.md",
    "content": "作用域（scope）是程序的一部分，在其中某些名字有意义。C++有几级作用域：\n\n- 全局（global）：名字定义在所有其他作用域之外。\n\n- 类（class）：名字定义在类内部。\n\n- 命名空间（namespace）：名字定义在命名空间内部。\n\n- 块（block）：名字定义在块内部。\n\n名字从声明位置开始直至声明语句所在的作用域末端为止都是可用的。\n\n见p43。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/separate_complilation.md",
    "content": "分离式编译（separate compilation）把程序分割为多个单独文件的能力。\n\n见p41。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/signed.md",
    "content": "带符号类型（signed）保存正数、负数或0的整型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/string.md",
    "content": "字符串（string）是一种库类型，表示可变长字符序列。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/temporary.md",
    "content": "临时值（temporary）编译器在计算表达式结果时创建的无名对象。为某表达式创建了一个临时值，则此临时值将一直存在直到包含有该表达式的最大的表达式计算完成为止。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/top-level_const.md",
    "content": "顶层const（top-level const）是一个const，规定某对象的值不能改变。\n\n见p57。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/type_alias.md",
    "content": "类型别名（type alias）是一个名字，是另一个类型的同义词，通过关键字typedef或[别名声明](./alias_declaration.md)语句来定义。\n\n见p60。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/type_checking.md",
    "content": "类型检查（type checking）是一个过程，编译器检查程序使用某给定类型对象的方式与该类型的定义是否一致。\n\n见p42。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/type_specifier.md",
    "content": "类型说明符（type specifier）类型的名字。\n\n见p38。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/undefined.md",
    "content": "未定义（undefined）即C++语言没有明确规定的情况。不论是否有意为之，未定义行为都可能引发难以追踪的运行时错误、安全问题和可移植性问题。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/uninitialized.md",
    "content": "未初始化（uninitialized）变量已定义但未被赋予初始值。一般来说，试图访问未初始化变量的值将引发[未定义](./undefined.md)的行为。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/unsigned.md",
    "content": "无符号类型（unsigned）保存大于等于0的整型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/variable.md",
    "content": "变量（variable）命名的对象或引用。C++语言要求变量先声明后使用。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/void.md",
    "content": "void类型 是一种有特殊用途的类型，既无操作也无值。不能定义一个void类型的变量。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch02/word.md",
    "content": "字（word）在指定机器上进行整数运算的自然单位。一般来说，字的空间足够存放地址。32位机器上的字通常占据4个字节。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/C-style_string.md",
    "content": "C风格字符串（C-style string）以空字符结束的字符数组。字符串字面值是C风格字符串，C风格字符串容易出错。\n\n见p109。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/begin.md",
    "content": "begin 是string和vector的成员，返回指向第一个元素的迭代器。也是一个标准库函数，输入一个数组，返回指向该数组首元素的指针。\n\n见p95。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/buffer_overflow.md",
    "content": "缓冲区溢出（buffer overflow）一种严重的程序故障，主要的原因是试图通过一个越界的索引访问容器内容，容器类型包括string、vector和数组等。\n\n见p94。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/class_template.md",
    "content": "类模板（class template）用于创建具体类类型的模板。要想使用类模板，必须提供关于类型的辅助信息。例如，要定义一个vector对象需要指定元素的类型：vector<int>包含int类型的元素。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/compiler_extension.md",
    "content": "编译器扩展（compiler extension）某个特定的编译器为C++语言额外增加的特性。基于编译器扩展编写的程序不易移植到其他编译器上。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/container.md",
    "content": "容器（container）是一种类型，其对象容纳了一组给定类型的对象。vector是一种容器类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/copy_initialization.md",
    "content": "拷贝初始化（copy initialization）使用赋值号（=）的初始化形式。新创建的对象是初始值的一个副本。\n\n见p76。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/difference_type.md",
    "content": "difference_type 由string和vector定义的一种带符号的整数类型，表示两个迭代器之间的距离。\n\n见p100。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/direct_initialization.md",
    "content": "直接初始化（direct initialization）不使用赋值号（=）的初始化形式。\n\n```\nstring s1(\"hello\");\t// 直接初始化\nstring s2 = \"hello\";// 拷贝初始化\n```\n\n见p76。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/empty.md",
    "content": "empty是string和vector的成员，返回一个布尔值。当对象的大小为0时返回真，否则返回假。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/end.md",
    "content": "end 是string和vector的成员，返回一个尾后迭代器。也是一个标准库函数，输入一个数组，返回指向该数组尾元素的下一位置的指针。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/getline.md",
    "content": "getline 在string头文件中定义的一个函数，以一个istream对象和一个string对象为输入参数。该函数首先读取输入流的内容直到遇到换行符停止，然后将读入的数据存入string对象，最后返回istream对象。其中换行符读入但不保留。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/index.md",
    "content": "索引（index）是下标运算符使用的值。表示要在string对象、vector对象或者数组中访问一个位置。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/instantiation.md",
    "content": "实例化（instantiation）编译器生成一个指定的模板类或函数的过程。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/iterator.md",
    "content": "迭代器（iterator）是一种类型，用于访问容器中的元素或者在元素之间移动。\n\n见p95。\n\n**其他笔记**\n\n某些对容器的操作可能使迭代器失效。见p98、p315。以我的理解，迭代器指向了一个元素，如果这个元素不再存在（不再是原来的对象了），那么这个迭代器就失效了，因为它指向了一个不再存在的对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/iterator_arithmetic.md",
    "content": "迭代器运算（iterator arithmetic）是string或vector的迭代器运算。迭代器与整数相加或相减得到一个新的迭代器，与原来的迭代器相比，新的迭代器向前或向后移动了若干个位置。两个迭代器相减得到它们之间的距离，此时它们必须指向同一个容器的元素或该容器尾元素的下一位置。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/off-the-end_iterator.md",
    "content": "尾后迭代器（off-the-end iterator）end函数返回的迭代器，指向一个并不存在的元素，该元素位于容器尾元素的下一位置。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/pointer_arithmetic.md",
    "content": "指针运算（pointer arithmetic）是指针类型支持的算术运算。指向数组的指针所支持的运算种类与[迭代器运算](./iterator_arithmetic.md)一样。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/prtdiff_t.md",
    "content": "prtdiff_t 是cstddef头文件中定义的一种与机器实现有关的带符号整数类型，它的空间足够大，能够表示数组中任意两个指针之间的距离。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/push_back.md",
    "content": "push_back 是vector的成员，向vector对象的末尾添加元素。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/range_for.md",
    "content": "范围for语句（range for）一种控制语句，可以在值的一个特定集合内迭代。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/size.md",
    "content": "size 是string和vector的成员，分别返回字符的数量或元素的数量。返回值的类型是size_type。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/size_t.md",
    "content": "size_t 是cstddef头文件中定义的一种与机器实现有关的无符号整数类型，它的空间足够大，能够表示任意数组的大小。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/size_type.md",
    "content": "size_type 是string和vector定义的类型的名字，能存放下任意string对象或vector对象的大小。在标准库中，size_type被定义为无符号类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/string.md",
    "content": "string是一种标准库类型，表示字符的序列。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/using_declaration.md",
    "content": "using声明（using declaration）令命名空间中的某个名字可被程序直接使用。\n\n```\nusing 命名空间::名字;\n```\n\n上述语句的作用是令程序可以直接使用名字，而无须写它前缀部分命名空间::。\n\n如：\n\n```\nusing std::string;\n```\n\nusing声明不应该放在头文件中，以免被不经意的包含。\n\n见p74。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/value_initialization.md",
    "content": "值初始化（value initialization）是一种初始化过程。内置类型初始化为0，类类型由类的默认构造函数初始化。只有当类包含默认构造函数时，该类的对象才会被值初始化。对于容器的初始化来说，如果只说明了容器的大小而没有指定初始值的话，就会执行值初始化。此时编译器会生成一个值，而容器的元素被初始化为该值。\n\n见p88。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/vector.md",
    "content": "vector 是一种[标准库](../ch01/standard_library.md)类型，容纳某指定类型的一组元素。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/下标运算符.md",
    "content": "[]运算符（[]operator）下标运算符。obj[j]得到容器对象obj中位置j的那个元素。索引从0开始，第一个元素的索引是0，尾元素的索引是obj.size()-1。下标运算符的返回值是一个对象。如果p是指针，n是整数，则p[n]与*(p+n)等价。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/箭头运算符.md",
    "content": "->运算符（->operator）箭头运算符，该运算符综合了解引用和点操作符。a->b等价于`(*a).b`。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/输入运算符.md",
    "content": "`>>运算符`（>>operator）标准库类型string定义的输入运算符，负责读入一组字符，遇到空白停止，读入的内容赋给运算符右侧的运算对象，该运算对象应该是一个string对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/输出运算符.md",
    "content": "<<运算符（<<operator）标准库类型string定义的输出运算符，负责输出string对象中的字符。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/递增运算符.md",
    "content": "++运算符（++operator）是迭代器和指针定义的递增运算符。执行加1操作使得迭代器指向下一个元素。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/逻辑与运算符.md",
    "content": "&&运算符（&&operator）逻辑与运算符。如果两个运算对象都为真，结果为真。只有当左侧运算对象为真时才会检查右侧运算对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/逻辑或运算符.md",
    "content": "||运算符（||operator）逻辑或运算符，任何一个运算对象是真，结果就为真。只有当左侧运算对象为假时才会检查右侧运算对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch03/逻辑非运算符.md",
    "content": "!运算符（!operator）逻辑非运算符。将它的运算对象的布尔值取反。如果运算对象是假，则结果为真，如果运算对象是真，则结果为假。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/arithmetic_conversion.md",
    "content": "算术转换（arithmetic conversion）从一种算术类型转换成另一种算术类型。在二元运算符的上下文中，为了保留精度，算术转换通常把较小的类型转换成较大的类型（例如整型转换成浮点型）。\n\n见p142。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/associativity.md",
    "content": "结合律（associativity）规定具有相同优先级的运算符如何组合在一起。结合律分为左结合律（运算符从左向右组合）和右结合律（运算符从右向左组合）。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/binary_operator.md",
    "content": "二元运算符（binary operator）有两个运算对象参与运算的运算符。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/cast.md",
    "content": "强制类型转换（cast）一种显式的类型转换。\n\n见p144。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/compound_expression.md",
    "content": "复合表达式（compound expression）含有多于一个运算符的表达式。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/const_cast.md",
    "content": "const_cast 一种涉及const的[强制类型转换](./cast.md)。将底层const对象转换成对应的非常量类型，或者执行相反的转换。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/conversion.md",
    "content": "转换（conversion）一种类型的值改变成另一种类型的值的过程。C++语言定义了内置类型的转换规则。类类型同样可以转换。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/expression.md",
    "content": "表达式（expression）C++程序中最低级别的计算。表达式将运算符作用于一个或多个运算对象，每个表达式都有对应的求值结果。表达式本身也可以作为运算对象，这时就得到了多个运算符求值的[复合表达式](./compound_expression.md)。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/implicit_conversion.md",
    "content": "隐式转换（implicit conversion）由编译器自动执行的类型转换。假如表达式需要某种特定的类型而运算对象是另外一种类型，此时只要规则允许，编译器就会自动地将运算对象转换成所需的类型。\n\n见p141。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/integral_promotion.md",
    "content": "整型提升（integral promotion）把一种较小的整数类型转换成与之最接近的较大整数类型的过程。不论是否真的需要，小整数类型（即short、char等）总是会得到提升。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/lvalue.md",
    "content": "左值（lvalue）是指那些求值结果为对象或者函数的表达式。一个表示对象的非常量左值可以作为赋值运算符的左侧运算对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/operand.md",
    "content": "运算对象（operand）表达式在某些值上执行运算，这些值就是运算对象。一个运算符有一个或多个相关的运算对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/operator.md",
    "content": "运算符（operator）决定表达式所做操作的符号。C++语言定义了一套运算符并说明了这些运算符作用于内置类型时的含义。C++还定义了运算符的优先级和结合律以及每种运算符处理的运算对象的数量。可以重载运算符使其能处理类类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/order_of_evaluation.md",
    "content": "求值顺序（order of evaluation）是某个运算符的运算对象的求值顺序。大多数情况下，编译器可以任意选择运算对象的求值顺序。不过运算对象一定要在运算符之前得到求值结果。只有&&、||、条件和逗号四种运算符明确规定了求值顺序。\n\n见p123。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/overloaded_operator.md",
    "content": "重载运算符（overloaded operator）针对某种运算符重新定义的适用于类类型的版本。第14章将介绍重载运算符的方法。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/precedence.md",
    "content": "优先级（precedence）规定了复合表达式中不同运算符的执行顺序。与低优先级的运算符相比，高优先级的运算符组合得更紧密。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/reinterpret_case.md",
    "content": "reinterpret_cast 把运算对象的内容解释成另外一种类型。这种强制类型转换本质上依赖于机器且非常危险。\n\n见p145。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/rvalue.md",
    "content": "右值（rvalue）是指一种表达式，其结果是值而非值所在的位置。\n\n右值不能放在赋值运算符的左侧。\n\n见p121。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/short-circuit_evaluation.md",
    "content": "短路求值（short-circuit evaluation）是一个专有名词，描述逻辑与运算符和逻辑或运算符的执行过程。如果根据运算符的第一个运算对象就能确定整个表达式的结果，求值终止，此时第二个运算对象将不会被求值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/sizeof.md",
    "content": "sizeof 是一个运算符，返回存储对象所需的字节数，该对象的类型可能是某个给定类型的名字，也可能由表达式的返回结果确定。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/static_cast.md",
    "content": "static_cast 显式地执行某种定义明确的类型转换，常用于替代由编译器隐式执行的类型转换。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/unary_operators.md",
    "content": "一元运算符（unary operators）只有一个运算对象参与运算的运算符。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/位与运算符.md",
    "content": "&运算符（&operator）位与运算符，由两个运算对象生成一个新的整型值。如果两个运算对象对应的位都是1，所得结果中该位为1；否则所得结果中该位为0。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/位异或运算符.md",
    "content": "^运算符（^operator）位异或运算符，由两个运算对象生成一个新的整型值。如果两个运算对象对应的位有且只有一个是1，所得结果中该位为1；否则所得结果中该位为0。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/位或运算符.md",
    "content": "|运算符（|operator）位或运算符，由两个运算对象生成一个新的整型值。如果两个运算对象对应的位至少有一个是1，所得结果中该位为1；否则所得结果中该位为0。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/位求反运算符.md",
    "content": "~运算符（~operator）位求反运算符，生成一个新的整型值。该值的每一位恰好与（可能是提升后的）运算对象的对应位相反。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/右移运算符.md",
    "content": "`>>运算符`（>>operator）右移运算符，除了移动方向相反，其他性质都和左移运算符类似。如果左侧运算对象是带符号类型，那么根据实现的不同新移入的内容也不同，新移入的位可能都是0，也可能都是符号位的副本。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/左移运算符.md",
    "content": "<<运算符（<<operator）左移运算符，将左侧运算对象的值的（可能是提升后的）副本向左移位，移动的位数由右侧运算对象确定。右侧运算对象必须大于等于0而且小于结果的位数。左侧运算对象应该是无符号类型，如果它是带符号类型，则一旦移动改变了符号位的值就会产生未定义的结果。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/条件运算符.md",
    "content": "?:运算符（?:operator）条件运算符，以下述形式提供if-then-else逻辑的表达式\n\n```\ncond ? expr1 : expr2;\n```\n\n如果条件cond为真，对expr1求值；否则对expr2求值。expr1和expr2的类型应该相同或者能转换成同一种类型。expr1和expr2中只有一个会被求值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/递减运算符.md",
    "content": "--运算符（--operator）递减运算符。包括两种形式：前置版本和后置版本。前置递减运算符得到一个左值，它给运算符减1并得到运算对象改变后的值。后置递减运算符得到一个右值，它给运算符减1并得到运算对象原始的、未改变的值的副本。注意：即使迭代器没有定义-运算符，也会有--运算符。\n\n见p131。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/递增运算符.md",
    "content": "++运算符（++operator）递增运算符。包括两种形式：前置版本和后置版本。前置递增运算符得到一个左值，它给运算符加1并得到运算对象改变后的值。后置递增运算符得到一个右值，它给运算符加1并得到运算对象原始的、未改变的值的副本。注意：即使迭代器没有定义+运算符，也会有++运算符。\n\n见p131。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch04/逗号运算符.md",
    "content": ",运算符（,operator）逗号运算符，是一种从左向右求值的二元运算符。逗号运算符的结果是右侧运算对象的值，当且仅当右侧运算对象是左值时逗号运算符的结果是左值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/block.md",
    "content": "块（block）包围在花括号内的由0条或多条语句组成的序列。块也是一条语句，所以只要是能使用语句的地方，就可以使用块。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/break_statement.md",
    "content": "break语句（break statement）终止离它最近的循环或switch语句。控制权转移到循环或switch之后的第一条语句。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/case_label.md",
    "content": "case标签（case label）在switch语句中紧跟在case关键字之后的[常量表达式](../ch02/const_expression.md)。在同一个switch语句中任意两个case标签的值不能相同。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/catch_clause.md",
    "content": "catch子句（catch clause）由三部分组成：catch关键字、括号里的异常声明以及一个语句块。catch子句的代码负责处理在异常声明中定义的异常。\n\n见p173。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/compound_statement.md",
    "content": "复合语句（compound statement）和[块](./block.md)是同义词。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/continue_statement.md",
    "content": "continue语句（continue statement）终止离它最近的循环的当前迭代。控制权转移到while或do while语句的条件部分、或者范围for循环的下一次迭代、或者传统for循环头部的表达式。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/dangling_else.md",
    "content": "悬垂else（dangling else）是一个俗语，指的是如何处理嵌套if语句中if分支多于else分支的情况。C++语言规定，else应该与前一个未匹配的if匹配在一起。使用花括号可以把位于内层的if语句隐藏起来，这样程序员就能更好地控制else该与哪个if匹配。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/default_label.md",
    "content": "default标签（default label）是一种特殊的case标签，当switch表达式的值与所有case标签都无法匹配时，程序执行default标签下的内容。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/do_while_statement.md",
    "content": "do while语句（do while statement）与while语句类似，区别是do while语句先执行循环体，再判断条件。循环体代码至少会执行一次。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/exception_class.md",
    "content": "异常类（exception class）是标准库定义的一组类，用于表示程序发生的错误。表5.1列出了不同用途的异常类。\n\n见p176。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/exception_declaration.md",
    "content": "异常声明（exception declaration）位于catch子句中的声明，指定了该[catch子句](./catch_clause.md)能处理的异常类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/exception_handler.md",
    "content": "异常处理代码（exception handler）程序某处引发异常后，用于处理该异常的另一处代码。和[catch子句](./catch_clause.md)是同义词。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/exception_safe.md",
    "content": "异常安全（exception safe）是一个术语，表示的含义是当抛出异常后，程序能执行正确的行为。\n\n如何编写异常安全的代码远远超出了本书的范围，本书仅介绍一些比较常规的提升异常安全性的技术。\n\n见p175。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/expression_statement.md",
    "content": "表达式语句（expression statement）即一条[表达式](../ch04/expression.md)后面跟上一个分号，令表达式执行求值过程。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/flow_of_control.md",
    "content": "控制流（flow of control）程序的执行路径。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/for_statement.md",
    "content": "for语句（for statement）提供迭代执行的迭代语句。常常用于遍历一个容器或者重复计算若干次。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/goto_statement.md",
    "content": "goto语句（goto statement）令控制权无条件转移到同一函数中一个指定的带标签语句。goto语句容易造成程序的控制流混乱，应禁止使用。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/if_else_statement.md",
    "content": "if else语句（if else statement）判断条件，根据其结果分别执行if分支或else分支的语句。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/if_statement.md",
    "content": "if语句（if statement）判断条件，根据其结果有选择地执行语句。如果条件为真，执行if分支的代码；如果条件为假，控制权转移到if结构之后的第一条语句。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/labeled_statement.md",
    "content": "带标签语句（labeled statement）前面带有标签的语句。所谓标签是指一个标识符以及紧跟着的一个冒号。对于同一个标识符来说，用作标签的同时还能用于其他目的，互不干扰。\n\n见p172。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/null_statement.md",
    "content": "空语句（null statement）只含有一个分号的语句。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/raise.md",
    "content": "引发（raise）含义类似于throw。在C++语言中既可以说抛出异常，也可以说引发异常。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/range_for_statment.md",
    "content": "范围for语句（range for statement）在一个序列中进行迭代的语句。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/switch_statement.md",
    "content": "switch语句（switch statement）一种条件语句，首先求switch关键字后面表达式的值，如果某个case标签的值与表达式的值相等，程序直接跨过之前的代码从这个case标签开始执行。当所有case标签都无法匹配时，如果有default标签，从default标签继续执行；如果没有，结束switch语句。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/terminate.md",
    "content": "terminate 是一个标准库函数，当异常没有被捕捉到时调用。terminate终止当前程序的执行。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/throw_expression.md",
    "content": "throw表达式（throw expression）一种中断当前执行路径的表达式。throw表达式抛出一个异常并把控制权转移到能处理该异常的最近的catch子句。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/try_block.md",
    "content": "try语句块（try block）跟在try关键字后面的块，以及一个或多个[catch子句](./catch_clause.md)。如果try语句块的代码引发异常并且其中一个catch子句匹配该异常类型，则异常被该catch子句处理。否则，异常将由外围try语句块处理，或者程序终止。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch05/while_statement.md",
    "content": "while语句（while statement）只要指定的条件为真，就一直迭代执行目标语句。随着条件真值的不同，循环可能执行多次，也可能一次也不执行。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/ambiguous_call.md",
    "content": "二义性调用（ambiguous call）是一种编译时发生的错误，造成二义性调用的原因是在函数匹配时两个或多个函数提供的匹配一样好，编译器找不到唯一的最佳匹配。\n\n见p210。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/argument.md",
    "content": "实参（argument）函数调用时提供的值，用于初始化函数的形参。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/assert.md",
    "content": "Assert 是一个预处理宏，作用于一条表示条件的表达式。当未定义预处理变量NDEBUG时，assert对条件求值。如果条件为假，输出一条错误信息并终止当前程序的执行。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/automatic_object.md",
    "content": "自动对象（autometic object）仅存在于函数执行过程的对象。当程序的控制流经过此类对象的定义语句时，创建该对象，当到达了定义所在的块的末尾时，销毁该对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/best_match.md",
    "content": "最佳匹配（best match）从一组重载函数中为调用选出的一个函数。如果存在最佳匹配，则选出的函数与其他所有可行函数相比，至少在一个实参上是更优的匹配，同时在其他实参的匹配上不会更差。\n\n见p218。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/candidate_function.md",
    "content": "候选函数（candidate function）解析某次函数调用时考虑的一组函数。候选函数的名字应该与函数调用使用的名字一致。并且在调用点候选函数的声明在作用域之内。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/constexpr.md",
    "content": "constexpr 可以返回常量表达式的函数，一个constexpr函数被隐式地声明成内联函数。\n\n定义constexpr要遵循这样的约定：函数的返回类型及所有形参的类型都得是字面值类型，而且函数体中必须有且只有一条return语句。\n\n见p214。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/default_argument.md",
    "content": "默认实参（default argument）当调用缺少了某个实参时，为该实参指定的默认值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/executable_file.md",
    "content": "可执行文件（executable file）是操作系统能够执行的文件，包含着与程序有关的代码。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/function_matching.md",
    "content": "函数匹配（function matching）编译器解析重载函数调用的过程，在此过程中，实参与每个重载函数的形参列表逐一比较。\n\n见p217。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/function_prototype.md",
    "content": "函数原型（function prototype）函数的声明，包含函数名字、返回类型和形参类型。要想调用某函数，在调用点之前必须声明该函数的原型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/hidden_name.md",
    "content": "隐藏名字（hidden name）某个作用域内声明的名字会隐藏掉外层作用域中声明的同名实体。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/initializer_list.md",
    "content": "initializer_list 是一个标准类，表示的是一个组花括号包围的类型相同的对象，对象之间以逗号隔开。\n\n见p197。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/inline_function.md",
    "content": "内联函数（inline function）请求编译器在可能的情况下在调用点展开函数。内联函数可以避免常见的函数调用开销。\n\n应当把内联函数和constexpr函数定义在头文件中。\n\n见p213。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/link.md",
    "content": "链接（link）是一个编译过程，负责把若干对象文件链接起来形成[可执行程序](./executable_file.md)。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/local_static_object.md",
    "content": "局部静态对象（local static object）它的值在函数调用结束后仍然存在。在第一次使用局部静态对象前创建并初始化它，当程序结束时局部静态对象才被销毁。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/local_variable.md",
    "content": "局部变量（local variable）定义在块中的变量。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/no_match.md",
    "content": "无匹配（no match）是一种编译时发生的错误，原因是在函数匹配过程中所有函数的形参都不能与调用提供的实参匹配。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/object_code.md",
    "content": "对象代码（object code）编译器将我们的源代码转换成对象代码格式。\n\n也叫目标代码。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/object_file.md",
    "content": "对象文件（object file）编译器根据给定的源文件生成的保存[对象代码](./object_code.md)的文件。一个或多个对象文件经过[链接](./link.md)生成[可执行文件](./executable_file.md)。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/object_lifetime.md",
    "content": "对象生命周期（object lifetime）每个对象都有相应的生命周期。块内定义的非静态对象的生命周期从它的定义开始，到定义所在的块末尾为止。程序启动后创建全局对象，程序控制流经过局部静态对象的定义时创建该局部静态对象；当main函数结束时销毁全局对象和局部静态对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/overloaded_function.md",
    "content": "重载函数（overloaded function）函数名与其他函数相同的函数。多个重载函数必须在形参数量或形参类型上有所区别。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/parameter.md",
    "content": "形参（parameter）在函数的形参列表中声明的局部变量。用实参初始化形参。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/pass_by_reference.md",
    "content": "引用传递（pass by reference）描述如何将实参传递给引用类型的形参。引用形参和其他形式的引用工作机理类似，形参被绑定到相应的实参上。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/pass_by_value.md",
    "content": "值传递（pass by value）描述如何将实参传递给非引用类型的形参。非引用类型的形参实际上是相应实参值的一个副本。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/preprocessor_macro.md",
    "content": "预处理宏（preprocessor macro）类似于内联函数的一种预处理功能。除了assert之外，现代C++程序很少再使用预处理宏了。\n\n见p215。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/recursion_loop.md",
    "content": "递归循环（recursion loop）描述某个递归函数没有终止条件，因而不断调用自身直至耗尽程序栈空间的过程。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/recursive_function.md",
    "content": "递归函数（recursive function）直接或间接调用自身的函数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/return_type.md",
    "content": "返回类型（return type）是函数声明的一部分，用于指定函数返回值的类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/trailing_return_type.md",
    "content": "尾置返回类型（trailing return type）在参数列表后面指定的返回类型。\n\n如：\n\n```\n// func接受一个int类型的实参，返回一个指针，该指针指向含有10个整数的数组\nauto func(int i) -> int(*)[10];\n```\n\n见p206。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch06/viable_function.md",
    "content": "可行函数（viable function）是候选函数的子集。可行函数能匹配本次调用，它的形参数量与调用提供的实参数量相等，并且每个实参类型都能转换成相应的形参类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/=default.md",
    "content": "=default 一种语法形式，位于类内部默认构造函数声明语句的参数列表之后，要求编译器生成构造函数，而不管类是否已经有了其他构造函数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/abstract_data_type.md",
    "content": "抽象数据类型（abstract data type）封装（隐藏）了实现细节的数据结构。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/access_specifier.md",
    "content": "访问说明符（access specifier）包括关键字public和private。用于定义成员对类的用户可见还是只对类的友元和成员可见。在类中说明符可以出现多次，每个说明符的有效范围从它自身开始，到下一个说明符为止。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/aggregate_class.md",
    "content": "聚合类（aggregate class）只含有公有成员的类，并且没有类内初始值或者构造函数。聚合类的成员可以用花括号括起来的初始值列表进行初始化。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/class.md",
    "content": "类（class）C++提供的自定义数据类型的机制。类可以包含数据、函数和类型成员。一个类定义一种新的类型和一个新的作用域。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/class_declaration.md",
    "content": "类的声明（class declaration）首先是关键字class（或者struct），随后是类名以及分号。如果类已经声明而尚未定义，则它是一个不完全类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/class_scope.md",
    "content": "类的作用域（class scope）每个类定义一个作用域。类作用域比其他作用域更加复杂，类中定义的成员函数甚至有可能使用定义语句之后的名字。\n\n见p253。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/const_member_function.md",
    "content": "常量成员函数（const member function）一个成员函数，在其中不能修改对象的普通（即既不是static也不是mutable）数据成员。const成员的this指针是指向常量的指针，通过区分函数是否是const可以进行重载。\n\n见p231。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/constructor.md",
    "content": "构造函数（constructor）用于初始化对象的一种特殊的成员函数。构造函数应该给每个数据成员都赋一个合适的初始值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/constructor_initializer_list.md",
    "content": "构造函数初始值列表（constructor initializer list）说明一个类的数据成员的初始值，在构造函数体执行之前首先用初始值列表中的值初始化数据成员。未经初始值列表初始化的成员将被[默认初始化](../ch02/default_initialization.md)。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/converting_constructor.md",
    "content": "转换构造函数（converting constructor）可以用一个实参调用的非显式构造函数。这样的函数隐式地将参数类型转换成类类型。\n\n见p263。\n\n如果构造函数只接受一个实参，则它实际上定义了转换为此类类型的隐式转换机制。\n\n比如：\n\n```\nstring s;\ns = \"hello\"; // \"hello\"转换成string，然后再赋给s\n```\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/data_abstraction.md",
    "content": "数据抽象（data abstraction）着重关注类型接口的一种编程技术。数据抽象令程序员可以忽略类型的实现细节，只关注类型执行的操作即可。数据抽象是面向对象编程和泛型编程的基础。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/default_constructor.md",
    "content": "默认构造函数（default constructor）当没有提供任何实参时使用的构造函数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/delegating_constructor.md",
    "content": "委托构造函数（delegating constructor）委托构造函数的初始值列表只有一个入口，指定类的另一个构造函数执行初始化操作。\n\n见p261。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/encapsulation.md",
    "content": "封装（encapsulation）分离类的实现与接口，从而隐藏了类的实现细节。在C++语言中，通过把实现部分设为private完成封装的任务。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/explicit_constructor.md",
    "content": "显式构造函数（explicit constructor）可以用一个单独的实参调用但是不能用于[隐式转换的构造函数](./converting_constructor.md)。通过在构造函数的声明之前加上explicit关键字就可以将其声明成显示构造函数。\n\n见p265。\n\n只能在类内声明构造函数时使用explicit关键字，在类外部定义时不应重复。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/forward_declaration.md",
    "content": "前向声明（forward declaration）对尚未定义的名字的声明，通常用于表示位于类定义之前的类声明。参见[不完全类型](./incomplete_type.md)。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/friend.md",
    "content": "友元（friend）类向外部提供其非公有成员访问权限的一种机制。友元的访问权限与成员函数一样。友元可以是类，也可以是函数。\n\n见p241。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/implementation.md",
    "content": "实现（implementation）类的成员（通常是私有的），定义了不希望为使用类类型的代码所用的数据及任何操作。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/incomplete_type.md",
    "content": "不完全类型（incomplete type）已经声明但是尚未定义的类型。不完全类型不能用于定义变量或者类的成员，但是用不完全类型定义指针或者引用是合法的。\n\n见p250。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/interface.md",
    "content": "接口（interface）类型提供的（公有）操作。通常情况下，接口不包含数据成员。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/member_function.md",
    "content": "成员函数（member function）类的函数成员。普通的成员函数通过隐式的this指针与类的对象绑定在一起；静态成员函数不与对象绑定在一起也没有this指针。成员函数可以重载，此时隐式的this指针参与函数匹配的过程。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/mutable_data_member.md",
    "content": "可变数据成员（mutable data member）这种成员永远不是const，即使它属于const对象。在[const函数](./const_member_function.md)内可以修改可变数据成员。\n\n见p245。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/name_lookup.md",
    "content": "名字查找（name lookup）根据名字的使用寻找匹配的声明的过程。\n\n见p254。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/private_member.md",
    "content": "私有成员（private member）定义在private访问说明符之后的成员，只能被类的友元或者类的其他成员访问。数据成员以及仅供本类使用而不作为接口的功能函数一般设为private。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/public_member.md",
    "content": "公有成员（public member）定义在public访问说明符之后的成员，可以被类的所有用户访问。通常情况下，只有实现类的接口的函数才被设为public。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/synthesized_default_constructor.md",
    "content": "合成默认构造函数（synthesized default constructor）对于没有显式地定义任何构造函数的类，编译器为其创建（合成）的默认构造函数。该构造函数检查类的数据成员，如果提供了类内初始值，就用它执行初始化操作；否则就对数据成员执行默认初始化。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch07/this_pointer.md",
    "content": "this指针（this pointer）是一个隐式的值，作为额外的实参传递给类的每个非静态成员函数。this指针指向代表函数调用者的对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch08/condition_state.md",
    "content": "条件状态（condition state）可被任何流类使用的一组标志和函数，用来指出给定流是否可用。\n\n见p279。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch08/file_mode.md",
    "content": "文件模式（file mode）类fstream定义的一组标志，在打开文件时指定，用来控制文件如何被使用。\n\n每个文件流类型都定义了一个默认的文件模式，当未指定时，就使用默认模式。ifstream默认模式是in，ofstream默认模式是out，fstream是in和out。\n\n见p286。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch08/file_stream.md",
    "content": "文件流（file stream）用来读写命名文件的流对象。除了普通的iostream操作，文件流还定义了open和close成员。成员函数open接受一个string或一个C风格字符串参数，指定要打开的文件名，它还可以接受一个可选的参数，指明文件打开模式。成员函数close关闭流所关联的文件，调用close后才可以调用open打开另一个文件。\n\n见p283。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch08/fstream.md",
    "content": "fstream 用于同时读写一个相同文件的文件流。默认情况下，fstream以in和out模式打开文件。\n\n见p283。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch08/ifstream.md",
    "content": "ifstream 用于从输入文件读取数据的文件流。默认情况下，ifstream以in模式打开文件。\n\n见p283。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch08/inheritance.md",
    "content": "继承（inheritance）程序设计功能，令一个类型可以从另一个类型继承接口。类ifstream和istringstream继承自istream，ofstream和ostringstream继承自ostream。第15章将介绍继承。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch08/istringstream.md",
    "content": "istringstream 用来从给定string读取数据的字符串流。\n\n见p287。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch08/ofstream.md",
    "content": "ofstream 用来向输出文件写入数据的文件流。默认情况下，ofstream以out模式打开文件。\n\n见p283。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch08/string_stream.md",
    "content": "字符串流（string stream）用于读写string的流对象。除了普通的iostream操作外，字符串流还定义了一个名为str的重载成员。调用str的无参版本会返回字符串流关联的string。调用时传递给它一个string参数，则会将字符串流与该string的一个拷贝相关联。\n\n见p287。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/adaptor.md",
    "content": "适配器（adaptor）标准库类型、函数或迭代器，它们接受一个类型、函数或迭代器，使其行为像另外一个类型、函数或迭代器一样。标准库提供了三种顺序容器适配器：stack、queue和priority_queue。每个适配器都在其底层顺序容器类型之上定义了一个新的接口。\n\n见p329。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/array.md",
    "content": "数组（array）固定大小的顺序容器。为了定义一个array，除了元素类型之外还必须给定大小。array中的元素可以用其位置下标来访问。array支持快速的随机访问。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/begin.md",
    "content": "begin 容器操作，返回一个指向容器首元素的迭代器，如果容器为空，则返回尾后迭代器。是否返回const迭代器依赖于容器的类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/cbegin.md",
    "content": "cbegin 容器操作，返回一个指向容器首元素的const_iterator，如果容器为空，则返回尾后迭代器。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/cend.md",
    "content": "cend 容器操作，返回一个指向容器尾元素之后(不存在的)的const_iterator。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/container.md",
    "content": "容器（container）保存一组给定类型对象的类型。每个标准库容器类型都是一个模板类型。为了定义一个容器，我们必须指定保存在容器中的元素的类型。除了array之外，标准库容器都是大小可变的。`\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/deque.md",
    "content": "deque 顺序容器。deque中的元素可以通过位置下标来访问。支持快速的随机访问。deque各方面都与vector类似，唯一的差别是，deque支持在容器头尾位置的快速插入和删除，而且在两端插入或删除元素都不会导致重新分配空间。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/end.md",
    "content": "end 容器操作，返回一个指向容器尾元素之后(不存在的)元素的迭代器。是否返回const迭代器依赖于容器的类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/forward_list.md",
    "content": "forward_list 顺序容器，表示一个单向链表。forward_list中的元素只能顺序访问。从一个给定元素开始，为了访问另一个元素，我们只能遍历两者之间的所有元素。forward_list上的迭代器不支持递减运算(--)。forward_list支持任意位置的快速插入(或删除)操作。与其他的容器不同，插入和删除发生在一个给定的迭代器之后的位置。因此，除了通常的尾后迭代器之外，forward_list还有一个\"首前\"迭代器。在添加新元素之后，原有的指向forward_list的迭代器仍有效。在删除元素后，只有原来指向被删除元素的迭代器才会失效。\n\n见p312。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/iterator_range.md",
    "content": "迭代器范围（iterator range）由一对迭代器指定的元素范围。第一个迭代器表示序列中的第一个元素，第二个迭代器指向最后一个元素之后的位置。如果范围为空。则两个迭代器是相等的（反之亦然，如果两个迭代器不等，则它们表示一个非空范围）。如果范围非空，则必须保证，通过反复递增第一个迭代器，可以到达第二个迭代器。通过递增迭代器，序列中每个元素都能被访问到。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/left-inclusive_interval.md",
    "content": "左闭合区间（left-inclusive interval）值范围，包含首元素，但不包含尾元素。通常表示为[i,j)，表示序列从i开始（包含）直至j结束（不包含）。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/list.md",
    "content": "list 顺序容器，表示一个双向链表。list中的元素只能顺序访问。从一个给定元素开始，为了访问另一个元素，我们只能遍历两者之间的所有元素。list上的迭代器既支持递增运算(++)，也支持递减运算(--)。list支持任意位置的快速插入(或删除)操作。当加入新元素后，迭代器仍然有效。当删除元素后，只有原来指向被删除元素的迭代器才会失效。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/off-the-beginning_iterator.md",
    "content": "首前迭代器（off-the-beginning iterator）表示一个forward_list开始位置之前（不存在的）元素的迭代器。是forward_list的成员函数before_begin的返回值。与end()迭代器类型，不能被解引用。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/off-the-end_iterator.md",
    "content": "尾后迭代器（off-the-end iterator）表示范围中尾元素之后位置的迭代器。通常被称为末尾迭代器（end iterator）。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/priority_queue.md",
    "content": "priority_queue 顺序容器适配器，生存一个队列，插入其中的元素不放在末尾，而是根据特定的优先级排列。默认情况下，优先级用元素类型上的小于运算符确定。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/queue.md",
    "content": "queue 顺序容器适配器，生成一个类型，使我们能将新元素添加到末尾，从头部删除元素。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/sequential_container.md",
    "content": "顺序容器（sequential container）保存相同类型对象有序集合的类型。顺序容器中的元素通过位置来访问。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/stack.md",
    "content": "stack 顺序容器适配器，生成一个类型，使我们只能在其一端添加和删除元素。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch09/vector.md",
    "content": "vector 顺序容器。vector中的元素可以通过位置下标访问。支持快速的随机访问。我们只能在vector末尾实现高效的元素添加/删除。向vector添加元素可能导致内存空间的重新分配，从而使所有指向vector的迭代器失效。在vector内部添加或删除元素会使所有指向插入或删除之后元素的迭代器失效。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/back_inserter.md",
    "content": "back_inserter 这是一个迭代器适配器，它接受一个指向容器的引用，生成一个插入迭代器，该插入迭代器用push_back向指定容器添加元素。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/bidirectional_iterator.md",
    "content": "双向迭代器（bidirectional iterator）支持前向迭代器的所有操作，还具有用`--`在序列中反向移动的能力。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/binary_predicate.md",
    "content": "二元谓词（binary predicate）接受两个参数的谓词。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/bind.md",
    "content": "bind 标准库函数，将一个或多个参数绑定到一个可调用表达式。bind定义在头文件functional中。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/callable_object.md",
    "content": "可调用对象（callable object）可以出现在调用运算符左边的对象。函数指针、lambda以及重载了函数调用运算符的类的对象都是可调用对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/capture_list.md",
    "content": "捕获列表（capture list）lambda表达式的一部分，指出lambda表达式可以访问所在上下文中哪些变量。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/cref.md",
    "content": "cref 标准库函数，返回一个可拷贝的对象，其中保存了一个指向不可拷贝类型的const对象的引用。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/forward_iterator.md",
    "content": "前向迭代器（forward iterator）可以读写元素，但不必支持`--`的迭代器。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/front_insertor.md",
    "content": "front_insertor 迭代器适配器，给定一个容器，生成一个用push_front向容器开始位置添加元素的插入迭代器。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/generic_algorithm.md",
    "content": "泛型算法（generic algorithm）类型无关的算法。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/input_iterator.md",
    "content": "输入迭代器（input iterator）可以读但不能写序列中元素的迭代器。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/insert_iterator.md",
    "content": "插入迭代器（insert iterator）迭代器适配器，生成一个迭代器，该迭代器使用容器操作向给定容器添加元素。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/inserter.md",
    "content": "插入器（inserter）迭代器适配器，接受一个迭代器和一个指向容器的引用，生成一个插入迭代器，该插入迭代器用insert在给定迭代器指向的元素之前的位置添加元素。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/istream_iterator.md",
    "content": "istream_iterator 读取输入流的流迭代器。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/iterator_category.md",
    "content": "迭代器类别（iterator category）根据所支持的操作对迭代器进行的分类组织。迭代器类别形成一个层次，其中更强大的类别支持更弱类别的所有操作。算法使用迭代器类别来指出迭代器参数必须支持哪些操作。只要迭代器达到所要求的最小类别，它就可以用于算法。例如，一些算法只要求输入迭代器。这类算法可以处理除只满足输出迭代器要求的迭代器之外的任何迭代器。而要求随机访问迭代器的算法只能用于支持随机访问操作的迭代器。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/lambda_expression.md",
    "content": "lambda表达式（lambda expression）可调用的代码单元。一个lambda类似一个未命名的内联函数。一个lambda以一个捕获列表开始，此列表允许lambda访问所在函数中的变量。类似函数，lambda有一个（可能为空的）参数列表、一个返回类型和一个函数体。lambda可以忽略返回类型。如果函数体是一个单一的return语句，返回类型就从返回对象的类型推断。否则，忽略的返回类型默认定位void。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/move_iterator.md",
    "content": "移动迭代器（move iterator）迭代器适配器，生成一个迭代器，该迭代器移动而不是拷贝元素。移动迭代器将在第13章中进行介绍。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/ostream_iterator.md",
    "content": "ostream_iterator 写输出流的迭代器。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/output_iterator.md",
    "content": "输出迭代器（output iterator）可以写元素，但不必具有读元素能力的迭代器。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/predicate.md",
    "content": "谓词（predicate）返回可以转换为bool类型的值得函数。泛型算法通常用来检测元素。标准库使用的谓词是一元（接受一个参数）或二元（接受两个参数）的。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/random-access_iterator.md",
    "content": "随机访问迭代器（random-access iterator）支持双向迭代器的所有操作加上比较迭代器值得关系运算符、下标运算符和迭代器上的算术运算，因此支持随机访问元素。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/ref.md",
    "content": "ref 标准库函数，从一个指向不能拷贝的类型的对象的引用生成一个可拷贝的对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/reverse_iterator.md",
    "content": "反向迭代器（reverse_iterator）在序列中反向移动的迭代器。这些迭代器交换了`++`和`--`的含义。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/stream_iterator.md",
    "content": "流迭代器（stream iterator）可以绑定到一个流的迭代器。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch10/unary_predicate.md",
    "content": "一元谓词（unary predicate）接受一个参数的谓词。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/associative_array.md",
    "content": "关联数组（associative array）元素通过关键字而不是位置来索引的数组。我们称这样的数组将一个关键字映射到其关联的值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/associative_container.md",
    "content": "关联容器（associative container）类型，保存对象的集合，支持通过关键字高效查找。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/hash.md",
    "content": "hash 特殊的标准库模板，无序容器用它来管理元素的位置。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/hash_function.md",
    "content": "哈希函数（hash function）将给定类型的值映射到整型(size_t)值的函数。相等的值必须映射到相同的整数；不相等的值应尽可能映射到不同整数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/key_type.md",
    "content": "key_type 关联容器定义的类型，用来保存和提取值的关键字的类型。对于一个map，key_type是用来索引map的类型。对于set，key_type和value_type是一样的。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/map.md",
    "content": "map 关联容器类型，定义了一个关联数组。类似vector，map是一个类模板。但是，一个map要用两个类型来定义：关键字的类型和关联的值的类型。在一个map中，一个给定关键字只能出现一次。每个关键字关联一个特定的值。解引用一个map迭代器会生成一个pair，它保存一个const关键字及其关联的值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/mapped_type.md",
    "content": "mapped_type 映射类型定义的类型，就是映射中关键字关联的值的类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/multimap.md",
    "content": "multimap 关联容器类型，类似map，不同之处在于，一个multimap中，一个给定的关键字可以出现多次。multimap不支持下标操作。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/multiset.md",
    "content": "multiset 保存关键字的关联容器类型。在一个multiset中，一个给定关键字可以出现多次。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/pair.md",
    "content": "pair类型，保存名为first和second的public数据成员。pair类型是模板类型，接受两个类型参数，作为其成员的类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/set.md",
    "content": "set 保存关键字的关联容器。在一个set中，一个给定的关键字只能出现一次。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/strict_weak_ordering.md",
    "content": "严格弱序（strict weak ordering）关联容器所使用的关键字间的关系。在一个严格弱序中，可以比较任意两个值并确定哪个更小。若任何一个都不小于另一个，则认为两个值相等。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/unordered_container.md",
    "content": "无序容器（unordered container）关联容器，用哈希技术而不是比较操作来存储和访问元素。这类容器的性能依赖于哈希函数的质量。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/unordered_map.md",
    "content": "unordered_map 保存关键字-值对的容器，不允许重复关键字。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/unordered_multimap.md",
    "content": "unordered_multimap 保存关键字-值对的容器，允许重复关键字。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/unordered_multiset.md",
    "content": "unordered_multiset 保存关键字的容器，允许重复关键字。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/unordered_set.md",
    "content": "unordered_set 保存关键字的容器，不允许重复关键字。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch11/value_type.md",
    "content": "value_type 容器中元素的类型。对于set和multiset，value_type和key_type是一样的。对于map和multimap，此类型是一个pair，其first成员类型为const key_type，second成员类型为mapped_type。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/allocator.md",
    "content": "allocator 标准库类，用来分配未构造的内存。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/dangling_pointer.md",
    "content": "空悬指针（dangling pointer）一个指针，指向曾经保存一个对象但现在已释放的内存。众所周知，空悬指针引起的程序错误非常难以调试。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/delete.md",
    "content": "delete 释放new分配的内存。delete p释放对象，delete []p释放p指向的数组。p可以为空，或者指向new分配的内存。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/deleter.md",
    "content": "释放器（deleter）传递给智能指针的函数，用来代替delete释放指针绑定的对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/destructor.md",
    "content": "析构函数（destructor）特殊的成员函数，负责在对象离开作用域或被释放时完成清理工作。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/dynamically_allocated.md",
    "content": "动态分配的（dynamically allocated）在自由空间中分配的对象。在自由空间中分配的对象直到被显式释放或程序结束才会销毁。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/free_store.md",
    "content": "自由空间（free store）程序可用的内存池，保存动态分配的对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/heap.md",
    "content": "堆（heap）自由空间的同义词。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/new.md",
    "content": "new 从自由空间分配内存。new T分配并构造一个类型为T的对象，并返回一个指向该对象的指针。如果T是一个数组类型，new返回一个指向数组首元素的指针。类似的，`new T[n]`分配n个类型为T的对象，并返回指向数组首元素的指针。默认情况下，分配的对象进行默认初始化。我们也可以提供可选的初始化器。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/placement_new.md",
    "content": "定位new（placement new）一种new表达式，接受一些额外的参数，在new关键字后面的括号中给出。例如，new (nothrow) int告诉new不要抛出异常。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/reference_count.md",
    "content": "引用计数（reference count）一个计数器，记录有多少用户共享一个对象。智能指针用它来判断什么时候释放所指向的对象是安全的。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/shared_ptr.md",
    "content": "shared_ptr 提供所有权共享的智能指针，对共享对象来说，当最后一个指向它的shared_ptr被销毁时会被释放。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/smart_pointer.md",
    "content": "智能指针（smart pointer）标准库类型，行为类似指针，但可以检查什么时候使用指针是安全的。智能指针类型负责在恰当的时候释放内存。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/unique_ptr.md",
    "content": "unique_ptr 提供独享所有权的智能指针，当unique_ptr被销毁时，它指向的对象被释放。unique_ptr不能直接拷贝或赋值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch12/weak_ptr.md",
    "content": "weak_ptr 一种智能指针，指向由shared_ptr管理的对象。在确定是否应释放对象时，shared_ptr并不把weak_ptr统计在内。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/copy-assignment_operator.md",
    "content": "拷贝赋值运算符（copy-assignment operator）接受一个本类型对象的赋值运算符版本。通常，拷贝赋值运算符的参数是一个const的引用，并返回指向本对象的引用。如果类未显式定义拷贝赋值运算符，编译器会为它合成一个。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/copy_and_swap.md",
    "content": "拷贝并交换（copy and swap）涉及赋值运算符的技术，首先拷贝右侧运算对象，然后调用swap并交换副本和左侧运算对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/copy_constructor.md",
    "content": "拷贝构造函数（copy constructor）一种构造函数，将新对象初始化为同类型另一个对象的副本。当向函数传递对象，或以传值方式从函数返回对象时，会隐式使用拷贝构造函数。如果我们未提供拷贝构造函数，编译器会为我们合成一个。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/copy_control.md",
    "content": "拷贝控制（copy control）特殊的成员函数，控制拷贝、移动、赋值及销毁本类类型对象时发生什么。如果类未定义这些操作，编译器会为它合成恰当的定义。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/copy_initialization.md",
    "content": "拷贝初始化（copy initialization）一种初始化形式，当我们使用=为一个新创建的对象提供初始化器时，会使用拷贝初始化。如果我们向函数传递对象或以传值方式从函数返回对象，以及初始化一个数组或一个聚合类时，也会使用拷贝初始化。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/deleted_function.md",
    "content": "删除的函数（deleted function）不能使用的函数。我们在一个函数的声明上指定=delete来删除它。删除的函数的一个常见用途是告诉编译器不要为类合成拷贝和/或移动操作。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/destructor.md",
    "content": "析构函数（destructor）特殊的成员函数，当对象离开作用域或被释放时进行清理工作。编译器会自动销毁每个数据成员。类类型的成员通过调用其析构函数来销毁；而内置类型或复合类型的成员的销毁则不需要做任何工作。特别是，析构函数不会释放指针成员指向的对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/lvalue_reference.md",
    "content": "左值引用（lvalue reference）可以绑定到左值的引用。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/memberwise_copy_assign.md",
    "content": "逐成员拷贝/赋值（memberwise copy/assign）合成的拷贝与移动构造函数及拷贝与移动赋值运算符的工作方式。合成的拷贝或移动构造函数依次处理每个非static数据成员，通过从给定对象拷贝或移动对应成员来初始化本对象成员；拷贝或移动赋值运算符从右侧运算对象中将每个成员拷贝赋值或移动赋值到左侧运算对象中。内置类型或复合类型的成员直接进行初始化或赋值。类类型的成员通过成员对应的拷贝/移动构造函数或拷贝/移动赋值运算符进行初始化或赋值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/move-assignment_operator.md",
    "content": "移动赋值运算符（move-assignment operator）接受一个本类型右值引用参数的赋值运算符版本。通常，移动赋值运算符将数据从右侧运算对象移动到左侧运算对象。赋值之后，对右侧运算对象执行析构函数必须是安全的。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/move.md",
    "content": "move 用来将一个右值引用绑定到一个左值的标准库函数。调用move隐含地承诺我们将不会再使用移后源对象，除了销毁它或赋予它一个新值之外。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/move_constructor.md",
    "content": "移动构造函数（move constructor）一种构造函数，接受一个本类型的右值引用。通常，移动构造函数将数据从其参数移动到新创建的对象中。移动之后，对给定的实参执行析构函数必须是安全的。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/overload_operator.md",
    "content": "重载运算符（overload operator）一种函数，重定义了运算符应用于类类型的对象时的函数。本章介绍了如何定义赋值运算符；第14章中将介绍重载运算符的更多细节内容。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/reference_count.md",
    "content": "引用计数（reference count）一种程序设计技术，通常用于拷贝控制成员的设计。引用计数记录了有多少对象共享状态。构造函数（不是拷贝/移动构造函数）将引用计数置为1。每当创建一个新副本时，计数值递增。当一个对象被销毁时，计数值递减。赋值运算符和析构函数检查递减的引用计数是否为0。如果是，它们会销毁对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/reference_qualifier.md",
    "content": "引用限定符（reference qualifier）用来指出一个非static成员函数可以用于左值或右值的符号。限定符&和&&应该放在参数列表之后或const限定符之后（如果有的话）。被&限定的函数只能用于左值；被&&限定的函数只能用于右值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/rvalue_reference.md",
    "content": "右值引用（rvalue_reference）指向一个将要销毁的对象的引用。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/synthesized_assignment_operator.md",
    "content": "合成赋值运算符（synthesized assignment operator）编译器为未显示定义赋值运算符的类创建的（合成的）拷贝或移动赋值运算符版本。除非定义为删除的，合成赋值运算符会逐成员地将右侧运算对象赋予（移动到）左侧运算对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/synthesized_copy_move_constructor.md",
    "content": "合成拷贝/移动构造函数（synthesized copy/move constructor）编译器为未显式定义对应的构造函数的类生成的拷贝或移动构造函数版本。除非定义为删除的，合成拷贝或移动构造函数分别通过从给定对象拷贝或移动成员来逐个初始化新对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch13/synthesized_destructor.md",
    "content": "合成析构函数（synthesized destructor）编译器为未显式定义析构函数的类创建的（合成的）版本。合成析构函数的函数体为空。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch14/call_signature.md",
    "content": "调用形式（call signature）表示一个可调用对象的接口。在调用形式中包括返回类型及一个实参类型列表，该列表在一对圆括号内，实参类型之间以逗号分隔。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch14/class-type_conversion.md",
    "content": "类类型转换（class-type conversion）包括由构造函数定义的从其他类型到类类型的转换以及由类类型转换运算符定义的从类类型到其他类型的转换。只接受单独一个实参的非显式构造函数定义了从实参类型到类类型的转换；而类类型转换运算符则定义了从类类型到某个指定类型的转换。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch14/conversion_operator.md",
    "content": "类型转换运算符（conversion operator）是类的成员函数，定义了从类类型到其他类型的转换。类型转换运算符必须是它要转换的类的成员，并且通常被定义为常量成员。这类运算符既没有返回类型，也不接受参数。它们返回一个可变为转换运算符类型的值，也就是说，operator int返回一个int，operator string返回一个string，以此类推。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch14/explicit_conversion_operator.md",
    "content": "显式的类型转换运算符（explicit conversion operator）由关键字explicit限定的类型转换运算符。这样的运算符用于条件中的隐式类型转换。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch14/function_object.md",
    "content": "函数对象（function object）定义了重载调用运算符的对象。在需要使用函数的地方都能使用函数对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch14/function_table.md",
    "content": "函数表（function table）形如map或vector的容器，容器中所存的值可以被调用。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch14/function_template.md",
    "content": "函数模板（function template）能够表示任意可调用类型的标准库模板。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch14/overloaded_operator.md",
    "content": "重载的运算符（overloaded operator）重定义了某种内置运算符的含义的函数。重载的运算符函数含有关键字operator，之后是要定义的符号。重载的运算符必须含有至少一个类类型的运算对象。重载运算符的优先级、结合律、运算对象数量都与内置版本一致。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch14/user-defined_conversion.md",
    "content": "用户定义的类型转换（user-defined conversion）类类型转换的同义词。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/abstract_base_class.md",
    "content": "抽象基类（abstract base class）含有一个或多个纯虚函数的类，我们无法创建抽象基类的对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/accessible.md",
    "content": "可访问的（accessible）能被派生类对象访问的基类成员。可访问性由派生类的派生列表中所用的访问说明符和基类中成员的访问级别共同决定。例如，通过公有继承而来的一个公有成员对于派生类的用户来说是可访问的；而私有继承而来的公有成员是不可访问的。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/base_class.md",
    "content": "基类（base class）可供其他类继承的类。基类的成员也将成为派生类的成员。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/class_derivation_list.md",
    "content": "类派生列表（class derivation list）罗列了所有基类，每个基类包含一个可选的访问级别，它定义了派生类继承该基类的方式。如果没有提供访问说明符，则当派生类通过关键字struct定义时继承是公有的；而当派生类通过关键字class定义时继承是私有的。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/derived-to-base_conversion.md",
    "content": "派生类向基类的类型转换（derived-to-base conversion）派生类对象向基类引用或者派生类指针向基类指针的隐式类型转换。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/derived_class.md",
    "content": "派生类（derived class）从其他类派生而来的类。派生类可以覆盖其基类的虚函数，也可以定义自己的新成员。派生类的作用域嵌套在基类作用域当中；派生类的成员能直接访问基类的成员。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/direct_base_class.md",
    "content": "直接基类（direct base class）派生类直接继承的基类，直接基类在派生类的派生列表中说明。直接基类本身也可以是一个派生类。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/dynamic_binding.md",
    "content": "动态绑定（dynamic binding）直到运行时才确定到底执行函数的哪个版本。在C++语言中，动态绑定的意思是在运行时根据引用或指针所绑定对象的实际类型来选择执行虚函数的某一个版本。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/dynamic_type.md",
    "content": "动态类型（dynamic type）对象在运行时的类型。引用所引对象或者指针所指对象的动态类型可能与该引用或指针的静态类型不同。基类的指针或引用可以指向一个派生类对象。在这样的情况中，静态类型是基类的引用（或指针），而动态类型是派生类的引用（或指针）。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/indirect_base_class.md",
    "content": "间接基类（indirect base class）不出现在派生类的派生列表中的基类。直接基类以直接或间接方式继承的类是派生类的间接基类。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/inheritance.md",
    "content": "继承（inheritance）由一个已有的类（基类）定义一个新类（派生类）的编程技术。派生类将继承基类的成员。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/object-oriented_programming.md",
    "content": "面向对象编程（object-oriented programming）利用数据抽象、继承以及动态绑定等技术编写程序的方法。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/override.md",
    "content": "覆盖（override）派生类中定义的虚函数如果与基类中定义的同名虚函数有相同的形参列表，则派生类版本将覆盖基类的版本。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/polymorphism.md",
    "content": "多态性（polymorphism）当用于面向对象编程的范畴时，多态性的含义是指程序能通过引用或指针的动态类型获取类型特定行为的能力。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/private_inheritance.md",
    "content": "私有继承（private inheritance）在私有继承中，基类的公有成员和受保护成员是派生类的私有成员。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/protected_access_specifier.md",
    "content": "protected访问说明符（protected access specifier）protected关键字之后定义的成员能被派生类的成员和友元访问。但是这些成员只对派生类对象是可访问的，对类的普通用户则是不可访问的。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/protected_inheritance.md",
    "content": "受保护的继承（protected inheritance）在受保护的继承中，基类的公有成员和受保护成员是派生类的受保护成员。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/public_inheritance.md",
    "content": "公有继承（public inheritance）基类的公有接口是派生类公有接口的组成部分。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/pure_virtual.md",
    "content": "纯虚函数（pure virtual）在类的内部声明虚函数时，在分号之前使用了=0。一个纯虚函数不需要（但是可以）被定义。含有纯虚函数的类是抽象基类。如果派生类没有对继承而来的纯虚函数定义自己的版本，则该派生类也是抽象的。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/refactoring.md",
    "content": "重构（refactoring）重新设计程序以便将一些相关的部分搜集到一个单独的抽象中，然后使用新的抽象替换原来的代码。通常情况下，重构类的方式是将数据成员和函数成员移动到继承体系的高级别节点当中，从而避免代码冗余。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/run-time_binding.md",
    "content": "运行时绑定（run-time binding）参见“动态绑定”。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/sliced_down.md",
    "content": "切掉（sliced down）当我们用一个派生类对象初始化基类对象或者为基类对象赋值时发生的情况。对象的派生类部分将被“切掉”，只剩下基类部分赋值给基类对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/static_type.md",
    "content": "静态类型（static type）对象被定义的类型或表达式产生的类型。静态类型在编译时是已知的。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch15/virtual_function.md",
    "content": "虚函数（virtual function）用于定义类型特定行为的成员函数。通过引用或指针对虚函数的调用直到运行时才被解析，依据是引用或指针所绑定对象的类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/class_template.md",
    "content": "类模板（class template）模板定义，可从它实例化出特定的类。类模板的定义以关键字template开始，后跟尖括号对`<>`，其内为一个用逗号分隔的一个或多个模板参数的列表，随后是类的定义。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/default_template_argument.md",
    "content": "默认模板实参（default template argument）一个类型或一个值，当用户未提供对应模板实参时，模板会使用它。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/explicit_instantiation.md",
    "content": "显式实例化（explicit instantiation）一个声明，为所有模板参数提供了显式实参。用来指导实例化过程。如果声明是extern的，模板将不会被实例化；否则，模板将利用指定的实参进行实例化。对每个extern模板声明，在程序中某处必须有一个非extern的显式实例化。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/explicit_template_argument.md",
    "content": "显式模板实参（explicit template argument）在一个函数调用中或定义模板类类型时，由用户提供的模板实参。显式模板实参在紧跟在模板名的尖括号对中给出。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/function_parameter_pack.md",
    "content": "函数参数包（function parameter pack）表示零个或多个函数参数的参数包。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/function_template.md",
    "content": "函数模板（function template）模板定义，可从它实例化出特定函数。函数模板的定义以关键字template开始，后跟尖括号对`<>`，其内为一个用逗号分隔的一个或多个模板参数的列表，随后是函数的定义。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/instantiate.md",
    "content": "实例化（instantiate）编译器处理过程，用实际的模板实参来生成模板的一个特殊实例，其中参数被替换为对应的实参。当函数模板被调用时，会自动根据传递给它的实参来实例化。而使用类模板时，则需要我们提供显式模板实参。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/instantiation.md",
    "content": "实例（instantiation）编译器从模板生成的类或函数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/member_template.md",
    "content": "成员模板（member template）本身是模板的成员函数。成员模板不能是虚函数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/nontype_parameter.md",
    "content": "非类型参数（nontype parameter）表示值的模板参数。非类型模板参数的实参必须是常量表达式。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/pack_expansion.md",
    "content": "包扩展（pack expansion）处理过程，将一个参数包替换为其中元素的列表。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/parameter_pack.md",
    "content": "参数包（parameter pack）表示零个或多个参数的模板或函数参数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/partial_specialization.md",
    "content": "部分特例化（partial specialization）类模板的一个版本，其中指定了某些但不是所有模板参数，或是一个或多个参数的属性未被完全指定。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/pattern.md",
    "content": "模式（pattern）定义了扩展后参数包中每个元素的形式。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/template_argument.md",
    "content": "模板实参（template argument）用来实例化模板参数的类型或值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/template_argument_deduction.md",
    "content": "模板实参推断（template argument deduction）编译器确定实例化哪个函数模板的过程。编译器检查那些使用模板参数的实参的类型，将这些类型或值绑定到模板参数，来自动实例化一个函数版本。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/template_parameter.md",
    "content": "模板参数（template parameter）在模板参数列表中指定的名字，可在模板定义内部使用。模板参数可以是类型参数，也可以是非类型参数。为了使用一个类模板，我们必须为每个模板参数提供显式实参。编译器使用这些类型或值实例化出一个类版本，其中所有用到模板参数的地方都被替换为实际的实参。当使用一个函数模板时，编译器使用调用中的函数实参推断模板实参，并使用推断出的模板实参实例化出一个特定的函数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/template_parameter_list.md",
    "content": "模板参数列表（template parameter list）用逗号分隔的参数列表，用于模板的定义或声明中。每个参数可以是一个类型参数，也可以是一个非类型参数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/template_parameter_pack.md",
    "content": "模板参数包（template parameter pack）表示零个或多个模板参数的参数包。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/template_specialization.md",
    "content": "模板特例化（template specialization）类模板、类模板的成员或函数模板的重定义，其中指定了某些（或全部）模板参数。模板特例化版本必须出现在原模版的声明之后，必须出现在任何利用特殊实参来使用模板的代码之前。一个函数模板中的每个模板参数都必须完全特例化。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/type_parameter.md",
    "content": "类型参数（type parameter）模板参数列表中的名字，用来表示类型。类型参数在关键字typename或classs之后指定。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/type_transformation.md",
    "content": "类型转换（type transformation）由标准库定义的类模板，可将给定的模板类型参数转换为一个相关类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch16/variadic_template.md",
    "content": "可变参数模板（variadic template）接受可变数目模板实参的模板。模板参数包用省略号指定（如class..., typename..., type-name...）\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/bitset.md",
    "content": "bitset 标准库类，保存二进制位集合，大小在编译时已知，并提供检测和设置集合中二进制位的操作。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/cmatch.md",
    "content": "cmatch csub_match对象的容器，保存一个regex与一个const char\\*输入序列匹配的相关信息。容器首元素描述了整个匹配结果。后续元素描述了子表达式的匹配结果。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/cregex_iterator.md",
    "content": "cregex_iterator 类似sregex_iterator，唯一的差别是此迭代器遍历一个char数组。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/csub_match.md",
    "content": "csub_match 保存一个正则表达式与一个const char\\*匹配结果的类型。可以表示整个匹配或子表达式的匹配。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/default_random_engine.md",
    "content": "默认随机数引擎（default random engine）用于普通用途的随机数引擎的类型别名。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/formatted_IO.md",
    "content": "格式化IO（formatted IO）读写操作，利用要读写的对象的类型来定义操作的行为。格式化输入操作执行合适要读取的类型的转换操作，如将ASCII码字符串转换为算术类型以及（默认地）忽略空白符。格式化输出操作将类型转换为可打印的字符表示形式、补白输出，还可能执行其他与输出类型相关的转换。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/get.md",
    "content": "get 模板函数，返回给定tuple的指定成员。例如，get<0>(t)返回tuple的第一个成员。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/high-order.md",
    "content": "高位（high-order）bitset中下标最大的那些位。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/low-order.md",
    "content": "低位（low-order）bitset中下标最小的那些位。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/manipulator.md",
    "content": "操纵符（manipulator）“操纵”流的类函数对象。操纵符可用重载的IO运算符<<和>>的右侧运算对象。大多数操纵符会改变流对象的内部状态。这种操纵符通常是成对的——一个改变状态，另一个恢复到流的默认状态。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/random-number_distribution.md",
    "content": "随机数分布（random-number distribution）标准库类型，根据其名字所指出的概率分布转换随机数引擎的输出值。例如，uniform_int_distribution<T>生成类型为T的均匀分布的整数，而normal_distribution<T>生成正态分布的值，依此类推。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/random-number_engine.md",
    "content": "随机数引擎（random-number engine）标准库类型，生成随机的无符号数。引擎的设计意图是只用作随机数分布的输入。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/random-number_generator.md",
    "content": "随机数发生器（random-number generator）一个随机数引擎类型和一个分布类型的组合。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/regex.md",
    "content": "regex 管理正则表达式的类。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/regex_error.md",
    "content": "regex_error 异常类型，当正则表达式中存在语法错误时抛出此异常。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/regex_match.md",
    "content": "regex_match 确定整个输入序列是否与给定regex对象匹配的函数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/regex_replace.md",
    "content": "regex_replace 使用一个regex对象来匹配输入序列并用给定格式替换匹配的子表达式的函数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/regex_search.md",
    "content": "regex_search 使用一个regex对象在给定输入序列中查找匹配的子序列的函数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/regular_expression.md",
    "content": "正则表达式（regular expression）一种描述字符序列的方式。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/seed.md",
    "content": "种子（seed）提供给随机数引擎的值，使引擎移动到生成的随机数序列中一个新的点。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/smatch.md",
    "content": "smatch ssub_match 对象的容器，提供一个regex与一个string输入序列匹配的相关信息。容器首元素描述了整个匹配结果。后续元素描述了子表达式的匹配结果。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/sregex_iterator.md",
    "content": "sregex_iterator 迭代器，使用给定的regex对象遍历一个string来查找匹配子串。其构造函数通过调用regex_search将迭代器定位到第一个匹配。递增迭代器的操作会调用regex_search，从给定string中当前匹配之后的位置开始查找匹配。解引用迭代器返回一个描述当前匹配的smatch对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/ssub_match.md",
    "content": "ssub_match 保存正则表达式与string匹配结果的类型。可以描述整个匹配或子表达式的匹配。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/subexpression.md",
    "content": "子表达式（subexpression）正则表达式模式中用括号包围的组成部分。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/tuple.md",
    "content": "tuple 模板，生成的类型保存指定类型的未命名成员。标准库没有限制一个tuple最多可以包含多少个成员。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch17/unformatted_IO.md",
    "content": "未格式化IO（unformatted IO）将流当作无差别的字节流来处理的操作。未格式化操作给用户增加了很多管理IO的负担。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/catch-all.md",
    "content": "捕获所有异常（catch-all）异常声明如(...)的catch子句。一条捕获所有异常的子句可以捕获任意类型的异常。常用于捕获局部检测的异常，该异常将重新抛出到程序的其他部分并最终解决问题。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/catch_clause.md",
    "content": "catch子句（catch clause）程序中负责处理异常的部分。catch子句包含关键字catch，后面是异常声明以及一个语句块。catch子句的代码负责处理异常声明中定义的异常。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/constructor_order.md",
    "content": "构造函数顺序（constructor order）在非虚继承中，基类的构造顺序与其在派生列表中出现的顺序一致。在虚继承中，首先构造虚基类。虚基类的构造顺序与其在派生类的列表中出现的顺序一致。只有最低层的派生类才能初始化虚基类。虚基类的初始值如果出现在中间基类中，则这些值将被忽略。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/exception_declaration.md",
    "content": "异常声明（exception declaration）catch子句中指定其能够处理的异常类型的部分。异常声明的行为与形参列表类似，其中的唯一一个形参通过异常对象进行初始化。如果异常说明符是非引用类型，则异常对象将被拷贝给catch。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/exception_handling.md",
    "content": "异常处理（exception handling）管理运行时异常的语言级支持。代码中一个独立开发的部分可以检测并引发异常，由程序的另一个独立开发的部分处理该异常。也就是说，程序的错误检测部分抛出异常，而错误处理部分在try语句块的catch子句中处理异常。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/exception_object.md",
    "content": "异常对象（exception object）用于在异常的throw和catch之间进行通信的对象。在抛出点创建该对象，该对象是被抛出的表达式的副本。在该异常的最后一段处理代码完成之前异常对象都一直存在。异常对象的类型是被抛出的表达式的静态类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/file_static.md",
    "content": "文件中的静态声明（file static）使用关键字static声明的仅对当前文件有效的名字。在C语言和之前的C++版本中，文件中的静态声明用于声明只能在当前文件中使用的名字。该特性在当前的C++版本中已经被未命名的命名空间替换了。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/function_try_block.md",
    "content": "函数try语句块（function try block）用于捕获构造函数初始化过程发生的异常。关键字try出现在表示构造函数初始值列表开始的冒号之前（或者当初始值列表为空时出现在函数体的左花括号之前），并以函数体右侧花括号之后的一个或几个catch子句作为结束。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/global_namespace.md",
    "content": "全局命名空间（global namespace）是每个程序的隐式命名空间，用于存放全局定义。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/handler.md",
    "content": "处理代码（handler）是“catch子句”的同义词。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/inline_namespace.md",
    "content": "内联的命名空间（inline namespace）内联命名空间中的名字可以看成是外层命名空间的成员。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/multiple_inheritance.md",
    "content": "多重继承（multiple inheritance）有多个直接基类的类。派生类继承所有基类的成员。可以为每个基类分别设定访问说明符。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/namespace.md",
    "content": "命名空间（namespace）将库或者其他程序集定义的名字放在同一个作用域中的机制。和C++的其他作用域不同，命名空间作用域可以定义成几个部分。我们可以打开并关闭命名空间，然后在程序的另一个地方重新打开并关闭该命名空间。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/namespace_alias.md",
    "content": "命名空间的别名（namespace alias）为某个给定的命名空间定义同义词的机制：\n\n```c++\nnamespace N1 = N;\n```\n\n将N1定义成命名空间N的另一个名字。命名空间可以含有多个别名，命名空间的原名和别名是等价的。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/namespace_pollution.md",
    "content": "命名空间污染（namespace pollution）当所有类和函数的名字都放置于全局命名空间时将造成命名空间污染。如果来自于多个供应商的代码都含有全局名字，则使用这些代码的大型程序可能会面临命名空间污染的问题。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/noexcept_operator.md",
    "content": "noexcept运算符（noexcept operator）该运算符返回一个bool值，用于表示给定的表达式是否会抛出异常。该表达式不会被求值，运算的结果是一个常量表达式。当提供的表达式不含throw并且只调用了做出不抛出说明的函数时，结果为true；否则结果为false。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/noexcept_specification.md",
    "content": "noexcept说明（noexcept specification）表示函数是否会抛出异常的关键字。当noexcept跟在函数的形参列表之后时，它可以连接一个括号括起来的常量表达式，前提是该表达式可以转换成bool值。如果忽略了该表达式，或者表达式的值为true，则函数不会抛出异常。如果表达式的值是false或者函数没有异常说明，则其可能抛出异常。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/nonthrowing_specification.md",
    "content": "不抛出说明（nonthrowing specification）该异常说明用于承诺某个函数不会抛出异常。如果一个做了不抛出说明的函数实际抛出了异常，将调用terminate。不抛出说明符是不含实参或者含有一个值为true的实参的noexcept。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/raise.md",
    "content": "引发（raise）常常作为抛出的同义词。C++程序员认为抛出异常和引发异常基本上是等价的。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/rethrow.md",
    "content": "重新抛出（rethrow）不指定表达式的throw。重新抛出只有在catch子句内部或者被catch直接或间接调用了的函数内时才有效。它的效果是将其接受的异常重新抛出。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/stack_unwinding.md",
    "content": "栈展开（stack unwinding）在搜寻catch时依次退出函数的过程。异常发生前构造的局部对象将在进入相应的catch前被销毁。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/terminate.md",
    "content": "terminate 是一个标准库函数，当异常未被捕获或者在处理异常的过程中发生了另一个异常时，terminate负责结束程序的执行。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/throw.md",
    "content": "throw e 该表达式将中断当前的执行路径，throw语句将控制权传递给最近的能够处理该异常的catch子句。表达式e将被拷贝给异常对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/try_block.md",
    "content": "try语句块（try block）含有关键字try以及一个或多个catch子句的语句块。如果try语句块中的代码引发了一个异常，并且某个catch可以匹配该异常，则异常将被这个catch处理。否则，异常被传递到try语句块之外并继续沿着调用链寻找与之匹配的catch。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/unnamed_namespace.md",
    "content": "未命名的命名空间（unnamed namespace）定义时未指定名字的命名空间。对于定义在未命名的命名空间中的名字，我们可以不用作用域运算符就直接访问它们。每个文件有一个独有的未命名的命名空间，其中的名字在文件外不可见。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/using_declaration.md",
    "content": "using声明（using declaration）是一种将命名空间中的某个名字注入当前作用域的机制：\n\n```c++\nusing std::cout;\n```\n\n上述语句使得命名空间std中的名字cout在当前作用域可见。之后，我们就可以直接使用cout而无须前缀std::了。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/using_directive.md",
    "content": "using指示（using directive）是具有如下形式的声明：\n\n```c++\nusing NS;\n```\n\n上述语句使得命名空间NS的所有名字在using指示所在的作用域以及NS所在的作用域都变得可见。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/virtual_base_class.md",
    "content": "虚基类（virtual base class）在派生列表中使用了关键字virtual的基类。在派生类对象中，虚基类部分只有一份，即使该虚基类在继承体系中出现了多次也是如此。对于非虚继承而言，构造函数只能初始化它的直接基类。但是对于虚继承来说，虚基类将被最低层的派生类初始化，因此最低层的派生类应该含有它的所有虚基类的初始值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch18/virtual_inheritance.md",
    "content": "虚继承（virtual inheritance）是多重继承的一种形式，基类被继承了多次，但是派生类共享该基类的唯一一份副本。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/anonymous_union.md",
    "content": "匿名union（anonymous union）未命名的union，不能用于定义对象。匿名union的成员也是外层作用域的成员。匿名union不能包含成员函数，也不能包含私有的或受保护的成员。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/bit-field.md",
    "content": "位域（bit-field）特殊的类成员，该成员含有一个整型值以指定为其分配的二进制位数。如果可能的话，在类中连续定义的位域将被压缩在一个普通的整数值中。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/discriminant.md",
    "content": "判别式（discriminant）是一种使用一个对象判断union的当前值类型的编程技术。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/dynamic_cast.md",
    "content": "dynamic_cast 是一个运算符，执行从基类向派生类的带检查的强制类型转换。当基类中至少含有一个虚函数时，该运算符负责检查指针或引用所绑定的对象的动态类型。如果对象类型与目标类型(或其派生类)一致，则类型转换完成。否则，指针转换将返回一个值为0的指针；引用转换将抛出一个异常。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/enumeration.md",
    "content": "枚举类型（enumeration）将一组整型常量命名后聚合在一起形成的类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/enumerator.md",
    "content": "枚举成员（enumerator）是枚举类型的成员。枚举成员是常量。可以用在任何需要整型常量的地方。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/free.md",
    "content": "free 是定义在cstdlib中的低层函数，负责释放内存。free只能释放由malloc分配的内存。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/link_directive.md",
    "content": "链接指示（link_directive）支持C++程序调用其他语言编写的函数的一种机制。所有编译器都应支持调用C++和C函数，至于是否支持其他语言则由编译器决定。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/local_class.md",
    "content": "局部类（local class）定义在函数内的类。局部类只有在其外层函数内可见。局部类的所有成员都必须定义在类的内部。局部类不能含有静态成员。局部类不能访问外层函数的非静态变量，只能访问类型名字、静态变量或枚举成员。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/malloc.md",
    "content": "malloc 是定义在cstdlib中的低层函数，负责分配内存。malloc分配的内存必须由free释放。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/mem_fn.md",
    "content": "mem_fn 是一个标准类模板，根据指向成员函数的指针生成一个可调用对象。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/nested_class.md",
    "content": "嵌套类（nested class）定义再其他类内部的类。嵌套类定义在它的外层作用域中：在外层类的作用域中嵌套类的名字必须唯一，在外层类之外可以被重用。在外层类之外访问嵌套类需要用作用域运算符指明嵌套类所属的范围。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/nested_type.md",
    "content": "潜逃类型（nested type）“嵌套类”的同义词。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/nonportable.md",
    "content": "不可移植（nonportable）固有的与机器有关的特性，当程序转移到其他机器或编译器上时需要修改代码。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/operator_delete.md",
    "content": "operator delete 是一个标准库函数，用于释放由operator new分配的未指明类型的、未构造的内存空间。相应的，operator delete[]释放由operator new[]为数组分配的内存。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/operator_new.md",
    "content": "operator new 是一个标准库函数，用于分配一个给定大小的、未指明类型的、未构造的内存空间。标准库函数operator new[] 为数组分配原始内存。与allocator类相比，这两个标准库函数提供的内存分配机制更低级。现代的C++程序应该使用allocator而不是这两个函数。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/placement_new_expression.md",
    "content": "定位new表达式（placement new expression）是new的一种特殊形式，在给定的内存中构造对象。它不分配内存，而是根据实参指定在哪儿构造对象。它是对allocator类的construct成员的行为的一种低级模拟。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/pointer_to_member.md",
    "content": "成员指针（pointer to member）其中既包含类类型，也包含指针所指的成员类型。成员指针的定义必须同时指定类的名字以及指针所指的成员类型：\n\n```c++\nT C::*pmem = &C::member;\n```\n\n该语句将pmem定义为一个指针，它可以指向类C的成员，并且该成员的类型是T，然后初始化pmem令其指向类C的名为member的成员。要使用该指针，我们提供C的一个对象或指针：\n\n```c++\nclassobj.*pmem;\nclassptr->*pmem;\n```\n\n从classptr或classobj中获取member。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/run-time_tyoe_identification.md",
    "content": "运行时类型识别（run-time type identification）是C++的一种特性，允许在运行时获取指针或引用的动态类型。RTTI运算符包括typeid和dynamic_cast，为含有虚函数的类的指针或引用提供动态类型。当作用于其他类型时，返回的结果是指针或引用的静态类型。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/scoped_enumeration.md",
    "content": "限定作用域的枚举类型（scoped enumeration）是一种新的枚举类型，它的枚举成员不能被外层作用域直接访问。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/typeid_operator.md",
    "content": "typeid运算符（typeid operator）是一个一元运算符，返回标准库类型type_info的引用，表示给定表达式的类型。当表达式是某个含有虚函数的类型的对象时，返回表达式的动态类型；此类表达式在运行时求值。如果表达式的类型是指针、引用或其他未定义虚函数的类型；则返回指针、引用或对象的静态类型；此类表达式不会被求值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/typeinfo.md",
    "content": "type_info typeid运算符返回的标准库类型。type_info的细节因机器而异，但是必须提供一组操作，其中名为name的函数负责返回一个表示类型名字的字符串。type_info对象不能被拷贝、移动或赋值。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/union.md",
    "content": "联合（union）是一种和类有些相似的类型。可以包含多个数据成员，但是同一时刻只能一个成员有值。联合可以有包括构造函数和析构函数在内的成员函数。联合不能被用作基类。在C++11新标准中，联合可以含有类类型成员，前提是这些成员自定义了拷贝控制成员。对于这样的联合来说，如果它们没有定义自己的拷贝控制成员，则编译器为它们生成删除的版本。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/unscoped_enumeration.md",
    "content": "不限定作用域的枚举类型（unscoped enumeration）该枚举类型的枚举成员在枚举类型的外层作用域中可以访问。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/ch19/volatile.md",
    "content": "volatile 是一种类型限定符，告诉编译器变量可能在程序的直接控制之外发生改变。它起到一种标示作用，令编译器不对代码做优化操作。\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/gen_index.sh",
    "content": "#! /bin/bash\n\nold_dir=$(pwd)\nout_file=terms_index.md\n\n#######################################################################\n\n# 设置每一个章节名字\n\nget_all_chapters()\n{\ncat << !CHAPTERS!\n第1章_开始\n第2章_变量和基本类型\n第3章_字符串、向量和数组\n第4章_表达式\n第5章_语句\n第6章_函数\n第7章_类\n第8章_IO库\n第9章_顺序容器\n第10章_泛型算法\n第11章_关联容器\n第12章_动态内存\n第13章_拷贝控制\n第14章_操作重载与类型转换\n第15章_面向对象程序设计\n第16章_模板与泛型编程\n第17章_标准库特殊设施\n第18章_用于大型程序的工具\n第19章_特殊工具与技术\n!CHAPTERS!\n}\n\nchapters=$(get_all_chapters)\n\n######################################################################\n\n# 生成该章节的索引文本\ngen_ch_index()\n{\n\tdir=$1\n\tcd $dir\n\tterms=$(ls *.md) # 所有的术语文本\n\tcd $old_dir\n\n\tfor term in $terms; do\n\t\t# 截取术语的英文名：文件名 -> 去掉后缀.md -> 将'_'替换成' '\n\t\tterm_name=${term%.md}\n\t\told_term_name=$term_name\n\t\tterm_name=${term_name//_/ }\n\t\t\n\t\t# 截取术语的中文名：文件第一行 -> 参数（... -> 参数\n\t\tfirst_line=$(head -1 ./$dir/$term)\n\t\tterm_name_cn=${first_line%%（*}\n\t\tif [ \"$first_line\" = \"$term_name_cn\" ]; then\n\t\t\t# 没有中文的情况，一般都是类名，故保留下划线\n\t\t\ttag=\"[$old_term_name](./$dir/$term)\"\n\t\telse\n\t\t\ttag=\"[$term_name_cn $term_name](./$dir/$term)\"\n\t\tfi\n\n\t\techo -e \"- $tag\" >> $out_file\n\tdone\n}\n\ngen_index()\n{\n\tn_ch=0 \t\t# 当前处理的第几章\n\tfor ch_name in $chapters; do\n\t\tn_ch=$(($n_ch + 1))\n\n\t\tcur_ch=ch # 章节的英文路径开头\n\n\t\tif [ $n_ch -lt 10 ]; then\n\t\t\tcur_ch=ch0\n\t\tfi\n\n\t\tcur_ch=${cur_ch}$n_ch\n\n\t\techo \"### $ch_name\" >> $out_file\n\t\techo >> $out_file\n\t\tgen_ch_index $cur_ch\n\t\techo >> $out_file\n\tdone\n\n\treturn 0\n}\n\nrm -f $out_file\ntouch $out_file\n\ngen_index\n"
  },
  {
    "path": "codes/CppPrimer/defined_terms/terms_index.md",
    "content": "### 第1章_开始\n\n- [参数 argument](./ch01/argument.md)\n- [赋值 assignment](./ch01/assignment.md)\n- [程序块 block](./ch01/block.md)\n- [缓冲区 buffer](./ch01/buffer.md)\n- [内置类型 built-in type](./ch01/built-in_type.md)\n- [cerr](./ch01/cerr.md)\n- [cin](./ch01/cin.md)\n- [类 class](./ch01/class.md)\n- [类类型 class type](./ch01/class_type.md)\n- [clog](./ch01/clog.md)\n- [注释 comment](./ch01/comment.md)\n- [条件 condition](./ch01/condition.md)\n- [cout](./ch01/cout.md)\n- [数据结构 data structure](./ch01/data_structure.md)\n- [编辑-编译-调试 edit-compile-debug](./ch01/edit-compile-debug.md)\n- [文件结束符 end-of-file](./ch01/end-of-file.md)\n- [表达式 expression](./ch01/expression.md)\n- [for语句 for statement](./ch01/for_statement.md)\n- [函数体 function body](./ch01/function_body.md)\n- [函数 function](./ch01/function.md)\n- [函数名 function name](./ch01/function_name.md)\n- [头文件 header](./ch01/header.md)\n- [if语句 if statement](./ch01/if_statement.md)\n- [初始化 initialize](./ch01/initialize.md)\n- [iostream](./ch01/iostream.md)\n- [istream](./ch01/istream.md)\n- [main](./ch01/main.md)\n- [操纵符 manipulator](./ch01/manipulator.md)\n- [成员函数 member function](./ch01/member_function.md)\n- [命名空间 namespace](./ch01/namespace.md)\n- [ostream](./ch01/ostream.md)\n- [形参列表 parameter list](./ch01/parameter_list.md)\n- [返回类型 return type](./ch01/return_type.md)\n- [源文件 source file](./ch01/source_file.md)\n- [标准错误 standard error](./ch01/standard_error.md)\n- [标准输入 standard input](./ch01/standard_input.md)\n- [标准库 standard library](./ch01/standard_library.md)\n- [标准输出 standard output](./ch01/standard_output.md)\n- [语句 statement](./ch01/statement.md)\n- [字符串常量 string literal](./ch01/string_literal.md)\n- [未初始化的变量 uninitialized variable](./ch01/uninitialized_variable.md)\n- [变量 variable](./ch01/variable.md)\n- [while语句 while statement](./ch01/while_statement.md)\n- [!=运算符 不等运算符](./ch01/不等运算符.md)\n- [::运算符 作用域运算符](./ch01/作用域运算符.md)\n- [+=运算符 复合赋值运算符](./ch01/复合赋值运算符.md)\n- [`>=运算符` 大于等于运算符](./ch01/大于等于运算符.md)\n- [`>运算符` 大于运算符](./ch01/大于运算符.md)\n- [头文件包含指令](./ch01/头文件包含指令.md)\n- [<=运算符 小于等于运算符](./ch01/小于等于运算符.md)\n- [<运算符 小于运算符](./ch01/小于运算符.md)\n- [.运算符 点运算符](./ch01/点运算符.md)\n- [==运算符 相等运算符](./ch01/相等运算符.md)\n- [=运算符 等号运算符](./ch01/等号运算符.md)\n- [()运算符 调用运算符](./ch01/调用运算符.md)\n- [`>>运算符` 输入运算符](./ch01/输入运算符.md)\n- [<<运算符 输出运算符](./ch01/输出运算符.md)\n- [--运算符 递减运算符](./ch01/递减运算符.md)\n- [++运算符 递增运算符](./ch01/递增运算符.md)\n\n### 第2章_变量和基本类型\n\n- [地址 address](./ch02/address.md)\n- [别名声明 alias declaration](./ch02/alias_declaration.md)\n- [算术类型 arithmetic type](./ch02/arithmetic_type.md)\n- [数组 array](./ch02/array.md)\n- [auto](./ch02/auto.md)\n- [基本类型 base type](./ch02/base_type.md)\n- [绑定 bind](./ch02/bind.md)\n- [字节 byte](./ch02/byte.md)\n- [类成员 class member](./ch02/class_member.md)\n- [复合类型 compound type](./ch02/compound_type.md)\n- [常量表达式 const expression](./ch02/const_expression.md)\n- [const](./ch02/const.md)\n- [常量指针 const pointer](./ch02/const_pointer.md)\n- [常量引用 const reference](./ch02/const_reference.md)\n- [转换 conversion](./ch02/conversion.md)\n- [数据成员 data member](./ch02/data_member.md)\n- [声明 declaration](./ch02/declaration.md)\n- [声明符 declarator](./ch02/declarator.md)\n- [decltype](./ch02/decltype.md)\n- [默认初始化 default initialization](./ch02/default_initialization.md)\n- [定义 definition](./ch02/definition.md)\n- [转义序列 escape sequence](./ch02/escape_sequence.md)\n- [全局作用域 global scope](./ch02/global_scope.md)\n- [头文件保护符 header guard](./ch02/header_guard.md)\n- [标识符 identifier](./ch02/identifier.md)\n- [类内初始值 in-class initializer](./ch02/in-class_initializer.md)\n- [列表初始化 list initialization](./ch02/list_initialization.md)\n- [字面值 literal](./ch02/literal.md)\n- [底层const low-level const](./ch02/low-level_const.md)\n- [不可打印字符 nonprintable character](./ch02/nonprintable_character.md)\n- [空指针 null pointer](./ch02/null_pointer.md)\n- [nullptr](./ch02/nullptr.md)\n- [对象 object](./ch02/object.md)\n- [指针 pointer](./ch02/pointer.md)\n- [指向常量的指针 pointer to const](./ch02/pointer_to_const.md)\n- [预处理器 preprocessor](./ch02/preprocessor.md)\n- [预处理器变量 preprocessor variable](./ch02/preprocessor_variable.md)\n- [引用 reference](./ch02/reference.md)\n- [对常量的引用 reference to const](./ch02/reference_to_const.md)\n- [作用域 scope](./ch02/scope.md)\n- [分离式编译 separate complilation](./ch02/separate_complilation.md)\n- [带符号类型 signed](./ch02/signed.md)\n- [字符串 string](./ch02/string.md)\n- [临时值 temporary](./ch02/temporary.md)\n- [顶层const top-level const](./ch02/top-level_const.md)\n- [类型别名 type alias](./ch02/type_alias.md)\n- [类型检查 type checking](./ch02/type_checking.md)\n- [类型说明符 type specifier](./ch02/type_specifier.md)\n- [未定义 undefined](./ch02/undefined.md)\n- [未初始化 uninitialized](./ch02/uninitialized.md)\n- [无符号类型 unsigned](./ch02/unsigned.md)\n- [变量 variable](./ch02/variable.md)\n- [void](./ch02/void.md)\n- [字 word](./ch02/word.md)\n\n### 第3章_字符串、向量和数组\n\n- [begin](./ch03/begin.md)\n- [缓冲区溢出 buffer overflow](./ch03/buffer_overflow.md)\n- [类模板 class template](./ch03/class_template.md)\n- [编译器扩展 compiler extension](./ch03/compiler_extension.md)\n- [容器 container](./ch03/container.md)\n- [拷贝初始化 copy initialization](./ch03/copy_initialization.md)\n- [C风格字符串 C-style string](./ch03/C-style_string.md)\n- [difference_type](./ch03/difference_type.md)\n- [直接初始化 direct initialization](./ch03/direct_initialization.md)\n- [empty](./ch03/empty.md)\n- [end](./ch03/end.md)\n- [getline](./ch03/getline.md)\n- [索引 index](./ch03/index.md)\n- [实例化 instantiation](./ch03/instantiation.md)\n- [迭代器运算 iterator arithmetic](./ch03/iterator_arithmetic.md)\n- [迭代器 iterator](./ch03/iterator.md)\n- [尾后迭代器 off-the-end iterator](./ch03/off-the-end_iterator.md)\n- [指针运算 pointer arithmetic](./ch03/pointer_arithmetic.md)\n- [prtdiff_t](./ch03/prtdiff_t.md)\n- [push_back](./ch03/push_back.md)\n- [范围for语句 range for](./ch03/range_for.md)\n- [size](./ch03/size.md)\n- [size_t](./ch03/size_t.md)\n- [size_type](./ch03/size_type.md)\n- [string](./ch03/string.md)\n- [using声明 using declaration](./ch03/using_declaration.md)\n- [值初始化 value initialization](./ch03/value_initialization.md)\n- [vector](./ch03/vector.md)\n- [[]运算符 下标运算符](./ch03/下标运算符.md)\n- [->运算符 箭头运算符](./ch03/箭头运算符.md)\n- [`>>运算符` 输入运算符](./ch03/输入运算符.md)\n- [<<运算符 输出运算符](./ch03/输出运算符.md)\n- [++运算符 递增运算符](./ch03/递增运算符.md)\n- [&&运算符 逻辑与运算符](./ch03/逻辑与运算符.md)\n- [||运算符 逻辑或运算符](./ch03/逻辑或运算符.md)\n- [!运算符 逻辑非运算符](./ch03/逻辑非运算符.md)\n\n### 第4章_表达式\n\n- [算术转换 arithmetic conversion](./ch04/arithmetic_conversion.md)\n- [结合律 associativity](./ch04/associativity.md)\n- [二元运算符 binary operator](./ch04/binary_operator.md)\n- [强制类型转换 cast](./ch04/cast.md)\n- [复合表达式 compound expression](./ch04/compound_expression.md)\n- [const_cast](./ch04/const_cast.md)\n- [转换 conversion](./ch04/conversion.md)\n- [表达式 expression](./ch04/expression.md)\n- [隐式转换 implicit conversion](./ch04/implicit_conversion.md)\n- [整型提升 integral promotion](./ch04/integral_promotion.md)\n- [左值 lvalue](./ch04/lvalue.md)\n- [运算对象 operand](./ch04/operand.md)\n- [运算符 operator](./ch04/operator.md)\n- [求值顺序 order of evaluation](./ch04/order_of_evaluation.md)\n- [重载运算符 overloaded operator](./ch04/overloaded_operator.md)\n- [优先级 precedence](./ch04/precedence.md)\n- [reinterpret_case](./ch04/reinterpret_case.md)\n- [右值 rvalue](./ch04/rvalue.md)\n- [短路求值 short-circuit evaluation](./ch04/short-circuit_evaluation.md)\n- [sizeof](./ch04/sizeof.md)\n- [static_cast](./ch04/static_cast.md)\n- [一元运算符 unary operators](./ch04/unary_operators.md)\n- [&运算符 位与运算符](./ch04/位与运算符.md)\n- [^运算符 位异或运算符](./ch04/位异或运算符.md)\n- [|运算符 位或运算符](./ch04/位或运算符.md)\n- [~运算符 位求反运算符](./ch04/位求反运算符.md)\n- [`>>运算符` 右移运算符](./ch04/右移运算符.md)\n- [<<运算符 左移运算符](./ch04/左移运算符.md)\n- [?:运算符 条件运算符](./ch04/条件运算符.md)\n- [--运算符 递减运算符](./ch04/递减运算符.md)\n- [++运算符 递增运算符](./ch04/递增运算符.md)\n- [,运算符 逗号运算符](./ch04/逗号运算符.md)\n\n### 第5章_语句\n\n- [块 block](./ch05/block.md)\n- [break语句 break statement](./ch05/break_statement.md)\n- [case标签 case label](./ch05/case_label.md)\n- [catch子句 catch clause](./ch05/catch_clause.md)\n- [复合语句 compound statement](./ch05/compound_statement.md)\n- [continue语句 continue statement](./ch05/continue_statement.md)\n- [悬垂else dangling else](./ch05/dangling_else.md)\n- [default标签 default label](./ch05/default_label.md)\n- [do while语句 do while statement](./ch05/do_while_statement.md)\n- [异常类 exception class](./ch05/exception_class.md)\n- [异常声明 exception declaration](./ch05/exception_declaration.md)\n- [异常处理代码 exception handler](./ch05/exception_handler.md)\n- [异常安全 exception safe](./ch05/exception_safe.md)\n- [表达式语句 expression statement](./ch05/expression_statement.md)\n- [控制流 flow of control](./ch05/flow_of_control.md)\n- [for语句 for statement](./ch05/for_statement.md)\n- [goto语句 goto statement](./ch05/goto_statement.md)\n- [if else语句 if else statement](./ch05/if_else_statement.md)\n- [if语句 if statement](./ch05/if_statement.md)\n- [带标签语句 labeled statement](./ch05/labeled_statement.md)\n- [空语句 null statement](./ch05/null_statement.md)\n- [引发 raise](./ch05/raise.md)\n- [范围for语句 range for statment](./ch05/range_for_statment.md)\n- [switch语句 switch statement](./ch05/switch_statement.md)\n- [terminate](./ch05/terminate.md)\n- [throw表达式 throw expression](./ch05/throw_expression.md)\n- [try语句块 try block](./ch05/try_block.md)\n- [while语句 while statement](./ch05/while_statement.md)\n\n### 第6章_函数\n\n- [二义性调用 ambiguous call](./ch06/ambiguous_call.md)\n- [实参 argument](./ch06/argument.md)\n- [assert](./ch06/assert.md)\n- [自动对象 automatic object](./ch06/automatic_object.md)\n- [最佳匹配 best match](./ch06/best_match.md)\n- [候选函数 candidate function](./ch06/candidate_function.md)\n- [constexpr](./ch06/constexpr.md)\n- [默认实参 default argument](./ch06/default_argument.md)\n- [可执行文件 executable file](./ch06/executable_file.md)\n- [函数匹配 function matching](./ch06/function_matching.md)\n- [函数原型 function prototype](./ch06/function_prototype.md)\n- [隐藏名字 hidden name](./ch06/hidden_name.md)\n- [initializer_list](./ch06/initializer_list.md)\n- [内联函数 inline function](./ch06/inline_function.md)\n- [链接 link](./ch06/link.md)\n- [局部静态对象 local static object](./ch06/local_static_object.md)\n- [局部变量 local variable](./ch06/local_variable.md)\n- [无匹配 no match](./ch06/no_match.md)\n- [对象代码 object code](./ch06/object_code.md)\n- [对象文件 object file](./ch06/object_file.md)\n- [对象生命周期 object lifetime](./ch06/object_lifetime.md)\n- [重载函数 overloaded function](./ch06/overloaded_function.md)\n- [形参 parameter](./ch06/parameter.md)\n- [引用传递 pass by reference](./ch06/pass_by_reference.md)\n- [值传递 pass by value](./ch06/pass_by_value.md)\n- [预处理宏 preprocessor macro](./ch06/preprocessor_macro.md)\n- [递归循环 recursion loop](./ch06/recursion_loop.md)\n- [递归函数 recursive function](./ch06/recursive_function.md)\n- [返回类型 return type](./ch06/return_type.md)\n- [尾置返回类型 trailing return type](./ch06/trailing_return_type.md)\n- [可行函数 viable function](./ch06/viable_function.md)\n\n### 第7章_类\n\n- [抽象数据类型 abstract data type](./ch07/abstract_data_type.md)\n- [访问说明符 access specifier](./ch07/access_specifier.md)\n- [聚合类 aggregate class](./ch07/aggregate_class.md)\n- [类的声明 class declaration](./ch07/class_declaration.md)\n- [类 class](./ch07/class.md)\n- [类的作用域 class scope](./ch07/class_scope.md)\n- [常量成员函数 const member function](./ch07/const_member_function.md)\n- [构造函数初始值列表 constructor initializer list](./ch07/constructor_initializer_list.md)\n- [构造函数 constructor](./ch07/constructor.md)\n- [转换构造函数 converting constructor](./ch07/converting_constructor.md)\n- [数据抽象 data abstraction](./ch07/data_abstraction.md)\n- [默认构造函数 default constructor](./ch07/default_constructor.md)\n- [=default](./ch07/=default.md)\n- [委托构造函数 delegating constructor](./ch07/delegating_constructor.md)\n- [封装 encapsulation](./ch07/encapsulation.md)\n- [显式构造函数 explicit constructor](./ch07/explicit_constructor.md)\n- [前向声明 forward declaration](./ch07/forward_declaration.md)\n- [友元 friend](./ch07/friend.md)\n- [实现 implementation](./ch07/implementation.md)\n- [不完全类型 incomplete type](./ch07/incomplete_type.md)\n- [接口 interface](./ch07/interface.md)\n- [成员函数 member function](./ch07/member_function.md)\n- [可变数据成员 mutable data member](./ch07/mutable_data_member.md)\n- [名字查找 name lookup](./ch07/name_lookup.md)\n- [私有成员 private member](./ch07/private_member.md)\n- [公有成员 public member](./ch07/public_member.md)\n- [合成默认构造函数 synthesized default constructor](./ch07/synthesized_default_constructor.md)\n- [this指针 this pointer](./ch07/this_pointer.md)\n\n### 第8章_IO库\n\n- [条件状态 condition state](./ch08/condition_state.md)\n- [文件模式 file mode](./ch08/file_mode.md)\n- [文件流 file stream](./ch08/file_stream.md)\n- [fstream](./ch08/fstream.md)\n- [ifstream](./ch08/ifstream.md)\n- [继承 inheritance](./ch08/inheritance.md)\n- [istringstream](./ch08/istringstream.md)\n- [ofstream](./ch08/ofstream.md)\n- [字符串流 string stream](./ch08/string_stream.md)\n\n### 第9章_顺序容器\n\n- [适配器 adaptor](./ch09/adaptor.md)\n- [数组 array](./ch09/array.md)\n- [begin](./ch09/begin.md)\n- [cbegin](./ch09/cbegin.md)\n- [cend](./ch09/cend.md)\n- [容器 container](./ch09/container.md)\n- [deque](./ch09/deque.md)\n- [end](./ch09/end.md)\n- [forward_list](./ch09/forward_list.md)\n- [迭代器范围 iterator range](./ch09/iterator_range.md)\n- [左闭合区间 left-inclusive interval](./ch09/left-inclusive_interval.md)\n- [list](./ch09/list.md)\n- [首前迭代器 off-the-beginning iterator](./ch09/off-the-beginning_iterator.md)\n- [尾后迭代器 off-the-end iterator](./ch09/off-the-end_iterator.md)\n- [priority_queue](./ch09/priority_queue.md)\n- [queue](./ch09/queue.md)\n- [顺序容器 sequential container](./ch09/sequential_container.md)\n- [stack](./ch09/stack.md)\n- [vector](./ch09/vector.md)\n\n### 第10章_泛型算法\n\n- [back_inserter](./ch10/back_inserter.md)\n- [双向迭代器 bidirectional iterator](./ch10/bidirectional_iterator.md)\n- [二元谓词 binary predicate](./ch10/binary_predicate.md)\n- [bind](./ch10/bind.md)\n- [可调用对象 callable object](./ch10/callable_object.md)\n- [捕获列表 capture list](./ch10/capture_list.md)\n- [cref](./ch10/cref.md)\n- [前向迭代器 forward iterator](./ch10/forward_iterator.md)\n- [front_insertor](./ch10/front_insertor.md)\n- [泛型算法 generic algorithm](./ch10/generic_algorithm.md)\n- [输入迭代器 input iterator](./ch10/input_iterator.md)\n- [插入器 inserter](./ch10/inserter.md)\n- [插入迭代器 insert iterator](./ch10/insert_iterator.md)\n- [istream_iterator](./ch10/istream_iterator.md)\n- [迭代器类别 iterator category](./ch10/iterator_category.md)\n- [lambda表达式 lambda expression](./ch10/lambda_expression.md)\n- [移动迭代器 move iterator](./ch10/move_iterator.md)\n- [ostream_iterator](./ch10/ostream_iterator.md)\n- [输出迭代器 output iterator](./ch10/output_iterator.md)\n- [谓词 predicate](./ch10/predicate.md)\n- [随机访问迭代器 random-access iterator](./ch10/random-access_iterator.md)\n- [ref](./ch10/ref.md)\n- [反向迭代器 reverse iterator](./ch10/reverse_iterator.md)\n- [流迭代器 stream iterator](./ch10/stream_iterator.md)\n- [一元谓词 unary predicate](./ch10/unary_predicate.md)\n\n### 第11章_关联容器\n\n- [关联数组 associative array](./ch11/associative_array.md)\n- [关联容器 associative container](./ch11/associative_container.md)\n- [哈希函数 hash function](./ch11/hash_function.md)\n- [hash](./ch11/hash.md)\n- [key_type](./ch11/key_type.md)\n- [map](./ch11/map.md)\n- [mapped_type](./ch11/mapped_type.md)\n- [multimap](./ch11/multimap.md)\n- [multiset](./ch11/multiset.md)\n- [pair](./ch11/pair.md)\n- [set](./ch11/set.md)\n- [严格弱序 strict weak ordering](./ch11/strict_weak_ordering.md)\n- [无序容器 unordered container](./ch11/unordered_container.md)\n- [unordered_map](./ch11/unordered_map.md)\n- [unordered_multimap](./ch11/unordered_multimap.md)\n- [unordered_multiset](./ch11/unordered_multiset.md)\n- [unordered_set](./ch11/unordered_set.md)\n- [value_type](./ch11/value_type.md)\n\n### 第12章_动态内存\n\n- [allocator](./ch12/allocator.md)\n- [空悬指针 dangling pointer](./ch12/dangling_pointer.md)\n- [delete](./ch12/delete.md)\n- [释放器 deleter](./ch12/deleter.md)\n- [析构函数 destructor](./ch12/destructor.md)\n- [动态分配的 dynamically allocated](./ch12/dynamically_allocated.md)\n- [自由空间 free store](./ch12/free_store.md)\n- [堆 heap](./ch12/heap.md)\n- [new](./ch12/new.md)\n- [定位new placement new](./ch12/placement_new.md)\n- [引用计数 reference count](./ch12/reference_count.md)\n- [shared_ptr](./ch12/shared_ptr.md)\n- [智能指针 smart pointer](./ch12/smart_pointer.md)\n- [unique_ptr](./ch12/unique_ptr.md)\n- [weak_ptr](./ch12/weak_ptr.md)\n\n### 第13章_拷贝控制\n\n- [拷贝并交换 copy and swap](./ch13/copy_and_swap.md)\n- [拷贝赋值运算符 copy-assignment operator](./ch13/copy-assignment_operator.md)\n- [拷贝构造函数 copy constructor](./ch13/copy_constructor.md)\n- [拷贝控制 copy control](./ch13/copy_control.md)\n- [拷贝初始化 copy initialization](./ch13/copy_initialization.md)\n- [删除的函数 deleted function](./ch13/deleted_function.md)\n- [析构函数 destructor](./ch13/destructor.md)\n- [左值引用 lvalue reference](./ch13/lvalue_reference.md)\n- [逐成员拷贝/赋值 memberwise copy assign](./ch13/memberwise_copy_assign.md)\n- [移动赋值运算符 move-assignment operator](./ch13/move-assignment_operator.md)\n- [移动构造函数 move constructor](./ch13/move_constructor.md)\n- [move](./ch13/move.md)\n- [重载运算符 overload operator](./ch13/overload_operator.md)\n- [引用计数 reference count](./ch13/reference_count.md)\n- [引用限定符 reference qualifier](./ch13/reference_qualifier.md)\n- [右值引用 rvalue reference](./ch13/rvalue_reference.md)\n- [合成赋值运算符 synthesized assignment operator](./ch13/synthesized_assignment_operator.md)\n- [合成拷贝/移动构造函数 synthesized copy move constructor](./ch13/synthesized_copy_move_constructor.md)\n- [合成析构函数 synthesized destructor](./ch13/synthesized_destructor.md)\n\n### 第14章_操作重载与类型转换\n\n- [调用形式 call signature](./ch14/call_signature.md)\n- [类类型转换 class-type conversion](./ch14/class-type_conversion.md)\n- [类型转换运算符 conversion operator](./ch14/conversion_operator.md)\n- [显式的类型转换运算符 explicit conversion operator](./ch14/explicit_conversion_operator.md)\n- [函数对象 function object](./ch14/function_object.md)\n- [函数表 function table](./ch14/function_table.md)\n- [函数模板 function template](./ch14/function_template.md)\n- [重载的运算符 overloaded operator](./ch14/overloaded_operator.md)\n- [用户定义的类型转换 user-defined conversion](./ch14/user-defined_conversion.md)\n\n### 第15章_面向对象程序设计\n\n- [抽象基类 abstract base class](./ch15/abstract_base_class.md)\n- [可访问的 accessible](./ch15/accessible.md)\n- [基类 base class](./ch15/base_class.md)\n- [类派生列表 class derivation list](./ch15/class_derivation_list.md)\n- [派生类 derived class](./ch15/derived_class.md)\n- [派生类向基类的类型转换 derived-to-base conversion](./ch15/derived-to-base_conversion.md)\n- [直接基类 direct base class](./ch15/direct_base_class.md)\n- [动态绑定 dynamic binding](./ch15/dynamic_binding.md)\n- [动态类型 dynamic type](./ch15/dynamic_type.md)\n- [间接基类 indirect base class](./ch15/indirect_base_class.md)\n- [继承 inheritance](./ch15/inheritance.md)\n- [面向对象编程 object-oriented programming](./ch15/object-oriented_programming.md)\n- [覆盖 override](./ch15/override.md)\n- [多态性 polymorphism](./ch15/polymorphism.md)\n- [私有继承 private inheritance](./ch15/private_inheritance.md)\n- [protected访问说明符 protected access specifier](./ch15/protected_access_specifier.md)\n- [受保护的继承 protected inheritance](./ch15/protected_inheritance.md)\n- [公有继承 public inheritance](./ch15/public_inheritance.md)\n- [纯虚函数 pure virtual](./ch15/pure_virtual.md)\n- [重构 refactoring](./ch15/refactoring.md)\n- [运行时绑定 run-time binding](./ch15/run-time_binding.md)\n- [切掉 sliced down](./ch15/sliced_down.md)\n- [静态类型 static type](./ch15/static_type.md)\n- [虚函数 virtual function](./ch15/virtual_function.md)\n\n### 第16章_模板与泛型编程\n\n- [类模板 class template](./ch16/class_template.md)\n- [默认模板实参 default template argument](./ch16/default_template_argument.md)\n- [显式实例化 explicit instantiation](./ch16/explicit_instantiation.md)\n- [显式模板实参 explicit template argument](./ch16/explicit_template_argument.md)\n- [函数参数包 function parameter pack](./ch16/function_parameter_pack.md)\n- [函数模板 function template](./ch16/function_template.md)\n- [实例化 instantiate](./ch16/instantiate.md)\n- [实例 instantiation](./ch16/instantiation.md)\n- [成员模板 member template](./ch16/member_template.md)\n- [非类型参数 nontype parameter](./ch16/nontype_parameter.md)\n- [包扩展 pack expansion](./ch16/pack_expansion.md)\n- [参数包 parameter pack](./ch16/parameter_pack.md)\n- [部分特例化 partial specialization](./ch16/partial_specialization.md)\n- [模式 pattern](./ch16/pattern.md)\n- [模板实参推断 template argument deduction](./ch16/template_argument_deduction.md)\n- [模板实参 template argument](./ch16/template_argument.md)\n- [模板参数列表 template parameter list](./ch16/template_parameter_list.md)\n- [模板参数 template parameter](./ch16/template_parameter.md)\n- [模板参数包 template parameter pack](./ch16/template_parameter_pack.md)\n- [模板特例化 template specialization](./ch16/template_specialization.md)\n- [类型参数 type parameter](./ch16/type_parameter.md)\n- [类型转换 type transformation](./ch16/type_transformation.md)\n- [可变参数模板 variadic template](./ch16/variadic_template.md)\n\n### 第17章_标准库特殊设施\n\n- [bitset](./ch17/bitset.md)\n- [cmatch](./ch17/cmatch.md)\n- [cregex_iterator](./ch17/cregex_iterator.md)\n- [csub_match](./ch17/csub_match.md)\n- [默认随机数引擎 default random engine](./ch17/default_random_engine.md)\n- [格式化IO formatted IO](./ch17/formatted_IO.md)\n- [get](./ch17/get.md)\n- [高位 high-order](./ch17/high-order.md)\n- [低位 low-order](./ch17/low-order.md)\n- [操纵符 manipulator](./ch17/manipulator.md)\n- [随机数分布 random-number distribution](./ch17/random-number_distribution.md)\n- [随机数引擎 random-number engine](./ch17/random-number_engine.md)\n- [随机数发生器 random-number generator](./ch17/random-number_generator.md)\n- [regex_error](./ch17/regex_error.md)\n- [regex_match](./ch17/regex_match.md)\n- [regex](./ch17/regex.md)\n- [regex_replace](./ch17/regex_replace.md)\n- [regex_search](./ch17/regex_search.md)\n- [正则表达式 regular expression](./ch17/regular_expression.md)\n- [种子 seed](./ch17/seed.md)\n- [smatch](./ch17/smatch.md)\n- [sregex_iterator](./ch17/sregex_iterator.md)\n- [ssub_match](./ch17/ssub_match.md)\n- [子表达式 subexpression](./ch17/subexpression.md)\n- [tuple](./ch17/tuple.md)\n- [未格式化IO unformatted IO](./ch17/unformatted_IO.md)\n\n### 第18章_用于大型程序的工具\n\n- [捕获所有异常 catch-all](./ch18/catch-all.md)\n- [catch子句 catch clause](./ch18/catch_clause.md)\n- [构造函数顺序 constructor order](./ch18/constructor_order.md)\n- [异常声明 exception declaration](./ch18/exception_declaration.md)\n- [异常处理 exception handling](./ch18/exception_handling.md)\n- [异常对象 exception object](./ch18/exception_object.md)\n- [文件中的静态声明 file static](./ch18/file_static.md)\n- [函数try语句块 function try block](./ch18/function_try_block.md)\n- [全局命名空间 global namespace](./ch18/global_namespace.md)\n- [处理代码 handler](./ch18/handler.md)\n- [内联的命名空间 inline namespace](./ch18/inline_namespace.md)\n- [多重继承 multiple inheritance](./ch18/multiple_inheritance.md)\n- [命名空间的别名 namespace alias](./ch18/namespace_alias.md)\n- [命名空间 namespace](./ch18/namespace.md)\n- [命名空间污染 namespace pollution](./ch18/namespace_pollution.md)\n- [noexcept运算符 noexcept operator](./ch18/noexcept_operator.md)\n- [noexcept说明 noexcept specification](./ch18/noexcept_specification.md)\n- [不抛出说明 nonthrowing specification](./ch18/nonthrowing_specification.md)\n- [引发 raise](./ch18/raise.md)\n- [重新抛出 rethrow](./ch18/rethrow.md)\n- [栈展开 stack unwinding](./ch18/stack_unwinding.md)\n- [terminate](./ch18/terminate.md)\n- [throw](./ch18/throw.md)\n- [try语句块 try block](./ch18/try_block.md)\n- [未命名的命名空间 unnamed namespace](./ch18/unnamed_namespace.md)\n- [using声明 using declaration](./ch18/using_declaration.md)\n- [using指示 using directive](./ch18/using_directive.md)\n- [虚基类 virtual base class](./ch18/virtual_base_class.md)\n- [虚继承 virtual inheritance](./ch18/virtual_inheritance.md)\n\n### 第19章_特殊工具与技术\n\n- [匿名union anonymous union](./ch19/anonymous_union.md)\n- [位域 bit-field](./ch19/bit-field.md)\n- [判别式 discriminant](./ch19/discriminant.md)\n- [dynamic_cast](./ch19/dynamic_cast.md)\n- [枚举类型 enumeration](./ch19/enumeration.md)\n- [枚举成员 enumerator](./ch19/enumerator.md)\n- [free](./ch19/free.md)\n- [链接指示 link directive](./ch19/link_directive.md)\n- [局部类 local class](./ch19/local_class.md)\n- [malloc](./ch19/malloc.md)\n- [mem_fn](./ch19/mem_fn.md)\n- [嵌套类 nested class](./ch19/nested_class.md)\n- [潜逃类型 nested type](./ch19/nested_type.md)\n- [不可移植 nonportable](./ch19/nonportable.md)\n- [operator_delete](./ch19/operator_delete.md)\n- [operator_new](./ch19/operator_new.md)\n- [定位new表达式 placement new expression](./ch19/placement_new_expression.md)\n- [成员指针 pointer to member](./ch19/pointer_to_member.md)\n- [运行时类型识别 run-time tyoe identification](./ch19/run-time_tyoe_identification.md)\n- [限定作用域的枚举类型 scoped enumeration](./ch19/scoped_enumeration.md)\n- [typeid运算符 typeid operator](./ch19/typeid_operator.md)\n- [typeinfo](./ch19/typeinfo.md)\n- [联合 union](./ch19/union.md)\n- [不限定作用域的枚举类型 unscoped enumeration](./ch19/unscoped_enumeration.md)\n- [volatile](./ch19/volatile.md)\n\n"
  },
  {
    "path": "codes/CppPrimer/gen_readme.sh",
    "content": "# 生成README.md文件\n\n#! /bin/bash\n\nold_dir=$(pwd)\nreadme_file=${old_dir}/README.md\n\n#######################################################################\n\n# 设置每一个章节名字\n\nget_all_chapters()\n{\ncat << !CHAPTERS!\n第1章_开始\n第2章_变量和基本类型\n第3章_字符串、向量和数组\n第4章_表达式\n第5章_语句\n第6章_函数\n第7章_类\n第8章_IO库\n第9章_顺序容器\n第10章_泛型算法\n第11章_关联容器\n第12章_动态内存\n第13章_拷贝控制\n第14章_操作重载与类型转换\n第15章_面向对象程序设计\n第16章_模板与泛型编程\n第17章_标准库特殊设施\n第18章_用于大型程序的工具\n第19章_特殊工具与技术\n!CHAPTERS!\n}\n\nchapters=$(get_all_chapters)\n\n######################################################################\n\n# 生成一个章节练习的md文本\n\ngen_exercise_txt()\n{\n\tch_name=$1\t\t\t\t# 章节名\n\tch_name=${ch_name/_/ }\t\t\t# 取消下划线\n\n\tch_dir=$(ls -d $2*)\t\t\t# 章节路径\n\tn_ch=$3\t\t\t\t\t# 第几章\n\n\t#echo \"$ch_name $ch_dir\"\n\n\tcd $ch_dir\n\n\texercise_files=$(ls -d exercise*); # 所有的练习文件，包括目录\n\techo \"## $ch_name\" >> $readme_file\n\techo >> $readme_file\n\techo \"|A|L|L|E|X|E|\" >> $readme_file\n\techo \"| :-: | :-: | :-: | :-: | :-: | :-: |\" >> $readme_file # 居中\n\t\n\tnum=0\t# 第几个练习\n\tfor exercise_file in $exercise_files; do\n\n\t\tnum=$((num + 1))\n\n\t\t# exercise_1_01a.cpp --> 01a.cpp --> 1a.cpp --> 1a --> 练习1.1a\n\t\ttab_name=${exercise_file##*_}\n\t\ttab_name=${tab_name##0}\n\t\ttab_name=${tab_name%%.*}\n\t\ttab_name=练习$n_ch.$tab_name\n\n\t\techo -e \"|[$tab_name]($ch_dir/$exercise_file \\\"./$ch_dir/$exercise_file\\\")\\c\" >> $readme_file\n\n\t\t# 6列一行\n\t\tif [ $((num % 6)) = \"0\" ]; then\n\t\t\techo \"|\" >> $readme_file\n\t\tfi\n\tdone\n\n\tif [ $((num % 6)) != \"0\" ]; then\n\t\techo \"|\" >> $readme_file\n\tfi\n\t\n\techo >> $readme_file\n\n\tcd ..\n\treturn 0\n}\n\n######################################################################\n\n# 生成一个章节的案例代码的文本\n\ngen_example_txt()\n{\n\tch_dir=$(ls -d $1*)\t\t# 章节路径\n\n\tcd $ch_dir\n\texample_files=$(ls -d example* 2> /dev/null); # 所有的案例文件，包括目录\n\n\tif [ \"$example_files\" = \"\" ]; then\n\t\tcd ..\n\t\treturn 0\n\tfi\n\n\techo \"### 案例代码\" >> $readme_file\n\techo >> $readme_file\n\n\tfor file in $example_files; do\n\t\tgrep_file=$file\n\t\tif [ -d $file ]; then\n\t\t\tgrep_file=$file/main.cpp\n\t\tfi\n\n\t\t# // example: 数组形参（p193） --> 数组形参（p193）\n\t\tdesc=$(grep \"// example\" $grep_file)\n\t\tdesc=${desc#*: }\n\n\t\tif [ \"$desc\" != \"\" ]; then\n\t\t\techo \"- [$desc]($ch_dir/$file)\" >> $readme_file\n\t\t\techo >> $readme_file\n\t\tfi\n\tdone\n\n\tcd ..\n\treturn 0\n}\n\n######################################################################\n\ngen_readme()\n{\n\tn_ch=0 \t\t# 当前处理的第几章\n\tfor ch_name in $chapters; do\n\t\tn_ch=$(($n_ch + 1))\n\n\t\tcur_ch=ch # 章节的英文路径开头\n\n\t\tif [ $n_ch -lt 10 ]; then\n\t\t\tcur_ch=ch0\n\t\tfi\n\n\t\tcur_ch=${cur_ch}$n_ch\n\n\t\tgen_exercise_txt $ch_name $cur_ch $n_ch\n\t\tgen_example_txt $cur_ch\n\tdone\n\n\treturn 0\n}\n\nmain()\n{\n\trm -f $readme_file\n\ttouch $readme_file\n\n\techo '# C++ Primer第五版学习笔记' >> $readme_file\n\techo -e '\\n学习环境：CentOS7 gcc4.8.5' >> $readme_file\n\techo -e '\\n以下内容需要更高的编译器版本支持：\\n\\n- 正则表达式库\\n\\n- 一些IO操纵符，比如hexfloat\\n\\n可以使用[在线编译器](http://coliru.stacked-crooked.com/)编译运行相关代码。' >> $readme_file\n\techo -e '\\n---' >> $readme_file\n\techo >> $readme_file\n\n\tgen_readme\n\n\treturn 0\n}\n\nmain\n\nexit 0\n"
  },
  {
    "path": "mkdocs.yml",
    "content": "site_name: C/C++学习笔记\nsite_description: C/C++学习笔记\n\ncopyright: 'Copyright &copy; 2018-2021 Diwen Liu'\n\ndocs_dir: notes\n\nrepo_url: https://github.com/demon90s/CppStudy\nedit_uri: \"tree/master/notes\"\n\nnav:\n  - 简介: index.md\n\n  - C语言程序设计:\n    - Chapters:\n      - ch01 C语言概述: CProgramming/ch01 C语言概述.md\n      - ch02 C语言基本概念: CProgramming/ch02 C语言基本概念.md\n      - ch03 格式化输入输出: CProgramming/ch03 格式化输入输出.md\n      - ch04 表达式: CProgramming/ch04 表达式.md\n      - ch05 选择语句: CProgramming/ch05 选择语句.md\n      - ch06 循环: CProgramming/ch06 循环.md\n      - ch07 基本类型: CProgramming/ch07 基本类型.md\n      - ch08 数组: CProgramming/ch08 数组.md\n      - ch09 函数: CProgramming/ch09 函数.md\n      - ch10 程序结构: CProgramming/ch10 程序结构.md\n      - ch11 指针: CProgramming/ch11 指针.md\n      - ch12 指针和数组: CProgramming/ch12 指针和数组.md\n      - ch13 字符串: CProgramming/ch13 字符串.md\n      - ch14 预处理器: CProgramming/ch14 预处理器.md\n      - ch15 编写大规模程序: CProgramming/ch15 编写大规模程序.md\n      - ch16 结构、联合和枚举: CProgramming/ch16 结构、联合和枚举.md\n      - ch17 指针的高级应用: CProgramming/ch17 指针的高级应用.md\n      - ch18 声明: CProgramming/ch18 声明.md\n      - ch19 程序设计: CProgramming/ch19 程序设计.md\n      - ch20 低级程序设计: CProgramming/ch20 低级程序设计.md\n      - ch21 标准库: CProgramming/ch21 标准库.md\n      - ch22 输入输出: CProgramming/ch22 输入_输出.md\n      - ch23 库对数值和字符数据的支持: CProgramming/ch23 库对数值和字符数据的支持.md\n      - ch24 错误处理: CProgramming/ch24 错误处理.md\n      - ch25 国际化特性: CProgramming/ch25 国际化特性.md\n      - ch26 其他库函数: CProgramming/ch26 其他库函数.md\n    - CheatSheet:\n      - 惯用法: CProgramming/cheatsheet/惯用法.md\n\n  - C++ Primer:\n    - Chapters: \n      - ch01 开始: CppPrimer/ch01 开始.md\n      - ch02 变量和基本类型: CppPrimer/ch02 变量和基本类型.md\n      - ch03 字符串、向量和数组: CppPrimer/ch03 字符串、向量和数组.md\n      - ch04 表达式: CppPrimer/ch04 表达式.md\n      - ch05 语句: CppPrimer/ch05 语句.md\n      - ch06 函数: CppPrimer/ch06 函数.md\n      - ch07 类: CppPrimer/ch07 类.md\n      - ch08 IO库: CppPrimer/ch08 IO库.md\n      - ch09 顺序容器: CppPrimer/ch09 顺序容器.md\n      - ch10 泛型算法: CppPrimer/ch10 泛型算法.md\n      - ch11 关联容器: CppPrimer/ch11 关联容器.md\n      - ch12 动态内存: CppPrimer/ch12 动态内存.md\n      - ch13 拷贝控制: CppPrimer/ch13 拷贝控制.md\n      - ch14 操作重载与类型转换: CppPrimer/ch14 操作重载与类型转换.md\n      - ch15 面向对象程序设计: CppPrimer/ch15 面向对象程序设计.md\n      - ch16 模板与泛型编程: CppPrimer/ch16 模板与泛型编程.md\n      - ch17 标准库特殊设施: CppPrimer/ch17 标准库特殊设施.md\n      - ch18 用于大型程序的工具: CppPrimer/ch18 用于大型程序的工具.md\n      - ch19 特殊工具与技术: CppPrimer/ch19 特殊工具与技术.md\n\n  - Effective Modern C++:\n    - ch01_型别推导:\n      - 条款1_理解模板型别推导: EffectiveModernCpp/ch01_型别推导/条款1_理解模板型别推导.md\n      - 条款2_理解auto型别推导: EffectiveModernCpp/ch01_型别推导/条款2_理解auto型别推导.md\n      - 条款3_理解decltype: EffectiveModernCpp/ch01_型别推导/条款3_理解decltype.md\n      - 条款4_掌握查看型别推导结果的方法: EffectiveModernCpp/ch01_型别推导/条款4_掌握查看型别推导结果的方法.md\n    - ch02_auto:\n      - 条款5_优先选用auto而非显式型别声明: EffectiveModernCpp/ch02_auto/条款5_优先选用auto而非显式型别声明.md\n      - 条款6_当auto推导的型别不符合要求时使用带显式型别的初始化物习惯用法: EffectiveModernCpp/ch02_auto/条款6_当auto推导的型别不符合要求时使用带显式型别的初始化物习惯用法.md\n\ntheme:\n  name: material\n  palette:\n    #scheme: slate\n    scheme: default \n  features:\n    - navigation.tabs\n  #logo:\n  #  icon: 'school'\n  #palette:\n  #  primary: 'blue'\n  #  accent: 'blue'\n  #font:\n  #  text: 'PT Sans'\n  #  code: 'PT Sans'\n  language: 'zh'\n  #feature:\n  #  tabs: true\n\nextra:\n  search:\n    language: 'en, jp'\n\nmarkdown_extensions:\n  - admonition\n  - codehilite\n  - pymdownx.mark\n  - pymdownx.highlight\n  - pymdownx.inlinehilite\n  - pymdownx.superfences\n  - pymdownx.snippets\n"
  },
  {
    "path": "notes/CProgramming/ch01 C语言概述.md",
    "content": "# 第一章 C语言概述\n\nC语言是20世纪70年代初期，在贝尔实验室开发出来的一种用途广泛的编程语言。\n\n---\n\n## C语言的历史\n\nC语言是Unix系统开发过程中的一个副产品。它被用来重写Unix系统。\n\n到20世纪80年代，许多计算机开始使用C语言开发程序，为了保证其程序的可移植性，建立标准成为了共识。\n\n1989年，通过C89标准。\n\n1999年，通过C99标准，但这个标准还没有被普遍使用。\n\n### C++ 语言\n\n虽然采纳了 ANSI/ISO 标准以后C语言自身不再发生变化。但是，从某种意义上说，随着基于C语言的新式语言的产生，C语言的演变还在继续。新式语言包括C++。它在许多方面对C语言进行了扩展，尤其是增加了面向对象编程的特性。\n\n随着C++语言的迅速普及，在不久的将来你很可能会用C++语言编写程序。果真如此，为何还要费心学习C语言呢？首先，C++语言比C语言更加难学，因此在掌握C++语言前，最好先精通C语言；其次，我们身边存在着大量的C语言代码，需要去维护和阅读；最后，不是每个人都喜欢改用C++编程，例如对于编写小规模的程序，使用C++反而不会获得多少好处。\n\n## C语言的优缺点\n\nC语言的优缺点都源于它最初的用途，以及其基础理论体系。\n\n- C语言是一种底层语言。它提供了对内存访问的功能。C程序的许多服务都依赖于操作系统提供的接口。\n\n- C语言是一种小型语言。C语言的特性不多，应用程序的绝大部分功能依赖于标准库。\n\n- C语言是一种包容性语言。C语言假设用户知道自己在做什么，因此有编写自由度。C语言不强制进行错误检查。\n\n### C语言的优点\n\n1. 高效。发明C语言的目的是为了替代汇编语言。\n\n2. 可移植。有标准库的存在。\n\n3. 功能强大、灵活。C语言的数据类型和运算符集合有足够强大的表达能力。\n\n4. 与Unix集成。\n\n### C语言的缺点\n\n1. C程序更容易隐藏错误。由于其灵活性，导致编写的代码令编译器很难检查错误。\n\n2. C程序可能会难以理解。\n\n3. C程序可能会难以修改。因为它设计时没考虑到维护的问题。C语言没有提供类，包等模块化概念。\n\n### 高效地使用C语言\n\n1. 学习规避C语言的缺陷。比如越界问题。\n2. 使用软件工具。\n3. 利用现有的代码库。\n4. 采用切合实际的编码规范。\n5. 避免“投机取巧”和极度复杂的代码。\n6. 使用标准C，少用经典C。标准C即是 ANSI C ，本书采用的是标准C。\n7. 避免不可以移植性。"
  },
  {
    "path": "notes/CProgramming/ch02 C语言基本概念.md",
    "content": "# 第二章 C语言基本概念\n\n---\n\n## 编写一个简单的C程序\n\n### 程序：显示双关语\n\n这是经典C的一个示例：\n\n```c\n// pun.c\n#include <stdio.h>\n\nmain()\n{\n    printf(\"To C, or not to C: that is the question.\\n\");\n}\n```\n\n### 编译和链接\n\n首先，需要一个.c文件保存程序代码，接下来需要把程序转换为机器可以执行的形式。通常包含下列三个步骤：\n\n- 预处理。首先会把程序送交给预处理器（ preprocessor ）。预处理器执行以#开头的命令。\n\n- 编译。修改后的程序现在可以进入编译器（ compiler ）了。编译器会把程序翻译成机器指令（即目标代码， object code ）。\n\n- 链接。链接器（ linker ）把由编译器产生的目标代码和任何其他附加代码整合在一起，产生完全可执行的程序。\n\n这个过程可以一步完成，即：\n\n```bash\ncc pun.c\n```\n\n在编译和链接好程序后，编译器 cc 会把可执行程序放到默认名为 a.out 的文件中。编译器 cc 有许多选项，其中 -o 允许给可执行程序选择一个名字：\n\n```bash\ncc -o pun pun.c\n```\n\n如果使用 gcc 进行编译，那么建议在编译时采用 -Wall 选项：\n\n```bash\ngcc -Wall -o pun pun.c\n```\n\n也可以手动分布完成：\n\n```bash\ncc -o main.i -E main.c # 预编译\ncc -o main.o -c main.i # 编译\ncc -o main main.o      # 链接\n```\n\n## 简单程序的一般形式\n\n形式如：\n\n```c\n指令\n\nint main()\n{\n    语句\n}\n```\n\n### 指令\n\n在编译C程序之前，预处理器会首先对C程序进行编辑。我们把预处理器执行的命令称为指令。这里只关注 #include 指令。\n\n```c\n#include <stdio.h>\n```\n\n这条指令说明，在编译前把 stdio.h 中的信息“包含”到程序中。这段程序中包含 stdio,h 的原因是：C语言没有内置的“读”和“写”命令。因此，进行输入/输出操作就需要用标准库中的函数来实现。\n\n这里是指预所有指令都是以#开头。一条指令必须占据一行，且不留分号结尾。\n\n### 函数\n\n函数是用来构建程序的一个构建块。C程序就是函数的集合。函数分为两大类：一类是程序员编写的函数，另一类则是由C语言的实现所提供的函数。后者可以称为库函数（ library function ）。\n\n在C语言中，函数仅仅是一系列组合在一起并且赋予了名字的语句。某些函数计算一个值，而某些函数不是。计算出一个值的函数可以用 return 语句来指定所“返回”的值。\n\n每个程序必须有一个 main 函数。 main 函数是非常特殊的：在执行程序时系统会自动调用 main 函数。\n\nmain 函数在程序终止时向操作系统返回一个状态码。 pun 程序始终让 main 函数返回0，0表明程序正常终止。\n\n建议加入 return 语句，如果不这样做，某些编译器可能会产生一条警告信息：\n\n```c\n// pun.c\n#include <stdio.h>\n\nmain()\n{\n    printf(\"To C, or not to C: that is the question.\\n\");\n    return 0;\n}\n```\n\n### 语句\n\n语句是程序运行时执行的命令。每条语句都要以分号结尾。\n\n一条语句可以占据多行。\n\n程序 pun.c 只用到了两种语句。一种是返回语句，一种则是函数调用（ function call ）语句。为了在屏幕上显示一条字符串就调用了 printf 函数。\n\n### 显示字符串\n\n我们用 printf 函数显示了一条字符串字面量（ string literal ）。字符串字面量是用一对双引号包围的一系列字符。\n\n当打印结束时， printf 函数不会自动跳转到下一输出行。为了让 printf 跳转到下一行，必须要在打印的字符串中包含一个 \\n （换行符）。写换行符就意味着终止当前行，然后把后续的输出转到下一行进行。\n\n换行符可以在一个字符串字面量中出现多次。比如：\n\n```c\nprintf(\"Brevity is the soul of wit.\\n -- Shakespeare\\n\");\n```\n\n### 注释\n\n注释就是代码的说明。在预编译后，注释会移除出代码。\n\n例如：\n\n```c\n/* This is a comment */\n```\n\n为 pun.c 增加注释：\n\n```c\n/*\tName: pun.c\n\tPurpose: Prints a bad pun.\n\tAuthor: K.N.King\n\tData written: 5/21/95\n*/\n```\n\n## 变量和赋值\n\n变量（ variable ）就是用来存储数据的存储单元。\n\n### 类型\n\n一个变量必须有一个类型。类型决定了存储单元的大小和对变量的操作方式。\n\nint 型变量可以存储整数，例如0、1、392或者-2553，但是，整数的取值范围是受限制的。某些计算机上，int型数值的最大取值仅仅是32767。\n\nfloat 型变量可以存储更大的数值，而且，float型变量可以存储带小数位的数据，例如379.125。但是，float 型变量有一些缺陷，即这类变量需要的存储空间要大于 int 型变量。而且，进行算术运算时 float 型变量通常比 int 型变量慢。另外， float 型变量所存储的数值往往只是实际数值的一个近似值。\n\n### 声明\n\n在使用变量前，必须对其进行声明，这也是为了便于编译器工作。例如，声明变量 height 的方式如：\n\n```c\nint height;\n```\n\n如果几个变量具有相同的类型，就可以把它们的声明合并：\n\n```c\nint height, length, width;\n```\n\n当 main 函数包含声明时，必须把声明放置在语句之前：\n\n```c\nmain()\n{\n    声明\n    语句\n}\n```\n\n### 赋值\n\n变量可以通过赋值（ assignment ）的方式获得一个值。例如：\n\n```c\nheight = 8;\nvolume = height * length * width;\n```\n\n赋值运算符的右侧可以是一个含有常量、变量和运算符的公式（表达式， expression ）。\n\n### 显示变量的值\n\n用 printf 可以显示当前变量的值。\n\n```c\nprintf(\"Height: %d\\n\", height);\n```\n\n占位符 %d 用来指明在打印过程中变量 height 的值的显示位置。 %d 仅用于 int 型变量，如果要打印 float 型变量，需要用 %f 来代替。默认情况下， %f 会显示小数点后6位数字，若需要显示小数点后n位数字，则可以把 .n 放置在%和f之间。\n\n```c\nprintf(\"Profit: $%.2f\\n\", profit);\n```\n\n### 初始化\n\n当程序开始执行时，某些变量会被自动设置为0，而大多数变量则不会。没有默认值并且尚未在程序中被赋值的变量是未初始化的（ uninitialized ）。\n\n使用初始化式对其变量进行初始化，如：\n\n```c\nint a = 0;\n```\n\n## 读入输入\n\n为了获取输入，就要用到 scanf 函数。 scanf 中的字母f和 printf 中的f含义相同，都是表示“格式化”的意思。 scanf 和 printf 函数都需要使用格式串（ format string ）来说明输入或输出的样式。\n\n为了读取一个 int 型数值，可以使用如下的 scanf 函数调用。\n\n```c\nscanf(\"%d\", &i);\n```\n\n字符串“%d”说明 scanf 读入的是一个整数，i是一个 int 型变量，用来存储读入的输入。\n\n读入一个 float 型数值时，需要这样的 scanf 调用：\n\n```c\nscanf(\"%f\", &x);\n```\n\n%f只适用于 float 型变量。\n\n## 定义常量\n\n常量（ constant ）是在程序执行过程中固定不变的量。当程序含有常量时，建议给这些常量命名。方式是使用宏定义（ macro defination ）。\n\n```c\n#define N 4\n```\n\n这里的 #define 是预处理指令。当程序进行编译时，预处理器会把每一个宏用其表示的值替换回来。\n\n此外，还可以利用宏来定义表达式：\n\n```c\n#define SCALE_FACTOR (5.0 / 9.0)\n```\n\n当宏包含运算时，必须用括号把表达式括起来。\n\n宏的名字一般用大写字母，这是大多数程序员遵守的规范。\n\n## 标识符\n\n标识符就是函数、变量、宏等实体的名字。\n\n标识符由字母、数字和下划线组成，且区分大小写。必须以字母或下划线开头。\n\n为了使名字清晰，可以使用两种命名风格：\n\n```c\nsymbol_table\nSymbolTable\n```\n\n**关键字**\n\n关键字（ keyword ）对编译器而言都有特殊的含义，因此标识符不能和关键字一样。\n\n所有的关键字见书本p19\n\n## C语言程序的布局\n\nC程序可以被看成一连串的**记号（ token ）**。记号就是无法分割的字符组。\n\n标识符、关键字、运算符、字符串等都是记号。\n\n记号之间可以有空格，换行等字符。\n\n有记号的概念后，C程序就可以这样书写：\n\n- 语句可以放到多行内。对于很长的语句这样很合适。\n\n- 记号间的空格可以更容易区分记号。比如运算符两边加空格方便阅读。\n\n- 缩进有助于识别程序嵌套。\n\n- 空行可以把程序划分为逻辑单元。"
  },
  {
    "path": "notes/CProgramming/ch03 格式化输入输出.md",
    "content": "# 第三章 格式化输入输出\n\nscanf 函数和 printf 函数是C语言使用最频繁的两个函数，它们用来支持格式化的读和写。\n\n---\n\n## printf 函数\n\nprintf 函数被设计用来显示格式串（ format string ）的内容，并且在字符串指定位置插入可能的值。\n\n```c\nprintf(格式串, 表达式1, 表达式2, ...);\n```\n\n格式串包含普通字符和转换说明（ conversion specification ），其中转换说明以字符%开头。\n\n!!!warning\n\tC语言编译器不会检测格式串中转换说明的数量是否和输出项的数量相匹配。\n\n### 转换说明\n\n在通用的情况下，转换说明可以有%m.pX格式或%-m.pX格式，这里的m和p都是整型常量，X是字母。m和p都是可选项。\n\n在转换说明%10.2f中，m是10，p是2，X是f。\n\n**最小字段宽度（ minimum field width ）** m指定了要显示的最小字符数量。如果要打印的数值比m个字符少，那么值在字段内是右对齐的。如果要多，那么字段宽度会自动扩展为需要的尺寸。\n\n**精度（ precision ）** p的含义依赖于**转换说明符X（ conversion specifier ）**的选择。对数来说，最常用的转换说明符有：\n\n- d，表示十进制形式的整数。p说明可以显示的数字的最少个数（如果需要，就在数前加上额外的零）；如果忽略掉p，默认它的值为1。\n\n- e，表示指数形式的浮点数。p说明小数点后应该出现的数字的个数（默认为6）。如果p为0，则不显示小数点。\n\n- f，表示“定点十进制”形式的浮点数，没有指数。p的含义与在说明符e中的一员。\n\n- g，表示指数形式或者定点十进制形式的浮点数，形式的选择根据数的大小决定。p可以说明显示的有效数字的最大数量。与转换说明符f不同，g的转换将不显示尾随零。\n\n### 转义序列\n\n我们经常把在格式串中用的代码`\\n`称为转义序列（ escape sequence ）。转义序列使字符串包含一些特殊字符而又不会使编译器引发问题。\n\n详细的说明：https://zh.cppreference.com/w/cpp/language/escape\n\n## scanf 函数\n\nscanf 函数也根据特定的格式读取输入， scanf 函数转换说明符的用法和 printf 函数转换说明符的用法本质上是一样的。\n\nscanf 函数有一些不易察觉的陷阱。使用 scanf 时，程序员必须检查转换说明的数量是否与输入变量的数量相匹配，并且检查每个转换是否适合相对应的变量。另一个陷阱涉及符号&，通常把符号&放在 scanf 函数调用的每个变量的前面。\n\n调用 scanf 函数是读取数据的一种有效但不理想的方法。许多专业C程序员避免用 scanf 函数，而是采用字符格式读取所有数据，然后再把它们转换成数值形式。\n\n### scanf 函数的工作方法\n\nscanf 函数本质上是一种“模式匹配”函数，也就是试图把输入的字符组与转换说明匹配成组。\n\nscanf 调用时，从左边开始处理字符串中的信息。对于格式串中的每一个转换说明， scanf 函数努力从输入的数据中定位适当类型的项，并且跳过必要的空格。然后， scanf 函数读入数据项，并且在遇到不可能属于此项的字符时停止。\n\n在寻找数的起始位置时， scanf 函数会忽略空白（ white-space ）字符（空格符、横向和纵向制表符、换页符、换行符）。\n\n### 格式串中的普通字符\n\n处理格式串中普通字符时， scanf 函数采取的动作依赖于这个字符是否为空白字符。\n\n- 空白字符。当在格式串中遇到一个或多个连续的空白字符时， scanf 函数从输入中重复读空白字符直到遇到一个非空白字符（把该字符“放回原处”）为止。\n\n- 其他字符。当在格式串中遇到一个非空白字符时， scanf 函数将把它与下一个输入字符进行比较。如果两个字符相匹配，那么 scanf 函数会放弃输入字符而继续处理格式串。如果两个字符不匹配，那么 scanf 函数会把不匹配的字符放回输入中，然后异常退出。"
  },
  {
    "path": "notes/CProgramming/ch04 表达式.md",
    "content": "# 第四章 表达式\n\n表达式是显示如何计算值的公式。最简单的表达式是变量和常量。变量表示程序运行时计算出的值；常量表示不变的值。\n\n运算符是构建表达式的基本工具。C语言提供了基本的运算符：\n\n- 算术运算符。\n- 关系运算符。\n- 逻辑运算符。\n\n---\n\n## 算术运算符\n\n算术运算符有：\n\n|一元运算符|二元运算符|\n|:-:|:-:|\n|+ -|+ - * / %|\n\n二元运算符要求有两个操作数，而一元运算符只要有一个操作数。\n\n一元运算符+无任何操作，它主要是为了强调某数值常量是正的。\n\n%被称之为 mod （求模）或 rem （取余）。 i % j 的数值是i除以j后的余数。\n\n除了%，二元运算符既允许操作数是整数也允许操作数是浮点数，或者允许两者的混合。当把 int 型操作数和 float 型操作数混合在一起时，运算结果是 float 型的。\n\n运算符/和%需要特别注意：\n\n- /可能产生意外的结果。当两个操作数都是整数时，运算符/通过丢掉分数部分的方法截取结果，因此1/2的结果是0。\n- %要求整数操作数；如果两个操作数中有一个不是整数，那么程序将无法通过编译。\n- 当/和%用于负数时，其结果与具体实现有关。如果操作数中有一个为负数，那么除法的结果既可以向上取整也可以向下取整。\n\n!!!note \"由实现定义\"\n\t术语由实现定义（ implementation-defined ）出现频率很高，意思是指软件在特定的平台上编译、链接和执行。根据实现的不同，程序的行为可能会稍有差异。\n\tC语言的目的之一是达到高效率，这经常意味着要与硬件行为相匹配。当-9除以7时，一些机器可能产生的结果是-1，而另一些机器的结果为-2，C标准简单地反映了这一现实。\n\t最好避免编写与实现定义的行为相关的程序。\n\n### 运算符的优先级和结合性\n\nC语言允许在所有表达式中用圆括号进行分组。但如果不使用圆括号，就采用运算符优先级（ operator precedence ）的规则来解决问题。算术运算符有下列相对优先级：\n\n- 最高优先级：+ -（一元运算符）\n- 中级优先级：* / %\n- 最低优先级：+ -（二元运算符）\n\n例如：\n\n```c\ni + j * k;    // 等价于 i + (j * k)\n```\n\n当一个表达式包含两个以上相同优先级的运算符时，单独的运算符优先级的规则是不够的。这种情况下，运算符的结合性（ associativity ）开始发挥作用。如果运算符是从左向右结合的，那么称这种运算符是左结合的（ left associative ）。二元算术运算符都是左结合的，所以：\n\n```c\ni - j - k;    // 等价于 (i - j) - k\n```\n\n如果运算符是从右向左结合的，那么称为右结合的（ right associative ）。一元运算符都是右结合的。\n\n## 赋值运算符\n\n一旦计算出表达式的值就常常需要把这个值存储在变量中，以便后面使用。C语言的=运算符（assignment）可以用于此目的。\n\n### 简单赋值\n\n表达式v = e的赋值效果是求出表达式e的值，并把此值复制给v。e可以是常量、变量或较为复杂的表达式：\n\n```c\ni = 5;\nj = i;\nk = 10 * i + j;\n```\n\n如果v和e的类型不同，那么赋值运算发生时会把e的值转化为v的类型：\n\n```c\nint i;\ni = 72.99; /* i is now 72 */\n```\n\n赋值操作产生结果，赋值表达式v=e的值就是赋值运算后v的值。因此，表达式i = 72.99的值是72。\n\n!!!note \"副作用\"\n\t大多数C语言运算符不会改变操作数的值，但是也有一些会改变。由于这类运算符所做的不再仅仅是计算出值，所以称它们有副作用（ side effect ）。简单的赋值运算符就是一个有副作用的运算符，它改变了运算符左边的操作数。表达式i=0产生的结果为0，作为副作用，把0赋值给i。\n\n运算符=是右结合的。所以：\n\n```c\ni = j = k = 0;\ni = (j = (k = 0)); // 等价\n```\n\n### 左值\n\n大多数C语言运算符允许它们的操作数是变量、常量或者包含其他运算符的表达式。然而，赋值运算符要求它左边的操作数必须是左值（ lvalue ）。左值表示存储在计算机内存中的对象，而不是常量或计算结果。变量是左值，而诸如10或2*i这样的表达式则不是左值。\n\n### 复合赋值\n\n利用变量原有值计算出新值并重新赋值给这个变量在C语言程序中是非常普遍的。例如：\n\n```c\ni = i + 2;\n```\n\nC语言的复合赋值运算符（ compound assignment operator ）允许缩短这种语句和其他类似的语句。\n\n```c\ni += 2;\n```\n\n+=运算符把右侧操作数的值加上左侧的变量，并把结果赋值给左侧的变量。还有另外的9种复合赋值运算符，包括：\n\n-= *= /= %=\n\n## 自增运算符和自减运算符\n\n++表示操作数加1，--表示操作数减1。++和--既可以作为前缀（ prefix ）运算符，也可以作为后缀（ postfix ）运算符使用。\n\n++和--也有副作用，它们会改变操作数的值。计算表达式++i的结果是i+1，副作用是自增i。计算表达式i++的结果是i，副作用是自增i。\n\n这个自增操作一定会在下一条语句执行前完成。\n\n## 表达式求值\n\n上述总结的运算符在下表列出了其优先级、结合性。更多讨论见书本p39。\n\n|优先级|类型名称|符号|结合性|\n|-|-|-|-|\n|1|后缀自增、自减|++ --|左结合|\n|2|前缀自增、自减，一元正负|++ -- + -|右结合|\n|3|乘法类|* / %|左结合|\n|4|加法类|+ -|左结合|\n|5|赋值|= *= /= %= += -=|右结合|\n\n**子表达式的求值顺序**\n\nC语言没有定义子表达式的求值顺序（除了含有逻辑与运算符及逻辑或运算符、条件运算符以及逗号运算符的子表达式）。"
  },
  {
    "path": "notes/CProgramming/ch05 选择语句.md",
    "content": "# 第五章 选择语句\n\n根据语句执行过程中顺序所产生的影响方式，C语言的其他语句大多属于以下三类：\n\n- 选择语句（ selection statement ）。 if 语句和 switch 语句允许程序在一组可选项中选择一条特定的执行路径。\n\n- 循环语句（ iteration statement ）。 while 语句、 do 语句和 for 语句支持重复操作。\n\n- 跳转语句（ jump statement ）。 break 语句、 continue 语句和 goto 语句引起无条件地跳转到程序中的某个位置。（ return 语句也属于此类）\n\nC语言还有其他两类语句，一类是由几条语句组合成一条语句的复合语句，一类是不执行任何操作的空语句。\n\n---\n\n## 逻辑表达式\n\n包括 if 语句在内的某些C语句都必须测试表达式的值是“真”还是“假”。诸如`i<j`这样的比较运算会产生整数：0（假）或1（真）。\n\n### 关系运算符\n\nC语言的关系运算符（ relational operator ）用在C语言中时产生的结果是0（假）或1（真）。\n\n|符号|含义|\n|:-:|:-:|\n|<|小于|\n|>|大于|\n|<=|小于或等于|\n|>=|大于或等于|\n\n关系运算符的优先级低于算术运算符，关系运算符都是左结合的。表达式`i+j<k-1`意味着`(i+j)<(k-1)`。\n\n### 判等运算符\n\n|符号|含义|\n|:-:|:-:|\n|==|等于|\n|!=|不等于|\n\n判定运算符是左结合的，也是产生0或1作为结果。然而，判等运算符的优先级低于关系运算符。例如表达式`i<j == j<k`等价于表达式`(i<j) == (j<k)`。\n\n### 逻辑运算符\n\n|符号|含义|\n|:-:|:-:|\n|!|逻辑非|\n|&&|逻辑与|\n|\\|\\||逻辑或|\n\n逻辑运算符所产生的结果是0或1。逻辑运算符将任何非零值操作数作为真值来处理，同时将任何零值操作作为假值来处理。\n\n运算符&&和||都对操作数进行“短路”计算。\n\n运算符!的优先级和一元正号、负号的优先级相同。运算符&&和||的优先级低于关系运算符和判等运算符。运算符!是右结合的，而运算符&&和||是左结合的。\n\n## if 语句\n\nif 语句允许程序通过测试表达式的值从两种选项中选择一种。 if 语句的最简单的格式如下：\n\nif (表达式) 语句\n\n执行 if 语句时，先计算圆括号内的表达式的值。如果表达式的值非零，那么接着执行括号后面的语句，C语言把非零值解释为真值。\n\n### 复合语句\n\n如果想用 if 语句处理两条或更多语句，该怎么办呢？可以引入复合语句（ compound statement ）。复合语句有如下格式：\n\n{ 多条语句 }\n\n### else 子句\n\nif 语句可以有 else 子句：\n\nif (表达式) 语句 else 语句\n\n如果在圆括号内的表达式的值为0，那么就执行 else 后边的语句。\n\n### 条件表达式\n\nC语言提供了一种特殊的运算符，这种运算符允许表达式依据条件的值产生两个值中的一个。\n\n条件运算符（ conditional operator ）由符号?和符号:组成，两个符号必须按如下格式一起使用：\n\n表达式1 ? 表达式2 : 表达式3\n\n条件运算符是C运算符中唯一一个要求3个操作数的运算符。因此，经常把它称为三元运算符。\n\n条件表达式的求值步骤是：首先计算出表达式1的值，如果此值不为零，那么计算表达式2的值，并且计算出来的值就是整个条件表达式的值；如果表达式1的值为零，那么计算表达式3的值，并且此值就是整个条件表达式的值。\n\n### 布尔值\n\n因为许多程序需要变量能存储假值和真值，所以C语言缺少适当的布尔类型可能会很麻烦。可以使用 int 型变量来模拟布尔类型：\n\n```c\nint flag;\nflag = 0;\nflag = 1;\n```\n\n为了使程序更加便于理解，一个好的方法是用类似 TRUE 和 FALSE 这样的名字定义宏：\n\n```c\n#define TRUE 1\n#define FALSE 0\n\nflag = FALSE;\nflag = TRUE;\n```\n\n为了更进一步实现这个想法，甚至可以定义用作类型的宏：\n\n```c\n#define BOOL int\n\nBOOL flag;\n```\n\n## switch 语句\n\nC语言提供了 switch 语句作为级联式 if 语句的替换：\n\n```c\nswitch (grade) {\n\tcase 4: printf(\"Excellent\"); break;\n\n\tcase 3: printf(\"Good\"); break;\n\n\tcase 2: printf(\"Average\"); break;\n\n\tcase 1: printf(\"Poor\"); break;\n\n\tcase 0: printf(\"Failing\"); break;\n\n\tdefault: printf(\"Illegal grade\"); break;\n}\n```\n\nswitch 语句的最常用的格式如下：\n\n```c\nswitch (表达式) {\n\tcase 常量表达式: 多条语句\n\t...\n\tcase 常量表达式: 多条语句\n\n\tdefault: 多条语句\n}\n```\n\nswitch 语句的组成部分：\n\n- 控制表达式。 switch 后面必须跟着右圆括号括起来的整型表达式。C语言把字符当成整数来处理，因此可以在 switch 语句中对字符进行判定。但是，不能用浮点数和字符串。\n\n- 情况标号。常量表达式（ constant expression ）更像是普通的表达式，5是常量表达式，5 + 10也是，而n + 10不是（除非n是表示常量的宏）。\n\n- 语句。每个情况标号的后边可以跟任意数量的语句，不需要用大括号括起来。每组语句的最后通常是 break 语句。"
  },
  {
    "path": "notes/CProgramming/ch06 循环.md",
    "content": "# 第六章 循环\n\n循环（ loop ）是重复执行某些语句（循环体）的一种语句。在C语言中，每个循环都有一个控制表达式（ controlling expression ）。每次执行循环体时都要对控制表达式进行计算。如果表达式为真，也就是值不为零，那么继续执行循环。\n\nC语言提供了3种循环语句： while 语句、 do 语句和 for 语句。\n\n---\n\n## while 语句\n\nwhile 语句的格式如下所示：\n\nwhile (表达式) 语句\n\n执行 while 语句时，首先计算控制表达式的值。如果值不为零（即真值），那么执行循环体，接着再次判定表达式。\n\n## do 语句\n\ndo 语句的格式如下所示：\n\ndo 语句 while (表达式);\n\n和处理 while 语句一样， do 语句的循环体也必须是一条语句（当然可以用复合语句）。\n\n执行 do 语句时，先执行循环体，再计算控制表达式的值。如果表达式的值是非零的，那么再次执行循环体。\n\n## for 语句\n\nfor 语句的格式如下所示：\n\nfor (表达式1; 表达式2; 表达式3) 语句\n\n循环开始执行前，表达式1是初始化步骤，并且只执行一次，表达式2用来控制循环的终止（只要表达式2不为零，那么将继续执行循环），而表达式3是在每次循环的最后被执行的一个操作。\n\n### 逗号运算符\n\n有些时候，我们可能喜欢编写有两个（或更多个）初始表达式的 for 语句，或者希望在每次循环时一次对几个变量进行自增操作。使用逗号表达式（ comma expression ）作为 for 语句中的第一个或第三个表达式可以实现这些想法。\n\n逗号表达式的格式如下所示：\n\n表达式1, 表达式2\n\n逗号表达式的计算要通过两步来实现：第一步，计算表达式1并且扔掉计算出的值。第二步，计算表达式2，把这个值作为整个表达式的值。计算表达式1始终会有副作用；如果没有，那么表达式1就没有了存在的意义。\n\n逗号运算符的优先级低于所有其他运算符。\n\n## 退出循环\n\n### break 语句\n\nbreak 语句还可以用于跳出 while、 do 或 for 循环。\n\n### continue 语句\n\ncontinue 语句无法跳出循环，它把程序控制正好转移到循环体结束之前的一点。 break 语句可以用于 switch 语句，而 continue 语句只能用于循环。\n\n### goto 语句\n\ngoto 语句可以跳转到函数中任何有标号的语句处。\n\n标号只是放置在语句开始处的标识符：\n\n标识符: 语句\n\ngoto 语句自身的格式如下：\n\ngoto 标识符;\n\n执行 goto 语句可以把控制转移到标号后的语句上，而且这些语句必须和 goto 语句本身在同一个函数中。\n\n## 空语句\n\n语句可以为空，也就是除了末尾处的分号以外什么符号也没有。"
  },
  {
    "path": "notes/CProgramming/ch07 基本类型.md",
    "content": "# 第七章 基本类型\n\n---\n\n## 整型\n\nC语言支持两种根本不同的数值类型：整型和浮点型。整型的值都是数，而浮点型可能还有小数部分。整型还分为有符号的和无符号的。\n\n!!!note\n\t在有符号数中，如果数为正数或零，那么最左边的位（符号位）为0，如果是负数，符号位则为1。默认情况下，C语言中的整型变量都是有符号的。\n\nC语言提供了不同尺寸的整型， int 是正常的尺寸。可以指明变量是 long 型或 short 型， signed 型或 unsigned 型。可以有这些类型组合：\n\nshort int, unsigned short int, int, unsigned int, long int, unsigned long int\n\n可以把 int 省略掉，即 short int 可以写成 short 。\n\n不同类型的整型表示的取值范围根据机器的不同而不同。\n\n### 整型常量\n\n这里说的常量表示在程序中以文本形式显示的数。C语言允许用十进制、八进制和十六进制形式书写整型常量。\n\n十进制常量包含数字`0~9`，但是一定不能以零开头：15 255 32767\n\n八进制常量只包含数字`0~7`，但是必须以零开头：017 0377 07777\n\n十六进制常量包含数字0~9和字母a~f，而且总是以0x开头：0xf 0xff 0xffff\n\n当程序中出现整型常量时，如果它属于 int 类型的取值范围，那么编译器会把此常量作为普通整数来处理，否则作为长整型处理。为了迫使编译器把常量作为长整型来处理，只需在后边加上一个字母L：15L\n\n为了指明是无符号常量，可以在常量后边加上字母U：15U\n\n还可以把UL写在一起：0xffffUL\n\n### 读写整数\n\n读写无符号、短的或长的整数需要一些新的转换说明符。\n\n- 读写无符号整数时，使用字母u、o或x代替转换说明符d。u代表十进制、o八进制、x十六进制。\n- 读写短整数时，在d、o、u或x前面加字母h。\n- 读写长整型时，在d、o、u或x前面加字母l。\n\n## 浮点型\n\n有时候需要变量存储带有小数的数，或者能存储极大数或极小数。这类数可以用浮点格式进行存储（因小数点是浮动的而得名）。C语言提供3种浮点型，它们对应不同的浮点格式：\n\n- float ：单精度浮点数\n- double ：双精度浮点数\n- long double ：扩展双精度浮点数\n\n### 浮点常量\n\n浮点常量有许多书写方式：57.0 57. 57E0 5.7E1\n\n用指数表示的是10的幂。\n\n默认情况下，浮点常量都以 double 的形式存储。为了表明只需要单精度，可以在常量的末尾处加上字母f，如 57.0f 。\n\n### 读写浮点数\n\n转换说明符 %e %f 和 %g 用于读写单精度浮点数，当读取 double 时，需要用 %lf ，而写 double 时，不需要加l。\n\n## 字符型\n\n给 char 类型的变量赋值：\n\n```c\nchar ch;\nch = 'a';\n```\n\nC语言会按小整数的方式处理字符。\n\n### 转义序列\n\n一些特殊的符号无法书写，比如换行符，这时候需要用C语言提供的特殊符号转义序列（ escape sequence ）。\n\n转义序列分成两种：字符转义序列和数字转义序列。\n\n### 字符处理函数\n\n可以使用 toupper 库函数把小写字母转成大写字母：\n\n```c\nch = toupper(ch);\n```\n\n被调用时，函数检查参数是否是小写字母，如果是，那么将它转换成大写字母，否则，函数返回参数的值。\n\n### 读写字符\n\n转换说明符 %c 允许 scanf 函数和 printf 函数对单独一个字符进行读写操作。\n\n```c\nchar c;\nscanf(\"%c\", &c);\nprintf(\"%c\", c);\n```\n\n在读入字符前， scanf 不会跳过空白字符。为了强制 scanf 函数在读入字符前跳过空白字符，需要在格式串转换说明符 %c 前面加上一个空格。\n\n```c\nchar c;\nscanf(\" %c\", &c);\n```\n\nC语言还提供了读写单独字符的其他方法。可以使用 getchar 和 putchar 函数来替代调用 scanf 函数和 printf 函数。每次调用 getchar 函数时，它会读入一个字符，并返回这个字符。\n\n```c\nch = getchar();\n```\n\nputchar 函数用来写单独一个字符：\n\n```c\nputchar(ch);\n```\n\ngetchar 和 putchar 会比较快，原因是它们的实现比较简单，并且通常用宏来实现。\n\n## sizeof 运算符\n\n运算符 sizeof 允许程序确定用来存储指定类型值所需的空间的大小。\n\nsizeof(类型名)\n\n上述表达式的值是无符号整数，这个整数表示用来存储属于类型名的值所需的字节数。\n\n表达式 sizeof(char) 的值始终为1。\n\n通常情况下， sizeof 运算符也可以应用于常量、变量和表达式。\n\n既然 sizeof 返回的是无符号的整型，所以最安全的做法是把 sizeof 表达式转换成 unsigned long 型。然后用转换说明符 %lu 进行。\n\n!!!note\n\t或者用%z来输出sizeof的结果。\n\n## 类型转换\n\n为了让计算机执行算术运算，通常要求操作数有相同的大小（即位的数量相同），并且要求存储的方式也相同。\n\nC语言允许表达式中混合使用基本数据类型，这种情况下编译器可能需要生成一些指令将某些操作数转换成不同类型，使得硬件可以对表达式进行计算。这类转换是隐式转换（ implicit conversion ）。C语言还允许程序员通过强制运算符执行显式转换（ explicit conversion ）。\n\n当发生下列情况时会进行隐式转换：\n\n- 当算术表达式或逻辑表达式中操作数类型不同时。\n- 当赋值运算符右侧表达式的类型和左侧变量的类型不匹配时。\n- 当函数调用中使用的参数类型与其对应的参数类型不匹配时。\n- 当 return 语句中表达式的类型和函数返回值的类型不匹配时。\n\n### 常用的算术转换\n\n常用的算术转换包括算术运算符、关系运算符和判等运算符。\n\n为了统一操作数的类型，通常把相对较狭小的操作数转换成另一个操作数的类型来实现（这就是所谓的提升）。最常用的是整型提升（integral promotion），它把字符或短整数转换成 int 。\n\n两种转换规则：\n\n- 任意操作数是浮点型的情况： float -> double -> long double\n- 两个操作数都不是浮点类型： int -> unsigned int -> long int -> unsigned long int\n\n### 赋值中的转换\n\nC语言遵循一个简单的规则：把赋值运算符右侧的表达式转换成左边变量的类型。把浮点数赋值给整型变量会丢掉小数点后的部分。如果取值在变量的类型范围之外，那么把值赋值给一个较小的类型变量将会得到无意义的结果（甚至更糟）。"
  },
  {
    "path": "notes/CProgramming/ch08 数组.md",
    "content": "# 第八章 数组\n\n数组是一种聚合（aggregate）变量，可以存储数值的集合。C语言中一共有两种聚合类型：数组和结构。\n\n---\n\n## 一维数组\n\n数组是含有多个数据值的数据结构，并且每个数据值具有相同的数据类型。这些数据值被称为元素。\n\n一维数组中的元素一个接一个地编排在单独一行。\n\n声明一个一维数组：\n\n```c\n#define N 10\nint a[N];\n```\n\n### 数组下标\n\n长度为n的数组元素的索引范围是0到n-1。\n\n使用数组元素：\n\n```c\na[0] = 1;\n```\n\n!!!warning\n\tC语言不要求检查下标的范围，当下标超出范围时，程序可能执行不可预知的行为。\n\n### 数组初始化\n\n数组可以在声明时获得一个初始值。\n\n数组初始化式（ array initializer ）：\n\n```c\nint a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n\n/* initial value of a is {1, 2, 3, 4, 5, 6, 0, 0, 0, 0} */\nint a[10] = {1, 2, 3, 4, 5, 6};\n\n/* initial value of a is {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} */\nint a[10] = {0};\n\n/* 可以忽略数组长度，编译器自行确认 */\nint a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};\n```\n\n### 对数组使用 sizeof 运算符\n\n运算符 sizeof 可以确定数组的大小（字节数）。\n\n利用 sizeof 来计算数组元素的大小：\n\n```c\n#define SIZE sizeof(a) / sizeof(a[0])\n```\n\n## 多维数组\n\n数组可以有任意维数。\n\n声明一个二维数组（或称为矩阵）：\n\n```c\nint m[5][9];\n```\n\n数组m有5行9列。\n\n为了在i行j列中存取数组m的元素，需要写成`m[i][j]`的形式。\n\nC语言按照行主序存储数组，也就是从第0行开始，接着第1行，如此下去。\n\n![](./images/二维数组存储.jpg)\n\n### 多维数组初始化\n\n通过嵌套一维数组的方法可以产生二维数组的初始化式：\n\n```\nint a[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};\n```\n\n## 常量数组\n\n在数组定义前加const使得数组变成一个常量数组，表示不能修改数组里面的元素的值。但这样的数组必须在程序运行前就定义好数组内容，一般用于字符串数组。\n\n```c\nconst int months[] = \n{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };\n```"
  },
  {
    "path": "notes/CProgramming/ch09 函数.md",
    "content": "# 第九章 函数\n\n函数是C语言中的构建块，本质上就是一个由语句组成的小程序。有了函数，程序就可以被划分成许多小块。\n\n---\n\n## 函数的定义和调用\n\n由一个案例说明函数中的一些基本概念：\n\n```\ndouble average(double a, double b)\n{\n    return (a + b) / 2;\n}\n```\n\n**返回类型**：即函数开始处放置的double，表示每次调用该函数返回的类型。\n\n**形式参数（parameter）**：标识符a和b\n\n**函数体**：花括号里的内容。\n\n**实际参数（argument）**：调用函数时，传递给形式参数的表达式。\n\n### 函数定义\n\n函数定义的一般格式：\n\n```\n返回类型 函数名(形式参数)\n{\n    声明\n    语句\n}\n```\n\n如果返回类型很长，可以把它单独放到一行。\n\nC89标准中，声明必须放在语句之前。\n\n### 函数调用\n\n这样调用函数：\n\n```\naverage(x, y);\n```\n\n注意，圆括号不能省略，否则无法正确调用。\n\n返回类型若非void，则会返回一个临时值，这个值可以保存到变量里，也可以丢弃。\n\n## 实际参数\n\n形式参数（parameter）出现在函数的定义中，表示函数调用时要提供的值。\n\n实际参数（argument）是出现在函数调用中的表达式。\n\n实际参数是通过**值传递**的。形参是实参的副本。\n\n### 实际参数的转换\n\n形参和实参的类型不一致时，会发生转换。应该先声明函数原型，然后再执行调用。\n\n这种转换属于隐式转换。实际参数将会转换成形式参数的类型。\n\n### 数组型实际参数\n\n数组名可以作为函数的参数，但是函数无法得知数组的长度，只能传递第二个参数以表明长度。\n\n通过此数组名就可以访问数组元素了。\n\n如果形式参数是多维数组，那么必须指明列的长度：\n\n```\nvoid sum(arr[][LEN], int n);\n```\n\n但这种用法比较少见。\n\n## return语句\n\n非void的函数必须用return语句来指定要返回的值：\n\n```\nreturn 表达式;\n```\n\n如果表达式的值得类型与返回值的类型不一致，那么会把表达式的值转换成返回值的类型。\n\n## 程序终止\n\n在main函数中执行return可以终止程序。\n\nmain函数的返回值是状态码，用于提供给操作系统，以表明程序的执行结果。正常结束返回0，异常结束返回非0值。\n\n**exit函数**\n\n另一种终止程序的办法是使用exit函数。此函数属于`<stdlib.h>`头。\n\n传递给exit的参数就是程序结束的状态码：\n\n```\nexit(0); // 正常终止\nexit(EXIT_SUCCESS); // 正常终止，此值为0\nexit(EXIT_FAILURE); // 异常终止，此值为1\n```\n\n## 递归\n\n如果函数调用它自己，那么此函数就是递归的（recursive）。\n\n为了防止无限递归，一定要有一个终止递归的条件。"
  },
  {
    "path": "notes/CProgramming/ch10 程序结构.md",
    "content": "# 第十章 程序结构\n\n---\n\n## 局部变量\n\n在函数体内或程序块内声明的变量叫局部变量。\n\n局部变量有如下性质：\n\n- 自动存储期限。局部变量的存储单元在函数被调用时分配的，在函数返回时收回。\n\n- 块作用域。作用域是可以引用该变量的代码文本部分。局部变量的作用域在程序块中，外部不可访问。\n\n### 静态局部变量\n\n使用static声明局部变量使其成为静态局部变量，它具有静态存储期限，而不再是自动存储期限。\n\n静态变量拥有永久的存储单元，在整个程序的执行期间都会保留。\n\n静态局部变量的作用域仍是块作用域。\n\n### 形式参数\n\n形式参数拥有和局部变量一样的性质，即自动存储期限和块作用域。\n\n其区别在于它是被实参初始化的。\n\n## 外部变量\n\n参数是给函数传递信息的一种方法。另一种方法就是使用外部变量（external variable）。\n\n外部变量声明于函数体外，也叫全局变量。它有如下性质：\n\n- 静态存储期限。如同在函数体内声明的static局部变量，静态存储期限的变量都会永久保留。\n\n- 文件作用域。外部变量的作用域为其声明处到文件末尾。\n\n### 外部变量的利弊\n\n使用外部变量容易引发的问题：\n\n- 在维护期间，如果改变了外部变量，那么就要检查引用了该外部变量的所有函数，以确认此变化对这些函数的影响\n\n- 如果外部变量的值错误，那么比较难定位是哪个函数赋予它错误的值\n\n- 使用了外部变量的函数难以复用，因为此函数不再独立，而是依赖于此外部变量\n\n要给外部变量起一个健全的名字，这样才不容易和其他变量混淆。\n\n## 程序块\n\n程序块就是这样的代码结构：\n\n```\n{ \n    多条声明\n    多条语句\n}\n```\n\n在程序块中声明的变量具有自动存储期限，出块时收回存储单元，其作用域在块内。\n\n函数体就是一个程序块。\n\n## 作用域\n\n当程序块内的声明命名了一个标识符时，如果此标识符已经可见（被其它地方声明并且可引用），那么新的声明就会”隐藏“旧的声明。\n\n## 构建C程序\n\n一个可能的编排程序的顺序：\n\n- \\#include指令\n\n- \\#define指令\n\n- 类型定义\n\n- 外部变量的声明\n\n- 除main函数之外的函数的原型\n\n- main函数的定义\n\n- 其它函数的定义"
  },
  {
    "path": "notes/CProgramming/ch11 指针.md",
    "content": "# 第十一章 指针\n\n---\n\n## 指针变量\n\n现代计算机把**内存分隔为字节（byte）**。每个字节都有唯一的地址（address）。\n\n变量占有一个或多个字节的内存，把第一个字节的地址称为变量的地址。\n\n地址是一个整数，用指针类型（pointer）的变量来存储。\n\n**指针变量的声明**\n\n在变量名前加星号，来声明一个指针变量。如：\n\n```\nint *p;\n```\n\n此声明说明p是指向int类型的对象。\n\n## 取地址运算符和间接寻址运算符\n\n### 取地址运算符\n\n得到一个变量的地址在它前面加'&'（取地址）。如：\n\n```\nint i;\nint *p = &i;\n```\n\n### 间接寻址运算符\n\n获取指针变量指向的存储空间首地址在它前面加`*`（间接寻址）。如：\n\n```\n*p = 10;\n```\n\n这个操作（\\*p）得到的就是变量的**别名**。该操作会修改变量的值。\n\n## 指针赋值\n\n只要是相同类型的指针，就可以相互赋值。这样它们就指向了相同的对象。\n\n## 指针作为参数\n\n指针可以做为函数的参数或返回值。\n\n这些情况，可能会用到指针类型的形参：\n\n0. 需要得到多个结果，故而用指针传出去\n\n0. 传入的对象太大，没有必要执行拷贝操作\n\n一般只有这些情况，返回一个指针类型才是安全的：\n\n0. 返回的是指针类型的参数\n\n0. 返回的是一个全局变量地址\n\n0. 返回的是一个static变量的地址"
  },
  {
    "path": "notes/CProgramming/ch12 指针和数组.md",
    "content": "# 第十二章 指针和数组\n\n---\n\n## 指针和数组\n\n当指针指向数组元素时，它可以操作数组元素。此时，可以对指针进行算术运算（加、减、比较）。\n\n数组名即是指向数组中第一个元素的指针。\n\n## 指针的算术运算\n\n指针指向了数组元素，之后对这个指针做算术运算就是合法的操作。但不要越界。\n\n- 指针加、减整数，代表移动元素指向\n\n- 指针相减，代表指针指向元素之间的距离\n\n- 指针比较，比较的是指针指向元素谁前谁后\n\n!!!warning\n\t不能相加两个指针。\n\n## 指针用于数组处理\n\n指针可以操作数组中的元素，比如遍历数组：\n\n```c\nint *p = 0;\nfor (p = &a[0]; p < &a[N]; ++p)\n{\n    // ...\n}\n```\n\n这里N是数组a的长度，虽然`a[N]`不存在，但对它取地址是合法且安全的操作。\n\n**使用\\*和++组合**\n\n如，`*p++`\n\n++操作的优先级高于\\*，所以这样的组合操作，会先操作指针p，然后获取其指向的内容。\n\n后置++会先返回p，然后对p递增，可以用这个操作遍历数组：\n\n```c\nint *p = &a[0];\nwhile (p < &a[LEN])\n{\n    int n = *p++;\n    // ...\n}\n```\n\n## 用数组名作为指针\n\n数组名即第一个元素的地址。即a与&a[0]等价。\n\n数组名是一个指针常量，不能改变其值，应该把它赋值给一个指针变量。\n\n因此可以这样遍历数组：\n\n```c\nint *p = 0;\nfor (p = a; p < a + LEN; ++p)\n{\n    // ...\n}\n```\n\n## 指针和多维数组\n\n二维数组在内存中实际上是一维的连续存储的。其首元素如果这样写：\n\n- `&a[0]`，代表第一行的首地址\n\n- `&a[0][0]`，代表第一个元素的首地址\n\n这俩地址的值是一样的，但意义不同，因为其指向元素的类型不一样。前者是`int*`，后者是`int`。\n\n### 多维数组名作为指针\n\n`int a[n][m]`这样的二维数组，其数组名a代表的是`a[0]`的地址，`a[i]`得到的是第i行的地址。\n\n这样的指针类型是：\n\n```c\nint (*p)[m] = a;\n```\n\n或\n```c\ntypedef int (*Line)[m];\nLine p = a;\n```\n\n这里定义了一个指向数组的指针，这个数组的元素有m个。"
  },
  {
    "path": "notes/CProgramming/ch13 字符串.md",
    "content": "# 第十三章 字符串\n\n---\n\n## 字符串字面量\n\n字符串字面量（ string literal ）是用一对双括号括起来的字符序列：\n\n```c\n\"Hello World\"\n```\n\n### 字符串字面量中的转义序列\n\n字符串字面量可以包含[转义序列](https://zh.cppreference.com/w/cpp/language/escape)：\n\n```c\n\"Hello\\tWorld\\n\"\n```\n\n### 延续字符串字面量\n\n字符串字面量可能太长，以至于无法放置在单独一行内可以把第一行用字符\\结尾，那么C语言就允许在下一行延续字符串字面量：\n\n```c\nprintf(\"put a disk in drive A, then \\\npress any key to continue\");\n```\n\n不只是字符串，字符\\可以用来分割任何长的符号。\n\n但\\有一个缺陷：字符串字面量必须从下一行的起始位置继续，从而破坏了程序的缩进结构。\n\n一个更好的办法是通过C语言的标准解决这个问题，也就是当两个或更多字符串字面量相连时（仅用空白字符分割），编译器必须把它们合并成单独一条字符串：\n\n```c\nprintf(\"put a disk in drive A, then\"\n\t   \"press any key to continue\\n\");\n```\n\n### 如何存储字符串字面量\n\n从本质上而言，C语言把字符串字面量作为字符数组来处理。当C语言编译器在程序中遇到长度为n的字符串字面量时，它会为字符串字面量分配长度为n+1的内存空间。+1存储的是额外的空字符，它用来标志字符串的末尾，用转义序列\\0来表示，其数值为0。\n\n例如，字符串字面量\"abc\"是一个有4个字符的字符数组：\n\n![字符串存储](./images/字符串存储.jpg)\n\n字符串字面量可以为空。字符串\"\"表示一个空串，仅有一个空字符。\n\n既然字符串字面量是作为数组来存储的，那么编译器会把它看作是char\\*类型的指针。\n\n### 字符串字面量的操作\n\n通常情况下可以在任何C语言允许使用char\\*指针的地方使用字符串字面量。例如：\n\n```c\nchar *p;\np = \"abc\"; /* 并非复制abc中的字符，而仅仅是使p指向字符串的第一个字符 */\n\nchar ch;\nch = \"abc\"[1]; /* C语言允许对指针添加下标，因此可以给字符串字面量添加下标 */\n```\n\n!!!warning\n\t对于一些编译器而言，改变字符串字面量的内容可能会导致运行异常。因此不推荐这么做。\n\n### 字符串字面量与字符常量\n\n只包含一个字符的字符串字面量不同于字符常量。\n\n字符串字面量\"a\"是指针，指向存放字符a以及后续空字符的内存单元。而字符常量'a'是一个整数。\n\n## 字符串变量\n\nC语言只要保证字符串是以空字符结尾的，任何一维的字符数组都可以用来存储字符串。\n\n定义字符串变量的惯用法：\n\n```c\n#define STR_LEN 80\nchar str[STR_LEN + 1]; /* 强调的事实是 str 可以存储最多80个字符 */\n```\n\n### 初始化字符串变量\n\n字符串变量可以在声明时进行初始化：\n\n```c\nchar str[8] = {0}; /* 空字符串 */\n\nchar date1[8] = \"June 14\";\n\nchar date2[8] = {'J', 'u', 'n', 'e', '1', '4', '\\0'};\n\nchar date3[] = \"June 14\"; /* 编译器自动计算长度 */\n```\n\n### 字符数组与字符指针\n\n比如：\n\n```c\nchar date1[] = \"June 14\"; /* 字符数组 */\nchar *date2 = \"June 14\"; /* 字符串字面量的指针 */\n```\n\n任何期望传递字符数组或字符指针的函数都将接受这两种声明的 date 作为参数。\n\n然而，需要注意，不能错误地认为上面两种 date 可以互换。两者之间有显著的差异：\n\n- 在声明为数组时，就像任意数组元素一样，可以修改存储在 date 中的字符。但不可以修改字符串字面量。\n\n- 在声明为数组时， date 是数组名。在声明为指针时， date 是变量，它可以指向其他字符串。\n\n## 字符串的读/写\n\n使用 printf 和 puts 函数来读写字符串。\n\n使用 scanf 和 gets 函数来读字符串。但 gets 函数不安全。推荐使用 fgets 函数。\n\n## 使用C语言的字符串库\n\nC语言的运算符无法操作字符串，C语言的库函数为字符串的操作提供了丰富的函数集。这些函数的原型驻留在 string.h 头文件中。比如：\n\nstrcpy 函数可以拷贝字符串。\n\nstrcat 函数可以追加字符串。\n\nstrcmp 函数可以比较字符串。\n\nstrlen 函数可以取得字符串的长度。\n\n## 字符串数组\n\n比如：\n\n```c\nchar *planets[] = {\n\t\"Mercury\",\n\t\"Venus\",\n\t\"Earth\",\n\t\"Mars\",\n\t\"Jupiter\",\n\t\"Saturn\",\n\t\"Uranus\",\n\t\"Neptune\",\n\t\"Pluto\",\n};\n```\n\nplanets 数组的每一个元素是一个字符串的指针。\n\n### 命令参数\n\n运行程序时，经常需要提供一些信息给程序，这是命令行参数（ command-line argument ）。必须把 main 函数定义为含有两个参数的函数：\n\n```c\nint main(int argc, char* argv[])\n{\n\n}\n```\n\nargc （参数计数）是命令行参数的数量（包括程序名本身，最少为1）。 argv (\"参数向量\")是指命令行参数的指针数组。 argv[0] 指向程序名， argv[n] 表示第n个参数。\n\nargv[argc] 始终是一个空指针。\n\n如果用户输入了下面的命令：\n\n```shell\nls -l remind.c\n```\n\n那么 argc 和 argv 是：\n\n- argc: 3\n\n- argv: {\"ls\", \"-l\", \"remind.c\", NULL}"
  },
  {
    "path": "notes/CProgramming/ch14 预处理器.md",
    "content": "# 第十四章 预处理器\n\n---\n\n## 预处理器的工作方式\n\n预处理器的行为是由指令控制的。这些指令是由#字符开头的一些命令。比如 #define 和 #include 。\n\n`#define`定义了一个宏——用来代表其他东西的一个名字。当宏在后面的程序中用到时，预处理器扩展它，将宏替换为它所定义的值。\n\n`#include`指令告诉预处理器打开一个特定的文件，将它的内容作为正在编译的文件的一部分。\n\n!!!note \"my note\"\n\t可以使用`gcc -E src.c`指令来查看预编译结果。\n\n## 预处理指令\n\n常见预处理指令包括：\n\n- 宏定义。`#define`定义一个宏，`#undef`删除一个宏。\n\n- 文件包含。即`#include`\n\n- 条件编译。`#if #ifdef #ifndef #elif #else #endif`\n\n指令的通用规则有：\n\n- 都以#开始。\n\n- 在指令的符号之间可以插入任意数量的空格或横向制表符。\n\n- 指令总是在第一个换行符处结束，除非明确地指明要继续，用\\字符换行。\n\n- 指令可以出现在程序的任何地方。\n\n- 注释可以与指令放在同一行。\n\n## 宏定义\n\n宏定义的作用范围从定义处开始到本文件末尾。\n\n### 简单宏定义\n\n简单宏定义的格式如：\n\n```c\n#define <宏名> [替换列表]\n```\n\n替换列表中可以有空格。甚至可以没有替换列表，此时宏替换后，就等于删除了这个宏一样。\n\n简单的宏定义一般用于：\n\n- 给字面量取一个别名\n- 辅助条件编译\n\n### 带参数的宏定义\n\n格式如：\n\n```c\n#define <宏名>(x1, x2, ..., xn) [替换列表]\n```\n\n注意点：\n\n- 宏名和参数列表的括号之间不能有空格，不然就是一个简单宏了\n\n- 参数列表可以为空，这样的宏使用起来就像一个函数\n\n- 参数只会替换记号，字符串内的同名单词并不会被替换\n\n带参数的宏一般用于：\n\n- 替代一些小的函数，这样程序的执行效率会高一些，并且函数可能更加通用，因为宏不必检查参数类型\n\n### \\#号和\\#\\#号\n\n宏替换列表中有两个特殊符号：\\#和\\#\\#，它们有如下的意义：\n\n- \\#号代表参数会被替换成一个字符串字面量，例如 :\n\n```c\n#define PRINT_INT(n) printf(#n \" = %d\\n\", n)\n```\n\n\\#n会被替换成\"n\"，相邻的字符串字面量可以连起来形成一个字符串字面量，所以PRINT_INT(a)的宏替换结果是：\n\n```c\nprintf(\"a = %d\\n\", a);\n```\n\n- \\#\\#代表将两边的记号连接在一起，成为一个记号，一个典型的例子：\n\n```c\n#define GENERIC_MAX(type)               \\\ntype type##_max(type x, type y)         \\\n{                                       \\\n        return x > y ? x : y;           \\\n};\n```\n\n这个宏定义定义了一个取最大值的函数，可以方便的为这个函数指定比较类型。\n\n值得注意的是，**#和##都在简单的宏替换后起作用**。\n\n### 宏定义中的圆括号\n\n如果宏定义的替换列表是一个表达式，那么为其增加圆括号是必不可少的工作。\n\n这是因为如果不加圆括号，在宏替换后，新的表达式可能会破坏替换列表表达式的运算优先级。\n\n**在替换列表表达式中使用圆括号有两条规则：**\n\n1. 用圆括号将替换列表括起来\n2. 用圆括号把每个宏参数括起来\n\n一个安全的宏的例子：\n\n```c\n#define SUM(x, y) ((x) + (y))\n```\n\n### 创建较长的宏\n\n一些废话：\n\n宏函数展开后，实际上只有一行。而编写的时候为了好看，可以用`\\`作为换行连接符号。\n\n另外，宏函数使用时看上去应该像普通函数一样：后面也要加分号。所以宏函数的替换列表的末尾应该没有分号。\n\n**直接上书上所给的解决方案：**\n\n```cpp\n#define ECHO(str)    \\\ndo                   \\\n{                    \\\n    gets(str);       \\\n    puts(str);       \\\n} while(0)\n\n// use\nECHO(str);\n```\n\n### 预定义宏\n\n常用预定义宏：\n\n|宏|说明|\n|-|-|\n|`__LINE__`|行号，十进制常数|\n|`__FILE__`|文件名|\n|`__DATE__`|文件编译时的日期|\n|`__TIME__`|文件编译时的时间|\n\n文件名，日期，时间的预定义宏展开后都是一个字符串变量。行号是一个整型变量。\n\n另外，不同的系统会定义不同的预定义宏，来标识其编译平台。如：\n\n- Linux下，`__unix`\n\n- Windows下，`_WIN32`\n\n这种预定义宏配合条件编译就可以做到跨平台编译代码。\n\n**特殊的预定义宏`__VA_ARGS__`**\n\nC99标准中，有一个特殊的预定义宏，它的作用是替换可变参数列表（...），但它要和##符号配合使用，此时##的意义不再是连接，而是：当可变参数列表为空的时候，去除`__VA_ARGS__`前面的逗号，从而避免编译错误。\n\n一个典型的例子：\n```c\n#define CONSOLE_DEBUG(fmt, ...)\\\n    printf(\"FILE: \"__FILE__\", LINE: %05d \"fmt\"\\n\", __LINE__, ##__VA_ARGS__);\n```\n\n`__FUNCTION__`\n\n这个宏代表了当前执行函数的函数名字符串。\n\n## 条件编译\n\n条件编译指令排除了不应该出现的文本。只有通过了条件编译的文本块才会被交给编译器编译。\n\n条件一般是一个普通的宏。\n\n书写格式如：\n\n```c\n#if MACRO\ncode\n#elif MACRO\ncode\n#else\ncode\n#endif\n```\n\n### defined 运算符\n\ndefined 运算符仅用于预处理器。\n\n```c\n#if defined(DEBUG)\n...\n#endif\n```\n\n如果标识符 DEBUG 是一个定义过的宏，则返回1，否则返回0。 defined 返回1意味着通过条件。\n\n指令说明：\n\n- \\#if, \\#elif可以判断这个宏的值，如果是0就不会通过条件编译\n\n- \\#ifdef, \\#ifndef可以判断这个宏是否被定义\n\n条件编译的作用一般是：\n\n- 为了支持跨平台编译\n\n- 排除一些调试代码\n\n## 其他指令\n\n**#error 指令**\n\n如果预处理器遇到一个`#error`指令，它会显示一个出错消息，大多数编译器会立即终止编译。\n\n```c\n#error You can not include this file\n```\n\n**#pragma指令**\n\n`#pragma`指令为要求编译器执行某些特殊操作提供了一种方法。\n\n使用#pragma pack预处理指令来设置字节对齐。具体用法如：\n\n```c\n#pragma pack(push)    // 保存现在的字节对齐状态\n#pragma pack(4)       // 设置4字节对齐\n// 这里定义的结构体最好以4字节对齐\n#pragma pack(pop)     // 恢复字节对齐状态\n```\n\n这里字节对齐的意思是，将结构体中最大内置类型的成员的长度与默认字节对齐数（比如是4）对比，如果谁小，那么就按谁来对齐。"
  },
  {
    "path": "notes/CProgramming/ch15 编写大规模程序.md",
    "content": "# 第十五章 编写大规模程序\n\n源文件包含函数的定义和外部变量，而头文件包含可以在源文件之间共享的信息。\n\n---\n\n## 源文件\n\n可以把程序分割成一定数量的源文件，源文件的扩展名为`.c`。源文件主要包含函数的定义和变量。其中一个源文件必须包含名为 main 的函数，作为程序的起始点。\n\n把程序分成多个源文件有许多显著的优点：\n\n- 把相关的函数和变量集合在单独一个文件中可以帮助明了程序的结构。\n\n- 可以单独对每一个源文件进行编译。如果程序规模很大而且需要频繁改变的话，这种方法可以极大地节约时间。\n\n- 利于复用。\n\n## 头文件\n\n当把程序分割成几个源文件时，问题也随之产生：某文件的函数如何能调用定义在其他文件中的函数？函数如何能访问其他文件中的外部变量？两个文件如何能共享同一个宏定义或类型定义？答案取决于`#include`指令。\n\n`#include`指令告诉预处理器打开指定的文件，并且把此文件的内容插入到当前文件中。这种打开的文件称为头文件，其扩展名为`.h`。\n\n### include 指令\n\n`#include`指令有两种书写格式：\n\n- `#include <文件名>` 搜索系统头文件所在目录，比如在 UNIX 系统中，通常是在 /usr/include\n\n- `#include \"文件名\"` 搜索当前目录，然后搜索系统目录\n\n利用加上诸如-I这样的命令行选项可以添加搜索头文件的位置。\n\n### 共享宏定义和类型定义\n\n大规模的程序包含用于几个源文件共享的宏定义和类型定义，这些定义应该放在头文件中。\n\n比如下图的例子：\n\n![宏定义和类型定义](./images/宏定义和类型定义.png)\n\n有两个源文件包含了 boolean.h\n\n把宏定义和类型定义放到头文件中有如下的好处：\n\n1. 不必把定义复制到需要的源文件，节约时间。\n\n2. 程序变得更加容易修改，改变定义只需要改变头文件。\n\n3. 不用担心源文件包含了相同的宏或类型而其定义不同。\n\n### 共享函数原型\n\n没有原型依赖的函数调用是很危险的，编译器的假设可能是错误的。当调用定义在其他文件中的函数时，要始终确保编译器在调用之前看到函数f的原型。\n\n解决办法就是把函数的原型放进头文件中，然后在所有调用函数f的地方包含头文件。\n\n其包含的方式可能如图所示：\n\n![共享函数原型](./images/共享函数原型.png)\n\n### 共享变量的声明\n\n变量可以在文件中共享。\n\n为了声明变量而不定义，需要在变量声明的开始处放置关键字 extern :\n\n```c\n/* in heaeder file */\nextern int i;\n```\n\n为了共享i，需要在一个源文件中定义i:\n\n```c\n/* in source file */\nint i;\n```\n\n### 保护头文件\n\n如果一个源文件同时包含一个头文件两次，那么可能产生编译错误（比如包含了两次相同的类型定义）。\n\n因此要用到一种保护头文件的方法：\n\n```c\n#ifndef BOOLEAN_H\n#define BOOLEAN_H\n\n/*\n real content\n*/\n\n#endif\n```\n\n如果再次包含此头文件，预处理器就不会再扩展真实的内容。\n\n### 头文件中的#error指令\n\n经常把#error指令放置在头文件中是用来检查不应该包含头文件的条件。例如：\n\n```c\n#ifndef DOS\n#error Graphics supported only under DOS\n#endif\n```\n\n如果非DOS的程序试图包含此头文件，那么编译将在#error指令处终止。\n\n## 构建多文件程序\n\n构建大程序的基本步骤：\n\n- 编译，必须对每一个源文件进行编译。不需要编译头文件。编译器产生一个文件，此文件包含来自源文件的目标代码，称为目标文件（object file）。\n\n- 链接，链接器把目标文件和库文件结合在一起生成一个可执行程序。\n\n大多数编译器允许用单独一步来构建程序：\n\n```c\ncc -m fmt fmt.c line.c word.c\n```\n\n### makefile\n\n使用 makefile 更易于构建大型程序。 makefile 列出了作为程序的部分文件，并描述了它们之间的依赖性。\n\n更多讨论见书本。\n\n!!!note \"my note\"\n\t一种自动生成依赖性的说明的方法是键入命令：`gcc -MM *.c`\n\n### 在程序外定义宏\n\n大多数 UNIX 编译器支持-D选项，允许在命令行指定一个宏定义：\n\n```c\ncc -DDEBUG=1 foo.c\n```\n\n定义了宏 Debug ，在 foo.c 程序中，且值为1。如同在 foo.c 中的开始出现：\n\n```c\n#define DEBUG 1\n```"
  },
  {
    "path": "notes/CProgramming/ch16 结构、联合和枚举.md",
    "content": "# 第十六章 结构、联合和枚举\n\n---\n\n## 结构变量\n\n结构的元素可能具有不同的类型，而且，每个成员都有名字。\n\n### 结构变量的声明\n\n一个声明结构变量的例子：\n\n```c\nstruct {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n} part1, part2;\n```\n\n每个结构变量都有三个成员：number, name, on_hand 。\n\n`struct {...}`指明了类型，而 part1 和 part2 则是具有这种类型的变量。\n\n结构的成员在内存中是按照声明的顺序存储的。第一个声明的变量放在存储位置的最前面。\n\n每个结构表示一种新的名字空间（ name space ）。 part1 的 number 和 part2 的 number 不会有冲突。\n\n### 结构变量的初始化\n\n结构变量可以在声明的同时进行初始化：\n\n```c\nstruct {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n} part1 = { 528, \"Disk drive\", 10 },\n  part2 = { 914, \"Printer cable\", 5 };\n```\n\n结构初始化式的表达式必须是常量。初始化式可以短于它所初始化的结构，这样任何“剩余”成员都用0作为它的初始值。\n\n### 对结构的操作\n\n为了访问结构内的成员，首先写出结构的名字，然后写出成员的名字：\n\n```c\nprintf(\"Part number: %d\\n\", part1.number);\n```\n\n结构成员的值是左值：\n\n```c\npart1.number = 258;\n```\n\n用于访问结构成员的句点是一个运算符，其优先级比较高：\n\n```c\n/* &计算的是 part1.on_hand 的地址 */\nscanf(\"%d\", &part1.on_hand);\n```\n\n另一种主要的结构操作是赋值运算：\n\n```c\npart2 = part1;\n/* 现在 par1 和 part2 每个成员的值都一样 */\n```\n\n可以用结构来复制数组：\n\n```c\nstruct { int a[10]; } a1, a2;\na1 = a2;\n```\n\n运算符=仅仅用于类型一致的结构。\n\n## 结构类型\n\n如果在两个地方编写了：\n\n```c\nstruct {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n} part1;\n\nstruct {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n} part2;\n```\n\n那么 part1 和 part2 就不是同一个类型，这样就不能执行赋值操作。为了解决这个问题，需要为表示结构的类型定义名字。方法有两种：\n\n- 使用结构标记\n\n- 使用 typedef 定义类型名\n\n### 结构标记的声明\n\n结构标记（ structure tag ）即：\n\n```c\nstruct part {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n};\n\n/* 用标记 part 声明变量 */\nstruct part part1, part2;\n```\n\n### 结构类型的定义\n\n即：\n\n```c\ntypedef struct {\n    int number;\n    char name[NAME_LEN + 1];\n    int on_hand;\n} Part;\n\n/* 声明变量 */\nPart part1, part2;\n```\n\n## 联合\n\n联合（ union ）也是由一个或多个成员构成的，而且这些成员可能具有不同的数据类型。但是，编译器只为联合中最大的成员分配足够的内存空间，联合的成员在这个空间内彼此覆盖。\n\n对于：\n\n```c\nunion {\n    int i;\n    float f;\n} u;\n\nstruct {\n    int i;\n    float f;\n} s;\n```\n\n他们的存储如：\n\n![联合和结构的存储](./images/联合和结构的存储.jpg)\n\n其中 u.i 和 u.f 具有相同的地址。\n\n## 枚举\n\n枚举（ enumeration ）是一种由程序员列出的值，而且程序员必须为每种值命名（枚举常量）：\n\n```c\nenum { CLUBS, DIAMONDS, HEARTS, SPADES } s1, s2;\n```\n\n虽然枚举和结构没什么共同的地方，但是它们的声明方式很类似。\n\n枚举常量的名字必须不同于闭合作用域内声明的其他标识符。\n\n### 枚举标记和枚举类型\n\n为了定义枚举标记，可以写成：\n\n```c\nenum suit { CLUBS, DIAMONDS, HEARTS, SPADES };\n\n/* 声明枚举变量 */\nenum suit s1, s2;\n```\n\n用 typedef 给枚举命名：\n\n```c\ntypedef enum { CLUBS, DIAMONDS, HEARTS, SPADES } Suit;\n\n/* 声明枚举变量 */\nSuit s1, s2;\n```\n\n### 枚举作为整数\n\n在系统内部，C语言会把枚举变量和常量作为整数来处理。枚举常量的值可以是任意整数：\n\n```c\nenum suit { CLUBS = 1, DIAMONDS = 2, HEARTS = 3, SPADES = 4 };\n```\n\n两个或多个枚举常量具有相同的值甚至也是合法的。\n\n当没有为枚举常量指定值时，它的值是一个大于前一个常量的值的值（大1）。默认第一个枚举常量的值为0。"
  },
  {
    "path": "notes/CProgramming/ch17 指针的高级应用.md",
    "content": "# 第十七章 指针的高级应用\n\n---\n\n## 动态存储分配\n\n任何单纯的数据结构（各种内置类型，数组，结构体），其大小在程序开始时已经确定了，且不能改变。而一些数据结构可能需要动态的改变其数据长度，比如链表。这就要用到**动态存储分配**（dynamic storage allocation）。\n\n使用动态存储分配的数据块存放在“堆”上，和其它存储区域不同的是，“堆”里的数据应该让程序员来控制释放（free）时机。\n\n为了动态地分配存储空间，将需要调用3种内存分配函数中的一种，这些函数都是声明在stdlib.h中的：\n\n0. malloc，分配内存块，但是不初始化它\n\n0. calloc，分配内存块，并对其清零\n\n0. realloc，调整先前分配的内存块\n\n由于malloc函数不需要对分配的内存块进行清除，所以它比calloc函数更高效。\n\n### 空指针\n\n当调用内存分配函数时，无法定位满足我们需要的足够大的内存块，这种问题始终可能出现。如果真的发生了这类问题，函数会返回空指针。\n\n**空指针**（null pointer）是指一个区别于所有有效指针的特殊值。\n\n!!!warning\n    程序员的责任是测试任意内存分配函数的返回值，并且在返回空指针时采取适当的操作。通过空指针试图访问内存的效果是未定义的，程序可能会崩溃或者出现不可预测的行为。\n\n用名为NULL的宏来表示空指针，可用下列方式测试malloc函数的返回值：\n\n```c\np = malloc(10000);\nif (p == NULL) {\n    /* allocation failed; take appropriate action */\n}\n```\n\n## 动态分配字符串\n\n动态内存分配经常用于字符串操作。字符串始终存储在固定长度的数组中，而且可能很难预测这些数组需要的长度。通过动态地分配字符串，可以推迟到程序运行时才作决定。\n\n### 使用malloc函数为字符串分配内存\n\n函数原型：\n\n```c\nvoid *malloc(size_t size);\n```\n\nsize_t是无符号整型，malloc分配了一段size个字节的连续空间，并返回该空间首地址。如果分配失败就返回NULL。\n\n因为C语言保证char型值确切需要一个字节的内存，为了给n个字符的字符串分配内存空间，可以写成：\n\n```c\np = malloc(n + 1);\n```\n\n通常情况下，可以把void\\*型值赋给任何指针类型的变量。然而，一些程序员喜欢强制转换malloc函数的返回值：\n\n```c\nchar *p = (char*)malloc(n + 1);\n```\n\n由于使用malloc函数分配内存不需要清除或者以任何方式初始化，所以p指向带有n+1个字符的未初始化的数组。\n\n可以调用strcpy函数对上述数组进行初始化：\n\n```c\nstrcpy(p, \"abc\");\n```\n\n数组中前4个字符分别为a, b, c和空字符。\n\n## 动态分配数组\n\n编写程序时，常常为难数组估计合适的大小。较方便的做法是等到程序运行时再来确定数组的实际大小。\n\n虽然malloc函数可以为数组分配内存空间，但calloc函数确实是最常用的一种选择。因为calloc函数对分配的内存进行初始化。realloc函数允许根据需要对数组进行“扩展”或“缩减”。\n\n### 使用malloc函数为数组分配存储空间\n\n当使用malloc函数为数组分配存储空间时，需要使用sizeof运算符来计算出每个元素所需要的空间数量。\n\n使用sizeof计算是必须的，因为这样计算的结果在不同平台下都是正确的。\n\n```c\nint *a = malloc(n * sizeof(int));\n```\n\n这里的n可以在程序执行期间计算出来。\n\n一旦a指向了动态分配的内存块，就可以把它用作数组的名字。这都要感谢C语言中数组和指针的紧密关系。可以使用下列循环对此数组进行初始化：\n\n```c\nfor (i = 0; i < n; ++i)\n    a[i] = 0;\n```\n\n### calloc函数\n\n函数原型：\n\n```c\nvoid *calloc(size_t nmemb, size_t size);\n```\n\nnmemb是数据单元的个数， size是一个数据单元的大小。返回成功申请的数据块首地址，失败返回NULL。\n\ncalloc不仅会从“堆”申请存储区域，还会把这段区域清零。也因此其执行效率没有malloc高。\n\n下列calloc函数的调用为n个整数的数组分配存储空间，并且保证全部初始为0：\n\n```c\na = calloc(n, sizeof(int));\n```\n\n通过调用以1作为第一个实际参数的calloc函数，可以为任何类型的数据项分配空间：\n\n```c\nstruct point { int x, y; } *p;\np = calloc(1, sizeof(struct point));\n```\n\n此语句执行后，p指向结构，且此结构的成员x和y都会被设置为0。\n\n### realloc函数\n\n一旦为数组分配完内存，稍后可能会发现数组过大或过小。realloc函数可以调整数组的大小使它更适合需要。\n\n函数原型：\n\n```c\nvoid *realloc(void *ptr, size_t size);\n```\n\nptr必须指向内存块，且此内存块一定是先通过malloc函数、calloc函数或realloc函数的调用获得的。size表示内存块的新尺寸，新尺寸可能会大于或小于原有尺寸。\n\nC标准列出几条关于realloc函数的规则：\n\n- 当扩展内存块时，realloc函数不会对添加进内存块的字节进行初始化。\n\n- 如果realloc函数不能按要求扩大内存块，那么它会返回空指针，并且在原有内存块中的数据不会发生改变。\n\n- 如果realloc函数调用时以空指针作为第一个实际参数，那么它的行为就像malloc函数一样。\n\n- 如果realloc函数调用时以0作为第二个实际参数，那么它会释放掉内存块。\n\n!!!warning\n    一旦realloc函数返回，请一定要对指向内存块的所有指针进行更新，因为可能realloc函数移动了其他地方的内存块。\n\n实际使用时，realloc应该始终对ptr指向的存储区域进行扩展。\n\nrealloc不是一个好用的函数，要很小心才行。这是因为原来的存储区域会被释放掉（虽然新的存储区域会可能和原来的重叠），其指针很可能都变的无效。\n\n## 释放存储\n\nmalloc函数和其他内存分配函数所获得的内存块都来自一个称为**堆**（heap）的存储池。调用这些函数经常会耗尽堆，或者要求大的内存块也可能耗尽堆，这会导致函数返回空指针。\n\n更糟的是，程序可能分配了内存块，然后又丢失了这些块的追踪路径，因而浪费了空间。如下例子：\n\n```\np = malloc(...);\nq = malloc(...);\np = q;\n```\n\n由于没有指针指向第一个内存块，所以再也不能使用此内存块了。\n\n对于程序而言，不再访问到的内存块被称为是**垃圾**（garbage）。在后边留有垃圾的程序有**内存泄漏**（memory leak）。一些语言提供了**垃圾收集器**（garbage collector），但C语言不提供。每个C程序负责回收各自的垃圾，方法是调用free函数来释放不需要的内存。\n\n如上例子，就是一个内存泄漏。第一块内存再也访问不到了，这应该就是上文所说的留有垃圾。\n\n**free**\n\n只有一个方法释放由动态存储分配函数分配的内存空间。就是使用free函数，如果不释放，那么这块资源就一直放在“堆”里，直到程序退出。\n\n函数原型：\n\n```c\nvoid free(void *ptr);\n```\n\n使用free函数很容易，只是简单地把指向不再需要的内存块的指针传递给free函数就可以了：\n\n```c\np = malloc(...);\nfree(p);\n```\n\n调用free函数来释放p所指向的内存块。然后会把这个释放的内存返回给堆，使此内存块可以被复用。\n\n### “悬空指针”问题\n\nfree操作会生成**悬空指针**（dangling pointer）。即调用free(p)函数会释放p指向的内存块，但是不会改变p本身。如果忘记了p不再指向有效内存块（而使用它），后果很严重。\n\n悬空指针是很难发现的，因为几个指针可能指向相同的内存块。在释放内存块时，全部的指针都会留有悬空。\n\n## 指向函数的指针\n\n函数也有地址，所以就可以有指针指向。一些功能强大的函数（像模板一样）都是通过函数指针和`void*`实现的。\n\n### 函数指针\n\n函数指针主要被存放在：\n\n- 数组里，方便日后调用\n- 形参，成为模板函数的实现，比如qsort\n\n定义一个函数指针类型的例子：\n\n```c\ntypedef void (*Func)();\n```\n\n### 函数入口地址\n\n函数名就是函数地址，但通常会对函数名做&运算，其实得到的结果是一样的。同样对函数指针做`*`运算（解引用）和直接拿函数指针用也是一样的，都是代表了函数的入口地址。\n\n一般会对函数名做&操作，对函数指针做`*`操作，让它们看上去比较像指针的使用。"
  },
  {
    "path": "notes/CProgramming/ch18 声明.md",
    "content": "# 第十八章 声明\n\n---\n\n## 什么是声明\n\n一个变量或者函数应该首先被声明，才会被使用。因为声明会告诉编译器这个变量或者函数的信息，然后编译器就可以检查其存储空间和作用域，以及使用时的语法是否正确。\n\n## 声明的语法\n\n声明的格式（声明式）是：\n\n**声明说明符 声明符**\n\n声明说明符描述了变量或者函数的性质，声明符代表变量名或者函数名，并且可以指明它的额外信息（如是一个数组or指针or函数）。\n\n声明说明符分为以下3类：\n\n0. 存储类型。四种：auto（块内默认存储类型，无需显示声明），static，extern和register（已经被现代编译器优化，一般不需要声明）。\n\n0. 类型限定符，有const和volatile 。\n\n0. 类型说明符，诸如int，long，unsighed，或者自定义数据类型等，对于函数，类型说明符就是返回类型。\n\n声明符就是一个标识符，然后可以用星号（代表指针），方括号（代表数组），圆括号（代表函数）修饰。\n\n可以看到，声明没有赋值的内容。\n\n## 存储类型\n\n### 变量的性质\n\nC程序中，变量都有三种性质：\n\n0. 存储期限，决定了变量的生存周期。具有自动存储期限的变量在第一次执行时获得内存单元，出块时释放内存单元；具有静态存储期限的变量在程序的生命周期内都拥有内存单元。\n\n0. 作用域，拥有块作用域的变量只能在块内被使用；拥有文件作用域的变量从声明变量开始到文件结束的范围都可以使用。\n\n0. 链接，拥有外部链接的变量（内存单元）可以被程序的其它文件访问；拥有内部链接的变量只可以在文件内访问；无链接的变量只能在一个函数内访问。\n\n上述三种性质取决于变量的声明位置以及变量的存储类型，比如不指明存储类型的话：\n\n- 在块内声明的变量，具有自动存储期限，块作用域，无链接。\n\n- 在程序的最外层声明的变量，具有静态存储期限，文件作用域，外部链接。\n\n### 作用域\n\n决定一个变量的作用域的，仅在于它的声明位置：\n\n1. 在块内声明的，具有块作用域。\n\n2. 在文件最外层声明的，具有文件作用域。\n\n作用域是编译级别的（非链接）语法，编译器根据变量的作用域检查其使用的位置是否正确。\n\n### static存储类型\n\n当static作用于一个块内声明的变量时，将改变它的存储期限为静态存储期限。\n\n当static作用于一个最外层声明的变量时，将改变它的链接为内部链接，使这个变量的内存单元不能被其它文件所访问。\n\n### extern存储类型\n\n用extern来声明一个变量，不会让编译器为它分配内存单元，它只是告诉编译器，这个变量是在别的地方定义的。因此：当extern作用于一个变量时，这个变量必须拥有静态存储期限且一般有外部链接。一般这个变量都是一个在最外层定义的变量。\n\n### 函数存储类型\n\n默认情况下，函数存储类型都是extern的，代表此函数的链接是外部链接，可以被其它文件访问。\n\n如果给函数加上static声明，那么这个函数的链接就会被修改成内部链接，只能在文件内访问。如果一个函数不需要被多个模块共享，那么就应该声明成static的。\n\n## const限定符\n\n声明一个编译器维度上的常量，但却不能看做一个常量表达式，从而不能定义一个数组的边界（应该用#define）。\n\nconst主要用于保护一个指针指向的对象不被修改，也就是定义一个常量指针，使其指向的空间不允许被修改。\n\n## 声明符\n\n声明符是由标识符和三个特殊符号组成的，这三个特殊符号是：\n\n- 放在标识符前面的`*`号\n\n- 放在标示符后面的`()`或者`[]`\n\n### 解释复杂声明\n\n有时候声明符包含了多个特殊符号，这就要通过两条规则进行解释才能理解。它们是：\n\n- 始终从内向外读声明符，也就是先定位标识符，然后往外读\n\n- `[]`和`()`始终优先于`*`，但`()`可以强制修改优先级\n\n## 初始化式\n\n在声明一个变量时，可以给它`=`一个初始值，这叫初始化式，而不是赋值。\n\n需要注意的几点：\n\n- 静态变量只能用常量表达式初始化，如果没有初始化，那么就是0\n\n- 拥有自动存储期限的变量如果没有初始化，其值就是未定义的（包括数组）\n\n- 数组的初始化（大括号闭合）必须用常量表达式初始化每一个元素"
  },
  {
    "path": "notes/CProgramming/ch19 程序设计.md",
    "content": "# 第十九章 程序设计\n\n虽然C语言不是专门用来编写大规模程序的，但许多大规模程序的确是用C语言编写的。相对于小型程序，编写一个大规模的程序需要更仔细的设计和更详细的计划。\n\n---\n\n## 模块\n\n当设计一个C程序（或其他任何语言的程序）时，最好将它看作是一些独立的模块。模块是一组功能（服务）的集合，其中一些功能可以被程序的其他部分（称为客户）使用。每个模块都有一个接口来描述所提供的功能。模块的细节，包括这些功能自身的源代码，都包含在模块的实现中。\n\n在C语言环境下，这些“功能”就是函数，模块的接口就是头文件，头文件中包含那些可以被其他文件调用的函数的原型。模块的实现就是包含该模块中函数的定义的源文件。\n\n将程序分割成模块有一系列好处：\n\n- 抽象。我们知道模块会做什么，但不需要知道这些功能是如何被实现的。因为抽象的存在，使我们不必为了修改部分程序而了解整个程序是如何工作的。\n\n- 可复用性。每一个提供一定功能的模块，都有可能在另一个程序中复用。\n\n- 可维护性。将程序模块化后，程序中的错误通常只会影响一个模块，因为更容易找到并解决错误。在解决了错误后，重新编译程序只需要将该模块的实现进行编译即可。\n\n一旦我们已经认同了模块化程序设计是正确的方向，接下来的问题就是设计程序的过程中究竟应该定义哪些模块。\n\n### 内聚性与耦合性\n\n一个好的模块并不是随意的一组声明。好的模块应该具有下面两个性质：\n\n- 高内聚性。模块中的元素应该相互紧密相关。\n\n- 低耦合性。模块之间应该尽可能相互独立。低耦合性可以使程序更便于修改，并方便以后复用模块。\n\n### 模块的类型\n\n由于需要高内聚性、低耦合性，模块通常会属于下面几类：\n\n- 数据池。表示一些相关变量或常量的集合。通常这类模块是一些头文件。\n\n- 库。库是一组相关函数的集合。\n\n- 抽象对象。一个抽象对象是指对于隐藏的数据结构进行操作的一组函数的集合。\n\n- 抽象数据类型。将具体数据的实现方式隐藏起来的数据类型称为抽象数据类型。作为客户的模块可以使用该类型来声明变量，但不会知道这些变量的具体数据结构。如果客户模块需要对变量进行操作，则必须调用抽象数据类型所提供的函数。\n\n## 信息隐藏\n\n一个设计良好的模块经常对它的客户隐藏一些信息。例如我们的栈模块的使用者就不需要知道究竟栈是用数组实现的还是用链表。信息隐藏有两大优点：\n\n- 安全性。数据必须通过模块自身提供的函数来操作，而这些函数都是经过测试的。\n\n- 灵活性。无论对模块的内部机制进行多大的改动，都不会很复杂。不需要改变模块的接口。\n\n在C语言中，可以用于强行信息隐藏的工具是 static 存储类型。将一个函数声明成 static 类型可以使函数内部链接，从而阻止其他文件（包括模块的客户）调用这个函数。将一个带文件作用域的变量声明成 static 类型可以达到类似的效果，使该变量只能被同一个文件中的其他函数访问。\n\n## 抽象数据类型\n\n对于作为抽象对象的模块，有一个缺点：不可能对同一个对象有多个实例。为了达到这个目的，需要进一步创建一个新的类型。这就是抽象数据类型。然后模块的接口函数需要传入这个类型对象的指针对其进行操作。\n\n但C语言不提供封装的功能，客户可以访问抽象数据类型的成员。确实有技巧可以达到类似的目的，但使用起来笨拙。\n\n实现封装的最佳方法是使用C++语言。实际上，C++语言产生的原因之一就是因为C语言不能很好的支持抽象数据类型。\n\n## C++语言\n\n略。"
  },
  {
    "path": "notes/CProgramming/ch20 低级程序设计.md",
    "content": "# 第二十章 低级程序设计\n\n前面几章中讨论的是C语言中高级的、与机器无关的特性。有一些程序需要进行位级别的操作。位操作和其他一些低级运算在编写系统程序、加密程序、图形程序以及一些需要高执行速度或高效地使用空间的程序时非常有用。\n\n本章描述的一些技术需要用到数据在内存中如何存储的知识，这对不同机器和编译器可能会不同。依赖于这些技术很可能会使程序丧失可移植性。\n\n---\n\n## 按位运算符\n\n### 移位运算符\n\n移位运算符可以改变数的二进制形式，将它的位向左或者向右移动。\n\n|符号|含义|\n|:-:|:-:|\n|<<|左移位|\n|>>|右移位|\n\n运算符`<<`和`>>`的操作数可以是任意整数型或字符型的。对两个操作数都会进行整数提升，返回值的类型是左边操作数提升后的类型。\n\n`i<<j`的值是将i中的位左移j位后的结果。每次从i的最左端溢出一位，在i的最右端补一个0位。`i>>j`的值是将i中的位右移j位后的结果。如果i是无符号数或非负数，则需要在i的最左端补0。如果i是负值，其结果是由实现定义的。一些补0，一些补1。\n\n!!!note\n    可移植性技巧：最好仅对无符号数进行移位运算。\n\n### 按位求反、按位与运算符、按位异或运算符和按位或运算符\n\n|符号|含义|\n|:-:|:-:|\n|~|按位求反|\n|&|按位与|\n|^|按位异或|\n|\\||按位或|\n\n上面的顺序也是运算符优先级的顺序。\n\n## 结构中的位域\n\nC语言提供了声明成员为位域的结构。\n\n比如：\n\n```c\nstruct file_date {\n    unsigned int day:5;\n    unsigned int month:4;\n    unsigned int year:7;\n};\n\nstruct file_data fd;\nfd.day = 28;\nfd.month = 12;\nfd.year = 8; /* represents 1988 */\n```\n\n这个结构占据32个比特，每个成员后面的数字指定了它所占用位的长度。\n\n位域有一个限制，C语言不允许将&运算符作用于位域。\n\n位域之间没有间隙，直到剩下的空间不够用来放下一个位域了，这时，一些编译器会跳到下一个存储单元继续存放位域，而另一些则会将位域拆开跨存储单元存放。位域的存放的顺序也是由实现定义的。"
  },
  {
    "path": "notes/CProgramming/ch21 标准库.md",
    "content": "# 第二十一章 标准库\n\n---\n\n## 标准库的使用\n\nC89标准库有15个部分，即15个头文件。\n\n标准头主要由函数原型、类型定义和宏定义组成。\n\n**`<assert.h>` 诊断**\n\n允许程序插入自我检查，一旦检查失败，程序就被终止。\n\n**`<ctype.h>` 字符处理**\n\n提供用于字符分类及大小写转换的函数。\n\n**`<errno.h>` 错误**\n\n提供了error number，它是一个左值，可以在调用特定库函数后进行检测，来判断调用过程中是否有错误发生。\n\n**`<float.h>` 浮点类型的特性**\n\n提供了用于描述浮点类型特定的宏，包括值的范围及精度。\n\n**`<limits.h>` 整数类型的大小**\n\n提供了用于描述整数类型特性的宏，包括他们的最大值和最小值。\n\n**`<locale.h>` 本地化**\n\n提供一些函数来帮助程序适应针对某个国家或地区的特定行为方式。包括显示数的方式、货币的格式、字符集以及日期和时间的表示形式。\n\n**`<math.h>` 数学计算**\n\n提供常见的数学函数。\n\n**`<setjmp.h>` 非本地跳转**\n\n提供了setjmp和longjmp函数，setjmp会标记程序中的一个位置，随后可以用longjmp返回标记的位置。可以实现从一个函数跳转到另一个函数中，绕过正常的函数返回机制。主要用来处理程序中的严重问题。\n\n**`<signal.h>` 信号处理**\n\n提供了用于处理异常的函数，包括中断和运行时错误。signal可以设置一个函数，使系统信号到达时自动调用该函数；raise函数用来产生信号。\n\n**`<stdarg.h>` 可变参数**\n\n提供一些工具用于编写参数个数可变的函数。\n\n**`<stddef.h>` 常用定义**\n\n提供经常使用的类型和宏定义。\n\n**`<stdio.h>` 输入与输出**\n\n提供大量的输入和输出函数，包括对文件的顺序访问和随机访问操作。\n\n**`<stdlib.h>` 常用实用程序**\n\n包含大量无法划归其它头的函数。包含函数：将字符串转换成数，产生伪随机数，执行内存管理任务，与操作系统通信，执行搜索与排序等。\n\n**`<string.h>` 字符串处理**\n\n包含操作字符串的函数。\n\n**`<time.h>` 日期和时间**\n\n提供相应的函数来获取时间，操纵时间，以及格式化时间。\n\n### 对标准库中所用名字的限制\n\n只要包含了标准头（没有不包含的情况吧？），必须遵循两条规则：\n\n1. 不用自己定义标准头已定义过的宏\n\n2. 具有文件作用域的库名也不可以在文件层次重定义\n\n还有一些命名规则，不要与标准库有冲突：\n\n- 由一个下划线和一个大写字母开头，或由两个下划线开头的标识符，是标准库保留的标识符\n\n- 由一个下划线开头的标识符，被保留用作具有文件作用域的标识符和标记，只可用于函数内部声明\n\n- 在标准库中所有具有外部链接的标识符被保留用作具有外部链接的标识符，比如printf\n\n### 使用宏隐藏的函数\n\nC语言允许在头中定义与库函数同名的宏。从而使得宏隐藏掉函数。\n\n使用宏可能会提高程序的运行速度。如果有不想使用宏的情况，可能是因为想缩小可执行代码的大小。\n\n若要删掉宏，可用如下方法：\n\n```\n#include <stdio.h>\n#undef getchar\n```\n\n还可以禁用宏：\n\n```\n(getchar)()\n```\n\n## stddef.h 常用定义\n\n此头提供了常用的类型和宏的定义。定义的类型包括以下几个：\n\n- ptrdiff_t，指针相减的结果类型，是有符号整数\n\n- size_t，sizeof运算符返回的类型，是无符号整数\n\n- wchar_t，一种足够大的，可以用来表示所有支持的地区的所有字符的类型\n\n其中一个宏是：offsetof，其意思是求得结构体的起点到指定成员间的字节数。\n\n比如，有下面的结构体：\n\n```c\nstruct s {\n    char a;\n    int b[2];\n    float c;\n};\n```\n\noffsetof(struct s, a)的值一定是0，因为结构体的首元素的地址一定是结构体的地址；\n\noffsetof(struct s, b)的值可能是1，但也可能是4（考虑到字节对齐）。"
  },
  {
    "path": "notes/CProgramming/ch22 输入_输出.md",
    "content": "# 第二十二章 输入 输出\n\n---\n\n## 流\n\n流意味着任意输入的源或任意输出的目的地。输入流通常和键盘相关，输出流通常和屏幕相关。\n\n流还可以表示为磁盘上的文件，以及其他设备。\n\n### 文件指针\n\n流的访问是通过 **文件指针（file pointer）** 实现的。此指针的类型为`FILE*`。\n\nstdio.h提供了3种标准流，这三个标准流是备用的，不能声明、打开、关闭它们。\n\n|文件指针|流|默认的含义|\n|:-:|:-:|:-:|\n|stdin|标准输入|键盘|\n|stdout|标准输出|屏幕|\n|stderr|标准错误|屏幕|\n\n**重定向（redirection）**\n\n操作系统允许通过重定向机制来改变标准流默认的含义。\n\n例如：\n\n```\ndemo < in.data\n```\n\n称为**输入重定向（input redirection）**，即把stdin流表示为文件in.dat，而非键盘。对于程序demo而言，它并不知道输入流是来自键盘还是文件。\n\n这样子是**输出重定向（output redirection）**：\n\n```\ndemo > out.dat\n```\n\n如此一来，写入stdout的内容将不再输出到屏幕，而是文件out.dat。\n\n### 文本文件与二进制文件\n\n文件就是字节的序列。\n\n文本文件中，字节表示字符。\n\n二进制文件中，字节就是字节，可以用于表示任意类型的数据。\n\nDOS系统中，这两种文件之间有如下差异：\n\n- 行的结尾。文本文件写入换行符时，换行符扩展成一对字符，即回行符和跟随的回车符。如果把换行符写入二进制文件时，它就是一个单独的字符（换行符）。\n\n- 文件末尾。文本文件中，文件的结束标记是CTRL+Z字符（\\x1a）。二进制文件中，此字符没有特别的含义，跟其它任何字符一样。\n\n在Unix操作系统中，二进制文件和文本文件不进行区分，其存储方式一样。\n\n## 文件操作\n\n**打开文件**\n\n使用 fopen 函数。\n\n**关闭文件**\n\n使用 fclose 函数。\n\n**从命令行获取文件名**\n\n当程序需要打开一个文件时，通常通过命令行参数把文件名传给程序，这样更具灵活性。\n\n主函数：\n\n```c\nint main(int argc, char *argv[]);\n```\n\nargc是命令行实际参数的数量（非数组长度），argv是参数字符串数组。\n\nargv[0]是程序名，argv[1] ~ argv[argc-1]是剩余参数。\n\nargv[argc]是空指针。\n\n**临时文件**\n\ntmpfile 函数生成临时文件。\n\ntmpnam 函数生成一个临时的文件名。\n\n**文件缓冲**\n\n向磁盘直接读写数据相对比内存读写慢。使用缓冲区（buffer）来解决这个问题。写入流的数据首先放到缓冲区里面，当缓冲区满了（或关闭流）时，刷新缓冲区，把数据写入文件。\n\n输入流可以使用类似的方法进行缓冲：缓冲区包含来自输入设备的数据。\n\n使用 fflush 函数刷新缓冲区。\n\n**其它文件操作**\n\nremove 函数删除文件，rename 函数重命名文件。如果是用 fopen 和 tmpnam 产生的临时文件，可以使用 remove 把它删除，或者用 rename 使其成为永久文件。\n\n## 格式化的输入与输出\n\n即 ...printf 类函数 和 ...scanf 类函数的使用。\n\n### 检测文件末尾和错误条件\n\n每个流都有与之相关的两个指示器：**错误指示器**（error indicator），**文件末尾指示器**（end-of-file indicator）。\n\n打开流时，会清除这些指示器；流上的操作失败时会设置某个指示器。\n\n遇到文件末尾就设置文件末尾指示器，遇到错误就设置错误指示器。\n\n一旦设置了指示器，它就会保持这种状态，直到可能由 clearerr 调用而引发的明确清除操作为止。 clearerr 可以清除文件末尾指示器和错误指示器。\n\n如果设置了文件末尾指示器， feof 返回非零值。\n\n如果设置了错误指示器， ferror 返回非零值。\n\n## 字符的输入/输出\n\n输入输出的字符类型应使用int，原因之一是由于函数通过返回EOF说明文件末尾or错误情况，EOF是一个负的整型常量。\n\n### 输出函数\n\n```c\nint fputc(int c, FILE *stream);\nint putc(int c, FILE *stream);\nint putchar(int c);\n```\n\n### 输入函数\n\n```c\nint fgetc(FILE *stream);\nint getc(FILE *stream);\nint getchar(void);\n```\n\n## 行的输入/输出\n\n### 输出函数\n\n```c\nint fputs(const char *s, FILE *stream);\nint puts(const char *s);\n```\n\nputs函数向标准输出输出一行字符串，会自动添加一个换行符。\n\nfputs不会自动添加换行符。\n\n### 输入函数\n\n```c\nchar *fgets(char *s, int size, FILE *stream);\nchar *gets(char *s);\n```\n\ngets函数逐个读取字符，存储到s中，直到读到换行符时停止，并把换行符丢弃。\n\nfgets当读入了size-1个字符时或读到换行符时停止，且会存储换行符。\n\n如果出现错误，或者在存储任何字符之前达到了输入流的末尾，函数返回空指针。否则返回第一个实参。\n\n函数会在字符串的末尾存储空字符。\n\n## 块的输入输出\n\n```c\nsize_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);\n\nsize_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);\n```\n\nfread函数和fwrite函数允许程序在单步中读写大的数据块。\n\nfwrite函数被设计用来把数组复制给流。第一个参数是数组首元素的地址，第二个参数是每个数组元素的大小（以字节为单位），第三个参数是要写的元素的数量，第四个参数是文件指针，说明了要写的数据位置。\n\nfwrite返回实际写入的元素数量，如果写入错误，此数就会小于第三个参数。\n\nfread函数从流读入数组的元素。其参数类似fwrite。\n\nfread返回实际读入的元素数量，此数应该等于第三个参数。否则可能达到了文件末尾或者出现了错误。使用feof和ferror确定出问题的原因。\n\n检查fread的返回值是非常重要的。\n\n## 文件的定位\n\n每个流都有相关联的**文件位置（file position）**。打开文件时，根据模式可以在文件的起始处或者末尾处设置文件位置。\n\n在执行读或写操作时，文件位置会自动推进。\n\nstdio.h提供了一些函数，用于确定当前的文件位置或者改变文件位置：\n\n```c\nint fseek(FILE *stream, long offset, int whence);\n\nlong ftell(FILE *stream);\n\nvoid rewind(FILE *stream);\n\nint fgetpos(FILE *stream, fpos_t *pos);\nint fsetpos(FILE *stream, fpos_t *pos);\n```\n\n**fseek**函数改变第一个参数相关的文件的位置。第二个参数说明新位置是根据文件的起始处、当前位置还是文件末尾来计算，也就是第三个参数来计算。\n\n第三个参数可取值为：\n\n- SEEK_SET，文件的起始处。\n\n- SEEK_CUR，文件的当前位置。\n\n- SEEK_END，文件的末尾处。\n\n**ftell**函数返回当前文件位置。如果发生错误，ftell返回-1L，并且把错误码存储到errno。\n\n**rewind**函数会把文件位置设置到起始处。rewind还会为fp清除错误指示器。\n\n**fgetpos**和**fsetpos**用于处理大的文件，使用fpos_t表示文件位置，它可能是一个结构。函数成功返回0，失败返回非0值并把错误码存放到errno中。\n\n## 字符串的输入/输出\n\nsprintf和snprintf函数将按写到数据流一样的方式写字符到字符串。\n\nsscanf函数从字符串中读出数据就像从数据流中读数据一样。\n\n### 输出函数\n\n```c\nint sprintf(char *str, const char *format, ...);\nint snprintf(char *str, size_t size, const char *format, ...);\n```\n\n类似于printf函数，唯一不同是sprintf函数把输出写入字符数组而不是流。当完成向字符串写入的时候，sprintf函数会添加一个空字符，并返回所存储的字符数量（不计空字符）。如果遇到错误，返回负值。\n\nsnprintf写入的字符数量不会超过size-1，结尾空字符不计。只要size不是0，都会有空字符。\n\n### 输入函数\n\n```c\nint sscanf(const char *str, const char *format, ...);\n```\n\nsscanf与scanf类似，唯一的不同就是sscanf从字符数组中读取数据而不是流。\n\nsscanf函数返回成功读入并存储的数据项的数量，如果在找到第一个数据项之前到达了字符串的末尾，那么sscan函数返回EOF。"
  },
  {
    "path": "notes/CProgramming/ch23 库对数值和字符数据的支持.md",
    "content": "# 第二十三章 库对数值和字符数据的支持\n\n---\n\n## float.h：浮点型的特性\n\n提供了用来定义浮点型的范围及精度的宏。\n\n## limits.h：整型取值范围\n\n仅定义了每种整数类型的取值范围的宏。\n\n## math.h：数学计算\n\nmath.h里的函数处理的都是浮点类型的数值。\n\n在 UNIX/Linux 下编译，需要指明连接 math 库：-lm\n\nmath.h中定义的函数包含下面5种类型：\n\n- 三角函数 sin cos tan acos asin atan atan2\n\n- 双曲函数 cosh sinh tanh\n\n- 指数和对数函数 exp log ...\n\n- 幂函数 pow sqrt\n\n- 就近取整函数，绝对值函数和取余函数 ceil fabs floor fmod\n\n### 错误\n\n在math.h里声明的函数，如果出现错误（可能是参数不对），会把错误码存到errno。且若函数返回值大于double的最大值，那么函数会返回一个特殊值HUGE_VAL（double类型表示无穷大，Linux下输出成inf）。\n\nerrno有两种可能值：\n\n1. EDOM：代表定义域错误（Linux下值为33），即参数取值不对，比如给sqrt传一个负数。\n2. ERANGE：代表取值范围错误（返回值）（Linux下值为34），无法用double来表示了。比如exp(1000)。（PS：不是所有的数学函数出现返回值为无穷大时都会置errno为ERANGE）\n\n## ctype.h：字符处理\n\nctype.h提供了两类对字符进行处理的：\n\n1. 测试字符性质\n2. 对字符进行大小写转换\n\n## string.h：字符串处理\n\n这些函数的参数的合法性需要程序员来保证。\n\n### 复制函数\n\n```c\nvoid *memcpy(void *dest, const void *src, size_t n);\nchar *strcpy(char *dest, const char *src);\nchar *strncpy(char *dest, const char *src, size_t n);\n```\n\n### 拼接函数\n\n```c\nchar *strcat(char *dest, const char *src);\nchar *strncat(char *dest, const char *src, size_t n);\n```\n\n### 比较函数\n\n```c\nint strcmp(const char *s1, const char *s2);\nint strncmp(const char *s1, const char *s2, size_t n);\n```\n\n### 搜索函数\n\n```c\nchar *strchr(const char *s, int c);\nvoid *memchr(const void *s, int c, size_t n);\nchar *strrchr(const char *s, int c);\nchar *strtok(char *str, const char *delim);\n```\n\n### 其它函数\n\n```c\nvoid *memset(void *s, int c, size_t n);\nsize_t strlen(const char *s);\n```"
  },
  {
    "path": "notes/CProgramming/ch24 错误处理.md",
    "content": "# 第二十四章 错误处理\n\n---\n\n## assert.h: 诊断\n\n```c\nvoid assert(int expression);\n```\n\nassert声明在assert.h中，实际上是一个宏。其参数必须是一种“断言”，即被认为正常情况下一定为真的表达式。\n\n每次执行assert时，判断此断言，若为假（0），则显示一条错误信息，并调用abort函数终止程序执行。\n\n这条错误信息包含了：断言（以文本格式）、包含assert调用的文件名、assert调用所在的行号。\n\n**禁止assert调用**\n\n方法是：在包含assert.h之前，定义宏NDEBUG。如：\n\n```c\n#define NDEBUG\n#include <assert.h>\n```\n\n## errno.h: 错误\n\n标准库中的一些函数通过向errno.h中声明的errno变量存储一个错误代码来表示有错误发生。\n\n大部分使用errno变量的函数集中在math.h，但也有一些在标准库的其他部分。\n\n如果errno不为0，则说明函数调用过程中有错误发生。\n\nerrno在程序开始的时候值为0，通常在调用函数前把errno置为0，库函数不会将errno清零，这是程序员的责任。\n\n用法如：\n\n```c\ny = sqrt(x); // x为负数则出错\nif (errno != 0) {\n    fprintf(stderr, \"sqrt error, terminated. \\n\");\n    exit(EXIT_FAILURE);\n}\n```\n\n**perror函数和strerror函数**\n\n```c\nvoid perror(const char *s);\nchar *strerror(int errnum);\n```\n\n当库函数向errno存储了一个非零值时，通过perror函数和strerror函数可以得到描述这种错误的信息。\n\nperror函数声明在stdio.h中，它会按照如下顺序把错误信息输出到stderr：\n\n    调用perror的参数: 出错消息（内容根据errno的值决定）\n\nstrerror函数声明在string.h中，它传入errno，返回一个指针，指向描述出错消息的字符串。\n\n## signal.h: 信号处理\n\nsignal.h提供了处理异常的工具，即信号（signal）。信号有两种类型：运行时错误（例如除以0），程序以外导致的事件（例如用户输入了ctrl+c）。\n\n当有错误或外部事件发生时，我们称产生了一个信号。大多数信号是**异步的**：它们可以在程序执行过程中的任意时刻发生。\n\n### 信号宏\n\nsignal.h定义了一系列宏，用于表示不同的信号。参见书本。\n\n### signal函数\n\n```\ntypedef void (*sighandler_t)(int);\nsighandler_t signal(int signum, sighandler_t handler);\n```\n\nsignal函数安装一个信号处理函数。第一个参数是信号的代码，第二个参数是一个指向信号处理函数的指针。\n\n一旦随后在程序执行过程中出现了对应的信号，信号处理函数就会被自动调用，信号的代码作为参数传递给信号处理函数。\n\n除非信号是由调用abort函数或raise函数引发的，否则信号处理函数不应该调用任何库函数，或者试图使用一个静态存储期限的变量。\n\n一旦信号处理函数返回，程序会从信号发生点恢复并继续执行。但是，如果信号是SIGABRT，当处理函数返回时程序会异常终止。如果信号是SIGFPE，那么处理函数返回的结果是UB（即不要使用它）。\n\nsignal函数的返回值是指定信号的前一个处理函数的指针。\n\n### 预定义的信号处理函数\n\nsignal.h提供了两个预定义的信号处理函数（都用宏表示）：\n\n- SIG_DFL。表示按默认方式处理信号，大多数情况下会导致程序终止。\n\n- SIG_IGN，指明随后当信号SIGINT发生时，忽略该信号。\n\n当程序刚开始执行时，根据不同的实现，每个信号的处理函数都会被初始化为SIG_DFL和SIG_IGN。\n\n如果signal调用失败（即无法对指定信号安装处理函数），就返回SIG_ERR（不是一个函数），并在errno中存入错误代码。\n\nC语言要求，除了SIGKILL以外，当一个信号的处理函数被调用时，该信号的处理函数要被重置为SIG_DFL，或者以其他方式加以封锁。\n\n### raise函数\n\n```c\nint raise(int sig);\n```\n\n通常信号都是自然产生的，但也可以通过raise函数触发。\n\nraise函数的返回值可以用来测试调用是否成功：0代表成功，非0代表失败。\n\n## setjmp.h: 非局部跳转\n\n```c\n#include <setjmp.h>\nint setjmp(jmp_buf env);\nvoid longjmp(jmp_buf env, int val);\n```\n\n通常情况下，函数会返回到它被调用的位置。setjmp.h可以使一个函数直接跳转到另一个函数，而不需要返回。\n\nsetjmp宏“标记”程序中的一个位置，随后可以用longjmp函数跳转到该位置。这一机制主要用于错误处理。\n\nsetjmp宏会将当前“环境”保存到一个jmp_buf类型的变量中，然后返回0。如果要返回setjmp宏所标记的位置，可以使用longjmp函数，调用的参数是调用setjmp宏时使用的同一个jmp_buf类型的变量。longjmp函数首先会根据jmp_buf变量的内容恢复当前环境，然后从setjmp宏调用中返回。这次setjmp宏的返回值是val，即调用longjmp函数时的第二个参数（如果val是0，那么返回1）。\n\n如果longjmp的参数未被setjmp初始化，调用longjmp的结果是UB。"
  },
  {
    "path": "notes/CProgramming/ch25 国际化特性.md",
    "content": "# 第二十五章 国际化特性\n\n---\n\n## locale.h 本地化\n\nlocale.h提供的函数用于控制标准库中对于不同的地区会不一样的部分。\n\n地区通常指一个国家，或者一个国家的不同区域。\n\n在标准库中，依赖地区的部分包括：\n\n- 数值的格式。比如一些地区的小数点是用逗号表示\n\n- 货币的格式。不同国家的货币符号不同。\n\n- 字符集。字符集依赖于地区使用的语言。亚洲国家通常比西方国家需要更大的字符集。\n\n- 日期和时间的表示格式。\n\n### 类别\n\n通过修改地区，程序可以改变它的行为来适应不同地区。\n\n可以使用一些宏来指定一个**类型**：\n\n- LC_COLLATE。影响两个字符串比较函数的行为（strcoll和strxfm）。\n\n- LC_CTYPE。影响ctype.h中函数的行为，除了isdigit和isxdigit。同时还影响stdlib.h中的多字节函数。\n\n- LC_MONETRAY。影响由localeconv函数返回的货币格式信息。\n\n- LC_NUMERIC。影响格式化输入/输出函数使用的小数点字符以及stdlib.h中的字符串转换函数（atof和strtod），还会影响localeconv函数返回的非货币格式信息。\n\n- LC_TIME。影响strftime函数的行为，该函数将时间转换成字符串。\n\n### setlocale函数\n\n```c\nchar *setlocale(int category, const char *locale);\n```\n\nsetlocale函数修改当前的地区。如果第一个参数是LC_ALL，就会影响所有的类型。C语言标准对第二个参数仅定义了两种可能值：\"c\"和\"\"。其余的由实现定义，比如， gcc 对于简体中文的地区，可以是\"zh_CN.UTF-8\"\n\n程序执行开始时，都会隐含调用：`setlocale(LC_ALL, \"C\");`\n\n如果用\"\"作为第二个参数，就切换到了本地模式（native locale），这种模式下程序会适应本地的环境。\n\n如果调用成功，返回一个关于地区名字的字符串指针。如果调用失败，返回空指针。\n\nsetlocale函数也可以当作搜索函数使用，如果第二个参数是空指针，setlocale函数会返回一个指向字符串的指针，这个字符串与当前地区类型的设置相关联。\n\n### localeconv函数\n\n```c\nstruct lconv *localeconv(void);\n```\n\n函数返回的struct lconv结构包含了当前地区的详细信息，此结构具有静态存储期限。\n\n详细信息参考书本。\n\n## 多字节字符和宽字符\n\n因为定义已经把char型值的大小限制为一个字节，所以通过改变char类型的含义来处理更大的字符集显然是不可能的。\n\nC语言提供了两种可扩展字符集的编码：**多字节字符**（multibyte character）和**宽字符**（wide character）。\n\nC标准要求0字节始终用来表示空字符。\n\n### 多字节字符\n\n在多字节字符编码中，一个或多个字节表示一个可扩展字符。C语言要求的基本字符是单字节的。\n\n一些多字节字符集依靠**依赖状态编码**（state-dependent encoding）。在这类编码中，每个多字节字符序列都以**初始移位状态**（initial shift state）开始。序列中稍后遇到的一些多字节字符会改变移位状态，并且影响后续字节的含义。\n\nMB_LEN_MAX和MB_CUR_MAX说明了多字节字符中字节的最大数量。MB_LEN_MAX定义在limits.h中，给出了任意支持地区的最大值。MB_CUR_MAX定义在stdlib.h中，给出了当前地区的最大值。\n\n### 宽字符\n\n宽字符是一种其值表示字符的整数，所有宽字符要求相同的字节数。\n\n宽字符具有wchar_t类型。\n\n一个宽字符常量可以写成：`L'a'`\n\n一个宽字符字符串可以写成：`L\"abc\"`\n\n即在普通字符常量前用字母L作为前缀。\n\n!!!note \"my note\"\n    注意，在使用宽字符前，需要设置好本地环境，比如，要使用简体中文的宽字符，那么要先执行 `setlocale(LC_ALL, \"zh_CN.UTF-8\")` ，这样才能正确地解析宽字符。\n\n### 多字节字符函数\n\n```c\n#include <stdlib.h>\nint mblen(const char *s, size_t n);\nint mbtowc(wchar_t *pwc, const char *s, size_t n);\nint wctomb(char *s, wchar_t wc);\n```\n\n### 多字节字符串函数\n\n```c\n#include <stdlib.h>\nsize_t mbstowcs(wchar_t *dest, const char *src, size_t n);\nsize_t wcstombs(char *dest, const wchar_t *src, size_t n);\n```\n\n## 三字符序列\n\n见书本简要介绍。\n\n!!!note\n    这就是一种字符替换方式，由于某些国家不支持C语言的标准的字符书写方式。"
  },
  {
    "path": "notes/CProgramming/ch26 其他库函数.md",
    "content": "# 其他库函数\n\n---\n\n## stdarg.h 可变长度实参\n\n```c\nvoid va_start(va_list ap, last);\ntype va_arg(va_list ap, type);\nvoid va_end(va_list ap);\n```\n\nstdarg.h提供了一种工具可以让我们自行编写的函数具有可变长的参数列表（varying number of arguments of varying types）。stdarg.h定义了一种va_list类型和三种宏，名为va_start, va_arg, va_end, 可以把这些宏看成是带有上述原型的函数。\n\n书中使用了此例进行讲解：\n\n```c\nint max_int(int n, ...)    // n must be at least 1\n{\n    va_list ap;\n    int i, current, largest;\n\n    va_start(ap, n);\n    largest = va_arg(ap, int);\n\n    for (i = 1; i < n; ++i) {\n        current = va_arg(ap, int);\n        if (current > largest)\n            largest = current;\n    }\n\n    va_end(ap);\n    return largest;\n}\n```\n\n函数的第一个实参n说明了跟随其后的其他参数的数量。\n\n形参列表中的...符号表示可变数量的参数。带有可变参数的函数必须至少有一个“正常的”形参，在最后一个正常的参数后边始终会有省略号出现在参数列表的末尾。\n\n`va_list ap`声明了一个变量，使得函数可以访问到可变参数。\n\n`va_start(ap, n)`指出了实参列表中可变长度开始的位置。\n\n`va_arg(ap, int)`把获取当前的可变参数，然后自动前进到下一个可变参数处。int说明希望此参数是int类型的。\n\n函数返回前，使用语句`va_end(ap)`进行清扫。\n\n### 调用带有可变实参列表的函数\n\n调用带有可变实参列表的函数是一个固有的风险提议。\n\n这里主要的难点就是带有可变实参列表的函数很难确定传递过来的参数的数量或类型。所以必须把这个信息传递给函数，并且函数假设知道了这个信息。上述max_int函数依靠第一个实参来指明跟随其后的其他参数的数量，并且它还设定参数是int类型的。\n\n另一个问题就是不得不处理NULL作为参数的情况，具体见书本。\n\n### v...printf类函数\n\n```c\nint vprintf(const char *format, va_list ap);\nint vfprintf(FILE *stream, const char *format, va_list ap);\nint vsprintf(char *str, const char *format, va_list ap);\n```\n\n不同于printf等函数，v...printf类函数具有固定数量的实参，每个v...printf类函数的最后一个实参都是一个va_list型值。这个类型的值意味着此函数可以由带有可变实参列表的函数调用。\n\n实际上，v...printf类函数主要用于编写“包装”函数。包装函数接收可变数量的实参，并稍后把这些参数传递给v...printf类函数（通过va_list）。\n\n这种包装函数的核心内容是：\n\n- va_start(ap, arg)\n\n- 把ap传递给v...printf\n\n- va_end(ap)\n\n## stdlib.h 通用的实用工具\n\n### 字符串转换函数\n\n```c\nint atoi(const char *nptr);\nlong long atoll(const char *nptr);\n\nlong int strtol(const char *nptr, char **endptr, int base);\nlong long int strtoll(const char *nptr, char **endptr, int base);\n```\n\n### 伪随机生成函数\n\n伪随机数的生成方法是：\n\n- 先设置一个随机种子（srand）\n\n- 调用rand函数根据随机种子生成一个伪随机数\n\n如果每次程序运行的随机种子都一样，那么rand出来的数就会一样。因此通常采用当前时间戳作为随机种子（但如果两次启动间隔不足一秒，时间戳也是一样滴）。\n\n```c\nvoid srand(unsigned int seed);\nint rand(void);\n```\n\n### 与环境的通信\n\n与外部通信的标准库函数可以：\n\n- 为操作系统返回一个程序结束的状态码\n\n- 获取环境变量\n\n- 执行操作系统的命令\n\n**返回状态码**\n\n在main中执行return语句，即返回了一个状态码给操作系统；或者在程序的任意处执行exit函数，也可以终止程序并返回一个状态码给操作系统。\n\nexit是正常性质的结束程序，可以清理程序打开的资源。\n\natexit函数还可以注册一个函数，在程序正常结束前，执行这个注册函数。可以注册多个atexit函数，调用顺序和注册顺序一致。\n\n```c\nvoid exit(int status);\nint atexit(void (*function)(void));\n```\n\n**获取环境变量**\n\n环境变量是一组存放到静态存储区的字符串，描述了操作系统的环境，比如PATH。使用getenv就可以获取它的值。\n\n```c\nchar *getenv(const char *name);\n```\n\n**执行命令**\n\n主要是通过system函数执行一个外部的命令。system函数返回该命令的终止状态码。\n\n```c\nint system(const char *command);\n```\n\n### 搜索和排序工具\n\n用于搜索的工具是：bsearch（实现为二分查找），用于排序的工具是：qsort（实现为快速排序）。\n\n```c\nvoid qsort(void *base, size_t nmemb, size_t size,\n                  int(*compar)(const void *, const void *));\n\nvoid *bsearch(const void *key, const void *base,\n                     size_t nmemb, size_t size,\n                     int (*compar)(const void *, const void *));\n```\n\n### 整数算术运算函数\n\n**abs求绝对值**\n\n函数原型：\n```c\nint abs(int j);\n```\n\n**div求除法运算结果**\n\n函数原型：\n```c\ndiv_t div(int numerator, int denominator);\n```\n\n结果是第一个实参除以第二个实参。结果是一个div_t类型，它包含了商和余数，定义如下：\n```c\ntypedef struct\n{\n    int quot;    /* Quotient.  */\n    int rem;     /* Remainder. */\n} div_t;\n```\n\n但第二个实参一定不能为0，不然就会出现段错误。因此判断除数是否合法的责任就交给了程序员。\n\n## time.h 日期和时间\n\n标准库提供了三种表示时间的类型：\n\n0. clock_t：按照“时钟滴答”进行测量的时间值\n\n0. time_t：日历时间（时间戳），由于这个类型在不同平台下定义不同（unsigned int or long），因此输出的时候应当做一个强制转换。\n\n0. struct tm：分解时间，一种适合人类理解的时间格式\n\nclock_t和time_t是支持算术运算的，但是它们具体是整型还是浮点型并没有被C标准说明。但struct tm的类型定义很清楚：\n\n|成员|说明|\n|-|-|\n|tm_sec|分后的秒，[0, 61]，允许两个额外的闰秒|\n|tm_min|时后面的分，[0, 59]|\n|tm_hour|午夜后的时，0到23|\n|tm_mday|月份中的第几天，[1,31]|\n|tm_mon|一月份以后的月，[0,11]|\n|tm_year|从1900年开始的年|\n|tm_wday|星期日以后的天，[0,6]|\n|tm_yday|一月一日后的天，[0,365]|\n|tm_isdst|夏令时标记，夏令时有效为正数，否则为0，如果未知，可为-1|\n\n**时钟滴答**\n\n```c\nclock_t clock(void);\n```\n\nclock函数返回处理器的时间（时钟滴答），即程序开始运行到执行到此的消耗的时间。但它的单位不是秒，为了将它转换成秒，可以给它除以宏CLOCK_PER_SEC。\n\nclock_t不能表示日期，只是善于表示时间区间（两个clock_t相减获得比较精准的时间差）。\n\n```c\n(clock() - start_clock) / (double)CLOCK_PER_SEC\n```\n\n加double强制转换的理由是，标准C没有指明宏CLOCK_PER_SEC的类型，也没有说明clock_t的类型，所以必须用强制转换明确一下类型。\n\n**日历时间**\n\n```c\ntime_t time(time_t *t);\ndouble difftime(time_t time1, time_t time0);\n```\n\ntime用来获取时间戳（UNIX从1970年为纪元），difftime获取两个时间戳的间隔，但这种计算间隔的方式没有用clock_t计算精准。\n\n**分解时间**\n\n```c\ntime_t mktime(struct tm *tm);\nstruct tm *localtime(const time_t *timep);\nchar *asctime(const struct tm *tm);\n```\n\n- mktime将分解时间转换成日历时间（时间戳）。但它有一个很好得到地方，除了转换成日历时间，它还会先修正分解时间，如果分解时间中的某些值不正确的话。修正的规则就是“进位”，把溢出的时间补给高位的时间。比如tm_mday超过了31，那么tm_mon就会增加至少1。可以利用这一个修正的规则来计算未来的日期。见代码案例。\n\n- localtime根据日历时间，获得本地的分解时间\n\n- asctime获取分解时间的字符串格式，末尾还会有一个换行符\n\n**时间转换函数**\n\n有 ctime strftime 等。"
  },
  {
    "path": "notes/CProgramming/cheatsheet/惯用法.md",
    "content": "# 惯用法\n\n---\n\n**判定变量是否落在某个数值范围内（p48）**\n\n```c\nif (0 <= i && i < n) { /* ... */ }\n\n/* 相反的情况（i在范围之外） */\nif (i < 0 || i >= n) { /* ... */ }\n```\n\n**用 while 语句建立无限循环（p63）**\n\n```c\nwhile (1) { /* ... */ }\n```\n\n**for 语句的惯用法（p66）**\n\n```c\n/* 从0向上加到n-1 */\nfor (i = 0; i < n; ++i) { /* ... */ }\n\n/* 从1向上加到n */\nfor (i = 1; i <= n; ++i) { /* ... */ }\n\n/* 从n-1向下减到0 */\nfor (i = n-1; i >= 0; --i) { /* ... */ }\n\n/* 从n向下减到1 */\nfor (i = n; i > 0; --i) { /* ... */ }\n```\n\n**用 for 语句建立无限循环（p67）**\n\n```c\nfor (;;) { /* ... */ }\n```\n\n**读入并忽略掉所有当前输入行中的其余字符（p87）**\n\n```c\nwhile (getchar() != '\\n'); /* skips rest of line */\n```\n\n**跳过无限数量的空格字符（p87）**\n\n```c\nwhile ((ch = getchar()) == ' '); /* skips blanks */\n```\n\n**数组和 for 循环（p99）**\n\n```c\nint a[N];\nfor (i = 0; i < N; i++)\n\t\ta[i] = 0;\t\t\t/* clear a */\n\nfor (i = 0; i < N; i++)\n\tscanf(\"%d\", &a[i]);\t\t/* reads data into a */\n\nfor (i = 0; i < N; i++)\n\tsum += a[i];\t\t\t/* sums the elements of a */\n```\n\n**数组名作为指针（p160）**\n\n```c\nfor (p = a; p < a + N; p++)\n\tsum += *p;\n```\n\n**定义字符串变量**\n\n```c\n#define STR_LEN 80\nchar str[STR_LEN + 1];\n```\n\n**令s指向字符串尾部**\n\n```c\nwhile (*s) s++; /* s最终指向了空字符 */\nwhile (*s++);\t/* s最终指向了空字符后一个字符 */\n```\n\n**字符串拷贝**\n\n```c\nwhile ((*p++ = *s2++) != '\\0');\n```\n\n**设置位**\n\n```c\ni |= 1 << j;\t\t/* set bit j */\n```\n\n**将位清0**\n\n```c\ni &= ~(1 << j);\t\t/* clear bit j */\n```\n\n**测试位**\n\n```c\nif (i & 1 << j) ... /* test bit j */\n```\n\n**scanf**\n\n```c\nwhile (scanf(\"%d\", &i) == 1) {\n\t/* ... */\n}\n```\n\n**getc**\n\n```c\nwhile ((ch = getc(fp)) != eof) {\n\t/* ... */\n}\n```"
  },
  {
    "path": "notes/CppPrimer/ch01 开始.md",
    "content": "# 第一章 开始\n\n学习一门新的程序设计语言的最好方法就是练习编写程序。\n\n---\n\n## 编写一个简单的C++程序\n\n每个C++程序都包含一个或多个函数（function），其中一个必须命名为**main**。操作系统通过调用main来运行C++程序。\n\n下面是一个简单的main函数，它什么都不做，只是返回给操作系统一个值：\n\n```c++\nint main()\n{\n    return 0;\n}\n```\n\nmain函数的返回类型必须为int。int类型是一种**内置类型**（built-in type），即语言自身定义的类型。\n\n函数体是一个以左**花括号**（curly brace）开始，以右花括号结束的语句块（block of statements）。\n\nreturn语句结束函数的执行。main的返回值被用来指示状态。返回值0表明成功，非0的返回值的含义由系统定义，通常用来指出错误类型。\n\n**重要概念：类型**\n\n一种类型不仅定义了数据元素的内容，还定义了这类数据上可以进行的运算。程序所处理的数据都保存在变量中，而每个变量都拥有自己的类型。\n\n### 编译、运行程序\n\n编写好程序后，我们就需要编译它，这依赖于操作系统和编译器。\n\n**程序源文件命名约定**\n\n程序文件通常被称为源文件（source file）。它以一个后缀为结尾，告诉系统这个文件是一个C++程序，比如.cpp。\n\n## 初识输入输出\n\nC++包含了一个全面的**标准库**（standard library）来提供IO机制（以及很多其他设施）。\n\n**iostream**库包含两个基础类型istream和ostream，分别表示输入流和输出流。一个流就是一个字符序列，是从IO设备读出或写入IO设备的。\n\n**标准输入输出对象**\n\n标准库定义了4个IO对象。为了处理输入，使用名为cin的istream类型的对象。这个对象被称为**标准输入**（standard input）。对于输出，使用名为cout的ostream类型的对象。这个对象被称为**标准输出**（standard output）。另外还有其他两个ostream对象，名为cerr和clog，cerr通常用来输出警告和错误消息，因此被称为**标准错误**（standard error）。clog用来输出程序运行时的一般性消息。\n\n系统通常将程序所运行的窗口与这些对象关联起来。因此，当读取cin，数据将从程序正在运行的窗口读入，当向cout写入数据时，将会写到同一个窗口。\n\n!!!note\n\tclog关联到标准错误，默认情况下，写到clog的数据是被缓冲的。写到cerr的数据是不缓冲的。\n\n**一个使用IO库的程序**\n\n```c++\n#include <iostream>\nint main()\n{\n        std::cout << \"Enter two numbers:\" << std::endl;\n        int v1 = 0, v2 = 0;\n        std::cin >> v1 >> v2;\n        std::cout << \"The sum of \" << v1 << \" and \" << v2 << \" is \"\n                  << v1 + v2 << std::endl;\n        return 0;\n}\n```\n\n`#include <iostream>`告诉编译器我们想要使用iostream库。尖括号中的名字指出了一个**头文件**（header）。每个使用标准库设施的程序都必须包含相关的头文件。#include指令和头文件的名字必须写在同一行中。#include指令一般出现在所有函数之外，源文件的开始位置。\n\n**向流写入数据**\n\n```c++\nstd::cout << \"Enter two numbers\" << std::endl;\n```\n\n这条语句执行了一个**表达式**（expression）。在C++中，一个表达式产生一个计算结果，它由一个或多个运算对象和（通常是）一个运算符组成。这条语句中的表达式使用了**输出运算符**（<<）。\n\n`<<`运算符接受两个运算对象：左侧必须是一个ostream对象；右侧是要打印的值。此运算符将给定的值写到给定的ostream对象中。计算结果是左侧的ostream对象。\n\n\"Enter two numbers\"是一个**字符串字面值常量**（string literal），它是用一对双引号包围的字符序列。\n\nendl是一个被称为**操纵符**（manipulator）的特殊值。写入endl的效果是结束当前行，并将与设备关联的缓冲区（buffer）中的内容刷到设备中。\n\n!!!note\n\t一个表达式接一个分号就是一条语句。\n\n**使用标准库中的名字**\n\n前缀std::指出名字cout和endl是定义在名为**std**的**命名空间**（namespace）中的。\n\n命名空间可以帮助我们避免不经意的名字定义冲突。标准库定义的所有名字都在命名空间std中。\n\n**从流读取数据**\n\n首先定义两个名为v1和v2的**变量**（variable）来保存输入：\n\n```c++\nint v1 = 0, v2 = 0;\n```\n\n这两个变量被定义为int类型，并**初始化**（initialize）为0。初始化一个变量，就是在变量创建的同时为它赋予一个值。\n\n```c++\nstd::cin >> v1 >> v2;\n```\n\n这条语句读入输入数据。**输入运算符**（>>）接受一个istream作为其左侧运算对象，接受一个对象作为其右侧运算对象。它从给定的istream读入数据，并存入给定对象中。输入运算符返回其左侧运算对象作为计算结果。\n\n## 注释简介\n\n注释（comments）通常用于概述算法，确定变量的用途，或者解释晦涩难懂的代码段。编译器会忽略掉注释，因此注释对程序的行为和性能不会有任何影响。\n\n**C++中注释的种类**\n\nC++中有两种注释：单行注释和界定符注释。\n\n**注释界定符不能嵌套**\n\n界定符对形式的注释是以`/*`开始，以`*/`结束的。因此，一个注释不能嵌套在另一个注释之内。\n\n如果在调试期间要注释掉包含界定符对形式注释的代码，最好的方式是用单行注释方式注释掉代码段的每一行。\n\n```c++\n// /*\n//  * comments\n//  */\n```\n\n## 控制流\n\n语句一般是顺序执行的，语句块的第一条语句首先执行，然后是第二条，以此类推。但程序设计语言提供了多种不同的控制语句，允许我们写出更为复杂的执行路径。\n\n### while语句\n\nwhile语句反复执行一段代码，直到给定条件为假为止。\n\nwhile语句的形式为：\n\n```c++\nwhile (condition)\n    statement\n```\n\nwhile语句的执行过程是交替地检测condition条件和执行关联的语句statement，直至condition为假时停止。所谓**条件**就是一个产生真或假的结果的表达式。\n\nstatement可以是语句块，也叫循环体。所谓语句块（block），就是用花括号包围的语句序列。语句块也是语句的一种，在任何要求使用语句的地方都可以使用语句块。\n\n### for语句\n\n每个for语句都包含两部分：循环头和循环体。循环头控制循环体的执行次数，它由三部分组成：一个初始化语句（init-statement）、一个循环条件（condition）以及一个表达式（expression）。\n\n比如：\n\n```c++\nfor (int val = 1; val <= 10; ++val)\n    sum += val;\n```\n\nfor循环的总体执行流程：\n\n0. 创建变量val，将其初始化为1，它仅在循环内部存在。\n\n0. 检测val是否小于等于10。如果检测成功，执行循环体。若失败，退出循环。\n\n0. 将val的值增加1。\n\n0. 重复第二个步骤。\n\n### 读取数量不定的输入数据\n\n使用这样的方法连续读入数据，直到遇到文件尾（Linux下，输入CTRL+D）：\n\n```c++\nwhile (std::cin >> value)\n    statement\n```\n\n输入运算符返回其左侧对象，因此，此循环条件检测的是std::cin。\n\n当使用一个istream对象作为条件时，其效果是检测流的状态。如果流是有效的，那么检测成功。当遇到文件结束符（end-of-file），或遇到一个无效输入时，istream对象的状态会变为无效。\n\n### if语句\n\nif也对一个条件求值，书本里有一个完整的例子。\n\n语法大致如此：\n\n```c++\nif (condition) {\n    statements\n} else {\n    statements\n}\n```\n\n## 类简介\n\n在C++中，通过定义一个**类**（class）来定义自己的数据结构。一个类定义了一个类型，以及与其关联的一组操作。\n\n类似使用标准库设施，我们也需要使用头文件来自己的类。习惯上，头文件根据类名来命名，使用.h作为头文件的后缀。标准库的头文件通常不带后缀。\n\n### 初识成员函数\n\n**什么是成员函数？**\n\n有这样的检测条件：\n\n```c++\nitem1.isbn() == item2.isbn()\n```\n\n调用名为isbn的成员函数（member function）。成员函数是定义为类的一部分的函数。\n\n使用**点运算符（.）**来表达我们需要“名为item1的对象的isbn成员”。点运算符只能用于类类型的对象。其左侧运算对象必须是一个类类型的对象，右侧运算对象必须是该类型的一个成员名，运算结果为右侧运算对象指定的成员。\n\n我们使用**调用运算符（()）**来调用一个函数，它是一对圆括号，里面放实参列表（可能为空）。"
  },
  {
    "path": "notes/CppPrimer/ch02 变量和基本类型.md",
    "content": "# 第二章 变量和基本类型\n\n---\n\n## 基本内置类型\n\nC++定义了一套包括**算术类型**（arithmetic type）和**空类型**（void）在内的基本数据类型。其中算术类型包含了字符、整型数、布尔值和浮点数。空类型不对应具体的值，仅用于一些特殊的场合，如可作为函数的返回值。\n\n### 算术类型\n\n算术类型分为整型和浮点型。\n\n算术类型的尺寸（所占比特数）在不同机器上有所差别。C++标准规定了尺寸的最小值，编译器允许赋予这些类型更大的尺寸。某一类型所占的比特数不同，它所能表示的数据范围也不一样。\n\n算术类型尺寸表格见书本p30。\n\n布尔类型（bool）的取值是true或者false。\n\n浮点型可表示单精度、双精度和扩展精度值。一般来说，类型float和double分别有7和16个有效位，float以1个字（32比特）来表示，double以2个字（64比特）来表示。\n\n**带符号类型和无符号类型**\n\n除去布尔类型和扩展的字符型之外，其它整型可以划分为**带符号的**（signed）和**无符号的**（unsigned）两种。带符号类型可以表示正数、负数或0，无符号类型仅能表示大于等于0的值。\n\n与其他整型不同，字符型被分成了三种：char、signed char和unsigned char。类型char会表现为上述形式中的一种，具体是哪种由编译器决定。\n\n!!!note\n\t在GCC上测试，char是有符号的。\n\n选择类型的一些经验准则：\n\n- 当明确知晓数值不可能为负时，选用无符号类型。\n\n- 使用int执行整数运算，如果数值超过了int的表示范围，选用long long。\n\n- 在算术表达式中不要使用char或bool。因为char在不同机器上的表现方式不一样。\n\n- 执行浮点数运算选用double。因为double精度更高，且运算代价和float没有相差无几。\n\n### 类型转换\n\n对象的类型定义了对象能包含的数据和能参与的运算，其中一种运算被大多数类型支持，就是将对象从一种给定的类型**转换**（convert）为另一种相关类型。\n\n当在程序的某处我们使用了一种类型而其实对象应该取另一种类型时，程序会自动进行类型转换。\n\n类型所能表示的值的范围决定了转换的过程：\n\n- 当把非bool的算术值赋给bool类型时，初始值为0则结果为false，否则结果为true。\n\n- 当把bool值赋给非bool类型时，初始值为false则结果为0，初始值为true则结果为1。\n\n- 当把一个浮点数赋给整数类型时，结果值将仅保留浮点数中小数点之前的部分。\n\n- 当把一个整数值赋给浮点类型时，小数部分记为0。如果该整数所占的空间超过了浮点类型的容量，精度有可能损失。\n\n- 当我们赋给无符号类型一个超出它表示范围的值时，结果是初始值对无符号类型表示数值总数取模后的余数。\n\n- 当我们赋给带符号类型一个超出它表示范围的值时，结果是**未定义的**（undefined）。此时，程序可能继续工作、可能崩溃、也可能生成垃圾数据。\n\n[==测试代码==](https://github.com/demon90s/CppStudy/blob/master/CppPrimer/labs/test_type_convert.cpp)\n\n!!!warning\n\t切勿混用带符号类型和无符号类型（比如拿有符号数和无符号数做比较）。如果表达式里既有带符号类型又有无符号类型，当带符号类型取值为负时会出现异常结果，这是因为带符号数会自动地转换成无符号数。\n\n### 字面值常量\n\n一个形如42的值被称作字面值常量（literal），这样的值一望便知。每个字面值常量都对应一种数据类型，字面值常量的形式和值决定了它的数据类型。\n\n**整型和浮点型字面值**\n\n我们可以将整型字面值写作十进制数、八进制数或十六进制数的形式。以0开头的整数代表八进制数，以0x或0X开头的代表十六进制数。例如，我们能用下面的任意一种形式来表示数值20：\n\n`20 /* 十进制 */`    `024 /* 八进制 */`    `0x14 /* 十六进制 */`\n\n十进制字面值的类型是int、long和long long中尺寸最小的那个。八进制和十六进制字面值的类型是能容纳其数值的int、unsigned int、long、unsigned long、long long和unsigned long long中尺寸最小者。\n\n浮点型字面值表现为一个小数或以科学计数法表示的指数，其指数部分用E或e标识：\n\n3.14159 3.14159E0    0.    0e0    .001\n\n默认的，浮点型字面值是一个double。\n\n!!!note\n\tGCC下，像20这样的十进制整数字面值，类型是int\n\n**字符和字符串字面值**\n\n由单引号括起来的一个字符称为char型字面值，双引号括起来的零个或多个字符则构成字符串字面值。\n\n'a'    // 字符字面值\n\n\"a\"    // 字符串字面值\n\n字符串字面值的类型实际上是由常量字符构成的数组（array）。编译器在每个字符串的结尾处添加一个空字符（'\\0'），因此，字符串字面值的实际长度要比它的内容多1。\n\n**转义序列**\n\n有两类字符程序员不能直接使用：一类是**不可打印**（nonprintable）字符，如退格或其他控制字符；另一类是有特殊含义的字符，如引号、问号、反斜线。这些情况下需要用到**转义序列**（escape sequence），转义序列以反斜线作为开始。\n\n转义序列见书本p36。\n\n**指定字面值的类型**\n\n通过添加一些前缀和后缀，可以改变整型、浮点型和字符型字面值的默认类型。\n\n这些前缀和后缀见书本p37。\n\n**布尔字面值和指针字面值**\n\ntrue和false是布尔类型的字面值。\n\n## 变量\n\n变量提供一个具名、可供程序操作的存储空间。C++中的每个变量都有其数据类型，数据类型决定着变量所占内存空间的大小和布局方式、该空间能存储的值的范围，以及变量能参与的运算。\n\n### 变量定义\n\n变量定义的基本形式是：首先是**类型说明符**（type specifier），随后紧跟由一个或多个变量名组成的列表，其中变量名以逗号分割，最后以分号结束。列表中每个变量名的类型由类型说明符指定，定义时还可以为一个或多个变量赋初值：\n\n```\nint sum = 0, value = 0;\n```\n\n**术语：何为对象？**\n\n通常情况下，对象是指一块能存储数据并具有某种类型的内存空间。\n\n**初始值**\n\n当对象在创建时获得了一个特定的值，我们说这个对象被**初始化**（initialized）了。\n\n!!!warning\n\t初始化不是赋值，初始化的含义是创建变量时赋予一个初始值，而赋值的含义是把对象当前值擦除，而以一个新值替代。\n\n**列表初始化**\n\n要想定义一个名为units_sold的int变量并初始化为0，以下4条语句都可以做到这一点：\n\n```\nint units_sold = 0;\nint units_sold = {0};\nint units_sold(0);\nint units_sold{0};\n```\n\n作为C++11新标准的一部分，用花括号来初始化变量得到了全面应用。这种初始化的形式被称为**列表初始化**（list initialization）。\n\n当用于内置类型的变量时，这种初始化形式有一个重要特点，如果我们使用列表初始化且初始值存在丢失信息的风险，则编译器将报错。\n\n> my note: 在我的GCC 4.8.5下面，这种情况会报warning。\n\n**默认初始化**\n\n如果定义变量时没有指定初值，则变量被**默认初始化**（default initialized）。\n\n如果是内置类型的变量未被显示初始化，它的值由定义的位置决定。定义于函数体之外的变量被初始化为0。定义在函数体内部的内置类型变量将**不被初始化**（uninitialized）。一个未被初始化的内置类型变量的值是未定义的。\n\n每个类各自决定其初始化对象的方式。\n\n!!!tip\n\t建议初始化每一个内置类型的变量。\n\n### 变量声明和定义的关系\n\n为了允许把程序拆分成多个逻辑部分来编写，C++语言支持分离式编译（separate compilation）机制，该机制允许将程序分割为若干个文件，每个文件可被独立编译。\n\n为了支持分离式编译，C++语言将声明和定义区分开来。**声明**（declaration）使得名字为程序所知，一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而**定义**负责创建与名字关联的实体。\n\n如果想声明一个变量而非定义它，就在变量名前添加关键字extern，而且不要显示初始化变量：\n\n```\nextern int i;    // 声明i而非定义i\nint j;           // 声明并定义j\n```\n\n任何包含了显式初始化的声明即成为定义。\n\n!!!NOTE\n\t变量能且只能被定义一次，但是可以被多次声明。\n\n### 标识符\n\nC++的标识符（identifier）由字母、数字和下画线组成，其中必须以字母或下画线开头。标识符的长度没有限制，但是对大小写敏感。\n\nC++语言保留了一些名字供语言本身使用，这些名字不能被用作标识符。见书本p43。\n\n同时，C++也为标准库保留了一些名字。用户自定义的标识符中不能连续出现两个下画线，也不能以下画线紧连大写字母开头。此外，定义在函数体外的标识符不能以下画线开头。\n\n### 名字的作用域\n\n不论是在程序的什么位置，使用到的每个名字都会指向一个特定的实体：变量、函数、类型等。\n\n**作用域**（scope）是程序的一部分，在其中名字有其特定的含义。C++语言中大多数作用域都以花括号分隔。\n\n同一个名字在不同的作用域中可能指向不同的实体。名字的有效区域始于名字的声明语句，以声明语句所在的作用域末端为结束。\n\n名字main定义于所有花括号之外，它和其他大多数定义在函数体之外的名字一样拥有**全局作用域**（global scope）。一旦声明后，全局作用域内的名字在整个程序的范围内都可使用。\n\n> my note: 在花括号内定义的变量拥有块作用域。for语句内定义的名字，只能在for语句之内访问。\n\n**嵌套的作用域**\n\n作用域能彼此包含，被包含的作用域称为**内层作用域**（inner scope），包含着别的作用域的作用域称为**外层作用域**（outer scope）。\n\n作用域中一旦声明了某个名字，它所嵌套着的所有作用域中都能访问该名字。同时，允许在内层作用域中重新定义外层作用域已有的名字。\n\n## 复合类型\n\n复合类型（compound type）是指基于其他类型定义的类型。\n\n### 引用\n\n**引用**（reference）为对象起了另外一个名字。通过将声明符写成&d的形式来定义引用类型，其中d是声明的变量名：\n\n```\nint ival = 1024;\nint &refVal = ival;    // refVal指向ival（是ival的另一个名字）\n```\n\n定义引用时，程序把引用和它的初始值绑定（bind）在一起，而不是将初始值拷贝给引用。一旦初始化完成，引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象，因此引用必须初始化。\n\n!!!note\n\t引用并非对象，相反的，它只是为一个已经存在的对象所起的另外一个名字。\n\n引用只能绑定在对象上，而不能与字面值或某个表达式的计算结果绑定在一起。\n\n### 指针\n\n**指针**（pointer）是“指向”另外一种类型的复合类型。指针本身就是一个对象，允许对指针赋值和拷贝。和其他内置类型一样，在块作用域内定义的指针如果没有被初始化，也将拥有一个不确定的值。\n\n定义指针类型的方法将声明符写成\\*d的形式，其中d是变量名。如果在一条语句中定义了几个指针变量，每个变量的前面都必须有符号\\*。\n\n```\nint *p1, *p2;    // p1和p2都是指向int型对象的指针\n```\n\n**获取对象的地址**\n\n指针存放某个对象的地址，要想获取该地址，需要使用取地址符（操作符&）：\n\n```\nint ival = 42;\nint *p = &ival; // p存放变量ival的地址，或者说p是指向变量ival的指针\n```\n\n因为引用不是对象，没有实际地址，所以不能定义指向引用的指针。\n\n通常，所有指针的类型都要和它所指的对象严格匹配。\n\n```\ndouble dval;\ndouble *pd = &dval;    // 正确，初始值是double型对象的地址\n\nint *pi = pd;          // 错误，指针pi的类型和pd的类型不匹配\n```\n\n**指针值**\n\n指针的值（即地址）应属下列4种状态之一：\n\n0. 指向一个对象。\n\n0. 指向紧邻对象所占用空间的下一个位置。\n\n0. 空指针，意味着指针没有指向任何对象。\n\n0. 无效指针，也就是上述情况之外的其他值。\n\n**利用指针访问对象**\n\n如果指针指向了一个对象，则允许使用**解引用符**（操作符\\*）来访问对象：\n\n```\nint ival = 42;\nint *p = &ival;\ncout << *p;    // 由符号*得到指针p所指的对象，输出42\n```\n\n!!!warning\n\t解引用操作仅适用于那些确实指向了某个对象的有效指针。否则其行为是未定义的。\n\n**空指针**\n\n**空指针**（null pointer）不指向任何对象。以下列出几个生成空指针的方法：\n\n```\nint *p1 = nullptr;    // 等价于 int *p1 = 0\nint *p2 = 0;\n// 需要首先#include <cstdlib>\nint *p3 = NULL;\n```\n\n**void\\*指针**\n\nvoid\\*是一种特殊的指针类型，可用于存放任意对象的地址。\n\n利用void\\*指针能做的事儿比较有限：拿它和别的指针比较、作为函数的输入或输出，或者赋给另外一个void\\*指针。不能直接操作void\\*所指的对象。\n\n### 理解复合类型的声明\n\n变量的定义包括一个基本数据类型（base type）和一组声明符。在同一条定义语句中，虽然基本数据类型只有一个，但是声明符的形式却可以不同：\n\n```\n// i是一个int型的数，p是一个指向int型的指针，r是一个int型的引用\nint i = 1024, *p = &i, &r = i;\n```\n\n!!!tip\n\t很多程序员容易迷惑于基本数据类型和类型修饰符之间的关系，其实后者不过是声明符的一部分罢了。\n\n\t面对一条比较复杂的指针或引用的声明语句时，从右向左阅读有助于弄清楚它的真实含义。\n\n## const限定符\n\n有时我们希望定义这样一种变量，它的值不能被改变。为了满足这一要求，可以用关键字**const**对变量的类型加以限定：\n\n```\nconst int bufSize = 512;\n```\n\n这样就把bufSize定义成了一个常量。任何试图为bufSize赋值的行为都将引发错误。\n\n因为const对象一旦创建后其值就不能再改变，所以const对象必须初始化。\n\n**默认情况下，const对象仅在文件内有效**\n\n当以编译时初始化的方式定义一个const对象时，就如对bufSize的定义一样：\n\n```\nconst int bufSize = 512;\n```\n\n编译器将在编译过程中把用到该变量的地方都替换成对应的值。\n\n为了支持这一用法，同时避免对同一变量的重复定义，默认情况下，const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时，其实等同于在不同文件中分别定义了独立的变量。\n\n!!!note\n\t如果想在多个文件之间共享const对象，必须在变量的定义之前添加extern关键字。\n\n### const的引用\n\n把引用绑定到const对象上，称之为对**常量的引用**（reference to const）。\n\n```\nconst int a = 1024;\nconst int &r = a;\n```\n\n对常量的引用不能修改它所绑定的对象的值。\n\n**初始化和对const的引用**\n\n引用的类型必须与其所引用对象的类型一致，但是有两个例外。第一种例外情况就是在初始化常量引用时允许用任意表达式作为初始值，只要该表达式的结果能转换成引用的类型即可。\n\n```\nint i = 42;\nconst int &r1 = i;    // 允许将const int&绑定到一个普通int对象上\nconst int &r2 = 42;   // 正确：r1是一个常量引用\nconst int &r3 = r1 * 2; // 正确：r3是一个常量引用\nint &r4 = r1 * 2;     // 错误：r4是一个普通的非常量引用\n```\n\n**对const的引用可能引用一个并非const的对象**\n\n必须认识到，常量引用仅对引用可参与的操作做出了限定，对引用的对象本身是不是一个常量未作限定。\n\n### 指针和const\n\n**指向常量的指针**（pointer to const）不能改变其所指对象的值。想要存放常量对象的地址，只能使用指向常量的指针：\n\n```\nconst double pi = 3.14;\nconst double *p = &pi;\n```\n\n指针的类型必须与其所指对象的类型一致，但是有两个例外。第一种例外情况是允许令一个指向常量的指针指向一个非常量对象：\n\n```\ndouble dval = 3.14;\ncptr = &dval;    // 正确，但是不能通过cptr改变dval的值\n```\n\n**const指针**\n\n指针是一个对象，可以把它定义成const的，叫**常量指针**（const pointer）。把\\*放在const关键字之前用以说明指针是一个常量：\n\n```\nint n = 0;\nint *const p = &n;\n```\n\n### 顶层const\n\n顶层const（top-level const）表示指针本身是一个常量。\n\n底层const（low-level const）表示指针所指对象是一个常量。\n\n更一般的，顶层const可以表示任意的对象是常量，这一点对任何数据类型都适用。底层const则与指针和引用等复合类型的基本类型部分有关。比较特殊的是，指针类型既可以是顶层const也可以是底层const。\n\n```\nint i = 0;\nint *const p1 = &i;    // 不能改变p1的值，p1是一个顶层const\nconst int ci = 42；    // 不能改变ci的值，ci是一个顶层const\nconst int *p2 = &ci;   // 允许改变p2的值，p2是一个底层const\nconst int *const p3 = p2; // 靠右的const是顶层const，靠左的是底层const\nconst int &r = ci;     // 用于声明引用的const都是底层const\n```\n\n### constexpr和常量表达式\n\n**常量表达式**（const expression）是指值不会改变，且在编译过程中就能得到计算结果的表达式。\n\n这些都是常量表达式：\n\n- 字面值\n\n- 用常量表达式初始化的const对象\n\n**constexpr变量**\n\n用const定义的变量并不一定是常量表达式，因此要换一种方法定义常量表达式。\n\nC++11标准提供了constexpr关键字，让编译器验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量，而且必须用常量表达式初始化：\n\n```\nconstexpr int mf = 20;    // 20是常量表达式\nconstexpr int limit = mf + 1;    // mf + 1是常量表达式\nconstexpr int sz = size();    // 只有当size是一个constexpr函数时，才是一条正确的声明语句\n```\n\n**字面值类型**\n\n在编译时就能得到计算，类型比较简单，值也显而易见的类型，叫**字面值类型**（literal type）。\n\n算术类型，引用，指针都属于字面值类型。\n\n尽管指针和引用都能定义成constexpr，但它们的初始值却受到严格限制。一个constexpr指针的初始值必须是nullptr或者0，或者是存储于某个固定地址中的对象。\n\n**指针和constexpr**\n\n如果在constexpr声明中定义了一个指针，那么它只对指针有效，与指针所指的对象无关：\n\n```\nconst int *p = nullptr;      // p是一个指向常量的指针\nconstexpr int *p2 = nullptr; // p2是一个常量指针\n```\n\n与其他常量指针类似，constexpr指针既可以指向常量也可以指向一个非常量：\n\n```\nint j = 0;\nconstexpr int i = 42;\n// i和j都必须定义在函数体外\nconstexpr const int *p = &i;    // p是常量指针，指向整型常量i\nconstexpr int *p1 = &j;         // p1是常量指针，指向整数j\n```\n\n## 处理类型\n\n随着程序越来越复杂，程序中用到的类型也越来越复杂，这种复杂性体现在两个方面。一是一些类难于“拼写”。二是有时候根本搞不清到底需要什么类型，需要从程序的上下文中寻求帮助。\n\n### 类型别名\n\n**类型别名**（type alias）是一个名字，它是某种类型的同义词。使用类型别名可以让复杂的名字变得简单，有助于程序员清楚地知道使用该类型的真实目的。\n\n传统的定义类型别名的方法是使用typedef:\n\n```\ntypedef double wages;    // wages是double的同义词\n```\n\n新标准规定了一种新的方法，使用**别名声明**（alias declaration）来定义类型的别名：\n\n```\nusing SI = Sales_item;    // SI是Sales_item的别名\n```\n\n这种方法使用关键字using作为别名声明的开始，其后紧跟别名和等号，其作用是把等号左侧的名字规定成等号右侧类型的别名。\n\n### auto类型说明符\n\nC++11新标准引入了auto类型说明符，用它让编译器替我们去分析表达式所属的类型。\n\n显然，auto定义的变量必须有初始值：\n\n```\n// 由val1和val2相加的结果可以推断出item的类型\nauto item = val1 + val2;\n```\n\n使用auto也能在一条语句中声明多个变量。因为一条声明语句只能有一个基本数据类型，所以该语句中所有变量的初始基本数据类型都必须一样：\n\n```\nauto i = 0, *p = &i; // 正确，i是整数，p是整型指针\nauto sz = 0, pi = 3.14; // 错误，sz和pi的类型不一致\n```\n\n**复合类型、常量和auto**\n\n编译器推断出来的auto类型有时候和初始值的类型并不完全一样，编译器会适当地改变结果类型使其更符合初始化规则。\n\n首先，当引用被用作初始值时，真正参与初始化的其实是引用对象的值。此时编译器以引用对象的类型作为auto的类型：\n\n```\nint i = 0, &r = i;\nauto a = r; // a是一个int\n```\n\n其次，auto一般会忽略掉顶层const，同时底层const则会保留下来：\n\n```\nconst int ci = i, &cr = ci;\nauto b = ci; // b是一个int，ci的顶层const被忽略\nauto c = cr; // c是一个int，cr是ci的别名，其顶层const被忽略\nauto d = &i; // d是一个int*\nauto e = &ci; // e是一个const int*，对常量对象取地址是一种底层const\n```\n\n如果希望推断出来的auto类型是一个顶层const，需要明确指出:\n\n```\nconst auto f = ci; // ci推演成int，f是const int\n```\n\n还可以将引用的类型设为auto，此时原来的初始化规则仍然适用：\n\n```\nauto &q = ci; // q是一个整型常量引用，绑定到ci\nauto &h = 42; // 错误：不能为非常量引用绑定到字面值\nconst auto &j = 42; // 正确，可以为常量引用绑定字面值\n```\n\n### decltype类型指示符\n\nC++新标准引入了第二种类型说明符**decltype**，它的作用是选择并返回操作数的数据类型。在此过程中，编译器分析表达式并得到它的类型，却不实际计算表达式的值：\n\n```\ndecltype(f()) sum = x; // sum的类型就是函数f的返回类型\n```\n\n编译器并不实际调用f，而是使用当调用发生时f的返回值的类型作为sum的类型。\n\n如果decltype使用的表达式是一个变量，则decltype返回该变量的类型（包括顶层const和引用在内）：\n\n```\nconst int ci = 0, &cj = ci;\ndecltype(ci) x = 0; // x的类型是const int\ndecltype(cj) y = x; // y的类型是const int&, y绑定到x\n```\n\n**decltype和引用**\n\n如果decltype使用的表达式不是一个变量，则decltype返回表达式结果对应的类型。如果表达式向decltype返回一个引用类型，一般来说，意味着该表达式的结果对象能作为一条赋值语句的左值：\n\n```\n// decltype的结果可以是引用类型\nint i = 42, *p = &i, &r = i;\ndecltype(r + 0) b; // 正确，加法的结果是int，因此b是一个int\ndecltype(*p) c; // 错误，c是int&，必须初始化\n```\n\n如果表达式的内容是解引用操作，则decltype将得到引用类型。\n\n有一种情况需要特别注意：对于decltype所用的表达式来说，如果变量名加上了一对括号，编译器就会把它当成一个表达式。变量是一种可以作为赋值语句左值的特殊表达式，所以这样的decltype就会得到引用类型：\n\n```\n// decltype的表达式如果是加上了括号的变量，结果是引用\ndecltype((i)) d; // 错误，d是int&，必须初始化\ndecltype(i) e; // 正确，e是一个int。\n```\n\n!!!warning\n\t切记，decltype((variable))的结果永远是引用，而decltype(variable)结果只有当variable本身就是一个引用时才是引用。\n\n## 自定义数据结构\n\n从最基本的层面理解，数据结构是把一组相关的数据元素组织起来然后使用它们的策略和方法。\n\nC++语言允许用户以类的形式自定义数据类型，而库类型string、istream、ostream等也都是以类的形式定义的，就像第1章的Sales_item类型一样。\n\n### 定义Sales_data类型\n\nSales_data初步定义如下：\n\n```\nstruct Sales_data {\n    std::string bookNo;\n    unsigned units_sold = 0;\n    double revenue = 0.0;\n};\n```\n\n我们的类以关键字struct开始，紧跟着类名和类体（其中类体部分可为空）。类体由花括号包围形成了一个新的作用域。\n\n类体右侧的表示结束的花括号后必须写一个分号。\n\n**类数据成员**\n\n类体定义类的成员，我们的类只有数据成员（data member）。类的数据成员定义了类的对象的具体内容，每个对象都有自己的一份数据成员拷贝。\n\nC++11新标准规定，可以为数据成员提供一个**类内初始值**（in-class initializer）。创建对象时，类内初始值将用于初始化数据成员。没有初始值的成员将被默认初始化。\n\n### 编写自己的头文件\n\n为了确保各个文件中类的定义一致，类通常被定义在头文件中，而且类所在头文件的名字应与类的名字一样。\n\n头文件通常包含那些只能被定义一次的实体，如类、const和constexpr变量等。\n\n**预处理器概述**\n\n确保头文件多次包含仍能安全工作的常用技术是**预处理器**（preprocessor）。\n\nC++程序使用**头文件保护符**（header guard）来避免头文件重复包含。\n\n```\n#ifndef SALES_DATA_H\n#define SALES_DATA_H\n\nstruct Sales_data {\n    std::string bookNo;\n    unsigned units_sold = 0;\n    double revenue = 0.0;\n};\n\n#endif\n```"
  },
  {
    "path": "notes/CppPrimer/ch03 字符串、向量和数组.md",
    "content": "# 第三章 字符串、向量和数组\n\n---\n\n## 命名空间的using声明\n\n有如下形式：\n\n```c++\nusing namespace::name;\n```\n\n使用完using声明（using declaration）后，就可以省略掉名字前的前缀了（如std::）。\n\n```c++\n#include <iostream>\n\nusing std::cin;\n\nint main()\n{\n    int i;\n    cin >> i;\n    cout << i;    // 错误，没有对应的using声明，必须使用完整的名字\n    return 0;\n}\n```\n\n**头文件不应包含using声明**\n\n这是因为头文件会被其它文件引用，从而使其它文件也使用了using声明，有可能造成命名冲突。\n\n## 标准库类型string\n\nstring表示可变长的字符序列。使用string类型需要包含string头文件。\n\n### 定义和初始化string对象\n\n如何初始化类的对象是由类本身决定的。一个类可以定义很多种初始化对象的方式，只不过这些方式之间必须有所区别。\n\n**初始化string对象的方式：**\n\n```c++\nstring s1;           // 默认初始化，s1是一个空串\nstring s2(s1);       // s2是s1的副本\nstring s2 = s1;      // 等价于s2(s1)\nstring s3(\"value\");  // s3是字面值\"value\"的副本，不包括最后的空字符\nstring s3 = \"value\"; // 等价于s3(\"value\")\nstring s4(n, 'c');   // 初始化为由n个字符c组成的串\n```\n\n**直接初始化和拷贝初始化**\n\n如果使用等号（=）初始化一个变量，实际上执行的是**拷贝初始化（copy initialization）**，编译器把等号右侧的对象初始值拷贝到新创建的对象中去。如果不适用等号，则执行的是**直接初始化**（direct initialization）。\n\n```c++\nstring s5 = \"hiya\";  // 拷贝初始化\nstring s6(\"hiya\");   // 直接初始化\nstring s7(10, 'c');  // 直接初始化\n```\n\n### string对象上的操作\n\n如：\n\n- `os << s`, 将s写入输出流os当中，返回os\n\n- `is >> s`，从输入流中读取字符串赋值给s，字符串以空白分隔，返回is\n\n- `getline(is, s)`，从输入流中读取一行赋值给s，返回is\n\n- `s.empty()`，如果s为空，返回true\n\n- `s.size()`，返回s中的字符数，与s.length()等价\n\n- `s[n]`，返回s中第n个字符的引用\n\n- `s1 + s2`，返回s1和s2连接后的结果\n\n- `s1 = s2`，用s2的副本代替s1\n\n- `s1 == s2`，`s1 != s2`，如果s1和s2完全一样，则相等\n\n- <, <=, >, >=，顺序比较字符大小，完全一致再比较长度\n\ngetline函数会读取换行符，但不会把它存入字符串中。getline返回输入流。\n\n**string::size_type类型**\n\nsize函数返回的是一个string::size_type类型的值。这是一个无符号的整数。\n\nstring类和大多数标准库类型都定义了几种配套类型，它们体现的是标准库与机器无关的特性。\n\n**字面值和string对象相加**\n\n标准库允许把字符字面值和字符串字面值转换成string对象，所以在需要string对象的地方就可以使用这两种字面值来替代。\n\n```c++\nstring s1 = s1 + \"hi\";\nstring s2 = s1 + ',';\n```\n\n### 处理string对象中的字符\n\n字符处理函数的头文件是cctype，它和C的ctype.c一样，只不过前者是C++的命名规范。在书本p82有cctype头文件中的函数说明。\n\n**处理每个字符？使用基于范围的for语句**\n\n如果想对string对象中的每个字符做点儿什么操作，目前最好的办法是使用C++11新标准提供的一种语句：**范围for**（range for）语句。这种语句遍历序列中的每个元素并对序列中的每个值执行某种操作，其语法格式是：\n\n```c++\nfor (declaration : expression)\n    statement\n```\n\n其中，expression部分是一个对象，用于表示一个序列。declaration部分负责定义一个变量，该变量将被用于访问序列中的基础元素。每次迭代，declaration部分的变量会被初始化为expression部分的下一个元素值。\n\n如：\n\n```c++\nfor (auto c : str)\n    // do something\n```\n\n这里c是str中字符的副本，若要改变str中的字符，需要用引用：\n\n```c++\nfor (auto &c : str)\n    // do something\n```\n\n**使用下标运算符**\n\n`[ ]`符号叫做下标运算符，范围是[0, s.size())，越界的结果是UB（undefined behavior，未定义行为）。\n\n## 标准库类型vector\n\nvector是对象的集合，也叫容器（container）。集合中的每个对象都有一个索引，索引用于访问对象。\n\nvector是一个类模板。模板是为编译器提供的一份生成类或函数的说明。\n\nvector是模板而非类型，由vector生成的类型必须包含元素的类型，如：\n\n```c++\nvector<int> v;\n```\n\nvector中存放的是对象，而引用不是对象，故不能存储引用。\n\n### 定义和初始化vector对象\n\nvector模板控制着初始化向量的方法。\n\n定义vector对象的方法有：\n\n- `vector<T> v1`，默认初始化，v1是一个空的vector\n\n- `vector<T> v2(v1)`，v2中包含v1所有元素的副本\n\n- `vector<T> v2 = v1`，等价于v2(v1)\n\n- `vector<T> v3(n, val)`，v3包含了n个重复的元素，每个元素的值都是val\n\n- `vector<T> v4(n)`，v4包含了n个执行了值初始化的对象\n\n- `vector<T> v5{a,b,c...}`，v5里包含了用a,b,c...初始化的元素\n\n- `vector<T> v5 = {a,b,c...}`，等价于`vector<T> v5{a,b,c...}`\n\n**值初始化**\n\n值初始化（value initialize），是指如果是内置类型，则初始值为0；如果是类类型，执行类默认初始化。\n\n`vector<T>(n)`中，所有元素将执行值初始化。\n\n### 向vector中添加元素\n\npush_back函数把一个元素压入vector对象的尾端。\n\nvector的对象能高效地增长，因此更常见的情况是：创建一个空vector，然后在运行时再利用vector的成员函数push_back向其中添加元素。\n\n**一定不能在遍历vector的时候改变vector对象的大小。**\n\n!!!note \"关键概念：vector对象能高效增长\"\n\tC++标准要求vector应该能在运行时高效快速地添加元素。因此既然vector对象能高效地增长，那么在定义vector对象的时候设定其大小就没有什么必要了，只有一种例外，即当所有元素的值都一样。一旦元素的值有所不同，更有效的办法是先定义一个空的vector对象，再在运行时向其中添加具体值。\n\n### 其它vector操作\n\n如（很多和string类似）：\n\n- `v.empty()`，如果v不含有任何元素，返回true\n\n- `v.size()`，返回v中的元素个数\n\n- `v[n]`，返回v中第n个位置上元素的引用\n\n- `v1 = v2`，v2中的元素将拷贝替换v1的\n\n- `v1 = {a,b,c...}`，列表中的元素将拷贝替换v1中的\n\n- `v1 == v2`, `v1 != v2`，元素数量相同，对应位置的元素也相等，则相等\n\n- <,<=,>,>=，比首个相异元素的大小，如都一样，比长度，即字典顺序\n\nsize返回的类型由vector定义的size_type类型。\n\n```c++\nvector<int>::size_type    // 正确\nvector::size_type         // 错误\n```\n\n只有当元素的值可比较时，vector对象才能被比较。\n\n只能对确已存在的元素执行下标操作。\n\n## 迭代器介绍\n\n使用迭代器（iterator）是一种通用的访问容器中元素的方法。\n\n迭代器有有效和无效之分。有效的迭代器指向某个元素，或指向尾元素的下一个位置，其它情况都属于无效。\n\n### 使用迭代器\n\n有迭代器的类型同时拥有返回迭代器的成员。\n\n标准库容器都拥有名为begin和end的成员（函数）。其中begin成员负责返回指向第一个元素的迭代器。\n\nend成员负责返回指向容器“尾元素的下一个位置”的迭代器。叫**尾后迭代器（off-the-end iterator）**。\n\n如果容器为空，begin和end都返回尾后迭代器。即：`v.begin() == v.end()`\n\n如：\n\n```c++\nauto b = v.begin();\nauto e = v.end();\n```\n\n**迭代器运算符**\n\n标准容器迭代器的运算符：\n\n- `*iter`，返回迭代器所指对象的引用（解引用）\n\n- `iter->mem`，解引用iter，并获取其成员mem，等价于`(*iter).mem`\n\n- `++iter`，令iter指示容器中的下一个元素\n\n- `--iter`，令iter指示容器中的上一个元素\n\n- `iter1 == iter2`，如果两个迭代器指示的是同一个元素，或者它们都是尾后迭代器，则相等，反之不相等\n\n迭代器指示一个元素时，才可对其解引用。对尾后迭代器或者无效迭代器解引用的结果是UB。\n\n**迭代器类型**\n\n标准库类型使用iterator和const_iterator来表示迭代器类型。\n\n如：\n\n```c++\nvector<int>::iterator it1;\nvector<int>::const_iterator it2;\n```\n\nit1能读写元素，而it2只能读。\n\n认定某个类型是迭代器类型当且仅当它支持一套操作，这套操作使得我们能访问容器的元素，或者从某个元素移动到另外一个元素。\n\n**begin和end运算符**\n\nbegin和end返回的具体类型由对象是否是常量决定。如果对象是常量，返回const_iterator，否则返回iterator。\n\n为了专门得到const_iterator类型的迭代器，C++11中可以使用cbegin和cend：\n\n```c++\nauto it = v.cbegin();\n```\n\n**箭头运算符**\n\n即`->`，它把解引用和成员访问两个操作结合在一起。即：\n\n`(*iter).mem`等价于`iter->mem`。\n\n**某些对vector对象的操作会使迭代器失效**\n\n任何一种可能改变vector对象容量的操作，比如push_back，都会使该vector对象的迭代器失效。\n\n### 迭代器运算\n\n递增运算令迭代器每次移动一个元素，所有的标准库容器的迭代器都支持递增运算，也支持==和!=运算。\n\nstring和vector的迭代器提供了额外的运算符，有：\n\n- `iter + n`，新迭代器向前移动若干个元素，它指向容器的一个元素，或是尾后迭代器\n\n- `iter - n`，新迭代器向后移动若干个元素，它指向容器的一个元素，或是尾后迭代器\n\n- `iter1 - iter2`，得到迭代器之间的距离，参与计算的迭代器必须是指向同一个容器中的元素或者尾元素的下一个位置\n\n- `>,>=,<,<=`，比较迭代器所处的位置，前面的小于后面的，参与计算的迭代器必须是指向同一个容器中的元素或者尾元素的下一个位置  \n\n**迭代器的算数运算**\n\n迭代器相减的结果的类型是difference_type，表示右侧的迭代器要移动多少个位置才能到达左侧的。\n\ndifference_type是一个带符号的整数，string和vector都定义了这个类型。\n\n## 数组\n\n数组是存放相同类型的对象的容器，这些对象是匿名的。\n\n数组的大小确定不变。\n\n数组是一种内置类型。\n\n### 定义和初始化内置数组\n\n数组是一种复合类型，其声明形如`a[N]`。N叫维度，说明了数组中元素的个数，必须大于0，且必须是一个常量表达式，即其值在编译期间已知。\n\n默认情况下，数组的元素执行默认初始化，这意味着在函数块内定义的执行默认初始化的含内置类型元素的数组，其元素的值未定义。\n\n定义数组的时候必须指定数组的类型，不允许用auto关键字由初始值的列表推断类型。数组的元素应为对象，所以不存在存储引用的数组。\n\n**显式初始化数组元素**\n\n即列表初始化，此时可以忽略数组的维度，维度由编译器推断出来。\n\n如：\n\n```c++\nint a1[10] = {0}; // 剩下的元素执行值初始化，即为0\nint a2[] = {1, 2, 3};\n```\n\n**字符数组的特殊性**\n\n可以用字符串字面值对此类数组进行初始化。如：\n\n```c++\nchar s[] = \"hello\";\n```\n\n这样初始化的数组包含结尾的空字符。\n\n**不允许拷贝和赋值**\n\n这样的操作是非法的：\n\n```c++\nint a1[] = {1, 2, 3};\nint a2[] = a1; // 非法\n```\n\n**理解复杂的数组声明**\n\n定义一个指针数组：\n\n```c++\nint* a[10] = {};\n```\n\n定义一个指向数组的指针：\n\n```c++\nint (*ptr)[10] = &a;\n```\n\n定义一个绑定到数组的引用：\n\n```c++\nint (&a_ref)[10] = a;\n```\n\n默认情况下，类型修饰符从右向左依次绑定。不过理解数组的复杂声明时，应该由内向外理解。即从数组的名字开始按照由内向外的顺序阅读。\n\n### 访问数组元素\n\n使用数组下标的时候，通常将其定义为size_t类型，这是一种机器相关的无符号类型。定义在cstddef头文件中，是C标准库stddef.h头文件的C++版本。\n\n可以使用范围for语句来遍历数组。\n\n```c++\nfor (auto i : arr)\n    cout << i << \" \";\ncout << endl;\n```\n\n**检查下标的值**\n\n与string和vector一样，数组的下标是否在合理范围之内由程序员负责检查。\n\n!!!warning\n\t大多数常见的安全问题都源于缓冲区溢出错误。当数组或其他类似数据结构的下标越界并试图访问非法内存区域时，就会产生此类错误。\n\n### 指针和数组\n\n在很多用到数组名字的地方，编译器都会自动地将其替换为一个**指向数组首元素的指针**。\n\n**decltype**\n\n下面得到一个数组类型：\n\n```c++\nint a1[10] = {};\ndecltype(a1) a2;\n```\n\n**auto**\n\n下面得到一个整型指针：\n\n```c++\nint a1[10] = {};\nauto a2(a1);\n```\n\n**指针也是迭代器**\n\nstring和vector的迭代器支持的运算，指针都支持。\n\n使用递增运算符既可以让指向数组元素的指针向前移动到下一个位置上。\n\n这样可以获取数组尾元素的下一个位置的指针：\n\n```c++\nint *end = &a[N];\n```\n\n不过C++11提供了begin和end函数，可以获取数组首元素的指针和尾后指针：\n\n```c++\nint a[10] = {};\nint *beg_p = begin(a);\nint *end_p = end(a);\n```\n\n这俩函数定义在头文件iterator.h中。\n\n尾后指针不能解引用和递增操作。\n\n和迭代器一样，两个指针相减的结果是它们之间的距离。参与运算的两个指针必须指向同一个数组当中的元素。\n\n**下标和指针**\n\n对数组执行下标运算其实是对指向数组元素的指针执行下标运算：\n\n```c++\nint i = ia[2];    // ia转换成指向数组首元素的指针\n                  // ia[2]得到(ia + 2)所指的元素\nint *p = ia;      // p指向ia的首元素\ni = *(p + 2);     // 等价于i = ia[2]\n```\n\n只要指针指向的是数组中的元素，都可以执行下标运算。\n\n内置的下标运算符可以处理负值，这和标准库类型的下标不一样（必须是无符号的）。\n\n### C风格字符串\n\nC风格的字符串即是字符串字面量，也是一种字符数组，并以空字符结尾（null terminated）。\n\np109列举了C语言标准库提供的一组函数，可以操作C风格字符串，他们定义在cstring头文件中。\n\n**c_str函数**\n\nstring可使用c_str函数返回其C风格的字符串，如：\n\n```c++\nstring s(\"hello\");\nconst char *c_s = s.c_str();\n```\n\n无法保证返回的C风格字符串一直有效，因此通常在返回后再把它拷贝到另一个地方。\n\n**使用数组初始化vector对象**\n\n如：\n\n```c++\nint a[] = {1, 2, 3};\nvector<int> vec(begin(a), end(a));\n```\n\n## 多维数组\n\n多维数组，实际上是数组的数组。\n\n如：`int a[3][4]`，可由内而外理解，a是一个含有3个元素的数组，每个元素又是一个含有4个元素的数组。\n\n对于二维数组，常把第一个维度看作行，第二个维度看作列。\n\n**多维数组的初始化**\n\n如：\n\n```c++\nint a[3][4] = {\n    {0, 1, 2, 3},\n    {4, 5, 6, 7},\n    {8, 9, 10, 11}\n};\n```\n\n列表初始化中未列出的元素执行值初始化。\n\n**多维数组的下标引用**\n\n如果表达式含有的下标运算符数量和维度一样多，该表达式的结果将是给定类型的元素；否则表达式的结果是内层数组。\n\n```c++\nint a[3][4] = {};\nint (&row)[4] = a[2]; // row绑定到a的第二个数组上\n```\n\n**使用范围for语句处理多维数组**\n\n如果是外层循环，控制变量将得到数组类型。\n\n除了最内层的循环外，其他所有循环控制变量都应该是引用类型（因为若不是引用，编译器会认为外层控制变量是指针类型，而无法遍历一个指针）。\n\n**指针和多维数组**\n\n当程序使用多维数组名字时，也会自动将其转换成指向数组首元素的指针。\n\n多维数组的首元素是一个内层数组，故使用多维数组名将得到一个指向内层数组的指针。\n\n即：\n\n```c++\nint a[2][3] = {};\nint (*p)[3] = a;\n```\n\n还可以使用auto或者begin来得到指向内层数组的指针。\n\n**类型别名简化多维数组的指针**\n\n可以这样定义一个数组类型：\n\n```c++\nusing int_arr = int[4]; // C++11\ntypedef int int_arr[4];\n```\n"
  },
  {
    "path": "notes/CppPrimer/ch04 表达式.md",
    "content": "# 第四章 表达式\n\n表达式由一个或多个**运算对象**（operand）组成，对表达式求值将得到一个**结果**（result）。字面值和变量是最简单的表达式，其结果就是字面值和变量的值。把一个**运算符**（operator）和一个或多个运算对象组合起来可以生成较复杂的表达式。\n\n---\n\n## 基础\n\n以下几个基础概念涉及大多数表达式。\n\n### 基本概念\n\nC++定义了一元运算符（unary operator）和二元运算符（binary operator）。作用于一个运算对象的运算符是一元运算符；作用于两个运算对象的运算符是二元运算符。函数调用是一种特殊的运算符，它对运算对象的数量没有限制。\n\n**重载运算符**\n\nC++语言定义了运算符作用于内置类型和复合类型的运算对象时所执行的操作。当运算符作用于类类型的运算对象时，用户可以自行定义其含义，称之为**重载运算符**（overloaded operator）。\n\n我们使用重载运算符时，其包括运算对象的类型和返回值的类型，都是由该运算符定义的；但是运算对象的个数、运算符的优先级和结合律都是无法改变的。\n\n**左值和右值**\n\nC++的表达式要不然是**右值**（rvalue），要不然就是**左值**（lvalue）。左值可以位于赋值语句的左侧，右值则不能。当一个对象被用作右值的时候，用的是对象的值（内容）；当对象被用作左值的时候，用的是对象的身份（内存中的位置）。\n\n求值结果的临时值是一种右值。\n\n```\nFoo &revVal(); // 函数调用返回一个左值\nFoo retVal();  // 函数调用返回一个右值\n```\n\n### 优先级与结合律\n\n**复合表达式**（compound expression）是指含有两个或多个运算符的表达式。求复合表达式的值需要首先将运算符和运算对象合理地组合在一起，优先级和结合律决定了运算对象组合的方式。\n\n一般来说，高优先级运算符的运算对象要比低优先级运算符的运算对象更为紧密地组织在一起。如果优先级相同，则其组合规则由结合律确定。\n\n表达式中的括号无视上述规则，程序员可以使用括号将表达式的某个局部括起来使得其得到优先运算。\n\np147页罗列出了全部的运算符和其优先级、结合律的信息。\n\n### 求值顺序\n\n优先级规定了运算对象的组合方式，但是没有说明运算对象按照什么顺序求值。在大多数情况下，不会明确求值的顺序。如：\n\n```\nint i = f1() * f2();\n```\n\nf1和f2一定在乘法之前被调用，但是谁先调用无从得知。\n\n对于那些没有指定执行顺序的运算符来说，如果表达式指向并修改了同一个对象，将会引发错误并产生未定义的行为。比如下面的表达式是未定义的：\n\n```\nint i = 0;\ncout << i << \" \" << ++i << endl;\n```\n\n编译器可能先求++i的值，再求i的值，所以结果无法预知。\n\n有4种运算符明确规定了运算对象的求值顺序：逻辑与（&&）、逻辑或（||）、条件运算符（?:）和逗号运算符（,）。\n\n**求值顺序、优先级、结合律**\n\n以下两条经验准则对书写复合表达式有益：\n\n0. 拿不准的时候最好用括号来强制让表达式的组合关系符合程序逻辑的要求。\n\n0. 如果改变了某个运算对象的值，在表达式的其他地方不要再使用这个运算对象。\n\n第二条有一个重要例外，当改变运算对象的子表达式本身就是另外一个子表达式的运算对象时该规则无效。例如，`*++iter`，递增运算符改变iter的值，iter（已经改变）的值又是解引用运算符的运算对象。此时，求值的顺序不会成为问题。\n\n> my note：上面这句话还是挺绕口的，反正这种类似写法的运算不会成为问题。\n\n## 算术运算符\n\n算术运算符中，一元运算符的优先级最高，然后是乘法和除法，优先级最低的是加法和减法。p124页列出的算术运算符满足左结合律，意味着当优先级相同时，按照从左向右的顺序进行组合。\n\n算术运算符的运算对象和求值结果都是右值。在表达式求值之前，小整数类型的运算对象被提升成较大的整数类型，所有运算对象最终会转换成同一类型。\n\n!!!warning \"提示：溢出和其他算术运算异常\"\n\t算术表达式有可能产生未定义的结果。一部分原因是数学性质本身：例如除数是0的情况；另外一部分则源于计算机的特点：例如溢出，当计算结果超出该类型所能表示的范围时就会产生溢出。\n\n整数相除的结果还是整数，也就是说，如果商含有小数部分，直接抛弃。\n\n运算符%俗称“取余”或“取模”运算符，负责计算两个整数相除所得的余数。参与取余运算的运算对象必须是整数类型。\n\n关于正负号的运算，除了-m导致溢出的特殊情况，其他时候，(-m)/n和m/(-n)都等于-(m/n)，m%(-n)等于m%n，(-m)%n等于-(m%n)。\n\n## 逻辑和关系运算符\n\n关系运算符作用于算术类型或指针类型，逻辑运算符作用于任意能转换成布尔值的类型。逻辑运算符和关系运算符的返回值都是布尔类型。值为0的运算对象表示假，否则表示真。这两种运算符的运算对象和求值结果都是右值。\n\n> my note: 本节的概念比较简单，故而不做更多笔记。更多细节可查阅书本p127。\n\n## 赋值运算符\n\n赋值运算符的左侧运算对象必须是一个可修改的左值。赋值运算的结果是它左侧的运算对象，并且是一个左值。如果赋值运算符的左右两个运算对象类型不同，则右侧运算对象将转换成左侧运算对象的类型。\n\nC++11新标准允许使用花括号括起来的初始值列表作为赋值语句的右侧运算对象：\n\n```\nint k = 0;\nk = {3.14};    // 错误，窄化转换\n```\n\n如果左侧运算对象是内置类型，那么初始值列表最多只能包含一个值，且该值即使转换的话其所占空间也不应该大于目标类型的空间。\n\n对于类类型来说，赋值运算的细节由类本身决定。\n\n无论左侧运算对象的类型是什么，初始值列表都可以为空。此时，编译器创建一个值初始化的临时量并将其赋给左侧运算对象。\n\n赋值运算满足右结合律。\n\n> my note: 还有一些概念比较简单，见书本p130。\n\n## 递增和递减运算符\n\n递增和递减运算符有两种形式：前置版本和后置版本。前置版本首先将运算对象加1（or减1），然后将改变后的对象作为求值结果。后置版本也会将对象加1（or减1），但是求值结果是运算对象改变之前那个值的副本。\n\n这两种运算符必须作用于左值运算对象。前置版本将对象本身作为左值返回，后置版本则将对象原始值的副本作为右值返回。\n\n!!!note \"建议：除非必须，否则不用后置版本\"\n\t这是因为后置版本将原始值存储下来以便于返回，而前置版本避免了这个工作。尤其是对于迭代器类型，这种额外的工作消耗巨大。\n\n**在一条语句中混用解引用和递增运算符**\n\n可以使用后置的递增运算符来控制循环输出一个vector对象内容：\n\n```\nauto pbeg = v.begin();\nwhile (pbeg != v.end)\n    cout << *pbeg++;\n```\n\n后置递增运算符的优先级高于解引用运算符，因此\\*pbeg++等价于\\*(pbeg++)。这是一种被广泛使用的、有效的写法。\n\n## 成员访问运算符\n\n点运算符和箭头运算符都可以用于访问成员，其中，点运算符获取类对象的一个成员；箭头运算符与点运算符有关，表达式`ptr->mem`等价于`(*ptr).mem`。\n\n因为解引用运算符的优先级低于点运算符，所以要向上面那样加上括号。\n\n箭头运算符作用于一个指针类型的运算对象，结果是一个左值。点运算符分成两种情况：如果成员所属的对象是左值，那么结果就是左值；反之，如果成员所属的对象是右值，那么结果是右值。\n\n> my note: 上面左值右值的讨论不是很能理清，但若是结合实际情况，应该不会很难懂。\n\n## 条件运算符\n\n条件运算符（?:）允许我们把简单的if-else逻辑嵌入到单个表达式当中，条件运算符按照如下形式使用：\n\ncond ? expr1:expr2\n\n条件运算符的执行过程是：首先求cond的值，如果为真则对expr求值并返回该值，否则对expr2求值并返回该值。举例：\n\n```\nstring finalgrade = (grade < 60) ? \"fail\" : \"pass\";\n```\n\n当条件运算符的两个表达式都是左值或者能转换成同一种左值类型时，运算的结果是左值；否则是右值。\n\n## 位运算符\n\n位运算符作用于整数类型的运算对象，并把运算对象看成是二进制位的集合。\n\n|运算符|功能|用法|\n|-|-|-|\n|~|位求反|~expr|\n|<<|左移|expr1 << expr2|\n|>>|右移|expr1 >> expr2|\n|&|位与|expr1 & expr2|\n|^|位异或|expr1 ^ expr2|\n|\\||位或|expr1 \\| expr2|\n\n一般来说，如果运算对象是“小整型”，则它的值会被自动提升成较大的整数类型。运算对象可以是带符号的，也可以是无符号的。如果运算对象是带符号的且它的值为负，那么位运算如何处理运算对象的“符号位”依赖于机器。\n\n!!!warning\n\t强烈建议将位运算符用于处理无符号类型。\n\n> my note: 一个提升例子就是，如果对char做位运算，它会先被提升为int。\n\n**移位运算符**\n\n<<和>>运算符的内置含义是对其运算对象执行基于二进制位的移动操作。首先令左侧运算对象的内容按照右侧运算对象的要求移动指定位数，然后将经过移动的（可能还进行了提升）左侧运算对象的拷贝作为求值结果。其中，右侧的运算对象一定不能为负，而且必须严格小于结果的位数，否则就会产生未定义的行为。移出边界之外的位数被舍弃掉了。\n\n左移运算符<<在右侧插入值为0的二进制位。右移运算符>>的行为依赖于左侧运算对象的类型：如果是无符号的，在左侧插入值为0的二进制位；如果是带符号的，在左侧插入符号位的副本或值为0的二进制位，如何选择视具体环境而定。\n\n## sizeof运算符\n\nsizeof运算符返回一条表达式或一个类型名字所占的字节数。sizeof运算符满足右结合律，其所得的值是一个size_t类型的常量表达式。它有两种形式：\n\n- sizeof(type)\n- sizeof expr\n\n> my note: 常量表达式意味着在编译期间就能得到计算。\n\n第二种形式中，sizeof返回的是表达式结果类型的大小。\n\nsizeof运算符的结果部分地依赖于其作用的类型：\n\n- 对char或者类型为char的表达式执行sizeof运算，结果得1。\n\n- 对引用类型执行sizeof运算得到被引用对象所占空间大小。\n\n- 对指针执行sizeof运算得到指针本身所占空间的大小。\n\n- 对解引用指针执行sizeof运算得到指针指向对象所占空间的大小，指针不需要有效。\n\n- 对数组执行sizeof运算得到整个数组所占空间大小。\n\n- 对string对象或vector执行sizeof运算只返回该类型固定部分的大小，不会计算对象中的元素占用了多少空间。\n\n## 逗号运算符\n\n**逗号运算符**（comma operator）含有两个运算对象，按照从左向右的顺序依次求值。\n\n它首先对左侧的表达式求值，然后将求值结果丢弃掉。逗号运算符真正的结果是右侧表达式的值。如果右侧运算对象是左值，那么结果也是左值。\n\n## 类型转换\n\n考虑下面这条表达式，它的目的是将ival初始化为6：\n\n```\nint ival = 3.541 + 3;\n```\n\nC++语言不会直接将两个不同类型的值相加，而是先根据类型转换规则设法将运算对象的类型统一后再求值。上述转换是自动进行的，它们被称作**隐式转换**（implicit conversion）。\n\n很多时候，如果表达式中既有整数类型的运算对象也有浮点数类型的运算对象，整型会转换成浮点型。\n\n**何时发生隐式转换**\n\n在下面这些情况下，编译器会自动地转换运算对象的类型：\n\n- 在大多数表达式中，比int类型小的整型值首先提升为较大的整数类型。\n\n- 在条件中，非布尔值转换成布尔类型。\n\n- 初始化过程中，初始值转换成变量的类型；在赋值语句中，右侧运算对象转换成左侧运算对象的类型。\n\n- 如果算术运算或关系运算对象有多种类型，需要转换成同一种类型。\n\n### 算术转换\n\n**算术转换**（arithmetic conversion）的含义是把一种算术类型转换成另外一种算术类型。\n\n**整数提升**（integral promotion）负责把小整数转换成较大的整数类型。\n\n> my note: 这里的概念很细，感觉没有必要一一记录，因为书中已经有了明晰的介绍，可参阅p142。\n\n### 其他隐式转换\n\n**数组转换成指针：**在大多数用到数组的表达式中，数组自动转换成指向数组首元素的指针。\n\n**指针的转换：**常量整数值0或者字面值nullptr能转换成任意指针类型；指向对象的指针可以转换成void\\*指针。\n\n**转换成布尔类型：**如果指针或算术类型的值为0，转换的结果是false，否则是true。\n\n**转换成常量：**允许将指向非常量类型的指针转换成底层const版本的指针，对于引用也是一样。\n\n```\nint i;\nconst int *p = &i;    // 非常量的地址转换成const的地址\n```\n\n**类类型定义的转换：**类类型能定义由编译器自动执行的转换。\n\n```\nstring s = \"a value\";    // 字符串字面量转换成string类型\nwhile (cin >> a);        // while的条件部分把cin转换成布尔值\n```\n\n### 显式转换\n\n**命名的强制类型转换**\n\n一个命名的强制类型转换有如下形式：\n\ncast-name<type>(expression);\n\n其中，type是转换的目标类型而expression是要转换的值。如果type是引用类型，则结果是左值。cast-name是static_cast, dynamic_cast, const_cast和reinterpret_cast中的一种。dynamic_cast支持运行时识别，直到19章（p730）才会讲解。\n\n**static_cast**\n\n任何具有明确定义的类型转换，只要不包含底层const，都可以使用static_cast。\n\n```\n// 进行强制类型转换以便执行浮点数除法\ndouble slope = static_cast<double>(j) / i;\n```\n\n当需要把一个较大的算术类型赋值给较小的类型时，static_cast非常有用。此时，强制类型转换表示，我们知道并且不在乎潜在的精度损失。\n\nstatic_cast对于编译器无法自动执行的类型转换也非常有用。例如，我们可以使用static_cast找回存在于void\\*的指针中的值：\n\n```\nvoid *p = &d;    // 正确，任何非常量对象的地址都能存入void*\n\n// 正确，将void*转换回初始的指针类型\ndouble *dp = static_cast<double*>(p);\n```\n\n必须确保转换后所得的类型就是指针所指的类型。类型一旦不符，将产生未定义的后果。\n\n**const_cast**\n\nconst_cast只能改变运算对象的底层const：\n\n```\nconst char *pc;\nchar *p = const_cast<char*>(pc);    // 正确，但是通过p写值是未定义的行为\n```\n\n如果对象本身是一个非常量，使用强制类型转换获得写权限是合法的行为。然而如果对象是一个常量，执行写操作就会产生未定义的后果。\n\nconst_cast常常用于有函数重载的上下文中，这将在第6章介绍（p208）。\n\n**reinterpret_cast**\n\nreinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。比如：\n\n```\nint *ip;\nchar *pc = reinterpret_cast<char*>(ip);\n```\n\n我们必须牢记pc所指的真实对象是一个int而非字符。\n\n> my note: reinterpret_cast非常危险，书中建议尽量避免使用。因为它本质上依赖于机器。且没有介绍应用场景。另外，书中也建议尽量避免其他的强制类型转换，强制类型转换应当在其合适的应用场景中使用。\n\n**旧式的强制类型转换**\n\n在早期版本的C++语言中，显式地进行强制类型转换包含两种形式：\n\n```\ntype(expr);   // 函数形式的强制类型转换\n(type)expr;   // C语言风格的强制类型转换\n```\n\n根据所涉及的类型不同，旧式的强制类型转换分别具有const_cast, static_cast或reinterpret_cast相似的行为。\n\n!!!warning\n\t与命名的强制类型转换相比，旧式的强制类型转换从表现形式上来说不那么清晰明了，容易被看漏，所以一旦转换过程出现问题，追踪起来也更加困难。\n"
  },
  {
    "path": "notes/CppPrimer/ch05 语句.md",
    "content": "# 第五章 语句\n\n---\n\n## 简单语句\n\nC++语言中的大多数语句都以分号结束，一个表达式，比如ival+5，末尾加上分号就变成了**表达式语句**(expression statement)，表达式语句的作用是执行表达式并丢弃掉求值结果：\n\n```c++\nival + 5;    // 无意义的表达式语句\ncout << ival;// 有意义的表达式语句\n```\n\n**空语句**\n\n最简单的语句是**空语句**（null statement），它只有一个分号：\n\n```c++\n; // 空语句\n```\n\n如果在程序的某个地方，语法上需要一条语句但是逻辑上不需要，此时应该使用空语句。\n\n**复合语句（块）**\n\n复合语句（compound statement）是指用花括号括起来的语句和声明的序列，复合语句也被称作**块**（block）。一个块就是一个作用域。\n\n如果在程序的某个地方，语法上需要一条语句，但是逻辑上需要多条语句，则应该使用复合语句。\n\n所谓空块，是指内部没有任何语句的一对花括号。空块的作用等价于空语句：\n\n```c++\nwhile (cin >> s && s != sought)\n{}  // 空块\n```\n\n## 语句作用域\n\n可以在if、switch、while和for语句的控制结构内定义变量。定义在控制结构当中的变量只在相应语句的内部可见，一旦语句结束，变量也就超出其作用范围了：\n\n```c++\nwhile (int i = get_num()) // 每次迭代时创建并初始化\n    cout << i << endl;\ni = 0;    // 错误，在循环外部无法访问\n```\n\n> my note: 如上述例子的写法，似乎没有什么实际意义。\n\n## 条件语句\n\nC++语言提供了两种按条件执行的语句。一种是if语句，它根据条件决定控制流；另一种是switch语句，它计算一个整型表达式的值，然后根据这个值从几条执行路径中选择一条。\n\n### if语句\n\nif语句的作用是：判断一个指定的条件是否为真，根据判断结果决定是否执行另外一条语句。\n\n> my note: 这里讲解的语法自己已经很熟悉了，故不做更多笔记。\n\n### switch语句\n\nswitch语句提供了一条便利的途径使得我们能够在若干固定选项中做出选择。\n\n---\n\n**my note**\n\nswitch语句我已经很熟悉了，故不做更多笔记。\n\n在switch内部定义变量，一个好的办法是：在case分支下，通过使用块把变量定义在块内，从而确保后面所有case标签都在变量的作用域之外。具体的原因见p163。\n\n```c++\ncase true:\n    {\n        // 正确：声明语句位于语句块内部\n        string file_name = get_file_name();\n    }\n    break;\n```\n\n> my note: 要记住的是，C++语言规定，不允许跨过变量的初始化语句直接跳转到该变量作用域内的另一个位置。基于此，在case分支后面定义并初始化一个变量很可能是不合法的，因为此分支可能被跳过，而另一个分支却访问了此变量。\n\n---\n\n## 迭代语句\n\n迭代语句通常称之为循环，它重复执行操作直到满足某个条件才停下来。while和for语句在执行循环体之前检查条件，do while语句先执行循环体，然后再检查条件。\n\n### while语句\n\n语法格式是：\n\nwhile (condition)\n    statement\n\n只要condition的求值结果为真就一直执行statement。如果condition第一次求值就是false，statement一次都不执行。\n\nwhile的条件部分可以是一个表达式或者是一个带初始化的变量声明。\n\n**使用while循环**\n\n当不确定到底要迭代多少次时，使用while循环比较合适。还有一种情况也应该使用while循环，这就是我们想在循环结束后访问循环控制变量。\n\n### 传统的for语句\n\nfor语句的语法形式是\n\nfor (init-statement: condition; expression)\n    statement\n\n> my note: 传统for语句我已经比较熟悉了，故不做更多笔记。\n\n### 范围for语句\n\nC++11新标准引入了一种更简单的for语句，这种语句可以遍历容器或其他序列的所有元素。**范围for语句**（range for statement）的语法形式是：\n\nfor (declaration : expression)\n    statement\n\nexpression必须是一个序列，比如用花括号括起来的初始值列表、数组、或者vector或string等类型的对象，这些类型的共同特点是拥有能返回迭代器的begin和end成员。\n\ndeclaration定义一个变量，序列中的每个元素都能转换成该变量的类型。\n\n每次迭代都会重新定义循环控制变量，并将其初始化成序列中的下一个值，之后才会执行statement。\n\n在范围for语句中，预存了end()的值。一旦在序列中添加（删除）元素，end函数的值就可能变得无效了。因此不能通过范围for语句增加vector对象的元素。\n\n### do while语句\n\ndo while语句和while语句非常相似，唯一的区别是，do while语句先执行循环体后检查条件。不管条件的值如何，我们都至少会执行一次循环。do while语句的语法形式如下：\n\n```c++\ndo\n    statement\nwhile (condition);\n```\n\ncondition使用的变量必须定义在循环体之外。\n\n> my note: 如果在condition中定义变量，是没有意义的，因为statement无法访问它，它在do while循环的外面也无法访问。\n\n## 跳转语句\n\n跳转语句中断当前的执行过程。C++语言提供了4种跳转语句：break, continue, goto和return。本章介绍前三种，return在第六章介绍（p199页）。\n\n### break语句\n\nbreak语句负责终止离它最近的while, do while, for或switch语句，并从这些语句之后的第一条语句开始执行。\n\n### continue语句\n\ncontinue语句终止最近的循环中的当前迭代并立即开始下一次迭代。continue语句只能出现在for, while和do while循环的内部。\n\n### goto语句\n\ngoto语句（goto statement）的作用是从goto语句无条件跳转到同一函数内的另一条语句。\n\n> my note: 书上建议不要使用goto语句。我认为它应该有其合适的应用场景，只不过对于初学者并不需要。\n\ngoto语句的语法形式是：\n\n```c++\ngoto lable:\n```\n\nlabel是用于标识一条语句的标示符。**带标签的语句**（labeled statement）是一种特殊的语句，在它之前有一个标示符以及一个冒号：\n\n```c++\nend: return; // 带标签的语句，可以作为goto的目标\n```\n\n标签标示符独立于变量或其他标示符的名字，因此，标签标示符可以和程序中其他实体的标示符使用同一个名字而不会相互干扰。\n\n和switch语句类似，goto语句也不能将程序的控制权从变量的作用域之外转移到作用域之内。\n\n```c++\n// ...\ngoto end:\n    int ix = 10; // 错误，goto语句绕过了一个带初始化的变量定义\n\nend:\n    // 错误，goto绕过了ix的声明\n    ix = 12;\n```\n\n跳回到变量定义之前意味着系统将销毁该变量，然后重新创建它：\n\n```c++\n// 向后跳过一个带初始化的变量定义是合法的\nbegin:\n    int sz = get_size();\n    if (sz <= 0)\n        goto begin;\n```\n\n## try语句块和异常处理\n\n异常是指存在于运行时的反常行为，这些行为超出了函数正常功能的范围。\n\n当程序的某部分检测到一个它无法处理的问题时，需要用到异常处理。此时，检测出问题的部分应该发出某种信号以表明程序遇到了故障，无法继续下去了，而信号的发出方无须知道故障将在何处得到解决。\n\n如果程序中含有可能引发异常的代码，那么通常也会有专门的代码处理问题。\n\n异常处理机制为程序中异常检测和异常处理这两部分的协作提供支持。在C++语言中，异常处理包括：\n\n- **throw 表达式（throw expression）**，异常检测部分使用throw表达式来表示它遇到了无法处理的问题。我们说throw**引发（raise）**了异常。\n\n- **try语句块（try block）**，异常处理部分使用try语句块处理异常。try语句块以关键字try开始，并以一个或多个**catch子句（catch clause）**结束。try语句块中代码抛出的异常通常会被某个catch子句处理。\n\n- 一套**异常类（exception class）**，用于在throw表达式和相关的catch子句之间传递异常的具体信息。\n\n> my note: 编写异常安全的代码非常困难，关于异常安全的概念见书本p175。另外，本书中会介绍一些比较常规的提升异常安全性的技术，但不会详细介绍编写异常处理的代码。\n\n### throw表达式\n\n抛出异常的一个例子是：\n\n```c++\nthrow runtime_error(\"Data must refer to same ISBN\");\n```\n\n该异常是类型runtime_error的对象。抛出异常将终止当前的函数，并把控制权转移给能处理该异常的代码。\n\n> my note: 书本上有例子的详细解读，见p173。\n\n### try语句块\n\ntry语句块的通用语法形式是：\n\n```c++\ntry {\n    program-statements\n} catch (exception-declaration) {\n    handler-statements\n} // ...\n```\n\n当选中了某个catch子句处理异常之后，执行与之对应的块。catch一旦完成，程序跳转到try语句块最后一个catch子句之后的那条语句继续执行。\n\ntry语句块内声明的变量在catch子句内无法访问。\n\n一个简要的例子（摘自书本）：\n\n```c++\nwhile (cin >> item1 >> item2) {\n    try {\n        // ... 可能抛出一个异常的代码\n    } catch (runtime_error err) {\n        cout << err.what() << \"\\nTry Again? Enter y or n\" << endl;\n        char c;\n        cin >> c;\n        if (!cin || c == 'n')\n            break; // 跳出while循环\n    }\n}\n```\n\n> my note: 见书本更详细的解读。\n\n**函数在寻找处理代码的过程中退出**\n\n当异常被抛出时，首先搜索抛出该异常的函数。如果没找到匹配的catch子句，终止该函数，并在调用该函数的函数中继续寻找。如果还是没找到匹配的catch子句，这个新的函数也被终止，继续搜索调用它的函数。以此类推，直到找到适当类型的catch子句为止。\n\n如果最终还是没能找到，程序转到名为terminate的标准库函数。该函数的行为与系统有关，一般情况下，执行该函数将导致程序非正常退出。\n\n### 标准异常\n\nC++标准库定义了一组类，用于报告标准库函数遇到的问题。这些异常类也可以用在用户编写的程序中。这些异常类见书本p176。\n\n异常类型只定义了一个名为what的成员函数，该函数没有任何参数，返回值是一个指向C风格字符串的异常说明。\n\n如果异常类型有一个字符串初始值，则what返回该字符串，否则返回的内容由编译器决定。\n"
  },
  {
    "path": "notes/CppPrimer/ch06 函数.md",
    "content": "# 第六章 函数\n\n函数是一个命名了的代码块，我们通过调用函数执行相应的代码。函数可以有0个或多个参数，而且（通常）会产生一个结果。\n\n---\n\n## 函数基础\n\n一个典型的函数（function）定义包括以下部分：返回类型（return type）、函数名字、由0个或多个形参（parameter）组成的列表以及函数体。\n\n我们通过**调用运算符（call operator）**来执行函数。调用运算符的形式是一对圆括号，它作用于一个表达式，该表达式是函数或者指向函数的指针；圆括号内是一个用逗号隔开的实参列表，我们用实参初始化函数的形参。调用表达式的类型就是函数的返回类型。\n\n函数的调用完成两项工作：一是用实参初始化函数对应的形参，二是将控制权转移给被调用函数。此时，**主调函数**（calling function）的执行暂时被中断，**被调函数**（called function）开始执行。\n\n当遇到一条return语句时函数结束执行过程。函数的返回值用于初始化调用表达式的结果。\n\n### 局部对象\n\n在C++语言中，名字有作用域，对象有**生命周期（lifetime）**，理解这两个概念非常重要：\n\n- 名字的作用域是程序文本的一部分，名字在其中可见。\n- 对象的生命周期是程序执行过程中该对象存在的一段时间。\n\n形参和函数体内部定义的变量统称为**局部变量（local variable）**。它们仅在函数的作用域内可见。\n\n在所有函数体之外定义的对象存在于程序的整个执行过程中。此类对象在程序启动时被创建，直到程序结束才会销毁。局部变量的生命周期依赖于定义的方式。\n\n**自动对象**\n\n对于普通局部变量对应的对象来说，当函数的控制路径经过变量定义语句时创建该对象，当到达定义所在的块末尾时销毁它。我们把只存在于块执行期间的对象称为**自动对象（automatic object）**。\n\n形参是一种自动对象。函数开始时为形参申请存储空间，函数一旦终止，形参就被销毁。\n\n对于局部变量对应的自动对象来说，如果变量定义本身含有初始值，就用这个初始值进行初始化；否则执行默认初始化（内置类型产生未定义的值）。\n\n**局部静态对象**\n\n**局部静态对象（local static object）**在程序的执行路径第一次经过对象定义语句时初始化，并且直到程序终止才被销毁。\n\n一个例子（其解释见书本p185）：\n\n```c++\n// 统计函数被调用了多少次\nsize_t count_calls()\n{\n    static size_t ctr = 0; // 调用结束后，这个值仍然有效\n    return ++ctr;\n}\n```\n\n**函数返回类型**\n\n大多数类型都能用作函数的返回类型。一种特殊的返回类型是void，它表示函数不返回任何类型。函数的返回类型不能是数组类型或函数类型，但可以是指向数组或函数的指针。\n\n### 函数声明\n\n函数的名字必须在使用之前声明。类似于变量，函数只能定义一次，但可以声明多次。\n\n函数的声明和定义唯一的区别是声明无须函数体，用一个分号替代即可。\n\n函数的三要素（返回类型、函数名、形参类型）描述了函数的接口，说明了调用该函数所需的全部信息。函数声明也称作**函数原型（function prototype）**。\n\n**在头文件中进行函数声明**\n\n我们建议函数在头文件中声明，在源文件中定义。\n\n这是因为如果把函数声明放在头文件中，就能确保同一函数的所有声明保持一致。而且一旦我们想改变函数的接口，只需改变一条声明即可。\n\n定义函数的源文件应该把含有函数声明的头文件包含进来，编译器负责验证函数的定义和声明是否匹配。\n\n### 分离式编译\n\nC++语言支持所谓的**分离式编译（separate compilation）**。分离式编译允许我们把程序分割到几个文件中去，每个文件独立编译。\n\n> my note: 这部分内容我比较熟悉了，详细解释见书本p186。\n\n## 参数传递\n\n!!!note\n\t形参初始化的机理与变量初始化一样。\n\n如果形参是引用类型，它将绑定到对应的实参上；否则，将实参的值拷贝后赋给形参。\n\n当形参是引用类型时，我们说它对应的实参被**引用传递**（passed by reference）或者函数被**传引用调用**（called by reference）。\n\n当实参的值被拷贝给形参时，形参和实参是两个相互独立的对象。我们说这样的实参被**值传递**（passed by value）或函数被**传值调用**（called by value）。\n\n### 传值参数\n\n当初始化一个非引用类型的变量时，初始值被拷贝给变量。此时，对变量的改动不会影响初始值。\n\n**指针形参**\n\n当执行指针拷贝操作时，拷贝的是指针的值。拷贝之后，两个指针是不同的指针。因为指针使我们可以间接地访问它所指的对象，所以通过指针可以修改它所指对象的值。\n\n!!!note\n\t熟悉C的程序员常常使用指针类型的形参访问函数外部的对象。在C++语言中，建议使用引用类型的形参代替指针。\n\n### 传引用参数\n\n对于引用的操作实际上是作用于引用所引的对象上，引用形参也是如此。通过使用引用形参，允许函数改变一个或多个实参的值。\n\n**使用引用避免拷贝**\n\n拷贝大的类类型对象或者容器对象比较低效。甚至有的类型根本就不支持拷贝操作。此时应该使用引用形参访问该类型的对象。\n\n如果函数无须改变引用形参的值，最好将其声明为常量引用。\n\n**使用引用形参返回额外信息**\n\n一个函数只能返回一个值，然而有时函数需要同时返回多个值，引用形参为我们一次返回多个结果提供了有效的途径。那就是通过引用形参并修改它（也就是修改了其引用的对象），从而作为结果传出。\n\n### const形参和实参\n\n当形参是const时，必须注意关于顶层const的讨论（p57）。\n\n当用实参初始化形参时会忽略形参的顶层const。即当形参有顶层const时，传递给它常量对象或者非常量对象都是可以的。\n\n忽略形参的顶层const可能产生意想不到的结果：\n\n```c++\nvoid fcn(const int i) {}\nvoid fcn(int i) {}    // 错误：重复定义\n```\n\n在C++中，允许我们定义若干具有相同名字的函数，不过前提是不同函数的形参列表有明显的区别。因为顶层const被忽略了，所以在上面的代码中传入两个fcn函数的参数可以完全一样（从而编译器不知道该调用哪一个）。\n\n**指针或引用形参与const**\n\n我们可以使用非常量初始化一个底层const，但是反过来不行（不能用一个常量初始化一个非底层const）；同时一个普通的引用必须用同类型的对象初始化。\n\n**尽量使用常量引用**\n\n把函数不会改变的形参定义成（普通的）引用是一种常见错误，这么做给函数的调用者一种误导，即函数可以修改它的实参的值。此外，使用引用而非常量引用也会极大地限制函数所能接受的实参类型（比如无法传入一个常量对象了）。\n\n比如下面这个例子将导致编译错误（p192）：\n\n```c++\n// 不良设计，第一个形参的类型应该是const string&\nstring::size_type find_char(string &s, char c, string::size_type &occurs);\n//...\nfind_char(\"Hello World\", 'o', ctr); // 无法编译通过\n```\n\n### 数组形参\n\n当我们为函数传递一个数组时，实际上传递的是指向数组首元素的指针。\n\n尽管不能以值传递的方式传递数组，但是我们可以把形参写成类似数组的形式：\n\n```c++\n// 每个函数都有一个const int*类型的形参\nvoid print(const int*);\nvoid print(const int[]);    // 可以看出来，函数的意图是作用于一个数组\nvoid print(const int[10]);  // 这里的维度表示我们期望数组含有多少元素，实际不一定\n```\n\n!!!warning\n\t和其他使用数组的代码一样，以数组作为形参的函数也必须确保使用数组时不会越界。\n\n因为数组是以指针的形式传递给函数的，所以一开始函数并不知道数组的确切尺寸，调用者应该为此提供一些额外的信息。管理指针形参有三种常用技术。\n\n1. 使用标记指定数组长度，如C风格字符串。\n\n2. 使用标准库规范，如传递首元素和尾后元素的指针，来表示一个范围。\n\n3. 显示传递一个表示数组大小的形参。\n\n> my note: 以上技术详细解读见书本p194。\n\n**数组形参和const**\n\n当函数不需要对数组元素执行写操作的时候，数组形参应该是指向const的指针。只有当函数确实要改变元素值的时候，才把形参定义成指向非常量的指针。\n\n**数组引用形参**\n\nC++语言允许将变量定义成数组的引用，基于同样的道理，形参也可以是数组的引用。此时，引用形参绑定到对应的实参上，也就是绑定到数组上。\n\n```c++\n// 正确，形参是数组的引用，维度是类型的一部分\nvoid print(int (&arr)[10])\n{\n    for (auto elem : arr)\n        cout << elem << endl;\n}\n```\n\n但这一用法也限制了print函数的可用性，我们只能将函数作用于大小为10的数组。\n\n**传递多维数组**\n\n和所有数组一样，当将多维数组传递给函数时，真正传递的是指向数组首元素的指针，也就是一个指向数组的指针。数组第二维（以及后面所有维度）的大小都是数组类型的一部分，不能省略：\n\n```c++\n// matrix指向数组的首元素，该数组的元素是由10个整数构成的数组\nvoid print(int (*matrix)[10], int rowSize) { /* ... */ }\n```\n\n也可以使用数组的语法定义函数，此时编译器会一如既往地忽略掉第一个维度：\n\n```c++\n// 等价定义\nvoid print(int matrix[][10], int rowSize) { /* ... */ }\n```\n\nmatrix的声明看起来是一个二维数组，实际上形参是指向含有10个整数的数组的指针。\n\n### main: 处理命令行选项\n\n有时候我们需要给main函数传递实参。一种常见的情况是用户通过设置一组选项来确定函数所要执行的操作。例如：\n\n```c++\nprog -d -o ofile data0\n```\n\n这些命令行选项通过两个（可选的）形参传递给main函数。\n\n```c++\nint main(int argc, char *argv[]) { ... }\n```\n\n第二个形参argv是一个数组，它的元素是指向C风格字符串的指针；第一个参数argc表示数组中字符串的数量；argc至少为1。\n\n当实参传给main函数之后，argv的第一个元素指向程序的名字或者一个空字符串，接下来的元素依次传递命令行提供的实参。最后一个指针之后的元素值保证为0。\n\n以上面的为例，argc应该等于5，argv应该包含如下的C风格字符串：\n\n```c++\nargv[0] = \"prog\";    // 或者argv[0]也可以指向一个空字符串\nargv[1] = \"-d\";\nargv[2] = \"-o\";\nargv[3] = \"ofile\";\nargv[4] = \"data0\";\nargv[5] = 0;\n```\n\n!!!warning\n\t当使用argv中的实参时，一定要记得可选的实参从argv[1]开始；argv[0]保存程序的名字，而非用户的输入。\n\n### 含有可变形参的函数\n\n为了编写能处理不同数量实参的函数，C++11新标准提供了两种主要的方法：\n\n- 如果所有的实参类型相同，可以传递一个名为initializer_list的标准库类型；\n\n- 如果实参的类型不同，我们可以编写一种可变参数模板，其细节将在16.4节介绍（p618）。\n\nC++还有一种特殊的形参类型（即省略符），可以用它传递可变数量的实参。这种功能一般只用于与C函数交互的接口程序。\n\n**initializer_list形参**\n\ninitializer_list是一种标准库类型，用于表示某种特定类型的值的数组。initializer_list类型定义在同名的头文件中。\n\ninitializer_list对象中的元素永远是常量值，我们无法改变initializer_list对象中元素的值。\n\n**省略符形参**\n\n省略符形参是为了便于C++程序访问某些特殊的C代码而设置的。\n\n!!!warning\n\t省略符形参应该仅仅用于C和C++通用的类型。特别应该注意的是，大多数类类型的对象在传递给省略符形参时都无法正确拷贝。\n\n省略符形参只能出现在形参列表的最后一个位置，它的形式无外乎以下两种：\n\n```c++\nvoid foo(param_list, ...);\nvoid foo(...);\n```\n\n## 返回类型和return语句\n\nreturn语句终止当前正在执行的函数并将控制权返回到调用该函数的地方。\n\nreturn语句有两种形式：\n\n```c++\nreturn;\nreturn expression;\n```\n\n### 无返回值函数\n\n没有返回值的return语句只能用在返回类型是void的函数中。返回void的函数不要求非得有return语句，因为在这类函数的最后一句后面会隐式地执行return。\n\n### 有返回值函数\n\n只要函数的返回类型不是void，则该函数内的每条return语句必须返回一个值。return语句返回值的类型必须与函数的返回类型相同，或者能隐式地转换成函数的返回类型。\n\n**值是如何被返回的**\n\n返回一个值的方式和初始化一个变量或形参的方式完全一样：返回的值用于初始化调用点的一个临时量，该临时量就是函数调用的结果。\n\n如果函数返回引用，则该引用仅是它所引对象的一个别名。\n\n**不要返回局部对象的引用或指针**\n\n函数完成后，它所占用的存储空间也随之被释放掉。因此，函数终止意味着局部变量的引用将指向不再有效的内存区域。\n\n**列表初始化返回值**\n\nC++11新标准规定，函数可以返回花括号包围的值的列表。此处的列表也用来对表示函数返回的临时量进行初始化。如果列表为空，临时量执行值初始化；否则，返回的值由函数的返回类型决定。\n\n**主函数main的返回值**\n\n我们允许main函数没有return语句直接结束，这样编译器将隐式地插入一条返回0的return语句，表示执行成功。\n\n为了使返回值与机器无关，cstdlib头文件定义了两个预处理变量，可以用来表示成功与失败：\n\n```c++\nint main()\n{\n    if (some_failure)\n        return EXIT_FAILURE;\n    else\n        return EXIT_SUCCESS;\n}\n```\n\n**递归**\n\n如果函数调用了它自身，不管这种调用是直接的还是间接的，都称该函数为**递归函数**（recursive function）。\n\n在递归函数中，一定有某条路径是不包含递归调用的；否则，函数将“永远”递归下去，换句话说，函数将不断地调用它自身直到程序栈空间耗尽为止。\n\nmain函数不能调用它自己。\n\n### 返回数组的指针\n\n因为数组不能被拷贝，所以函数不能返回数组。不过，函数可以返回数组的指针或引用。使用**类型别名**（p60）可以简化这种返回类型：\n\n```c++\ntypedef int arrT[10];    // arrT是一个类型别名，表示含有10个整数的数组\nusing arrT = int[10];    // arrT的等价声明\narrT* func(int i);       // func返回一个指向含有10个整数的数组的指针\n```\n\n**声明一个返回数组指针的函数**\n\n形式如下：\n\n```c++\nType (*function(param_list))[dimension]\n```\n\n类似于其他数组的声明，Type表示元素的类型，dimension表示数组的大小。（\\*表示返回的是一个指针。）\n\n举一个例子：\n\n```c++\nint (*func(int i))[10];\n```\n\n可以按照以下的顺序来逐层理解该声明的含义：\n\n- `func(int i)`表示调用func函数时需要一个int类型的实参。\n\n- `(*func(int i))`意味着我们可以对函数的调用结果执行解引用操作。\n\n- `(*func(int i))[10]`表示解引用func的调用将得到一个大小是10的数组。\n\n- `int (*func(int i))[10]`表示数组中的元素是int类型。\n\n**使用尾置返回类型**\n\nC++新标准提供了另一种简化上述func声明的方法，就是使用**尾置返回类型（trailing return type）**。任何函数的定义都能使用尾置返回，但是这种形式对于返回类型比较复杂的函数最有效。\n\n尾置返回类型跟在形参列表后面并以一个->符号开头。为了表示函数真正的返回类型跟在形参列表之后，我们在本应该出现返回类型的地方放置一个auto：\n\n```c++\n// func接受一个int类型的实参，返回一个指针，该指针指向含有10个整数的数组\nauto func(int i) -> int(*)[10];\n```\n\n**使用decltype**\n\n如果我们知道函数返回的指针将指向哪个数组，就可以使用decltype关键字声明返回类型（即获得一个数组类型）。\n\n案例：\n\n```c++\nint odd[] = {1, 3, 5, 7, 9};\nint even[] = {0, 2, 4, 6, 8};\n\n// 返回一个指针，该指针指向含有5个整数的数组\ndecltype(odd) *arrPtr(int i)\n{\n    return (i % 2) ? &odd : &even;\n}\n```\n\n> my note: decltype并不负责把数组类型转换成对应的指针。\n\n## 函数重载\n\n如果同一作用域内的几个函数名字相同但形参列表不同，我们称之为**重载（overload）函数**。比如：\n\n```c++\nvoid print(const char *cp);\nvoid print(const int *beg, const int *end);\nvoid print(const int ia[], size_t size);\n```\n\n这些函数接受的形参类型不一样，但是执行的操作非常类似。当调用这些函数时，编译器会根据传递的实参类型推断想要的是哪个函数。\n\n函数的名字仅仅是让编译器知道它调用的是哪个函数，而函数重载可以在一定程度上减轻程序员起名字、记名字的负担。\n\nmain函数不能重载。\n\n不允许两个函数除了返回类型以外其他所有的要素都相同。比如：\n\n```c++\nRecord lookup(const Account&);\nbool lookup(const Account&);    // 错误，与上一个函数相比只有返回类型不同\n```\n\n> my note: 返回类型不同的函数，也可以是重载的。只要函数名相同而形参有明显的不同。\n\n**重载和const形参**\n\n顶层const不影响传入函数的对象。一个拥有顶层const的形参无法和另一个没有顶层const的形参区分开来：\n\n```c++\nRecord lookup(Phone);\nRecord lookup(const Phone);    // 重复声明\n```\n\n如果形参是某种类型的指针或引用，则通过区分其指向的是常量对象还是非常量对象可以实现函数重载，此时的const是底层的：\n\n```c++\nRecord lookup(Account&);       // 此函数作用于Account的引用\nRecord lookup(const Account&); // 新函数，作用于常量引用\n```\n\n这种情况下，当我们传递一个非常量对象时，编译器会优先选用非常量版本的函数（尽管传给常量版本的也可以）。\n\n**const_cast和重载**\n\nconst_cast在重载函数的情境中最有用。比如这两个重载函数：\n\n```c++\n// 比较两个string对象的长度，返回较短的那个引用\nconst string &shorterString(const string &s1, const string &s2)\n{\n    return s1.size() <= s2.size() ? s1 : s2; \n}\n\n// 重载\nstring &shorterString(string &s1, string &s2)\n{\n    auto &r = shorterString(const_cast<const string&>(s1), const_cast<const string&>(s2));\n    return const_cast<string&>(r);\n}\n```\n\n下面重载的版本中，首先将它的实参强制转换成了对const的引用，然后调用了shorterString函数的const版本。const版本返回对const string的引用，这个引用事实上绑定在一个非常量实参上。因此，可以再将其转换回普通的const&，这显然是安全的。\n\n> my note: 传入非常量的实参将调用非常量的版本。\n\n**调用重载的函数**\n\n定义了一组重载函数后，我们需要以合理的实参调用它们。**函数匹配（function matching）**是指一个过程，在这个过程中我们把函数调用与一组重载函数中的某一个关联起来。编译器首先将调用的实参与重载集合中的每一个函数的形参进行比较，然后根据比较的结果决定到底调用哪个函数。\n\n当调用重载函数时有三种可能的结果：\n\n- 编译器找到一个与实参**最佳匹配（best match）**的函数，并生成调用该函数的代码。\n\n- 找不到任何一个函数与调用的实参匹配，此时编译器发出**无匹配（no match）**的错误信息。\n\n- 有多于一个函数可以匹配，但是每一个都不是明显的最佳选择。此时也将发生错误，称为**二义性调用（ambiguous call）**。\n\n### 重载与作用域\n\n一般来说，将函数声明置于局部作用域内不是一个明智的选择。\n\n如果我们在内层作用域中声明名字，它将隐藏外层作用域中声明的同名实体。对于函数而言也是如此。\n\n> my note: 具体例子可见于书本p210。总之，如果在内层作用域声明了一个函数，那么外层的同名的函数都将变得不可见，因此无法找到外层的重载版本。\n\n## 特殊用途语言特性\n\n### 默认实参\n\n这样一种形参，在函数的很多次调用中它们都被赋予一个相同的值，此时，我们把这个反复出现的值称为函数的**默认实参**（default argument）。调用含有默认实参的函数时，可以包含该实参，也可以省略该实参。如：\n\n```c++\ntypedef string::size_type sz;\nstring screen(sz ht = 24, sz wid = 80, char backrnd = ' ');\n```\n\n一旦某个形参被赋予了默认值，它后面的所有形参都必须有默认值。\n\n**使用默认实参调用函数**\n\n如果我们想使用默认实参，只要在调用函数的时候省略该实参就可以了。如：\n\n```c++\nstring window;\nwindow = screen();    // 等价于screen(24, 80, ' ');\nwindow = stcreen(66); // 等价于screen(66, 80, ' ');\n```\n\n函数调用时实参按其位置解析，默认实参负责填补函数调用缺少的尾部实参。\n\n当设计含有默认实参的函数时，其中一项任务是合理设置形参的顺序，尽量让不怎么使用默认值的形参出现在前面，而让那些经常使用默认值的形参出现在后面。\n\n**默认实参初始值**\n\n局部变量不可以作为默认实参。另外只要表达式的类型可以转换成形参类型，该表达式就可以作为默认实参。\n\n如：\n\n```c++\nint g_a = 0;\nvoid f(int a = g_a);\n```\n\n### 内联函数和constexpr函数\n\n调用普通函数比直接写其语句要慢，这是因为调用函数包含一些额外的工作。\n\n**内联函数可以避免函数调用的开销**\n\n将函数指定为内联函数（inline），通常就是将它在每个调用点上“内联地”展开。\n\n内联说明只是向编译器发出一个请求，编译器可以选择忽略这个请求。\n\n内联机制用于优化规模小，流程直接，频繁调用的函数。\n\n**constexpr函数**\n\n是指能用于**常量表达式**的函数。\n\n函数的返回类型及所有形参都得是字面值类型，且函数体内必须有且只有一条return语句。\n\n如：\n\n```c++\nconstexpr int new_sz() { return 8; }\nconstexpr int foo = new_sz();\n```\n\nconstexpr函数被隐式地指定为内联函数。\n\n**把内联函数和constexpr函数放在头文件内**\n\n这是因为内联函数和constexpr函数可以多次定义，且必须完全一致。所以把它们都定义在头文件内。\n\n### 调试帮助\n\n程序可以包含一些用于调试的代码，但是这些代码只在开发程序时使用。当应用程序编写完成准备发布时，要先屏蔽掉调试代码。这种方法用到两项预处理功能：assert和NDEBUG。\n\n**assert预处理宏**\n\nassert是一种**预处理宏（preprocessor macro）**。所谓预处理宏其实是一个预处理变量，它的行为有点类似于内联函数。assert宏使用一个表达式作为它的条件：\n\n```c++\nassert(expr);\n```\n\n首先对expr求值，如果表达式为假（即0），assert输出信息并终止程序的执行。如果表达式为真（即非0），assert什么也不做。\n\nassert宏定义在cassert头文件中。\n\nassert宏常用于检查“不能发生”的条件。（即确实不应该发生的事情，发生了就崩溃吧）\n\n**NDEBUG预处理变量**\n\nassert的行为依赖于一个名为NDEBUG的预处理变量的状态。如果定义了NDEBUG，则assert什么也不做。默认状态下没有定义NDEBUG，此时assert将执行运行时检查。\n\n我们可以使用一个#define语句定义NDEBUG，从而关闭调试状态。或者使用编译器提供的命令行选项定义预处理变量：\n\n```c++\n$ CC -D NDEBUG main.c\n```\n\n这条命令的作用等价于在main.c文件的一开始写#define NDEBUG。\n\n我们可以把assert当成调试程序的一种辅助手段，但是不能用它代替真正的运行时逻辑检查，也不能代替程序本身应该包含的错误检查。\n\n除了用于assert，也可以使用NDEBUG编写自己的调试代码。\n\n比如：\n\n```c++\nvoid print(const int ia[], size_t size)\n{\n#ifndef NDEBUG\n    // __func__是编译器定义的一个局部静态变量，用于存放函数的名字\n    cerr << __func__ << \"\": array size is: \" << size << endl;\n#endif\n\n// ...\n}\n```\n\n编译器为每个函数都定义了`__func__`，除此之外，预处理器还定义了4个对于调试程序很有用的名字：\n\n- `__FILE__`, 存放文件名的字符串字面值。\n\n- `__LINE__`, 存放当前行号的整型字面值。\n\n- `__TIME__`, 存放文件编译时间的字符串字面值。\n\n- `__DATA__`, 存放文件编译日期的字符串字面值。\n\n## 函数匹配\n\n本节(p217)讲述编译器如何确定调用哪个重载函数，并以下述这组函数及其调用为例：\n\n```c++\nvoid f();\nvoid f(int);\nvoid f(int, int);\nvoid f(double, double = 3.14);\nf(5.6);    // 调用void f(double, double);\n```\n\n**确定候选函数和可行函数**\n\n函数匹配的第一步是选定本次调用对应的重载函数集，集合中的函数成为**候选函数（candidate function）**。候选函数具备两个特征：\n\n1. 与被调用函数同名。\n\n2. 其声明在调用点可见。\n\n第二步考察本次调用提供的实参，然后从候选函数中选出能被这组实参调用的函数，这些新选出的函数称为**可行函数（viable function）**。可行函数也有两个特征：\n\n1. 其形参数量与本次调用提供的实参数量相等。\n\n2. 每个实参的类型与对应的形参类型相同，或者能转换成形参的类型。\n\n> my note: 如果没有找到可行函数，编译器将报告无匹配函数的错误。\n\n**寻找最佳匹配（如果有的话）**\n\n第三步是从可行函数中选择与本次调用最匹配的函数。在这一过程中，逐一检查函数调用提供的实参，寻找形参类型与实参类型最匹配的那个可行函数。\n\n如果有且只有一个函数满足下列条件，则匹配成功：\n\n- 该函数每个实参的匹配都不劣于其他可行函数需要的匹配。\n\n- 至少有一个实参的匹配优于其他可行函数提供的匹配。\n\n> my note: 如果编译器检查了每一个可行函数，没有一个能脱颖而出，则会报告二义性调用错误。\n\n### 实参类型转换\n\n为了确定最佳匹配，编译器将实参类型到形参类型的转换划分成几个等级，具体排序如下所示：\n\n1. 精确匹配，包括以下情况：\n\n- 实参类型和形参类型相同。\n- 实参从数组类型或函数类型转换成对应的指针类型。\n- 向实参添加顶层const或者从实参中删除顶层const。\n\n2. 通过const转换实现的匹配（p143）。\n\n3. 通过类型提升实现的匹配（p142）。\n\n4. 通过算数类型转换或指针转换实现的匹配（p142）。\n\n5. 通过类类型转换实现的匹配（参见14.9节，p514）。\n\n> my note: 详细案例解析见书本p220。\n\n## 函数指针\n\n函数指针指向的是函数而非对象。函数的类型由它的返回类型和形参类型共同决定，与函数名无关。例如：\n\n```c++\nbool lengthCompare(const string&, const string&);\n```\n\n该函数的类型是：`bool (const string&, const string&);`\n\n要想声明一个指向该函数的指针，只需要将函数名替换成指针即可：\n\n```c++\nbool (*pf)(const string&, const string&);\n```\n\n**使用函数指针**\n\n当我们把函数名作为一个值使用的时候，该函数名自动转换成指针（指向该函数的）。\n\n例如，可以这样给把函数地址赋值给指针：\n\n```c++\npf = lengthCompare; // pf指向名为lengthCompare的函数\n\npf = &lengthCompare; // 等价的赋值语句，取地址符是可选的\n```\n\n可以直接对指向函数的指针调用该函数，无须解引用指针：\n\n```\nbool b1 = pf(\"Hello\", \"Hi\");\nbool b2 = (*pf)(\"Hello\", \"Hi\"); // 等价调用\nbool b3 = lengthCompare(\"Hello\", \"Hi\"); // 等价调用\n```\n\n可以给函数指针赋一个nullptr或0，表示没有指向任何函数。\n\n**重载函数的指针**\n\n当使用了重载函数时，编译器必须确定一个能和指针类型精确匹配的函数，即返回类型和形参列表都要一样。\n\n**函数指针形参**\n\n不能定义函数类型的形参，但是形参可以是指向函数的指针。\n\n当把函数名作为实参使用，它会自动转换成指针。\n\n定义一个函数（以及指针）类型的方法有：\n\n- typedef\n\n```c++\ntypedef bool Func(int); // Func是函数类型\ntypedef bool (*FuncP)(int); // FuncP是函数指针类型\n```\n\n- decltype\n\n假如已经有了一个函数：`bool Foo(int);`\n\n```c++\ndecltype(Foo) Func;\ndecltype(Foo) *FuncP;\n```\n\n- using\n\n```c++\nusing Func = bool(int);\nusing FuncP = bool(*)(int);\n```\n\n> my note: 关于使用直接声明的方法，以及使用尾置返回类型的方法来确定一个返回类型为函数指针的函数，见书本p223。因为我觉得使用上述别名定义已经能满足这种需求了。"
  },
  {
    "path": "notes/CppPrimer/ch07 类.md",
    "content": "# 第七章 类\n\n类的基本思想是**数据抽象**(data abstraction)和**封装**(encapsulation)。\n\n数据抽象就是**接口(interface)与实现(implementation)分离**的技术。\n\n接口就是暴露给用户的操作，比如公有的成员函数。\n\n实现就是数据成员、接口的实现、私有的成员函数。\n\n通过**抽象数据类型(abstract data type)**，来实现数据抽象和封装。\n\n!!!note\n\t在第二章自定义的Sales_data类由于没有实现封装，且没有定义自己的操作，因此不是一个抽象数据类型。后续章节的内容将要对其进行改造使其成为一个抽象数据类型。\n\n---\n\n## 定义抽象数据类型\n\n封装就是隐藏，抽象数据类型隐藏了自己的成员变量，外部只能使用其接口来间接访问其成员。\n\n**定义成员函数**\n\n类内的所有成员必须声明在类的内部。\n\n类的成员函数可以定义在类的内部，也可以定义在类的外部。\n\n!!!note\n\t定义在类内部的函数是隐式的inline函数。\n\n**引入this**\n\n当调用一个成员函数时，实际上是替某个对象调用它。\n\n成员函数通过名为**this**的隐式参数来访问此对象。this指向了此对象的地址。\n\n在成员函数内部，可以省略this来访问成员。\n\nthis是一个常量指针，不能够修改其值。\n\n当成员函数中调用另一个成员函数时，将隐式传递this指针。\n\n**引入const成员函数**\n\n参数列表之后，添加const关键字，表明传入的this指针是一个指向常量对象的指针。故此成员函数内，不能修改成员变量的内容。\n\nconst对象只能调用const版本的成员函数（因此如果函数不修改成员变量，那么为了提高灵活性，应该把函数声明成const版本的）。\n\n**类作用域和成员函数**\n\n类本身就是一个作用域。\n\n成员函数的定义必须包含其所属的类名（使用作用域运算符）。\n\n如果成员函数声明为const版本的，其定义时，也要在参数列表后加const。\n\n成员函数体可以随意使用类中的成员，无须在意成员出现的顺序，这是因为编译器分两步处理类：首先编译成员的声明，然后才轮到成员函数体。\n\n**定义一个返回this对象的函数**\n\n可以使用如下语句返回this对象：\n\n```c++\nreturn *this;\n```\n\n返回类型使用引用类型，表明返回的就是this所指的对象。\n\n一般来说，当我们定义的函数类似于某个内置运算符时，应该令函数的行为尽量模仿这个运算符。比如说内置的赋值运算符把它的左侧运算对象当成左值返回，这种情况下，函数就可以返回this对象的引用。（见书本p233的详细讨论）\n\n### 定义类相关的非成员函数\n\n有些函数也提供了操作类对象的方法，但他们不属于类的成员函数。\n\n可以把这些函数放到类的头文件中声明。这些函数也可以看成是类的接口。\n\n有可能会把这些函数声明称友元，从而方便它们直接操作成员变量。\n\n### 构造函数\n\n类通过一个或几个特殊的成员函数初始化其成员变量，这些函数叫**构造函数（constructor）**。\n\n每当类对象被创建，构造函数就会被执行。\n\n构造函数名和类名一致，无返回类型，可能有多个（参数个数差异），不能是const的。\n\n对于const对象，构造函数执行完毕后，它才获得const属性。\n\n**合成的默认构造函数**\n\n如果对象没有初始值，它将执行默认初始化。\n\n类通过**默认构造函数(default constructor)**来执行默认初始化。如果没有显示定义过构造函数，编译器就会自动生成一个，叫做合成的默认构造函数。\n\n合成的默认构造函数根据如下规则初始化类成员：\n\n- 如果存在类内初始值，使用它来初始化成员\n\n- 否则，对成员执行默认初始化\n\n**某些类不能依赖合成的默认构造函数**\n\n所谓不能依赖，就是不可以让编译器生成默认构造函数，要自己定义一个。其原因可能是：\n\n- 如果定义了自己的构造函数，那么编译器就不会生成默认的构造函数，此类就没有了默认构造函数。\n\n- 默认构造函数可能执行的是错误的操作，比如内置类型若没有类内初始值，则进行默认初始化，其值未定义。\n\n- 有时候，编译器无法生成默认构造函数，比如类成员中有类，而此类有可能没有默认构造函数。\n\n**=default**\n\nC++11中，使用这种语句来让编译器生成一个默认构造函数：\n\n```c++\nSalesData() = default;\n```\n\n!!!note\n\t这种情况下，应当对内置类型的数据成员提供类内初始值，否则应当使用构造函数初始值列表形式的默认构造函数。\n\n**构造函数初始值列表**\n\n参数列表后，函数体前的一部分内容叫构造函数初始值列表（constructor initialize list）。\n\n它负责为对象的成员变量赋初值。\n\n如果成员不在初始化列表中，它用类内初始值初始化（如果存在），否则执行默认初始化。\n\n### 拷贝、赋值和析构\n\n拷贝构造函数，当初始化变量时以值传递或函数返回一个对象时，会发生拷贝。\n\n赋值运算，当使用了赋值运算符时，会发生对象的赋值操作。\n\n析构函数，当一个变量不在存在时，会执行析构。\n\n这些操作如果不显示定义，编译器就会合成一个，合成的拷贝赋值版本只是做了浅拷贝操作。\n\n**某些类不能依赖合成的版本**\n\n如果类中有成员绑定了外部的对象（比如动态内存），那么就不可依赖合成的版本。\n\n可使用容器管理必要的存储空间，当发生拷贝等操作时，容器也会执行正确的拷贝。\n\n## 访问控制与封装\n\n使用**访问说明符（access specifiers）**加强类的封装性。\n\n- public说明符之后的成员对外可见，外部可访问，public成员定义类的接口。\n\n- private说明符之后的成员对内可见，外部无法访问，即隐藏了实现细节。\n\n**class和struct**\n\n其区别仅仅在于默认的访问权限。class默认为private，struct默认是public。\n\n> my note: 作为接口，应当是public的，而实现细节（数据成员或相关函数）应当为private的。\n\n### 友元\n\n类可以允许其他类或者函数访问它的非公有成员，方法是令其他类或者函数成为它的友元（friend）。\n\n即在函数或类前面加friend关键字。\n\n友元声明只能出现在类的内部。它并非函数声明，函数声明还要在别的地方声明。\n\n一般来说，最好在类定义的开始或结束前的位置集中声明友元。\n\n!!!note \"关键概念：封装的益处\"\n\t封装有两个重要的优点：\n\n\t- 确保用户代码不会无意间破坏封装对象的状态。\n\n\t- 被封装的类的具体实现可以随时改变，而无须调整用户级别的代码。\n\n## 类的其它特性\n\n### 类成员再探\n\n**定义一个类型成员**\n\n可以在类的内部定义一个类型（使用typedef或using），这个类型也有访问限制。\n\n通常放在类的开头位置。\n\n**令成员作为内联函数**\n\n规模较小的成员函数适合声明成内联函数（定义时在前面加inline即可）。\n\n如果定义在类内的函数，默认就是inline的。\n\ninline成员函数通常定义到类的头文件中，即声明和定义在同一个文件中。\n\n**重载成员函数**\n\n和普通函数的重载规则一样。只要参数的数量or类型有区别，就可以重载。\n\n如果是const版本的成员函数（传入const this），那么也可以重载。因为本质上，其隐式参数this的类型改变了。\n\n**类数据成员的初始值**\n\n可以给类数据成员一个类内初始值。使用等号或者花括号。\n\n### 返回\\*this的成员函数\n\n返回引用的函数是左值的，意味着这些函数（返回\\*this）返回的是对象本身而非对象的副本。\n\n!!!note\n\t一个const成员函数如果以引用的形式返回\\*this，那么它的返回类型将是常量引用。\n\n> my note: 书本使用一个详细的案例Screen来阐述本节的知识点。见p243。\n\n但是如此一来（const成员函数返回const引用），就无法继续让返回的对象调用非常量版本的成员函数。一个解决的办法就是**重载一个非常量版本的接口**，定义一个私有的常量版本的函数，负责具体工作，而非常量版本的接口负责调用它，并返回非常量引用。\n\n建议：对于公共代码使用私有功能函数。\n\n### 类类型\n\n每个类是一个唯一的类型，即使其内容完全一样。\n\n**类的声明**\n\n可以暂时声明类而不定义它，这叫前置声明（forward declaration）。\n\n这种类型，在没有定义前是一个不完全类型（incomplete type）。这种类型只能在有限的情况下使用：\n\n- 定义指向这种类型的指针or引用\n\n- 声明以不完全类型为参数or返回值的函数\n\n要创建一个类的对象，则必须已经定义好了这个类，这是因为编译器需要知道类的存储空间大小。\n\n只有被定义，才能访问其成员。\n\n声明一个前置类型的方法：\n\n```c++\nclass A;\n\nstruct B;\n\nnamespace game\n{\n    class C;    // 前置声明一个在命名空间中的类\n}\n```\n\n### 友元再探\n\n类可以把普通函数定义成友元，也可以把类，类的成员函数定义成友元。\n\n友元类有权访问本类的非公有成员。\n\n## 类的作用域\n\n一个类就是一个作用域。\n\n类的作用域之外，普通的成员只能通过对象、引用or指针访问。对于类型成员的访问，需要使用域运算符`::`来访问。\n\n### 名字查找与类的作用域\n\n编译器处理完类的全部声明后，才会处理成员函数的定义。因此成员函数体中可以使用类中定义的任何位置的名字。\n\n**成员函数中的名字查找**\n\n按如下方式解析：\n\n- 在块内查找声明\n\n- 在类内查找，所有成员都可以被考虑\n\n- 在类的外围作用域中查找\n\n## 构造函数再探\n\n### 构造函数初始值列表\n\n如果没有在构造函数的初始值列表中显示初始化成员，那么该成员将执行默认初始化。\n\n!!!note\n\t如果成员是const、引用，或者属于某种未提供默认构造函数的类类型，我们必须通过构造函数初始值列表为这些成员提供初始值。\n\n**成员初始化的顺序**\n\n成员的初始化顺序和它们在类内的定义顺序一致。\n\n而非其在初始值列表中的顺序，初始值列表只是做了初始化的工作。所以要让初始值列表中的成员顺序与定义顺序一致。\n\n**有默认实参的构造函数**\n\n如果构造函数的所有实参都有默认实参，那么它实际上也同时定义了默认构造函数。\n\n### 委托构造函数\n\nC++11可以定义委托构造函数（delegating constructor）。一个委托构造函数使用它所属类的其他构造函数执行他自己的初始化过程，或者说它把它自己的一些职责委托给了其他构造函数。\n\n当一个构造函数委托给另一个构造函数时，受委托的构造函数的初始值列表和函数体被依次执行。\n\n> my note: 即先执行受委托的构造函数内容，再执行自己的。\n\n### 默认构造函数的作用\n\n当对象被默认初始化或值初始化时，自动执行默认构造函数。\n\n默认构造函数在以下情况发生：\n\n- 不使用初始值定义一个非静态变量或者数组时\n\n- 当类含有类类型的成员且使用合成的默认构造函数时\n\n- 当类类型的成员没有在构造函数初始值列表中显式初始化时\n\n值初始化在以下情况下发生：\n\n- 数组初始化时，若提供的初始值少于数组大小时\n\n- 不使用初始值定义一个局部静态变量时\n\n- 书写形如T()的表达式显式请求值初始化时\n\n### 隐式的类类型转换\n\n如果构造函数只接受一个实参，则它实际上定义了**转换构造函数（converting constructor）**。\n\n即定义了一个隐式转换机制。如string的接受一个const char\\*版本的构造函数。\n\n使用explicit阻止这种隐式转换机制，explicit只能放到类内声明构造函数里。\n\n### 聚合类\n\n聚合类（aggregate class）使得用户可以直接访问其成员。当类满足如下条件时，是聚合的：\n\n- 所有成员都是public的\n\n- 没有定义任何构造函数\n\n- 没有类内初始值\n\n- 没有基类，没有virtual函数\n\n可以使用花括号括起来的成员初始值列表来初始化聚合类对象。\n\n### 字面值常量类（ Literal Classes）\n\n类也可以是字面值类型。\n\n这样的类可以含有constexpr函数成员，且符合constexpr函数的所有要求，且是隐式const的。\n\n数据成员都是字面值类型的聚合类是字面值常量类。\n\n如果不是聚合类，满足如下条件也是一个字面值常量类：\n\n- 数据成员都是字面值类型\n\n- 至少含有一个constexpr构造函数\n\n- 如果数据成员含有类内初始值，则内置类型成员的初始值必须是一条常量表达式；类类型成员必须使用自己的constexpr构造函数\n\n- 类必须使用析构函数的默认定义\n\n## 类的静态成员\n\n有时候类需要一些只与类相关，而与具体对象无关的特殊成员，这就是静态成员。\n\n**声明静态成员**\n\n在声明前加static关键字。\n\n静态成员可以是public或private。数据成员可以是常量，引用，指针，类类型等。\n\n对象不包含与静态数据成员有关的数据。\n\n静态函数不包含this指针。\n\n**使用类的静态成员**\n\n使用作用域运算符访问静态成员。\n\n类的对象、引用或指针可以访问静态成员。\n\n类的成员函数可以直接访问静态成员。\n\n**定义静态成员**\n\nstatic只能出现在类的内部，不能出现在外部。\n\n静态数据成员不属于类的对象，不是有构造函数初始化的。静态数据成员定义在函数体之外，一旦定义，就一直存在于程序的整个生命周期中。\n\n```c++\ndouble T::a = 1; // 定义并初始化一个静态成员\n```\n\n**静态成员的类内初始化**\n\n通常，不应该在类内初始化静态数据成员。\n\n不过，可以为静态成员提供const整数类型的类内初始值，且要求静态成员必须是字面值常量类型。\n"
  },
  {
    "path": "notes/CppPrimer/ch08 IO库.md",
    "content": "# 第八章 IO库\n\n---\n\n## IO类\n\n为了支持不同种类的IO处理操作，标准库定义了这几种类型：\n\n- iostream 定义了用于读写流的基本类型\n\n- fstream 定义了读写命名文件的类型\n\n- sstream 定义了读写内存string对象的类型\n\n它们分别定义在同名的头文件中。\n\n**IO类型间的关系**\n\n类型ifstream和istringstream都继承自istream。我们可以像使用istream对象一样来使用它们。对于ostream也是如此。\n\n### IO对象无拷贝或赋值\n\n由于不能拷贝IO对象，因此也不能将形参或返回类型设置为流类型。进行IO操作的函数通常以引用方式传递或返回流。\n\n读写一个IO对象会改变其状态，因此传递和返回的引用不能是const的。\n\n### 条件状态\n\nIO类定义了一些函数和标志，可以帮助我们访问和操纵流的条件状态。见p279。\n\n一个IO错误的例子：\n\n```c++\nint ival;\ncin >> ival;\n```\n\n如果试图在标准输入上键入Boo，读操作就会失败，cin进入错误状态。\n\n如果输入一个文件结束符标识，cin也会进入错误状态。\n\n一个流一旦发生错误，其上后续的IO操作都会失败。确定一个流对象的状态的最简单的方法是将它当作一个条件来使用：\n\n```c++\nwhile (cin >> word)\n    // ok\n```\n\n!!!note\n\t读取失败后，不会消耗掉缓冲区的内容。因此这种情况，通常读取一个字符串，然后转换字符串为数字。\n\n### 管理输出缓冲\n\n每个输出流都管理一个缓冲区，用来保存程序读写的数据。如果执行下面的代码：\n\n```c++\nos << \"please enter a value: \";\n```\n\n文本串可能立即打印出来，但也有可能被操作系统保存在缓冲区中，随后再打印。这样可以带来很大的性能提升。\n\n导致缓冲区刷新的原因有：\n\n- 程序正常结束\n\n- 缓冲区满时\n\n- 使用操纵符，如endl，来显式刷新缓冲区\n\n- 读cin或写cerr，都会导致cout的缓冲区被刷新\n\n**刷新输出缓冲区**\n\nIO库还提供了两个操纵符用于刷新缓冲区：\n\n- flush 刷新缓冲区，但不输出任何额外字符\n\n- ends 向缓冲区插入一个空字符，然后刷新缓冲区\n\n**unitbuf操纵符**\n\n如果想在每次输出操作后都刷新缓冲区，我们可以使用unitbuf操纵符。\n\n```c++\ncout << unitbuf;    // 所有输出操作后都会立即刷新缓冲区\ncout << nounitbuf;  // 回到正常的缓冲方式\n```\n\n!!!warning\n\t如果程序崩溃，输出缓冲区不会刷新\n\n## 文件输入输出\n\n除了继承自iostream类型的行为之外，fstream中定义的类型还增加了一些新的成员来管理与流关联的文件。见p283。\n\n### 使用文件流对象\n\n当想要读写一个文件时，可以定义一个文件流对象，并将对象与文件关联起来。\n\n每个文件流类都定义了一个名为open的成员函数，它完成一些系统相关的操作，来定位给定的文件，并视情况打开为读或写模式。\n\n创建文件流对象时，如果提供了一个文件名，则open会被自动调用：\n\n```c++\nifstream in(file);    // 构造一个ifstream并打开给定的文件\nofstream out;         // 输出文件流未关联到任何文件\n```\n\n!!!note\n\t当一个fstream对象被销毁时，close会自动被调用。\n\n### 文件模式\n\n每个流都有一个关联的文件模式，用来指出如何使用文件。见p286。\n\n每个文件流类型都定义了一个默认的文件模式，当未指定文件模式时，就使用此默认模式。\n\n- 与ifstream关联的文件默认以in模式打开；\n\n- 与ofstream关联的文件默认以out模式打开；\n\n- 与fstream关联的文件默认以in和out模式打开。\n\n**以out模式打开文件会丢失已有数据**\n\n默认情况下，当我们打开一个ofstream时，文件的内容会被丢弃。\n\n阻止丢弃的方法是同时指定app模式：\n\n```c++\nofstream out(\"file1\");    // 文件被截断\nofstream app(\"file2\", ofstream::app);    // 保留文件内容，写操作在文件末尾进行\n```\n\n## string流\n\nsstream头文件定义了三个类型来支持内存IO：\n\n- istringstream从string读取数据。\n\n- ostringstream向string写入数据。\n\n- stringstream既可以从string读数据，也可以向string写数据。\n\nsstream增加了一些成员来管理与流相关联的string。见p287。\n\n### 使用istringstream\n\n当我们的某些工作是对整行文本进行处理，而其他一些工作是处理行内的单个单词时，通常可以使用istringstream。\n\n### 使用ostringstream\n\n当我们逐步构造输出，希望最后一期打印时，ostringstream是很有用的。"
  },
  {
    "path": "notes/CppPrimer/ch09 顺序容器.md",
    "content": "# 第九章 顺序容器\n\n---\n\n## 顺序容器概览\n\n顺序容器有：vector, deque, list, forward_list, array, string。见p292表9.1。\n\n所有顺序容器都提供了快速顺序访问元素的能力。但是，这些容器在以下方面都有不同的性能折中：\n\n- 向容器添加或从元素中删除元素的代价\n\n- 非顺序访问容器中元素的代价\n\nstring和vector将元素保存在连续的内存空间中。由于元素是连续存储的，由元素的下标来计算其地址是非常快速的。但是，在其中间添加或删除元素就会非常耗时，因为这需要移动插入或删除位置之后的所有元素。而且，添加元素可能导致分配额外的存储空间，这种情况下，每个元素都会移动到新的存储空间中。\n\nlist和forward_list两个容器添加和删除操作都很快速。作为代价，它们不支持元素的随机访问，为了访问一个元素，只能遍历整个容器。与vector、deque和array相比，这两个容器的额外内存开销也很大。\n\ndeque支持快速随机访问，在deque的中间位置插入或删除元素的代价（可能）很高。但是，在deque的两端添加或删除元素都是很快的。\n\n**确定使用哪种容器**\n\n!!!tip\n\t通常，使用vector是最好的选择，除非你有很好的理由选择其他容器。\n\n见书本p293的详细讨论。\n\n## 容器库概览\n\n容器类型上的操作形成了一种层次：\n\n- 某些操作是通用的，见表9.2，295页。\n\n- 某些操作仅针对顺序容器（表9.3，299页）、关联容器（表11.7，388页）或无序容器（表11.8，395页）。\n\n- 还有一些操作只适用于一小部分容器。\n\n### 迭代器\n\n迭代器有着公共的接口：如果一个迭代器提供某个操作，那么所有提供相同操作的迭代器对这个操作的实现方式都是相同的。比如解引用操作。\n\n表3.6（96页）列出了容器迭代器支持的所有操作。表3.7（99页）列出了迭代器支持的算术运算，这些运算只能应用于string、vector、deque和array。\n\n**迭代器范围**\n\n迭代器范围由一对迭代器表示，通常被称为begin和end，它们标记了容器中元素的一个范围。这个范围被称为左闭合区间：[begin, end)\n\n**使用左闭合区间蕴含的编程假定**\n\n假定begin和end构成一个合法的迭代器范围，则：\n\n- 如果begin与end相等，则范围为空\n\n- 如果begin与end不等，则范围至少包含一个元素，且begin指向该范围中的第一个元素\n\n- 我们可以对begin递增若干次，使得begin == end\n\n### 容器定义和初始化\n\n每个容器类型都定义了一个默认构造函数。除array之外，其他容器的默认构造函数都会创建一个指定类型的空容器，且都可以指定容器大小和元素初始值的参数。\n\n见表9.3，p299。\n\n**将一个容器初始化为另一个容器的拷贝**\n\n方法有两种：\n\n- 直接拷贝整个容器，两个容器的类型和元素的类型都必须匹配。\n\n- 拷贝一个迭代器范围，容器类型不一定匹配，且元素类型只要能够转换即可。\n\n### 赋值和拷贝\n\n赋值运算符将其左边容器中的全部元素替换为右边容器中的元素的拷贝。具体见p302。\n\n**使用assign(仅顺序容器)**\n\n赋值运算要求两边容器类型和元素类型相同。顺序容器（除了array）还定义了一个名为assign的成员，允许从一个相容的序列中赋值。\n\n**使用swap**\n\n调用swap操作后，两个容器中的元素将会交换。\n\n除了array，交换两个容器的操作保证会很快，因为元素本身并未交换，swap只是交换了两个容器的内部数据结构。\n\n### 容器大小操作\n\n每个容器都支持这些大小相关的操作：\n\n- 成员函数size，返回容器中元素的数目，forward_list不支持；\n\n- empty，当size为0时返回true，否则返回false；\n\n- max_size，返回一个大于或等于该容器所能容纳的最大元素数的值，这是一个很大的值。\n\n### 关系运算符\n\n每个容器都支持相等运算符（==和!=），除了无序关联容器外的所有容器都支持关系运算符（>, >=, <, <=）。关系运算符左右两边的运算对象必须是相同类型的容器，且必须保存相同类型的元素。\n\n比较两个容器实际上是进行元素的逐对比较。\n\n!!!note\n\t只有当元素类型定义了相应的比较运算符时，才可以使用关系运算符比较两个容器。\n\n## 顺序容器操作\n\n顺序容器和关联容器的不同之处在于两者组织元素的方式。这些不同之处直接关系到了元素如何存储、访问、添加及删除。\n\n### 向顺序容器添加元素\n\n标准库容器提供了灵活的内存管理。在运行时可以动态添加或删除元素来改变容器大小。表9.5，p305。\n\n!!!warning\n\t向一个deque、string或vector插入元素会使所有指向容器的迭代器、引用和指针失效。\n\n\t将元素插入到deque、string或vector中的任何位置都是合法的。然而，这样做可能很耗时。\n\n**关键概念：容器元素是拷贝**\n\n当我们用一个对象来初始化容器时，或将一个对象插入到容器中时，实际上放入到容器中的是对象值的一个拷贝。\n\n### 访问元素\n\n表9.6（p310）列出了我们可以用来在顺序容器中访问元素的操作。如果容器中没有元素，访问操作的结果是未定义的。\n\n**下标操作和安全的随机访问**\n\n提供快速随机访问的容器（string、vector、deque和array）也都提供下标运算符。保证下标合法是程序员的责任，编译器不检查越界错误。\n\n如果想确保下标是合法的，可以使用at成员函数。at成员函数类似下标运算符，如果下标越界，at会抛出一个out_of_range异常。\n\n### 删除元素\n\n见表9.7，p311页。\n\n!!!warning\n\t删除deque中除首尾之外的任何元素都会使所有迭代器、引用、指针失效。指向vector或string中删除点之后位置的迭代器、引用和指针都会失效。\n\t\n\t删除元素之前，程序员必须确保它们是存在的。\n\n### 改变容器大小\n\n可以使用resize来增大或缩小容器。如果当前大小大于所要求的大小，容器后部的元素会被删除；如果当前大小小于新大小，会将新元素添加到容器后部。\n\nresize接受一个可选的元素指参数，用来初始化新添加的元素。如果未提供，新元素进行值初始化。\n\n### 容器操作可能使迭代器失效\n\n使用失效的迭代器、引用、或指针是一种严重的错误。\n\n向容器添加元素后：\n\n- 如果容器是vector或string，且存储空间被重新分配，那么所有的迭代器都会失效。如果空间未重新分配，指向插入位置之前的元素的迭代器仍有效，但之后的迭代器会失效。\n\n- 对于list和forward_list，指向容器的迭代器仍有效。\n\n当从容器中删除元素后：\n\n- 对于list和forward_list，指向容器其他位置的迭代器仍有效。\n\n- 对于string和vector，被删除元素之前的元素的迭代器仍有效。\n\n详细见书本p315。\n\n## 额外的 string 操作\n\n除了顺序容器共同的操作之外， string 类型还提供了一些额外的操作。\n\n### 构造 string 的其他方法\n\n使用下面这些方法可以构造 string ：\n\n以下 n, len2, pos2 都是无符号值。\n\n|方法|说明|\n|-|-|\n|string s(cp, n)|s是cp指向的数组中前n个字符的拷贝|\n|string s(s2, pos2)|s是 string s2 从下标 pos2 开始的字符拷贝|\n|string s(s2, pos2, len2)|s是 string s2 从下标 pos2 开始 len2 个字符的拷贝，不管 len2 的值是多少，构造函数至多拷贝 s2.size() - pos2 个字符。\n\n**substr 操作**\n\nsubstr 返回一个 string ，它是原始 string 的一部分或全部的拷贝。\n\n`s.substr(pos, n)` 返回一个 string ，包含s中从pos开始的n个字符的拷贝。pos默认为0,n默认为 s.size() - pos ，即拷贝从 pos 开始的所有字符。\n\n### 改变 string 的其他方法\n\nstring 类型支持顺序容器的赋值运算符以及 assign, insert, erase 操作。除此之外，它还定义了额外的 insert 和 erase 版本。即使用下标的版本。\n\n这些函数都拥有许多重载的版本。\n\nassign 版本还接受C风格字符串。\n\nappend 和 replace 是额外的成员函数， append 在 string 末尾进行插入操作， replace 替换内容，它是调用 erase 和 insert 的一种简写形式。\n\n### string 搜索操作\n\nstring 提供了6个搜索函数，它们都有4个重载版本。它们都返回一个 string::size_type 的值作为匹配位置（下标）。如果搜索失败，返回 string::npos ，其值为 -1 。\n\n可以给函数一个搜索的起始位置 pos ，它默认值是0：\n\n`auto pos = s.find_first_of(numbers, pos);`\n\n### compare 函数\n\n这是字符串比较函数，和C标准库的 strcmp 很相似。\n\n### 数值转换\n\n标准库提供了数值转换的函数。\n\n如果 string 不能转换成一个数值，那么会抛出一个 invalid_argument 的异常。如果转换得到的数值无法用任何类型来表示，则抛出一个 out_of_range 异常。\n\n这些函数有：\n\n- to_string\n- stoi, stol, stoul, stof等\n"
  },
  {
    "path": "notes/CppPrimer/ch10 泛型算法.md",
    "content": "# 第十章 泛型算法\n\n标准库并未给每个容器都定义成员函数来实现一些特殊的操作，如查找元素、替换或删除元素、重排元素等。而是定义了一组泛型算法。它们实现了一些经典算法的公共接口，可以用于不同类型的元素和多种容器类型，包括内置的数组类型。\n\n---\n\n## 概述\n\n大多数算法定义在头文件algorithm中，头文件numeric中定义了一组数值泛型算法。\n\n通常，算法并不直接操作容器，而是遍历由两个迭代器指定的一个元素范围来进行操作。\n\n算法不依赖于容器，但依赖于元素类型的操作。比如，find用元素类型的==运算符完成序列中的元素与给定值的比较。大多数算法提供了一种方法，允许我们使用自定义的操作来代替默认的运算符（即使用谓词）。\n\n## 初识泛型算法\n\n附录A按照操作方式列出了所有的算法。\n\n除了少数例外，标准库算法都对一个范围内的元素进行操作。我们将此元素范围称为“输入范围”。\n\n理解算法的最基本的方法就是了解它们是否读取元素、改变元素或是重排元素顺序。\n\n### 只读算法\n\n一些算法只会读取其输入范围内的元素，而从不改变元素。比如find。\n\n**操作两个序列的算法**\n\n举一个列子：equal算法，它比较两个序列中的元素。此算法接受三个迭代器：前两个表示第一个序列中的元素的范围，第三个表示第二个序列的首元素：\n\n```c++\n// roster2中的元素数目应该至少与roster1一样多\nequal(roster1.cbegin(), roster1.cend(), roster2.cbegin());\n```\n\n这样的算法基于一个非常重要的假设：它假定第二个序列至少与第一个序列一样长。\n\n### 写容器元素的算法\n\n一些算法将新值赋予序列中的元素。当我们使用这类算法时，必须注意确保序列原大小至少不小于我们要求算法写入元素数目（note：如容器大小足够）。\n\n这样的算法比如fill。\n\n**介绍back_inserter**\n\n一种保证算法有足够元素空间来容纳输出数据的方法是使用**插入迭代器**（insert iterator）。插入迭代器是一种向容器中添加元素的迭代器。当我们通过一个插入迭代器赋值时，一个与赋值号右侧值相等的元素被添加到容器中。\n\n### 重排元素的算法\n\n某些算法会重排容器中元素的顺序，比如sort，它利用元素类型的<运算符来实现排序。\n\n## 定义操作\n\n很多算法都会比较输入序列中的元素。默认情况下，这类算法使用元素类型的<或==运算符完成比较。标准库为这些算法定义了额外的版本，允许我们提供自己定义的操作来替代默认运算符。\n\n### 向算法传递函数\n\nsort接受第三个参数，此参数是一个谓词（predicate）。\n\n**谓词**\n\n谓词是一个可调用的表达式，其调用结果是一个能用作条件的值。标准库算法使用的谓词分为两类：\n\n- 一元谓词，意味着它们只接受单一参数\n\n- 二元谓词，意味着它们有两个参数\n\n接受谓词的算法对输入序列中的元素调用谓词。\n\n### lambda表达式\n\n我们传递给算法的谓词必须严格接受一个或两个参数，但是有时我们希望进行的操作需要更多的参数，超出了算法对谓词的限制。\n\n**介绍lambda**\n\n我们可以向一个算法传递任何类别的可调用对象，对于一个对象或一个表达式，如果可以对其使用可调用运算符，则称它为可调用的。\n\n一个lambda表达式表示一个可调用的代码单元。可以将其理解为一个未命名的内联函数。一个lambda表达式具有一个返回类型、一个参数列表和一个函数体。但与函数不同，lambda可以定义在函数内部。\n\n一个lambda表达式具有如下形式：\n\n```c++\n[capture list](parameter list) -> return type { function body }\n```\n\n其中，capture list是一个lambda所在函数中定义的局部变量的列表。\n\n可以忽略返回类型，这时会自动推断返回类型。\n\n```c++\nauto func = [](){ return 42; };\n```\n\n### lambda捕获和返回\n\n当定义一个lambda时，编译器生成一个与lambda对应的新的（未命名的）类类型。当向一个函数传递一个lambda时，同时定义了一个新类型和该类型的一个对象。类似地，当使用auto定义一个用lambda初始化的变量时，定义了一个从lambda生成的类型的对象。\n\n默认情况下，从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员。类似任何普通类的数据成员，lambda的数据成员也在lambda对象创建时被初始化。\n\n变量捕获的方式可以是值或引用。值捕获是变量的拷贝，引用捕获是变量的引用。\n\n!!!warning\n\t当以引用方式捕获一个变量时，必须保证在lambda执行时变量是存在的。\n\n**建议：**尽量保持lambda的变量捕获简单化。如果可能的话，应该避免捕获指针或引用。见p351。\n\n**隐式捕获**\n\n可以让编译器根据lambda体中的代码来推断要使用哪些变量。为了指示编译器推断捕获列表，应在捕获列表中写一个&或=。&告诉编译器采用捕获引用方式，=则表示采用值捕获方式。\n\n如：\n\n```c++\n// sz为隐式捕获，值捕获方式\nwc = find_if(words.begin(), words.end(),\n             [=](const string &s) { return s.size() >= sz; } );\n```\n\n详见lambda捕获列表，p352。\n\n**可变lambda**\n\n默认情况下，对于一个值拷贝的变量，lambda不会改变其值。如果希望改变，必须在参数列表后加上关键字mutable。\n\n```c++\nvoid fcn3()\n{\n    size_t v1 = 42;\n    // f可以改变它捕获的变量的值\n    auto f = [v1]() mutable { return ++v1; };\n    v1 = 0;\n    auto j = f(); // j为43\n}\n```\n\n### 参数绑定\n\n对于那种只在一两个地方使用的简单操作，lambda表达式是最有用的。如果需要在很多地方使用相同的操作，或者一个操作需要很多语句完成，通常应该定义一个函数。\n\n如果lambda的捕获列表为空，通常可以用函数来代替它。但如果捕获列表不为空就不能直接代替了。\n\n**标准库bind函数**\n\n为了解决这个问题，可以使用一个新的名为bind的标准库函数，它定义在头文件functional中。它接受一个可调用对象，生成一个新的可调用对象来“适应”原对象的参数列表。\n\n```c++\nauto newCallable = bind(callable, arg_list);\n```\n\nnewCallable本身是一个可调用对象，arg_list是一个逗号分隔的参数列表，对应给定的callable参数。即，当我们调用newCallable时，newCallable会调用callable，并传递给它arg_list中的参数。\n\narg_list中的参数可能包含形如`_n`的名字，这些参数是“占位符”，表示newCallable的参数。比如：`_1`为newCallable的第一个参数，`_2`为第二个参数。\n\n**使用placeholders名字**\n\n名字`_n`都定义在一个名为placeholders的命名空间中，这个命名空间本身定义在std命名空间中。\n\n一种简单的using语句是：\n\n```c++\nusing namespace namespace_name;\n```\n\n这种形式说明希望所有来自namespace_name的名字都可以在我们的程序中直接使用。如：\n\n```c++\nusing namespace std::placeholders;\n```\n\n这使得placeholders定义的所有名字都可用。\n\n## 再探迭代器\n\n除了每个容器的迭代器，标准库在头文件iterator中还定义了额外几种迭代器。\n\n- 插入迭代器：这些迭代器被绑定到一个容器上，可以用来向容器插入元素。\n\n- 流迭代器：这些迭代器被绑定到输入或输出流上，可以来遍历所关联的IO流。\n\n- 反向迭代器：这些迭代器向后而不是向前移动。\n\n- 移动迭代器：不拷贝其中的元素，而是移动它们。将在13.6.2节（p480页）介绍。\n\n### 插入迭代器\n\n插入器是一种迭代器适配器，它接受一个容器，生成一个迭代器，能实现向给定容器添加元素。当我们通过一个插入迭代器进行赋值时，该迭代器调用容器操作来向给定容器的指定位置插入一个元素。\n\n```c++\nit = t; // 在it指定的当前位置插入值t。\n```\n\n插入迭代器有三种类型，差异在于元素插入的位置：\n\n- back_inserter，创建一个使用push_back的迭代器。\n\n- front_inserter，创建一个使用push_front的迭代器。\n\n- inserter，创建一个使用insert的迭代器。此函数接受第二个参数，这个参数必须是一个指向给定容器的迭代器。元素将被插入到给定迭代器所表示的元素之前。\n\n### iostream迭代器\n\nistream_iterator读取输入流，ostream_iterator向一个输出流写数据。这些迭代器将它们对应的流当作一个特定类型的元素序列来处理。\n\n通过使用流迭代器，我们可以使用泛型算法从流对象读取数据以及向其写入数据。\n\n详细操作见p359。\n\n### 反向迭代器\n\n反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。对于反向迭代器，递增（以及递减）操作的含义会颠倒过来。\n\n可以通过rbegin, rend, crbegin, crend成员函数来获得反向迭代器。这些成员函数返回指向容器尾元素和首元素之前一个位置的迭代器。\n\n## 泛型算法结构\n\n任何算法的最基本的特性是它要求其迭代器提供哪些操作。算法所要求的迭代器操作可以分为5个迭代器类别。\n\n|迭代器|要求|\n|-|-|\n|输入迭代器|只读，不写；单遍扫描，只能递增|\n|输出迭代器|只写，不读；单遍扫描，只能递增|\n|前向迭代器|可读写；多遍扫描，只能递增|\n|双向迭代器|可读写；多遍扫描，可递增递减|\n|随机访问迭代器|可读写，多遍扫描，支持全部迭代器运算|\n\n### 5类迭代器\n\n类似容器，迭代器也定义了一组公共操作。一些操作所有迭代器都支持，另一些只有特定类别的迭代器才支持。\n\n如ostream_iterator只支持递增、解引用和赋值。vector、string、deque的迭代器除了这些操作，还支持递减、关系和算术运算。\n\n除了输出迭代器之外，一个高层类别的迭代器支持低层类别迭代器的所有操作。\n\n### 算法的形参模式\n\n大多数算法具有如下4种形式之一：\n\n```\nalg(beg, end, other args);\nalg(beg, end, dest, other args);\nalg(beg, end, beg2, other args);\nalg(beg, end, beg2, end2, other args);\n```\n\n其中，alg是算法名字，beg和end表述输入范围。几乎所有算法都有一个输入范围。\n\n**接受单个目标迭代器的算法**\n\ndest参数是一个表示算法可以写入目的位置的迭代器。算法假定（assume）：按其需要写入数据，不管写入多少个元素都是安全的。\n\n一般dest被绑定到一个插入迭代器或是一个ostream_iterator。插入迭代器会将新元素添加到容器中，因为保证空间是足够的。\n\n**接受第二个输入序列的算法**\n\n接受beg2或beg2和end2的算法用这些迭代器表示第二个输入范围。\n\n接受单独beg2的算法假定从beg2开始的序列与beg和end所表示的范围至少一样大。\n\n### 算法命名规范\n\n除了参数规范，算法还遵循一套命名和重载规范。\n\n**一些算法使用重载形式传递一个谓词**\n\n函数的一个版本用元素类型的运算符来比较元素；另一个版本接受一个额外的谓词参数，来代替<或==：\n\n```\nunique(beg, end);\nunique(beg, end, comp);\n```\n\n**\\_if版本的算法**\n\n接受一个元素值的算法通常有另一个不同名的（不是重载的）版本，该版本接受一个谓词代替元素值。接受谓词参数的算法都有附加的\\_if前缀：\n\n```\nfind(beg, end, val);\nfind_if(beg, end, pred);\n```\n\n**区分拷贝元素的版本和不拷贝的版本**\n\n默认情况下，重排元素的算法将重排后的元素写回给定的输入序列中。这些算法还提供另一个版本，将元素写到一个指定的输出目的位置。\n\n```\nreverse(beg, end);\nreverse_copy(beg, end, dest);\n```\n\n## 特定容器的算法\n\n链表类型list定义了几个成员函数形式的算法。通用版本的sort要求随机访问迭代器，因此不能用于list。\n\n链表类型定义的其他算法的通用版本可以用于链表，但代价太高。这些算法需要交换输入序列中的元素。一个链表可以通过改变元素间的链接而不是真的交换它们的值来快速“交换”元素。因此，这些链表版本的算法的性能比对应的通用版本好得多。\n\n这些算法见p369。\n\n**链表特有的操作会改变容器**\n\n多数链表特有的算法与通用版本的很相似，但不完全相同，其中一个至关重要的区别是链表版本的会修改底层的容器。例如， remove 的链表版本会删除指定的元素， unique 的链表版本会删除第二个和后继的重复元素。\n\n!!!note\n\t对于通用版本的，如 std::remove ，不会删除容器的元素。它只会迁移元素。之后需要调用 erase 才能执行确切的删除动作。"
  },
  {
    "path": "notes/CppPrimer/ch11 关联容器.md",
    "content": "# 第十一章 关联容器\n\n关联容器与顺序容器有着根本的不同：\n\n- 关联容器中的元素是按关键字来保存和访问的。\n\n- 顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的。\n\n关联容器支持高效的关键字查找和访问，有两个主要的关联容器：\n\n- map，其元素是一些关键字-值对，关键字起到索引作用，值则表示与之相关的数据。\n\n- set，每个元素只包含一个关键字。\n\n---\n\n## 使用关联容器\n\nmap是关键字-值对的集合，通常被称为关联数组。关联数组与“正常”数组类似，不同之处在于其下标不必是整数。我们通过一个关键字而不是位置来查找值。\n\nset就是关键字的简单集合。\n\n具体使用案例见书本p375。\n\n## 关联容器概述\n\n关联容器（有序的和无序的）都支持9.2节（第294页）中介绍的普通容器操作。关联容器不支持顺序容器的位置相关的操作，例如push_front。\n\n除了与顺序容器相同的操作之外，关联容器还支持一些顺序容器不支持的操作（见p388）和类型别名（见p381）。\n\n关联容器的迭代器都是双向的。\n\n### 定义关联容器\n\n```c++\nmap<string, size_t> word_count; // 空容器\nset<string> exclude = {\"the\", \"but\", \"and\"}; // 列表初始化\n\n// 三个元素；authors将姓映射为名\nmap<string, string> authors = {\n    {\"Joyce\", \"James\"},\n    {\"Austen\", \"Jane\"},\n    {\"Dickens\", \"Charles\"}\n};\n```\n\n**初始化multimap或multiset**\n\n一个map或set中的关键字必须是唯一的，即，对于一个给定的关键字，只能有一个元素的关键字等于它。\n\nmultimap和multiset没有此限制，它们都允许多个元素具有相同的关键字（这些元素会相邻存储）。\n\n### 关键字类型的要求\n\n对于有序容器，关键字类型必须定义元素比较的方法，默认情况下，标准库使用关键字类型的<运算符来比较两个关键字。\n\n**使用关键字类型的比较函数**\n\n用来组织一个容器中元素的操作的类型也是该容器类型的一部分。为了指定使用自定义的操作，必须在定义关联容器类型时提供此操作的类型（比如一个函数指针类型）。\n\n```c++\nbool compareIsbn(const Sales_data &lhs, const Sales_data &rhs)\n{\n    return lhs.isbn() < rhs.isbn();\n}\n\nmultiset<Sales_data, decltype(compareIsbn)*> bookstore(compareIsbn);\n```\n\n### pair类型\n\npair类型定义在头文件utility中。\n\n一个pair保存两个数据成员，pair是一个用来生成特定类型的模板。\n\n```c++\npair<string, string> anon; // 保存两个string\npair<string, vector<int>> line; // 保存string和vector<int>\n```\n\npair的默认构造函数对数据成员进行值初始化。也可以为每个成员提供初始化器：\n\n```c++\npair<string, string> author{\"James\", \"Joyce\"};\n```\n\npair的数据成员是public的，两个成员分别是first，second。\n\n**创建pair对象的函数**\n\n```c++\npair<string, int>\nprocess(vector<string> &v)\n{\n    // 处理v\n    if (!v.empty())\n        return {v.back(), v.back().size()}; // 列表初始化\n    else\n        return pair<string, int>(); // 隐式构造返回值\n}\n```\n\n## 关联容器操作\n\n除了表9.2(第295页)中列出的类型，关联容器还定义了这些类型：\n\n- key_type, 此容器类型的关键字类型\n\n- mapped_type, 每个关键字关联的类型，只适用于map\n\n- value_type, 对于set，与key_type相同，对于map, 为`pair<const key_type, mapped_type>`\n\n### 关联容器迭代器\n\n当解引用一个关联容器迭代器时，我们会得到一个类型为容器的value_type的值的引用。对map而言，value_type是一个pair类型。\n\n!!!note\n\t必须记住，一个map的value_type是一个pair，我们可以改变pair的值，但不能改变关键字成员的值。\n\n**set的迭代器是const的**\n\n与不能改名map元素的关键字一样，一个set中的关键字也是const的。可以用一个set迭代器来读取元素的值，但不能修改。\n\n**遍历关联容器**\n\nmap和set类型都支持begin和end操作，我们可以利用这些函数获取迭代器，然后用迭代器来遍历容器。\n\n```c++\nauto map_it = word_count.cbegin();\nwhile (map_it != word_count.cend()) {\n    // ...\n    ++map_it; // 递增迭代器，移动到下一个元素\n}\n```\n\n!!!note\n\t当使用一个迭代器遍历一个map、multimap、set或multiset时，迭代器按关键字升序遍历元素。\n\n**关联容器和算法**\n\n我们通常不对关联容器使用泛型算法。更多讨论见书本p383。\n\n### 添加元素\n\n关联容器的insert成员向容器中添加一个元素或一个元素范围。由于map和set包含不重复的关键字，因此插入一个已存在的元素对容器没有任何影响。\n\n**向map添加元素**\n\n对一个map进行insert操作时，必须记住元素类型是pair。\n\n```c++\nword_count.insert({word, 1});\nword_count.insert(make_pair(word, 1));\nword_count.insert(pair<string, size_t>(word, 1));\nword_count.insert(map<string, size_t>::value_type(word, 1));\n```\n\n**向multiset或multimap添加元素**\n\n由于一个multi容器中的关键字不必唯一，在这些类型上调用insert总会插入一个元素：\n\n```c++\nmultimap<string, string> authors;\n// 插入第一个元素\nauthors.insert({\"Barth, John\", \"Sot-Weed Factor\"});\n// 正确，添加第二个元素\nauthors.insert({\"Barth, John\"}, \"Lost in the Funhouse\");\n```\n\n对允许重复关键字的容器，接受单个元素的insert操作返回一个指向新元素的迭代器。\n\n### 删除元素\n\n关联容器定义了三个版本的erase：\n\n- 与顺序容器一样，传递给erase一个迭代器或一个迭代器范围来删除一个元素或一个元素范围。\n\n- 接受一个key_type参数，删除所有匹配给定关键字的元素（如果存在的话），返回实际删除的元素的数量。\n\n对于保存不重复关键字的容器，erase的返回值总是0或1。\n\n### map的下标操作\n\nmap和unordered_map容器提供了下标运算符和一个对应的at函数。\n\nset类型不支持下标操作，不能对一个multimap或一个unordered_multimap进行下标操作，因为这些容器中可能有多个值与一个关键字相关联。\n\nmap下标运算符接受一个索引获取与此关键字相关联的值，如果关键字不在map中，会为它创建一个元素并插入到map中，关联值将进行值初始化。\n\n**使用下标操作的返回值**\n\n当对一个map进行下标操作时，会获得一个mapped_type对象。\n\n当解引用一个map迭代器时，会得到一个value_type对象。\n\n!!!note\n\t与vector与string不同，map的下标运算符返回的类型与解引用map迭代器得到的类型不同。\n\n### 访问元素\n\n如果我们关心的只不过是一个特定元素是否已在容器中，使用find比较好。\n\n对于不允许重复关键字的容器，可能使用find还是count没什么区别。\n\n对于允许重复关键字的容器，count会统计有多少个元素有相同的关键字。\n\n## 无序容器\n\n无序容器不是使用比较运算符来组织元素，而是使用一个哈希函数和关键字类型的==运算符。\n\n在关键字类型的元素没有明显的序关系的情况下，无序容器是非常有用的。"
  },
  {
    "path": "notes/CppPrimer/ch12 动态内存.md",
    "content": "# 第十二章 动态内存\n\n我们的程序到目前为止只使用过静态内存或栈内存。\n\n- 静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。\n\n- 栈内存用来保存定义在函数内的非static对象。\n\n分配在静态或栈内存中的对象由编译器自动创建和销毁。\n\n- 对于栈对象，仅在其定义的程序块运行时才存在。\n\n- static对象在使用之前分配，在程序结束时销毁。\n\n除了静态内存和栈内存，每个程序还拥有一个内存池，这部分内存被称作自由空间或堆（heap）。程序用堆来存储动态分配（dynamically allocate）的对象。\n\n动态对象的生存周期由程序来控制，当动态对象不再使用时，我们的代码必须显示地销毁它们。\n\n---\n\n## 动态内存与智能指针\n\nC++中，动态内存的管理是通过一对运算符来完成的：\n\n- new，在动态内存中为对象分配空间并返回一个指向该对象的指针。\n\n- delete，接受一个动态对象的指针，销毁该对象，并释放与之关联的内存。\n\n为了更容易（同时也更安全）地使用动态内存，新的标准提供了两种智能指针（smart pointer）类型来管理动态对象。\n\n智能指针的行为类似常规指针，重要的区别是它负责自动释放所指向的对象。两种智能指针的区别在于管理底层指针的方式：\n\n- shared_ptr允许多个指针指向同一个对象；\n\n- unique_ptr则“独占”所指向的对象。\n\n- 标准库还定义了一个名为weak_ptr的伴随类，它是一种弱引用，指向shared_ptr所管理的对象。\n\n这些类型定义在memory头文件中。\n\n### shared_ptr类\n\n智能指针也是模板，当创建一个智能指针时，必须提供指向的类型：\n\n```c++\nshared_ptr<string> p1; // shared_ptr, 可以指向string\n```\n\n默认初始化的智能指针中保存着一个空指针。\n\n解引用一个智能指针返回它指向的对象。如果在一个条件判断中使用智能指针，效果就是检测它是否为空：\n\n```c++\nif (p1) *p1 = \"hi\";\n```\n\n更多的操作见p401。\n\n**make_shared函数**\n\n最安全的分配和使用动态内存的方法是调用标准库函数make_shared。此函数在动态内存中分配一个对象并初始化它，返回指向此对象的shared_ptr。\n\n```c++\n// 指向一个值为42的int的shared_ptr\nshared_ptr<int> p3 = make_shared<int>(42);\n\n// p6指向一个动态分配的空vector<string>\nauto p6 = make_shared<vector<string>>();\n```\n\n类似顺序容器的emplace成员，make_shared用其参数来构造给定类型的对象。如果我们不传递任何参数，对象就会进行值初始化。\n\n**shared_ptr的拷贝和赋值**\n\n每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象：\n\n```c++\nauto p = make_shared<int>(42); // p指向的对象只有p一个引用者\nauto q(p); // p和q指向相同的对象，此对象有两个引用者\n```\n\n可以认为每个shared_ptr都有一个关联的计数器，通常称其为**引用计数**(reference count)。无论何时我们拷贝一个shared_ptr，计数器都会递增。当我们给shared_ptr赋予一个新值或是shared_ptr被销毁时，计数器就会递减。\n\n一旦一个shared_ptr的计数器变为0，它就会自动释放自己所管理的对象。\n\n!!!note\n\t到底是由一个计数器还是其他数据结构来记录有多少指针共享对象，完全由标准库的具体实现决定。关键是智能指针类能记录有多少个shared_ptr指向相同的对象，并能在恰当的时候自动释放对象。\n\n**使用了动态生存期的资源的类**\n\n程序使用动态内存出于以下三种原因之一：\n\n1. 程序不知道自己需要多少对象\n\n2. 程序不知道所需对象的准确类型\n\n3. 程序需要在多个对象间共享数据\n\n容器类是出于第一种原因而使用动态内存的典型例子，我们将在第15章看到出于第二种原因的例子。本章介绍出于第三种原因的例子。\n\n### 直接管理内存\n\nC++提供了new运算符分配内存，delete运算符释放new分配的内存。\n\n相对于智能指针，使用这两个运算符管理内存非常容易出错。\n\n**使用new动态分配和初始化对象**\n\n在自由空间分配的内存是无名的，因此new无法为其分配的对象命名，而是返回一个指向该对象的指针：\n\n```c++\nint *pi = new int; // pi指向一个动态分配的、未初始化的无名对象\n```\n\n默认情况下，动态分配的对象是默认初始化的，这意味着内置类型或组合类型的对象的值将是未定义的，而类类型将使用默认构造函数进行初始化。\n\n可以使用直接初始化方式来初始化一个动态分配的对象：\n\n```c++\nint *pi = new int(1024);\n\nvector<int> *pv = new vector<int>{1, 2, 3};\n```\n\n**动态分配的const对象**\n\n用new分配const对象是合法的：\n\n```c++\nconst int *pci = new const int(1024);\n```\n\n类似其他任何const对象，一个动态分配的const对象必须进行初始化。\n\n**内存耗尽**\n\n一旦一个程序用光了它所有可用的内存，new表达式就会失败（并返回一个空指针）。默认情况下，如果new不能分配所要求的内存空间，它会抛出一个类型为bad_alloc的异常。\n\n我们可以改变使用new的方式来阻止它抛出异常：\n\n```c++\n// 如果分配失败，new返回一个空指针\nint *p1 = new int; // 如果分配失败，new抛出std::bad_alloc\nint *p2 = new (nothrow) int; // 如果分配失败，new返回一个空指针\n```\n\n**释放动态内存**\n\n为了防止内存耗尽，在动态内存使用完毕后，必须将其归还给系统。我们通过delete表达式（delete expression）来将动态内存归还给系统。\n\n```c++\ndelete p; // p必须指向一个动态分配的对象或是一个空指针\n```\n\n释放一块并非new分配的内存，或者将相同的指针值释放多次，其行为是未定义的。\n\n更多有关使用原生指针管理动态内存的危险的讨论见书本p409。\n\n### shared_ptr和new结合使用\n\n如果不初始化一个智能指针，它就会被初始化为一个空指针。还可以用new返回的指针来初始化智能指针：\n\n```c++\nshared_ptr<int> p2(new int(42)); // p2指向一个值为42的int\n```\n\n接受指针参数的智能指针构造函数是explicit的，因此必须使用直接初始化形式来初始化一个智能指针：\n\n```c++\nshared_ptr<int> p1 = new int(1024); // 错误：必须使用直接初始化形式\nshared_ptr<int> p2(new int(1024));  // 正确：使用了直接初始化形式\n```\n\n默认情况下，一个用来初始化智能指针的普通指针必须指向动态内存，因为智能指针默认使用delete释放它所关联的对象（可以提供自己的操作来替代delete）。\n\n更多关于智能指针使用的讨论见p412。\n\n### 智能指针和异常\n\n程序需要确保在异常发生后资源能被正确地释放。一个简单的确保资源被释放的方法是使用智能指针：\n\n```c++\nvoid f()\n{\n    shared_ptr<int> sp(new int(42)); // 分配一个对象\n    // 这段代码抛出一个异常，且在f中未被捕获\n} // 函数结束时shared_ptr自动释放内存\n```\n\n无论是否发生了异常，局部对象都会被销毁，sp是指向这块内存的唯一指针，因此内存会被释放掉。\n\n如果使用了内置指针管理内存，且在new之后在对应的delete之前发生了异常，则内存不会被释放：\n\n```c++\nvoid f()\n{\n    int *ip = new int(42); // 动态分配一个新对象\n    // 这段代码抛出一个异常，且在f中未被捕获\n    delete ip; // 在退出以前释放内存\n}\n```\n\n如果在new和delete之间发生了异常，且异常未在f中被捕获，则内存就永远不会被释放了。\n\n**使用我们自己的释放操作**\n\n这里给一个简单的定义删除器的例子，而具体的讨论见书本p416。\n\n```c++\nauto deleter = [](int* p)\n{   \n\tstd::cout << \"delete data: \" << *p << std::endl;\n\tdelete p;\n};  \n\nstd::shared_ptr<int> p(new int(42), deleter);\n```\n\n### unique_ptr\n\n与shared_ptr不同，某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时，它所指向的对象也被销毁。\n\n与shared_ptr不同，没有类似make_shared的标准库函数返回一个unique_ptr。当我们定义一个unique_ptr时，需要将其绑定到一个new返回的指针上。\n\n```c++\nunique_ptr<double> p1; // 可以指向一个double的unique_ptr\nunique_ptr<int> p2(new int(42)); // p2指向一个值为42的int\n```\n\n由于一个unique_ptr拥有它指向的对象，因此unique_ptr不支持普通的拷贝或赋值操作。\n\n更多有关unique_ptr操作的讨论见p418。\n\n### weak_ptr\n\nweak_ptr是一种不控制所指对象生存期的智能指针，它指向一个shared_ptr管理的对象。将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。一旦最后一个指向对象的shared_ptr被销毁，对象就会被释放。即使有weak_ptr指向对象，对象还是会被释放。\n\n当我们创建一个weak_ptr时，要用一个shared_ptr来初始化它：\n\n```c++\nauto p = make_shared<int>(42);\nweak_ptr<int> wp(p); // wp若共享p；p的引用计数未改变\n```\n\n由于对象可能不存在，我们不能使用weak_ptr直接访问对象，而必须调用lock。如果存在，lock返回一个指向共享对象的shared_ptr。否则返回一个空shared_ptr。\n\n```c++\nif (shared_ptr<int> np = wp.lock()) { // 如果np不为空则条件成立\n    // 在if中，np与p共享对象\n}\n```\n\n## 动态数组\n\nC++语言和标准库提供了两种一次分配一个对象数组的方法：\n\n- 一种new表达式语法，可以分配并初始化一个对象数组。\n\n- 标准库中包含一个名为allocator的类，允许我们将分配和初始化分离。使用allocator通常会提供更好的性能和更灵活的内存管理能力。\n\n!!!note\n\t大多数应用应该使用标准库容器而不是动态分配的数组。使用容器更为简单、更不容易出现内存管理错误并且可能有更好的性能。\n\n### new和数组\n\n为了让new分配一个对象数组，我们要在类型名之后跟一对方括号，在其中指明要分配的对象的数目：\n\n```c++\n// 调用get_size确定分配多少个int\nint *pia = new int[get_size()]; // pia指向第一个int\n```\n\n方括号中的大小必须是整型，但不必是常量。\n\n**分配一个数组会得到一个元素类型的指针**\n\n当用new分配一个数组时，我们并未得到一个数组类型的对象，而是得到一个数组元素类型的指针。\n\n!!!note\n\t要记住我们所说的动态数组并不是数组类型，这是很重要的。\n\n**初始化动态分配对象的数组**\n\n默认情况下，new分配的对象，不管是单个分配的还是数组中的，都是默认初始化的。可以对数组中的元素进行值初始化，方法是在大小之后跟一对空括号：\n\n```c++\nint *pia = new int[10]; // 10个未初始化的int\nint *pia2 = new int[10](); // 10个值初始化为0的int\n```\n\n新标准中，我们还可以提供一个元素初始化器的花括号列表：\n\n```c++\n// 10个int分别用列表中对应的初始化器初始化\nint *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};\n```\n\n**释放动态数组**\n\n为了释放动态数组，我们使用一种特殊形式的delete——在指针前加上一个空方括号对：\n\n```c++\ndelete p; // p必须指向一个动态分配的对象或为空\ndelete [] pa; // pa必须指向一个动态分配的数组或为空\n```\n\n数组的元素按逆序销毁，即，最后一个元素首先被销毁，然后是倒数第二个，依此类推。\n\n**智能指针和动态数组**\n\n标准库提供了一个可以管理new分配的数组的unique_ptr版本：\n\n```c++\n// up指向一个包含10个未初始化int的数组\nunique_ptr<int[]> up(new int[10]);\nup.release(); // 自动用delete[]销毁其指针\n```\n\n> my note: 这里似乎有错误，release方法据p418介绍，是放弃对指针的控制权，返回指针。并不销毁原来指向的对象。另一个事例见：http://zh.cppreference.com/w/cpp/memory/unique_ptr/release\n\n当unique_ptr销毁时，会自动销毁其指向的对象。\n\n### allocator类\n\nnew和delete有一些灵活性上的局限：\n\n- new将内存分配和对象构造组合在了一起。\n\n- delete将对象析构和内存释放组合在了一起。\n\n当分配一大块内存时，我们通常计划在这块内存上按需构造对象。在此情况下，我们希望将内存分配和对象构造分离。这意味着我们可以分配大块内存，但只在真正需要时才真正执行对象创建操作。\n\n**allocator类**\n\n标准库allocator类定义在头文件memory中，它帮助我们将内存分配和对象构造分离开来。它分配的内存是原始的、未构造的。\n\nallocator也是模板，为了定义一个allocator对象，我们必须指明这个allocator可以分配的对象类型。当一个allocator对象分配内存时，它会根据给定对象类型来确定恰当的内存大小和对齐位置：\n\n```c++\nallocator<string> alloc; // 可以分配string的allocator对象\nauto const p = alloc.allocate(n); // 分配n个未初始化的string\n```\n\n**allocator分配未构造的内存**\n\nallocator分配的内存是未构造的（unconstructed）。我们按需要在此内存中构造对象。\n\n```c++\nauto q = p; // q指向最后构造元素之后的位置\nalloc.construct(q++); // *q为空字符串\nalloc.construct(q++, \"hi\"); // *q为hi!\n```\n\n还未构造对象的情况下就使用原始内存是错误的。\n\n当我们用完对象后，必须对每个构造的元素调用destroy来销毁它们。\n\n```c++\nwhile (q != p)\n    alloc.destroy(--q); // 释放我们真正构造的string\n```\n\n一旦元素被销毁后，就可以重新用这部分内存来保存其他string，也可以将其归还给系统。释放内存通过调用deallocate来完成：\n\n```c++\nalloc.deallocate(p, n);\n```\n\n我们传递给deallocate的指针不能为空，它必须指向由allocate分配的内存。而且，传递给deallocate的大小参数必须与调用allocated分配内存时提供的大小参数具有一样的值。\n\n**拷贝和填充未初始化内存的算法**\n\n标准库为allocator类定义了两个伴随算法，可以在未初始化内存中创建对象。见p429。\n"
  },
  {
    "path": "notes/CppPrimer/ch13 拷贝控制.md",
    "content": "# 第十三章 拷贝控制\n\n当定义一个类时，我们显示或隐式地指定在此类型的对象拷贝、移动、赋值、销毁时做什么。一个类通过定义五种特殊的成员函数来控制这些操作，包括：拷贝构造函数（copy constructor）、拷贝赋值运算符（copy-assignment operator）、移动构造函数（move constructor）、移动赋值运算符（move-assignment operator）和析构函数（destructor）。\n\n这些操作称为**拷贝控制操作（copy control）**。\n\n如果一个类没有定义所有这些拷贝控制成员，编译器会自动为它定义缺失的操作。\n\n---\n\n## 拷贝、赋值与销毁\n\n### 拷贝构造函数\n\n如果一个构造函数的第一个参数是自身类类型的引用，且任何额外参数都有默认值，则此构造函数是拷贝构造函数。\n\n```c++\nclass Foo {\npublic:\n    Foo();               // 默认构造函数\n    Foo(const Foo&);     // 拷贝构造函数\n};\n```\n\n**合成拷贝构造函数**\n\n如果我们没有为一个类定义拷贝构造函数，编译器会为我们定义一个。合成拷贝构造函数（synthesized copy constructor）会将其参数的成员逐个拷贝到正在创建的对象中。\n\n每个成员的类型决定了它如何拷贝：对类类型的成员，会使用其拷贝构造函数来拷贝；内置类型的成员则直接拷贝；如果数组元素是类类型，则使用元素的拷贝构造函数来逐个进行拷贝。\n\n**拷贝初始化**\n\n拷贝初始化通常使用拷贝构造函数来完成。拷贝初始化不仅在用=定义变量时会发生，在下列情况下也会发生：\n\n- 将一个对象作为实参传递给一个非引用类型的形参\n\n- 从一个返回类型为非引用类型的函数返回一个对象\n\n- 从花括号列表初始化一个数组中的元素或一个聚合类中的成员\n\n某些类类型还会对它们所分配的对象使用拷贝初始化，如调用标准库容器的insert或push成员时。\n\n### 拷贝赋值运算符\n\n类可以控制其对象如何赋值：\n\n```c++\nSales_data trans, accum;\ntrans = accum;    // 使用Sales_data的拷贝赋值运算符\n```\n\n**重载赋值运算符**\n\n重载运算符（overloaded operator）本质上是函数，其名字是由operator关键字后接运算符符号组成。因此，赋值运算符就是一个名为operator=的函数。\n\n重载运算符的参数表示运算符的运算对象。某些运算符，包括赋值运算符，必须定义为成员函数。如果一个运算符是一个成员函数，其左侧运算对象就绑定到隐式的this参数。对于二元运算符，例如赋值运算符，其右侧运算对象作为显式参数传递。\n\n拷贝赋值运算符接受一个与其所在类相同类型的参数：\n\n```c++\nclass Foo {\npublic:\n    Foo& operator=(const Foo&);    // 赋值运算符\n};\n```\n\n**合成拷贝赋值运算符**\n\n如果一个类未定义自己的拷贝赋值运算符，编译器会为它合成一个。合成的版本会将右侧运算对象的每个非static成员赋予左侧运算符对象的对应成员。对于数组类型的成员，逐个赋值数组元素。\n\n### 析构函数\n\n析构函数执行与构造函数相反的操作：构造函数初始化对象的非static数据成员，还可能做一些其他工作；析构函数释放对象使用的资源，并销毁对象的非static数据成员。\n\n析构函数是一个类的成员函数，名字由一个波浪号接类名构成。它没有返回值，也不接受参数：\n\n```c++\nclass Foo {\npublic:\n    ~Foo();    // 析构函数\n};\n```\n\n由于析构函数不接受参数，因此它不能被重载。一个类只能有一个析构函数。\n\n**析构函数完成什么工作**\n\n在一个构造函数中，成员的初始化是在函数体执行之前完成的，且按照它们在类中出现的顺序进行初始化。\n\n在一个析构函数中，首先执行函数体，然后销毁成员。成员按初始化顺序逆序销毁。\n\n通常，析构函数释放对象在生存期分配的所有资源。\n\n析构的部分是隐式的，不存在像构造函数中初始化列表的东西控制成员如何销毁。成员销毁时发生什么完全依赖于成员的类型。销毁类类型的成员需要执行成员自己的析构函数。内置类型没有析构函数，因此销毁内置类型成员什么也不需要做。\n\nNote：隐式销毁一个内置类型指针的成员不会delete它所指向的对象。\n\n**什么时候会调用析构函数**\n\n无论何时一个对象被销毁，就会自动调用其析构函数：\n\n- 变量在离开其作用域时被销毁\n\n- 当一个对象被销毁时，其成员被销毁\n\n- 容器或数组被销毁时，其元素被销毁\n\n- 对于动态分配的对象，当对指向它的指针应用delete运算符时被销毁\n\n- 对于临时对象，当创建它的完整表达式结束时被销毁\n\n### 三/五法则\n\nC++语言不要求为一个类定义所有的拷贝控制操作，但是这些操作通常应该看成一个整体。\n\n**需要析构函数的类也需要拷贝和赋值操作**\n\n当要决定一个类是否要定义自己的拷贝控制成员时，一个基本原则是首先确定这个类是否需要一个析构函数。如果需要，那么几乎可以肯定它也需要一个拷贝构造函数和一个拷贝赋值运算符。\n\n原因分析如下：假如有一个类的构造函数中需要动态分配内存，在析构函数中释放动态内存。如果采用合成的拷贝和赋值操作，那么指向动态内存的指针就会被拷贝，当类的对象释放时，此指针指向的内存可能被释放两次，其结果是未定义的。\n\n**需要拷贝操作的类也需要赋值操作，反之亦然**\n\n如果一个类为一个对象分配一个独有的、唯一的序号。这个类就需要一个拷贝构造函数为每个新创建的对象生成一个新的、独一无二的序号。除此之外，这个拷贝构造函数从给定对象拷贝所有其他数据成员。这个类还需要定义拷贝赋值运算符来避免将序号赋予目的对象。\n\n此例子引入了第二个基本原则：如果一个类需要一个拷贝构造函数，几乎可以肯定它也需要一个拷贝赋值运算符。\n\n### 使用=default\n\n可以通过将拷贝控制成员定义为=default来显式地要求编译器生成合成的版本。\n\n### 阻止拷贝\n\n当使用某些拷贝控制操作没有合理意义的情况下，定义类时必须采用某种机制加以阻止。比如iostream类阻止了拷贝，以避免多个对象写入或读取相同的IO缓冲。\n\n**定义删除的函数**\n\n在新标准下，可以通过将拷贝构造函数和拷贝赋值运算符定义为**删除的函数（deleted function）**来阻止拷贝。\n\n删除的函数是这样一种函数：我们虽然声明了它们，但不能以任何方式使用它们。\n\n在函数的参数列表后接=delete来通知编译器，将它定义为删除的：\n\n```c++\nstruct NoCopy {\n    NoCopy(const NoCopy&) = delete;    // 阻止拷贝\n    NoCopy& operator=(const NoCopy&) = delete;    // 阻止赋值\n};\n```\n\n还可以对任何函数指定=delete。\n\n**析构函数不能是删除的成员**\n\n如果析构函数被删除，就无法销毁此类对象。对于一个删除了析构函数的类型（或者其某个成员删除了析构函数），编译器将不允许定义该类型的变量或创建该类型的临时对象。\n\n**合成的拷贝控制成员可能是删除的**\n\n对于某些类来说，编译器会把一些合成的成员定义为删除的函数。其规则是：如果一个类有数据成员不能默认构造、拷贝、复制或销毁，则对应的成员函数将被定义为删除的。\n\n细节见书本。\n\n**private拷贝控制**\n\n在新标准发布之前，类是通过将其拷贝构造函数和拷贝赋值运算符声明为private来阻止拷贝。为了阻止友元和成员函数访问私有成员，就不定义这些成员。\n\n## 拷贝控制和资源管理\n\n通常，管理类外资源的类必须定义拷贝控制成员。这种类需要通过析构函数来释放对象所分配的资源。一旦一个类需要析构函数，那么它几乎肯定也需要一个拷贝构造函数和一个拷贝赋值运算符。\n\n为了定义这些成员，必须先确定类对象的拷贝语义。一般有两种选择：可以定义拷贝操作，使类的行为看起来像一个值或者像一个指针。\n\n类的行为像一个值，意味着它应该有自己的状态。当拷贝一个对象时，副本和原对象是完全独立的。改变副本不会影响原对象，反之亦然。\n\n类的行为像一个指针，意味着拷贝一个对象时，副本和原对象使用相同的底层数据。改变副本也会改变原对象，反之亦然。\n\n详细讨论见书本和代码案例。\n\n使用这样的例子解释：\n\n```c++\nclass HasPtr {\npublic:\n     // 准备定义构造函数、拷贝构造函数、拷贝赋值运算符、析构函数\nprivate:\n    std::string *ps;    // 管理的类外资源\n    int i;\n};\n```\n\n### 行为像值的类\n\n对于类管理的资源，每个对象都应该拥有一份自己的拷贝。\n\n为了实现类值的行为，HasPtr需要：\n\n- 定义一个拷贝构造函数，完成string的拷贝，而不是拷贝指针\n\n- 定义一个析构函数来释放string\n\n- 定义一个拷贝赋值运算符来释放对象当前的string，并从右侧运算对象拷贝string\n\n**类值拷贝赋值运算符**\n\n一个好的模式是先将右侧运算对象拷贝到一个局部临时对象中。当拷贝完成后，销毁左侧对象的现有成员就是安全的了。接着再将数据从临时对象拷贝到左侧运算对象的成员中。\n\n这样就可以正确进行自赋值操作。\n\n### 定义行为像指针的类\n\n这种情况下，HasPtr仍然需要通过析构函数来释放string。但只有当最后一个指向string的HasPtr对象销毁时，它才可以释放string。\n\n令一个类展现类似指针的行为的最好方法是使用shared_ptr来管理类中的资源。shared_ptr类自己会记录有多少用户共享它所指向的对象，当没有用户使用对象时，shared_ptr类负责释放资源。\n\n但是有时候我们希望直接管理资源，这种情况下，可以使用**引用计数（reference count）**。\n\n**引用计数**\n\n引用计数的工作方式如下：\n\n- 除了初始化对象外，每个构造函数（除了拷贝构造函数）还要创建一个引用计数，用来记录有多少个对象与正在创建的对象共享状态。计数器初始化为1。\n\n- 拷贝构造函数不分配新的计数器，而是拷贝给定对象的数据成员，包括计数器。拷贝构造函数递增共享的计数器。\n\n- 析构函数递减计数器，如果变为0，则析构函数释放状态。\n\n- 拷贝赋值运算符递增右侧运算对象的计数器，递减左侧运算对象的计数器。如果左侧运算对象的计数器变为0，则销毁状态。\n\n引用计数应该保存在动态内存中，这样才能保证共享引用计数。\n\n## 交换操作\n\n管理资源的类通常还定义一个名为swap的函数。一些算法会在需要交换两个元素时调用swap。\n\n如果一个类定义了自己的swap，那么算法将使用类自定义的版本。否则算法将使用标准库定义的swap。标准库定义的版本可能像这样：\n\n```c++\nHasPtr temp = v1;\nv1 = v2;\nv2 = temp;\n```\n\n但对于HasPtr这样管理外部资源的类，可以直接交换指针，而不是分配多一个副本。\n\n```c++\nstring *temp = v1.ps;\nv1.ps = v2.ps;\nv2.ps = temp;\n```\n\nswap函数的存在是为了优化代码。详细定义方法见书本。\n\n**在赋值运算符中使用swap**\n\n定义swap的类通常用swap来定义它们的赋值运算符。这些运算符使用了一种名为**拷贝并交换（copy and swap）**的技术。\n\n```c++\nHasPtr &HasPtr::operator=(HasPtr rhs)\n{\n    swap(*this, rhs);\n    return *this;\n}\n```\n\nrhs是右侧运算对象的一个副本，它会在赋值运算符结束时被自动销毁。\n\n这种技术自动处理了自赋值的情况且天然就是异常安全的。\n\n## 对象移动\n\n新标准的一个最主要的特性是可以移动而非拷贝对象的能力。在很多情况下，对象拷贝后就立即销毁了，这种情况下，使用移动而非拷贝会大幅提升性能。\n\n使用移动而不是拷贝的另一个原因源于IO类或unique_ptr这样的类。这些类都包含不能被共享的资源（指针或IO缓冲）。因此，这些类的对象不能拷贝但可以移动。\n\n### 右值引用\n\n为了支持移动操作，新标准引入了一种新的类型——**右值引用（rvalue reference）**。右值引用必须绑定到右值——一个将要销毁的对象。因此，我们可以自由地将一个右值引用的资源移动到另一个对象中。\n\n```c++\nint i = 42;\nint &r = i;    // 正确：r引用i\nint &&rr = i;  // 错误：不能将一个右值引用绑定到一个左值上\nint &&r2 = i * 42; // 正确：将rr2绑定到乘法结果上\n```\n\n**左值持久，右值短暂**\n\n左值与右值的区别：左值有持久的状态，而右值要么是字面常量，要么是在表达式求值过程中创建的临时对象。\n\n**变量是左值**\n\n变量可以看作只有一个运算对象而没有运算符的表达式。类似其他任何表达式，变量表达式也有左值/右值属性。变量表达式都是左值。\n\n因此，我们不能将一个右值引用绑定到一个右值引用类型的变量上：\n\n```c++\nint &&rr1 = 42;     // 正确：字面常量是右值\nint &&rr2 = rr1;    // 错误：表达式rr1是左值！\n```\n\n**标准库move函数**\n\n虽然不能将一个右值引用直接绑定到一个左值上，但我们可以显式地将一个左值转换为对应的右值引用类型。方法是通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用。\n\n```c++\nint &&rr3 = std::move(i); // OK\n```\n\nmove调用告诉编译器：我们有一个左值，但我们希望像一个右值一样处理它。调用move就意味着承诺：除了对i赋值或销毁它外，我们将不再使用它。\n\n### 移动构造函数和移动赋值运算符\n\n移动的版本从给定对象“窃取”资源而不是拷贝资源。\n\n移动构造函数的第一个参数是该类型的一个右值引用。与拷贝构造函数一样，任何额外的参数都必须有默认实参。\n\n除了完成资源的移动，移动构造函数还必须确保移后源对象处于这样一个状态——销毁它是无害的。\n\n```c++\nStrVec::StrVec(StrVec &&s) noexcpet // 移动构造函数不应抛出异常\n // 成员初始化器接管s中的资源\n : elements(s.elements), first_free(s.first_free), cap(s.cap)\n{\n    // 令s进入这样的状态——对其运行析构函数是安全的\n    s.elements = s.first_free = s.cap = nullptr;\n}\n```\n\n**移动操作、标准库容器和异常**\n\n移动操作通常不分配资源，因此通常不会抛出异常，我们应当将此事通知标准库。除非标准库知道我们的移动构造函数不会抛出异常，否则它会认为移动我们的类对象时可能会抛出异常，并且为了处理这种可能性而做一些额外的工作。\n\n通知的方法是在构造函数中指明noexcept。\n\n详细的解释见书本p474。\n\n**移动赋值运算符**\n\n移动赋值运算符执行与析构函数和移动构造函数相同的工作。类似拷贝赋值运算符，移动赋值运算符必须正确处理自赋值：\n\n```c++\nStrVec& StrVec::operator=(StrVec &&rhs) noexcept\n{\n    // 直接检测自赋值\n    if (this != &rhs) {\n        free();    // 释放已有资源\n        elements = rhs.elements; // 接管资源\n        first_free = rhs.first_free;\n        cap = rhs.cap;\n        // 将rhs置于可析构状态\n        rhs.elements = rhs.first_free = rhs.cap = nullptr;\n    }\n    return *this;\n}\n```\n\n**移后源对象必须可析构**\n\n从一个对象移动数据并不会销毁此对象，但有时在移动操作完成后，源对象会被销毁。因此，当我们编写一个移动操作时，必须确保移后源对象进入一个可析构的状态。\n\n除了将移后源对象置为析构安全的状态之外，移动操作还必须保证对象仍然是有效的，即可以安全地为其赋予新值或者可以安全地使用而不依赖其当前值。但是移动操作对移后源对象中留下的值没有任何要求。因此，我们的程序不应该依赖于移后源对象中的数据。\n\n!!!warning\n    在移动操作之后，移后源对象必须保持有效的、可析构的状态，但是用户不能对其值进行任何假设。\n\n**合成的移动操作**\n\n如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符或者析构函数，编译器就不会为它合成移动构造函数和移动赋值运算符了。只有当一个类没有定义任何自己版本的拷贝控制成员，且类的每个非static数据成员都可以移动时，编译器才会为它合成移动构造函数或移动赋值运算符。\n\n如果类定义了一个移动构造函数或一个移动赋值运算符，则该类的拷贝版本会被定义为删除的。\n\n**移动右值，拷贝左值**\n\n如果一个类既有移动构造函数，也有拷贝构造函数，编译器使用普通的函数匹配规则来确定使用哪个构造函数。赋值操作的情况类似。\n\n```c++\nStrVec v1, v2;\nv1 = v2;                    // v2是左值，使用拷贝赋值\nStrVec getVec(istream&);    // getVec返回一个右值\nv2 = getVec(cin);           // getVec(cin)是一个右值；使用移动赋值\n```\n\n**更新三/五法则**\n\n所有五个拷贝控制成员应该看作一个整体：一般来说，如果一个类定义了任何一个拷贝操作，它就应该定义所有五个操作。这些类通常拥有一个资源，而拷贝成员必须拷贝此资源。一般来说，拷贝一个资源会导致一些额外开销。在这种拷贝并非必要的情况下，定义了移动构造函数和移动赋值运算符的类就可以避免此问题。\n\n## 右值引用和成员函数\n\n除了构造函数和赋值运算符之外，成员函数也可以提供拷贝和移动的版本：一个版本有一个右值引用参数，而另一个版本有一个const左值引用。\n\n```c++\nvoid push_back(const X&);   // 拷贝：绑定到任意类型的X\nvoid push_back(X&&);        // 移动：只能绑定到类型X的可修改的右值\n```\n\n**右值和左值引用成员函数**\n\n我们可以强制左侧运算对象是一个左值。\n\n我们指出this的左值/右值属性的方式与定义const成员函数相同，即，在参数列表后放置一个**引用限定符（reference qualifier）**。\n\n```c++\nclass Foo {\npublic:\n    Foo &operator=(const Foo&) &;   // 只能向可修改的左值赋值\n};\n```\n\n引用限定符可以是&或&&，分别指出this可以指向一个左值或右值。如果存在const限定符，引用限定符必须跟随在const限定符之后。"
  },
  {
    "path": "notes/CppPrimer/ch14 操作重载与类型转换.md",
    "content": "# 第十四章 操作重载与类型转换\n\n---\n\n## 基本概念\n\n重载的运算符是具有特殊名字的函数：它们的名字由关键字operator和其后要定义的运算符号共同组成。和其他函数一样，重载的运算符也包含返回类型、参数列表以及函数体。\n\n重载运算符函数的参数数量与该运算符作用的运算对象数量一样多。一元运算符有一个参数，二元运算符有两个。对于二元运算符来说，左侧运算对象传递给第一个参数，右侧运算对象传递给第二个参数。\n\n如果一个运算符函数是成员函数，则它的第一个（左侧）运算对象绑定到隐式的this指针上，因此，成员运算符函数的参数数量比运算符的运算对象总数少一个。\n\np491列出了可以重载的运算符。\n\n**直接调用一个重载的运算符函数**\n\n我们能像调用普通函数一样直接调用运算符函数：\n\n```c++\ndata1 + data2;\t\t\t\t// 普通的表达式\noperator+(data1, data2);\t// 等价的函数调用\n\ndata1 += data2;\t\t\t\t// 基于“调用”的表达式\ndata1.operator+=(data2);\t// 对成员运算符函数的等价调用\n```\n\n**某些运算符不应该被重载**\n\n- 逻辑与、逻辑或运算符，这些运算符指定了运算对象的求值顺序，又因为使用重载的函数运算符本质上是一次函数调用，那么求值顺序的规则无法得到应用。\n\n- 取地址运算符，逗号运算符，C++语言已定义了其特殊含义，不应该被重载，否则其行为将异于常态。\n\np492讨论了如何选择重载运算符。\n\n**选择作为成员或者非成员**\n\n下面的准则有助于选择将运算符定义为成员函数还是普通的非成员函数：\n\n- 赋值（=）、下标（[]）、调用（()）、成员访问箭头（->），必须是成员。\n\n- 复合赋值运算符一般来说应该是成员，但并非必须。\n\n- 改变对象状态的运算符或者与给定类型密切相关的运算符，如递增、递减和解引用运算符，通常应该是成员。\n\n- 具有对称性的运算符可能转换任意一端的运算对象，例如算术、相等性、关系和位运算等，通常应该是普通的非成员函数。\n\n## 输入和输出运算符\n\nIO标准库分别使用>>和<<执行输入和输出操作，IO库定义了用其读写内置类型的版本，而类则需要自定义适合其对象的新版本以支持IO操作。\n\n### 重载输出运算符<<\n\n通常情况下，输出运算符的第一个形参是一个非常量ostream对象的引用。是非常量是因为向流写入内容会改变其状态；是引用是因为无法复制一个ostream对象。\n\n第二个形参一般来说是一个常量的引用，它是我们想打印的类类型。是引用是因为我们希望避免复制实参；是常量是因为打印对象不会改变对象的内容。\n\n为了与其他输出运算符保持一致，operator<<一般要返回它的ostream形参。\n\n```c++\nostream &operator<<(ostream &os, const Sales_data &item)\n{\n\tos << item.isbn() << \" \" << item.units_sold << \" \"\n\t   << item.revenue << \" \" << item.avg_price();\n\n\treturn os;\n}\n```\n\n!!!note\n\t通常，输出运算符应该主要负责打印对象的内容而非控制格式，输出运算符不应该打印换行符。\n\n### 重载输入运算符>>\n\n通常情况下，输入运算符的第一个形参是运算符将要读取的流的引用，第二个形参是将要读入到的（非常量）对象的引用，返回某个给定流的引用。第二个形参之所以必须是个非常量是因为输入运算符本身的目的就是将输入读入到这个对象中。\n\n```c++\nistream& operator>>(istream &is, Sales_data &item)\n{\n\tdouble price; // 不需要初始化，因为我们将先读入数据到price，之后才使用它\n\tis >> item.bookNo >> item.units_sold >> price;\n\tif (is)\n\t\titem.revenue = item.units_sold * price;\n\telse\n\t\titem = Sales_data(); // 输入失败：对象被赋予默认状态\n\n\treturn is;\n}\n```\n\n!!!note\n\t输入运算符必须处理输入可能失败的情况，而输出运算符不需要。\n\n## 算术和关系运算符\n\n通常情况下，我们把算术运算符和关系运算符定义成非成员函数以允许对左侧或右侧的运算对象进行转换。因为这些运算符一般不需要改变运算对象的状态，所以形参都是常量的引用。\n\n如果类定义了算术运算符，则它一般也会定义一个对应的复合赋值运算符。此时，最有效的方式是使用复合赋值来定义算术运算符：\n\n```c++\nSales_data\noperator+(const Sales_data &lhs, const Sales_data &rhs)\n{\n\tSales_data sum = lhs;\t\t// 把lhs的数据成员拷贝给sum\n\tsum += rhs;\t\t\t\t\t// 把rhs加到sum中\n\treturn sum;\n}\n```\n\n### 相等运算符\n\n```c++\nbool operator==(const Sales_data &lhs, const Sales_data &rhs)\n{\n\treturn lhs.isbn() == rhs.isbn() &&\n\t       lhs.units_sold == rhs.units_sold &&\n\t       lhs.revenue == rhs.revenue;\n}\n\nbool operator!=(const Sales_data &lhs, const Sales_data &rhs)\n{\n\treturn !(lhs == rhs);\n}\n```\n\n## 赋值运算符\n\n之前介绍了拷贝赋值和移动赋值运算符，它们可以把类的一个对象赋值给类的另一个对象。此外，类还可以定义其他赋值运算符以使用别的类型作为右侧运算对象。\n\n比如：\n\n```c++\nvector<string> v;\nv = {\"a\", \"b\", \"c\"};\n```\n\n```c++\nclass StrVec {\npublic:\n\tStrVec& operator=(std::initializer_list<std::string>)\n\t{\n\t\t// ...\n\t}\n};\n```\n\n**复合赋值运算符**\n\n```c++\n// 作为成员的二元运算符：左侧运算对象绑定到隐式的this指针\nSales_data& Sales_data::operator+=(const Sales_data &rhs)\n{\n\tunits_sold += rhs.units_sold;\n\trevenue += rhs.revenue;\n\treturn *this;\n}\n```\n\n## 下标运算符\n\n表示容器的类通常可以通过元素在容器中的位置访问元素，这些类一般会定义下标运算符operator[]。\n\n为了与下标的原始定义兼容，下标运算符通常以所访问元素的引用作为返回值，这样做的好处是下标可以出现在赋值运算符的任意一端。最好同时定义下标运算符的常量版本和非常量版本，当用作于一个常量对象时，下标运算符返回常量引用以确保我们不会给返回的对象赋值。\n\n```c++\nclass StrVec {\npublic:\n\tstd::string& operator[](std::size_t n) { return elements[n]; }\n\n\tconst std::string& operator[](std::size_t n) const { return elements[n]; }\n\nprivate:\n\tstd::string *elements;\t// 指向数组首元素的指针\n};\n```\n\n## 递增和递减运算符\n\n在迭代器类中通常会实现递增运算符++和递减运算符--，这两种运算符使得类可以在元素的序列中前后移动。\n\n对于内置类型来说，递增和递减运算符既有前置版本也有后置版本。同样，我们也应该为类定义两个版本的递增和递减运算符。\n\n```c++\nclass StrBlobPtr {\npublic:\n\tStrBlobPtr& operator++();\t\t// 前置运算符\n\tStrBlobPtr& operator--();\n};\n```\n\n**区分前置和后置运算符**\n\n后置版本接受一个额外的（不被使用的）int类型的形参，这个形参的唯一作用就是区分前置版本和后置版本的函数，而不是真的要在实现后置版本时参与运算。\n\n```c++\nclass StrBlobPtr {\npublic:\n\tStrBlobPtr operator++(int);\t\t// 后置运算符\n\tStrBlobPtr operator--(int);\n};\n```\n\n!!!note\n\t为了与内置版本保持一致，后置运算符应该返回对象的原值，返回的形式是一个值而非引用。\n\n## 成员访问运算符\n\n在迭代器类及智能指针类中常常用到解引用运算符和箭头运算符。\n\n```c++\nclass StrBlobPtr {\npublic:\n\tstd::string& operator*() const;\n\tstd::string* operator->() const\n\t{\n\t\t// 将实际工作委托给解引用运算符\n\t\treturn & this->operator*();\n\t}\n};\n```\n\n## 函数调用运算符\n\n如果类重载了函数调用运算符，则我们可以像使用函数一样使用该类的对象。因为这样的类同时也能存储状态，所以与普通函数相比它们更灵活。\n\n```c++\nstruct absInt {\n\tint operator()(int val) const {\n\t\treturn val < 0 ? -val : val;\n\t}\n};\n```\n\n这个类只定义了一种操作：函数调用运算符，它负责接受一个int类型的实参，然后返回该实参的绝对值。\n\n使用调用运算符的方式是令一个absInt对象作用于一个实参列表，这一过程看起来非常像调用函数的过程：\n\n```c++\nint i = -42;\nabsInt absObj;\nint ui = absObj(i);\t\t// 将i传递给absObj.operator()\n```\n\n函数调用运算符必须是成员函数。一个类可以定义多个不同版本的调用运算符，相互之间应该在参数数量或类型上有所区别。\n\n如果类定义了调用运算符，则该类的对象称作函数对象（function object）。因为可以调用这种对象，所以我们说这些对象的行为像函数一样。\n\n### lambda是函数对象\n\n当我们编写了一个lambda后，编译器将该表达式翻译成一个未命名类的未命名对象。在lambda表达式产生的类中含有一个重载的函数调用运算符，默认情况下，它是一个const成员函数。\n\n当一个lambda表达式通过引用捕获变量时，将由程序负责确保lambda执行时引用所引用的对象确实存在。因此，编译器可以直接使用该引用而无须在lambda产生的类中将其存储为数据成员。\n\n通过值捕获的变量被拷贝到lambda中。因此，这种lambda产生的类必须为每个值捕获的变量建立对应的数据成员，同时创建构造函数，令其使用捕获的变量的值来初始化数据成员。\n\n### 标准库定义的函数对象\n\n标准库定义了一组表示算术运算符、关系运算符和逻辑运算符的类，每个类分别定义了一个执行命名操作的调用运算符。例如，plus类定义了一个函数调用运算符用于对一对运算对象执行+操作。\n\n这些类都被定义成模板的形式，我们可以为其指定具体的应用类型，这里的类型即调用运算符的形参类型。例如，`plus<string>`令string的加法运算符作用于string对象。\n\n```c++\nplus<int> intAdd;\t\t\t// 可执行int加法的函数对象\nint sum = intAdd(10, 20);\t// 使用intAdd::operator(int, int)求10和20的和\n```\n\np510列出了所有这些函数对象类，它们定义在functional头文件中。\n\n**在算法中使用标准库函数对象**\n\n表示运算符的函数对象类常用来替换算法中的默认运算符。比如，默认情况下排序算法使用operator<将序列按照升序排列。如果要执行降序排列的话，我们可以传入一个greater类型的对象。\n\n```c++\n// 传入一个临时的函数对象用于执行两个string对象的>比较运算\nsort(svec.begin(), svec.end(), greater<string>());\n```\n\n### 可调用对象与function\n\nC++语言中有几种可调用的对象：函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类。\n\n和其他对象一样，可调用对象也有类型。lambda有它自己唯一的未命名的类类型；函数及函数指针的类型由其返回值和实参类型决定。\n\n然而，两个不同类型的可调用对象却可能共享同一种调用形式（call signature）。调用形式指明了调用返回的类型以及传递给调用的实参类型。一种调用形式对应一个函数类型，例如：\n\n```c++\nint(int, int)\n```\n\nfunction是一个模板，当创建一个具体的function类型时我们必须提供额外的信息，此额外信息是指该function类型能够表示的对象的调用形式：\n\n```c++\nfunction<int(int, int)>\n```\n\n这里声明的function类型，表示接受两个int、返回一个int的可调用对象：\n\n```c++\nfunction<int(int, int)> f1 = add;\t\t\t// 函数指针\nfunction<int(int, int)> f2 = divide();\t\t// 函数对象类的对象\nfunction<int(int, int)> f3 = [](int i, int j) { return i * j; };\t// lambda\n\ncout << f1(4, 2) << endl;\ncout << f2(4, 2) << endl;\ncout << f3(4, 2) << endl;\n```\n\n## 重载、类型转换与运算符\n\n在263页中我们看到由一个实参调用的非显示构造函数定义了一种隐式的类型转换，这种构造函数将实参类型的对象转换成类类型。我们同样能定义对于类类型的类型转换，通过定义类型转换运算符可以做到这一点。\n\n转换构造函数和类型转换运算符共同定义了**类类型转换（class-type conversions）**。\n\n### 类型转换运算符\n\n**类型转换运算符（conversion  operator）**是类的一种特殊成员函数，它负责将一个类类型的值转换成其他类型。其一般形式如下：\n\n```c++\noperator type() const;\n```\n\n其中type表示某种类型。类型转换运算符可以面向任意类型（除了void之外）进行定义，只要该类型能作为函数的返回类型。\n\n类型转换运算符既没有显式的返回类型，也没有形参，而且必须定义成类的成员函数。类型转换运算符通常不应该改变待转换对象的内容，因此，一般被定义成const成员。\n\n举个例子，我们定义一个比较简单的类，令其表示0到255之间的一个整数：\n\n```c++\nclass SmallInt {\npublic:\n\tSmallInt(int i = 0) : val(i)\n\t{\n\t\tif (i < 0 || i > 255)\n\t\t\tthrow std::out_of_range(\"Bad SmallInt value\");\n\t}\n\n\toperator int() const { return val; }\nprivate:\n\tstd::size_t val;\n};\n```\n\nSmallInt类的构造函数将算数类型的值转换成SmallInt对象，而类型转换运算符将SmallInt对象转换成int：\n\n```c++\nSmallInt si;\nsi = 4;\t\t\t// 4 -> SmallInt, 然后调用赋值运算符\nsi + 3;\t\t\t// si -> int，然后执行整数的加法\n```\n\n**类型转换运算符可能产生意外结果**\n\n在实践中，类很少提供类型转换运算符。但有一种例外：对于类来说，定义向bool的类型转换还是比较普遍的现象，但这会遇到一个问题：因为bool是一种算术类型，所以类类型的对象转换成bool后就能被用在任何需要算术类型的上下文中（这不是期望的）。\n\n**显式的类型转换运算符**\n\n为了防止上述异常情况的发生，C++新标准引入了显式的类型转换运算符（explicit conversion operator）：\n\n```c++\nclass SmallInt {\npublic:\n\t// 编译器不会自动执行这一类型转换\n\texplicit operator int() const { return val; }\n};\n```\n\n编译器不会将一个显式的类型转换运算符用于隐式类型转换：\n\n```c++\nSmallInt si = 3;\t\t\t// 正确：SmallInt的构造函数不是显式的\nsi + 3;\t\t\t\t\t\t// 错误：此处需要隐式的类型转换，但类的运算符是显式的\nstatic_cast<int>(si) + 3;\t// 正确：显式地请求类型转换\n```\n\n但，如果表达式被用作条件，则编译器会将显示的类型转换自动应用于它。\n\n!!!note\n\t向bool的类型转换通常用在条件部分，因此operator bool一般定义成explicit的。\n\n14.9.2节和14.9.3节讨论了避免二义性的类型转换和函数匹配遇到重载运算符时可选函数的问题，详细见书本p517。"
  },
  {
    "path": "notes/CppPrimer/ch15 面向对象程序设计.md",
    "content": "# 第十五章 面向对象程序设计\n\n---\n\n## OOP概述\n\n**面向对象程序设计（object-oriented  programming）**的核心思想是数据抽象、继承和动态绑定。\n\n通过数据抽象，我们可以将类的接口与实现分离。\n\n使用继承，可以定义相似的类型并对其相似关系建模。\n\n使用动态绑定，可以在一定程度上忽略相似类型的区别，而以统一的方式使用它们的对象。\n\n**继承**\n\n通过继承（inheritance）联系在一起的类构成一种层次关系。层次关系的根部有一个基类（base class），其他类直接或间接地从基类继承而来，这些继承得到的类称为派生类（derived class）。\n\n基类负责定义在层次关系中所有类共同拥有的成员，而每个派生类定义各自特有的成员。\n\n```c++\n// 基类的部分定义\nclass Quote {\npublic:\n\tstd::string isbn() const;\n\tvirtual double net_price(std::size_t n) const;\n};\n\nclass Bulk_quote : public Quote {\npublic:\n\tdouble net_price(std::size_t) const override;\n};\n```\n\n对于某些函数，基类希望它的派生类各自定义适合自身的版本，此时基类就将这些函数声明成**虚函数(virtual function)**。\n\n派生类必须通过使用**类派生列表（class deriveation list）**明确指出它是从哪个基类继承而来的，其形式是：首先一个冒号，后面紧跟以逗号分隔的基类列表，其中每个基类前面可以有访问说明符。\n\n派生类必须在其内部对所有重新定义的虚函数进行声明，最好在后面加上override关键字，以确保编译器为我们做语法检查。\n\n### 定义基类\n\n```c++\nclass Quote {\npublic:\n\tQuote() = default;\n\tQuote(const std::string &book, double sales_price) : bookNo(book), price(sales_price) {}\n\n\tstd::string isbn() const { return bookNo; }\n\t\n\tvirtual ~Quote() = default;\t// 对析构函数进行动态绑定\n\tvirtual double net_price(std::size_t n) const { return n * price; }\n\nprivate:\n\tstd::string bookNo;\n\nprotected:\n\tdouble price = 0.0;\n};\n```\n\n!!!note\n\t基类通常都应该定义一个虚析构函数，即使该函数不执行任何实际操作也是如此。\n\n**成员函数与继承**\n\n派生类可以继承其基类的成员，但遇到像net_price这样的与类型相关的操作时，派生类必须对其重新定义。即，派生类需要对这些操作提供自己的新定义以覆盖（override）从基类继承而来的旧定义。\n\n在C++语言中，基类必须将它的两种成员函数区分开来：\n\n- 一种是基类希望其派生类进行覆盖的函数，则将其定义为虚函数。\n\n- 另一种是基类希望派生类直接继承而不要改变的函数。\n\n当我们使用指针或引用调用虚函数时，该调用将被动态绑定。根据引用或指针所绑定的对象类型不同，该调用可能执行基类的版本，也可能执行某个派生类的版本。\n\n**访问控制与继承**\n\n派生类可以继承定义在基类中的成员，但派生类的成员函数不一定有权访问从基类继承而来的成员。\n\n派生类能访问公有成员、受保护的成员，不能访问私有成员。\n\n### 定义派生类\n\n```c++\nclass Bulk_quote : public Quote {\npublic:\n\tBulk_quote() = default;\n\tBulk_quote(const std::string &book, double p, std::size_t qty, double disc) : \n\t\tQuote(book, p), min_qty(qty), discount(disc) {}\n\n\t// 覆盖基类的函数版本以实现基于大量购买的折扣政策\n\tdouble net_price(std::size_t) const override;\n\nprivate:\n\tstd::size_t min_qty = 0;\t// 适用折扣最低购买量\n\tdouble discount = 0.0;\t\t// 折扣\n};\n```\n\n**派生类中的虚函数**\n\n派生类经常覆盖它继承的虚函数。如果派生类没有覆盖其基类中的某个虚函数，则该虚函数的行为类似于其他的普通成员，派生类会直接继承其在基类中的版本。\n\n派生类可以在它覆盖的函数前使用virtual关键字，但不是必须的。C++11新标准允许派生类显式地注明它使用某个成员函数覆盖了它继承的虚函数，办法函数后面加一个关键字override。\n\n**派生类对象及派生类向基类的类型转换**\n\n一个派生类对象包含多个组成部分：\n\n- 一个包含派生类自己定义的（非静态）成员的子对象。\n\n- 一个与派生类继承的基类对应的子对象，如果有多个基类，那么这样的子对象也有多个。\n\nC++标准并没有明确规定派生类的对象在内存中如何分布。\n\n我们能把派生类的对象当成基类对象来使用，而且也能将基类的指针或引用绑定到派生类对象的基类部分上。\n\n```c++\nQuote item;\t\t\t\t// 基类对象\nBulk_quote bulk;\t\t// 派生类对象\nQuote *p = &item;\t\t// p指向Quote对象\np = &bulk;\t\t\t\t// p指向bulk的Quote部分\n```\n\n这种转换称为**派生类到基类的转换(derived-to-base)**，这是一种隐式转换。\n\n**派生类的构造函数**\n\n尽管派生类对象中含有从基类继承而来的成员，但派生类并不能直接初始化这些成员。派生类必须使用基类的构造函数来初始化它的基类部分。\n\n!!!note\n\t每个类控制它自己的成员初始化过程。\n\n\t首先初始化基类的部分，然后按照声明的顺序依次初始化派生类的成员。\n\n**继承与静态成员**\n\n如果基类定义了一个静态成员，则在整个继承体系中只存在该成员的唯一实例。\n\n静态成员遵循通用的访问控制规则，如果基类中的成员是private的，则派生类无权访问它。如果某静态成员是可访问的，则我们既能通过基类使用它也能通过派生类使用它。\n\n**防止继承的发生**\n\n有时候我们会定义这样一种类，我们不希望其他类继承它，或者不想考虑它是否适合作为一个基类。为了实现这一目的，C++11新标准提供了一种防止继承发生的方法，即在类名后跟一个关键字final：\n\n```c++\nclass NoDerived final { /**/ };\t\t\t// NoDerived不能作为基类\n```\n\n### 类型转换与继承\n\n可以将基类的指针或引用绑定到派生类对象上有一层极为重要的含义：当使用基类的引用（或指针）时，实际上我们并不清除该引用（或指针）所绑定对象的真实类型。该对象可能是基类的对象，也可能是派生类的对象。\n\n!!!note\n\t和内置指针一样，智能指针类也支持派生类向基类的类型转换，这意味着我们可以将一个派生类对象的指针存储在一个基类的智能指针内。\n\n**静态类型与动态类型**\n\n表达式的静态类型在编译时总是已知的，它是变量声明时的类型或表达式生成的类型。\n\n动态类型则是变量或表达式表示的内存中的对象的类型。动态类型直到运行时才可知。\n\n```c++\ndouble ret = item.net_price(n);\n```\n\nitem的静态类型是Quote&，它的动态类型则依赖于item绑定的实参。\n\n如果表达式既不是引用也不是指针，则它的动态类型永远与静态类型一致。\n\n当我们用一个派生类对象为一个基类对象初始化或赋值时，只有该派生类对象中的基类部分会被拷贝、移动或赋值，它的派生类部分将被忽略掉。\n\n更多关于基类和派生类之间类型转换的讨论见p534。\n\n## 虚函数\n\n**对虚函数的调用可能在运行时才被解析**\n\n当某个虚函数通过指针或引用调用时，编译器产生的代码直到运行时才能确定应该调用哪个版本的函数。被调用的函数是与绑定到指针或引用上的对象的动态类型相匹配的那一个。\n\n**派生类中的虚函数**\n\n当我们在派生类中覆盖了某个虚函数时，可以再一次使用virtual关键字指出该函数的性质。然而这么做并非必须，因为一旦某个函数被声明成虚函数，则在所有派生类中它都是虚函数。\n\n覆盖的虚函数的形参类型必须与被它覆盖的基类函数完全一致。返回类型也必须与基类函数匹配。\n\n**final和override说明符**\n\n如果派生类定义了一个函数与基类中虚函数的名字相同但是形参列表不同，这仍然是合法的行为。编译器将认为新定义的这个函数与基类中原有的函数是相互独立的。这时，派生类的函数并没有覆盖掉基类中的函数，这往往是一种失误，我们原本很可能希望覆盖。\n\n在C++11新标准中我们可以使用override关键字来说明派生类中的虚函数，这么做的好处是编译器可以为我们发现上述错误。如果我们使用override标记了某个函数，但该函数并没有覆盖已存在的虚函数，此时编译器将报错。\n\n我们还能把某个虚函数函数指定为final，如果虚函数函数定义成了final，则之后任何尝试覆盖该函数的操作都将引发错误。\n\n```c++\nstruct D {\n\tvoid f(int) const final;\t// 不允许后续的其他类覆盖\n}\n```\n\n**虚函数与默认实参**\n\n虚函数可以拥有默认实参，如果某次函数调用使用了默认实参，则该实参值由本次调用的静态类型决定，即使用基类中定义的默认实参，即使实际运行的是派生类中的函数版本也是如此。\n\n如果虚函数使用默认实参，则基类和派生类中定义的默认实参最好一致。\n\n**回避虚函数机制**\n\n在某些情况下，我们希望对虚函数的调用不要进行动态绑定，而是强迫执行虚函数的某个特定版本。使用作用域运算符可以实现这一目的，例如：\n\n```c++\ndouble undiscounted = baseP->Quote::net_price(42);\n```\n\n该代码强行调用Quote的net_price函数，而不管baseP实际指向的对象到底是什么。该调用将在编译时完成解析。\n\n!!!WARNING\n\t1. 如果一个派生类虚函数需要调用它的基类版本，但是没有使用作用域运算符，则在运行时该调用将被解析为对派生类版本的自身调用，从而导致无限递归。\n\t2. 在构造函数和析构函数中调用虚函数无法完成动态绑定, 因为此时派生类处于 destroy 的状态, 使用它是不正确的.\n\n## 抽象基类\n\n通过在函数体的位置（即在声明语句的分号）书写=0就可以将一个虚函数说明为纯虚函数。\n\n```c++\ndouble net_price(std::size_t) const = 0;\n```\n\n**含有纯虚函数的类是抽象基类**\n\n抽象基类负责定义接口，而后续的其他类可以覆盖该接口。我们不能（直接）创建一个抽象基类的对象。\n\n!!!note\n\t但可以给 net_price 这样的纯虚函数一个实现，并显式地调用它。它可以作为给默认的实现给派生类型调用。\n\n## 访问控制与继承\n\n每个类分别控制自己的成员初始化过程，与之类似，每个类还分别控制着其成员对于派生类来说是否**可访问（accessible）**。\n\n**受保护的成员**\n\n一个类使用protected关键字来声明那些它希望与派生类分享但是不想被其他公共访问使用的成员。protected说明符可以看做是public和private中和后的产物：\n\n- 和私有成员类似，受保护的成员对于类的用户来说是不可访问的。\n\n- 和公有成员类似，受保护的成员对于派生类的成员和友元来说是可访问的。\n\n此外，protected还有一条重要的性质：\n\n- 派生类的成员或友元只能通过派生类对象来访问基类的受保护成员。派生类对于一个基类对象中受保护成员没有任何访问权限。\n\n上述规则可以用下面的例子来理解：\n\n```c++\nclass Base {\nprotected:\n\tint prot_mem;\n};\n\nclass Sneaky : public Base {\n\tfriend void clobber(Sneaky&);\t\t// 能访问Sneaky::prot_mem\n\tfriend void clobber(Base&);\t\t\t// 不能访问Base::prot_mem\n\tint j;\t\t\t\t\t\t\t\t// j默认是private\n};\n\n// 正确：clobber能访问Sneaky对象的private和protected成员\nvoid clobber(Sneaky &s) { s.j = s.prot_mem = 0; }\n\n// 错误：clobber不能访问Base的protected成员\nvoid clobber(Base &b) { b.prot_mem = 0; }\n```\n\n**公有、私有和受保护继承**\n\n某个类对其继承而来的成员的访问权限受到两个因素影响：\n\n- 一是在基类中该成员的访问说明符。\n\n- 二是在派生类的派生列表中的访问说明符。\n\n派生访问说明符对于派生类的成员（及友元）能否访问其直接基类的成员没什么影响，对基类成员的访问权限只与基类中的访问说明符有关。\n\n> my note: 派生列表中的访问说明符限制了派生类用户对继承而来的成员的访问权限，而基类的成员访问说明符限制了基类用户的访问权限，这里的用户也包括了派生类。\n\n**派生类向基类转换的可访问性**\n\n派生类向基类的转换是否可访问由使用该转换的代码决定，同时派生类的派生访问说明符也会有影响。假定D继承自B：\n\n- 只有当D公有地继承B时，用户代码才能使用派生类向基类的转换；如果D继承B的方式是受保护的或者私有的，则用户代码不能使用该转换。\n\n- 不论以什么方式继承B，D的成员和友元都能使用派生类向基类的转换；派生类向其直接基类的类型转换对于派生类的成员和友元来说都是可访问的。\n\n- 如果D继承B的方式是公有的或者受保护的，则D的派生类的成员和友元可以使用D向B的转换；反之，如果D继承B的方式是私有的，则不能使用。\n\n!!!tip\n\t对于代码中的某个给定节点来说，如果基类的公有成员是可访问的，则派生类向基类的类型转换也是可访问的；反之则不行。\n\n关于这个概念可以参考[==测试代码==](https://github.com/demon90s/CppStudy/tree/master/CppPrimer/labs/test_derived_to_base.cpp)\n\n**友元与继承**\n\n友元关系不能继承，基类的友元在访问派生类成员时不具有特殊性，类似的，派生类的友元也不能随意访问基类的成员。\n\n**改变个别成员的可访问性**\n\n有时候我们需要改变派生类继承的某个名字的访问级别，通过using声明可以达到这一目的：\n\n```c++\nclass Base {\npublic:\n\tstd::size_t size() const { return n; }\nprotected:\n\tstd::size_t n;\n};\n\nclass Derived : private Base {\npublic:\n\t// 令size成员保持public访问级别\n\tusing Base::size;\nprotected:\n\tusing Base::n;\n};\n```\n\n因为Derived使用了私有继承，所以继承而来的成员size和n默认情况下是Derived的私有成员。然而，我们使用using声明语句改变了这些成员的可访问性。\n\n通过在类的内部使用using声明语句，我们可以将该类的直接或间接基类中任何可访问成员标记出来。using声明语句中名字的访问权限由该using声明语句之前的访问说明符来决定。\n\n!!!note\n\t派生类只能为那些它可以访问的名字提供using声明。\n\n## 继承中的类作用域\n\n每个类定义自己的作用域，在这个作用域内我们定义类的成员。\n\n当存在继承关系时，派生类的作用域嵌套在其基类的作用域之内，所以派生类才能像使用自己的成员一样使用基类的成员。\n\n**在编译时进行名字查找**\n\n一个对象、引用或指针的静态类型决定了该对象的哪些成员是可见的。即使静态类型与动态类型可能不一致（当使用基类的引用或指针时会发生这种情况），但是我们能使用哪些成员仍然是由静态类型决定的。\n\n**名字冲突与继承**\n\n和其他作用域一样，派生类也能重用定义在其直接基类或间接基类中的名字，此时定义在内层作用域（即派生类）的名字将隐藏定义在外层作用域（即基类）的名字。\n\n**通过作用域运算符来使用隐藏的成员**\n\n作用域运算符将覆盖掉原有的查找规则。\n\n!!!note\n\t除了覆盖继承而来的虚函数之外，派生类最好不要重用其他定义在基类中的名字。\n\n## 构造函数与拷贝控制\n\n和其他类一样，位于继承体系中的类也需要控制当其对象执行一系列操作时发送什么样的行为，这些操作包括创建、拷贝、移动、赋值和销毁。\n\n### 虚析构函数\n\n继承关系对基类拷贝控制最直接的影响是基类通常应该定义一个虚析构函数，这样我们就能动态分配继承体系中的对象了。\n\n当我们delete一个动态分配的对象的指针时，将执行析构函数。如果该指针指向继承体系中的某个类型，则有可能出现指针的静态类型与被删除对象的动态类型不符的情况。我们通过在基类中奖析构函数定义成虚析构函数以确保执行正确的析构函数版本。\n\n```c++\nclass Quote {\npublic:\n\t// 如果我们删除的是一个指向派生类对象的基类指针，则需要虚析构函数\n\tvirtual ~Quote() = default;\t// 动态绑定析构函数\n};\n```\n\n!!!warning\n\t如果基类的析构函数不是虚函数，则delete一个指向派生类对象的基类指针将产生未定义的行为。\n\n之前介绍的经验准则说，如果一个类需要析构函数，那么它同样需要拷贝和赋值操作。但这里基类的析构函数并不遵顼这个准则，它是一个重要的例外。\n\n虚析构函数还将阻止合成移动操作。\n\n### 合成拷贝控制与继承\n\n基类或派生类的合成拷贝控制成员的行为与其他合成的构造函数、赋值运算符或析构函数类似：它们对类本身的成员依次进行初始化、赋值或销毁操作。此外，合成的成员还负责使用直接基类中对应的操作对一个对象的直接基类部分进行初始化、赋值或销毁的操作。例如：\n\n- 合成的Bulk_quote默认构造函数运行Disc_quote的默认构造函数，后者又运行Quote的默认构造函数。\n\n对于派生类的析构函数来说，它除了销毁派生类自己的成员外，还负责销毁派生类的直接基类；该直接基类又销毁它自己的直接基类，以此类推直至继承链的顶端。\n\n### 派生类的拷贝控制成员\n\n派生类的拷贝和移动构造函数在拷贝和移动自有成员的同时，也要拷贝和移动基类部分的成员。类似的，派生类赋值运算符也必须为其基类部分的成员赋值。\n\n和构造函数及赋值运算符不同的是，析构函数只负责销毁派生类自己分配的资源。派生类对象的基类部分是自动销毁的。\n\n**定义派生类的拷贝或移动构造函数**\n\n```c++\nclass Base { /**/ };\n\nclass D : public Base {\npublic:\n\t// 默认情况下，基类的默认构造函数初始化对象的基类部分\n\t// 要想使用拷贝或移动构造函数，我们必须在构造函数初始值列表中显式地调用该构造函数\n\tD(const D &d) : Base(d) {}\n\tD(const &&d) : Base(std::move(d)) {}\n};\n```\n\n**派生类赋值运算符**\n\n```c++\n// Base::operator=(const Base&) 不会被自动调用\nD &D::operator=(const D &rhs)\n{\n\tBase::operator=(rhs); // 为基类部分赋值\n}\n```\n\n**派生类析构函数**\n\n在析构函数体执行完成后，对象的成员会被隐式销毁。类似的，对象的基类部分也是隐式销毁的。因此，和构造函数及赋值运算符不同的是，派生类析构函数只负责销毁由派生类自己分配的资源：\n\n```c++\nclass D : public Base {\npublic:\n\t// Base::~Base被自动调用执行\n\t~D() { /* 该处由用户定义清除派生类成员的操作 */ }\n};\n```\n\n对象销毁的顺序正好与其创建的顺序相反：派生类析构函数首先执行，然后是基类的析构函数，以此类推，沿着继承体系的反方向直到最后。\n\n### 继承的构造函数\n\n在C++11新标准中，派生类能够重用其直接基类定义的构造函数。\n\n一个类只初始化它的直接基类，出于同样的原因，一个类也只继承其直接基类的构造函数。类不能继承默认、拷贝和移动构造函数。如果派生类没有直接定义这些构造函数，则编译器将为派生类合成它们。\n\n派生类继承基类构造函数的方式是提供一条注明了（直接）基类名的using声明语句。\n\n```c++\nclass Bulk_quote : public Disc_quote {\npublic:\n\tusing Disc_quote::Disc_quote; // 继承Disc_quote的构造函数\n\tdouble net_price(std::size_t) const;\n};\n```\n\n通常情况下，using声明语句只是令某个名字在当前作用域内可见。当当作用于构造函数时，using声明语句将令编译器产生代码。对于基类的每个构造函数，编译器都在派生类中生成一个形参列表完全相同的构造函数。\n\n在Bulk_quote类中，继承的构造函数等价于：\n\n```c++\nBulk_quote(const std::string &book, double price, std::size_t qty, double disc) :\n\tDisc_quote(book, price, qty, disc) {}\n```\n\n如果派生类含有自己的数据成员，则这些成员将被默认初始化。"
  },
  {
    "path": "notes/CppPrimer/ch16 模板与泛型编程.md",
    "content": "# 第十六章 模板与泛型编程\n\n模板是C++泛型编程的基础。\n\n模板就是函数或者类的公式，当使用模板时，编译器会把模板转换成特定的类或者函数。\n\n---\n\n## 定义模板\n\n### 函数模板\n\n一个函数模板（function template）就是一个公式，用来生成针对特定类型的函数版本。\n\n一个函数模板可能像下面这样：\n\n```c++\ntemplate<typename T>\nint template_compare(const T &t1, const T &t2)\n{\n    if (t1 < t2) return -1;\n\n    if (t1 > t2) return 1;\n\n    return 0;\n}\n```\n\ntemplate是关键字，之后尖括号内容叫**模板参数列表（template parameter list）**。\n\n!!!note\n\t在模板定义中，模板参数列表不能为空。\n\n当使用模板时，我们（隐式或显式地）指定模板实参（template argument），将其绑定到模板参数上。\n\ncompare函数声明了一个名为T的类型参数。在compare中，我们用名字T表示一个类型。而T表示的实际类型则在编译时根据compare的使用情况来确定。\n\n**实例化函数模板**\n\n当调用一个函数模板时，编译器会用函数实参推断出模板实参。即，当我们调用compare时，编译器使用实参的类型来确定绑定到模板参数T的类型。例如：\n\n```c++\ncout << compare(1, 0) << endl;\t// T为int\n```\n\n函数实参类型是int。编译器推断出模板实参为int，并将它绑定到模板参数T。\n\n编译器用推断出的模板参数实例化（instantiate）一个特定版本的函数。当编译器实例化一个模板时，它使用实际的模板实参代替对应的模板参数来创建出模板的一个新“实例”。\n\n**模板类型参数与非模板参数**\n\n一般来说，我们可以将类型参数看作类型说明符，就像内置类型或类类型说明符一样使用。类型参数可以用来指定返回类型或函数的参数类型，以及在函数体内用于变量声明或类型转换。\n\n类型参数前必须使用关键字class或typename。在模板参数列表中，他们的含义没什么不同。但看起来typename更加直观。\n\n除了定义类型参数，还可以在模板中定义非类型参数（nontype parameter）。一个非类型参数表示一个值而非一个类型。我们通过一个特定的类型名来指定非类型参数。\n\n当一个模板被实例化时，非类型参数被一个用户提供的或编译器推断出的值所代替。这些值必须是常量表达式，从而允许编译器在编译时实例化模板。\n\n**inline和constexpr的函数模板**\n\n可以把函数模板声明成inline或constexpr的，inline或constexpr说明符放在模板参数列表之后，返回类型之前：\n\n```c++\ntemplate<typename T> inline T min(const T&, const T&);\n```\n\n**模板编译**\n\n编译器遇到模板定义时，它并不生成代码。只有当我们实例化出模板的一个特定版本时，编译器才会生成代码。当我们使用（而不是定义）模板时，编译器才生成代码。\n\n这一个特性影响到了我们如何组织代码以及错误何时被检测到。\n\n通常，当我们调用一个函数时，编译器只需要掌握函数的声明。类似的，当我们使用一个类类型的对象时，类定义必须是可用的，但成员函数的定义不必已经出现。因此，我们将类定义和函数声明放在头文件中，而普通函数和类的成员函数的定义放在源文件中。\n\n模板则不同：为了生成一个实例化版本，编译器通常需要掌握函数模板或类模板成员函数的定义。因此，模板的头文件通常既包括声明也包括定义。\n\n!!!warning\n\t保证传递给模板的实参支持模板所要求的操作，以及这些操作在模板中能正确工作，是调用者的责任。\n\n### 类模板\n\n类模板（class template）是用来生成类的蓝图的。与函数模板不同之处是，编译器不能为类模板推断模版参数类型。为了使用类模板，我们必须在模板名后的尖括号中提供额外信息——用来代替模板参数的模板实参列表。\n\n类似函数模板，类模板以关键字template开始，后跟模板参数列表。在类模板的定义中，我们将模版参数当作替身，代替使用模板时用户需要提供的类型或值。\n\n**实例化类模板**\n\n当使用一个类模板时，我们必须提供额外信息。这些额外信息是显式模板实参（explicit template argument）列表，它们被绑定到模版参数。编译器使用这些模板实参来实例化出特定的类。\n\n!!!note\n\t一个类模板的每个实例都形成一个独立的类。类型`Blob<string>`与任何其他Blob类型都没有关联。\n\n**类模板的成员函数**\n\n我们既可以在类模板的内部，也可以在外部定义成员函数，且定义在类模板内的成员函数被隐式声明为内联函数。\n\n类模板的成员函数本身是一个普通函数。但是，类模板的每个实例都有其自己版本的成员函数。因此，类模板的成员函数具有和类模板相同的模板参数。\n\n例如对于Blob的成员应该是这样的：\n\n```c++\ntemplate<typename T>\nret-type Blob<T>::member_name(parm-list)\n```\n\n**模板与友元**\n\n当一个类包含一个友元声明时，类与友元各自是否是模板是相互无关的。\n\n- 如果一个类模板包含一个非模板友元，则友元被授权可以访问所有模板实例。\n\n- 如果友元自身是模板，类可以授权给所有友元模板实例，也可以只授权给特定实例。\n\n**一对一友好关系**\n\n类模板与另一个模板间友好关系的最常见的形式是建立对应实例及其友元间的友好关系。\n\n为了引用（类或函数）模板的一个特定实例，我们必须首先声明模板自身。一个模板声明包括模板参数列表：\n\n```c++\n// 前置声明，在Blob中声明友元所需要的\ntemplate<typename> class BlobPtr;\ntemplate<typename> class Blob;\t\t// 运算符==中的参数所需要的\ntemplate<typename T> bool operator==(const Blob<T>&, const Blob<T>&);\n\ntemplate<typename T> class Blob {\n\t// 每个Blob实例将访问权限授予相同类型实例化的BlobPtr和相等运算符\n\tfriend class BlobPtr<T>;\n\tfriend bool operator==<T>(const Blob<T>&, const Blob<T>&);\n};\n```\n\n!!!note\n\t注意上述模板参数列表只有一个typename，这样的前置声明是合法的。\n\n**通用和特定的模板友好关系**\n\n```c++\n// 前置声明，在将模板的一个特定实例声明为友元时要用到\ntemplate<typename T> class Pal;\n\nclass C {\n\tfriend class Pal<C>;\t\t\t\t\t// 用类C实例化的Pal是C的一个友元\n\ttemplate<typename T> friend class Pal2; // Pal2的所有实例都是C的友元，这种情况无须前置声明\n};\n```\n\n**模板类型别名**\n\n我们可以定义一个typedef来引用实例化的类：\n\n```c++\ntypedef Blob<string> StrBlob;\n```\n\n**类模板的static成员**\n\n类模板可以声明static成员：\n\n```c++\ntemplate<typename T> class Foo {\npublic:\n\tstatic std::size_t count() { return ctr; }\nprivate:\n\tstatic std::size_t ctr;\n};\n\ntemplate<typename T>\nsize_t Foo<T>::ctr = 0; // 定义并初始化ctr\n```\n\n每个Foo实例都有其自己的static成员实例。\n\n一个static成员函数只有在使用时才会实例化。\n\n### 模板参数\n\n类似函数参数的名字，一个模板参数的名字没有什么内在的含义。我们可以使用任何名字：\n\n```c++\ntemplate <typename Foo> Foo calc(const Foo& a, const Foo& b) {}\n```\n\n一个模板参数名的可用范围是在其声明之后，至模板声明或定义结束之前。模板参数会隐藏外层作用域中声明的相同名字。而且，在模版内不能重用模版参数名。\n\n```c++\ntypedef double A;\ntemplate <typename A, typename B> void f(A a, B b)\n{\n\tA tmp = a; \t// tmp的类型为模版参数A的类型，而非double\n\tdouble B;\t// 错误：重声明模板参数B \n}\n```\n\n**模板声明**\n\n模板声明必须包含模板参数：\n\n```c++\n// 声明但不定义compare和Blob\ntemplate <typename T> int compare(const T&, const T&);\ntemplate <typename T> class Blob;\n```\n\n**使用类的类型成员**\n\n当编译器遇到类似T::mem这样的代码时，它不会知道mem是一个类型成员还是一个static数据成员，直到实例化时才会知道。但是，为了处理模板，编译器必须知道名字是否表示一个类型。\n\n默认情况下，C++语言假定通过作用域运算符访问的名字不是类型。因此，如果我们希望使用一个模板类型参数的类型成员，就必须显式告诉编译器该名字是一个类型。我们通过使用关键字typename来实现这一点：\n\n```c++\ntemplate <typename T>\ntypename T::value top(const T &c) { /* ... */ }\n```\n\n**默认模板实参**\n\n就像我们能为函数参数提供默认实参一样，我们也可以提供默认模板实参（default template argument）。\n\n例如，可以重写compare，默认使用标准库的less函数对象模板：\n\n```c++\n// compare有一个默认模板实参，和一个默认函数实参\ntemplate <typename T, typename F = less<T>>\nint compare(const T &v1, const T &v2, F f = F())\n{\n\tif (f(v1, v2)) return -1;\n\tif (f(v2, v1)) return 1;\n\treturn 0;\n}\n```\n\n与函数默认实参一样，对于一个模板参数，只有当它右侧的所有参数都有默认实参时，它才可以有默认实参。\n\n**模板默认实参与类模板**\n\n无论何时使用一个类模板，我们都必须在模板名之后接上尖括号。尖括号指出类必须从一个模板实例化而来。\n\n如果一个类模板为其所有模版参数都提供了默认实参，且我们希望使用这些默认实参，就必须在模板名之后跟一个尖括号对：\n\n```c++\ntemplate <class T = int>\nclass Numbers {\npublic:\n\tNumbers(T v = 0) : val(v) {}\nprivate:\n\tT val;\n};\n\nNumbers<long long> ll_n;\nNumbers<> int_n;\t// 空<>表示我们希望使用默认类型\n```\n\n### 成员模板\n\n一个类（无论是普通类还是类模板）可以包含本身是模板的成员函数。这种成员被称为成员模板（member template）。成员模板不能是虚函数。\n\n更多内容详见p596。\n\n### 控制实例化\n\n当模板被使用时才会进行实例化，这一特性意味着，相同的实例可能出现在多个对象文件中。当两个或多个独立编译的源文件使用了相同的模板，并提供了相同的模板参数时，每个文件中就会有该模板的一个实例。\n\n在大系统中，在多个文件中实例化相同模板的额外开销可能非常严重。在新标准中，我们可以通过显式实例化（explicit instantiation）来避免这种开销。\n\n一个显式实例化有如下形式：\n\n```c++\nextern template declaration;\t// 实例化声明\ntemplate declaration;\t\t\t// 实例化定义\n```\n\ndeclaration是一个类或函数声明，例如：\n\n```c++\nextern tempalte class Blob<string>;\t// 声明而不实例化\n\ntemplate class Blob<string>;\t\t// 实例化类模板的所有成员\n```\n\n当编译器遇到extern模板声明时，它不会在本文件中生成实例化代码。将一个实例化声明为extern就表示承诺在程序其他位置有该实例化的一个非extern声明（定义）。对于一个给定的实例化版本，可能有多个extern声明，但必须只有一个定义。\n\n!!!warning\n\t对每个实例化声明，在程序中某个位置必须有其显式的实例化定义。\n\n显式的实例化定义会实例化所有成员。\n\n关于**效率与灵活性**的详细讨论见p599，书中拿shared_ptr和unique_ptr举例说明，对于shared_ptr，它的删除器需要间接保存（比如为一个指针）；而对于unique_ptr，它的删除器是类类型的一部分，在创建时就必须指定好，可以作为unique_ptr的成员。\n\n- 效率：unique_ptr在编译时绑定了删除器，避免了间接调用删除器的运行时开销。\n\n- 灵活性：shared_ptr在运行时绑定删除器，使用户重载删除器更为方便。\n\n## 模板实参推断\n\n对于函数模板，编译器利用调用中的函数实参来确定其模板参数。从函数实参来确定模板实参的过程被称为模板实参推断（template argument deduction）。在模板实参推断过程中，编译器使用函数调用中的实参类型来寻找模板实参，用这些模板实参生成的函数版本与给定的函数调用最为匹配。\n\n### 类型转换与模板类型参数\n\n我们在一次调用中传递给函数模板的实参被用来初始化函数的形参。\n\n与往常一样，顶层const无论是在形参中还是在实参中，都会被忽略。在其他类型转换中，能在调用中应用于函数模板的包括如下两项：\n\n- const转换：可以将一个非const对象的引用（或指针）传递给一个const的引用（或指针）形参。\n\n- 数组或函数指针转换：如果函数形参不是引用类型，则可以对数组或函数类型的实参应用正常的指针转换。一个数组实参可以转换为一个指向其首元素的指针。一个函数实参可以转换为一个该函数类型的指针。\n\n其他类型转换都不能应用于函数模板。编译器通常不是对实参进行类型转换，而是生成一个新的模板实例。\n\n**使用相同模板参数类型的函数形参**\n\n一个模板类型参数可以用作多个函数形参的类型，这时候传递给这些形参的实参必须具有相同的类型。\n\n```c++\nlong lng;\ncompare(lng, 1024);\t// 错误，不能实例化compare(long, int)\n```\n\n**正常类型转换应用于普通函数实参**\n\n函数模板可以有用普通类型定义的参数，这种函数实参不进行特殊处理：它们正常转换为对应形参的类型。\n\n```c++\n// 传递给os的实参会进行正常的类型转换。\ntemplate <typename T> ostream &print(ostream &os, const T &obj)\n{\n\treturn os << obj;\n}\n```\n\n### 函数模板显式实参\n\n在某些情况下，编译器无法推断出模板实参的类型。其他一些情况下，我们希望用户控制模板实例化。当函数返回类型与参数列表中任何类型都不同时，这两种情况最常出现。\n\n**指定显式模板实参**\n\n有这样的例子：\n\n```c++\n// 编译器无法推断T1，它未出现在函数参数列表中\ntemplate <typename T1, typename T2, typename T3>\nT1 sum(T2, T3);\n```\n\n没有任何函数实参的类型可用来推断T1的类型，每次调用sum时调用者都必须为T1提供一个显式模板实参（explicit template argument）。\n\n显示模板实参在尖括号中给出，位于函数名之后，实参列表之前：\n\n```c++\n// T1是显式指定的，T2和T3是从函数实参类型推断而来的\nauto val3 = sum<long long>(i, lng);\t// long long sum(int, long)\n```\n\n显式模板实参按由左至右的顺序与对应的模板参数匹配；第一个模板实参与第一个模板参数匹配，第二个实参与第二个参数匹配，以此类推。\n\n**正常类型转换应用于显式指定的实参**\n\n对于模板类型参数已经显式指定了的函数实参，也进行正常的类型转换：\n\n```c++\nlong lng;\ncompare(lng, 1024);\t\t\t// 错误，模板参数不匹配\ncompare<long>(lng, 1024);\t// 正确，实例化compare(long, long)\n```\n\n### 尾置返回类型与类型转换\n\n在一些情况下，要求显式指定模板实参会给用户增添额外负担，而且不会带来什么好处。例如：\n\n```c++\ntemplate <typename It>\n??? &fcn(It beg, It end)\n{\n\t// 处理序列\n\treturn *beg;\t// 返回序列中一个元素的引用\n}\n```\n\n我们知道，返回类型就是`decltype(*beg)`。但是，在编译器遇到函数的参数列表之前，beg都是不存在的，这时候必须使用尾置返回类型：\n\n```c++\n// 尾置返回允许我们在参数列表之后声明返回类型\ntemplate <typename It>\nauto fcn(It beg, It end) -> decltype(*beg)\n{\n\t// 处理序列\n\treturn *beg;\t// 返回序列中一个元素的引用\n}\n```\n\n**进行类型转换的标准库模板类**\n\n有时我们无法直接获得所需要的类型，比如，让上面的fcn函数返回一个元素的值而非引用：所有迭代器操作都不会生成元素（的拷贝），只能生成元素的引用。\n\n为了获得元素类型，我们可以使用标准库的类型转换（type transformation）模板。这些模板定义在头文件type_traits中。这个头文件中的类通常用于所谓的模板元程序设计，这一主题已经超出本书的范围。但，类型转换模板在普通编程中也很有用。\n\n书本p606列出了所有类型转换模板。\n\n可以使用remove_reference来获得元素的类型：\n\n```c++\n// 为了使用模板参数的成员，必须用typename\ntemplate <typename It>\nauto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type\n{\n\t// 处理序列\n\treturn *beg;\t// 返回序列中一个元素的拷贝\n}\n```\n\n### 函数指针和实参推断\n\n当我们用一个函数模板初始化一个函数指针或为一个函数指针赋值时，编译器使用指针的类型来推断模板实参。\n\n例如：\n\n```c++\ntemplate <typename T> int compare(const T&, const T&);\n// pf1指向实例int compare(const int&, const int&);\nint (*pf1)(const int&, const int&) = compare;\n```\n\n如果不能从函数指针类型确定模板实参，则产生错误。\n\n### 模板实参推断和引用\n\n有例子：\n\n```c++\ntemplate <typename T> void f(T &p);\n```\n\n非常重要的有两点：\n\n- 编译器会应用正常的引用绑定规则；\n\n- const是底层的，不是顶层的。\n\n**从左值引用函数参数推断类型**\n\n当一个函数参数是模板类型参数的一个普通（左值）引用时，只能传递给它一个左值（一个变量，或者一个返回引用的表达式）。实参可以是const类型，也可以不是。如果实参是const的，则T将被推断为const类型。\n\n```c++\ntemplate <typename T> void f1(T &p);\t\t// 实参必须是一个左值\n\n// 对f1的调用使用实参所引用的类型作为模板参数类型\nf1(i);\t\t// i是一个int，模板参数类型T是int\nf1(ci);\t\t// ci是一个const int，模板参数T是const int\nf1(5);\t\t// 错误，传递给一个&参数的实参必须是一个左值\n```\n\n如果一个函数参数的类型是const T&，可以传递给它任何类型的实参。当函数参数本身是const时，T的类型推断的结果不会是一个const类型。const已经是函数参数类型的一部分。\n\n```c++\ntemplate <typename T> void f2(const T&);\t// 可以接受一个右值\n// f2中的参数是const &；实参中的const是无关的\n// 在每个调用中，f2的函数参数都被推断为const int&\nf2(i);\t\t// i是一个int，模板参数T是int\nf2(ci);\t\t// ci是一个const int，但模板参数T是int\nf2(5);\t\t// 一个const &参数可以绑定到一个右值；T是int\n```\n\n**从右值引用函数参数推断类型**\n\n当一个函数参数是一个右值引用时（形如T&&），我们可以传递给它一个右值，当我们传递一个右值时，类型推断过程类似普通的推断过程。\n\n```c++\ntemplate <typename T> void f3(T&&);\nf3(42);\t\t// 实参是一个int类型的右值，版本参数T是int\n```\n\n**引用折叠和右值引用参数**\n\n通常我们不能将一个右值引用绑定到一个左值上。但是，C++语言定义了两个例外：\n\n- 一种是move这种标准库设施\n\n- 另一种是，当我们将一个左值（如i）传递给函数的右值引用参数，且此右值引用指向模板类型参数（T&&）时，编译器推断模板类型参数为实参左值引用类型。因此，假如i是一个int对象，调用f3(i)时，编译器推断T的类型为int&，而非int。\n\n这样，就间接创建了一个引用的引用，形成了“折叠”：\n\n- x& &, x& &&, x&& &折叠成x&\n\n- x&& &&折叠成x&&\n\n如果将引用折叠规则和右值引用的特殊类型推断规则组合在一起，则意味着对一个左值调用f3，编译器推断T为一个左值引用类型，从而函数参数间接实例化成一个引用的引用，又通过折叠，变成一个左值引用：\n\n```c++\nf3(i);\t\t// 实参是一个左值，模版参数T是int&\n\n// 实例化代码，无效的代码，用于演示\nvoid f3<int&>(int& &&);\t// 当T是int&时，函数参数为int& &&\n\n// 实例化代码，最终结果\nvoid f3<int&>(int&);\t// 当T是int&时，函数参数折叠为int&\n```\n\n!!!note\n\t如果一个函数参数是指向模版参数类型的右值引用（如，T&&），则可以传递给它任意类型的实参。如果将一个左值传递给这样的参数，则函数参数被实例化为一个普通的左值引用（T&）。\n\n如果不想让一个左值传递给一个参数是右值引用的函数，就需要重载函数模板：\n\n```c++\ntemplate <typename T> void f(T&&);\t\t// 绑定到非const右值\ntemplate <typename T> void f(const T&);\t// 绑定到左值和const右值\n```\n\n### 理解std::move\n\n标准库move函数是使用右值引用的模板的一个很好的例子。虽然不能直接将一个右值引用绑定到一个左值上，但可以用move获得一个绑定到左值上的右值引用。\n\n**std::move是如何定义的**\n\n```c++\n// 在返回类型和类型转换中也要用到typename\ntemplate <typename T>\ntypename remove_reference<T>::type&& move(T &&t)\n{\n\treturn static_cast<typename remove_reference<T>::type&&>(t);\n}\n```\n\nmove的函数参数T&&是一个指向模板类型参数的右值引用。通过引用折叠，此参数可以与任何类型的实参匹配。\n\n```c++\nstring s(\"hi!\"), s2;\ns2 = std::move(string(\"bye!\"));\t\t// 正确：从一个右值移动数据\ns2 = std::move(s1);\t\t\t\t\t// 正确：但在赋值之后，s1的值是不确定的\n```\n\n**从一个左值static_cast到一个右值引用是允许的**\n\n虽然不能隐式地将一个左值转换为右值引用，但我们可以用static_cast显式地将一个左值转换为一个右值引用。\n\n对于操作右值引用的代码来说，将一个右值引用绑定到一个左值的特性允许它们截断左值。\n\n### 转发\n\n某些函数需要将其一个或多个实参连同类型不变地转发给其他函数。在此情况下，我们需要保持被转发实参的所有性质，包括实参类型是否是const的以及实参是左值还是右值。\n\n书本上给出一个翻转函数的例子：\n\n```c++\n// 接受一个可调用对象和另外两个参数的模板\n// 对“翻转”的参数调用给定的可调用对象\n// flip1是一个不完整的实现：顶层const和引用丢失了\ntemplate <typename F, typename T1, typename T2>\nvoid flip1(F f, T1 t1, T2 t2)\n{\n\tf(t2, t1);\n}\n```\n\n如果f定义成这样，就会出现问题：\n\n```c++\nvoid f(int a, int &b)\n{\n\tcout << a << \" \" << ++b << endl;\n}\n```\n\n如果我们通过flip1调用f，f所做的改变就不会影响实参：\n\n```c++\nf(42, i);\t\t\t// f改变了实参i\nflip1(f, j, 42);\t// 通过flip1调用f不会改变j\n```\n\n> my note: 这是因为有这样的传递路径： j -> flip1的形参t1(int) -> f的形参b(int&)，因此，f只是改变了flip1的形参，flip1的形参不会改变它的实参j。\n\n**定义能保持类型信息的函数参数**\n\n通过将一个函数参数定义为一个指向模板类型参数的右值引用，我们可以保持其对应实参的所有类型信息。而使用引用参数（无论左值还是右值）使得我们可以保持const属性，因为在引用类型中的const是底层的。\n\n```c++\ntemplate <typename F, typename T1, typename T2>\nvoid flip2(F f, T1 &&t1, T2 &&t2)\n{\n\tf(t2, t1);\n}\n```\n\n!!!note\n\t如果一个函数参数是指向模板类型参数的右值引用（如T&&），它对应的实参的const属性和左值/右值属性将得到保持。\n\n这个版本的flip2虽然能接受一个左值引用了，但却不能接受右值引用参数的函数。例如：\n\n```c++\nvoid g(int &&i, int &j) {}\n```\n\n即使给flip2传递一个右值：\n\n```c++\nflip2(g, i, 42);\t// 错误：不能从一个左值实例化int&&\n```\n\n函数参数与其他任何变量一样，都是左值表达式。因此，flip2中对g的调用将传递给g的右值引用参数一个左值。\n\n> my note: 虽然由42推断出来的参数类型是int&&，但它作为一个函数参数传递给g，则它就是一个左值。见p471（变量是左值）。\n\n**在调用中使用std::forward保持类型信息**\n\n我们可以使用一个名为forward的新标准库设施来传递flip2的参数，它能保持原始实参的类型。forward定义在头文件utility中。forward必须通过显式模板实参来调用。forward返回该显式实参类型的右值引用。即，forward<T>的返回类型是T&&。\n\n通过返回类型上的引用折叠，forward可以保持给定实参是左值/右值属性：\n\n```c++\ntemplate <typename Type> intermediary(Type &&arg)\n{\n\tfinalFcn(std::forward<Type>(arg));\n}\n```\n\n- 如果实参是一个右值，则Type是一个普通类型，forward<Type>返回Type&&。\n\n- 如果实参是一个左值，则通过引用折叠，Type本身是一个左值引用类型。在此情况下，返回类型是一个指向左值引用类型的右值引用。再次对forward<Type>的返回类型进行引用折叠，将返回一个左值引用类型。\n\n!!!note\n\t当用一个指向模版参数类型的右值引用函数参数时（T&&），forward会保持实参类型的所有细节。\n\n使用forward，我们可以重写翻转函数：\n\n```c++\ntemplate <typename F, typename T1, typename T2>\nvoid flip(F f, T1 &&t1, T2 &&t2)\n{\n\tf(std::forward<T2>(t2), std::forward<T1>(t1));\n}\n```\n\n## 重载与模板\n\n函数模板可以被另一个模板或一个普通非模板函数重载。并在以下几方面影响函数匹配规则：\n\n- 对于一个调用，其候选函数包括所有模板实参推断成功的函数模板实例。\n\n- 候选函数总是可行的，因为模板实参推断会排除任何不可行的模板。\n\n- 可行函数按类型转换来排序，当然，可以用于函数模板调用的类型转换是非常有限的。\n\n- 如果一个函数有最好的匹配，则选择此函数，如果有多个函数提供同样好的匹配，则：\n\n\t- 如果其中只有一个是非模板函数，则选择此函数。\n\n\t- 如果只有多个函数模板，且其中一个模板最特例化，则选择此模板。\n\t\n\t- 否则，此调用有歧义。\n\n详细讨论见p615。\n\n## 可变参数模板\n\n一个可变参数模板（variadic template）就是一个接受可变数目参数的模板函数或模板类。可变数目的参数被称为**参数包**（parameter packet）。存在两种参数包：\n\n- 模板参数包，表示零个或多个模板参数；\n\n- 函数参数包，表示零个或多个函数参数。\n\n我们用一个省略号来指出一个模板参数或函数参数表示一个包。在一个模板参数列表中，class...或typename...指出接下来的参数表示零个或多个类型的列表；一个类型名后面跟一个省略号表示零个或多个给定类型的非类型参数的列表。在函数参数列表中，如果一个参数的类型是一个模板参数包，则此参数也是一个函数参数包。\n\n```c++\n// Args是一个模板参数包；rest是一个函数参数包\n// Args表示零个或多个模板类型参数\n// rest表示零个或多个函数参数\ntemplate <typename T, typename... Args>\nvoid foo(const T &t, const Args& ... rest);\n```\n\n与往常一样，编译器从函数的实参推断模板参数类型，对于一个可变参数模板，编译器还会推断包中参数的数目。\n\n```c++\nfoo(i, s, 42, d);\t\t// 包中有三个参数\nfoo(\"hi\");\t\t\t\t// 空包\n```\n\n编译器将实例化出这样的foo版本：\n\n```c++\nvoid foo(const int&, const string&, const int&, const double&);\nvoid foo(const char(&)[3]);\n```\n\n**sizeof运算符**\n\n如果需要知道包中有多少个元素时，可以使用sizeof...运算符，类似sizeof，sizeof...返回一个常量表达式，而且不会对其实参求值。\n\n```c++\ntemplate <typename ... Args> void g(Args ... args)\n{\n\tcout << sizeof...(Args) << endl;\t// 类型参数的数目\n\tcout << sizeof...(args) << endl;\t// 函数参数的数目\n}\n```\n\n### 编写可变参数函数模板\n\n我们可以使用一个initializer_list来定义一个可接受可变数目实参的函数。但是，所有实参必须具有相容的类型。当我们既不知道想要处理的实参的数目也不知道它们的类型时，可变参数函数是很有用的。\n\n这里给出一个案例print函数来说明如何编写一个可变参数函数。\n\n可变参数函数通常是递归的。第一步调用处理包中的第一个实参，然后用剩余实参调用自身。为了终止递归，我们还需要定义一个非可变参数的版本。\n\n```c++\n// 用来终止递归并打印最后一个元素的函数\n// 此函数必须在可变参数版本的print定义之前声明\ntemplate <typename T>\nostream &print(ostream &os, const T &t)\n{\n\treturn os << t;\n}\n\n// 包中除了最后一个元素之外的其他元素都会调用这个版本的print\ntemplate <typename T, typename... Args>\nostream &print(ostream &os, const T &t, const Args&... rest)\n{\n\tos << t << \", \";\n\treturn print(os, rest...); // 递归调用，打印其他实参\n}\n```\n\n!!!warning\n\t当定义可变参数版本的print时，非可变参数版本的声明必须在作用域中。否则，可变参数版本会无限递归。\n\n### 包扩展\n\n对于一个参数包，除了获取其大小外，我们能对它做的唯一的事情就是扩展（expand）它。当扩展一个包时，我们还要提供用于每个扩展元素的模式（pattern）。扩展一个包就是将它分解为构成的元素，对每个元素应用模式，获得扩展后的列表。我们通过在模式右边放一个省略号（...）来触发扩展操作。\n\n```c++\ntemplate <typename T, typename... Args>\nostream &\nprint(ostream &os, const T &t, const Args&... rest)\t// 扩展Args\n{\n\tos << t << \", \";\n\treturn print(os, rest...);\t\t\t\t\t\t// 扩展rest\n}\n```\n\n第一个扩展操作扩展模板参数包，为print生成函数参数列表。第二个扩展操作出现在对print的调用中，此模式为print调用生成实参列表。\n\n对Args的扩展中，模式是const Args&，编译器将它应用到模板参数包Args中的每个元素。\n\n第二个扩展发生在对print的调用中，模式是函数参数包的名字（rest）。\n\n关于包扩展和其理解的更多详细的讨论见p621。\n\n## 模板特例化\n\n在某些情况下，通用模板的定义对特定类型是不合适的：通用定义可能编译失败或做的不正确。当我们不能（或不希望）使用模板版本时，可以定义类或函数模板的一个特例化版本。\n\n```c++\n// 可以比较任意两个类型\ntemplate <typename T> int compare(const T&, const T&);\n```\n\n上面的函数模板可能无法很好的处理字符指针，因此可以为它定义一个模板特例化（template specialization）版本。一个特例化版本就是模板的一个独立的定义，在其中一个或多个模板参数被指定为特定的类型。\n\n**定义函数模板特例化**\n\n当我们特例化一个函数模板时，必须为原模板中的每个模板参数都提供实参。为了指出我们正在实例化一个模板，应使用关键字template后跟一个空尖括号对。\n\n```c++\n// compare的特殊版本，处理字符数组的指针\ntemplate <>\nint compare(const char* const &p1, const char* const &p2)\n{\n\treturn strcmp(p1, p2);\n}\n```\n\n当我们定义一个特例化版本时，函数参数类型必须与一个先前声明的模板中对应的类型匹配。\n\n!!!note\n\t特例化的本质是实例化一个模板，而非重载它。因此，特例化不影响函数匹配。\n\n我们还可以特例化类模板，这一部分的讨论见p626。"
  },
  {
    "path": "notes/CppPrimer/ch17 标准库特殊设施.md",
    "content": "# 第17章 标准库特殊设施\n\n---\n\n## tuple类型\n\ntuple是类似pair的模板，但一个tuple可以有任意数量的成员。\n\n当我们希望将一些数据组合成单一对象，但又不想麻烦地定义一个新数据结构来表示这些数据时，tuple是非常有用的。\n\ntuple的一个常见用途是从一个函数返回多个值。书中给出的案例见p638。\n\n### 定义和初始化tuple\n\n当我们定义一个tuple时，需要指出每个成员的类型：\n\n```c++\ntuple<size_t, size_t, size_t> threeD;\t// 三个成员都设置为0\ntuple<string, int> someVal(\"constants\", 42);\n```\n\n当我们创建一个tuple对象时，可以使用tuple的默认构造函数，它会对每个成员进行值初始化。也可以为每个成员提供一个初始值。tuple的构造函数是explicit的，必须使用直接初始化语法。\n\n类似make_pair，标准库定义了make_tuple函数，用来生成tuple对象：\n\n```c++\n// 表示书店交易记录的tuple，包含: ISBN, 数量和每册书的价格\nauto item = make_tuple(\"0-999-78345-X\", 3, 20.00);\n```\n\nmake_tuple函数使用初始值的类型来推断tuple类型。本例中，item类型是`tuple<const char*, int, double>`。\n\n**访问tuple的成员**\n\ntuple的成员都是未命名的，要访问它们，就要使用get标准库函数模板，我们传递给get一个tuple对象，它返回指定成员的引用。\n\n```c++\nauto book = get<0>(item);\t\t\t// 返回item的第一个成员\nauto cnt = get<1>(item);\t\t\t// 返回item的第二个成员\n```\n\n> my note: 如果引用索引超出范围，那么编译阶段会报错。\n\n可以通过两个辅助类模板来查询tuple成员的数量和类型：\n\n```c++\ntypedef decltype(item) trans;\t\t// trans是itme的类型\n\n// 返回trans类型对象中成员的数量\nsize_t sz = tuple_size<trans>::value; // 返回3\n\n// cnt的类型与item中第二个成员相同\ntuple_element<1, trans>::type cnt = get<1>(item);\t// cnt是一个int\n```\n\n**关系和相等运算符**\n\ntuple的关系和相等运算符的行为类似容器的对应操作，这些运算符逐对比较左侧tuple和右侧tuple的成员。只有两个tuple具有相同数量的成员时并且成员也支持比较操作时，我们才能比较它们。\n\n## bitset类型\n\n标准库定义了bitset类，使得位运算的使用更为容易，并且能够处理超过最长整型类型大小的位集合。bitset类定义在头文件bitset中。\n\n### 定义和初始化bitset\n\nbitset类是一个类模板，它类似array类，具有固定的大小。当我们定义一个bitset时，需要声明它包含多少个二进制位。\n\n```c++\nbitset<32> bitvec(1U);\t// 32位；低位为1，其他位为0\n```\n\n二进制位的位置是从0开始编号的，因此，bitvec包含编号从0到31的32个二进制位。编号从0开始的二进制位被称为低位（low-order），编号到31结束的二进制位被称为高位（high-order）。\n\np641页列出了初始化一个bitset的方法。\n\n**使用unsigned值初始化bitset**\n\n当我们使用一个整型值来初始化bitset时，此值将被转换为unsigned long long类型并被当作位模式来处理。bitset中的二进制位将是此模式的一个副本。如果bitset的大小大于unsigned long long的位数，则剩余高位被置为0。如果小于，则只使用给定值中的低位，超出bitset大小的高位被丢弃。\n\n**从一个string初始化bitset**\n\n我们可以从一个string或一个字符数组指针来初始化bitset。两种情况下，字符都直接表示位模式。当我们使用字符串表示数时，字符串中下标最小的字符对应高位：\n\n```c++\nbitset<32> bitvec4(\"1100\");\t// 2、3两位为1，剩余两位为0\n```\n\n如果string包含的字符数比bitset少，则bitset的高位被置为0。\n\n!!!note\n\tstring的下标编号习惯于bitset恰好相反：string中下标最大的字符（最右）用来初始化bitset中的低位。\n\n### bitset操作\n\nbitset操作定义了多种检测或设置一个或多个二进制位的方法。见p643。\n\n```c++\nbitset<32> bitvec(1U);\nbool is_set = bitvec.any();\t\t\t// true，因为有1位置位\nbool is_not_set = bitvec.none();\t// false，因为有1位置位了\nbool all_set = bitvec.all();\t\t// false，因为只有1位置位了\nsize_t onBits = bitvec.count();\t\t// 返回1\nsize_t sz = bitvec.size();\t\t\t// 返回32\nbitvec.flip();\t\t\t\t\t\t// 翻转bitvec中的所有位\nbitvec.reset();\t\t\t\t\t\t// 将所有位复位\nbitvec.set();\t\t\t\t\t\t// 将所有位置位\n\nbitvec.flip(0);\t\t\t\t\t\t// 翻转第一位\nbitvec.set(0);\t\t\t\t\t\t// 置位第一位\nbitvec.reset(i);\t\t\t\t\t// 复位第i位\nbitvec.test(0);\t\t\t\t\t\t// 返回false，因为第一位已复位\n\nbitvec[0] = 0;\t\t\t\t\t\t// 将第一位复位\nbitvec[31] = bitvec[0];\t\t\t\t// 将最后一位设置为与第一位一样\n~bitvec[0];\t\t\t\t\t\t\t// 翻转第一位\n```\n\n**提取bitset的值**\n\nto_ulong和to_ullong操作都返回一个值，保存了与bitset对象相同的位模式，只有当bitset的大小小于等于对应的大小时，我们才能使用这两个操作，否则将会抛出overflow_error异常。\n\n```c++\nunsigned long ulong = bitvec3.to_ulong();\ncout << \"ulong = \" << ulong << endl;\n```\n\n**bitset的IO运算符**\n\n输入运算符从一个输入流读取字符，保存到一个临时的string对象中。直到读取的字符数达到对应bitset的大小时，或是遇到不是1或0的字符时，或是遇到文件尾或输入错误时，读取过程才停止。随即用临时string对象来初始化bitset。如果读取的字符数小于bitset的大小，高位被置为0。\n\n```c++\nbitset<16> bits;\ncin >> bits;\t\t// 从cin读取最多16个0或1\ncout << \"bits: \" << bits << endl;\n```\n\n## 正则表达式\n\n正则表达式（regular expression）是一种描述字符序列的方法，是一种极其强大的计算工具。本章节主要是介绍如何使用C++正则表达式库（RE库），它定义在头文件regex中，它包含多个组件：\n\n|组件|说明|\n|-|-|\n|regex|表示有一个正则表达式的类|\n|regex_match|将一个字符序列与一个正则表达式匹配|\n|regex_search|寻找第一个与正则表达式匹配的子序列|\n|regex_replace|使用给定格式替换一个正则表达式|\n|sregex_iterator|迭代器适配器，调用regex_search来遍历一个string中所有匹配的子串|\n|smatch|容器类，保存在string中搜索的结果|\n|ssub_match|string中匹配的子表达式的结果|\n\n> my note: 正则表达式库需要高级的gcc版本支持，我使用gcc5.3.1可以通过测试。\n\n函数regex_match和regex_search确定一个给定字符序列与一个给定regex是否匹配。如果整个输入序列与表达式匹配，则regex_match返回true；如果输入一个序列中一个子串与表达式匹配，则regex_search返回true。\n\n见书本p646更详细的讨论。\n\n## 随机数\n\n程序通常需要一个随机数源。在新标准出现之前，C和C++都依赖于一个简单的C库函数rand来生成随机数。此函数生成均匀分布的伪随机整数，每个随机数的范围在0和一个系统相关的最大值（至少为32767）之间。\n\nrand函数有一些问题：即使不是大多数，也有很多程序员需要不同范围的随机数。一些应用需要随机浮点数。一些程序需要非均匀分布的数。而程序员为了解决这些问题而试图转换rand生成的随机数的范围、类型或分布时，常常会引入非随机性。\n\n定义在头文件random中的随机数库通过一组协作的类来解决这些问题：随机数引擎（random-number engines）和随机数分布类（random-number distribution）。\n\n|组件|说明|\n|-|-|\n|引擎|类型，生成随机unsigned整数序列|\n|分布|类型，使用引擎返回服从特定概率分布的随机数|\n\n!!!note\n\tC++程序不应该使用库函数rand，而应使用default_random_engine类和恰当的分布类对象。\n\n### 随机数引擎和分布\n\n随机数引擎是函数对象类，它们定义了一个调用运算符，该运算符不接受参数并返回一个随机unsigned整数。我们可以通过调用一个随机数引擎对象来生成原始随机数。\n\n```c++\ndefault_random_engine e;\ncout << e() << endl;\t\t// 生成一个随机无符号数\n```\n\n标准库定义了多个随机数引擎类，区别在于性能和随机质量不同。\n\n**分布类型和引擎**\n\n为了得到一个指定范围内的数，我们使用一个分布类型的对象：\n\n```c++\n// 生成0到9之间（包含）均匀分布的随机数\nuniform_int_distribution<unsigned> u(0, 9);\ndefault_random_engine e;\ncout << u(e) << endl;\n```\n\n分布类型也是函数对象类。分布类型定义了一个调用运算符，它接受一个随机数引擎作为参数。分布对象使用它的引擎参数生成随机数，并将其映射到指定的分布。\n\n!!!note\n\t当我们说随机数发生器时，是指分布对象和引擎对象的组合。\n\n**引擎生成一个数值序列**\n\n随机数发生器有一个特性经常会使新手迷惑：即使生成的数看起来是随机的，但对于一个给定的发生器，每次运行程序它都会返回相同的数值序列。一个函数如果定义了局部的随机数发生器，应该将其（包括引擎和分布对象）定义为static的。否则，每次调用函数都会生成相同的序列。\n\n> my note: 但是我实验发现，使用random_device引擎，如果不定义成static的，仍然可以生成不同的序列。而且分布类型不定义成static的，也可以生成不同的序列。\n\n**设置随机数发生器种子**\n\n我们通常希望每次运行程序都会生成不同的随机结果，可以通过提供一个种子（seed）来达到这一目的。种子就是一个数值，引擎可以利用它从序列中一个新位置重新开始生成随机数。\n\n```c++\ndefault_random_engine e1;\t\t// 使用默认种子\ne1.seed(42);\t\t\t\t\t// 调用seed设置一个种子值\ndefault_random_engine e2(42);\t// 使用给定的种子值\n```\n\n如果引擎种子相同，将生成相同的序列。\n\n选择一个好种子，是极其困难的，可能最常用的方法是调用系统函数time。它定义在头文件ctime中，它返回从一个特定时刻到当前经过了多少秒。\n\n```c++\ndefault_random_engine e(time(0));\t// 稍微随机些的种子\n```\n\n> my note: 使用random_device引擎为另一个引擎创建一个种子也是一种方法。\n\n后续内容讨论了其他随机数的分布，比如：生成随机实数、生成非均匀分布随机数等。\n\n## IO库再探\n\n### 格式化输入与输出\n\n除了条件状态外，每个iostream对象还维护一个格式状态来控制IO如何格式化的细节。格式状态控制格式化的某些方面，如整型是几进制、浮点值的精度、一个输出元素的宽度等。\n\n标准库定义了一组操纵符来修改流的格式状态。一个操纵符是一个函数或是一个对象，会影响流的状态。\n\n!!!warning\n\t当操纵符改变流的格式状态时，通常改变后的状态对所有后续IO都生效。\n\n**控制布尔值的格式**\n\n默认情况下，bool值打印为1或0，通过对流使用boolalpha操纵符来修改原有格式：\n\n```c++\ncout << boolalpha << true << \" \" << false << endl; // 输出：true false\n```\n\n一旦向cout“写入”了boolalpha，我们就改变了cout打印bool值的方式。后续打印bool值的操作都会打印true或false，为了取消格式的改变，noboolalpha：\n\n```c++\ncout << noboolalpha;\n```\n\n**指定整型值的进制**\n\n默认情况下，整型值的输入输出使用十进制。我们可以使用操纵符hex、oct、dec将其改为十六进制、八进制或是改回十进制。\n\n```c++\ncout << \"default: \" << 20 << endl;\ncout << \"octal: \" << oct << 20 << endl;\ncout << \"hex: \" << hex << 20 << endl;\ncout << \"decimal: \" << dec << 20 << endl; \n```\n\n**在输出中指出进制**\n\n当对流应用showbase操纵符时，会在输出结果中显示进制：\n\n- 前导0x表示十六进制。\n\n- 前导0表示八进制。\n\n- 无前导字符串表示十进制。\n\n```c++\ncout << showbase;\t// 打印整型值时显示进制\ncout << \"default: \" << 20 << endl;\ncout << \"octal: \" << oct << 20 << endl;\ncout << \"hex: \" << hex << 20 << endl;\ncout << \"decimal: \" << dec << 20 << endl;\ncout << noshowbase; // 恢复流状态 \n```\n\n**指定打印精度**\n\nsetprecision操纵符接受一个参数，用来设置精度。它定义在头文件iomanip中。\n\n```c++\ncout << setprecision(3);\ncout << sqrt(2.0) << endl;\t// 输出：1.41\n```\n\n更多操纵符见p669。\n\n### 未格式化的输入/输出操作\n\n标准库提供了一组低层操作，支持未格式化IO（unformatted IO）。这组操作允许我们将一个流当作一个无解释的字节序列来处理。\n\n**单字节操作**\n\n有几个未格式化操作每次一个字节地处理流，它们会读取而不是忽略空白符。\n\n```c++\n// 读写一个字符\nchar ch;\nwhile (cin.get(ch))\n\tcout.put(ch);\n```\n\n|操作|说明|\n|-|-|\n|is.get(ch)|从istream is读取下一个字节存入字符ch中。返回is|\n|os.put(ch)|将字符ch输出到ostream os。返回os|\n|is.get()|将is的下一个字节作为int返回|\n|is.putback(ch)|将字符ch放回is。返回is|\n|is.unget()|将is向后移动一个字节。返回is|\n|is.peek()|将下一个字节作为int返回，但不从流中删除它|\n\n详细讨论见p673。\n\n**多字节操作**\n\n一些未格式化IO操作一次处理大块数据。如果速度是要考虑的重点问题的话，这些操作是很重要的，这些操作要求我们自己分配并管理用来保存和提取数据的字符数组。\n\n书中未给出代码案例讲解，具体操作见p674。\n\n### 流随机访问\n\n标准库提供了一对函数，来定位（seek）到流中给定的位置，以及告诉（tell）我们当前的位置。\n\n在大多数系统中，绑定到cin、cout、cerr和clog的流不支持随机访问，因为这种操作对它们没有意义。对这些流调用seek和tell会导致运行时出错，将流置于一个无效状态。\n\n**seek和tell函数**\n\n为了支持随机访问，IO类型维护一个标记来确定下一个读写操作要在哪里进行。标准库实际上定义了两对seek和tell函数，差别在于名字的后缀是g还是p，g版本表示我们正在读取数据，而p版本表示我们正在写入数据。\n\n|操作|说明|\n|-|-|\n|tellg() tellp()|返回一个输入流中（tellg）或输出流中（tellp）标记的当前位置|\n|seekg(pos) seekp(pos)|在一个输入流或输出流中奖标记重定位到给定的绝对地址。pos通常是前一个tell返回的值|\n|seekp(off, from) seekg(off, from)|在一个输入流或输出流中，奖标记定位到from之前或之后off个字符，from可以是：beg（流开始位置）, cur（流当前位置）, end（流结尾位置）|\n\n从逻辑上讲，我们只能对istream使用g版本，对ostream使用p版本。iostream则可以使用g版本又可以使用p版本。\n\n详细案例及讨论见书本p677。"
  },
  {
    "path": "notes/CppPrimer/ch18 用于大型程序的工具.md",
    "content": "# 第18章 用于大型程序的工具\n\n与仅需几个程序员就能开发完成的系统相比，大规模编程对程序设计语言的要求更高。大规模应用程序的特殊要求包括：\n\n- 在独立开发的子系统之间协同处理错误的能力。\n\n- 使用各种库（可能包含独立开发的库）进行协同开发的能力。\n\n- 对比较复杂的应用概念建模的能力。\n\n这三种C++语言特性正好能满足上述要求：异常处理、命名空间和多重继承。\n\n---\n\n## 异常处理\n\n异常处理（exception handling）机制允许程序中独立开发的部分能够在运行时就出现的问题进行通信并做出相应的处理。异常使得我们能够将问题的检测与解决过程分离开来。程序的一部分负责检测问题的出现，然后解决该问题的任务传递给程序的另一部分。检测环节无须知道问题处理模块的所有细节，反之亦然。\n\n### 抛出异常\n\n在C++语言中，我们通过抛出（throwing）一条表达式来引发（raised）一个异常。被选中的处理代码是在调用链中与抛出对象类型匹配的最近的处理代码。\n\n当执行一个throw时，后面的语句将不再被执行。程序的控制权从throw转移到与之匹配的catch模块。该catch可能是同一个函数中的局部catch，也可能位于直接或间接调用了发生异常的函数的另一个函数中。控制权的转移有两个重要的含义：\n\n1. 沿着调用链的函数可能会提早退出。\n\n2. 一旦程序开始执行异常处理代码，则沿着调用链创建的对象将被销毁。\n\n!!!note\n\t1. 一个异常如果没有被捕获，则它将终止当前的程序。\n\t2. 析构函数不应该抛出异常。这是因为异常抛出时将调用对象的析构函数, 如果析构函数又抛出异常, C++就要多处理这个异常, 它还没有这个能力.\n\n**异常对象**\n\n异常对象（exception object）是一种特殊的对象，编译器使用异常抛出表达式来对异常对象进行拷贝初始化。如果表达式是类类型，则它必须含有一个可访问的析构函数和一个可访问的拷贝或移动构造函数。如果该表达式是数组类型或函数类型，则表达式将被转换成与之对应的指针类型。\n\n当我们抛出一条表达式时，该表达式的静态编译时类型决定了异常对象的类型。如果一条throw表达式解引用一个基类指针，而该指针实际指向的是派生类对象，则抛出的对象将被切掉一部分，只有基类部分被抛出。\n\n### 捕获异常\n\ncatch子句（catch clause）中的异常声明（exception declaration）看起来像是只包含一个形参的函数形参列表。\n\n声明的类型决定了处理代码所能捕获的异常类型。这个类型必须是完全类型，它可以是左值引用，但不能是右值引用。\n\n当进入一个catch语句后，通过异常对象初始化异常声明中的参数。如果catch的参数类型是非引用类型，则该参数是异常对象的一个副本；如果是引用类型，则他是异常对象的一个别名。\n\n要注意的一点是：异常声明的静态类型将决定catch语句所能执行的操作。如果catch的参数是基类类型，则catch无法使用派生类特有的任何成员。\n\n**查找匹配的处理代码**\n\n在搜寻catch语句的过程中，我们最终找到的catch未必是异常的最佳匹配。相反，挑选出来的应该是第一个与异常匹配的catch语句。\n\n!!!note\n\t如果在多个catch语句的类型之间存在着继承关系，则我们应该把继承链最底端的类（most derived type）放在前面，而将继承链最顶端的类（least derived type）放在后面。\n\n**重新抛出**\n\n有时，一个单独的catch语句不能完整地处理某个异常。在执行了某些校正操作之后，当前的catch可能会决定由调用链更上一层的函数接着处理异常。一条catch语句通过重新抛出（rethrowing）的操作将异常传递给另外一个catch语句：\n\n```\nthrow;\n```\n\n空的throw语句只能出现在catch语句或catch语句直接或间接调用的函数之内。如果在处理代码之外的区域遇到了空throw语句，编译器将调用terminate。\n\n一个重新抛出语句并不指定新的表达式，而是将当前的异常对象沿着调用链向上传递。\n\n```c++\ncatch (my_error &eObj) {\n\teObj.status = errCodes::serverErr;\t// 修改了异常对象\n\tthrow;\t\t\t\t\t\t\t\t// 异常对象的status成员是severErr\n}\ncatch (other_error eObj) {\n\teObj.status = errCodes::badErr;\t\t// 只修改了异常对象的局部副本\n\tthrow;\t\t\t\t\t\t\t\t// 异常对象的status成员没有改变\n}\n```\n\n**捕获所有异常的处理代码**\n\n为了一次性捕获所有异常，我们使用省略号作为异常声明，这样的处理代码称为捕获所有异常（catch-all）的处理代码，形如catch(...)。它可以与任意类型的异常匹配。\n\ncatch(...)通常与重新抛出语句一起使用，其中catch执行当前局部能完成的工作，随后重新抛出异常：\n\n```c++\nvoid manip() {\n\ttry {\n\t\t// 这里的操作将引发并抛出一个异常\n\t}\n\tcatch (...) {\n\t\t// 处理异常的某些特殊操作\n\t\tthrow;\n\t}\n}\n```\n\n!!!note\n\t如果catch(...)与其他几个catch语句一起出现，则catch(...)必须在最后的位置。出现在捕获所有异常语句后面的catch语句将永远不会被匹配。\n\n### noexception异常说明\n\n对于用户及编译器来说，预先知道某个函数不会抛出异常大有裨益。编译器确认函数不会抛出异常，它就能执行某些特殊的优化操作。\n\n在C++11新标准中，我们可以通过提供noexcept说明（noexcept specification）指定某个函数不会抛出异常，其形式是关键字noexcept紧跟在函数的参数列表后面：\n\n```c++\nvoid recoup(int) noexcept;\t\t\t\t// 不会抛出异常\nvoid alloc(int);\t\t\t\t\t\t// 可能抛出异常\n```\n\n对于一个函数来说，noexcept说明要么出现在该函数的所有声明语句和定义语句中，要么一次也不出现。\n\n**违反异常说明**\n\n编译器并不会在编译时检查noexcept说明。如果noexcept函数抛出了异常，程序就会调用terminate以确保遵守不在运行时抛出异常的承诺。\n\n更多关于noexcept的讨论见p691。\n\n## 命名空间\n\n多个库将名字放置在全局命名空间中将引发命名空间污染（namespace pollution）。\n\n命名空间（namespace）为防止名字冲突提供了更加可控的机制。命名空间分割了全局命名空间，其中每个命名空间是一个作用域。\n\n### 命名空间的定义\n\n一个命名空间的定义包含两部分：首先是关键字namespace，随后是命名空间的名字。\n\n```c++\nnamespace cplusplus_primer {\n\tclass Sales_data {};\n\tvoid Foo() {}\n}\n```\n\n命名空间既可以定义在全局作用域内，也可以定义在其他命名空间中，但是不能定义在函数或类的内部。\n\n全局作用域是隐式的，所以它没有名字。这样的形式`::member_name`表示全局命名空间中的一个成员。\n\n**命名空间可以是不连续的**\n\n编写如下命名空间定义：\n\n```c++\nnamespace nsp {\n\t// 相关声明\n}\n```\n\n可能是定义了一个名为nsp的新命名空间，也可能是为已经存在的命名空间添加一些新成员。\n\n命名空间的组织方式类似于我们管理自定义类及函数的方式：\n\n- 命名空间的一部分成员的作用是定义类，以及声明作为类接口的函数及对象，则这些成员应该置于头文件中。\n\n- 命名空间成员的定义部分则置于另外的源文件中。\n\n在程序中某些实体只能定义一次：如非内联函数、静态数据成员、变量等，命名空间中定义的名字也需要满足这一要求，我们可以通过上面的方式组织命名空间并达到目的。\n\n更多命名空间的定义方式见p698。\n\n### 使用命名空间的成员\n\n像namespace_name::member_name这样使用命名空间的成员显然非常烦琐，如果命名空间名字很长时尤其如此。\n\n**命名空间别名**\n\n命名空间别名（namespace alias）使得我们可以为命名空间的名字设定一个短得多的同义词。例如：\n\n```c++\nnamespace cplusplus_primer {};\n```\n\n我们可以为其设定一个短得多的同义词：\n\n```c++\nnamespace primer = cplusplus_primer;\n\nnamespace Qlib = cplusplus_primer::QueryLib;\n```\n\n!!!note\n\t一个命名空间可以有好几个同义词或别名，所有别名都与命名空间原来的名字等价。\n\n**using声明：扼要概述**\n\n一条using声明（using declaration）语句一次只引入命名空间的一个成员。它使得我们可以清楚地知道程序中所用的到底是哪个名字。它的有效范围从using声明的地方开始，一直到using声明所在的作用域结束为止。在此过程中，外层作用域的同名实体将被隐藏。\n\n**using指示**\n\nusing指示（using directive）和using声明类似的地方是，我们可以使用命名空间名字的简写形式；和using声明不同的是，我们无法控制哪些名字是可见的，因为所有名字都是可见的。\n\nusing指示以关键字using开始，后面是关键字namespace以及命名空间的名字。它使得某个命名空间中所有的名字都可见，简写的名字从using指示开始，一直到using指示所在的作用域结束都能使用。\n\n更多关于using指示的讨论见p703。\n\n### 类、命名空间与作用域\n\n对命名空间内部名字的查找遵循常规的查找规则：即由内向外依次查找每个外层作用域。外层作用域也可能是一个或多个嵌套的命名空间，直到最外层的全局命名空间查找过程终止。\n\n**实参相关的查找与类类型形参**\n\n当我们给函数传递一个类类型的对象时，除了在常规的作用域查找外还会查找实参类所属的命名空间。这一例外对于传递类的引用或指针的调用同样有效。\n\n还有更多的讨论见书本。\n\n### 重载与命名空间\n\nusing声明或using指示能将某些函数添加到候选函数集。\n\n**与实参相关的查找与重载**\n\n对于接受类类型实参的函数来说，其名字查找将在实参类所属的命名空间中进行。这条规则对于如何确定候选函数集也有影响。我们将在每个实参类（以及实参类的基类）所属的命名空间中搜寻候选函数。在这些命名空间中所有与被调用函数同名的函数都将被添加到候选集当中。\n\n```c++\nnamespace NS {\n\tclass Quote {};\n\tvoid display(const Quote&) {}\n}\n\nclass Bulk_item : public NS::Quote {};\n\nint main()\n{\n\tBulk_item book1;\n\tdisplay(book1);\n\treturn 0;\n}\n```\n\n我们传递给display的实参属于类类型Bulk_item，因此该调用语句的候选函数不仅应该在调用语句所在的作用域中查找，而且也应该在Bulk_item及其基类Quote所属的命名空间中查找。命名空间NS中声明的函数display(const Quote&)也将被添加到候选函数集当中。\n\nusing声明与using指示都会把命名空间的函数注入当前的作用域，并在调用时加入到候选函数中。这有可能造成二义性调用等重载问题，具体见书本上的讨论。\n\n## 多重继承与虚继承\n\n多重继承是指从多个直接基类中产生派生类的能力。多重继承的派生类继承了所以父类的属性。书本以动物体系为例进行讲解。\n\n### 多重继承\n\n在派生类的派生列表中可以包含多个基类：\n\n```c++\nclass Bear : public ZooAnimal {};\nclass Panda : public Bear, public Endangered {};\n```\n\n每个基类包含一个可选的访问说明符。对于派生类能够继承的基类个数，C++没有进行特殊规定；但是在某个给定的派生列表中，同一个基类只能出现一次。\n\n在多重继承关系中，派生类的对象包含有每个基类的子对象。在Panda对象中含有一个Bear部分（其中又含有一个ZooAnimal部分）、一个Endangered部分以及在Panda中声明的非静态数据成员。\n\n**派生类构造函数初始化所有基类**\n\n构造一个派生类对象将同时构造并初始化它的所有基类子对象。与从一个基类进行的派生一样，多重继承的派生类的构造函数初始值也只能初始化它的直接基类：\n\n```c++\n// 显式地初始化所有基类\nPanda::Panda(std::string name, bool onExhibit) : \n\tBear(name, onExhibit, \"Panda\"),\n\tEndangered(Endangered::critical) {}\n\n// 隐式地使用Bear的默认构造函数初始化Bear子对象\nPanda::Panda() :\n\tEndangered(Endangered::critical) {}\n```\n\n基类的构造顺序与派生列表中基类的出现顺序保持一致，而与派生类构造函数初始值列表中基类的顺序无关。一个Panda对象的初始化顺序如下：\n\n- ZooAnimal是整个继承体系的最终基类，最先初始化。\n\n- 接下来初始化Panda的第一个直接基类Bear。\n\n- 然后初始化Panda的第二个直接基类Endangered。\n\n- 最后初始化Panda。\n\n**继承的构造函数与多重继承**\n\n在C++11新标准中，允许派生类从它的一个或几个基类中继承构造函数。但是如果从多个基类中继承了相同的构造函数（即形参列表完全相同），则程序将产生错误：\n\n```c++\nstruct Base1 {\n\tBase1(const std::string&);\n};\n\nstruct Base2 {\n\tBase2(const std::string&);\n};\n\n// 错误，试图从两个基类中继承D1::D1(const string&)\nstruct D1 : public Base1, public Base2 {\n\tusing Base1::Base1;\t\t\t// 从Base1继承构造函数\n\tusing Base2::Base2;\t\t\t// 从Base2继承构造函数\n\t\n\t// 解决办法：必须自定义一个接受string的构造函数\n\tD1(const std::string &s) : Base1(s), Base2(s) {}\n};\n```\n\n**析构函数与多重继承**\n\n和往常一样，派生类的析构函数只负责清除派生类本身分配的资源，派生类的成员及基类都是自动销毁的。合成的析构函数体为空。\n\n析构函数的调用顺序正好与构造函数相反，在我们的例子中，析构函数的调用顺序是：~Panda, ~Endangered, ~Bear, ~ZooAnimal。\n\n**多重继承的派生类的拷贝与移动操作**\n\n与只有一个基类的继承一样，多重继承的派生类如果定义了自己的拷贝/赋值构造函数和赋值运算符，则必须在完整的对象上执行拷贝、移动或赋值操作。只有当派生类使用的是合成版本的拷贝、移动或赋值成员时，才会自动对基类部分执行这些操作。在合成的拷贝控制成员中，每个基类分别使用自己的对应成员隐式地完成构造、赋值或销毁等工作。\n\n### 类型转换与多个基类\n\n在只有一个基类的情况下，派生类的指针或引用能自动转换成一个可访问基类的指针或引用。多个基类的情况与之类似。我们可以令某个可访问的基类的指针或引用直接指向一个派生类对象。\n\n```c++\n// 接受Panda的基类引用的一系列操作\nvoid print(const Bear&);\nvoid highlight(const Endangered&);\nostream& operator<<(ostream&, const ZooAnimal&);\n\nPanda ying_yang(\"ying_yang\");\nprint(ying_yang);\t\t\t\t\t// 绑定到Bear&\nhighlight(ying_yang);\t\t\t\t// 绑定到Endangered&\ncout << ying_yang << endl;\t\t\t// 绑定到ZooAnimal&\n```\n\n编译器不会在派生类向基类的几种转换中进行比较和选择，因为在它看来转换到任意一种基类都一样好。如果存在如下所示的print重载形式：\n\n```c++\nvoid print(const Bear&);\nvoid print(const Endangered&);\n```\n\n那么通过Panda对象这样调用print函数将产生二义性编译错误：\n\n```c++\nprint(ying_yang);\n```\n\n与只有一个基类的继承一样，对象、指针和引用的静态类型决定了我们能够使用哪些成员。\n\n### 多重继承下的类作用域\n\n在只有一个基类的情况下，派生类的作用域嵌套在直接基类和间接基类的作用域中。查找过程沿着继承体系自底向上进行，直到找到所需的名字。派生类的名字将隐藏基类的同名成员。\n\n在多重继承的情况下，相同的查找过程在所有直接基类中同时进行。如果名字在多个基类中都被找到，则对该名字的使用将具有二义性。\n\n对于一个派生类来说，从它的几个基类中分别继承名字相同的成员是完全合法的，只不过在使用这个名字时必须明确指出它的版本。\n\n!!!warning\n\t当一个类拥有多个基类时，有可能出现派生类从两个或更多基类中继承了同名成员的情况。此时，不加前缀限定符直接使用该名字将引发二义性。\n\n### 虚继承\n\n尽管在派生列表中同一个基类只能出现一次，但实际上派生类可以多次继承同一个类。派生类可以通过它的两个直接基类分别继承同一个间接基类，也可以直接继承某个基类，然后通过另一个基类再一次间接继承该类。\n\n在默认情况下，派生类中含有继承链上每个类对应的子部分。如果某个类在派生过程中出现了多次，则派生类中将包含该类的多个子对象。\n\n在C++语言中我们通过虚继承（virtual inheritance）的机制解决上述问题。虚继承的目的是令某个类做出声明，承诺愿意共享它的基类。共享的基类子对象被称为虚基类（virtual base class）。在这种机制下，不论虚基类在继承体系中出现了多少次，在派生类中都只包含唯一一个共享的虚基类子对象。\n\n例如：\n\n![](images/virtual_inheritance.png)\n\n!!!note\n\t虚派生只影响从指定了虚基类的派生类中进一步派生出的类，它不会影响派生类本身。\n\n**使用虚基类**\n\n我们指定虚基类的方式是在派生列表中添加关键字virtual:\n\n```c++\n// 关键字public和virtual的顺序随意\nclass Raccoon : public virtaul ZooAnimal {};\nclass Bear : public virtaul ZooAnimal {};\n```\n\n上述代码将ZooAnimal定义为Raccoon和Bear的虚基类。\n\nvirtual说明符表明了一种愿望，即在后续的派生类当中共享虚基类的同一份实例。至于什么样的类能够作为虚基类并没有特殊规定。如果某个类指定了虚基类，则该类的派生仍按常规方式进行：\n\n```c++\nclass Panda : public Bear, public Raccoon, public Endangered {};\n```\n\nPanda通过Raccon和Bear继承了ZooAnimal，因为Raccoon和Bear继承ZooAnimal的方式都是虚继承，所以在Panda中只有一个ZooAnimal基类部分。\n\n### 构造函数与虚继承\n\n在虚派生中，虚基类是由最低层的派生类初始化的。以我们的程序为例，当创建Panda对象时，由Panda的构造函数独自控制ZooAnimal的初始化过程。即使ZooAnimal不是Panda的直接基类，Panda的构造函数也可以初始化ZooAnimal：\n\n```c++\nPanda::Panda(std::string name, bool onExhibit)\n\t: ZooAnimal(name, onExhibit, \"Panda\"),\n\tBear(name, onExhibit),\n\tRaccoon(name, onExhibit),\n\tEndangered(Endangered::critical),\n\tsleeping_flag(false) {}\n```\n\n**虚继承的对象的构造方式**\n\n含有虚基类的对象的构造顺序与一般的顺序稍有区别：首先使用提供给最低层派生类构造函数的初始值初始化该对象的虚基类子部分，接下来按照直接基类在派生列表中出现的次序依次对齐进行初始化。\n\n例如，当我们创建一个Panda对象时：\n\n- 首先构造虚基类ZooAnimal。\n\n- 接下来构造Bear部分。\n\n- 然后构造Raccoon部分。\n\n- 然后构造第三个直接基类Endangered。\n\n- 最后构造Panda部分。\n\n!!!note\n\t虚基类总是先于非虚基类构造，与它们在继承体系中的次序和位置无关。\n\n**构造函数与析构函数的次序**\n\n一个类可以有多个虚基类。此时，这些虚的子对象按照它们在派生列表中出现的顺序从左向右依次构造。\n\n合成的拷贝和移动构造函数按照完全相同的顺序执行，合成的赋值运算符中的成员也按照该顺序赋值。和往常一样，对象的销毁顺序与构造函数正好相反。"
  },
  {
    "path": "notes/CppPrimer/ch19 特殊工具与技术.md",
    "content": "# 第19章 特殊工具与技术\n\n---\n\n## 控制内存分配\n\n某些程序需要自定义内存分配的细节，比如使用关键字new将对象放置在特定的内存空间中。为了实现这一目的，应用程序需要重载new运算符和delete运算符以控制内存分配的过程。\n\n### 重载new和delete\n\n当我们使用一条new表达式时：\n\n```c++\nstring *sp = new string(\"a value\");\nstring *arr = new string[10];\n```\n\n实际执行了三步操作。\n\n0. new表达式调用一个名为operator new(或者operator new[])的标准库函数。该函数分配一块足够大的、原始的、未命名的内存空间以便存储特定类型的对象（或者对象的数组）。\n\n0. 编译器运行相应的构造函数以构造这些对象，并为其传入初始值。\n\n0. 对象被分配了空间并构造完成，返回一个指向该对象的指针。\n\n当我们使用一条delete表达式删除一个动态分配的对象时：\n\n```c++\ndelete sp;\ndelete [] arr;\n```\n\n实际执行了两步操作。\n\n0. 对sp所指的对象或者arr所指的数组中的元素执行对应的析构函数。\n\n0. 编译器调用名为operator delete（或者operator delete[]）的标准库函数释放内存空间。\n\n如果我们定义了自己版本的operator new函数和operator delete函数，编译器将使用我们自定义的版本替换标准库定义的版本。\n\n!!!warning\n\t当自定义了全局的operator new函数和operator delete函数后，我们就担负起了控制动态内存分配的职责。这两个函数必须是正确的：因为他们是程序整个处理过程中至关重要的一部分。\n\n应用程序可以在全局作用域定义operator new函数和operator delete函数，也可以将它们定义为成员函数。当编译器发现一条new表达式或delete表达式后，将在程序中查找可供调用的operator函数。如果被分配（释放）的对象是类类型，则编译器首先在类及其基类的作用域中查找。如果没有找到，编译器在全局作用域查找，如果找到了用户自定义的版本，则使用该版本执行new表达式或delete表达式；如果没有找到，则使用标准库定义的版本。\n\n**operator new接口和operator delete接口**\n\n标准库定义了operator new和operator delete的8个重载版本，前四个可能会抛出bad_alloc异常，后四个则不会：\n\n```c++\n// 这些版本可能抛出异常\nvoid *operator new(size_t);\t\t\t\t\t// 分配一个对象\nvoid *operator new[](size_t);\t\t\t\t// 分配一个数组\nvoid *operator delete(void*) noexcept;\t\t// 释放一个对象\nvoid *operator delete[](void*) noexcept;\t// 释放一个数组\n\n// 这些版本承诺不会抛出异常\nvoid *operator new(size_t, nothrow_t&) noexcept;\nvoid *operator new[](size_t, nothrow_t&) noexcept;\nvoid *operator delete(void*, nothrow_t&) noexcept;\nvoid *operator delete[](void*, nothrow_t&) noexcept;\n```\n\n当我们将上述函数定义成类的成员时，它们是隐式静态的。更多讨论见p728。\n\n!!!note \"new 表达式与operator new函数\"\n\t一条new表达式的执行过程总是先调用operator new函数以获取内存空间，然后在得到的内存空间中构造对象。与之相反，一条delete表达式的执行过程总是先销毁对象，然后调用operator delete函数释放对象所占的空间。\n\n\t我们提供新的operator new函数和operator delete函数的目的在于改变内存分配方式，但是不管怎样，我们都不能改变new运算符和delete运算符的基本含义。\n\n**malloc函数与free函数**\n\nmalloc和free定义在cstdlib头文件中。\n\nmalloc函数接受一个表示待分配字节数的size_t，返回指向分配空间的指针或者返回0以表示分配失败。free函数接受一个void\\*，它是malloc返回的指针的副本，free将相关内存返回给系统。调用free(0)没有任何意义。\n\n如下所示是编写operator new和operator delete的一种简单方式，其他版本与之类似：\n\n```c++\nvoid *operator new(size_t size) {\n\tif (void *mem = malloc(size))\n\t\treturn mem;\n\telse\n\t\tthrow bad_alloc();\n}\n\nvoid operator delete(void *mem) noexcept { free(mem); }\n```\n\n### 定位new表达式\n\n与allocator不同的是，对于operator new分配的内存空间来说我们无法使用construct函数构造对象。我们应该使用new的定位new（placement new）形式构造对象：\n\n```\nnew (place_address) type\nnew (place_address) type (initializers)\nnew (place_address) type [size]\nnew (place_address) type [size] { braced initializers list }\n```\n\n当仅通过一个地址值调用时，定位new使用`operator new(size_t, void*)`“分配”它的内存。这是一个我们无法自定义的operator new版本。该函数不分配任何内存，它只是简单地返回指针实参；然后由new表达式负责在指定的地址初始化对象以完成整个工作。事实上，定位new允许我们在一个特定的、预先分配的内存地址上构造对象。\n\n!!!note\n\t当只传入一个指针类型的实参时，定位new表达式构造对象但是不分配内存。\n\n**显式的析构函数调用**\n\n我们既可以通过对象调用析构函数，也可以通过对象的指针或引用调用析构函数，这与调用其它成员函数没什么区别：\n\n```c++\nstring *sp = new string(\"a value\");\nsp->~string();\n```\n\n箭头运算符解引用指针sp以获得sp所指的对象，然后我们调用析构函数。和调用allocator的destroy类似，调用析构函数可以清除给定的对象但是不会释放该对象所在的空间。如果需要的话，我们可以重新使用该空间。\n\n!!!note\n\t调用析构函数会销毁对象，但是不会释放内存。\n\n## 运行时类型识别\n\n运行时类型识别（run-time type identification, RTTI）的功能由两个运算符实现：\n\n- typeid运算符，用于返回表达式的类型。\n\n- dynamic_cast运算符，用于将基类的指针或引用安全地转换成派生类的指针或引用。\n\n当我们将这两个运算符用于某种类型的指针或引用，并且该类型含有虚函数时，运算符将使用指针或引用所绑定对象的动态类型。\n\n这两个运算符特别适用于以下情况：我们想使用基类对象的指针或引用执行某个派生类操作并且该操作不是虚函数。\n\n!!!warning\n\t使用RTTI必须加倍小心。在可能的情况下，最好定义虚函数而非直接接管类型管理的重任。\n\n### dynamic_cast运算符\n\ndynamic_cast运算符（dynamic_cast operator）的使用形式如下所示：\n\n```\ndynamic_cast<type*>(e)\ndynamic_cast<type&>(e)\ndynamic_cast<type&&>(e)\n```\n\n其中，type必须是一个类类型，并且通常情况下该类型应该含有虚函数。第一种情况中，e必须是一个有效的指针。\n\n如果e的类型是type的公有派生类、e的类型是type的公有基类或者e的类型就是type，则转换可以成功。否则转换失败。如果一条dynamic_cast语句的转换目标是指针类型并且失败了，则结果为0。如果转换目标是引用类型并且失败了，则dynamic_cast运算符将抛出一个bad_cast异常。\n\n**指针类型的dynamic_cast**\n\n举一个简单的例子：\n\n```c++\nif (Derived *dp = dynamic_cast<Derived*>(bp))\n{\n\t// 使用dp指向的Derived对象\n} else {\t// bp指向一个Base对象\n\t// 使用bp指向的Base对象\n}\n```\n\n**引用类型的dynamic_cast**\n\n改写之前的程序：\n\n```c++\nvoid f(const Base &b)\n{\n\ttry {\n\t\tconst Derived &d = dynamic_cast<const Derived&>(b);\n\t\t// 使用b引用的Derived对象\n\t} catch (bad_cast) {\n\t\t// 处理类型转换失败的情况\n\t}\n}\n```\n\n### typeid运算符\n\ntypeid运算符允许程序向表达式提问：你的对象是什么类型？\n\ntypeid表达式的形式是typeid(e)，其中e可以是任意表达式或类型的名字。typeid操作的结果是一个常量对象的引用，该对象的类型是标准库类型type_info或者type_info的公有派生类型。type_info类定义在typeinfo头文件中。\n\ntypeid运算符可以用作于任意类型的表达式。其中：\n\n- 顶层const会被忽略。\n\n- 如果表达式是一个引用，则typeid返回该引用所引对象的类型。\n\n- 当typeid作用于数组或函数时，并不会执行向指针的标准类型转换。\n\n当运算对象不属于类类型或者是一个不包含任何虚函数的类时，typeid运算符指示的是运算对象的静态类型。而当运算对象是定义了至少一个虚函数的类的左值时，typeid的结果直到运行时才会求得。\n\n!!!note \"my note\"\n\t必须在使用 typeid 运算符前包含头文件 typeinfo\n\n**使用typeid运算符**\n\n```c++\nDerived *dp = new Derived;\nBase *bp = dp;\t\t\t// 两个指针都指向Derived对象\n\n// 在运行时比较两个对象的类型\nif (typeid(*bp) == typeid(*dp)) {\n\t// bp 和 dp 指向同一类型的对象\n}\n\n// 检查运行时类型是否是某种指定的类型\nif (typeid(*bp) == typeid(Derived)) {\n\t// bp 实际指向Derived对象\n}\n```\n\ntypeid是否需要运行时检查决定了表达式是否会被求值。只有当类型含有虚函数时，编译器才会对表达式求值。如果类型不含虚函数，则typeid返回表达式的静态类型。\n\n如果p是一个指向了有虚函数的类的空指针，则typeid(\\*p)将抛出一个名为bad_typeid的异常。\n\n### type_info类\n\ntype_info类的精确定义随着编译器的不同而略有差异。不过，C++标准规定type_info类必须定义在typeinfo头文件中，并至少提供下面的操作：\n\n|操作|说明|\n|-|-|\n|t1 == t2|如果type_info对象t1和t2表示同一种类型，返回true，否则返回false|\n|t1 != t2|和上面相反|\n|t.name()|返回一个C风格字符串，表示类型名字的可打印形式。类型名字的生成方式因系统而异|\n|t.before(t2)|返回一个bool值，表示t1是否位于t2之前。before所采用的顺序关系是依赖于编译器的|\n\n我们只能通过typeid运算符创建type_info对象。\n\n## 枚举类型\n\n枚举类型（enumeration）使我们可以将一组整型常量组织在一起。和类一样，每个枚举类型定义了一种新的类型。枚举属于字面值常量类型。\n\nC++包含两种枚举：限定作用域的和不限定作用域的。\n\n```c++\nenum class open_modes { input, output, append };\t\t// 限定作用域的枚举类型\nenum color { red, yellow, green };\t\t\t\t\t\t// 不限定作用域的枚举类型\nenum { floatPrec = 6, doublePrec = 10 };\t\t\t\t// 未命名的、不限定作用域的枚举类型\n```\n\n**枚举成员**\n\n如果是对于限定作用域的枚举类型，我们需要通过作用域运算符在外部显式地访问枚举成员。而对于不限定作用域的枚举类型，则没有这个要求。\n\n默认情况下，枚举值从0开始，依次加1。不过我们也能为一个或几个枚举成员指定专门的值，而且值可以重复（不唯一）。\n\n更多关于枚举的讨论见p738。\n\n## 类成员指针\n\n成员指针（pointer to member）指的是可以指向类的非静态成员的指针。对于指向静态成员的指针和普通的指针没有什么区别。\n\n当初始化一个成员指针时，我们令其指向类的某个成员，但是不指定该成员所属的对象；直到使用成员指针时，才提供成员所属的对象。\n\n为了解释本节的概念，使用下面这个类作为例子：\n\n```c++\nclass Screen {\npublic:\n\ttypedef std::string::size_type pos;\n\tchar get_cursor() const { return contents[cursor]; }\n\tchar get() const;\n\nprivate:\n\tstd::string contents;\n\tpos cursor;\n\tpos height, width;\n};\n```\n\n###数据成员指针\n\n我们必须在\\*之前添加classname::以表示当前定义的指针可以指向classname的成员。例如：\n\n```c++\nconst string Screen::*pdata;\n```\n\n初始化的方式：\n\n```c++\npdata = &Screen::contents;\n```\n\n在C++11中声明成员指针最简单的方式是使用auto或decltype：\n\n```c++\nauto pdata = &Screen::contetns;\n```\n\n**使用数据成员指针**\n\n必须清楚的一点是：当我们初始化一个成员指针或为成员指针赋值时，该指针并没有指向任何数据。只有当解引用成员指针时我们才提供对象的信息。\n\n```c++\nScreen myScreen, *pScreen = &myScreen;\n\nauto s = myScreen.*pdata;\ns = pScreen->*pdata;\n```\n\n**返回数据成员指针的函数**\n\n因为数据成员一般情况下是私有的，所以我们通常不能直接获得数据成员的指针。如果一个像Screen这样的类希望我们可以访问它的contents成员，最好定义一个函数，令其返回值是指向该成员的指针：\n\n```c++\nclass Screen {\npublic:\n\tstatic const std::string Screen::*data() {\n\t\treturn &Screen::contents;\n\t}\n};\n```\n\n当我们调用data函数时，将获得一个成员指针：\n\n```c++\nconst std::string Screen::*pdata = Screen::data();\n\n// 获得myScreen对象的contents成员\nauto s = myScreen.*pdata;\n```\n\n### 成员函数指针\n\n定义成员函数指针最简单的方法是使用auto：\n\n```c++\nauto pmf = &Screen::get_cursor;\n```\n\n和指向数据成员的指针意义，我们使用classname::\\*的形式声明一个指向成员函数的指针。\n\n如果成员存在重载的问题，我们必须显式地声明函数类型以明确指出我们想要使用哪个函数。例如，我们可以声明一个指针，令其指向含有两个形参的get：\n\n```c++\nchar (Screen::*pmf2)(Screen::pos, Screen::pos) const;\npmf2 = &Screen::get;\n```\n\n和普通指针不同的是，在成员函数和指向该成员的指针之间不存在自动转换规则：\n\n```c++\npmf = &Screen::get;\t\t\t// 必须显式地使用取地址运算符\npmf = Screen::get;\t\t\t// 错误，在成员函数和指针之间不存在自动转换规则\n```\n\n**使用成员函数指针**\n\n和使用指向数据成员的指针一样，我们使用`.*`或者`->*`运算符作用于指向成员函数的指针，以调用类的成员函数：\n\n```c++\nScreen myScreen, *pScreen = &myScreen;\nchar c1 = (pScreen->*pmf)();\nchar c2 = (myScreen.*pmf)(0, 0);\n```\n\n之所以上面使用成员指针两边的括号必不可少，是因为调用运算符的优先级更高。\n\n**使用成员指针的类型别名**\n\n```c++\nusing Action = char (Screen::*)(Screen::pos, Screen::pos) const;\n\nAction get = &Screen::get;\n```\n\n**成员指针函数表**\n\n对于普通函数指针和指向成员函数的指针来说，一种常见的用法是将其存入一个函数表当中。\n\n### 将成员函数用作可调用对象\n\n要想通过一个指向成员函数的指针进行函数调用，必须首先利用`.*`或`->*`将该指针绑定到一个对象上。因此，成员指针不是一个可调用对象，这样的指针不支持函数调用运算符。\n\n**使用function生成一个可调用对象**\n\n一种方法是使用标准库模板function：\n\n```c++\nfunction<bool (const string&)> fcn = &string::empty;\nfind_if(svec.begin(), svec.end(), fcn);\n```\n\n我们告诉function一个事实：即empty是一个接受string参数并返回bool值的函数。通常情况下，指向成员函数的对象将被传给隐式的this形参。当我们想要使用function为成员函数生成一个可调用对象时，必须首先“翻译”该代码，使隐式的形参变成显式的。\n\n我们提供给function的形式中还必须指明对象是否以指针或引用的形式传入。\n\n```c++\nvector<string*> pvec;\nfunction<bool (string*)> fp = &string::empty;\nfind_if(pvec.begin(), pvec.end(), fp);\n```\n\n**使用mem_fn生成一个可调用对象**\n\n要想使用function，我们必须提供成员的调用形式。我们也可以采取另外一种方法，通过使用标准库功能mem_fcn来让编译器负责推断成员的类型。mem_fcn也定义在functional头文件中，并且可以从成员指针生成一个可调用对象。\n\n```c++\nfind_if(svec.begin(), svec.end(), mem_fcn(&string::empty));\n```\n\n我们使用mem_fcn(&string::empty)生成一个可调用对象，该对象接受一个string实参，返回一个bool值。\n\nmem_fcn生成的可调用对象既可以通过对象调用，也可以通过指针调用：\n\n```c++\nauto f = mem_fcn(&string::empty);\nf(s);\nf(ps);\n```\n\n**使用bind生成一个可调用对象**\n\n我们还可以使用bind从成员函数生成一个可调用对象：\n\n```c++\nauto it = find_if(svec.begin(), svec.end(), bind(&string::empty, _1));\n```\n\n和function类似的地方是，当我们使用bind时，必须将函数中用于表示执行对象的隐式形参转换成显式的。和mem_fcn类型的是，bind生成的可调用对象的第一个实参可以是对象，也可以是指针。\n\n## 嵌套类\n\n一个类可以定义在另一个类的内部，前者称为嵌套类（nested class）或嵌套类型（nested type）。嵌套类常用于定义作为实现部分的类。\n\n嵌套类是一个独立的类，与外层类基本没什么关系。\n\n嵌套类的名字在外层类作用域中是可见的，在外层类作用域之外不可见。需要通过访问限定符来访问。\n\n**在外层类之外定义一个嵌套类**\n\n嵌套类必须声明在类的内部，但是可以定义在类的内部或者外部。具体例子可以参考：p747\n\n## union：一种节省空间的类\n\n联合（union）是一种特殊的类。一个union可以有多个数据成员，但是在任意时刻只有一个数据成员可以有值。当我们给union的某个成员赋值之后，该union的其他成员就变成未定义的状态了。分配给一个union的存储空间至少能容纳它的最大的数据成员。\n\n类的某些特性对union同样适用，但并非所有特性都如此。union不能包含引用类型的成员。\n\nunion可以定义包括构造函数和析构函数在内的成员函数。但是由于union既不能继承自其他类，也不能作为基类使用，所以在union中不能含有虚函数。\n\n**定义union**\n\nunion提供了一种有效的途径使得我们可以方便地表示一组类型不同的互斥值。比如：\n\n```c++\n// Token类型的对象只有一个成员，该成员的类型可能是下列类型中的任意一种\nunion Token {\n\tchar cval;\n\tint ival;\n\tdouble dval;\n};\n```\n\n**使用union**\n\nunion的名字是一个类型名。和其他内置类型一样，默认情况下union是未初始化的。我们可以像显式地初始化聚合类一样使用一对花括号内的初始值显式地初始化一个union。\n\n```c++\nToken first_token = {'a'};\t\t\t// 初始化cval成员\nToken last_token;\t\t\t\t\t// 未初始化的Token对象\nToken *pt = new Token;\t\t\t\t// 指向一个未初始化的Token对象的指针\n```\n\n如果提供了初始值，则该初始值被用于初始化第一个成员。\n\n我们使用通用的成员访问运算符访问一个union对象的成员：\n\n```c++\nlast_token.cval = 'z';\npt->ival = 42;\n```\n\n为union的一个数据成员赋值会令其他数据成员变成未定义的状态。因此，我们使用union时，必须清楚地知道当前存储在union中的值到底是什么类型。\n\n**匿名union**\n\n一旦我们定义了一个匿名union，编译器就自动为该union创建一个未命名的对象：\n\n```c++\nunion {\n\tchar cval;\n\tint ival;\n\tdouble dval;\n};\t// 定义了一个未命名的对象，我们可以直接访问它的成员\n\ncval = 'c';\nival = 42;\n```\n\n!!!note\n\t匿名union不能包含受保护的成员或私有成员，也不能定义成员函数。\n\n**含有类类型成员的union**\n\nC++11中，如果union的成员类型定义了自己的构造函数和/或拷贝控制成员，则该union的用法将变得很复杂。\n\n我们需要分别构造或析构该类类型的成员：当我们将union的值改为类类型成员对应的值时，必须运行该类型的构造函数；反之，当我们将类类型成员的值改为一个其他值时，必须运行该类型的析构函数。\n\n当union包含的是内置类型的成员时，编译器将按照成员的次序依次合成默认构造函数或拷贝控制成员。但是如果union含有类类型的成员，并且该类型自定义了默认构造函数或拷贝控制成员，则编译器将为union合成对应的版本并将其声明为删除的。\n\n我们通常把含有类类型成员的union内嵌在另一个类当中。这个类管理并控制与union的类类型成员有关的状态转换。具体见书本p751。\n\n## 局部类\n\n类可以定义在某个函数的内部，我们称这样的类为局部类（local class）。局部类定义的类型只在定义它的作用域内可见。\n\n!!!note\n\t局部类的所有成员（包括函数在内）都必须完整定义在类的内部。\n\n在实际编程中，因为局部类的成员必须完整定义在类的内部，所以成员函数的复杂性不可能太高。局部类的成员函数一般只有几行代码，否则就很难读懂了。\n\n## 固有的不可移植的特性\n\n为了支持低层编程，C++定义了一些固有的不可移植（nonportable）的特性。所谓不可移植的特性是指因机器而异的特性，当我们将含有不可移植特性的程序从一台机器转移到另一台机器上时，通常需要重新编写该程序。\n\n本节介绍C++从C语言继承而来的两种不可移植的特性：位域和volatile限定符。详见书本。\n\n### 链接指示：extern \"C\"\n\nC++程序有时需要调用其他语言编写的函数，最常见的是调用C语言编写的函数。其他语言中的函数名字也必须在C++中进行声明，并且该声明必须指定返回类型和形参列表。对于其他语言编写的函数来说，编译器检查其调用的方式与处理普通C++函数的方式相同，但是生成的代码有所区别。C++使用链接指示（linkage directive）指出任意非C++函数所用的语言。\n\n**声明一个非C++的函数**\n\n链接指示可以有两种形式：单个的或复合的。链接指示不能出现在类定义或函数定义的内部。\n\n比如：\n\n```c++\n// 可能出现在C++头文件<cstring>中的链接指示\n// 单语句链接指示\nextern \"C\" size_t strlen(const char*);\n\n// 复合语句链接指示\nextern \"C\" {\n\tint strcmp(const char*, const char*);\n\tchar *strcat(char*, const char*);\n}\n```\n\n!!!note\n\t有时候可能使用C编译器或C++编译器编译同一个源文件，这时候可以使用预处理器宏 `__cplusplus` 来条件引入 extern \"C\" 指示。比如：\n\n```c++\n#ifdef __cplusplus\nextern \"C\"\n#endif\nint strlen_custom(const char *str);\n```\n\n更多讨论见书本p759。"
  },
  {
    "path": "notes/EffectiveModernCpp/ch01_型别推导/条款1_理解模板型别推导.md",
    "content": "# 条款1: 理解模板型别推导\n\n有如下模板伪代码：\n\n```c++\ntemplate<typename T>\nvoid f(ParamType param);\n\nf(expr);\n```\n\nT的型别推导，不仅仅依赖实参的型别，还依赖ParamType的形式。具体有：\n\n- ParamType 具有指针或引用型别（非万能引用）。\n- ParamType 是一个万能引用。\n- ParamType 既非指针也非引用。\n\n## 情况1 ParamType是指针或引用\n\n即如下（可能的）:\n\n```c++\ntemplate<typename T>\nvoid f(T& param);\n```\n\n型别推导会这样做：\n\n1. 若expr具有引用型别，先将引用部分忽略。\n\n2. 尔后，对expr的型别和ParamType的型别执行模式匹配，来决定T的型别。\n\n## 情况2 ParamType是万能引用\n\n即如下：\n```c++\ntemplate<typename T>\nvoid f(T&& param);\n```\n\n注意，形参声明方式写作`T&&`。\n\n如果expr是左值，T和ParamType都会被推导为左值引用。\n\n如果expr是右值，则使用情况1中的规则。\n\n## 情况3 ParamType既非指针也非引用\n\n即如下:\n\n```c++\ntemplate<typename T>\nvoid f(T param);\n```\n\n导出规则为：\n\n- 若expr具有引用型别，则忽略其引用部分。\n- 之后，若expr是个const对象，也忽略之。\n\n!!!note\n\t这里说的const对象是指顶层const，即如果是`const char *const`，那么会推导成`const char *`。\n\n## 数组实参\n\n由于数组到指针的退化规则的存在，如果模板函数如：\n\n```c++\ntemplate<typename T>\nvoid f(T param);\n```\n\n那么将一个数组（比如 `const char name[] = \"diwen\"`）传递给它时，T会被推导成 `const char *` 。\n\n可以将形参声明成数组的引用，方法是将模板改成：\n\n```c++\ntemplate<typename T>\nvoid f(T& param);\n```\n\nT将推导出实际的数组型别，包含数组的尺寸。\n\n利用这一能力，可以创造一个模板，用来推导出数组含有的元素个数：\n\n```c++\ntemplate<typename T, std::size_t N>\nconstexpr std::size_t arraySize(T (&)[N]) noexcept\n{\n    return N;\n}\n```"
  },
  {
    "path": "notes/EffectiveModernCpp/ch01_型别推导/条款2_理解auto型别推导.md",
    "content": "# 条款2: 理解auto型别推导\n\n---\n\nauto型别推导就是模板型别推导，除了一个例外。\n\n对于模板型别推导的三种情况，auto型别推导的规则是一样的：\n\n- 情况1 型别饰词是指针或引用 将原表达式的引用部分忽略\n- 情况2 型别饰词是万能引用 若原表达式是左值 则推导为左值引用\n- 情况3 型别饰词既非指针也非引用 将原表达式引用部分忽略 顶层const忽略\n\n```c++\nauto x = 27;            // 情况3 x型别推导为int\nconst auto cx = x;      // 情况3 cx型别推导为const int\nconst auto& rx = x;     // 情况1 rx型别推导为const int&\n\nauto&& uref1 = x;       // 情况2 uref1型别推导为int&\nauto&& uref2 = cx;      // 情况2 uref2型别推导为const int&\nauto&& uref3 = 27;      // 情况2 uref3型别推导为int&&\n```\n\n对于数组和函数的推导规则，auto也是一样的。\n\n```c++\nconst char name[] = \"diwen\";            // name的型别是const char[6]\nauto arr1 = name;                       // arr1的型别是const char*\nauto& arr2 = name;                      // arr2的型别是const (&)char[6]\n\nvoid f(int, double);                    // f的型别是void(int, double)\nauto func1 = f;                         // func1的型别是void(*)(int, double)\nauto& func2 = f;                        // func2的型别是void(&)(int, double)\n```\n\n## 例外情况\n\n对于 `auto x{27}` 这样的初始化语句，x推导出的型别是`std::initializer_list`。而模板型别推导面对这样的情况就会失败：\n\n```c++\ntemplate<typename T>\nvoid f(T param);\n\nf({1, 2, 3});       // 错误！无法推导T的型别\n```\n\n在C++14中，auto可以用来说明函数返回值需要推导，此时应用的不是auto型别推导，而是模板型别推导，因此下面代码无法通过编译：\n\n```c++\nauto f()\n{\n    return {1, 2, 3};\n}\n```\n\n在C++14中，auto可以用来指定lambda式的形参型别，此时也是应用模板型别推导，因此下面代码也无法通过编译：\n\n```c++\nauto f = [](const auto &v) {};\nf({1, 2, 3});\n```\n"
  },
  {
    "path": "notes/EffectiveModernCpp/ch01_型别推导/条款3_理解decltype.md",
    "content": "# 条款3: 理解decltype\n\n---\n\n对于给定的名字或表达式，decltype能告诉你该名字或表达式的型别。\n\n如：\n\n```c++\nconst int i = 0;                // decltype(i) 是 const int\n\nbool f(const Widget& w);        // decltype(w) 是 const Widget&\n                                // decltype(f) 是 bool(const Widget&)\n\nstruct Point {\n    int x, y;                   // decltype(Point::x) 是 int\n};                              // decltype(Point::y) 是 int\n\nif (f(w)) ...                   // decltype(f(w)) 是 bool\n\ntemplate<typename T>\nclass vector {\npublic:\n    T& operator[](std::size_t index);\n};\n\nvector<int> v;                  // decltype(v) 是 vector<int>\nif(v[0] == 0) ...               // decltype(v[0]) 是 int&\n```\n\nC++11中，decltype主要用处大概在于声明那些返回值型别依赖于形参型别的函数模板。\n\n```c++\ntemplate<typename Container, typename Index>\nauto authAndAccess(Container& c, Index i)\n    -> decltype(c[i])\n{\n    authenticateUser();\n    return c[i];\n}\n```\n\n这里的auto和型别推导没有关系，这是C++11中的返回值型别尾序语法。\n\nC++14中可以去掉返回值型别尾序语法，只保留auto。此时auto会发生型别推导。\n\n请看下面**错误**的示范：\n\n```c++\ntemplate<typename Container, typename Index>\nauto authAndAccess(Container& c, Index i)\n{\n    authenticateUser();\n    return c[i];\n}\n```\n\n由于在模板型别推导中，上述表达式引用性会被忽略，于是：\n\n```c++\nstd::deque<int> d;\nauthAndAccess(d, 5) = 10;       // 返回 d[5] 但无法通过编译\n```\n\n上述函数返回值型别为int，作为函数的返回值，它是一个右值，所以无法将10赋给它。\n\n如何修补？在C++14中通过decltype(auto)解决。即：\n\n```c++\ntemplate<typename Container, typename Index>\ndecltype(auto) authAndAccess(Container& c, Index i)\n{\n    authenticateUser();\n    return c[i];\n}\n```\n\n此时推导规则采用的是decltype的规则。\n\n上述函数无法传递一个右值的容器对象，如何做到？采用万能引用。同时在调用`operator[]`时使用`std::forward`保持其型别。\n\n```c++\ntemplate<typename Container, typename Index>\ndecltype(auto)\nauthAndAccess(Container&& c, Index i)\n{\n    authenticateUser();\n    return std::forward<Container>(c)[i];\n}\n```\n\n如果decltype应用于复杂的左值表达式（非单纯只是一个名字）的话，decltype就保证得出的型别总是左值引用。\n\n请看：\n\n```c++\nint x = 0;\n```\n\ndecltype(x)结果是int。但如果`decltype((x))`的话，结果就成了`int&`。"
  },
  {
    "path": "notes/EffectiveModernCpp/ch01_型别推导/条款4_掌握查看型别推导结果的方法.md",
    "content": "# 条款4: 掌握查看型别推导结果的方法\n\n---\n\n我们在三个阶段需要知道型别推导的结果：\n\n1. 撰写代码阶段\n2. 编译阶段\n3. 运行时阶段\n\n## IDE编辑器\n\n在编译阶段，我们可以借助IDE编辑器。通常将鼠标悬停在某个程序实体，如变量、形参、函数等时，IDE会显示出实体的型别。\n\n要注意的是，对于像int这样的平凡型别，从IDE得到的信息大体良好。一旦较为复杂的型别现身，IDE显示的信息就不太有用了。\n\n## 编译器诊断信息\n\n在编译期显示推导出的型别，一条有效途径是令编译器报错，提示错误的型别。\n\n具体方法则是，声明而不定义一个类模板：\n\n```c++\ntemplate<typename T>\nclass TD;\n```\n\n然后写下如下代码：\n\n```c++\nTD<decltype(x)> xType;          // 诱发x的型别的错误消息\n```\n\n由于模板没有定义，编译器必然会报错，报错内容则包含着x的型别。\n\n## 运行时输出\n\n运行时输出的工具有标准库提供的typeid和typeinfo。\n\n如：\n\n```c++\nstd::cout << typeid(x).name();\n```\n\ntypeid返回一个typeinfo对象，它的方法name()的到一个`const char*`字符串来代表型别。\n\n但这种方法也有问题，一个是其输出的字符串描述会略显奇怪，且不同厂商的编译器得到的结果也不一样，比如int可能显示成i。\n\n更有甚者，它可能得不到正确的结果。书本里给出了一个模板型别推导的例子可供参考。同时说明：\n\n> 标准规格上说，std::type_info::name中处理型别的方式就仿佛是向函数模板按值传递形参一样。\n\n所以不能够依赖它能得到准确无误的型别。\n\n那么有什么其他办法吗？有。书中介绍了Boost的TypeIndex库。它提供的方法可以得到准确的型别。\n\n!!!note\n    总结就是，我们可以用各种方法得到型别推导结果，但理解型别推导的规则才是无可替代的。"
  },
  {
    "path": "notes/EffectiveModernCpp/ch02_auto/条款5_优先选用auto而非显式型别声明.md",
    "content": "# 条款5: 优先选用auto而非显式型别声明\n\n---\n\n直接使用显式型别有诸多问题存在。\n\n比如：\n\n```c++\nint x;\n```\n\nx的值忘记初始化了，它的值取决于它定义的位置。而使用auto，则我们必须对它初始化：\n\n```c++\nauto x = 10;\n```\n\n同时，对于复杂类型，使用auto会更加简单，比如：\n\n```c++\ntemplate<typename It>\nvoid dwim(It b, It e)\n{\n    while (b != e)\n        typename std::iterator_traits<It>::value_type currValue = *b;\n}\n```\n\n显然直接用auto会简单很多：\n\n```c++\ntemplate<typename It>\nvoid dwim(It b, It e)\n{\n    while (b != e)\n        auto currValue = *b;\n}\n```\n\n另外，lambda表达式的类型只有编译器知道，因此我们定义lambda表达式就只能使用auto：\n\n```c++\nauto f = [](const int &a) {};\n```\n\n在C++14中，我们还可以对形参的类型使用auto：\n\n```c++\nauto f = [](const auto &a) {};\n```\n\n我们可以用std::function存储闭包，但正如书本上讨论的，使用auto声明一个变量来存储它的优势更加显著。\n\n我们有时候会手动声明出错误的类型，从而导致非期望的结果。比如：\n\n```c++\nstd::vector<int> v;\nunsigned sz = v.size();\n```\n\nvector的size方法返回的并非是一个unsigned类型，而是`std::vector<T>::size_type`, 它可能是64位的，而unsigned类型是32位的。\n\n再比如：\n\n```c++\nstd::unordered_map<std::string, int> m;\n\nfor (const std::pair<std::string, int> &p : m)\n{\n\n}\n```\n\n`std::pair<std::string, int>`并不是m的键值类型，准确的类型应该是`std::pair<const std::string, int>`。因此上述迭代会带来复制和析构成本，且引用也没有意义。\n\n使用auto就可以避免我们声明出错误的类型：\n\n```c++\nauto sz = v.size();\n\nfor (const auto &p : m)\n{\n\n}\n```\n\nauto型别可以随着其初始化表达式型别的变化而自动随之改变，这意味着一种自动的重构。但如果是显式声明的话，就需要手动去修改各个位置的代码了。\n\n我们可能心存疑虑，改用auto后写出来的源代码会有可读性问题。记住，auto是一个可选项，而非必选项，如果显式型别更清晰、可维护性更高，当然可以使用。\n"
  },
  {
    "path": "notes/EffectiveModernCpp/ch02_auto/条款6_当auto推导的型别不符合要求时使用带显式型别的初始化物习惯用法.md",
    "content": "# 条款6: 当auto推导的型别不符合要求时使用带显式型别的初始化物习惯用法\n\n---\n\n以`std::vector<bool>`为例，这样写是正确的：\n\n```c++\nstd::vector<bool> bvec;\n...\nbool b = bvec[5];\n```\n\n但下面的代码却有未定义行为：\n\n```c++\nstd::vector<bool> bvec;\n...\nauto b = bvec[5];\n// 使用b\n```\n\n为何？\n\n`std::vector<bool>`经过了特化，它的`operator[]`返回的是一个`std::vector<bool>::reference`型别的对象，它是一个叫作“代理类”的东西。\n\n我们无法得到一个引用到比特的对象（`std::vector<bool>`用比特存储一个bool），故而它的内部实现做了一些工作。\n\n`bvec[5]`返回了一个临时的对象，而此对象会马上被析构，而其副本b就拥有了一个空悬的指针（可能的实现），对它进行解引用就会出现未定义行为。\n\n但作者不认为这是auto的错，我们实际上要的是另一种型别，那么可以这样的方法：\n\n```c++\nauto b = static_cast<bool>(bvec[5]);\n```\n\n这种写法之所以被作者推崇，是因为它显式的表达了代码书写者的意图。比如，对于这样的代码：\n\n```c++\ndouble calcEpsilon();\n\nfloat ep = calcEpsilon();\n```\n\nep的型别表达的含义是模糊的，无法断定是疏忽大意，还是故意舍弃精度。\n\n而这样：\n\n```c++\nauto ep = static_cast<float>(calcEpsilon());\n```\n\n就明显得多了，很显然就是故意舍弃了精度。"
  },
  {
    "path": "notes/index.md",
    "content": "# C/C++ 学习笔记\n\n---\n\n## 简单介绍\n\n曾经一直零零碎碎看着《C++ Primer》，学习效果却不是很明显，因此决定重新整理好笔记和练习，系统性地提高自己的C++编程水平。也期望把这份笔记当成一份 cheatsheet ，方便日后查阅。\n\n笔记分为两个部分：\n\n- 代码练习\n\n- markdown 笔记\n\n笔记使用 Github 来做内容管理。\n\n曾无意中看到 mkdocs ，觉得非常适合我这样的情况，于是舍弃了之前的为知笔记，把全部内容搬到了 Github 仓库，然后使用 mkdocs 生成了在线文档并推送至 Github page 。\n\n这个页面就是这样被呈现的。\n\n我还加入了一本C语言的学习书籍：《C语言程序设计：现代方法》。因为C++是C语言的延续，其中有很多设计思想都追溯至C语言，而且在实际开发中，C语言的写法不在少数，有必要学习整理一下。\n\n## 学习资料\n\n[C语言程序设计：现代方法](https://book.douban.com/subject/4279678/)\n\n[C++ Primer 5th](https://book.douban.com/subject/25708312/)\n\n[Effective Modern C++](https://book.douban.com/subject/30178902/)"
  }
]