├── Chapter01 ├── 02.helloworld.c ├── README.md ├── img │ ├── 使用C语言的7个步骤.png │ └── 用unix准备c程序.png ├── nonefile ├── 复习题.md └── 编程题.md ├── Chapter02 ├── 01.简单的C程序例子及程序分析.c ├── README.md ├── img │ ├── C程序的结构.png │ └── ISOC关键字.png ├── 复习题.md └── 编程题.md ├── Chapter03 ├── README.md ├── img │ └── 转义序列.png ├── 复习题.md └── 编程题.md ├── Chapter04 ├── README.md ├── img │ ├── 格式字符串.png │ └── 转换说明符.png ├── 复习题.md └── 编程题.md ├── Chapter05 ├── README.md ├── img │ └── 第9题.png ├── 复习题.md └── 编程题.md ├── Chapter06 ├── README.md ├── 复习题.md └── 编程题.md ├── Chapter07 ├── Codes │ ├── README.md │ ├── 练习题01.c │ ├── 练习题02.c │ ├── 练习题03.c │ ├── 练习题04.c │ ├── 练习题05.c │ ├── 练习题06.c │ ├── 练习题07.c │ └── 练习题08.c ├── README.md ├── 复习题.md └── 编程题.md ├── Chapter08 ├── README.md ├── codes │ ├── README.md │ ├── 练习题01.c │ └── 练习题03.c ├── img │ └── 缓冲输入和无缓冲输入.png ├── 复习题.md └── 编程题.md ├── Chapter09 ├── Codes │ └── README.md ├── README.md ├── img │ └── 声明并使用指针.png ├── 复习题.md └── 编程题.md ├── Chapter10 ├── Codes │ └── README.md ├── README.md ├── img │ └── 初始化二维数组的两种方式.png ├── 复习题.md └── 编程题.md ├── Chapter11 ├── Codes │ ├── README.md │ └── 练习题01.c └── README.md ├── Chapter12 ├── README.md └── img │ └── 5种存储类别.png ├── Chapter13 ├── README.md ├── img │ └── fopen中的模式字符串.png ├── 复习题.md └── 编程题.md ├── Chapter14 ├── README.md ├── 复习题.md └── 编程题.md ├── Chapter15 ├── README.md ├── 复习题.md └── 编程题.md ├── Chapter16 ├── README.md ├── img │ ├── ANSIC标准的一些数学函数.png │ └── 函数宏定义的组成.png ├── 复习题.md └── 编程题.md ├── Chapter17 └── README.md ├── README.md └── test_all_example.sh /Chapter01/02.helloworld.c: -------------------------------------------------------------------------------- 1 | // 打印显示 “Welcome to Programming with C language ” 2 | 3 | 4 | // 头文件的分析 5 | /* 很多函数的文件保存库,头文件只包含了函数的说明 */ 6 | 7 | /*引入头文件使用#include命令,并将文件名放在< >中,#include 和 < > 之间可以有空格,也可以没有。*/ 8 | 9 | /*头文件以.h为后缀,而C语言代码文件以.c为后缀。*/ 10 | 11 | /*#include 命令的作用也仅仅是将头文件中的文本复制到当前文件,然后和当前文件一起编译。*/ 12 | 13 | #include /*程序编译预处理命令*/ 14 | 15 | // 对于main()主函数的分析,任何一个程序必须有一,而且有且只能有一个main()函数 16 | // main()函数是程序开始执行的唯一主入口 17 | 18 | // { }构成函数的语句部分叫做函数体 19 | 20 | int main(void) /* 定义主函数main()*/ 21 | { 22 | // 关于 \n 的解释 —— 对输出结果进行换行输出处理 23 | 24 | printf("Welcome to Programming with C language\n"); /*调用printf()函数输出文字*/ 25 | 26 | return 0; /*返回一个整数0*/ 27 | } 28 | 29 | // C语言中的所有语句都必须以分号(;)结束,程序中的所有标点符号都是英文符号。 30 | 31 | -------------------------------------------------------------------------------- /Chapter01/README.md: -------------------------------------------------------------------------------- 1 | ## 第一章 初识C语言 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | 5 | ### 1. C语言起源 6 | - 时间:1972年 7 | - 地点:贝尔实验室 8 | - 创始人:丹尼斯.里奇(Dennis Ritch)和肯·汤姆逊(Ken Thompson) 9 | 起因:在开发UNIX操作系统时设计了C语言。在B语言基础上进行设计,起源于B语言。 10 | 11 | ### 2. 选择C语言的理由 12 | - 设计特性 13 | - 高效性 14 | - 可移植性 15 | - 强大而灵活 16 | - 面向程序员 17 | 18 | **缺点**:指针错误难以察觉。 19 | 20 | ### 3. C语言的应用范围 21 | - UNIX操作系统 22 | - 计算机游戏 23 | - 嵌入式系统 24 | - 机器人工厂 25 | - PC应用 26 | - 计算机语言 27 | - 自动驾驶汽车、相机、DVD播放机和现代设备的微处理器 28 | 29 | ### 4. 高级计算机语言和编译器 30 | 编译器是将`高级语言程序`翻译成计算机所需的详细`机器语言指令集`的程序。主要负责处理冗长乏味的细节工作。 31 | 32 | 高级语言以更抽象的方式描述行为,没有与特定的CPU或指令集相关联,因此不同条件下需要使用不同的编译器,将高级语言程序编译成成适合在本地运行的机器语言指令集程序。 33 | 34 | ### 5. 语言标准 35 | - 第一个ANSI/ISO C标准 36 | - 时间:1989年正式公布 37 | - 该标准(ANSI)定义了C语言和C标准库 38 | - 国际标准化组织于1990采用这套标准和ANSI C是完全相同的标准。 39 | 40 | - C99 标准 41 | - 1994年修订了C标准,最终发布了C99标准。 42 | - C11标准 43 | - 2007年承诺C标准的下一个版本是CIX。2011年发布了C11标准。 44 | 45 | ### 6. 使用C语言的7个步骤 46 | ![](./img/使用C语言的7个步骤.png) 47 | 48 | - 定义程序的目标 49 | - 设计程序 50 | - 编写代码 51 | - 编译(源代码) 52 | - 运行程序 53 | - 常见的环境(Windows、UNIX终端模式和Linux终端模式) 54 | - 测试和调试程序 55 | - 维护和修改程序 56 | 57 | ### 7. 编程机制 58 | 生成程序的具体过程因计算机环境而异。 59 | 60 | #### 7.1 目标代码文件、可执行文件和库 61 | 62 | C是可移植性语言,故可多环境使用,包括:`UNIX、Linux、MS-DOS、Windows和Macintosh OS`。 63 | 64 | 用C语言编写程序时,编写的内容被存储在叫作`源代码文本文件`中。文件名以 `.c` 结尾。 65 | 66 | 👉小Tips:在文件中,`点号(.)`前面的部分称为`基本名`,点号后面的部分称为`扩展名`。 67 | > **文件名 = 基本名 + `.` + 扩展名**。例:hello.c 68 | 69 | **基本策略**:使用程序将 `源代码文件` 转换为 `可执行文件`,此文件包含可以运行`机器语言代码`。 70 | 71 | C实现的两个步骤:**编译和链接**。`编译器`将`源代码`转换为`中间代码 `,`链接器`将此`中间代码`与`其他代码`相结合生成`可执行文件`。 72 | 73 | 分块管理使得`程序模块化`,编译器会分块编译各个模块,`链接器`会将预编译过的模块再结合起来。 74 | 75 | 如果需要改变一个模块,则`不必重新编译所有其他的模块`。 76 | 77 | 将`源代码`转换为`机器语言代码`,将结果放置在一个 **`目标代码文件`**(或简称为目标文件)中 (源代码由单个文件组成)。 78 | 79 | 目标代码文件缺失`启动代码`(程序与OS之间的接口)和`库函数`。 80 | 81 | **编译器作用**:把源代码转换成可执行代码的程序。 82 | 83 | **链接器作用**:把编写的`目标代码、系统和标准启动代码和库代码`合并成一个`文件(可执行文件)`。 84 | 85 | `目标文件(只包含编译器编写的代码翻译的机器语言代码)` 和 `可执行文件(包含程序中的库函数和启动代码的机器代码)` 是由`机器语言`指令组成。 86 | 87 | #### 7.2 UNIX系统 88 | 在UNIX系统上编辑UNIX C不具备自己的编辑器。但可以使用一种通用UNIX编辑器 ,例如`emacs ,jove ,vi 或X-Windows`等文本编辑器。 89 | 90 | UNIX区分大小写。 91 | 92 | 源文件是整个编译过程的开始,不是结束。 93 | 94 | #### 7.3 在UNIX系统上编译 95 | ![](./img/用unix准备c程序.png) 96 | 97 | -------------------------------------------------------------------------------- /Chapter01/img/使用C语言的7个步骤.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter01/img/使用C语言的7个步骤.png -------------------------------------------------------------------------------- /Chapter01/img/用unix准备c程序.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter01/img/用unix准备c程序.png -------------------------------------------------------------------------------- /Chapter01/nonefile: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /Chapter01/复习题.md: -------------------------------------------------------------------------------- 1 | ## 复习题 2 | **1.对编程而言,可移植性意味着什么?** 3 | > 可以多环境使用,例如:UNIX/Linux、Windows、Mac OS等。 4 | 5 | 6 | **2.解释源代码文件,目标代码文件和可执行文件有什么区别?** 7 | - 源代码文件:单个文件组成 8 | - 目标代码文件:只包含编译器编写的代码翻译的机器语言代码 9 | - 可执行文件:包含程序中的库函数和启动代码的机器代码 10 | 11 | 目标文件和可执行文件都是`机器代码`组成。 12 | 13 | **3.编程的7个步骤是什么?** 14 | 15 | ![](./img/使用C语言的7个步骤.png) 16 | 17 | **4.编译器的任务是什么?** 18 | 把源代码转换成可执行代码的程序。 19 | 20 | **5.链接器的任务是什么?** 21 | 22 | 把编写的`目标代码、系统和标准启动代码和库代码`合并成一个`文件(可执行文件)`。 23 | 24 | -------------------------------------------------------------------------------- /Chapter01/编程题.md: -------------------------------------------------------------------------------- 1 | 你刚被MacroMusicle有限公司录用,该公司准备进入欧洲市场,需要一个把英寸单位转换为厘米单位(1英寸 = 2.54厘米)的程序,该程序要提示用户输入英寸值。 2 | ```c 3 | #include 4 | 5 | int main(void) 6 | { 7 | int inch; // 定义变量 8 | const double MID = 2.54; // 定义常量 9 | printf("请输入要你的英寸值:"); 10 | scanf("%d",&inch); 11 | 12 | float ceremeter; // 定义变量 13 | ceremeter = MID * inch; // 变量转换公式 14 | printf("转换后的厘米为: %.2f\n",ceremeter); 15 | return 0; 16 | } 17 | ``` -------------------------------------------------------------------------------- /Chapter02/01.简单的C程序例子及程序分析.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Date : 2019 - 12 - 30 3 | * Author : Soler HO 4 | * Description : 简单的C语言程序 5 | */ 6 | #include // 包含另一个文件,C编译器软件包的标准部分,提供键盘输入和屏幕输出的支持。 7 | /**#include 指令和头文件 8 | * 是一条C预处理器指令(preprocessor directive),C编译器在编译前会对源代码做一些准备,即预处理(preprocessing)。 9 | * 10 | * stdio.h 文件的含义是标准输入/输出头文件。通常,在C程序顶部的信息集合被称为头文件(header)。 11 | */ 12 | 13 | int main(void) /*一个简单的C程序*/ 14 | /** main()函数 15 | * C程序一定要从main()函数开始执行,除了main()函数,可以任意命名其他函数。圆括号是用于识别main()是一个函数。 16 | */ 17 | { 18 | /** 花括号、函数题和块 19 | * { 20 | * ... 21 | * } 22 | * 23 | * 所有的C函数都使用花括号标记函数体的开始和结束。 24 | * 不能省略 25 | */ 26 | int num; 27 | /**声明一个名为num的变量,int是C语言的一个关键字(keyword),表示一种基本的C语言数据类型, 28 | * num是一个标识符(identifier),也就是一个变量、函数或其他实体的名称。 29 | * 30 | */ 31 | num = 7; /*为num赋一个值*/ 32 | 33 | printf("I'm a simple "); /*使用printf()函数*/ 34 | printf("Computer .\n"); 35 | printf("My favorite number is %d . \n",num); /*换行符是一个转义序列,转义序列用于代码难以表示或无法输入的字符,如 \t 代表 Tab键*/ 36 | return 0; 37 | } //结束 38 | 39 | -------------------------------------------------------------------------------- /Chapter02/README.md: -------------------------------------------------------------------------------- 1 | ## 第二章 C语言概述 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | 5 | ### 1. 解析第一个C程序 6 | 一个基本的程序结构包含如下部分: 7 | 8 | 9 | 10 | ```c 11 | #include // 包含另一个文件,C编译器软件包的标准部分,提供键盘输入和屏幕输出的支持。 12 | /**#include 指令和头文件 13 | * 是一条C预处理器指令(preprocessor directive),C编译器在编译前会对源代码做一些准备,即预处理(preprocessing)。 14 | * 15 | * stdio.h 文件的含义是标准输入/输出头文件。通常,在C程序顶部的信息集合被称为头文件(header)。 16 | */ 17 | 18 | int main(void) /*一个简单的C程序*/ 19 | /** main()函数 20 | * C程序一定要从main()函数开始执行,除了main()函数,可以任意命名其他函数。圆括号是用于识别main()是一个函数。 21 | */ 22 | { 23 | /** 花括号、函数体和块 24 | * { 25 | * ... 26 | * } 27 | * 28 | * 所有的C函数都使用花括号标记函数体的开始和结束。 29 | * 不能省略 30 | */ 31 | int num; 32 | /**声明一个名为num的变量,int是C语言的一个关键字(keyword),表示一种基本的C语言数据类型, 33 | * num是一个标识符(identifier),也就是一个变量、函数或其他实体的名称。 34 | * 35 | */ 36 | num = 7; /*为num赋一个值*/ 37 | 38 | printf("I'm a simple "); /*使用printf()函数*/ 39 | printf("Computer .\n"); 40 | printf("My favorite number is %d . \n",num); /*换行符是一个转义序列,转义序列用于代码难以表示或无法输入的字符,如 \t 代表 Tab键*/ 41 | return 0; 42 | } //结束 43 | ``` 44 | > 程序由一个或多个函数组成,必须有`main()` 函数。 45 | > 46 | > 函数由 `函数头` 和 `函数体` 组成。 47 | > - 函数头包含`函数名、传入函数的信息类型和函数的返回类型`。 48 | > - 函数体被`花括号`括起来,由一系列`语句、声明`组成。 49 | 50 | ### 2. 提高程序可读性的技巧 51 | - 使用有意义的变量名和函数名 52 | - 使用注释(模块太大时备注说明) 53 | - 在函数中用空行分隔概念上的多个部分。 54 | - 每条语句各占一行 55 | > 分行易读,配合分号,可以更好确定语句开始和结束。 56 | 57 | 58 | ### 3. 调试程序 59 | Bug:程序的错误 60 | 调试(Debug):找出并修正程序错误的过程。 61 | 62 | #### 3.1 语法错误 63 | 64 | 不遵循C语言的规则,把有效的C符号放在正确的位置。 65 | 66 | 一般使用 `编译器(gcc)` 时会报错,一般情况下,报错的位置比实际错误位置要滞后一行。 67 | 68 | #### 3.2 语义错误 69 | 70 | 编译器检测不到语义错误,因为这类错误未违反C语言的规则。 71 | 72 | 遵循C规则,但结果不正确的错误属于语义错误。 73 | 74 | #### 3.3 程序状态 75 | 程序状态:在程序的执行过程中,某给定点上所有变量值的集合。 76 | 77 | 跟踪程序状态的3个方法 78 | - 自己模拟计算机逐步执行程序。 79 | > 小部分程序实用,大型程序不适合。 80 | - 在程序中的关键点插入额外的`printf() 语句`,以监视制定变量值的变化。 81 | - 使用`调试器` 82 | > Linux环境中,C和C++一般使用`GDB调试器`。 83 | 84 | ### 4. 关键字和保留标识符 85 | 86 | 关键字由于特殊的含义,故不能用作标识符。 87 | 88 | ![](./img/ISOC关键字.png) 89 | 90 | 如果使用关键字不当(如,关键字用作变量名),编译器会将其视为语法错误。 91 | 92 | 保留标识符包括以下划线字符开头的标识符和标准库函数名,如printf() 。 93 | -------------------------------------------------------------------------------- /Chapter02/img/C程序的结构.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter02/img/C程序的结构.png -------------------------------------------------------------------------------- /Chapter02/img/ISOC关键字.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter02/img/ISOC关键字.png -------------------------------------------------------------------------------- /Chapter02/复习题.md: -------------------------------------------------------------------------------- 1 | ## 第二章 C语言概述 复习题 2 | 3 | **1.C语言的基本模块是什么?** 4 | 5 | 基本模块和用户自定义模块的都称为函数。 6 | 7 | **2.什么是语法错误和语义错误?** 8 | - 语法错误:违反组成语句或程序的语法规则。例:`printf "hello world"`; 9 | 10 | - 语义错误:含义错误。例:n3 = n2*n 11 | 12 | 3. Indiana Sloth编写了下面的程序,并征求你的意见,请帮助他评定。 13 | ```c 14 | #include stdio.h 15 | 16 | int main(void) 17 | ( 18 | int s 19 | s:=56; 20 | printf(There are s weeks in a year.); 21 | return 0; 22 | ``` 23 | 程序错误是无法正常运行的。正确的修改如下: 24 | ```c 25 | #include 26 | int main(void) 27 | { 28 | int s; 29 | s = 56; // 变量赋值方式不正确 30 | printf("There are %d weeks int a year.",s); 31 | return 0; 32 | } 33 | ``` 34 | 35 | 4. 假设下面的4个例子都是完整程序中的一部分,他们都输出什么结果? 36 | ```c 37 | a. printf("Baa Baa Black Sheep."); /*输出结果为:Baa Baa Black Sheep.Have you any wool?*/ 38 | printf("Have you any wool?\n"); 39 | b. printf("Begone!\nO creature of lard!\n"); /*输出结果为:Begone!此处换行 O creature of lard!*/ 40 | c. printf("What?\nNo/nfish?\n"); /*输出结果为:What? 此处换行 No/nfish?*/ 41 | d. int num; 42 | num = 2; 43 | printf("%d + %d = %d",num,num,num+num); /*输出结果为:2 + 2 = 4*/ 44 | ``` 45 | 46 | 5. 在`main、int、function、char、=`中,哪些是C语言的关键字? 47 | 48 | 关键字为:main、int char。 49 | 50 | 6. 如何以下面的格式输出变量words和lines的值(例,3020和350代表两个变量的值)? 51 | There were 3020 words and 350 lines 52 | ```c 53 | #include 54 | 55 | int main(void) 56 | { 57 | int words,lines; 58 | words = 3020; 59 | lines = 350; 60 | 61 | printf("There were %d words and %d lines\n",words,lines); 62 | 63 | return 0; 64 | } 65 | ``` 66 | 67 | 68 | 7. 考虑下面的程序 69 | ```c 70 | #include 71 | int main(void) 72 | { 73 | int a,b; 74 | a = 5; 75 | b = 2; /*第 7 行*/ 76 | b = a; /*第 8 行*/ 77 | a = b; /*第 9 行*/ 78 | printf("%d %d \n",b,a); 79 | return 0; 80 | } 81 | ``` 82 | 请问,在执行完第7、第8、第9行后,程序的状态分别是什么? 83 | - 执行完第7行,a = 5,b = 2; 84 | - 执行完第8行,a = 5,b = 5 85 | - 执行完第9行,a = 5,b = 5 86 | 87 | 8. 考虑下面的程序 88 | ```c 89 | #include 90 | 91 | int main(void) 92 | { 93 | int x,y; 94 | x = 10; 95 | y = 5; /*第 7 行*/ 96 | y = x + y; /*第 8 行*/ 97 | x = x * y; /*第 9 行*/ 98 | printf("%d %d \n",x,y); 99 | return 0; 100 | } 101 | ``` 102 | 请问,在执行完第7、第8、第9行后,程序的状态分别是什么? 103 | - 执行第7行后,x = 10,y = 5 104 | - 执行第8行后,x = 10,y = 15 105 | - 执行第9行后,x = 150,y = 15 106 | -------------------------------------------------------------------------------- /Chapter02/编程题.md: -------------------------------------------------------------------------------- 1 | ## 第二章 C语言概述 编程练习题 2 | 3 | 1. 编写一个程序,调用一次printf()函数,把你的姓名打印在一行,再调用一次printf()函数,把你的姓名打印在两行。然后,再调用两次printf()函数,把你的姓名打印在一行。打印形式如下: 4 | 5 | Soler HO 第一次的内容 6 | 7 | Soler 第二次打印 8 | 9 | HO 第三次打印 10 | 11 | Soler HO 第三次 和第四次 打印 12 | 13 | ```c 14 | #include 15 | 16 | int main(void) 17 | { 18 | printf("my name is : Soler HO\n");/*第一次打印内容*/ 19 | printf("my first name is : Soler \n");/*第二次打印 ---- 名字*/ 20 | printf("my last name is : HO \n"); /*第三次打印 ---- 姓*/ 21 | printf("total name is : Soler HO \n"); /*第三次和第四次打印*/ 22 | return 0; 23 | } 24 | ``` 25 | 2. 编写一个程序,打印你的姓名和地址 26 | ```c 27 | #include 28 | int main(void) 29 | { 30 | char name[] = "Soler HO"; 31 | char address[] = "Shanghai"; 32 | 33 | printf("my name is %s , Address is %s .\n",name,address); 34 | 35 | return 0; 36 | } 37 | ``` 38 | 3. 编写一个程序,把你的年龄转换成天数,并显示这两个值,不用考虑闰年的问题 39 | ```c 40 | /*编写一个程序,把你的年龄转换成天数,并显示这两个值,不用考虑闰年的问题*/ 41 | #include 42 | int main(void) 43 | { 44 | int age,days; 45 | int oneYear = 365; /*此处默认一年为365天*/ 46 | 47 | printf("please input your age : \n"); 48 | scanf("%d",&age); 49 | days = age * oneYear; 50 | printf("you live %d days\n",days); 51 | return 0; 52 | 53 | } 54 | ``` 55 | 56 | 4. 编写一个程序,生成以下输出: 57 | ``` 58 | For he's a jolly good fellow! 59 | For he's a jolly good fellow! 60 | For he's a jolly good fellow! 61 | Which nobody can deny! 62 | ``` 63 | 除了main()函数以外,该程序还要调用两个自定义函数:一个名为jolly(),用于打印前3条消息,调用一次打印一条:另一个函数名为deny(),打印最后一条消息。 64 | ```c 65 | #include 66 | 67 | void jolly(void); 68 | void deny(void); 69 | 70 | int main(void) 71 | { 72 | for(int i=1;i<=3;i++) 73 | jolly(); 74 | deny(); 75 | } 76 | void jolly(void) 77 | { 78 | printf("For He's a jolly good fellow!\n"); 79 | } 80 | 81 | void deny(void) 82 | { 83 | printf("which nobody can deny!\n"); 84 | } 85 | ``` 86 | 87 | 5. 编写一个程序,生成以下输出: 88 | ``` 89 | Brazil, Russia, India, China 90 | India, China, 91 | Brazil, Russia 92 | ``` 93 | 除了main()以外,该程序还要调用两个自定义函数:一个名为br(),调用一次打印一次"Brazil,Russia";另一个名为ic(),调用一次打印一次"India,China"。其他内容在main()函数中完成。 94 | ```c 95 | /*编程练习题05*/ 96 | #include 97 | 98 | void br(void); 99 | void ic(void); 100 | 101 | int main(void) 102 | { 103 | br(); 104 | printf(","); 105 | ic(); 106 | printf("\n"); 107 | ic(); 108 | printf(",\n"); 109 | br(); 110 | printf("\n"); 111 | return 0; 112 | } 113 | 114 | void br(void) 115 | { 116 | printf("Brazil,Russia"); 117 | } 118 | 119 | void ic(void) 120 | { 121 | printf("India,China"); 122 | } 123 | ``` 124 | 6. 编写一个程序,创建一个整型变量toes,并将toes设置为10.程序中还要计算toes的两倍和toes的平方。该程序应打印3个值,并分别描述以示区分。 125 | ```c 126 | #include 127 | 128 | int main(void) 129 | { 130 | int toes = 10; 131 | int twotimes = 2 * toes; 132 | int square = toes * toes; 133 | printf("origin toes is %d \n",toes); 134 | printf("double toes is %d \n",twotimes); 135 | printf("square toes is %d \n",square); 136 | 137 | return 0; 138 | } 139 | ``` 140 | 141 | 7. 编写一个程序,生成以下格式的输出: 142 | ``` 143 | Smile!Smile!Smile! 144 | Smile!Smile! 145 | Smile! 146 | ``` 147 | 该程序要定义一个函数,该函数要被调用一次打印一次“Smile!”,根据程序的需要使用该函数。 148 | ```c 149 | #include 150 | 151 | void smile(void); 152 | 153 | int main(void) 154 | { 155 | /*具体优化后续重新优化*/ 156 | for(int i = 3;i>=1;i--) 157 | smile(); 158 | printf("\n"); 159 | for(int i = 1;i<3;i++) 160 | smile(); 161 | printf("\n"); 162 | smile(); 163 | printf("\n"); 164 | return 0; 165 | } 166 | void smile(void) 167 | { 168 | printf("Smile!"); 169 | } 170 | ``` 171 | 8. 在C语言中,函数可以调用另一个函数。编写一个程序,调用一个名为one_three()的函数。 172 | 该函数在一行打印单词"one",再调用第2个函数two(),然后在另一行打印单词“three”。 173 | two()函数在一行显示单词“two”。main()函数在调用one_three()函数前要打印短语“starting now:”, 174 | 并在调用完毕后显示短语“done!”。因此,该程序的输出应如下所示: 175 | ``` 176 | starting now: 177 | one 178 | two 179 | three 180 | done! 181 | ``` 182 | ```c 183 | #include 184 | 185 | void one_three(void); 186 | void two(void); 187 | 188 | int main(void) 189 | { 190 | printf("starting Now : \n"); 191 | one_three(); /*函数嵌套*/ 192 | 193 | printf("Done!\n"); 194 | return 0; 195 | } 196 | void one_three(void) 197 | { 198 | printf("one \n"); 199 | two(); 200 | } 201 | 202 | void two(void) 203 | { 204 | printf("two\n"); 205 | printf("three\n"); 206 | } 207 | ``` -------------------------------------------------------------------------------- /Chapter03/README.md: -------------------------------------------------------------------------------- 1 | ## 第三章 数据和C 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | 5 | ### 1. 变量与常量数据 6 | 数据:承载信息的数字和字符。 7 | 8 | - **常量(constant)**:程序执行过程中不发生变化。 9 | - **变量(variable)**:程序执行期间会发生变化或者被赋值。 10 | 11 | 12 | ### 2. 数据:数据类型关键字 13 | - 常量数据,编译器一般通过用户书写的形式来识别类型。 14 | - 变量数据,要在声明时指定其类型。 15 | 16 | 17 | C语言的数据类型关键字:`int、long、short、char、float、double、void、signed、unsigned、_Bool`。 18 | - int表示整数类型 19 | - char用于指定字母或其它字符,也可表示较小整数。 20 | - float和double表示小数点的数 21 | - _Bool类型表示布尔值(true和false) 22 | 23 | #### 2.1 位、字节和字 24 | `位、字节和字`是描述计算机`数据单元`或`存储单元`的术语。 25 | > `最小的存储单元` ----> `位(bit)`,可存储`0`或`1`,也是计算机内存的基本构建块。 26 | > 27 | > 常用的`存储单位` ----> `字节(byte)`,`1 字节 = 8 bit(位)` 28 | > 29 | > 设计计算机时给定的`自然存储单位` ----> 字(word)`1字长 = 8位`。 30 | 31 | #### 2.2 整数类型 32 | 整数就是`没有小数部分`的数。计算机一般使用`二进制(0和1组成)`存储整数。 33 | 34 | #### 2.3 浮点数类型 35 | 浮点值:在一个值后面加上一个小数点。 36 | 37 | 计算机中的`浮点数`分成`小数部分`和`指数部分`来表示。 38 | 39 | #### 2.4 整数和浮点数的区别 40 | - 整数没小数部分,浮点数有小数部分。 41 | - 浮点数可以表示的范围比整数大。 42 | - 对于一些算术运算(减法),浮点数损失的精度更多。 43 | - 计算机的浮点数不能表示区间所有的值,浮点数通常是实际值的近似值。 44 | 45 | ### 3. C语言基本数据类型 46 | 基本数据类型由11个关键字组成:`int、long、short、char、float、double、void、signed、unsigned、_Bool、_Complex和_Imaginary`。 47 | #### 3.1 int类型 48 | int类型是有符号整型,即int类型的值必须是整数(正整数、负整数或零)。其取值范围取决于计算机系统。 49 | > 一般情况下,存储一个int占用一个机器字长。 50 | - `int` ----> 系统给定的基本整数类型,C语言规定int类型不小于16位。 51 | - `short 和 short int` ----> 最大的short类型整数小于或等于最大的int类型整数。C语言规定short类型至少占16位。 52 | - `long或long int` ----> 可表示整数大于或等于最大的int类型整数。C语言规定long类型至少占32位。 53 | - `long long 或 long long int` ----> 可表示的整数大于或等于最大的long类型整数。Long long类型至少占64位。 54 | 55 | `无符号整型`只能表示`零和正整数`。在整数类型前加上 unsigned关键字表明变量为无符号整型。 56 | > 无符号整型可表示的正整数比有符号整型的大。 57 | 58 | 声明int类型的变量,写int,再写变量名,以分号结束。 59 | > 如果是多个变量,既可单独声明,也可int后列出多个变量名,变量名之间用逗号分隔。 60 | ```c 61 | int years; 62 | int month,week.days; 63 | ``` 64 | - 初始化变量 65 | 初始化变量:为变量赋一个初始值(在变量后面加上`赋值运算符(=)`和`待赋给变量的值`即可)。 66 | 67 | ⚠️注意:最好不要把`初始化的变量`和`未初始化的变量`放在同一条声明中。 68 | 69 | 👉 总结:声明为变量创建和标记存储空间,并为其指定初始值。 70 | 71 | #### 3.2 使用字符:`char`类型 72 | char 类型用于`存储字符`(如字母或标点符号)。 73 | >技术角度,char是整数类型(实际上存储的是整数而不是字符)。 74 | 75 | 计算机用`ASCII编码`来处理字符,即特定的整数表示特定的字符。 76 | 77 | `char`类型被定义为`8位(1字节)`的存储单元。 78 | 79 | - 声明和初始化 80 | ```c 81 | char grade = 'A'; 82 | char grade = A; 83 | char grade = "A"; 84 | ``` 85 | > 在C语言中,用`单引号`括起来的单个字符称为`字符常量`。 86 | > 87 | > ⚠️注意:如果`省略单引号`,编译器则认为A是`变量名`。 88 | > 89 | > 如果使用`双引号` 括起来,则认为A是`字符串`。 90 | 91 | - 非打印字符 92 | 93 | 单引号只适用于字符、数字和标点符号。但表示特殊行为的符号序列,则只能通过转义序列来打印。常见的转义序列及其含义如下: 94 | 95 | ![](./img/转义序列.png) 96 | 97 | ⚠️注意:在使用ASCII码时,注意数字和数字字符的区别。 98 | 99 | #### 3.3 布尔类型 100 | `布尔值`表示`true`和`false`。C语言用1表示true,0表示false。 101 | 102 | `_Bool` ----> 布尔类型的关键字。布尔类型是无符号int类型,所占用的空间只能存储0或1即可。 103 | 104 | #### 3.4 实浮点类型 105 | 实浮点类型可表示 `单精度浮点型` 和 `双精度浮点型`。 106 | 107 | - `float` ----> 单精度浮点型,系统的基本浮点类型,可精确到至少6位有效数字。 108 | - `double` ----> 双精度浮点型,存储范围更大,能表示至少10位有效数字和更大的指数。 109 | 110 | #### 3.5 复数和虚数浮点数 111 | 虚数类型是可选的类型。 112 | 113 | 复数的`实部`和`虚部`类型都基于实浮点类型来构成。 114 | ```c 115 | float _Complex 116 | double _Complex 117 | long double _Complex 118 | float _Imaginary 119 | double _Imaginary 120 | long long _Imaginary 121 | ``` 122 | #### 3.6 如何声明简单变量 123 | > 1. 选择需要合适的类型 124 | > 2. 使用有效的字符给变量起一个变量名 125 | > 3. 声明格式:`类型说明符 变量名;` 126 | > 4. 声明的同时可进行初始化变量 127 | > 5. 可同时声明多个相同类型的多个变量,用逗号分隔各个变量名。 128 | > 6. ⚠️注意合理选择合适的类型(编译器类型检查时,会导致部分不匹配的数据丢失)。 129 | 130 | #### 3.7 求类型大小的例子 131 | ```c 132 | #include 133 | 134 | int main(void) 135 | { 136 | /* C99 为类型大小提供了%zd转换说明 */ 137 | 138 | /**sizeof()是C语言的内置运算符,以 字节 为单位给出指定类型的大小。 139 | * 140 | * 在C99 和 C11 提供 %zd 转换说明匹配sizeof 的返回类型。 141 | * 142 | * 如果一些编译器不支持,就使用 %u 或 %lu 代替 %zd。 143 | * 144 | */ 145 | printf("打印 int 类型的字节大小为:%zd \n",sizeof(int)); 146 | printf("打印 char 类型的字节大小为:%zd \n",sizeof(char)); 147 | printf("打印 long 类型 的字节大小为:%zd \n",sizeof(long)); 148 | printf("打印 long long 类型 的字节大小为:%zd \n",sizeof(long long)); 149 | printf("打印 double 类型的字节大小为:%zd \n",sizeof(double)); 150 | printf("打印 long double 类型的字节大小为:%zd \n",sizeof(long double)); 151 | 152 | return 0; 153 | } 154 | ``` 155 | 156 | 👉 小Tips:使用`printf()` 函数时,切记检查每个待打印值对应的转换说明,还要检查转换说明的类型是否与待打印值的类型相匹配。 157 | > 转换说明简单的形式:`百分号(%)` 和 `转换字符` 组成,如`%d`。 158 | -------------------------------------------------------------------------------- /Chapter03/img/转义序列.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter03/img/转义序列.png -------------------------------------------------------------------------------- /Chapter03/复习题.md: -------------------------------------------------------------------------------- 1 | ## 第三章 数据和C 复习题 2 | 3 | 1. 指出下面各种数据使用的合适数据类型(有些可使用多种数据类型): 4 | 5 | a. East Simpleton的人口 6 | > int short int unsigned short 7 | 8 | b. DVD 影碟的价格 9 | > float double 10 | 11 | c. 本章出现次数最多的字母 12 | > char 13 | 14 | d. 本章出现次数最多的字母次数 15 | > int unsigned int 16 | 17 | 2. 在什么情况下使用long类型的变量代替int类型的变量? 18 | > 超出了int能够表示的范围,如果要处理更大的值,需保持系统一致(如32位或64位),从而可以提高程序的可移植性。 19 | 20 | 3. 使用哪些可移植性的数据类型可以获得32位有符号整数?选择的理由是什么? 21 | 常见的:int_32t(系统本身32位)、int_fast32_t(运算最快的32位) 22 | 23 | 4. 指出下列常量的类型和含义(如果有的话): 24 | 25 | a. '\b' char类型 26 | 27 | b. 1066 int 类型 28 | 29 | c. 99.44 float类型 30 | 31 | d. 0XAA int 类型(十六进制格式) 32 | 33 | e. 2.0e30 long double类型 34 | 35 | 5. Dottie Cawm编写了一个程序,请找出程序中的错误。 36 | ```c 37 | #include 38 | main 39 | ( 40 | float g;h; 41 | float tax,rate; 42 | g = e21; 43 | tax = rate * g; 44 | ) 45 | ``` 46 | 正确的代码如下: 47 | ```c 48 | #include 49 | int main(void) 50 | { 51 | float g,h; 52 | float tax,rate; 53 | /*上面可合并*/ 54 | float g,h,tax,rate; 55 | g = 1e21; //e前面至少要有一个数字 56 | tax = rate * g; 57 | } 58 | ``` 59 | 8. 假设一个程序开始处有如下的声明: 60 | ```c 61 | int imate = 2; 62 | long shot = 53456; 63 | char grade = 'A'; 64 | float log = 2.71828; 65 | ``` 66 | 在下面printf()语句中添上合适的类型说明符: 67 | ```c 68 | printf("The odds against the %___ were %___ to 1.\n", imate, shot); 69 | printf("A score of %___ is not an %___ grade.\n", log, grade); 70 | ``` 71 | 答案code如下: 72 | ```c 73 | printf("The odds against the %d were %d to 1.\n", imate, shot); 74 | printf("A score of %f is not an %c grade.\n", log, grade); 75 | ``` 76 | 9. 假设ch为char类型变量。使用转义序列、十进制值、八进制字符常量以及十六进制字符常量等方法将其赋值为回车符(假设使用ASCII编码)。 77 | ```c 78 | char ch = '\r'; 79 | char ch = 77; 80 | char ch = '\0115'; 81 | char ch = '\x4D'; 82 | ``` 83 | 10. 改正下面程序(在C中/表示除法)。 84 | ```c 85 | void main(int) / this progarm is perfect / 86 | { 87 | cows, legs integer; 88 | printf("How many cow legs did you count?\n); 89 | scanf("%c", legs); 90 | cows = legs / 4; 91 | printf("That implies there are %f cows.\n", cows) 92 | } 93 | ``` 94 | 修正后的Code如下: 95 | ```c 96 | #include 97 | int main(void) /*this progarm is perfect*/ 98 | { 99 | int cows,legs; 100 | printf("How many cow legs did you count ? \n"); 101 | scanf("%d",&legs); 102 | cows = legs/4; 103 | printf("That implies there are %d cows .\n",cows); 104 | return 0; 105 | } 106 | ``` 107 | 11. 指出下列转义字符的含义: 108 | ```c 109 | a.\n /*换行符*/ 110 | b.\\ /*反斜杠*/ 111 | c.\" /*双引号*/ 112 | d.\t /*指标符号Tab*/ 113 | ``` 114 | 115 | -------------------------------------------------------------------------------- /Chapter03/编程题.md: -------------------------------------------------------------------------------- 1 | ## 第三章 数据和C 练习题 2 | 3 | 1. 通过试验(即编写带有此类问题的程序)观察系统如何处理整数上溢、浮点数上溢和浮点数下溢的情况。 4 | 5 | 6 | 2. 编写程序,要求提示输入一个ASCII码值(如:66),然后打印出输入的字符。 7 | ```c 8 | #include 9 | 10 | int main(void) 11 | { 12 | int asc_num; 13 | printf("请输入ASCII码值(如66): \n"); 14 | scanf("%d",&asc_num); 15 | printf("对应的字符为:%c \n",asc_num); 16 | return 0; 17 | } 18 | ``` 19 | 3. 编写一个程序,发出一声警报,然后打印出下面的文本。 20 | ``` 21 | Startled by the sudden sound,Sally Shouted, 22 | "By the Great Pumpkin,What was that!" 23 | ``` 24 | ```c 25 | #include 26 | /*提示:ANSI C \a 表示警报*/ 27 | int main(void) 28 | { 29 | printf("\a"); 30 | printf("Startled by the sudden sound,Sally Shouted,\n"); 31 | printf("\"By the Great Pumpkin,What was that!\"\n"); 32 | return 0; 33 | } 34 | ``` 35 | 4. 编写一个程序,读取一个浮点数,先打印成小数点形式,再打印成指数形式。如果系统支持,再打印成p计数法(即十六进制计数法)。按以下个数输出(实际显示的指数位数因系统而异): 36 | ``` 37 | Enter a floating-point value:64.25 38 | fixed-point notation:64.250000 39 | exponential notation:6.425000e+01 40 | p notation:0x1.01p+6 41 | ``` 42 | ```c 43 | #include 44 | 45 | int main(void) 46 | { 47 | float value; 48 | printf("Enter a float-point value: \n"); 49 | scanf("%f",&value); 50 | printf("fixed-point notation : %.6f \n",value); 51 | printf("exponential notation : %e \n",value); 52 | printf("p notation : %p \n",value); 53 | return 0; 54 | } 55 | ``` 56 | 5. 一年大约有3.156 * 10^7 秒,编写程序,提示用户输入年龄,然后显示该年龄对应的秒数。 57 | ```c 58 | #include 59 | /*该chapter未学习#define语句,故不使用*/ 60 | int main(void) 61 | { 62 | float years_second = 3.156e+7; 63 | int age; 64 | printf("please input your age : \n"); 65 | scanf("%d",&age); 66 | float age_to_second = years_second * age; 67 | printf("your age is equal to %e second \n",age_to_second); 68 | return 0; 69 | } 70 | ``` 71 | 72 | 73 | 6. 一个水分子的质量约为3.0*10^-23克,1夸脱水大约是950克。编写程序,提示用户输入水的夸脱数,并显示水分子的数量。(个人备注:由于该章节还没有学习#define,所以就不再使用#define方式去定义常量。) 74 | ```c 75 | #include 76 | 77 | int main(void) 78 | { 79 | float one_quality = 3.0e-23; 80 | int one_quart = 950; 81 | int quart; 82 | printf("please input water quart_num : \n"); 83 | scanf("%d",&quart); 84 | /*下面两个语句可合并为同一个*/ 85 | float total_quality = quart * one_quart; 86 | float num = total_quality / one_quality; 87 | printf("water molecule num is %e \n",num); 88 | return 0; 89 | } 90 | ``` 91 | 92 | 7. 1英寸相当于2.54 厘米。编写程序,提示用户输入身高(/英寸),然后以厘米为单位显示身高。 93 | ```c 94 | #include 95 | 96 | int main(void) 97 | { 98 | float one_inch_cm = 2.54; 99 | float cm,inch; 100 | printf("please input your height (/inch) : \n"); 101 | scanf("%f",&inch); 102 | cm = inch * one_inch_cm; 103 | printf("your height is %.2f cm \n",cm); 104 | return 0; 105 | } 106 | ``` 107 | 108 | 109 | 110 | 8. 在美国的体积测量系统中,1品脱等于2杯,1杯等于8盎司,1盎司等于2大汤勺,1大汤勺等于3茶勺。编写程序,提示用户输入杯数,并以品脱、盎司、汤勺、茶勺为单位显示等量。 111 | ```c 112 | #include 113 | 114 | int main(void) 115 | { 116 | float one_cup,one_pint,one_ounce,one_spoon,one_teaspoon; 117 | printf("please input your cup_num : \n"); 118 | scanf("%f",&one_cup); 119 | 120 | one_pint = one_cup/2; 121 | one_ounce = 8; 122 | one_spoon = one_ounce * 2; 123 | one_teaspoon = one_spoon * 3; 124 | 125 | printf("%.2f cup is %.2f pint is %.2f ounce is %.2f spoon is %.2f teaspoon \n", 126 | one_cup,one_pint,one_ounce,one_spoon,one_teaspoon); 127 | 128 | return 0; 129 | } 130 | ``` 131 | -------------------------------------------------------------------------------- /Chapter04/README.md: -------------------------------------------------------------------------------- 1 | ## 第四章 字符串和格式化输入/输出 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | ## 1. 字符串简介 5 | ### 1.1 char类型数组和null字符 6 | 字符串 是一个或多个字符的序列。 7 | > 由双引号括起来。 8 | 9 | C语言没有专门用于储存字符串的变量类型,字符串都是被存储到`char类型`的数组中。 10 | 11 | 数组是由`连续`的存储单元组成,字符串的字符被存储在`相邻的存储单元`中,每个单元存储一个字符。 12 | 13 | ⚠️注意:在数组末尾位置的字符是 `\0`。这是一个`空字符(null character)`。 14 | 15 | C语言中使用 `\0` 规定了标记字符串的`结束`。空字符串不是数字0。是非打印字符,对应的ASCII码值是:0。 16 | 17 | ### 1.2 `strlen()`函数 18 | string.h头文件 包含了`strlen()`函数和其他与字符串相关的函数(如拷贝字符串的函数和字符串查找函数)。 19 | 20 | `sizeof运算符`以`字节`为单位给出对象的`大小`。(把字符串末尾不可见的空字符计算在内) 21 | > 运算对象是类型时,圆括号不可少,是特定变量时,可有可无。 22 | 23 | 24 | `strlen()`函数给出字符串中的`字符长度`。 25 | 26 | ## 2. 常量和C预处理器 27 | ```c 28 | #define PI 3.14; 29 | ``` 30 | 这样输入实际值的常量叫作 `符号常量` 或 `明示常量`。 31 | 32 | ### 2.1 创建符号常量的方法 33 | - 声明一个变量,然后将变量设置为所需的常量。 34 | - 使用 `#define`(C预处理器)来定义常量(编译程序时替换)。 35 | > ~~中间没有 = 符号,末尾不加分号~~。 36 | > 37 | > 一般推荐 `#define` 声明的常量变量名`大写`。大写常量可提高程序的可读性。 38 | 39 | ### 2.2 const限定符 40 | C90标准增加`const关键字`。用于限定一个变量为`只读`。 41 | ```c 42 | const int age = 25; // 变量在程序中不可更改 43 | ``` 44 | const 比 #define更灵活。 45 | 46 | ### 2.3 明示常量 47 | C头文件中提供了两种明示常量。 48 | - `limits.h` 提供了与整数类型大小限制相关的一系列明示常量。 49 | - `float.h` 提供了与浮点数大小限制相关的明示常量。 50 | 51 | ## 3. `printf()` 和 `scanf()` 52 | 53 | `printf()` 和 `scanf()` 函数让用户与程序交流,属于`输入输出函数(I/O函数)`。 54 | 55 | ### 3.1 printf() 函数 56 | 请求printf() 函数打印数据的指令要与待打印数据的类型相匹配。 57 | 58 | printf()中包含的信息: 59 | 60 | ![](./img/格式字符串.png) 61 | 62 | `转换说明`:指定如何把数据转换成可显示的形式。ANSI C常见的转换说明符如下: 63 | 64 | ![](./img/转换说明符.png) 65 | 66 | ⚠️注意:***格式字符串中的转换说明一定要与后面的每个项相匹配***。 67 | 68 | 👉**小Tips**:可以使用`转换说明符`控制输出的外观:`字段宽度、小数位和字段内的布局`。 69 | 70 | ### 3.2 scanf() 函数 71 | scanf() 函数的规则: 72 | - 如果`scanf()` 读取`基本变量类型的值`,在`变量名前面加上一个 &`。 73 | - 如果用 `scanf()` 把字符串读入到`字符数组`中,`不要使用 &`。 74 | -------------------------------------------------------------------------------- /Chapter04/img/格式字符串.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter04/img/格式字符串.png -------------------------------------------------------------------------------- /Chapter04/img/转换说明符.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter04/img/转换说明符.png -------------------------------------------------------------------------------- /Chapter04/复习题.md: -------------------------------------------------------------------------------- 1 | ## 第四章 字符串和格式化输入/输出 复习题 2 | 3 | 1. 再次运行程序清单 4.1 ,但是在要求输入名时,请输入名和姓(根据语文书写习惯),名和姓中间有个空格,看看会发生什么情况?为什么? 4 | ```c 5 | 程序不能正常运行。 6 | name中的scanf只能读取用户输入的名,而输入的姓则被放在临时存储区,等待下一个scanf() 语句来读取。所以体重会被读成姓。 7 | ``` 8 | 9 | 2. 假设下列示例是完整程序中的一部分,他们打印的结果分别是什么? 10 | ```c 11 | a. printf("He sold the painting for $2.2f.\n",2.345e2); // 输出:He sold the painting for $234.50. 12 | b. printf("%c%c%c\n",'H',105,'\41'); // 输出:Hi! ASCII值转换而来。 13 | c. #define Q "His Hamlet was funny without being vulgar." 14 | printf("%s\n has %d characters.\n",Q,strlen(Q)); // 输出:His Hamlet was funny without being vulgar. // 注意换行符 has 42 character. 15 | d. printf("Is %2.2e the same as %2.2f?\n",1201.0,1201.0); // 输出:Is 1.20e+003 the same as 1210.00? 16 | ``` 17 | 18 | 3. 在第2题中的c中,要输出包含双引号的字符串Q,应如何修改? 19 | ```c 20 | #define Q "His Hamlet was funny without being vulgar." 21 | printf("\"%s\"\n has %d characters.\n",Q,strlen(Q)); 22 | ``` 23 | 24 | 25 | 4. 找出下面程序中的错误 26 | ```c 27 | define B booboo 28 | define X 10 29 | main(int) 30 | { 31 | int age; 32 | char name; 33 | printf("Please enter your first name."); 34 | scanf("%s",name); 35 | printf("All right, %c, what's your age ?\n",name); 36 | scanf("%f",age); 37 | xp = age + X; 38 | printf("That's a %s! You must be at least %d .\n",B,xp); 39 | rerun 0; 40 | } 41 | ``` 42 | 修改后的完成程序如下: 43 | ```c 44 | #include 45 | #define B "booboo" 46 | #define X 10 47 | int main(void) 48 | { 49 | int age,xp; 50 | char name[40]; 51 | printf("Please enter your first name."); 52 | scanf("%s",name); 53 | printf("All right, %s, what's your age ?\n",name); 54 | scanf("%d",&age); 55 | xp = age + X; 56 | printf("That's a %s! You must be at least %d .\n",B,xp); 57 | return 0; 58 | } 59 | ``` 60 | 61 | 5. 假设一个程序的开头是这样: 62 | ```c 63 | #define BOOK "War and Peace" 64 | int main(void) 65 | { 66 | float cost = 12.99; 67 | float percent = 80.0; 68 | } 69 | ``` 70 | 构造一个使用BOOK、cost和percent的printf() 语句,打印以下内容:
71 | This copy of "War and Peace" sells for $12.99.
72 | That is 80% of list
73 | 74 | ```c 75 | printf("This copy of \"%s\" sells for %.2f.\nThat is %d%% of list",BOOK,cost,percent); 76 | ``` 77 | 78 | 6. 打印下列各项内容要分别使用什么转换说明? 79 | a. 一个字段宽度与位数相同的十进制整数
80 | b. 一个形如8A、字段宽度为4的十六进制数
81 | c. 一个形如232.346、字段宽度为10的浮点数
82 | d. 一个形如2.33e+002、字段宽度为12的浮点数
83 | e. 一个字段宽度为30、左对齐的字符串
84 | ```c 85 | // 具体使用的转换说明符如下: 86 | a. %d 87 | b. %4x 88 | c. %10.3f 89 | d. %12.2e 90 | e. %-30s // + 表示右对齐 - 表示左对齐 91 | ``` 92 | 93 | 7. -------------------------------------------------------------------------------- /Chapter04/编程题.md: -------------------------------------------------------------------------------- 1 | ## 第四章 字符串和格式化输入/输出 编程练习题 2 | 3 | 1. 编写一个程序,提示用户输入名和姓,然后以 “名,姓”的格式打印出来。 4 | ```c 5 | #include 6 | 7 | int main(void) 8 | { 9 | char lname[20],fname[20]; 10 | printf("please input your first name : "); 11 | scanf("%s",&fname); 12 | printf("please input your last name : "); 13 | scanf("%s",&lname); 14 | printf("my name is : %s,%s \n",fname,lname); 15 | return 0; 16 | } 17 | ``` 18 | 19 | 2. 编写一个程序,提示用户输入名和姓,并执行以下操作:
20 | a. 打印名和姓,包括双引号
21 | b. 在宽度为20的字段右端打印名和姓,包括双引号
22 | c. 在宽度为20的字段左端打印名和姓,包括双引号
23 | d. 在比姓名宽度宽3的字段中打印名和姓
24 | ```c 25 | #include 26 | #include 27 | int main(void) 28 | { 29 | char fname[30],lname[30]; 30 | printf("please input your name : "); 31 | scanf("%s %s",&fname,&lname); 32 | printf("this is a : \"%s %s\" \n",fname,lname); 33 | printf("this is b : \"%20s %20s\" \n",fname,lname); 34 | printf("this is c : \"%-20s %-20s\" \n",fname,lname); 35 | printf("this is d : %*s %*s \n",strlen(fname)+3,fname,strlen(lname)+3,lname); 36 | return 0; 37 | } 38 | ``` 39 | 40 | 3. 编写一个程序,读取一个浮点数,首先以小数点记数法,然后以指数记数法打印。用下面的格式输出(系统不同,指数记数法显示的位数可能不同)
41 | a. 输入21.3 或2.1e+001
42 | b. 输入+21.290 或 2.129E+001
43 | ```c 44 | #include 45 | 46 | int main(void) 47 | { 48 | float fnum; 49 | printf("please input a float number : "); 50 | scanf("%f",&fnum); 51 | printf("a. float is %.1f ; e usage is : %.1e \n",fnum,fnum); 52 | printf("b. float is %.3f ; e usage is : %.3e \n",fnum,fnum); 53 | return 0; 54 | } 55 | ``` 56 | 4. 编写一个程序,提示用户输入身高(单位;英寸)和姓名,然后以下面的格式显示用户刚输入的信息:
57 | Dadney,you are 6.208 feet tall
58 | 使用float类型,并用/作为除号,如果愿意,可以要求用户以厘米为单位输入身高,并以米为单位显示出来。 59 | ```c 60 | #include 61 | 62 | int main(void) 63 | { 64 | float cm,m; 65 | char name[20]; 66 | printf("please input your name : "); 67 | scanf("%s",name); 68 | printf("please input your height : "); 69 | scanf("%f",&cm); 70 | m = cm/100; 71 | printf("%s , you are %.f cm = %.2f m tall \n",name,cm,m); 72 | return 0; 73 | } 74 | ``` 75 | 5. 编写一个程序,提示用户输入以兆位每秒(Mb/s)为单位的下载速度和以兆字节(MB)为单位的文件大小。程序中应计算文件的下载时间。注意,这里1字节等于8位。使用float类型,并用/作为除号。该程序要以下面的格式打印 3 个变量的值(下载速度、文件大小和下载时间),显示小数点后面两位数字:
76 | At 18.12 megabits per second, a file of 2.20 megabytes
77 | downloads in 0.97 seconds. 78 | ```c 79 | #include 80 | 81 | int main(void) 82 | { 83 | float megabits,megabytes; 84 | printf("please input megabits : "); 85 | scanf("%f",&megabits); 86 | printf("please input megabytes : "); 87 | scanf("%f",&megabytes); 88 | float time = 8 * megabytes / megabits; 89 | printf("At %.2f megabits per second, a file of %.2f megabytes \n",megabits,megabytes); 90 | printf("downloads in %.2f seconds.\n",time); 91 | return 0; 92 | } 93 | ``` 94 | 6. 编写一个程序,先提示用户输入名,然后提示用户输入姓。在一行打印用户输入的名和姓,下一行分别打印名和姓的字母数。字母数要与相应名和姓的结尾对齐,如下所示: 95 | ``` 96 | Melissa Honeybee 97 | 7 8 98 | 接下来,再打印相同的信息,但是字母个数与相应名和姓的开头对齐,如下所示: 99 | Melissa Honeybee 100 | 7 8 101 | ``` 102 | 103 | ```c 104 | #include 105 | #include 106 | int main(void) 107 | { 108 | char lname[30],fname[30]; 109 | printf("please input your first name : "); 110 | scanf("%s",fname); 111 | printf("please input your last name : "); 112 | scanf("%s",lname); 113 | printf("%s %s\n",fname,lname); 114 | int fname_len = strlen(fname); 115 | int lname_len = strlen(lname); 116 | printf("%*d %*d \n",fname_len,fname_len,lname_len,lname_len); 117 | printf("%*d %*d \n",-fname_len,fname_len,-lname_len,lname_len); 118 | return 0; 119 | } 120 | ``` 121 | 7. 编写一个程序,将一个double类型的变量设置为1.0/3.0,一个float类型的变量设置为1.0/3.0。分别显示两次计算的结果各3次:一次显示小数点后面6位数字;一次显示小数点后面12位数字;一次显示小数点后面16位数字。程序中要包含float.h头文件,并显示FLT_DIG和DBL_DIG的值。1.0/3.0的值与这些值一致吗? 122 | ```c 123 | #include 124 | #include 125 | 126 | int main(void) 127 | { 128 | double num_01 = 1.0/3.0; 129 | float num_02 = 1.0/3.0; 130 | printf("No.1 num_01 = %.6f ; num_02 = %.6f \n",num_01,num_02); 131 | printf("No.2 num_01 = %.12f ; num_02 = %.12f \n",num_01,num_02); 132 | printf("No.3 num_01 = %.16f ; num_02 = %.16f \n",num_01,num_02); 133 | printf("FLT_DIG = %d ; DBL_DIG = %d \n",FLT_DIG,DBL_DIG); 134 | return 0; 135 | } 136 | ``` 137 | 138 | 139 | 8. 编写一个程序,提示用户输入旅行的里程和消耗的汽油量。然后计算并显示消耗每加仑汽油行驶的英里数,显示小数点后面一位数字。接下来,使用1加仑大约3.785升,1英里大约为1.609千米,把单位是英里/加仑的值转换为升/100公里(欧洲通用的燃料消耗表示法),并显示结果,显示小数点后面 1 位数字。注意,美国采用的方案测量消耗单位燃料的行程(值越大越好),而欧洲则采用单位距离消耗的燃料测量方案(值越低越好)。使用#define 创建符号常量或使用 const 限定符创建变量来表示两个转换系数。 140 | ```c 141 | #include 142 | 143 | const float L_Per_gallon = 3.785; 144 | const float km_Per_Mile = 1.609; 145 | 146 | int main(void) 147 | { 148 | float jouney_len,oil_vol; 149 | printf("please input your jouney length : "); 150 | scanf("%f",&jouney_len); 151 | printf("please input your oil : "); 152 | scanf("%f",&oil_vol); 153 | printf("oil_vol / jouney_len = %.f / %.f = %.1f \n",oil_vol,jouney_len,oil_vol/jouney_len); 154 | printf("transfer : oil_vol / jouney_len = %.f / %.f = %.1f \n",oil_vol*L_Per_gallon,jouney_len*km_Per_Mile, 155 | oil_vol*L_Per_gallon/(jouney_len*km_Per_Mile)); 156 | 157 | return 0; 158 | } 159 | ``` 160 | 161 | -------------------------------------------------------------------------------- /Chapter05/README.md: -------------------------------------------------------------------------------- 1 | ## 第五章 运算符、表达式与语句 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | 5 | ## 1. 基本运算符 6 | ### 1.1 `赋值`运算符 7 | 注意点:C语言中,`=` 不意味着 “ 相等 ”,而是一种`赋值`运算符。 8 | ```c 9 | int score = 98; // = 号的左边是变量名,右侧是赋给变量的值。 10 | ``` 11 | 符号 `=` 被称为`赋值`运算符。 12 | 13 | 注意读法:不是score等于98,而是“把值98赋给变量score”。 14 | 15 | 赋值运算符`左侧`必须`引用`一个`存储位置`,最简单的方式就是`使用“变量名”`。 16 | 17 | C使用可修改的左值(modifiable lvalue)标记可赋值的实体。 18 | 19 | - 几个术语:数据对象、左值、右值和运算符 20 | 21 | 赋值表达式语句的目的:把值储存到内存位置上。 22 | 23 | **数据对象(Data Object)**:用于储存值的数据存储区域 24 | 25 | 使用变量名是标识对象的一种方法。 26 | 27 | **左值(lvalue)**:C语言的术语,用于标识特定数据`对象的名称`或者`表达式`。 28 | 29 | **对象** 指的是实际的数据存储,而**左值**是用于标识或定位存储位置的标签。 30 | 31 | C标准新增了:`可修改的左值(Modifiable lvalue)`,用于标识可修改的对象。 32 | 33 | **右值**:可以是常量、变量或其他可求值的表达式(如:函数调用)。 34 | 35 | **运算对象**:运算符操作的对象。 36 | 37 | ### 1.2 `加减法`运算符 38 | - 计算加法 39 | 40 | `加法运算符(addition operator)`:用于`加法`运算,使其两侧的值相加。 41 | 42 | ```c 43 | int num01,num02; 44 | int sum = num01 + num02; 45 | ``` 46 | 计算减法 47 | 48 | `减法运算符(Subtraction operator)`:用于`减法`运算,使其左侧的数减去右侧的数。 49 | 50 | `+ 和 - `运算符都被称为`“二元运算符(Binary Operator)”`:需要`两个运算对象`才可以完成操作。 51 | 52 | ```c 53 | int num01,num02; 54 | int minus = num01 - num02; 55 | ``` 56 | ### 1.3 `符号`运算符`+、-` 57 | `一元运算符(unary operator)`:只需要一个运算对象。 58 | 59 | ```c 60 | int num01 = -6; 61 | int num02 = +4; 62 | ``` 63 | 64 | ### 1.4 `乘除法`运算符 65 | 66 | 乘法运算符 `*`。 67 | 68 | 除法运算符 `/`。/左侧是被除数,右侧的值是除数。 69 | 70 | ⚠️注意点 71 | - 整数除法和浮点数除法不相同,因为浮点数除法的结果是浮点数,而整数除法的结果是整数。整数是没有小数部分的数。 72 | 73 | - 整数的除法结果的小数部分会被丢弃,这种过程叫做`截断(truncation)`。不会四舍五入结果。 74 | 75 | - 混合整数和浮点数计算的结果都是浮点数。C99使用的是趋零截断。 76 | 77 | ## 2. 其他运算符 78 | ### 2.1 `sizeof`运算符和`size_t`类型 79 | - `sizeof()`运算符 80 | `sizeof`运算符以`字节`为单位返回运算`对象的大小`。 81 | 82 | 在C语言中,1字节定义为char类型占用的空间大小. 83 | 84 | - size_t 类型 85 | C语言规定,sizeof返回size_t类型的值。是无符号整数类型,但它不是新类型。 86 | 87 | size_t 是语言定义的标准类型。 88 | 89 | ### 2.2 `求模`运算符(modulus operator) 90 | 用于`整数运算`。 91 | 92 | 求模运算符给出左侧整数除以右侧整数的`余数(Remainder)`。 93 | 94 | ⚠️注意:`求模`运算符只能用于`整数`,~~不能用于浮点数~~。 95 | 96 | ### 2.3 `递增/递减`运算符 97 | `递增/递减运算符(Increment Operator)`执行简单的任务,将其运算对象`递增/递减1`。 98 | 99 | 有两种形式: 100 | - 后缀模式(i++/i--) 101 | > `使用i的值之后`,递增/递减1 102 | - 前缀模式(++i/--i) 103 | > 前缀:`使用i之前`的值之前,递增/递减1 104 | 105 | ## 3. 表达式与语句 106 | C语言的基本步骤由语句组成,大多数的语句由表达式构成。 107 | 108 | ### 3.1 表达式(Expression) 109 | 表达式由`运算符`和`运算对象`组成。 110 | 111 | ### 3.2 语句(statement) 112 | 语句是C程序的基本模块。一条语句相当于一条完整的计算机指令。在C中,大部分语句以`分号`结尾。 113 | 114 | - 简单的语句是空语句(一个分号结尾)。 115 | ```c 116 | ; // 空语句 117 | ``` 118 | - 赋值表达式语句:shoes = 40; 119 | - 函数表达式语句:printf("sam = %d\n",sam); 120 | ### 3.3 复合语句(块) 121 | 122 | `复合语句(compound statement)`使用花括号括起来的一条或者是多条语句,复合语句也称为`块(block)`。 123 | 124 | 例如:while语句块 125 | ```c 126 | index = 0; 127 | while(index++<10) 128 | { 129 | sam = 10 * index + 2; 130 | printf("sam = %d \n",sam); 131 | } 132 | ``` 133 | ## 4. 类型转换 134 | ### 4.1 基本的类型转换规则 135 | - 当类型转换出现表达式时,无论是unsigned还是signed的char和short都会自动转换成int. 136 | 137 | - 涉及到两种类型的运算,两个值会被分别转换成两种类型的更高级别。 138 | 139 | - 类型的级别从高到低依次是:long double、double、float、unsigned long、long long、unsigned long、long unsigned int、int。 140 | >如果当long和int的大小相同是,unsigned int 会比 long 的级别高。 141 | 142 | - 赋值表达式语句中,计算的最终结果会被转换成被赋值变量的类型。会发生升级或者是降级。 143 | 144 | > `升级`:从较小类型转换成较大类型。
145 | > `降级`:把一种类型转换成更低级别的类型。当浮点类型被降级为整数类型时,原来的浮点值会被截断。 146 | 147 | - 当作为函数参数传递时,char和short被转换成int,float被转换成double。 148 | 149 | ### 4.2 对于待赋值的值与目标类型不匹配的情况时,规则如下: 150 | 151 | - 目标类型是`无符号整型`,且待赋的值是`整数`,额外的位将被忽略。 152 | 153 | - 如果目标类型是一个`有符号整型`,且待赋的值是`整数`,结果因实现而异。 154 | 155 | - 如果目标类型是一个`整型`,且待赋的值是`浮点数`,这种行为是未定义的。 156 | 157 | ## 5. 带参数的函数 158 | `函数原型`:就是函数的声明,描述了函数的`返回值`和`参数`。 159 | 160 | 如果函数`不接受任何参数`,函数头的`圆括号`中应该写上`关键字void`。 161 | 162 | 声明函数就创建了被称为`形式参数(formal argument或者formal parameter,简称形参)`的变量。 163 | 164 | `被函数调用传递的值`称为`实际参数(actual argument或者是actual parameter,简称实参)`。 165 | 166 | `形参`是`变量`,`实参`就是`函数调用提供的值`,实参被赋给相应的形参。 167 | 168 | 变量名是函数私有的,即在函数中定义的函数名不会和别处的相同名称发生冲突。 169 | -------------------------------------------------------------------------------- /Chapter05/img/第9题.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter05/img/第9题.png -------------------------------------------------------------------------------- /Chapter05/复习题.md: -------------------------------------------------------------------------------- 1 | ## 第五章 运算符、表达式与语句 复习题 2 | 3 | 1. 假设所有变量的类型都是int,下列各项变量的值是多少? 4 | ```c 5 | a. x = ( 2 + 3 ) * 6; // 30 6 | b. x = (12 + 6) / 2 * 3; // 27 7 | c. y = x = (2 + 3) / 4 ;// x= y=1 8 | d. y = 3 + 2 * (x = 7/2); // x =3 ;y = 9 9 | ``` 10 | 11 | 2. 假设所有变量的类型是int,下列各项变量的值是多少? 12 | ```c 13 | a. x = (int)3.8 + 3.3; // 6 14 | b. x = (2 + 3) * 10.5; // 50 15 | c. x = 3/5 * 22.0; // 0 16 | d. x = 22.0 * 3 / 5; // 13 17 | ``` 18 | 19 | 3. 对下列各表达式求值: 20 | ```c 21 | a. 30.0 / 4.0 * 5.0; // 37.5 22 | b. 30.0 / (4.0 * 5.0); // 1.5 23 | c. 30 / 4 * 5; // 35 24 | d. 30 * 5 / 4; // 37 25 | e. 30 / 4.0 * 5; // 37.5 26 | f. 30 / 4 * 5.0; // 35.0 27 | ``` 28 | 29 | 4. 请找出下面的程序中的错误 30 | ```c 31 | int main(void) 32 | { 33 | int i = 1, 34 | float n; 35 | printf("Watch out! Here come a banch of fractions!\n"); 36 | while (i < 30) 37 | n = 1/i; 38 | printf(" %f",n); 39 | printf("That's all , folks !\n"); 40 | return; 41 | } 42 | ``` 43 | 修改后的程序为: 44 | ```c 45 | #include 46 | int main(void) 47 | { 48 | int i = 1; 49 | flaot n; 50 | printf("Watch out! Here come a banch of fractions!\n"); 51 | while (i++ < 30) // i = 1 构成死循环 52 | { 53 | n = 1.0/i; 54 | printf(" %f \n",n); 55 | } 56 | printf("That's all , folks !\n"); 57 | return 0; 58 | } 59 | ``` 60 | 61 | 5. 这是程序清单 5.9 的另一个版本,从表面上看,该程序只使用了一条scanf()语句,比程序清单5.9 简单,请找出不如原版之处。 62 | ```c 63 | #include  64 | #define S_TO_M 60 65 | int main(void) 66 | { 67 | int sec, min, left; 68 | 69 | printf("This program converts seconds to minutes and "); 70 | printf("seconds.\n"); 71 | printf("Just enter the number of seconds.\n"); 72 | printf("Enter 0 to end the program.\n"); 73 | while (sec > 0) { 74 | scanf("%d", &sec); 75 | min = sec/S_TO_M; 76 | left = sec % S_TO_M; 77 | printf("%d sec is %d min, %d sec. \n", sec, min, left); 78 | printf("Next input?\n"); 79 | } 80 | printf("Bye!\n"); 81 | return 0; 82 | } 83 | ``` 84 | 85 | 5.9 程序清单 86 | ```c 87 | #include 88 | #define SEC_PER_MIN 60 89 | int main(void) 90 | { 91 | int sec,min,left; 92 | printf("Convert seconds to minutes and seconds!\n"); 93 | printf("Enter the number of seconds (<=0 to quit):\n"); 94 | scanf("%d",&sec); 95 | while (sec > 0) 96 | { 97 | min = sec / SEC_PER_MIN; 98 | left = sec % SEC_PER_MIN; 99 | printf("%d seconds is %d minutes, %d seconds.\n",sec,min,left); 100 | printf("Enter next value (<=0 to quit):\n"); 101 | scanf("%d"&sec); 102 | } 103 | printf("Done !\n"); 104 | 105 | return 0; 106 | } 107 | ``` 108 | 109 | 答案:。。。。。。 110 | 111 | 6. 下面的程序将打印出什么内容? 112 | ```c 113 | #include 114 | #define FORMAT "%s! C is coll!\n" 115 | int main(void) 116 | { 117 | int num =10 ; 118 | printf(FORMAT,FORMAT); // %s! C is coll! 换行 ! C is coll! 119 | printf("%d\n",++num); // 11 120 | printf("%d\n",num++); // 11 121 | printf("%d\n",num--); // 12 上一步加完,然后再-- 122 | printf("%d\n",num); // 11 123 | return 0; 124 | } 125 | ``` 126 | 127 | 7. 下面的程序将打印出什么内容? 128 | ```c 129 | #include 130 | int main(void) 131 | { 132 | char c1,c2; 133 | int diff; 134 | float num; 135 | c1 = 's'; 136 | c2 = 'O'; 137 | diff = c - c2; //ASCII码值 83 - 79 138 | num = diff; 139 | printf("%c%c%c:%d %3.2f\n",c1,c2,c1,diff,num); // SOS:4 4.00 140 | return 0; 141 | } 142 | ``` 143 | 144 | 8. 下面的程序将打印出什么内容? 145 | ```c 146 | #include 147 | #define TEN 10 148 | int main(void) 149 | { 150 | int n = 0; 151 | while (n++ < TEN) 152 | printf("%5d",n); 153 | printf("\n"); 154 | return 0; 155 | } 156 | ``` 157 | 打印结果如下: 158 | ```c 159 | 1 2 3 4 5 6 7 8 9 10 160 | ``` 161 | 162 | 9. 修改上一个程序,使其可以打印字母a~z。 163 | ```c 164 | #include 165 | int main(void) 166 | { 167 | char ch_lower = 'a'; 168 | char ch_upper = 'A'; 169 | while (ch_lower <= 'z'&& ch_upper <= 'Z') 170 | printf("%c%c ",ch_upper++,ch_lower++); 171 | printf("\n"); 172 | return 0; 173 | } 174 | ``` 175 | 10. 下面的程序会打印出什么? 176 | ```c 177 | a. 178 | int x = 0; 179 | while (++x < 3) 180 | printf("%4d",x); // 1 2 181 | 182 | b. 183 | int x = 100; 184 | 185 | while (x++ < 103) 186 | printf("%4d\n",x); //注意换行 101 102 103 104 187 | printf("%4d\n",x); //注意换行 101 102 103 104 188 | 189 | c. 190 | char ch = 's'; 191 | while (ch < 'w') 192 | { 193 | printf("%c",ch); //打印单个字符 194 | ch++; 195 | } 196 | printf("%c\n",ch); // 注意换行 stuvw 197 | ``` 198 | 199 | 11.下面的程序会打印什么? 200 | ```c 201 | #define MESG "COMPUTER BYTES DOG" 202 | #include 203 | int main(void) 204 | { 205 | int n = 0; 206 | while (n < 5) 207 | printf("%s\n",MESG); 208 | n++; 209 | // 程序有问题,会一直打印 COMPUTER BYTES DOG ,直到关闭程序为止,类似造成死循环。 210 | printf("That's all.\n"); 211 | return 0; 212 | } 213 | ``` 214 | 215 | 12. 分别编写一条语句,完成下列任务(或者说,使其具有以下副作用) 216 | ```cpp 217 | a. 将变量x的值增加10 // x = x + 10; 或 x+=10; 218 | b. 将变量x的值增加1 // x++,++x 或 x+=1 219 | c. 将a与b之和的两倍赋给c // c = 2*(a+b) 220 | d. 将a与b的两倍之和赋给c // c = a+2*b 221 | ``` 222 | 223 | 13. 分别编写一条语句,完成下列任务 224 | ```c 225 | a. 将变量 x 的值减少1 // x-- 或 --x 或 x -=1 226 | b. 将 n 除以 k 的余数赋给 m // m = n%k 227 | c. q 除以 b 减去 a,并将结果赋给 p // p = q/b - a 228 | d. a 与 b 之和除以 c 与 d 的乘积,并将结果赋给 x // x = (a + b)/(c*d) 229 | ``` -------------------------------------------------------------------------------- /Chapter05/编程题.md: -------------------------------------------------------------------------------- 1 | ## 第五章 运算符、表达式与语句 编程练习题 2 | 3 | 1. 编写一个程序,把用分钟表示的时间转换成用小时和分钟表示的时间。使用#define或const创建一个表示60的符号常量或const变量。通过while循环让用户重复输入值,直到用户输入小于或等于0的值才停止循环。 4 | ```c 5 | #include 6 | #define HOUR_PER_MIN 60 // 一小时60分钟 7 | 8 | int main(void) 9 | { 10 | int min,hour,left; 11 | 12 | printf("把分钟转换成 小时 - 分钟数形式\n"); 13 | printf("请输入你的分钟数(如果小于或等于0的值停止循环):"); 14 | scanf("%d",&min); 15 | 16 | while(min>0) 17 | { 18 | hour = min / HOUR_PER_MIN; // 截断小时数 19 | left = min % HOUR_PER_MIN; // 剩下的分钟数 20 | 21 | printf("%d 分钟 = %d 小时 - %d 分钟\n",min,hour,left); 22 | 23 | printf("请输入下一个分钟数(如果小于或等于0的值停止循环):"); 24 | scanf("%d",&min); 25 | } 26 | printf("结束了!\n"); 27 | 28 | return 0; 29 | } 30 | ``` 31 | 32 | 33 | 2. 编写一个程序,提示用户输入一个整数,然后打印从该数到比该数大10的所有整数(例如,用户输入5,则打印5~15的所有整数,包括5和15)。要求打印的各值之间用一个空格、制表符或换行符分开。 34 | ```c 35 | #include 36 | 37 | int main(void) 38 | { 39 | int num01; 40 | 41 | printf("请输入一个整数:"); 42 | scanf("%d",&num01); 43 | 44 | int num02 = num01 + 10; 45 | while(num01<=num02) 46 | { 47 | printf(" %d",num01); 48 | num01++; // 递增运算符的应用 49 | } 50 | printf("\n"); 51 | return 0; 52 | } 53 | ``` 54 | 3. 编写一个程序,提示用户输入天数,然后将其转换成周数和天数。例如,用户输入18,则转换成2周4天。以下面格式显示结果:
55 | 18 days are 2 weeks, 4 days.
56 | 通过while循环让用户重复输入天数,当用户输入一个非正值时(如0或-20),循环结束。 57 | ```c 58 | #include 59 | #define WEEK_PER_DAY 7 60 | 61 | int main(void) 62 | { 63 | int days,weeks,left; 64 | 65 | printf("请输入天数(天数为非正值时停止):"); 66 | scanf("%d",&days); 67 | 68 | while(days>0) 69 | { 70 | weeks = days / WEEK_PER_DAY; // 截断 71 | left = days % WEEK_PER_DAY; // 剩余的天数 72 | printf("%d days are %d weeks %d days\n",days,weeks,left); 73 | 74 | printf("请输入下一个天数(天数为非正值时停止):"); 75 | scanf("%d",&days); 76 | } 77 | printf("结束了!\n"); 78 | 79 | return 0; 80 | 81 | } 82 | ``` 83 | 84 | 4. 编写一个程序,提示用户输入一个身高(单位:厘米),并分别以厘米和英寸为单位显示该值,允许有小数部分。程序应该能让用户重复输入身高,直到用户输入一个非正值。其输出示例如下: 85 | ```markdown 86 | Enter a height in centimeters: 182 87 | 182.0 cm = 5 feet, 11.7 inches 88 | Enter a height in centimeters (<=0 to quit): 168.7 89 | 168.0 cm = 5 feet, 6.4 inches 90 | Enter a height in centimeters (<=0 to quit): 0 91 | bye 92 | ``` 93 | 94 | ```c 95 | #include 96 | #define INCH_P_CM 0.3937008 //一厘米是0.3937008英寸 97 | #define FEET_P_CM 0.0328084 //一厘米是0.032808英尺 98 | #define INCH_P_FEET 12 //一英尺(feet)是12英寸(inch) 99 | 100 | int main(void) 101 | { 102 | float height; 103 | int feet; 104 | float inch; 105 | 106 | printf("请输入身高(单位:厘米)(输入一个非正值就停止):"); 107 | scanf("%f", &height); 108 | 109 | while (height>0) 110 | { 111 | feet = height * INCH_P_FEET; 112 | inch = height * INCH_P_CM - feet * INCH_P_FEET; 113 | printf("%.1f CM = %d feet %.1f inches \n",height,feet,inch); 114 | 115 | printf("请输入下一个身高(单位:厘米)(输入一个非正值就停止):"); 116 | scanf("%f", &height); 117 | } 118 | 119 | printf("结束了!bye-bye!"); 120 | 121 | return 0; 122 | } 123 | ``` 124 | 125 | 5. 修改程序addemup.c(程序清单5.13),你可以认为addemup.c是计算20天里赚多少钱的程序(假设第1天赚$1、第2天赚$2、第3天赚$3,以此类推)。修改程序,使其可以与用户交互,根据用户输入的数进行计算(即,用读入的一个变量来代替20)。 126 | ```c 127 | #include 128 | 129 | int main(void) 130 | { 131 | int count=0; 132 | int sum = 0; 133 | int end_days; 134 | 135 | printf("请输入天数:"); 136 | scanf("%d",&end_days); 137 | 138 | while(count++ 150 | 151 | int main(void) 152 | { 153 | int count=0; 154 | int sum = 0; 155 | int end_days; 156 | 157 | printf("请输入天数:"); 158 | scanf("%d",&end_days); 159 | 160 | while(count++ 171 | double Cal_cube(double n); // 声明函数原型 172 | 173 | int main(void) 174 | { 175 | double n,cube; 176 | 177 | printf("请输入一个数字:"); 178 | scanf("%lf", &n); 179 | 180 | cube = Cal_cube(n); 181 | printf("%.2f 对应的立方值是:%.2f \n", n,cube); 182 | 183 | return 0; 184 | } 185 | 186 | double Cal_cube(double n) // 计算立方值的函数 187 | { 188 | return n*n*n; 189 | } 190 | ``` 191 | 192 | 8. 编写一个程序,显示求模运算的结果。把用户输入的第1个整数作为求模运算符的第2个运算对象,该数在运算过程中保持不变。用户后面输入的数是第1个运算对象。当用户输入一个非正值时,程序结束。其输出示例如下: 193 | ```markdown 194 | This program computes moduli. 195 | Enter an integer to serve as the second operand: 256 196 | Now enter the first operand: 438 197 | 438 % 256 is 182 198 | Enter next number for first operand (<= 0 to quit): 1234567 199 | 1234567 % 256 is 135 200 | Enter next number for first operand (<= 0 to quit): 0 201 | Done 202 | ``` 203 | ```c 204 | #include 205 | 206 | int main(void) 207 | { 208 | int first_object; 209 | int sec_object; 210 | int result ; 211 | 212 | printf("This program computes moduli. \n"); 213 | 214 | printf("Enter an integer to serve as the second operand : "); 215 | scanf("%d", &sec_object); 216 | 217 | printf("Now enter the first operand : "); 218 | scanf("%d", &first_object); 219 | while (first_object > 0) 220 | { 221 | result =first_object % sec_object; 222 | printf("%d %% %d is %d \n", first_object, sec_object, result); 223 | printf("Enter next number for first operand (<= 0 to quit) : "); 224 | scanf("%d", &first_object); 225 | } 226 | 227 | printf("结束了!\n"); 228 | return 0; 229 | } 230 | ``` 231 | 9. 题目太长,直接用图片了 232 | 233 | ![](./img/第9题.png) 234 | 235 | ```c 236 | #include 237 | 238 | void Temperatures(double fahrenheit); 239 | 240 | double fahrenheit; // 华氏温度 241 | double celsius; // 摄氏温度 242 | double kelvin; // 开氏温度(卡尔文温标) 243 | 244 | int main(void) 245 | { 246 | 247 | 248 | printf("请输入一个华氏温度(输入q就停止):"); 249 | 250 | 251 | while(scanf("%lf",&fahrenheit)) 252 | { 253 | Temperatures(fahrenheit); 254 | printf("Fahrenheit : %.2f Celsius : %.2f Kelvin : %.2f \n",fahrenheit,celsius,kelvin); 255 | printf("请输入一个华氏温度(输入q就停止):"); 256 | scanf("%lf",&fahrenheit); 257 | } 258 | printf("结束了!Bye-bye!\n"); 259 | 260 | return 0; 261 | } 262 | 263 | void Temperatures(double fahrenheit) 264 | { 265 | // 用来存储常量 266 | const double left = 32.0; 267 | const double add = 237.16; 268 | 269 | celsius = 5.0 / 9.0 * (fahrenheit - left); 270 | kelvin = fahrenheit + add; 271 | } 272 | ``` -------------------------------------------------------------------------------- /Chapter06/README.md: -------------------------------------------------------------------------------- 1 | ## 第六章 循环控制语句 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | 5 | 伪代码:一种用简单的句子表示程序思路的办法,与计算机语言的形式相对应。 6 | > - 伪代码有助于设计程序的逻辑。 7 | > - 注意力集中在程序的组织和逻辑上,不用在设计程序时还要分心如何用编程语言表达自己的想法。 8 | 9 | ## 1. while循环语句 10 | 11 | 伪代码语法格式: 12 | ``` 13 | 获得第1个用于测试的值 14 | 当测试为真时 15 | 处理值 16 | 获取下一个值 17 | ``` 18 | 19 | C风格语法格式 20 | ```c 21 | while (expression) 22 | statement 23 | ``` 24 | > - expression 可以是任何表达式。也是循环的入口,所以入口条件满足才可以进入循环。 25 | > - statement 部分可以是`分号结尾`的`简单语句`,也可以是 `花括号` 的 `复合语句`。 26 | 27 | 如果 while循环 中的 expression为 false 时,即可终止循环。 28 | 29 | ## 2. 关系运算符和关系表达式 30 | 关系表达式:用于测试条件状态。 31 | 32 | |运算符|含义| 33 | |:--:|:--:| 34 | |<|小于| 35 | |<=|小于或等于| 36 | |==|等于| 37 | |>=|大于或等于| 38 | |>|大于| 39 | |!=|不等于| 40 | 41 | 关系运算符常用于构造while语句和其他C语句中用到的关系表达式。 42 | 43 | ⚠️注意:注意点:比较`浮点数`时,尽量使用`<和>`,由于浮点数的`舍入误差`会导致在逻辑上应该相等的两个数却不相等。 44 | 45 | C语言中,表达式为真的值是1,为假的值是0。 46 | 47 | 48 | ### 2.1 `_Bool类型` 49 | C99新增 `_Bool类型`,由英国数学家George Boole的名字命名的,他开发了用代数表示逻辑和解决逻辑的问题。 50 | 51 | 在编程中,表示`真`或`假`的变量叫做`布尔变量(Boolean variable)`,所以_Bool是C语言中的布尔变量的类型名。 52 | 53 | `_Bool类型`的变量只能储存1(真)或者0(假)。 54 | 55 | ## 3. for循环 56 | 关键字:for 57 | 58 | 一般形式: 59 | 60 | ```c 61 | for(initialize;test_expression;update) // 在test为假或者是0之前,重复执行statement操作部分。 62 | statement; 63 | ``` 64 | > for圆括号中的表达式也叫做控制表达式,都是完整表达式。 65 | 66 | for语句使用 `3个表达式` 控制循环过程,分别用`分号隔开`。 67 | 68 | initialize 表达式在执行for语句之前只执行一次,然后对test表达式求值,如果表达式为真(或非零)执行循环一次。然后对update表达式进行求值,并再次对test表达式进行检查。 69 | 70 | for语句是一个`入口条件`循环,即在执行循环之前决定表达式是否执行。 71 | 72 | for循环可能一次都不执行,statement部分可以是一个`简单语句`或者是`复合语句`。 73 | 74 | for循环嵌套的规则:外层循环控制行,内层循环控制列 75 | 76 | ## 4. `do...while` 循环 77 | do while循环在执行玩循环体后才执行测试条件,至少执行循环体一次。 78 | 79 | ### 4.1 一般表达形式 80 | ```c 81 | do 82 | { 83 | statement; 84 | }while(expression); 85 | 在test为假或者为0之前,重复执行statement部分 86 | ``` 87 | `do while`是一个`出口条件`循环,即在执行完循环体后根据测试统计决定是否执行循环体的内容。 88 | 89 | 例子: 90 | ```c 91 | do 92 | { 93 | 提示用户输入密码 94 | 读取用户输入的密码 95 | }while(用户输入的密码不等于密码); 96 | ``` 97 | 98 | ### 4.2 循环的使用选择问题 99 | - 一般原则是在执行循环之前测试条件比较好 100 | - 测试放在循环的开头,程序的可读性更高。 101 | 102 | 当循环涉及到初始化和更新变量时,用`for 循环`比较合适,而在其他的情况下使用while循环更好。 103 | 104 | -------------------------------------------------------------------------------- /Chapter06/复习题.md: -------------------------------------------------------------------------------- 1 | ## 第六章 循环控制语句 复习题 2 | 3 | 1. 写出执行完下列各行后quack的值是多少?后5行中使用的是第1行quack的值。 4 | ```c 5 | int quack = 2; 6 | quack += 5; // 7 7 | quack *= 10; // 20 8 | quack -= 6; // -4 9 | quack /= 8; // 0 10 | quack %= 3; // 3 11 | ``` 12 | 13 | 2. 假设 value 是int类型,下面循环的输出是什么? 14 | ```c 15 | for (value 36; value > 0; value /=2) 16 | printf("%3d",value); 17 | ``` 18 | 如果value是double类型,会出现什么问题? 19 | ```markdown 20 | 36 18 9 4 2 1 21 | %3d的说明符不能适用double类型,所以不正确 22 | ``` 23 | 24 | 25 | 3. 用代码表示以下测试条件 26 | ```c 27 | a. x大于5 // x>5 28 | b. scanf() 读取一个名为x的double类型值且失败 // scanf("%lf",&x)!=1 29 | c. x的值等于5 // x==5 30 | ``` 31 | 32 | 4. 用代码表示以下测试条件 33 | ```c 34 | a. scanf() 成功读入一个整数 // scanf("%d",&x)==1 35 | b. x不等于5 // x!=5 36 | c. x大于或等于20 // x>=20 37 | ``` 38 | 39 | 5. 下面的程序有点问题,请找出问题所在。 40 | ```c 41 | #include 42 | int main(void) 43 | { 44 | int i,j,list(10); 45 | for (i=1,i<=10,i++) 46 | { 47 | list[i] = 2 * i + 3; 48 | for (j = 1,j>=i,j++) 49 | printf(" %d",list[j]); 50 | printf("\n"); 51 | } 52 | ``` 53 | 修改后的程序为: 54 | ```c 55 | #include 56 | int main(void) 57 | { 58 | int i,j,list[10]; 59 | for(i = 0;i<=9;i++) 60 | { 61 | list[i] = 2 * i + 3; 62 | for(j = 1;j<=i;j++) 63 | printf(" %d",list[j]); 64 | printf("\n"); 65 | } 66 | return 0; 67 | } 68 | ``` 69 | 70 | 6. 编写一个程序打印下面的图案,要求使用嵌套循环: 71 | ```c 72 | $$$$$$$$ 73 | $$$$$$$$ 74 | $$$$$$$$ 75 | $$$$$$$$ 76 | ``` 77 | 代码如下: 78 | ```c 79 | #include 80 | int main(void) 81 | { 82 | int i,j; 83 | for(i = 0;i<=4;i++){ 84 | for(j=0;j<=8;j++) 85 | printf("$"); 86 | printf("\n"); 87 | } 88 | return 0; 89 | } 90 | ``` 91 | 92 | 7. 下面的程序各打印什么内容? 93 | ```c 94 | a. 95 | #include 96 | int main(void) 97 | { 98 | int i = 0; 99 | while (++i < 4) 100 | printf("Hi! "); 101 | do 102 | printf("Bye! "); 103 | while (i++ < 8) 104 | return 0; 105 | } 106 | // 打印结果为:3次(1,2,3) Hi! Hi! Hi! 107 | // 打印结果为:5次(4,5,6,7,8) Bye! Bye! Bye! Bye! Bye! do while是先打印,再判断循环, 108 | b. 109 | #include 110 | int main(void) 111 | { 112 | int i; 113 | char ch; 114 | for (i = 0,ch='A',i<4;i++,ch +=2 * i) 115 | printf("%c",ch); 116 | return 0; 117 | } 118 | // 打印结果为:ACGM 119 | ``` 120 | 121 | 8. 假设用户输入的是 Go west,young man!,下面各程序的输出是什么?(在ASCII码中,! 紧跟在空格字符后面) 122 | ```c 123 | a. 124 | #include 125 | int main(void) 126 | { 127 | char ch; 128 | scanf("%c",&c); 129 | while (ch!='g') 130 | { 131 | printf("%c",ch) 132 | scanf("%c",&ch); 133 | } 134 | return 0; 135 | } 136 | // 打印输出为:Go West,youn 137 | b. 138 | #include 139 | int main(void) 140 | { 141 | char ch; 142 | scanf("%c",&ch); 143 | while (ch!='g') 144 | { 145 | pritnf("%c",++ch); 146 | scanf("%c",&ch); 147 | } 148 | return 0; 149 | } 150 | // 打印输出为:Hp!xftu-!zpvo 151 | 152 | c. 153 | #include 154 | int main(void) 155 | { 156 | char ch; 157 | do{ 158 | pritnf("%c",++ch); 159 | scanf("%c",&ch); 160 | }while (ch!='g') 161 | return 0; 162 | } 163 | // 打印结果为:Go west,young 164 | d. 165 | #include 166 | int main(void) 167 | { 168 | char ch; 169 | scanf("%c",&ch); 170 | for(ch = '$';ch!='g';scanf("%c",&ch)) 171 | printf("%c",ch); 172 | return 0; 173 | } 174 | // 打印结果为:$o west,youn 175 | ``` 176 | 177 | 178 | 9. 下面的程序打印什么内容? 179 | ```c 180 | #include  181 | int main(void) 182 | { 183 | int n, m; 184 | n = 30; 185 | while (++n <= 33) 186 | printf("%d|", n); 187 | // 打印结果为:31|32|33|34 188 | n = 30; 189 | do 190 | printf("%d|", n); 191 | while (++n <= 33); 192 | // 打印结果为:30|31|32|33| 193 | 194 | printf("\n***\n"); // 打印 *** 195 | 196 | for (n = 1; n*n < 200; n += 4) 197 | printf("%d\n", n); 198 | // 打印结果为:1 5 9 13 (各自占一行) 199 | 200 | printf("\n***\n"); //打印 *** 201 | 202 | for (n = 2, m = 6; n < m; n *= 2, m += 2) 203 | printf("%d %d\n", n, m); 204 | /*打印结果为: 205 | 2 6 206 | 4 8 207 | 8 10 208 | */ 209 | 210 | printf("\n***\n"); // 打印 *** 211 | 212 | for (n = 5; n > 0; n--) 213 | { 214 | for (m = 0; m <= n; m++) 215 | printf("="); 216 | printf("\n"); 217 | } 218 | return 0; 219 | /*打印结果为: 220 | ====== 221 | ===== 222 | ==== 223 | === 224 | == 225 | */ 226 | } 227 | ``` 228 | 229 | 10. 考虑下面的声明 230 | ```c 231 | double mint[10]; 232 | a. 数组名是什么? // 答案:mint 233 | b. 该数组有多少个元素? // 答案:10 234 | c. 每个元素可以储存什么类型的值? // 答案:double 235 | d. 下面的哪一个scanf() 的用法正确? // 答案:ii 236 | i. scanf("%lf",mint[2]) // 237 | ii. scanf("%lf",&mint[2]) 238 | iii. scanf("%lf",&mint) // 可以改为 scanf("lf",&mint[0])时即为正确 239 | ``` 240 | 241 | 11. Noah先生喜欢以2计数,所以编写了下面的程序,创建了一个储存2、4、6、8等数字的数组。整个程序是否有错误之处?如果有,请指出。 242 | ```c 243 | #include 244 | #define SIZE 8 245 | int main(void) 246 | { 247 | int by_twos[SIZE]; 248 | int index; 249 | for (index = 1;index <= SIZE;index++) 250 | by_twos[index] = 2 * index; 251 | for(index = 1;index <=SIZE;index++) 252 | prinf("%d",by_twos); 253 | printf("\n"); 254 | return 0; 255 | } 256 | ``` 257 | 完全修改并调试后的程序为: 258 | ```c 259 | #include 260 | #define SIZE 8 261 | int main(void) 262 | { 263 | int by_twos[SIZE]; 264 | int index; 265 | for (index = 0;index < SIZE;index++) 266 | { by_twos[index] = 2 * (index+1); 267 | //for(index = 0;index < SIZE;index++) 268 | printf("%d ",by_twos[index]); 269 | } 270 | printf("\n"); 271 | return 0; 272 | } 273 | ``` 274 | 275 | 12. 假设要编写一个返回long类型值的函数,函数定义中应包含什么? 276 | ```markdown 277 | 返回值的类型设置为 long类型,return语句返回一个long类型值 278 | ``` 279 | 280 | 281 | 13. 定义一个函数,接受一个int类型的参数,并以long类型返回参数的平方值。 282 | ```c 283 | long square(int n) 284 | { 285 | return n * n; 286 | } 287 | ``` 288 | 289 | 14. 下面的程序打印什么内容? 290 | ```c 291 | #include 292 | int main(void) 293 | { 294 | int k; 295 | for(k = 1,printf("%d: Hi! \n",k);printf("k = %d \n",k),k*k <26; 296 | k+=2,printf("Now k is %d \n",k)) 297 | printf("k is %d in the loop\n",k); 298 | return 0; 299 | 300 | } 301 | ``` 302 | 输出结果为: 303 | ```markdown 304 | 1: Hi! 305 | k = 1 306 | k is 1 in the loop 307 | Now k is 3 308 | k = 3 309 | k is 3 in the loop 310 | Now k is 5 311 | k = 5 312 | k is 5 in the loop 313 | Now k is 7 314 | k = 7 315 | ``` -------------------------------------------------------------------------------- /Chapter06/编程题.md: -------------------------------------------------------------------------------- 1 | ## 第六章 循环控制语句 编程练习题 2 | 3 | 1. 编写一个程序,创建一个包含26个元素的数组,并在其中存储26个小写字母,然后打印数组的所有内容。 4 | ```c 5 | #include 6 | int main(void) 7 | { 8 | char ch[26]; 9 | int i; 10 | for (i = 0;i < 26;i++) 11 | { 12 | printf("%c",'a'+i); // 小写 13 | printf("%c ",'A'+i); // 内容延伸 大写 14 | } 15 | printf("\n"); 16 | return 0; 17 | } 18 | ``` 19 | 20 | 2. 使用嵌套循环,按下面的格式打印字符: 21 | ```markdown 22 | $ 23 | $$ 24 | $$$ 25 | $$$$ 26 | $$$$$ 27 | ``` 28 | code如下: 29 | ```c 30 | #include 31 | int main(void) 32 | { 33 | int i,j; 34 | // 外层循环控制行,内层循环控制列 35 | for (i = 1;i<=5;i++) // 36 | { 37 | for(j = 1;j 56 | int main(void) 57 | { 58 | int i,j; 59 | for(int i =0;i<5;i++) //控制行 60 | { 61 | for (int j=0;j 81 | int main(void) 82 | { 83 | int i,j; 84 | char ch = 'A'; 85 | for(i = 0;i<6;i++) //控制行 86 | { 87 | for(j = 0;j 107 | int main(void) 108 | { 109 | int i,j; 110 | char ch; 111 | printf("please input your character : "); 112 | scanf("%c",&ch); 113 | // 输出的行数 114 | int row = ch - 'A' + 1; 115 | for (i = 0;i0;j--) // 处理降序 122 | printf("%c",'A'+j); 123 | printf("\n"); 124 | } 125 | return 0; 126 | } 127 | ``` 128 | 129 | 130 | 6. 编写一个程序打印一个表格,每一行打印一个整数、该数的平方、该数的立方。要求用户输入表格的上下限。使用一个for循环。 131 | ```c 132 | #include 133 | int main(void) 134 | { 135 | int min,max; 136 | printf("please input your min : "); 137 | scanf("%d",&min); 138 | printf("please input your max : "); 139 | scanf("%d",&max); 140 | for (int i = min;i <=max;i++) 141 | { 142 | printf("num : %d ; square = %d ; Cubic = %d \n",i,i*i,i*i*i); 143 | } 144 | printf("\n"); 145 | return 0; 146 | } 147 | ``` 148 | 149 | 7. 编写一个程序把一个单词读入一个字符数组中,然后倒序打印这个单词。提示:strlen() 函数(第4章中介绍过)可用于计算数组最后一个字符的下标。 150 | ```c 151 | #include 152 | #include 153 | int main(void) 154 | { 155 | char ch[30]; 156 | printf("please input a word : "); 157 | scanf("%s",ch); 158 | int len = strlen(ch); 159 | for (int i = len;i>=0;i--) 160 | { 161 | printf("%c",ch[i]); 162 | } 163 | printf("\n"); 164 | return 0; 165 | } 166 | ``` 167 | 168 | 8. 编写一个程序,要求用户输入两个浮点数,并打印两数字之差除以两数乘积的结果。在用户输入非数字之前,程序应循环处理用户输入的每对值。 169 | ```c 170 | #include 171 | int main(void) 172 | { 173 | float a,b,result; 174 | printf("please input two float numbers : "); 175 | while (scanf("%f %f",&a,&b)==2) 176 | { 177 | result = (a-b)/(a*b); 178 | printf(" result = %f \n",result); 179 | printf("please input two float numbers : "); 180 | } 181 | printf("\n"); 182 | return 0; 183 | } 184 | ``` 185 | 186 | 9. 修改练习8,使用一个函数返回计算的结果。 187 | ```c 188 | #include 189 | float result(float a,float b); 190 | int main(void) 191 | { 192 | float a,b; 193 | printf("please input two float numbers : "); 194 | while (scanf("%f %f",&a,&b)==2) 195 | { 196 | printf(" result = %f \n",result(a,b)); 197 | printf("please input next two float numbers : "); 198 | } 199 | return 0; 200 | } 201 | 202 | float result(float a,float b) 203 | { 204 | float res; 205 | return res=(a-b)/(a*b); 206 | } 207 | ``` 208 | 209 | 10. 编写一个程序,要求用户输入一个上限整数和一个下限整数,计算从上限到下限范围内所有整数的平方和,并显示计算结果。然后程序继续提示用户输入上限和下限整数,并显示结果,直到用户输入的上限整数小于下限整数为止。程序的运行示例如下: 210 | ```markdown 211 | Enter lower and upper integer limits: 5 9 212 | The sums of the squares from 25 to 81 is 255 213 | iEnter next set of limits: 3 25 214 | The sums of the squares from 9 to 625 is 5520 215 | Enter next set of limits: 5 5 216 | Done 217 | ``` 218 | Code如下: 219 | ```c 220 | #include 221 | 222 | int main(void) 223 | { 224 | int min,max; 225 | printf("Enter lower and upper integer limits : "); 226 | while(scanf("%d %d",&min,&max)==2 && min < max) 227 | { 228 | int sum = 0; 229 | for (int i = min;i<=max;i++) 230 | sum += i*i; 231 | printf("The sums of the squares from %d to %d is %d \n",min*min,max*max,sum); 232 | printf("Enter next set of limits : "); 233 | } 234 | printf("Done!\n"); 235 | 236 | return 0; 237 | } 238 | ``` 239 | 240 | 11. 编写一个程序,在数组中读入8个整数,然后按倒序打印这8个整数。 241 | ```c 242 | // 例子类似程序清单 6.19 243 | #include 244 | #define SIZE 8 245 | 246 | int main(void) 247 | { 248 | int arr[SIZE],index; 249 | printf("please input 8 integer : "); 250 | for (index = 0;index=0;index--) 258 | printf("%d ",arr[index]); 259 | 260 | printf("\n"); 261 | return 0; 262 | } 263 | ``` 264 | 265 | 266 | 12. 考虑以下两个无限序列: 267 | ```markdown 268 | 1.0 + 1.0/2.0 + 1.0/3.0 + 1.0/4.0 + … 269 | 1.0 - 1.0/2.0 + 1.0/3.0 - 1.0/4.0 + … 270 | ``` 271 | 编写一个程序计算这两个无限序列的总和,直到到达某次数。提示:奇数个-1 相乘得-1,偶数个-1相乘得1。让用户交互地输入指定的次数,当用户输入0或负值时结束输入。查看运行100项、1000项、10000项后的总和,是否发现每个序列都收敛于某值? 272 | ```c 273 | #include 274 | float formula_01(int times); 275 | float formula_02(int times); 276 | 277 | int main(void) 278 | { 279 | int times; 280 | printf("please input times : "); 281 | while (scanf("%d",×)==1 && times > 0) 282 | { 283 | printf("%f %f\n",formula_01(times),formula_02(times)); 284 | printf("please input next times : "); 285 | } 286 | return 0; 287 | } 288 | 289 | float formula_01(int times) 290 | { 291 | float sum = 0.0; 292 | for (int i = 1;i<=times;i++) 293 | { 294 | sum = sum + 1.0 / i; 295 | } 296 | return sum; 297 | } 298 | 299 | float formula_02(int times) 300 | { 301 | int even = -1; 302 | float sum = 0.0; 303 | for (int j = 1;j<=times;j++) 304 | { 305 | even *=-1; // 奇数个-1相乘得-1 306 | sum += (1.0/j)*even; 307 | } 308 | return sum; 309 | } 310 | ``` 311 | 312 | 13. 编写一个程序,创建一个包含8个元素的int类型数组,分别把数组元素设置为2的前8次幂。使用for循环设置数组元素的值,使用do while循环显示数组元素的值。 313 | ```c 314 | #include 315 | int main(void) 316 | { 317 | int num[8]; 318 | num[0] = 1; 319 | int i = 0; 320 | for (int i = 1;i<=8;i++) 321 | num[i] = 2* num[i-1]; 322 | do 323 | { 324 | printf("%d ",num[i]); 325 | i++; 326 | }while (i<8); 327 | printf("\n"); 328 | return 0; 329 | } 330 | ``` 331 | 332 | 14. 编写一个程序,创建两个包含8个元素的double类型数组,使用循环提示用户为第一个数组输入8 个值。第二个数组元素的值设置为第一个数组对应元素的累积之和。例如,第二个数组的第 4个元素的值是第一个数组前4个元素之和,第二个数组的第5个元素的值是第一个数组前5个元素之和(用嵌套循环可以完成,但是利用第二个数组的第5个元素是第二个数组的第4个元素与第一个数组的第5个元素之和,只用一个循环就能完成任务,不需要使用嵌套循环)。最后,使用循环显示两个数组的内容,第一个数组显示成一行,第二个数组显示在第一个数组的下一行,而且每个元素都与第一个数组各元素相对应。 333 | ```c 334 | #include 335 | int main(void) 336 | { 337 | double first_arr[8],second_arr[8]; 338 | printf("please input first array 8 numbers : "); 339 | for (int i = 0;i < 8;i++) 340 | { 341 | scanf("%lf",&first_arr[i]); 342 | if(i>0) 343 | second_arr[i] = first_arr[i] + second_arr[i-1]; 344 | else 345 | second_arr[i] = first_arr[i]; 346 | } 347 | printf("After adding second array 8 numbers : "); 348 | for (int i = 0;i<8;i++) 349 | printf("%.lf ",second_arr[i]); 350 | printf("\n"); 351 | return 0; 352 | } 353 | ``` 354 | 355 | 356 | 15. 编写一个程序,读取一行输入,然后把输入的内容倒序打印出来。可以把输入储存在char类型的数组中,假设每行字符不超过255。回忆一下,根据%c转换说明,scanf()函数一次只能从输入中读取一个字符,而且在用户按下Enter键时scanf()函数会生成一个换行字符(\n)。 357 | ```c 358 | #include 359 | #include 360 | int main(void) 361 | { 362 | char ch[255]; 363 | int i; 364 | printf("please input content : "); 365 | do 366 | { 367 | i++; 368 | scanf("%c",&ch[i]); 369 | }while(ch[i]!= '\n'); 370 | 371 | for (;i>=0;i--) // 不进行初始化 372 | { 373 | printf("%c",ch[i]); 374 | } 375 | printf("\n"); 376 | return 0; 377 | } 378 | ``` 379 | 380 | 16. Daphne 以10%的单利息投资了100美元(也就是说,每年投资获利相当于原始投资的10%)。Deirdre以 5%的复合利息投资了 100 美元(也就是说,利息是当前余额的 5%,包含之前的利息)。编写一个程序,计算需要多少年Deirdre的投资额才会超过Daphne,并显示那时两人的投资额。 381 | ```c 382 | #include 383 | const float RATE01=0.1; 384 | const float RATE02=0.05; 385 | 386 | int main(void) 387 | { 388 | float Daphne_money = 100; 389 | float Deirdre_money = 100; 390 | int year; 391 | for (year = 0;Deirdre_money<=Daphne_money;year++) 392 | { 393 | Daphne_money += 100 * RATE01; 394 | Deirdre_money *= (1+ RATE02); 395 | } 396 | printf("Deirdre need %d years over Daphne \nDeirdre acccount is : %f \nDaphne account is : %f \n",year,Deirdre_money,Daphne_money); 397 | 398 | return 0; 399 | } 400 | ``` 401 | 402 | 403 | 17. Chuckie Lucky赢得了100万美元(税后),他把奖金存入年利率8%的账户。在每年的最后一天, Chuckie取出10万美元。编写一个程序,计算多少年后Chuckie会取完账户的钱? 404 | ```c 405 | #include 406 | 407 | int main(void) 408 | { 409 | int year =1; 410 | double dollar = 1000000; 411 | while (dollar > 100000) 412 | { 413 | year++; 414 | dollar += dollar * 0.08; 415 | dollar -= 100000; 416 | } 417 | printf("need %d years !\n",year); 418 | return 0; 419 | } 420 | ``` 421 | 422 | 18. Rabnud博士加入了一个社交圈。起初他有5个朋友。他注意到他的朋友数量以下面的方式增长。第1周少了1个朋友,剩下的朋友数量翻倍;第2周少了2个朋友,剩下的朋友数量翻倍。一般而言,第N周少了N个朋友,剩下的朋友数量翻倍。编写一个程序,计算并显示Rabnud博士每周的朋友数量。该程序一直运行,直到超过邓巴数(Dunbar’s number)。邓巴数是粗略估算一个人在社交圈中有稳定关系的成员的最大值,该值大约是150。 423 | ```c 424 | #include 425 | 426 | int main(void) 427 | { 428 | int friends,loss,week; 429 | friends = 5; 430 | for(loss = 1,week = 0;friends <=150;loss++,week++) 431 | { 432 | printf("the week %d , Rabnud has %d friends \n",week,friends); 433 | friends = (friends - loss)*2; 434 | } 435 | return 0; 436 | } 437 | ``` -------------------------------------------------------------------------------- /Chapter07/Codes/README.md: -------------------------------------------------------------------------------- 1 | ## 第七章 编程练习题代码 2 | -------------------------------------------------------------------------------- /Chapter07/Codes/练习题01.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Date : 2020 - 02 - 02 3 | * Author : Soler HO 4 | * 5 | * Book :C Primer Plus 6 | * 7 | * Description : 编写程序,读取输入,读到#字符就停止 8 | * 然后报告读取的空格数、换行符数和所有其他字符的数量 9 | * 10 | * 这里实现的方式使用了while 、if else 的方式,也可以使用 switch的方式 11 | * 12 | */ 13 | #include 14 | #define STOP '#' // 停止信号 15 | #define SPACE ' ' // 声明空格 16 | 17 | int main(void) 18 | { 19 | char ch; 20 | int lines = 0; // 换行符数 21 | int spaces = 0; // 空格数 22 | int others = 0; // 其他字符的数量 23 | 24 | printf("请输入内容(输入#即可停止):\n"); 25 | 26 | while((ch =getchar())!=STOP) // 注意点:换成scanf()函数读取,即不能结束 27 | { 28 | if(ch == '\n') 29 | lines++; 30 | else if(ch == SPACE) 31 | spaces++; 32 | else 33 | others++; 34 | } // while循环结束 35 | printf("空格数为:%d ;换行符数为: %d ;其他的字符数为: %d\n",spaces,lines,others); 36 | 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /Chapter07/Codes/练习题02.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Date : 2020 - 02 - 02 3 | * Author : Soler HO 4 | * 5 | * Book :C Primer Plus 6 | * 7 | * Description : 8 | * 编写一个程序读取输入,读到#字符就停止 9 | * 程序要打印每个输入的字符以及对应的ASCII码(十进制),一行打印8个字符。 10 | * 11 | * 建议:使用字符计数和求模运算符(%)在每8个循环周期时打印一个换行符。 12 | * 13 | */ 14 | 15 | #include 16 | #define STOP '#' 17 | 18 | const int NUM = 8; 19 | 20 | int main(void) 21 | { 22 | char ch; 23 | int count = 0; // 统计数量 24 | printf("请输入要打印的字符(输入#就停止):"); 25 | 26 | while((ch = getchar()) !=STOP) // 判断读取的字符 27 | { 28 | count++; 29 | putchar(ch); // 输出 30 | printf(" : %d ",ch); // 把字符转换成ASCII码形式 31 | if(count%8 == 0) // 不需要加花括号 32 | printf("\n"); 33 | } // while循环结束 34 | printf("\n"); 35 | printf("内容结束!拜拜!\n"); 36 | 37 | return 0; 38 | } 39 | -------------------------------------------------------------------------------- /Chapter07/Codes/练习题03.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Date : 2020 - 02 - 02 3 | * Author : Soler HO 4 | * 5 | * Book :C Primer Plus 6 | * 7 | * Description : 8 | * 9 | * 编写一个程序,读取整数直到用户输入0,输入结束后, 10 | * 程序应报告用户输入的偶数(不包括0)的个数,这些偶数的平均值、输入的奇数个数及其奇数的平均值。 11 | * 12 | */ 13 | #include 14 | 15 | 16 | int main(void) 17 | { 18 | int num; 19 | int even_Count = 0; // 偶数的数量 20 | int odd_Count = 0; // 奇数的数量 21 | int Sum_even = 0; // 偶数的和 22 | int Sum_odd = 0; // 奇数的和 23 | 24 | printf("请输入数字,以空格作为间隔,以0作为结尾:"); 25 | 26 | while ((scanf("%d",&num)) == 1) 27 | { 28 | if(num == 0) 29 | break; // break 直接结束循环 30 | else if(num % 2==0) 31 | { 32 | even_Count++; 33 | Sum_even += num; 34 | } // 偶数的个数统计和计算偶数的总和 35 | else 36 | { 37 | odd_Count++; 38 | Sum_odd += num; 39 | } // 奇数的个数统计和计算偶数的总和 40 | } // while循环结束 41 | printf("偶数的个数为:%d ; 所有的偶数的平均数为: %d \n",even_Count,Sum_even/even_Count); 42 | printf("奇数的个数为:%d ; 所有的奇数的平均数为: %d \n",odd_Count,Sum_odd/odd_Count); 43 | 44 | return 0; 45 | } -------------------------------------------------------------------------------- /Chapter07/Codes/练习题04.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Date : 2020 - 02 - 03 3 | * Author : Soler HO 4 | * 5 | * Book :C Primer Plus 6 | * 7 | * Description : 8 | * 使用if else语句编写一个程序读取输入,读到#停止。 9 | * 用感叹号替换句号,用两个感叹号替换原来的感叹号,最后输出进行了多少次替换。 10 | * 11 | * 12 | */ 13 | #include 14 | #define STOP '#' 15 | #define FULL_STOP '.' 16 | 17 | int main(void) 18 | { 19 | char ch; 20 | int count = 0; 21 | 22 | printf("请输入一句话,包含句号和感叹号(输入#就停止):"); 23 | 24 | while((ch = getchar()) !=STOP) 25 | { 26 | if(ch == FULL_STOP) 27 | { 28 | putchar('!'); 29 | count++; 30 | } 31 | else if(ch =='!') 32 | { 33 | putchar('!'); 34 | putchar('!'); 35 | count++; 36 | } 37 | else 38 | putchar(ch); 39 | } 40 | printf("一共转换了 %d 次\n",count); 41 | 42 | return 0; 43 | } -------------------------------------------------------------------------------- /Chapter07/Codes/练习题05.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Date : 2020 - 02 - 03 3 | * Author : Soler HO 4 | * 5 | * Book :C Primer Plus 6 | * 7 | * Description : 8 | * 使用switch语句,重新编写程序读取输入,读到#停止。 9 | * 用感叹号替换句号,用两个感叹号替换原来的感叹号,最后输出进行了多少次替换。 10 | * 11 | * 12 | */ 13 | #include 14 | 15 | int main(void) 16 | { 17 | char ch; 18 | int count = 0; 19 | 20 | printf("请输入一句话,包含句号和感叹号(输入#就停止):\n"); 21 | 22 | while((ch = getchar())!='#') 23 | { 24 | switch(ch) 25 | { 26 | case '.': 27 | putchar('!'); 28 | count++; 29 | break; 30 | case '!': 31 | putchar('!!'); 32 | count++; 33 | break; 34 | default: 35 | putchar(ch); 36 | } 37 | } 38 | printf("一共转换了 %d 次\n",count); 39 | 40 | return 0; 41 | } -------------------------------------------------------------------------------- /Chapter07/Codes/练习题06.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Date : 2020 - 02 - 03 3 | * Author : Soler HO 4 | * 5 | * Book :C Primer Plus 6 | * 7 | * Description : 编写程序读取输入,读到#停止,输出ei出现的次数。 8 | * 9 | * 逻辑运算符的应用 10 | * 11 | */ 12 | #include 13 | int main(void) 14 | { 15 | char ch; 16 | int count = 0; 17 | char first_char; 18 | 19 | printf("请输入一些内容(输入#停止):\n"); 20 | 21 | while((ch = getchar()) !='#') 22 | { 23 | if(ch=='e'){ 24 | first_char = ch; // 先让程序记住一个 25 | } 26 | if(ch=='i'&&first_char=='e') 27 | { 28 | count++; 29 | } 30 | } 31 | printf("内容中ei出现的次数是:%d \n",count); 32 | return 0; 33 | } -------------------------------------------------------------------------------- /Chapter07/Codes/练习题07.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Date : 2020 - 02 - 03 3 | * Author : Soler HO 4 | * 5 | * Book :C Primer Plus 6 | * 7 | * Description : 8 | * 编写程序,要求输入一周中的工作小时数,然后打印工资总额、税金以及净工资。作如下假设: 9 | * a.基本工资=1000 美元/小时 10 | * b.加班(超过40 小时)=1.5 倍的时间 11 | * c.税率: 前300 美元为15% 12 | * 续150美元为20% 13 | * 余下的为25%。 14 | * 用#define 定义常量,不必关心本例是否符合当前的税法。 15 | */ 16 | #include 17 | #define BASE_EVERY_HOUR 1000 18 | #define RATE01 0.15 19 | #define RATE02 0.2 20 | #define RATE03 0.25 21 | 22 | int main(void) 23 | { 24 | int hours; // 时间 25 | float base_salary; // 基本工资 26 | float rate; // 税收 27 | float income; // 收入 28 | const int regular_time = 40; // 正规工时 29 | 30 | printf("请输入你每周工作的时间:"); 31 | scanf("%d", &hours); 32 | 33 | if(hours < regular_time) 34 | { 35 | base_salary = BASE_EVERY_HOUR * regular_time; 36 | if(base_salary >=300 && base_salary<=450) 37 | rate = 300*0.15 + (base_salary - 300) * 0.2; 38 | if(base_salary >=450) 39 | rate = 300*0.15 + 150 * 0.2 + (base_salary - 450) * 0.25; 40 | income = base_salary - rate; 41 | } 42 | else 43 | { 44 | base_salary = BASE_EVERY_HOUR * hours; 45 | if(base_salary >=300 && base_salary<=450) 46 | rate = 300*0.15 + (base_salary - 300) * 0.2; 47 | if(base_salary >=450) 48 | rate = 300*0.15 + 150 * 0.2 + (base_salary - 450) * 0.25; 49 | if(base_salary < 300) 50 | rate = 0; 51 | income = base_salary - rate; 52 | } 53 | 54 | printf("你的总工资是:%.2f ; 收入是:%.2f ; 税收是:%.2f\n",base_salary,income,rate); 55 | 56 | return 0; 57 | 58 | } -------------------------------------------------------------------------------- /Chapter07/Codes/练习题08.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Date : 2020 - 02 - 03 3 | * Author : Soler HO 4 | * 5 | * Book :C Primer Plus 6 | * 7 | * Description : 8 | * 编写程序,要求输入一周中的工作小时数,然后打印工资总额、税金以及净工资。作如下假设: 9 | * a.基本工资=1000 美元/小时 10 | * b.加班(超过40 小时)=1.5 倍的时间 11 | * c.税率: 前300 美元为15% 12 | * 续150美元为20% 13 | * 余下的为25%。 14 | * 用#define 定义常量,不必关心本例是否符合当前的税法。 15 | * 16 | * 17 | 18 | 第八题题目要求 19 | 20 | 修改练习7 中的假设a,使程序提供一个选择工资等级的菜单。用switch 选择工资等级。程序运行的开头应该像这样: 21 | ***************************************************************** 22 | Enter the number corresponding to the desired pay rate or action: 23 | 1)$8.75/hr 2)$9.33/hr 24 | 3)$10.00/hr 4)$11.20/hr 25 | 5)quit 26 | ***************************************************************** 27 | 28 | 如果选择1 到4,那么程序应该请求输入工作小时数。程序应该一直循环运行,直到输入5。 29 | 如果输入1 到5 以外的选项,那么程序应该提醒用户合适的选项是哪些,然后再循环。用#define 为各种工资等级和税率定义常量。 30 | */ 31 | 32 | #include 33 | #define RATE01 0.15 34 | #define RATE02 0.2 35 | #define RATE03 0.25 36 | 37 | int main(void) 38 | { 39 | int hours; // 工作工时 40 | int number; // 编号 41 | float BASE; // 基本时薪 42 | float base_salary; // 总的工资 43 | float rate; // 税收 44 | float income; // 实际收入 45 | char ch; // 读入选择的字符 46 | const int regular_time = 40; // 正规工时 47 | 48 | printf("输入 s 开始,输入 # 开始计算:"); 49 | 50 | while((ch = getchar()) !='#') 51 | { 52 | printf("*****************************************************************\n"); 53 | printf("Enter the number corresponding to the desired pay rate or action:\n"); 54 | printf("%-25s%-25s\n%-25s%-25s\n%-25s\n","1) $8.75/r","2) $9.33/hr","3) $10.00/hr","4) $11.20/hr","5) quit"); 55 | printf("*****************************************************************\n"); 56 | printf("Your choice is:\n"); 57 | scanf("%d",&number); 58 | 59 | switch(number) 60 | { 61 | case 1: 62 | BASE = 8.75; 63 | break; 64 | case 2: 65 | BASE = 9.33; 66 | break; 67 | case 3: 68 | BASE = 10.00; 69 | break; 70 | case 4: 71 | BASE = 11.20; 72 | break; 73 | case 5: 74 | break; 75 | 76 | default: 77 | printf("请重新输入(1 到 5 数字):\n"); 78 | continue; 79 | } // switch结束 80 | 81 | printf("请输入你这个周工作的时间:\n"); 82 | scanf("%d",&hours); 83 | 84 | if(hours < regular_time) 85 | { 86 | base_salary = BASE * regular_time; 87 | if(base_salary >=300 && base_salary<=450) 88 | rate = 300*0.15 + (base_salary - 300) * 0.2; 89 | if(base_salary >=450) 90 | rate = 300*0.15 + 150 * 0.2 + (base_salary - 450) * 0.25; 91 | income = base_salary - rate; 92 | } 93 | else 94 | { 95 | base_salary = BASE * hours; 96 | if(base_salary >=300 && base_salary<=450) 97 | rate = 300*0.15 + (base_salary - 300) * 0.2; 98 | if(base_salary >=450) 99 | rate = 300*0.15 + 150 * 0.2 + (base_salary - 450) * 0.25; 100 | if(base_salary < 300) 101 | rate = 0; 102 | income = base_salary - rate; 103 | } 104 | 105 | printf("你的总工资是:%.2f ; 收入是:%.2f ; 税收是:%.2f\n",base_salary,income,rate); 106 | 107 | return 0; 108 | 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /Chapter07/README.md: -------------------------------------------------------------------------------- 1 | ## 第七章 分支和跳转控制语句 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | ## 1. `if`、`if...else`语句 5 | if语句被称为`分支语句(Branching statement)`或`选择语句(selection statement)`。 6 | 7 | ### 1.1 `if`和`if...else`常见形式 8 | #### 一般形式1 9 | ```c 10 | if(expression) 11 | statement 12 | // 如果expression为真,则执行statement部分。 13 | ``` 14 | 15 | #### 一般形式2 16 | ```c 17 | if(expression) 18 | statement01 19 | else 20 | statement02 21 | 22 | // 如果expression为真,执行statement01部分,否则,执行statement02部分。 23 | ``` 24 | 25 | #### 一般形式03 26 | ```c 27 | if(expression01) 28 | statement01 29 | else if(expression02) 30 | statement02 31 | else 32 | statement03 33 | 34 | // 如果expression01为真,执行statement01部分;如果expression02为真,执行statement02部分,否则,直接执行statement03部分。 35 | ``` 36 | 37 | 38 | ### 1.2 关于多层嵌套问题的注意点 39 | 在多层嵌套里面,多注意if与else与谁配对问题。 40 | 41 | 规则是:如果没有花括号,else与离它最近的if匹配,除非最近的if被花括号括起来。 42 | 43 | 注意点:要缩进的`statement语句`部分,可以是一条简单句或者是复杂语句。 44 | 45 | 对于简单语句,不需要加花括号,如果语句太长,使用花括号,来提高代码的可读性。 46 | 47 | 48 | ### 1.3 ctype.h头文件的字符测试函数 49 | 50 | |函数名|如果是下列参数时,返回值为真| 51 | |:--:|:--:| 52 | |islower()|小写字母| 53 | |isupper()|大写字母| 54 | |isalpha()|字母| 55 | |isalnum()|字母或十进制数字| 56 | |iscntrl()|控制字符| 57 | |isprint()|可打印字符(包含空格) 58 | |isgraph()|可打印字符(不包含空格) 59 | |isdigit()|十进制数| 60 | |isxdigit()|十六进制的数| 61 | |isblank()|标准空白字符(“空格”,'\t')| 62 | |isspace()|空位字符(空格,换行符(\n),换页符(\t),\v,\r,\f)| 63 | |ispunct()|isspace()和isalnum()返回false的可打印字符 64 | |tolower()|大写变小写| 65 | |toupper()|小写变大写| 66 | 67 | ### 1.4 ctype.h头文件的字符映射函数 68 | 69 | |函数名|行为| 70 | |:--:|:--:| 71 | |tolower()|如果参数是大写字符,该函数返回小写字符,否则,返回原始参数| 72 | |toupper|如果参数是小写字符,该函数返回大写字符,否则,返回原始参数| 73 | 74 | ### 1.5 经典例子:统计单词 75 | ```c 76 | #include 77 | #include // 为isspace()函数提供原型 78 | #include // 为bool、true、false提供定义 79 | #define STOP '!' 80 | 81 | int main(void) 82 | { 83 | char c; // 读入字符 84 | char prev; // 读入的前一个字符 85 | long n_chars = 0L; // 字符数 86 | int n_lines = 0; // 行数 87 | int n_words = 0; // 单词数 88 | int p_lines = 0; // 不完整的行数 89 | bool inword = false; // 如果c在单词中,inword等于true 90 | 91 | printf("请输入你要分析的文本(输入!就停止):"); 92 | prev = '\n'; 93 | while((c=getchar())!=STOP) 94 | { 95 | n_chars++; // 统计字符 96 | if(c == '\n') 97 | n_lines ++; //统计行数 98 | if(!isspace(c)&&!inword) 99 | { 100 | inword = true; // 开始一个新的单词 101 | n_words++; //统计单词 102 | } 103 | if(isspace(c)&&inword) 104 | inword = false; // 打到单词的末尾 105 | prev = 0; // 保存字符的值 106 | } 107 | if(prev != '\n') 108 | p_lines = 1; 109 | printf("一共 %ld 个字符,一共 %d 个单词 ,一共 %d 行",n_chars,n_words,n_lines); 110 | 111 | printf("partial lines = %d \n",p_lines); 112 | 113 | return 0; 114 | } 115 | ``` 116 | 117 | ## 2. 逻辑运算符 118 | 119 | 逻辑运算符的运算对象通常是关系表达式。 120 | 121 | `!`运算符只需要一个运算对象,其他的两个逻辑运算符都需要两个运算对象,左右两侧各一个。 122 | 123 | ### 2.1 三个运算符 124 | 125 | |逻辑运算符|含义|解释说明|iso646.h头文件中拼写| 126 | |:--:|:--:|:--:|:--:| 127 | |`&&`|与|两个为真,则为真|and| 128 | |$\|\|$| 或 |其中之一为真,即可为真|or| 129 | |`!`|非|非真即假,反之亦然|not| 130 | 131 | ### 2.2 优先级问题 132 | `逻辑运算符`的优先级比`关系运算符`的优先级`低`。 133 | 134 | `!`运算符的优先级高,比`乘法运算符`还高,与`递增运算符`的优先级相同,只比`圆括号`的优先级低。 135 | 136 | `&&`运算符的优先级比`||`运算符高,两者的优先级比关系运算符低,比赋值运算符高。 137 | 138 | ### 2.3 求值顺序 139 | 逻辑表达式的求值顺序都是`从左往右`,一旦发现有使得整个表达式为假的,立即停止求值。 140 | 141 | ## 3. 条件运算符:`?:` 142 | C提供了`条件表达式(conditional expression)`作为表达if else 语句的一种便捷方式,表达式使用:`?: `条件运算符。 143 | 144 | 运算符分为两部分,需要`3个运算对象`。也就是所谓的`三元运算符`,也是C语言中的`唯一`的三元运算符。 145 | 146 | ```c 147 | x = (y<0)?-y:y; 148 | 149 | // = 和 ; 之间的内容是条件表达式,语句的意思:如果y小于0,那么 x = -y;否则x = y。 150 | ``` 151 | if else表达式为: 152 | 153 | ```c 154 | if(y<0) 155 | x = -y; 156 | else 157 | x = y; 158 | ``` 159 | 160 | 通用的语法格式: 161 | ```c 162 | expression01 ? expression02:expression03; 163 | 164 | // 如果expression01为真(非0),整个条件表达式的值与expression02的值相同,expression01为假(0),表达式的值与expression03的值相同。 165 | min = (a < b)?b:a; // 如果a小于b,将min设置为b,否则,设置为a。 166 | ``` 167 | 168 | ## 4. continue语句和break语句 169 | ### 4.1 continue语句 170 | 执行continue语句时,会跳过本次迭代的剩余部分,并开始 `重新一轮的迭代`。 171 | 172 | 使用continue的好处:减少主语句组中的一级缩进。语句多层嵌套时,简洁的格式提高了代码的可读性。 173 | 174 | continue还可用作`占位符`。 175 | 176 | ### 4.2 break语句 177 | break语句不是跳至执行下一轮循环,而是导致`退出当前循环`。 178 | 179 | 和continue一样,如果使用break使得代码复杂,就不要使用break语句。 180 | 181 | ## 5. switch语句和break语句 182 | switch语句一般应用于`多重选择`,当然,也可以用`if else`的`多重嵌套`来完成。 183 | ### 5.1 一般语法格式 184 | ```c 185 | switch(expression) 186 | { 187 | case lable01: 188 | statement01; 189 | break; 190 | case lable02: 191 | statement01; 192 | break; 193 | default: 194 | statement03; 195 | } 196 | ``` 197 | ### 5.2 对语法格式的解释说明 198 | 可以是多个标签语句,default语句是可选的。 199 | 200 | 程序根据`expression的值`来跳转到相应的`case标签`位置,然后执行剩下的所有的语句,除非到break语句进行重新定向。 201 | 202 | `expression` 和 `case 标签` 都必须是整数值(包括char类型)。 203 | 204 | 标签必须是`常量` 或完全由`常量组成的表达式`。 205 | 206 | 如果case标签与expression的值匹配,控制则转至标有default的语句(存在该语句的话),否则,直接转至执行紧跟switch语句后面的语句。 207 | 208 | ## 6. goto语句 209 | 210 | goto语句使得程序控`制跳`转至相应的`标签语句`,`冒号` 用于`分隔标签` 和 `标签语句`。 211 | 212 | 标签名遵循变量命名规则,标签语句可以出现在goto的前面或者是后面。 213 | ### 6.1 goto语句的形式 214 | ```c 215 | goto label; 216 | . 217 | . 218 | . 219 | label:statement 220 | ``` 221 | ### 6.2 goto语句问题 222 | ⚠️建议:谨慎使用,或根本不用。 223 | 224 | goto语句有两部分:goto和标签名。标签的命名规则遵循变量命名规则。 225 | 226 | ```c 227 | goto:part2; 228 | 229 | // 该程序语句要能够完全运行,函数还必须包含另一条标注为 part2 的语句。 230 | 231 | part2:printf("函数语句\n"); 232 | ``` 233 | 234 | ### 6.3 为什么要避免goto语句? 235 | 原则是,C语言程序中`不使用goto语句`。 236 | 237 | `break`和`continue`语句是goto的特殊形式,使用break和continue的好处:名称已经确定其用法,这些语句不使用标签,不用担心把标签放错位置导致的危险。 238 | -------------------------------------------------------------------------------- /Chapter07/复习题.md: -------------------------------------------------------------------------------- 1 | ## 第七章 分支和跳转控制语句 复习题 2 | 3 | 1. 判断下列表达式时true还是false。 4 | ```c 5 | a. 100 > 3 && 'a' > 'c'; // false 6 | b. 100 > 3 || 'a' > 'c'; // true 7 | c. !(100>3); // false 8 | ``` 9 | 10 | 2. 根据下列描述的条件,分别构造一个表达式 11 | ```c 12 | a. number等于或大于90,但是小于100 // number>=90 && number < 100; 13 | b. ch不是字符q或k // ch !== 'q' || ch !== 'k'; 14 | c. number在1~9之间(包括1和9),但不是5 // number >=1 && number <=9 && number!==5; 15 | d. number不在1~9 // number < 1 && number > 9; 16 | ``` 17 | 18 | 3. 下面的程序关系表达式过于复杂,而且还有些错误,请简化并改正。 19 | ```c 20 | #include 21 | int main(void) 22 | { 23 | int weight, height; /* weight以磅为单位,height以英寸为单位 */ 24 | 25 | scanf("%d, weight, height); 26 | if(weight < 100 && height > 64) 27 | if(height >= 72) 28 | printf("You are very tall for your weight.\n"); 29 | else if(height < 72 && > 64) 30 | printf("You are tall for your weight.\n"); 31 | else if(weight > 300 && !(weight <= 300) 32 | && height < 48) 33 | if(!(height >= 48)) 34 | printf(" You are quite short for your weight.\n"); 35 | else 36 | printf("Your weight is ideal.\n"); 37 | 38 | return 0; 39 | } 40 | ``` 41 | 42 | 修改版本如下: 43 | ```c 44 | #include 45 | int main(void) 46 | { 47 | int weight, height; 48 | 49 | scanf("%d %d", &weight, &height); 50 | if (weight < 100 && height > 64) 51 | if (height >= 72) 52 | printf("You are very tall for your weight. \n"); 53 | else 54 | printf("You are tall for your weight. \n"); 55 | else if (weight > 300 && height < 48) 56 | printf(" Your are quite short for your weight. \n"); 57 | else 58 | printf("Your weight is ideal. \n"); 59 | 60 | return 0; 61 | } 62 | ``` 63 | 64 | 4. 下列各个表达式的值是多少? 65 | ```c 66 | a. 5>2 // true,即是1 67 | b. 3+4 > 2 && 3<2 // false,即是0 68 | c. x>=y || y > x // 1 69 | d. d = 5 + (6 > 2) // 6 70 | e. 'X' > 'T' ? 10 : 5 // 10 71 | f. x > y ? y > x : x > y // 0 72 | ``` 73 | 74 | 5. 下面的程序将打印什么? 75 | ```c 76 | #include 77 | int main(void) 78 | { 79 | int num; 80 | for (num = 1;num <=11;num++) 81 | { 82 | if(num %3 == 0) 83 | putchar('$'); 84 | else 85 | putchar('*'); 86 | putchar('#'); 87 | putchar('%'); 88 | } 89 | putchar('\n'); 90 | return 0; 91 | } 92 | // 打印结果为:*#%*#%$#%*#%*#%$#%*#%*#%$#%*#%*#% 93 | ``` 94 | 95 | 6. 下面的程序将打印什么? 96 | ```c 97 | #include 98 | int main(void) 99 | { 100 | int i = 0; 101 | while (i < 3) 102 | { 103 | switch (i++){ 104 | case 0:printf("fat "); 105 | case 1:printf("hat "); 106 | case 2:printf("cat "); 107 | default:printf("Oh no!"); 108 | } 109 | putchar('\n'); 110 | } 111 | return 0; 112 | } 113 | /*打印结果为: 114 | fat hat cat Oh no! 115 | hat cat Oh no! 116 | cat Oh no! 117 | */ 118 | ``` 119 | 7. 下面的程序有哪些错误? 120 | ```c 121 | #include 122 | int main(void) 123 | { 124 | char ch; 125 | int lc = 0; 126 | int uc = 0; 127 | int oc = 0; 128 | while ((ch = getchar())!='#') 129 | { 130 | if('a' <= ch >= 'z') 131 | lc++; 132 | else if (!(ch < 'A') || !(ch) > 'z') 133 | uc++; 134 | oc++; 135 | } 136 | printf(%d lowercase, %d uppercase, %d other,lc,uc,oc); 137 | return 0; 138 | } 139 | ``` 140 | 修改后的程序: 141 | ```c 142 | #include 143 | #include 144 | int main(void) 145 | { 146 | char ch; 147 | int lc = 0; 148 | int uc = 0; 149 | int oc = 0; 150 | while ((ch = getchar())!='#') 151 | { 152 | if(islower(ch)) 153 | lc++; 154 | else if (isupper(ch)) 155 | uc++; 156 | else 157 | oc++; 158 | } 159 | printf("%d lowercase, %d uppercase, %d other \n",lc,uc,oc); 160 | return 0; 161 | } 162 | ``` 163 | 164 | 8. 下面的程序将打印什么? 165 | ```c 166 | #include 167 | int main(void) 168 | { 169 | int age = 20; 170 | while (age++ <= 65) // 171 | { 172 | if ((age % 20)==0) 173 | printf("You are %d . Here is a raise.\n",age); 174 | if (age =65) 175 | printf("You are %d . Here is your gold watch.\n",age); 176 | } 177 | return 0; 178 | } 179 | /*打印结果:死循环打印如下内容 180 | You are %d . Here is your gold watch. 181 | */ 182 | ``` 183 | 184 | 9. 给定下面的输入时,以下程序将打印什么? 185 | ```c 186 | /* 187 | q 188 | c 189 | h 190 | b 191 | */ 192 | #include 193 | int main(void) 194 | { 195 | char ch; 196 | 197 | while((ch = getchar()) != '#') 198 | { 199 | if(ch == '\n') 200 | continue; 201 | printf("Step 1\n"); 202 | if(ch == 'c') 203 | continue; 204 | else if(ch == 'b') 205 | break; 206 | else if(ch == 'g') 207 | goto laststep; 208 | printf("Step 2\n"); 209 | laststep: printf("Step 3\n"); 210 | } 211 | printf("Done!\n"); 212 | return 0; 213 | } 214 | /*打印结果为: 215 | q 216 | Step 1 217 | Step 2 218 | Step 3 219 | c 220 | Step 1 221 | g 222 | Step 1 223 | Step 3 224 | b 225 | Step 1 226 | Done! 227 | */ 228 | ``` 229 | 230 | 10. 重写复习题9,但这次不能使用continue 和 goto语句。 231 | ```c 232 | #include 233 | int main(void) 234 | { 235 | char ch; 236 | 237 | while((ch = getchar()) != '#') 238 | { 239 | if(ch != '\n') 240 | { 241 | printf("Step 1\n"); 242 | if(ch != 'c') 243 | { 244 | if(ch == 'b') 245 | break; 246 | if(ch != 'g') 247 | printf("Step 2\n"); 248 | printf("Step 3\n"); 249 | } 250 | } 251 | } 252 | printf("Done!\n"); 253 | return 0; 254 | } 255 | ``` 256 | -------------------------------------------------------------------------------- /Chapter07/编程题.md: -------------------------------------------------------------------------------- 1 | ## 第七章 分支和跳转控制语句 编程练习题 2 | 3 | 1. 编写程序,读取输入,读到#字符就停止然后报告读取的空格数、换行符数和所有其他字符的数量。 4 | ```c 5 | #include 6 | #define STOP '#' // 停止信号 7 | #define SPACE ' ' // 声明空格 8 | 9 | int main(void) 10 | { 11 | char ch; 12 | int lines = 0; // 换行符数 13 | int spaces = 0; // 空格数 14 | int others = 0; // 其他字符的数量 15 | 16 | printf("请输入内容(输入#即可停止):\n"); 17 | 18 | while((ch =getchar())!=STOP) // 注意点:换成scanf()函数读取,即不能结束 19 | { 20 | if(ch == '\n') 21 | lines++; 22 | else if(ch == SPACE) 23 | spaces++; 24 | else 25 | others++; 26 | } // while循环结束 27 | printf("空格数为:%d ;换行符数为: %d ;其他的字符数为: %d\n",spaces,lines,others); 28 | 29 | return 0; 30 | } 31 | ``` 32 | 33 | 2. 编写一个程序读取输入,读到#字符就停止程序要打印每个输入的字符以及对应的ASCII码(十进制),一行打印8个字符。建议:使用字符计数和求模运算符(%)在每8个循环周期时打印一个换行符。 34 | ```c 35 | #include 36 | #define STOP '#' 37 | 38 | const int NUM = 8; 39 | 40 | int main(void) 41 | { 42 | char ch; 43 | int count = 0; // 统计数量 44 | printf("请输入要打印的字符(输入#就停止):"); 45 | 46 | while((ch = getchar()) !=STOP) // 判断读取的字符 47 | { 48 | count++; 49 | putchar(ch); // 输出 50 | printf(" : %d ",ch); // 把字符转换成ASCII码形式 51 | if(count%8 == 0) // 不需要加花括号 52 | printf("\n"); 53 | } // while循环结束 54 | printf("\n"); 55 | printf("内容结束!拜拜!\n"); 56 | 57 | return 0; 58 | } 59 | ``` 60 | 61 | 3. 编写一个程序,读取整数直到用户输入0,输入结束后,程序应报告用户输入的偶数(不包括0)的个数,这些偶数的平均值、输入的奇数个数及其奇数的平均值。 62 | ```c 63 | #include 64 | int main(void) 65 | { 66 | int num; 67 | int even_Count = 0; // 偶数的数量 68 | int odd_Count = 0; // 奇数的数量 69 | int Sum_even = 0; // 偶数的和 70 | int Sum_odd = 0; // 奇数的和 71 | 72 | printf("请输入数字,以空格作为间隔,以0作为结尾:"); 73 | 74 | while ((scanf("%d",&num)) == 1) 75 | { 76 | if(num == 0) 77 | break; // break 直接结束循环 78 | else if(num % 2==0) 79 | { 80 | even_Count++; 81 | Sum_even += num; 82 | } // 偶数的个数统计和计算偶数的总和 83 | else 84 | { 85 | odd_Count++; 86 | Sum_odd += num; 87 | } // 奇数的个数统计和计算偶数的总和 88 | } // while循环结束 89 | printf("偶数的个数为:%d ; 所有的偶数的平均数为: %d \n",even_Count,Sum_even/even_Count); 90 | printf("奇数的个数为:%d ; 所有的奇数的平均数为: %d \n",odd_Count,Sum_odd/odd_Count); 91 | 92 | return 0; 93 | } 94 | ``` 95 | 96 | 4. 使用if else语句编写一个程序读取输入,读到#停止。用感叹号替换句号,用两个感叹号替换原来的感叹号,最后输出进行了多少次替换。 97 | ```c 98 | #include 99 | #define STOP '#' 100 | #define FULL_STOP '.' 101 | 102 | int main(void) 103 | { 104 | char ch; 105 | int count = 0; 106 | 107 | printf("请输入一句话,包含句号和感叹号(输入#就停止):"); 108 | 109 | while((ch = getchar()) !=STOP) 110 | { 111 | if(ch == FULL_STOP) 112 | { 113 | putchar('!'); 114 | count++; 115 | } 116 | else if(ch =='!') 117 | { 118 | putchar('!!'); 119 | count++; 120 | } 121 | else 122 | putchar(ch); 123 | } 124 | printf("一共转换了 %d 次\n",count); 125 | 126 | return 0; 127 | } 128 | ``` 129 | 130 | 5. 使用switch重写练习4 131 | ```c 132 | #include 133 | 134 | int main(void) 135 | { 136 | char ch; 137 | int count = 0; 138 | 139 | printf("请输入一句话,包含句号和感叹号(输入#就停止):\n"); 140 | 141 | while((ch = getchar())!='#') 142 | { 143 | switch(ch) 144 | { 145 | case '.': 146 | putchar('!'); 147 | count++; 148 | break; 149 | case '!': 150 | putchar('!!'); 151 | count++; 152 | break; 153 | default: 154 | putchar(ch); 155 | } 156 | } 157 | printf("一共转换了 %d 次\n",count); 158 | 159 | return 0; 160 | } 161 | ``` 162 | 163 | 6. 编写程序读取输入,读到#停止,输出ei出现的次数。 164 | > 可以使用 “Receive your eieio award” 来进行测试 165 | ```c 166 | #include 167 | int main(void) 168 | { 169 | char ch; 170 | int count = 0; 171 | char first_char; 172 | 173 | printf("请输入一些内容(输入#停止):\n"); 174 | 175 | while((ch = getchar()) !='#') 176 | { 177 | if(ch=='e'){ 178 | first_char = ch; // 先让程序记住一个 179 | } 180 | if(ch=='i'&&first_char=='e') 181 | { 182 | count++; 183 | } 184 | } 185 | printf("内容中ei出现的次数是:%d \n",count); 186 | return 0; 187 | } 188 | ``` 189 | -------------------------------------------------------------------------------- /Chapter08/README.md: -------------------------------------------------------------------------------- 1 | ## 第八章 字符输入/输出和输入验证 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | ## 1. 单字符I/O:`getchar()`和 `putchar()` 5 | 6 | `getchar()` 和 `putchar()` 每次都只处理一个字符。 7 | 注意点:`getchar()`和`scanf()`函数的区别用法 8 | 9 | `getchar()`读取`每个字符`,`包括空格、制表符和换行符`。 10 | 11 | `scanf()`在读取数字时,则会`跳过空格、制表符和换行符`。 12 | ### 1.1 缓冲的概述 13 | 用户输入的字符被收集并储存在一个叫做 **缓冲区** 的`临时存储区`,按下Enter之后,程序才可以使用用户输入的字符。 14 | 15 | ![缓冲区](./img/缓冲输入和无缓冲输入.png) 16 | 17 | - **缓冲输入**:按下Enter之前,不会重复打印刚输入的字符,这种类型叫做缓冲输入。 18 | - **无缓冲输入**:回显用户输入的字符后立即重新打印该字符的就是输入无缓冲。 19 | 20 | ## 缓冲分为两类 21 | 22 | - **完全缓冲I/O**:当缓冲区被`填满`时猜刷新缓冲区(内容被发送至目的地),通常出现在`文件输入`中。 23 | 24 | 缓冲区的大小取决于系统,常见的大小是`512 字节`。 25 | 26 | - **行缓冲I/O**:出现在`换行符`时刷新缓冲区。`键盘输入`通常是 行缓冲输入。按下Enter键就会刷新缓冲区。 27 | ## 2. 文件、流和键盘输入 28 | 29 | ### 2.1 文件 30 | **文件(file)** 是存储器中储存信息的区域。 31 | 32 | 文件保存在某种`永久存储器`中(如硬盘、U盘或DVD等)。 33 | 34 | C语言中有许多用于`打开、读取、写入和关闭文件`的库函数。 35 | 36 | #### 两个层次去分析: 37 | 38 | - 较低层面 39 | - C可以使用主机操作系统的基本文件工具直接处理文件。 40 | - 直接调用操作系统的函数叫做`底层I/O(low-level I/O)`。 41 | - 系统不同,所以不可能为普通的底层创建标准库。 42 | 43 | - 较高层面 44 | - 通过标准`I/O(standard I/O package)`来处理文件。 45 | - 创建用于处理文件的标准模型和一套I/O函数。 46 | 47 | 从概念上来讲,C程序处理的是流而不是直接处理文件。 48 | 49 | ### 2.2 流(stream) 50 | 51 | 流(stream)是一个实际输入或输出映射的理想化数据流。意味着不同的属性和不同种类的输入,由属性更统一的流来表示。 52 | 53 | ### 2.3 文件的结尾 54 | 计算机操作系统要以某种方式判断文件的开始和结束。 55 | 56 | 检测文件结尾的方法:在文件末尾放一个特殊的`字符标记文件结尾`。 57 | 58 | 操作系统使用的另一种方法:储存文件大小的信息。 59 | 60 | 在C语言中,使用`getchar()`读取文件检测结尾时将返回一个特殊的值,即:`EOF(end of file的缩写)`。 61 | 62 | scanf()函数检测到文件`结尾`时也返回`EOF`。 63 | 64 | 通常,EOF定义在stdio.h文件中: 65 | 66 | ```c 67 | #define EOF (-1) 68 | ``` 69 | > 为什么是-1?----------> -1 都不对应任何字符集,所以,使用-1作为标记文件的结尾。 70 | 71 | `getchar()`函数的返回值通常介于`0 ~ 127`,对应的值都对因相应的字符集。 72 | 73 | ### 2.4 文件结尾`EOF`的问题 74 | ```c 75 | #include 76 | 77 | int main(void) 78 | { 79 | int ch; 80 | 81 | while((ch = getchar()) != EOF) 82 | putchar(ch); 83 | 84 | return 0; 85 | } 86 | ``` 87 | 88 | 程序的解析: 89 | - 不用定义,因为stdio.h中已经定义。 90 | - 不用管EOF的实际值,在stdio.h中用#define预处理器指令定义,可直接使用,不用再编写代码假定EOF为某一个值。 -------------------------------------------------------------------------------- /Chapter08/codes/README.md: -------------------------------------------------------------------------------- 1 | ## 第八章 编程练习题代码 2 | -------------------------------------------------------------------------------- /Chapter08/codes/练习题01.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Date : 2021 - 12 - 25 3 | * Author : Soler HO 4 | * 5 | * Book :C Primer Plus 6 | * 7 | * Description : 设计一个程序,统计在读到文件结尾之前读取的字符数 8 | * 9 | */ 10 | #include 11 | #define STOP '\n' 12 | int main(void) 13 | { 14 | char ch; 15 | int count = 0; 16 | puts("please input your string : "); 17 | while ((ch = getchar())!= STOP) 18 | count++; 19 | printf("the character count is %d \n",count); 20 | return 0; 21 | } -------------------------------------------------------------------------------- /Chapter08/codes/练习题03.c: -------------------------------------------------------------------------------- 1 | /** 2 | * Date : 2020 - 02 - 12 3 | * Author : Soler HO 4 | * 5 | * Book :C Primer Plus 6 | * 7 | * Description : 8 | * 9 | * 编写一个程序,在遇到EOF之前,把输入作为字符流读取。该程序要报告输入的大写字母和小写字母的个数。 10 | * 假设大小写字母数是连续的。或者使用ctype.c库中的分类函数更方便。 11 | */ 12 | 13 | #include 14 | 15 | #define STOP '#' 16 | 17 | int main(void) 18 | { 19 | char ch; 20 | int Count_Capital_letters = 0; // 计算大写字母个数 21 | int Count_small_letters = 0; // 计算小写字母个数 22 | 23 | printf("请输入内容:\n"); 24 | 25 | while((ch = getchar())!= STOP) 26 | { 27 | if(ch >= 'A'&&ch <= 'Z') 28 | Count_Capital_letters++; 29 | else if(ch >= 'a'&&ch <= 'z') 30 | Count_small_letters++; 31 | } 32 | 33 | printf("大写字母的个数为: %d ; 小写字母的个数为: %d \n",Count_Capital_letters,Count_small_letters); 34 | 35 | return 0; 36 | 37 | } -------------------------------------------------------------------------------- /Chapter08/img/缓冲输入和无缓冲输入.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter08/img/缓冲输入和无缓冲输入.png -------------------------------------------------------------------------------- /Chapter08/复习题.md: -------------------------------------------------------------------------------- 1 | ## 第八章 字符输入/输出和输入验证 2 | 3 | 1. `putchar(getchar())` 是一个有效的表达式,它实现什么功能?`getchar(putchar())` 是否也是有效表达式? 4 | > 1) `putchar(getchar())` 功能:将程序读取的下一个字符并打印,getchar()的返回值是putchar() 的参数。 5 | > 6 | > 2) `getchar(putchar())` 无效表达式,原因:getchar()无需参数,而putchar则需要一个参数。 7 | 8 | 9 | 2. 下面的语句分别完成什么任务? 10 | ```c 11 | a. putchar('H'); // 打印H 12 | b. putchar('\007'); // 发出一下警报声 13 | c. putchar('\n'); // 自带换行,所以会再进行一次换行 14 | d. putchar('\b'); // 退后一格 15 | ``` 16 | 17 | 3. 假设有一个名为count的可执行程序,用于统计输入的字符数。设计一个使用count程序统计essay文件字符数的命令行,并把统计结果保存在essayct文件中。 18 | ```c 19 | count essayct #将essay作为输入,然后通过重定向降结果放在essayct文件中 20 | ``` 21 | 22 | 4. 给定复习题3中的程序和文件,下面哪一条是有效的命令? 23 | ```c 24 | a. essayct count // 不是,essay是输入文件,不是可执行文件 27 | ``` 28 | 5. EOF是什么?✅ 29 | > 一个返回信号(一个特殊值,一般是-1),表明监测到文件结尾处。主要是getchar() 和scanf() 函数来使用。 30 | 31 | 6. 对于给定的输入(ch是int类型,而且是缓冲输入),下面各个程序的输出分别是什么? 32 | ```c 33 | a. 输入如下: 34 | If you quit, i will.[enter] 35 | 程序段如下: 36 | while ((ch == getchar())!='i') // 输入读取中,如果=i就直接输出前面的内容 37 | putchar(ch); // 输出内容:If you qu 38 | 39 | b. 输入如下: 40 | Harbor[enter] 41 | 程序段如下: 42 | while((ch = getchar()) != '\n') // 从第一个进行读取,读到最后是空行时结束 43 | { 44 | // 最后的输出是:HJacrthjacrt 45 | putchar(ch++); // 注意H+1是I 46 | putchar(++ch); // 上一个是I,再次++,就变成了J 47 | } 48 | ``` 49 | 7. C如何处理不同计算机系统中的不同文件和换行约定? 50 | > 将文件的不同属性和种类统一映射为流来处理。 51 | 52 | 8. 在使用缓冲输入的系统汇总,把数值和字符串混合输入会遇到什么潜在的问题? 53 | > a. 读取到数值类型时:会直接跳过空格和换行符 54 | > 55 | > b. 读取到字符串类型时:字符串则不会。 56 | 57 | 所以如果存在混合情况,则需要在处理字符输入之前就把换行符直接优先处理掉。 58 | -------------------------------------------------------------------------------- /Chapter08/编程题.md: -------------------------------------------------------------------------------- 1 | ## 第八章 编程练习题 2 | 3 | 1. 设计一个程序,统计在读到文件结尾之前读取的字符数 4 | 5 | [代码code](./codes/ch08_p1.c) 6 | 7 | 2. 编写一个程序,在遇到EOF之前,把输入作为字符流读取。程序要打印每个输入的字符及其相应的ASCII十进制值。 8 | ```c 9 | #include 10 | 11 | int main(void) 12 | { 13 | char ch; 14 | int row = 0; 15 | printf("please input your test string : \n"); 16 | // 此处使用while和if方式,后续会继续优化code 17 | while ((ch = getchar()) != EOF) 18 | { 19 | if(ch >= 32){ // 直接打印字符 20 | putchar(ch); 21 | printf(" -- %d ",ch); 22 | row++; 23 | } 24 | // 换行符的情况 25 | else if (ch == '\n'){ 26 | printf("\\n"); 27 | printf(" -- %d ",ch); 28 | putchar(ch); 29 | row = 0; // 遇到换行符直接将其重置为0 30 | } 31 | // 制表符的情况 32 | else if (ch == '\t'){ 33 | printf("\\t"); 34 | printf(" -- %d ",ch); 35 | putchar(ch); 36 | row++; 37 | } 38 | // 控制字符表示法的情况 39 | else{ 40 | putchar('^'); 41 | putchar(ch+64); 42 | printf(" -- %d ",ch); 43 | } 44 | if(row == 10){ 45 | putchar('\n'); 46 | row = 0; 47 | } 48 | } 49 | return 0; 50 | } 51 | ``` 52 | 53 | 3. 编写一个程序,在遇到EOF之前,把输入作为字符流处读取。程序的需求:报告输入中的大写字符和小写 -------------------------------------------------------------------------------- /Chapter09/Codes/README.md: -------------------------------------------------------------------------------- 1 | ## 第九章 编程练习题代码 2 | -------------------------------------------------------------------------------- /Chapter09/README.md: -------------------------------------------------------------------------------- 1 | ## 第九章 函数 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | ## 1.函数相关概念 5 | `函数原型(function prototype)`告诉编译器函数的类型。 6 | 7 | `调用函数(function call)`:执行函数。 8 | 9 | `定义函数(function definition)`:明确函数要做什么。 10 | 11 | 使用函数之前,要用ANSI C形式声明函数原型。当函数接受参数时,函数原型用逗号分隔的列表指明参数的数量和类型。 12 | 13 | 用符号常量作为参数,实际参数(actual argument)简称`实参`。 14 | 15 | `形式参数(formal argument)`简称`形参`。 16 | 17 | 和定义在函数中变量一样,`形式参数`也是`局部变量`,属该函数私有。 18 | 19 | 在ANSI C要求在每个变量前都要声明其类型。 20 | 21 | 圆括号中只有`参数名列表`,而参数的类型在后面声明。 22 | 23 | 注意点:普通的局部变量在`左花括号`之后声明。 24 | ## 2. 函数原型问题 25 | 26 | 声明函数的类型,不用声明任何参数。 27 | 28 | 主调函数把它的参数储存在被称为`栈(stack)`的临时存储区,被调函数从栈中读取这些参数。 29 | 30 | 针对参数不匹配的问题,ANSI C标准要求在函数声明时还要声明变量的类型,即使用函数原型(function prototype)来声明函数的返回类型,参数的数量和每个参数的类型。 31 | 32 | 错误和警告的区别:`错误`导致`无法编译`,而`警告`仍然`允许编译`。 33 | 34 | ### 2.1 无参数和未指定参数 35 | 假设函数原型为: 36 | 37 | ```c 38 | void print_name(void); 39 | ``` 40 | 41 | 一个支持ANSI C的编译器会假定用户没有用函数原型来声明函数,不会检查参数。为了表明函数没有参数,应该在`圆括号`中使用`关键字`。 42 | 43 | ```c 44 | void print_name(void); 45 | ``` 46 | 47 | 支持ANSI C的编译器解释为print_name()不接受任何参数。然后在调用该函数时,编译器会检查以确保没有使用参数。 48 | 49 | ### 2.2 函数原型的优点 50 | 函数原型是C语言不错的工具,能让编译器捕获在使用函数时可能出现的许多错误或纰漏。 51 | 52 | 53 | ## 3. 递归 54 | 55 | ### 3.1 什么是递归? 56 | 57 | 函数`自己调用自己`。这个调用过程就是`递归(recursion)`。 58 | 59 | 递归方案更简洁,但是效率不如循环。 60 | 61 | ### 3.2 递归函数的基本原理 62 | 63 | - 每级函数调用都有自己的变量。 64 | - 每次函数调用都会返回一次 65 | - 递归函数中位于递归调用之前的语句,均按被调函数的顺序执行。 66 | - 递归函数只能位于递归调用之后的语句,均按被调函数相反的顺序执行。 67 | - 虽然每级递归都有自己的变量,但是并没有拷贝函数的代码,程序按顺序执行函数中的代码,而递归函数就相当于从头开始执行函数的代码。 68 | - 递归函数必须包含能让递归调用停止的语句。通常是递归函数都使用if或其他等价的测试条件在函数形参等于某个特定值时终止递归。所以,每次递归调用的形参都要使用不同的值。 69 | 70 | ### 3.3 尾递归 71 | 把递归调用置于`函数末尾`,即正好在`return语句之前`,此类型的递归称为`尾递归`。 72 | 73 | ### 3.4 递归的优缺点 74 | - 优点 75 | - 为某些编程问题提供了最简单的解决方案。 76 | - 缺点 77 | - 会快速消耗计算机的内存资源。 78 | - 不方便阅读和维护。 79 | 80 | 常见递归的例子:斐波那契数列。 81 | 82 | ## 4. 指针简介 83 | 指针(pointer):一个值为内存地址的变量(或数据对象)。 84 | 85 | **指针变量的值是:地址**。 86 | 87 | ```c 88 | ptr = &bah; // 把ptr指向bah。 89 | 90 | //ptr是可修改的左值,而&bah是右值。 91 | 92 | // ptr的值是bah的地址。 93 | ``` 94 | > 要创建指针变量,先要声明指针变量的类型。 95 | 96 | ### 4.1 声明指针 97 | 声明指针变量时必须指定指针所指向变量的类型,因为不同的变量类型占用不同的存储空间,一些指针操作要求知道操作对象的大小。 98 | 99 | 声明示例: 100 | 101 | ```c 102 | int *pi; // pi 是指向int类型变量的指针 103 | char *ch; // ch 是指向char类型变量的指针 104 | float *f1,*f2; // f1、f2都是指向float类型变量的指针 105 | ``` 106 | 107 | `类型说明符`表明了指针所指向对象的类型,`(*)`表明声明的变量也是一个`指针`。 108 | 109 | ![](./img/声明并使用指针.png) 110 | 111 | `(*)`和`指针`之间的`空格可有可无`,通常情况下,在`声明`是`使用空格`,在`解引用变量`时`省略空格`。 112 | 113 | 对于ANSI C标准而言,直接提供了 `%p` 格式的转换说明。 114 | 115 | ### 4.2 间接运算符:`*` 116 | 117 | 假设ptr指向bah。如下: 118 | 119 | ```c 120 | ptr = &bah; 121 | ``` 122 | 123 | 使用`间接运算符*(indirection operator)`找出储存在bah中的值,该运算符也称为`解引用运算符(dereferencing operator)`。 124 | 125 | **注意点**:不要把`间接运算符` 和 `二元乘法运算符(*)`混淆,使用的符号虽然相同,但语法功能不相同。 126 | 127 | 128 | ```c 129 | val = *ptr; // 找出ptr指向的值 130 | ``` 131 | 132 | 语句`ptr = &bah;` 和 `val = *ptr;` 放在一起等于以下的语句: 133 | 134 | ```c 135 | val = *ptr; 136 | ``` 137 | 138 | ### 4.3 总结:与指针相关的运算符 139 | 140 | #### 地址运算符:`&` 141 | 一般注解: 142 | 143 | 后面跟一个变量名,`&` 给出该变量的地址。 144 | 145 | ```c 146 | &number 表示变量number的地址 147 | ``` 148 | #### 地址运算符:`*` 149 | 一般注解: 150 | 151 | 后跟一个指针名或地址时, `*` 会给出储存在指针指向地址上的值。 152 | 153 | ```c 154 | number = 10; 155 | ptr = &number; // 指向number的指针 156 | val = *ptr; // 把ptr指向的地址上的值赋给val 157 | ``` 158 | 最终,把 ` 10 `赋给 `val`。 159 | 160 | 161 | ## 5. 函数小结 162 | 163 | ### 5.1 形式 164 | 典型的ANSI C函数的定义形式为: 165 | 166 | ```c 167 | 返回类型 名称(形参声明列表) 168 | 函数体 169 | 170 | 形参声明列表是用逗号分隔的一系列变量声明。除了形参变量外,函数的其他的变量均在函数体的花括号之内声明。 171 | ``` 172 | 173 | ```c 174 | int diff(int x,int y) 175 | { // 函数体开始 176 | int z; // 声明局部变量 177 | z = x - y; 178 | return z; // 返回一个值 179 | } // 函数体结束 180 | ``` 181 | 182 | ### 5.2 传递值 183 | 实参用于把值从`主调函数`传递给`被调函数`。 184 | 185 | 如果变量 a = 5 和 b = 2; 那么调用 186 | 187 | ```c 188 | c = diff(a,b); 189 | ``` 190 | 把 5 和 2 分别传递给变量 x 和 y。 191 | 192 | 5 和 2 就称为 **实际参数(简称实参)** 。`diff()函数`中定义的变量 x 和 y 称为 **形式参数(简称形参)** 。 193 | 194 | 被调函数一般不会改变主调函数中的变量,如果要改变,应使用指针作为参数。 195 | 196 | ### 5.3 函数的返回类型 197 | 函数的返回类型指的是`函数返回值的类型`。如果`返回值`的类型与`声明`的返回类型`不匹配`,返回值将被转换成函数声明的返回类型。 198 | 199 | #### 5.4 函数签名 200 | 函数的`返回类型`和`形参列表` 构成了函数签名。 201 | 202 | 函数签名指定了传入函数的值的类型和函数返回值的类型。 203 | 204 | ```c 205 | double duff(double, int) // 函数原型 206 | 207 | int main(void) 208 | { 209 | double q,x; 210 | int n; 211 | ... 212 | q = duff(x,n); 213 | } 214 | 215 | double duff(double u, int k) // 函数的定义 216 | { 217 | double tor; 218 | ... 219 | return tor; // 返回double类型的值 220 | } 221 | ``` 222 | -------------------------------------------------------------------------------- /Chapter09/img/声明并使用指针.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter09/img/声明并使用指针.png -------------------------------------------------------------------------------- /Chapter09/复习题.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter09/复习题.md -------------------------------------------------------------------------------- /Chapter09/编程题.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter09/编程题.md -------------------------------------------------------------------------------- /Chapter10/Codes/README.md: -------------------------------------------------------------------------------- 1 | ## 第十章 编程练习题代码 2 | -------------------------------------------------------------------------------- /Chapter10/README.md: -------------------------------------------------------------------------------- 1 | ## 第十章 数组和指针 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | ## 1. 数组的概述 5 | ### 1.1 声明数组 6 | 数组由 `数据类型` 相同的一系列元素组成。 7 | 8 | 需要使用数组时,通过声明数组告诉编译器数组中内含多少元素和这些元素的类型。 9 | 10 | 普通变量可以使用的类型,数组元素都可以使用。 11 | 12 | 数组声明示例: 13 | 14 | ```c 15 | int main(void) 16 | { 17 | float candy[365]; /*内含365个float类型元素的数组*/ 18 | char code[12]; /*内含12个char类型元素的数组*/ 19 | int states[50]; /*内含50个int类型元素的数组*/ 20 | } 21 | ``` 22 | 23 | 只要有 `方括号([])` 就都是数组,方括号中的数字表明数组中的`元素个数`。 24 | 25 | 如果要 `访问` 数组中的元素,通过使用数组 `下标数字(也称为索引)`表示数组中的各元素。 26 | 27 | 数组的元素的编号从 ` 0 ` 开始。 28 | ### 1.2 初始化数组 29 | 数组通常被用来储存程序需要的数据。 30 | 31 | 只储存单个值的变量又叫做 ` 标量变量(scalar variable)`。 32 | 33 | 用以 ` 逗号 ` 分隔的 ` 元素值列表(用花括号括起来)`来初始化数组,各元素值之间用 ` 逗号 ` 分隔。 在逗号和元素值之间可以使用空格。 34 | 35 | ```c 36 | int num[4] = {1,2,3,4}; 37 | ``` 38 | ### 1.3 给数组元素赋值 39 | 声明数组之后,就可以借助数组 `下标(或索引)`给数组元素赋值。 40 | 41 | **注意点**:在C语言中,不允许把数组作为一个单位赋值给另一个数组,除了初始化以外也不允许使用花括号列表的形式赋值。 42 | 43 | 案例代码: 44 | 45 | ```c 46 | #define SIZE 5 47 | int main(void) 48 | { 49 | int oxen[SIZE] = {5,3,2,8}; // 初始化没问题 50 | int yaks[SIZE]; 51 | 52 | yaks = oxen; // 不允许这么赋值 53 | yaks[SIZE] = oxen[SIZE]; // 数组的下标越界 54 | yaks[SIZE] = {5,3,2,8}; // 不起作用 55 | } 56 | ``` 57 | ### 1.4 使用指定初始化器(C99) 58 | 59 | C99的新特性:`指定初始化器(disignated initializer)`。 60 | 61 | ``` 62 | int arr[6] = {0,0,0,0,0,0,2}; // 传统的语法 63 | ``` 64 | > C99规定,可以初始化列表中使用带方括号的下标指明待初始化的元素。 65 | 66 | ``` 67 | int arr[6] = {[5] = 2}; // 把arr[5] 初始化为2 68 | ``` 69 | 70 | > 对于一般的初始化,在初始化一个元素后,末初始化的元素的都会被设置为 0 。 71 | 72 | ⚠️注意:***`在使用数组时,要防止数组下标超出边界。必须确保下标是有效的值`***。 73 | 74 | ## 2. 多维数组 75 | 76 | 77 | ### 2.1 初始化二维数组 78 | 初始化二维数组是建立在`初始化一维数组`的基础上。 79 | 80 | 初始化一维数组的格式如下: 81 | 82 | ```c 83 | 数据类型 arr[5] = {val01,val02,val03,val04,val05}; // val01、val02等表示数据类型的值。 84 | ``` 85 | 86 | > 初始化时也可省略内部的花括号,只保留最外面的一对花括号。只要保证初始化的`数值个数`正确,初始化结果就不会出错。 87 | 88 | > 如果初始化的数值不够,按照先后顺序逐行初始化,直到用完所有的值。后面没有值初始化的元素被统一初始化为0。 89 | 90 | 初始化二维数组的两种方法: 91 | 92 | ![](./img/初始化二维数组的两种方式.png) 93 | 94 | ### 2.2 其他多维数组 95 | 96 | 声明一个三维数组: 97 | 98 | ```c 99 | int box[10][20][30]; // 10个二维数组(每个二维数组都是20行30列) 100 | ``` 101 | 102 | 对于多维数组的处理,使用`多重循环`控制来处理。 103 | 104 | ## 3.数组和指针 105 | 106 | 指针以`符号`形式使用地址。 107 | 108 | `数组表示法`也是`变相使用指针`。 109 | 110 | - 指针的值是所指向对象的地址。地址的表示方式依赖于计算机内部的硬件。 111 | 112 | - 在指针前面使用运算符可以得到该指针所指向对象的值。 113 | 114 | - 指针加1,指针的值递增它所指向`类型的大小`(以字节为单位)。 115 | 116 | ## 4. 函数、数组和指针 117 | 118 | 使用数组表示法处理数组的函数,也就是使用指针作为参数。 119 | 120 | 数组名是数组首元素的地址。 121 | > 既可以用`指针`表示`数组名`,也可以使用`数组名`表示`指针`。 122 | 123 | 124 | 125 | ## 5. 指针操作 126 | ```c 127 | #include 128 | int main(void) 129 | { 130 | int urn[5] = {10,20,30,40,50}; 131 | int *ptr1, *ptr2, *ptr3; 132 | 133 | ptr1 = urn; // 指向数组的首个元素,也就是urn数组的首地址 134 | ptr2 = &urn[2];// 一个地址赋给指针,也就是urn数组的第三个元素(urn[2])的地址 135 | 136 | printf("指针的值 ;间接引用指针 ; 指针的地址:\n"); 137 | // 解引用:*运算符给出指针指向地址上储存的值。注意:不要解引用未初始化的指针。 138 | // 取址:指针也有主机的地址和值。&运算符给出指针本身的地址。 139 | printf("ptr1 = %p ; *ptr1 = %d ; &ptr1 = %p \n", ptr1,*ptr1,&ptr1); 140 | 141 | // 指针的加法:整数都会与指针所指向类型的大小(以字节为单位)相乘,然后结果与初始地址相加。 142 | ptr3 = ptr1 + 4; 143 | printf("\n 指针加上一个整数 :\n"); 144 | printf("ptr1 + 4 = %p ; *(ptr1 + 4) = %d \n",ptr1+4,*(ptr1+4)); 145 | 146 | ptr1++; // 递增指针:指向数组元素的指针可以让指针移动到数组的下一个元素。 147 | printf("\n ptr1++的值是:\n"); 148 | printf("ptr1 = %p ; *ptr1 = %d ; &pt1 = %p \n",ptr1,*ptr1,&ptr1); 149 | 150 | ptr2--; // 递减指针 151 | printf("--ptr2的值是:\n"); 152 | printf("ptr2 = %p ; *ptr2 = %d ; &pt2 = %p\n",ptr2,*ptr2,&ptr2); 153 | 154 | --ptr1; // 恢复为初始值 155 | ++ptr2; // 恢复为初始值 156 | 157 | printf("\n 恢复为初始值的指针为:\n"); 158 | printf("ptr1 = %p ; ptr2 = %p\n",ptr1,ptr2); 159 | 160 | // 一个指针减去另一个指针 161 | printf("\n 一个指针减去另一个指针:\n"); 162 | printf("ptr2 = %p ; ptr1 = %p ; ptr1 - ptr2 = %td \n ",ptr1,ptr2,ptr1 -ptr2); 163 | 164 | // 一个指针减去一个整数:指针必须是第一个运算对象,整数是第二个运算对象。整数乘以指针指向类型的大小(以字节为单位),然后初始地址减去乘积。 165 | printf("\n 一个指针减去一个整数:\n"); 166 | printf("ptr3 = %p ; ptr3 - 2 = %p \n ",ptr3,ptr3-2); 167 | 168 | return 0; 169 | } 170 | ``` 171 | ## 6. 保护数组中的数据 172 | 地址传递往往会导致会出现一些问题,原始数据在传递过程中会被修改。 173 | 174 | #define 可以创建类似const功能的符号常量,但由于安全性角度考虑,优先推荐使用 `const`定义常量。 175 | 176 | 指针const的指针不能用于改变值。 177 | 178 | 对函数的形参使用const不仅能保护数据,还能让函数处理const数组。 179 | 180 | ## 7. 变长数组(VLA) 181 | ⚠️注意:变长数组不能改变大小 182 | > 变长数组中的“变” 不是指可以修改已创建数组的大小。一旦创建了变长数组,它的大小则保持不变。此处的“变” 指的是:在创建数组时,可以使用变量指定数组的维度。 183 | 184 | C99/C11标准规定,可以省略原型中的形参名,可以使用`星号`来代替省略的`维度`。 185 | 186 | ```C 187 | int num2d(int, int , int arr[*][*]); // arr是一个变长数组(VLA),省略了维度形参名。 188 | ``` 189 | 190 | `变长数组名`实际上也是`指针`。 191 | 192 | 变长数组允许`动态内存分配`,说明可以在程序运行时`指定数组的大小`。 193 | -------------------------------------------------------------------------------- /Chapter10/img/初始化二维数组的两种方式.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter10/img/初始化二维数组的两种方式.png -------------------------------------------------------------------------------- /Chapter10/复习题.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter10/复习题.md -------------------------------------------------------------------------------- /Chapter10/编程题.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /Chapter11/Codes/README.md: -------------------------------------------------------------------------------- 1 | ## 第十一章 编程练习代码 2 | -------------------------------------------------------------------------------- /Chapter11/Codes/练习题01.c: -------------------------------------------------------------------------------- 1 | /******************************************************************** 2 | * 3 | * 日期 : 2020 - 07 - 29 4 | * 作者 : Soler HO 5 | * 6 | * 书籍 : C Primer Plus 6 edition 7 | * 8 | * 第 11 章 9 | * 10 | * 题目01 : 设计并测试一个函数,从输入中获取下n个字符(包括空白、制表符、换行符), 11 | * 把结果储存在一个数组里,它的地址被传递作为一个参数。 12 | * 13 | ********************************************************************/ 14 | 15 | #include 16 | #define LEN 10 /*输入字符长度*/ 17 | 18 | char *getnchar(char *str,int n); 19 | 20 | int main(void) 21 | { 22 | char input[LEN]; /*存储字符串的数组*/ 23 | char *check; 24 | printf("请输入10个以内的字符:\n"); // 第二种:puts("请输入10个以内的字符:\n") 25 | check = getnchar(input,LEN-1); 26 | if(check==NULL) /*返回是空指针*/ 27 | puts("输入失败"); 28 | else 29 | puts(input); 30 | 31 | puts("结束了!Bye!\n"); 32 | 33 | return 0; 34 | } 35 | 36 | char *getnchar(char *str,int n) 37 | { 38 | int i ; 39 | char ch; 40 | 41 | for(i = 0;i 最简单的方式:在声明显式指明数组的大小。 45 | 46 | ### 2.1 `gets()` 函数 47 | `scanf()`和转换说明`%s`只能读取`一个单词`。 48 | 49 | `gets()`读取一整行的输入,直至遇到换行符,然后丢弃换行符,存储其余字符,并在后面加一个空字符,然成为一个C字符串 50 | 51 | `gets()`函数经常和`puts()`函数`配对`使用。 52 | 53 | `puts()`函数显示字符串,并在`末尾添加换行符`。 54 | ### 2.2 `fgets()` 函数 55 | 56 | `fgets()`函数 ------- 专门处理文件输入 57 | 58 | `fgets()`函数经常和`fputs()`函数配对使用,除非该函数不在字符串末尾添加换行符。 59 | 60 | gets()和fgets()的区别 61 | 62 | `fgets()`遇到`换行符`会`存储在字符串`中。gets()函数则会`丢弃换行符`。 63 | 64 | fgets() 函数的语法格式: 65 | 66 | ```c 67 | char *fgets(char *str, int n, FILE *stream); 68 | ``` 69 | > - str 指向一个字符数组的指针,存储要读取的字符串。 70 | > - n 要读取的最大字符数(包括空字符),通常是是以str传递的数组长度。 71 | > - stream 指向FILE对象的指针,标识要从中读取字符的流。 72 | 73 | `fgets()`中第二个参数是限制读入的字符来防止溢出问题,第三个参数指明要读入的文件,如果从键盘读入,则使用stdin。 74 | ### 2.3 `scanf()` 函数 75 | 使用`%`转换说明符,`scanf`函数如果内容过长,会造成`数据溢出`。 76 | 77 | 在`%s`转换说明中使用`字段宽度`可以`防止溢出`。 78 | 79 | ## 3. 字符串输出 80 | C语言有3个标准库函数用于打印字符串:`puts()`、`fputs()`、`printf()` 。 81 | ### 3.1 `puts()`函数和`fputs()`函数 82 | fputs()函数是puts()针对文件定制的版本,两者的区别如下: 83 | - `fputs()`函数的第2个参数指明要写入数据的文件。要显示文件则需要使用stdio.h库中的stdout作为参数。 84 | - `fputs()`不会输出的末尾添加换行符。 85 | - 输入问题:puts()`丢弃`输入中的换行符,而`fputs()保留`。 86 | - 输出问题:puts()`添加`换行符,而`fputs()不添加`。 87 | 88 | ### 3.2 `printf()`函数 89 | 细节请参考 「[chapter04](../Chapter04/README.md)」 90 | 91 | ## 4. 字符串函数 92 | C语言库中提供多个处理字符串的函数,全部都放在`string.h头文件`中。 93 | 94 | 最常用的函数: 95 | - `strlen()` 统计字符串的长度 96 | - `strcat()` 拼接字符串 97 | - `strncat()` 拼接两个字符串,然后检查第一个 98 | - `strcmp()` 字符串的比较 99 | - `strncmp()` 比较字符串中的字符 100 | - `strcpy()` 拷贝字符串,相当于字符串赋值运算符 101 | - `strncpy()` 102 | 103 | ### 4.1 字符串拼接的`strcat()`函数 104 | 105 | `strcat()`函数用于`拼接`字符串,接受两个字符串作为参数。 106 | 107 | 把第2个字符串`附加`在第1个字符串末尾,并把拼接后形成的新字符串作为第1个字符串,第2个则保持不变。 108 | 109 | `strcat()`函数的类型是:char *(即,指向char的指针) 110 | 111 | `strcat()`函数的缺点:无法检查第1个数组是否可以容纳第2个字符串数组。 112 | 113 | ### 4.2 `strncat()`函数 114 | > 字符串的拼接,并检查第一个数组的大小。 115 | 116 | 目的:为了弥补strcat()函数不能检查第一个数组大小的缺陷。防止字符溢出。 117 | 118 | ### 4.3 比较`strcmp()`函数 119 | 120 | `strcmp()`函数比较的是字符串的`内容`,而不是字符串的地址。也不是字符。 121 | 122 | ### 4.4 `strcpy()` 函数 123 | 利用`strcpy()`函数拷贝整个字符串 124 | 125 | 调用`strcpy()`把整个字符串从`临时数组拷贝`到目标数组中。 126 | 127 | `strcpy()`函数相当于字符串`赋值`运算符。 128 | 129 | `strcpy()`函数拷贝的是`字符串的地址`而不是字符串本身。 130 | 131 | `strcpy()`函数的两个有用属性 132 | 133 | - 返回的类型是`char *`,返回的事第一个参数的值,也就是一个字符的地址。 134 | - 第1个参数不必指向数组的开始。该属性一般用于拷贝数组的一部分。 135 | 136 | ### 4.5 `strncpy()` 函数 137 | 注意点:`strncpy()`函数会`检查目标空间`是否能够容纳字符串的副本。 138 | 139 | 调用`strncpy()`把整个字符串从临时数组拷贝到目标数组中。 140 | 141 | `strncpy()`函数相当于字符串赋值运算符。 142 | 143 | `strncpy()`函数拷贝的是字符串的地址而不是字符串本身。 144 | 145 | ### 4.6 `sprintf()` 函数 146 | 把数据写入字符串但不打印在显示器上的`sprintf()`函数 147 | 148 | `sprintf()`函数和printf()函数类似,但是`sprintf()`函数是把数据写入字符串,但是不打印在显示器上。 149 | 150 | `sprintf()`函数的声明是在`stdio.h`中,而不是在string.h中。 151 | 152 | `sprintf()`函数可以把`多个元素组合`成一个字符串。 153 | 154 | `sprintf()`函数的第一个参数是:目标字符串的地址,其它参数和printf()函数相同。 155 | 156 | printf()函数的格式:格式字符串和待写入项的列表。 157 | 158 | sprintf()函数获取输入,并将其格式化标准形式,然后把格式化后的字符串存储。 159 | 160 | ## 5. 字符串排序例子 161 | ```c 162 | #include 163 | #include 164 | #define SIZE 81 165 | #define LIM 20 166 | #define HALT " " 167 | void stsrt(char *strings [],int num);/*字符串排序函数*/ 168 | char *s_gets(char *st,int n); 169 | 170 | int main(void) 171 | { 172 | char input[LIM][SIZE]; /*存储输入的数组*/ 173 | char *ptstr[LIM]; /*内含指针变量的数组*/ 174 | int ct = 0; /*输入计数*/ 175 | int k; /*输出计数*/ 176 | 177 | printf("输入 %d 行内容,并进行排序!\n",LIM); 178 | printf("请在新的一行按任意键即可停止!\n"); 179 | while (ct0) 201 | { 202 | temp =strings[top]; 203 | strings[top] = strings[seek]; 204 | strings[seek] = temp; 205 | } 206 | } 207 | 208 | char *s_gets(char *st,int n) 209 | { 210 | char * ret_val; 211 | int i; 212 | 213 | ret_val = fgets(st,n,stdin); 214 | if(ret_val) 215 | { 216 | while (st[i]!='\n'&&st[i]!='\0') 217 | i++; 218 | if(st[i] == '\n') 219 | st[i]='\0'; 220 | else 221 | while (getchar()!='\n') 222 | continue; 223 | } 224 | return ret_val; 225 | } 226 | ``` 227 | ## 6. 命令行参数 228 | 带命令行参数的 `main()`函数 229 | 230 | 对于C编译器来说,允许main()`没有参数`或者是`两个参数`。当main()函数有两个参数时, 231 | 232 | - 第一个参数:命令行中的字符串的数量。int类型的`argc`(argument count(表示参数计数))。 233 | 系统用空格表示一个字符串的结束和下一个字符串的开始。 234 | 235 | - 第二个参数:指向指针的指针叫做`argv`(表示参数值【argument value】) 236 | 237 | ```c 238 | #include 239 | 240 | /*命令行字符串存储,并把每个字符串的地址存储在指针数组中,数组的地址被存储在第二个参数中*/ 241 | int main(int argc,char *argv []) 242 | { 243 | int count; 244 | 245 | printf("这个命令行有 %d 个参数:",argc-1); 246 | for(count = 1;count被储存的每个值都占用一定的物理内存(这一块的内存称为对象) 9 | > 10 | >对象可储存一个或多个值。一个对象可能并未储存实际的值,但在储存适当的值时具有一定相应的大小。 11 | 12 | 软件角度 13 | >需要一种方法访问对象,可通过声明变量来进行。 14 | 15 | #### 1.1 作用域 16 | 作用域描述程序中可访问标识符区域。 17 | 18 | **块**:一对花括号括起来的代码区域。 19 | 20 | C变量的作用域有 `块作用域、函数作用域、函数原型作用域或文件作用域`。 21 | >**块作用域**:声明在语句块中的变量,在语句块中可见可用。常见的块语句:`for循环、while循环、do while循环和if语句`。 22 | > 23 | >**函数作用域**:声明在函数体内,函数体内可用。仅用于 `goto语句` 的标签。 24 | > 25 | >**函数原型作用域**:形参定义到原型声明结束,仅仅在函数原型的`括号() `里面可用。 26 | > 27 | >**文件作用域**:变量定义在函数的外面,从定义到文件末尾可见。 28 | 29 | #### 1.2 链接 30 | C语言中变量有3种链接属性:***外部链接***、***内部链接*** 和 ***无链接***。 31 | >外部链接可在多文件程序中使用,内部链接只能在当前单元使用。 32 | 33 | 无链接变量:块作用域、函数作用域、函数原型作用域 34 | 35 | 文件作用域中的变量可以是外部链接或内部链接。 36 | 37 | 存储类别说明符static 可区分外部链接和内部链接。 38 | ```cpp 39 | int a = 6; // 外部链接 40 | static int b = 5;// 内部链接 41 | ``` 42 | 43 | #### 1.3 存储期 44 | 作用域和链接描述了标识符的可见性。 45 | 46 | ![](img/5种存储类别.png) 47 | 48 | 存储期 描述了 标识符访问的对象的生存周期。 49 | 50 | C语言中对象的4种存储期:***`静态存储期、线程存储期、自动存储期、动态分配存储期`***。 51 | >**静态存储期**:在程序执行期间会一直存在。文件作用域变量具有静态存储期。 52 | > 53 | >**线程存储期**:用于并发程序设计,程序执行可分为多个线程。具有线程存储期的对象,从被声明时到线程结束一直存在。 54 | > 55 | >**自动存储期**:自动把变量占用的内存视为一个可重复使用的工作区或暂存区。⚠️注意点:变长数组的存储期是从声明处到块的末尾。常见的情况:局部变量 56 | 57 | 58 | #### 1.4 自动变量 59 | 声明在块或函数头中的任何变量都属于自动存储类别(默认情况)。 60 | 61 | 可以使用 `关键字auto` 显式定义并使用自动变量。 62 | >auto是存储类别说明符,为了考虑C/C++程序的兼容性问题,一般不建议使用。 63 | 64 | **没有花括号的块** 65 | 66 | >不是所有的代码块的都有花括号的,例如if和for语句。 67 | 68 | **自动变量的初始化** 69 | 70 | >自动变量不会初始化,除非使用显式初始化。 71 | ```cpp 72 | int a; //仅仅分配内存空间,值未知(不一定是默认值0,可能是非常量表达式初始化自动变量) 73 | int b = 7; //变量被初始化,分配了内存空间和初始值 74 | ``` 75 | 76 | #### 1.5 寄存器变量 77 | 变量通常是存储在计算机内存中。 78 | 79 | 目的:加快访问和处理变量的速度(加快计算速度)。 80 | 81 | 寄存器变量存储在寄存器中而非内存中,故无法获取寄存器变量的地址。 82 | 83 | 84 | >使用存储器类别说明符register便可声明寄存器变量。 85 | >```cpp 86 | >void macho(register int n) 87 | >``` 88 | >在函数头中使用 `关键字regsiter` ,便可请求形参是寄存器变量。 89 | 90 | 91 | #### 1.6 块作用域的静态变量 92 | 静态:变量在内存中`地址固定`,不是值不动。 93 | 94 | 具有文件作用域的自动变量必须具有静态存储期。 95 | 96 | 也称为`内部静态存储类别`(函数内部,而非内部链接)。 97 | 98 | 99 | #### 1.7 外部链接的静态变量 100 | 外部链接的静态变量具有文件作用域、外部链接和静态存储期。也称为 `外部存储类别`。 101 | 102 | 变量称为外部变量(变量的定义性声明放在函数的外部)。 103 | 104 | >为了说明外部变量,一般在函数中使用 `关键字 extern` 说明。 105 | > 106 | >如果代码文件使用的外部变量定义在另一个代码文件中,则必须使用 extern 在该文件中声明该变量。 107 | 108 | 外部变量域自动变量类似,可被显式初始化。 109 | 110 | >区别:外部变量如果未初始化,会被自动初始化为0。 111 | 112 | ⚠️注意:外部变量只能初始化一次,且必须在定义该变量时进行。 113 | 114 | #### 1.8 内部链接的静态变量 115 | 116 | 只能用于同一个文件中的函数。 117 | 118 | 用存储类别说明符 `static` 定义变量具有静态存储期、文件作用域和内部链接。 119 | ```cpp 120 | static int val = 1; // 静态变量,内部链接 121 | ``` 122 | 123 | 使用存储类别说明符 `extern` 在函数中重复声明任何具有文件作用域的变量。 124 | 125 | > 该类型的声明`不会改变其链接属性`。 126 | 127 | #### 1.10 存储类别说明符 128 | C语言中6个存储类别说明符关键字: 129 | - auto 130 | >自动存储期,只能用于块作用域的变量声明中。 131 | > 132 | > 主要为了明确表达要使用域外部变量同名的局部变量的意图。 133 | - register 134 | >用于块作用域的变量。归类为寄存器。 135 | - static 136 | >说明符创建的对象具有静态存储期,载入程序时创建对象,当程序结束时对象消失。 137 | - extern 138 | - _Thread_local 139 | - typedef 140 | 141 | #### 1.11 存储类别和函数 142 | 函数也分为`外部函数`或`静态函数`。 143 | > 144 | >- `外部函数`可被其他文件的函数访问 145 | >- `静态函数`只能用于其定义所在的文件。 146 | 147 | 一般使用 `extern关键字` 声明定义在其他文件中的函数。 148 | >目的:表明在当前文件中使用的函数被定义在别处(该文件的外部)。 149 | > 150 | > 除非使用 `static关键字`,否则一般函数声明都默认为 `extern`。 151 | 152 | #### 1.12 存储类别的选择 153 | 保护性程序设计的黄金法则:`“按需知道” 原则`。 154 | >尽量在函数内部解决该函数的任务,只共享某些需要共享的变量。 155 | 156 | ### 2. 随机数函数和静态变量 157 | 内部链接的静态变量的函数:`随机数函数`。 158 | 159 | ANSI C库提供 `rand()` 函数生成随机数。 160 | 161 | ### 3. 分配内存:`malloc()` 和 `free()` 162 | `malloc()` 和 `free()` 函数原型都在 `stdlib.h` 头文件中。 163 | #### 3.1 `malloc()` 函数 164 | 165 | `malloc()`函数:可在程序运行时分配更多的内存。 166 | > 接受一个参数:**所需的内存字节数**。 167 | > 168 | >找到合适的空闲内存块,⚠️注意:内存是匿名(分配内存,但不会为其赋名)。 169 | > 170 | >返回动态分配内存块的首字节地址。 171 | 172 | `malloc()`函数 可用于返回指向`数组`的指针、指向`结构`的指针等。 173 | 174 | 声明一个指针,调用 malloc(),将其返回值赋给指针,使用指针访问数组的元素(指针是静态或自动)。 175 | 176 | 👉**Notes**:`malloc()` 必须和 `free()` 函数配套使用。***malloc申请的内存块,必须要用free() 去释放,否则会造成内存泄漏***。 177 | >`free()` 函数的参数是 `malloc()`返回的`地址`。 178 | 179 | #### 3.2 `free()` 函数 180 | 181 | 静态内存的数量在编译时固定,在程序运行期间不会改变。 182 | 183 | 自动变量使用的内存数量在程序运行期间自动增加或减少。 184 | 185 | 动态分配的内存数量只会增加,除非使用 `free()` 函数来释放。 186 | 187 | ~~不能用`free() `释放通过其它方式分配的内存。~~ 188 | 189 | `free()` 函数位于程序的末尾,释放`malloc() `函数分配的内存(参数指向的内存块)。 190 | >否则造成内存泄漏。 191 | 192 | #### 3.3 `calloc()` 函数 193 | `calloc()` 函数 可进行内存分配。 194 | 195 | 语法格式: 196 | ```cpp 197 | void *calloc(size_t nitems, size_t size) 198 | ``` 199 | >接受两个无符号整数作为参数(ANSI规定是size_t类型) 200 | > 201 | >nitems -- 所需的存储单元个数。 202 | > 203 | >size -- 元素的大小(以字节为单位) 204 | 205 | calloc的特性:把块中的所有位置都设置为0(注意:在某些硬件系统中,不是所有的位都设置为0来表示浮点值0)。 206 | 207 | `free()` 函数 可用于释放 `calloc()` 分配的内存。 208 | 209 | #### 3.4 动态内存分配和变长数组 210 | 变长数组(VLA):自动存储类型。 211 | >程序在离开变长数组定义所在的块时,变长数组占用的内存空间会被释放。~~不必使用free()~~。 212 | 213 | 未使用的内存块分散在已使用的内存块之间。 214 | 215 | 使用动态内存同时比使用栈内存慢。 216 | 217 | 218 | ### 4. ANSI C类型限定符 219 | >const关键字(C90特性) ——————> 恒心常性 220 | > 221 | >volatile关键字(C90特性)——————> 易变性 222 | > 223 | >restrict关键字(C99特性)——————> 用于提高编译器优化 224 | > 225 | >_Atomic(C11特性)——————> 支持并发程序设计 226 | 227 | 228 | 以 `const关键字` 声明的对象,其~~值不能通过赋值或递增、递减来修改~~。模式为`只读`。 229 | 230 | #### 4.1 在指针和形参声明中使用const 231 | - const放在*的左侧任意位置,限定`指针指向的数据`不能改变。 232 | ```cpp 233 | const floar *pf; //pf指向的值不能被改变 234 | ``` 235 | - const放在*的右侧,限定`指针本身`不能改变。 236 | ```cpp 237 | float * const pt; //pt本身的值不能改变 238 | ``` 239 | #### 4.2 对全局数据使用const 240 | const来对全局变量进行限制,避免更改数据。 241 | 242 | 一般在文件间共享const数据,两个策略: 243 | - 遵循外部变量的常用规则(在一个文件中使用定义式声明,在其他文件中使用引用式声明(用extern关键字))。 244 | - 把 const 变量放在一个头文件中,然后在其他文件中包含该头文件。 245 | 246 | #### 4.3 volatile类型限定符(C90) 247 | 告知计算机,代理(而不是变量所在的程序)可以改变该变量的值。 248 | 249 | 被用于硬件地址以及其他程序或同时运行的线程中共享数据。 250 | 251 | >volatile变量对编译器的优化有帮助。 252 | 253 | #### 4.4 restrict关键字(C99) 254 | 允许编译器优化某部分代码更好地支持计算。 255 | ⚠️注意:**只能用于指针**。表明指针是访问数据对象的唯一且初始的方式。 256 | 257 | 使用`restrict关键字`,编译器可以选择捷径优化计算。 258 | 259 | >可用于函数形参中的指针。 260 | 261 | 262 | #### 4.5 _Atomic类型限定符(C11) 263 | 264 | 并发程序设计把程序执行分成可以同时执行的多个线程。 265 | 266 | 可选的头文件:`stdatomic.h` 和 `threads.h`。 267 | 268 | 通过各种宏函数来访问原子类型。 269 | 270 | 当一个线程对一个原子类型的对象执行原子操作时,其他线程不能访问该对象。 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | -------------------------------------------------------------------------------- /Chapter12/img/5种存储类别.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter12/img/5种存储类别.png -------------------------------------------------------------------------------- /Chapter13/README.md: -------------------------------------------------------------------------------- 1 | ## 第十三章 文件输入/输出 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | 5 | 文件用于 `存储程序、文档、数据、书信、表格、照片、视频` 等。 6 | ### 1. 与文件进行通信 7 | 文件:通常是在磁盘或固态硬盘上的一段已命名的存储区。 8 | 9 | C语言提供两种文件模式:`文本模式` 和 `二进制模式`。 10 | 11 | **所有文件的内容都以`二进制形式(0或1)`存储**。 12 | 13 | ### 2. 标准I/O 14 | C程序会自动打开3个文件:**`标准输入(standard input)`**、**`标准输出(standard ouput)`**、**`标准错误输出(standard error output)`**。 15 | 16 | |标准文件|文件指针|通常使用的设备| 17 | |:--:|:--:|:--:| 18 | |标准输入|stdin|键盘| 19 | |标准输出|stdout|显示器| 20 | |标准错误|stderr|显示器| 21 | 22 | 标准I/O的好处: 23 | - 可移植 24 | - 有专门的函数简化了处理不同I/O的问题 25 | - 输入和输出都是缓冲(缓冲极大提高数据传输速率)。 26 | 27 | 在打开文件时,程序一定要判断文件是否打开成功。 28 | ```cpp 29 | if(fopen==NULL){ 30 | ... 31 | exit(EXIT_FAILURE); 32 | } 33 | ``` 34 | > 一旦打开失败,直接会终止后续操作。 35 | 36 | `exit() 函数` 关闭所有打开的文件并结束程序。 37 | 38 | 在stdlib.h头文件中: 39 | >标准要求 `0 `或 宏 `EXIT_SUCCESS`:表明成功结束程序。 40 | > 41 | >宏 `EXIT_FAILURE`:表明结束程序失败。 42 | 43 | #### 2.1 `fopen()` 和 `fclose()` 函数 44 | 都声明在 `stdlib.h` 头文件中。 45 | 46 | |函数|函数原型|语法格式|功能|备注| 47 | |:--:|:--|:--|:--|:--| 48 | |`fopen()`|`FILE *fopen(const char *filename,const char * mode);`|`FILE *fp = fopen("filename",mode)`|打开文件|返回一个文件指针:FILE *fp 指向一个记录文件信息的数据结构
例:`fp = fopen("hello_c.txt","r");`| 49 | |`fclose()`|`int fclose(FILE * stream);`|`fclose(fp)`|关闭文件|关闭成功返回0,失败返回EOF(-1),存储空间不足或者被移除都会出现I/O错误,都会导致失败。| 50 | >文件指针的类型是指向FILE的指针,FILE是一个定义在 `stdlib.h` 中的`派生类型`。 51 | 52 | mode的内容参考: 53 | ![](img/fopen中的模式字符串.png) 54 | 55 | 56 | #### 2.2 `getc()` 和 `putc()` 函数 57 | 与 `getchar()` 和 `putchar()` 函数类似。 58 | 59 | 区别: 60 | >需要告知 `getc()` 和 `putc()` 函数 使用哪一个文件。 61 | 62 | |函数|函数原型|语法格式|功能|备注| 63 | |:--:|:--|:--|:--|:--| 64 | |`gets()`|`int getc(FILE *stream)`|`ch = getc(fp)`|从fp指定文件中获取一个字符,读到文件结尾返回EOF|`getc(stdin) == getchar(ch);`| 65 | |`putc()`|`int putc(int char,FILE *stream)`|`putc(ch,fp)`|把ch放入fp指向文件|`puts(ch,stdout) == putchar(ch);`| 66 | 67 | #### 2.3 一个简单的文件压缩程序 68 | 例子:把一个文件中选定的数据拷贝到另一个文件中。 69 | ```cpp 70 | //reducto.c 把文件压缩成原来的1/3 71 | #define _CRT_SECURE_NO_WARNINGS 72 | #include 73 | #include //提供exi()原型 74 | #include //提供strcpy()、strcat()原型 75 | #define LEN 40 76 | int main(int argc, char * argv[]) 77 | { 78 | FILE * in, *out; //声明两个指向FILE的指针 79 | int ch; 80 | char name[LEN]; //存储输出文件名 81 | int count = 0; 82 | 83 | if (argc < 2)//检查命令行参数 84 | { 85 | fprintf(stderr, "Usage: %s filename\n", argv[0]); 86 | exit(EXIT_FAILURE); //表明程序失败退出 87 | } 88 | //设置输入,设置mode为可读 89 | if ((in = fopen(argv[1], "r")) == NULL) 90 | { 91 | fprintf(stderr, "I couldn't open the file \"%s\"\n ", argv[1]); 92 | exit(EXIT_FAILURE); 93 | } 94 | //设置输出 95 | strncpy(name, argv[1], LEN - 5); // 拷贝文件名 96 | name[LEN - 5] = '\0'; 97 | strcat(name, ".red"); 98 | if ((out = fopen(name, "w")) == NULL) //设置可写方式打开file 99 | { 100 | fprintf(stderr, "Can't create output file.\n"); 101 | exit(3); 102 | } 103 | //拷贝数据 104 | while ( (ch=getc(in))!=EOF) //通过使用EOF来告知程序已经读取到文件结尾,从而结束程序。 105 | { 106 | if (count++ % 3 == 0) 107 | putc(ch, out); 108 | } 109 | if (fclose(in) != 0 || fclose(out) != 0) 110 | fprintf(stderr, "Error in closing files.\n"); 111 | 112 | return 0; 113 | } 114 | ``` 115 | >`fprintf()`和 `printf()` 类似,⚠️注意点:`fptrintf()` 第一个参数必须是一个`文件指针`。 116 | 117 | #### 2.4 标准I/O的原理 118 | 步骤: 119 | - 调用`fopen()`打开文件。 120 | - `fopen()`不仅打开一个文件,还创建了一个缓冲区(在读写模式下会创建两个缓冲区)以及一个包含文件和缓冲区数据的结构。 121 | - fopen()返回一个指向该结构的指针,以便其他函数知道该如何找到该结构。 122 | - 结构中通常包含一个指定流中当前未知的文件位置指示器。还包括错误和文件结尾的指示器、一个指向缓冲区开始处的指针,一个文件标识符和一个计数(统计实际拷贝进缓冲区的字节数)。 123 | 124 | - 调用一个定义在 `stdio.h` 中的输入函数,如`fscanf()、getc()或fgets()`。 125 | - 调用函数时,文件中的数据块会被拷贝到缓冲区中,最初调用函数,除了填充缓冲区外,还要设置指针变量所指向的结构中的值。 126 | - 在初始化结构和缓冲区后,输入函数按要求从缓冲区中读取数据,在读取数据时,文件位置指示器被设置为指向刚读取字符的下一个字符。 127 | - 当输入函数发现已读完缓冲区中的所有字符时,会请求把下一个缓冲大小的数据块从文件拷贝到该缓冲区中。 128 | - 输出函数以类似的方式把数据写入缓冲区。当缓冲区被填满时,数据将被拷贝至文件中。 129 | 130 | ### 3. 文件I/O:`fprintf()、fscanf()、fgets()、fputs()` 131 | 132 | #### 3.1 `fprintf()` 和 `fscanf()` 函数 133 | `fprintf()` 和 `fscanf()`函数的工作方式与 `printf()` 和 `fscanf()`函数的类似。 134 | 135 | 👉小区别: 136 | >`fprintf()` 和 `fscanf()` 函数的第一个参数`必须指定待处理的文件`。 137 | 138 | 例子: 139 | ```cpp 140 | /*addaword.c --fprintf() fscanf() rewind()*/ 141 | #define _CRT_SECURE_NO_WARNINGS 142 | #include 143 | #include 144 | #include 145 | #define MAX 41 146 | 147 | int main(void) 148 | { 149 | FILE *fp; 150 | char words[MAX]; 151 | if ((fp = fopen("wordy", "a+")) == NULL) //更新(读写)模式打开文件,只允许在文件末尾添加内容 152 | { 153 | fprintf(stdout, "Can't open \"wordy\" file.\n"); 154 | exit(EXIT_FAILURE); 155 | } 156 | puts("Enter words to add to the file; press the #"); 157 | puts("key at the beginning of a line to terminate."); 158 | while ((fscanf(stdin, "%40s", words) == 1) && (words[0] != '#')) 159 | fprintf(fp, "%s\n", words); 160 | puts("File contents:"); 161 | /** 关于 rewind() 函数的介绍说明 162 | * 163 | * rewind()函数 在头文件 stdio.h 中 164 | * 用于将文件指针重新指向文件的开头,同时清除和文件流相关的错误和EOF标记。 165 | * 相当于调用 fseek()函数 166 | * 函数原型:void rewind(FILE *stream); 167 | * 168 | */ 169 | rewind(fp);//回到文件开始处。 170 | while (fscanf(fp, "%s", words) == 1) 171 | puts(words); 172 | puts("Done!"); 173 | if (fclose(fp) != 0) 174 | fprintf(stderr, "Error closing file\n"); 175 | 176 | return 0; 177 | } 178 | ``` 179 | 180 | #### 3.2 `fgets()` 和 `fputs()`函数 181 | 移步 👉 【[第11章]()】 182 | 183 | 184 | ### 4. 随机访问 185 | 186 | #### 4.1 `fseek()` 和 `ftell()` 函数 187 | 188 | |函数名|函数原型|功能|参数|返回值| 189 | |:--|:--|:--|:--|:--| 190 | |`fseek()`|`int fseek(FILE *stream, long int offset, int whence);`|重定位流上的文件指针|第一个参数stream为`文件指针`
第二个参数offset为`偏移量`,正数表示正向偏移,负数表示负向偏移
第三个参数是`模式`,确定文件起始点,几种明示常量为:`SEEK_SET(文件开头)、 SEEK_CUR(当前位置) 或 SEEK_END(文件末尾)`|如果成功,则该函数返回零,否则返回非零值。| 191 | |`ftell`|`long ftell(FILE *stream);`|得到文件位置指针当前位置相对于文件首的偏移字节数。|`FILE *stream`返回指针的文件流|成功----返回当前文件指针的位置
出错----返回-1L,是长整数的-1值。| 192 | 193 | `fseek`例子: 194 | ```cpp 195 | fseek(fp,0L,SEEK_SET); //定位至文件开始处 196 | fseek(fp,10L,SEEK_SET); //定位至文件中的第10个字节 197 | fseek(fp,2L,SEEK_CUR); //从文件当前位置前移2个字节 198 | fseek(fp,0L,SEEK_END); // 定位至文件结尾 199 | fseek(fp,-10L,SEEK_END); // 从文件结尾处回退10个 200 | ``` 201 | 202 | `ftell`例子: 203 | ```cpp 204 | #include 205 | 206 | int main() 207 | { 208 | FILE *stream; 209 | int len; 210 | 211 | stream = fopen("file.txt","r"); // 只读方式打开 212 | if(stream == NULL) 213 | { 214 | perror("打开文件错误"); 215 | return(-1); 216 | } 217 | fseek(stream,0,SEEK_END); 218 | len = ftell(stream); //返回类型为long,把file.txt文件开始处到文件结尾的字节数赋给len 219 | fclose(stream); 220 | 221 | printf("file.txt的总大小为 = %d 字节\n",len); 222 | 223 | return(0); 224 | } 225 | ``` 226 | 假设文件file.txt中的内容为 227 | ```txt 228 | www.github.com/solerho 229 | ``` 230 | 使用gcc运行程序后结果如下: 231 | ```shell 232 | [root@centos8 examples]# gcc ftell.c 233 | [root@centos8 examples]# ls 234 | a.out file.txt ftell.c 235 | [root@centos8 examples]# ./a.out 236 | file.txt的总大小为 = 23 字节 237 | ``` 238 | 239 | #### 4.2 二进制模式和文本模式 240 | 不同之处: 241 | - UNIX 242 | - UNIX只有一种文件格式,所以不需要进行特殊的转换。 243 | - UNIX使用 `\n` 表示换行符。 244 | >以`文本模式`打开时,C能识别`Ctrl-Z`作为`文件结尾标记的字符`。 245 | > 246 | >以`二进制模式`打开相同的文件时,`Ctrl-Z` 字符被看作是`文件中的一个字符`,而实际的文件结尾符在该字符后面。 247 | - MS-DOS 248 | - MS-DOS编译器都用 `Ctrl + Z` 标记文件。 249 | - MS-DOS用 `\r\n` 组合表示文件换行符。 250 | >以`文本模式`打开相同的文件,C程序把`\r\n`看成`\n`,但是,以二进制模式打开该文件时,程序能看见这两个字符。 251 | #### 4.3 可移植性 252 | C模型无法做到与Unix模型一致,因为历史上C就是因为Unix而生,但是其他系统不能保证与Unix模型一致。因此,ANSI对这些函数降低了要求,下面是一些限制: 253 | 254 | - 在`二进制模式`中,实现不必支持`SEEK_END模式`。 255 | - 在`文本模式`中,只有以下调用能保证其相应的行为。 256 | 257 | >|函数调用|效果| 258 | >|:--|:--| 259 | >|`fseek(file,0L,SEEK_SET)`|定位至文件开始处| 260 | >|`fseek(file,0L,SEEK_CUR)`|保持当前位置不动| 261 | >|`fseek(file,0L,SEEK_END)`|定位至文件结尾| 262 | >|`fseek(file,ftell-pos,SEEK_SET)`|到距文件开始处ftell-pos的位置,ftell-pos是ftell()的返回值| 263 | 264 | #### 4.4 `fgetpos()` 和 `fsetpos()` 函数 265 | - fgetpos() 函数 266 | 267 | 语法格式: 268 | ```cpp 269 | int fgetpos( FILE *stream, fpos_t *position ); 270 | ``` 271 | >参数说明: 272 | > 273 | > **stream** : 当前文件流的指针 274 | > 275 | > **fpos_t** : 用来表示文件读写指针位置的类型,用来指明正在操作的文件中读或写的位置,文件头处为0。fpos_t在不同的平台下有不同的类型。 276 | > 277 | > **position** : 指向 fpos_t 对象的指针 278 | > 279 | > **功能**:处理较大文件(字节数超过long范围),解决 `fseek()` 和 `ftell()`函数存在的问题。 280 | > 281 | > **返回值**:执行成功时返回0,否则返回非0值。 282 | ANSI C定义了如何使用 `fpos_t` 类型,`fgetpos()` 函数的原型如下: 283 | ```cpp 284 | int fgetpos(FILE * restrict stream,fpos_t * restrict pos); 285 | ``` 286 | - `fsetpos()` 函数 287 | 288 | 语法格式: 289 | ```cpp 290 | int fsetpos(FILE *stream,const fpost_t *pos); 291 | ``` 292 | >调用函数时,使用pos指向位置上的 `fpos_t` 类型值来设置文件指针指向该值指定的位置。 293 | > 294 | >函数成功返回0,失败则返回非0。 295 | 296 | ### 5. 二进制I/O:`fread()` 和 `fwrite()` 297 | 298 | 如果以程序所用的表示法把数据储存在文件中。则称以`二进制形式存储数据`。 299 | 300 | 不存在从`数值形式`到`字符串`的转换过程。对于标准I/O,`fread()` 和 `fwrite()` 函数用于以二进制形式处理数据。 301 | 302 | 所有的数据都是以 二进制形式存储。 303 | 304 | ANSI C 和许多OS都识别两种文件格式:`二进制` 和 `文本`。 305 | 306 | ### 6. 其他I/O函数 307 | >一般都是成功返回0,不成功返回非零值:EOF(-1) 308 | 309 | |函数|函数原型|功能| 310 | |:--|:--|:--| 311 | |`ungetc()`|`int ungetc(int c, FILE* fp)`|把c指定的字符放回输入流中,如果把一个字符放回输入流,下次调用标准输入函数时将读取该字符。| 312 | |`fflush()`|`int fflush(FILE *stream)`|调用函数引起输出缓冲区中所有的未写入数据被发送到stream指定的输出文件,该过程叫作 **`刷新缓冲区`**。
如果指针stream是空指针,所有输出缓冲区都被刷新。| 313 | |`setvbuf()`|`int setvbuf(FILE* fp, char * buf, int mode, size_t size);`|创建一个提供I/O函数替换使用的缓冲区。| 314 | 315 | #### `size_t fwrite()` 函数 316 | `fwrite()` 函数的原型: 317 | ```cpp 318 | size_t fwrite(const void * restrict ptr,size_t size,size_t nmemb,FILE * restrict stream); 319 | ``` 320 | > `指针ptr` :待写入数据块的地址。 321 | > 322 | > `size`:待写入数据块的大小(以字节为单位)。 323 | > 324 | > `nmemb` :待写入数据块的数量。 325 | > 326 | > `stream` :指定待写入的文件。 327 | `fwrite()` 函数返回成功写入项的数量。 328 | 329 | #### `size_t fread()` 函数 330 | `fread()` 函数的原型: 331 | ```cpp 332 | size_t fread(void * restrict ptr,,size_t size,size_t nmemb,FILE * restrict stream); 333 | ``` 334 | > 在 `fread()` 函数中,`ptr` 是待读取文件数据在内存中的地址。`stream` 指定待读取的文件。 335 | > 336 | > fread() 函数返回成功读取项的数量。 337 | 338 | #### `int feof(FILE *fp)` 和 `int ferror(FILE *fp)` 函数 339 | 如果标准输入函数EOF,则通常表明函数已到达文件结尾。 340 | > `feof()` 函数返回一个非零值,否则返回0。 341 | > 342 | > `ferror()` 函数返回一个非零值,否则返回0。 343 | 344 | ```cpp 345 | /* append.c -- 把文件附加到另一个文件末尾 */ 346 | #include 347 | #include 348 | #include 349 | #define BUFSIZE 4096 350 | #define SLEN 81 351 | void append(FILE *source, FILE *dest); 352 | char * s_gets(char * st, int n); 353 | int main(void) 354 | { 355 | FILE *fa, *fs; // fa 指向目标文件,fs 指向源文件 356 | int files = 0; // 附加的文件数量 357 | char file_app[SLEN]; // 目标文件名 358 | char file_src[SLEN]; // 源文件名 359 | int ch; 360 | puts("Enter name of destination file:"); 361 | s_gets(file_app, SLEN); 362 | if ((fa = fopen(file_app, "a+")) == NULL) 363 | { 364 | fprintf(stderr, "Can't open %s\n", file_app); 365 | exit(EXIT_FAILURE); 366 | } 367 | if (setvbuf(fa, NULL, _IOFBF, BUFSIZE) != 0) 368 | { 369 | fputs("Can't create output buffer\n", stderr); 370 | exit(EXIT_FAILURE); 371 | } 372 | puts("Enter name of first source file (empty line to quit):"); 373 | while (s_gets(file_src, SLEN) && file_src[0] != '\0') 374 | { 375 | if (strcmp(file_src, file_app) == 0) 376 | fputs("Can't append file to itself\n", stderr); 377 | else if ((fs = fopen(file_src, "r")) == NULL) 378 | fprintf(stderr, "Can't open %s\n", file_src); 379 | else 380 | { 381 | if (setvbuf(fs, NULL, _IOFBF, BUFSIZE) != 0) 382 | { 383 | fputs("Can't create input buffer\n", stderr); 384 | continue; 385 | } 386 | append(fs, fa); 387 | if (ferror(fs) != 0) 388 | fprintf(stderr, "Error in reading file %s.\n", 389 | file_src); 390 | if (ferror(fa) != 0) 391 | fprintf(stderr, "Error in writing file %s.\n", 392 | file_app); 393 | fclose(fs); 394 | files++; 395 | printf("File %s appended.\n", file_src); 396 | puts("Next file (empty line to quit):"); 397 | } 398 | } 399 | 400 | printf("Done appending. %d files appended.\n", files); 401 | rewind(fa); 402 | printf("%s contents:\n", file_app); 403 | while ((ch = getc(fa)) != EOF) 404 | putchar(ch); 405 | puts("Done displaying."); 406 | fclose(fa); 407 | return 0; 408 | } 409 | 410 | void append(FILE *source, FILE *dest) 411 | { 412 | size_t bytes; 413 | static char temp[BUFSIZE]; // 只分配一次 414 | while ((bytes = fread(temp, sizeof(char), BUFSIZE, source)) > 0) 415 | fwrite(temp, sizeof(char), bytes, dest); 416 | } 417 | 418 | char * s_gets(char * st, int n) 419 | { 420 | char * ret_val; 421 | char * find; 422 | ret_val = fgets(st, n, stdin); 423 | if (ret_val) 424 | { 425 | find = strchr(st, '\n'); // 查找换行符 426 | if (find) // 如果地址不是NULL, 427 | *find = '\0'; // 在此处放置一个空字符 428 | else 429 | while (getchar() != '\n') 430 | continue; 431 | } 432 | return ret_val; 433 | } 434 | ``` 435 | ### 7. 用二进制I/O进行随机访问 436 | ```cpp 437 | /* randbin.c -- 用二进制I/O进行随机访问 */ 438 | #include 439 | #include 440 | #define ARSIZE 1000 441 | int main() 442 | { 443 | double numbers[ARSIZE]; 444 | double value; 445 | const char * file = "numbers.dat"; 446 | int i; 447 | long pos; 448 | FILE *iofile; 449 | // 创建一组 double类型的值 450 | for (i = 0; i < ARSIZE; i++) 451 | numbers[i] = 100.0 * i + 1.0 / (i + 1); 452 | // 尝试打开文件 453 | if ((iofile = fopen(file, "wb")) == NULL) 454 | { 455 | fprintf(stderr, "Could not open %s for output.\n", file); 456 | exit(EXIT_FAILURE); 457 | } 458 | // 以二进制格式把数组写入文件 459 | fwrite(numbers, sizeof(double), ARSIZE, iofile); 460 | fclose(iofile); 461 | if ((iofile = fopen(file, "rb")) == NULL) 462 | { 463 | fprintf(stderr, 464 | "Could not open %s for random access.\n", file); 465 | exit(EXIT_FAILURE); 466 | } 467 | // 从文件中读取选定的内容 468 | printf("Enter an index in the range 0-%d.\n", ARSIZE - 1); 469 | while (scanf("%d", &i) == 1 && i >= 0 && i < ARSIZE) 470 | { 471 | pos = (long) i * sizeof(double); // 计算偏移量 472 | fseek(iofile, pos, SEEK_SET); // 定位到此处 473 | fread(&value, sizeof(double), 1, iofile); //读取该位置上的数据值 474 | printf("The value there is %f.\n", value); 475 | printf("Next index (out of range to quit):\n"); 476 | } 477 | // 完成 478 | fclose(iofile); 479 | puts("Bye!"); 480 | return 0; 481 | } 482 | ``` 483 | 484 | ### 总结 485 | 输入函数`getc()`、`fgets()`、`fscanf()`和`fread()` 都是从`文件开始处按顺序`读取文件。 486 | 487 | `fseek()` 和 `ftell()` 函数让程序可以`随机访问文件中的任意位置`。`fgetpos()` 和 `fsetpos()` 把类似的功能扩展到更大的文件。 488 | 489 | 与文本模式相比,二进制模式更容易进行随机访问。 490 | -------------------------------------------------------------------------------- /Chapter13/img/fopen中的模式字符串.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter13/img/fopen中的模式字符串.png -------------------------------------------------------------------------------- /Chapter13/复习题.md: -------------------------------------------------------------------------------- 1 | ## 第十三章 文件输入/输出 2 | 3 | ### 1.下面的程序有什么问题? 4 | ```cpp 5 | int main(void) 6 | { 7 | int * fp; 8 | int k; 9 | fp = fopen("gelatin"); 10 | for (k = 0; k < 30; k++) 11 | fputs(fp, "Nanette eats gelatin."); 12 | fclose("gelatin"); 13 | return 0; 14 | } 15 | ``` 16 | **解答说明** 17 | |函数|函数原型|语法格式|功能|备注| 18 | |:--:|:--|:--|:--|:--| 19 | |`fopen()`|`FILE *fopen(const char *filename,const char * mode);`|`FILE *fp = fopen("filename",mode)`|打开文件|返回一个文件指针:FILE *fp 指向一个记录文件信息的数据结构
例:`fp = fopen("hello_c.txt","r");`| 20 | |`fclose()`|`int fclose(FILE * stream);`|`fclose(fp)`|关闭文件|关闭成功返回0,失败返回EOF(-1),存储空间不足或者被移除都会出现I/O错误,都会导致失败。| 21 | 22 | 内容中的三个错误点: 23 | 24 | 1. `fopen()` 函数需要提供一个模式(mode)。 25 | 2. `fclose()` 函数需要一个文件指针,不能使用文件名。 26 | 3. `fputs()` 函数的参数顺序调换。(为了代码的可读性,建议在输出字符串结尾加一个`换行符(\n)`) 27 | 28 | ### 2. 下面的程序完成什么任务?(假设在命令行环境中运行) 29 | ```cpp 30 | #include 31 | #include 32 | #include 33 | int main(int argc, char *argv []) 34 | { 35 | int ch; 36 | FILE *fp; 37 | if (argc < 2) 38 | exit(EXIT_FAILURE); 39 | if ((fp = fopen(argv[1], "r")) == NULL) 40 | exit(EXIT_FAILURE); 41 | while ((ch = getc(fp)) != EOF) 42 | if (isdigit(ch)) 43 | putchar(ch); 44 | fclose(fp); 45 | return 0; 46 | } 47 | ``` 48 | **解答说明** 49 | 50 | 如果文件可以正常打开,会打开与命令行第一个参数名相同名称的文件,并打印显示文件中的每个数字字符。 51 | 52 | ### 3. 假设程序中有下列语句: 53 | ```cpp 54 | #include 55 | FILE * fp1,* fp2; 56 | char ch; 57 | fp1 = fopen("terky", "r"); 58 | fp2 = fopen("jerky", "w"); 59 | ``` 60 | 另外,假设成功打开了两个文件。补全下面函数调用中缺少的参数: 61 | 62 | a.ch = getc(); 63 | 64 | b.fprintf( ,"%c\n", ); 65 | 66 | c.putc( , ); 67 | 68 | d.fclose(); /* 关闭terky文件 */ 69 | 70 | **解答说明** 71 | ```cpp 72 | 73 | ``` 74 | 75 | ### 4. 编写一个程序,不接受任何命令行参数或接受一个命令行参数。如果有一个参数,将其解释为文件名;如果没有参数,使用标准输入(stdin)作为输入。假设输入完全是浮点数。该程序要计算和报告输入数字的算术平均值。 76 | **解答说明** 77 | ```cpp 78 | 79 | ``` 80 | 81 | 82 | -------------------------------------------------------------------------------- /Chapter13/编程题.md: -------------------------------------------------------------------------------- 1 | ## 第十三章 文件输入/输出 2 | 3 | -------------------------------------------------------------------------------- /Chapter14/README.md: -------------------------------------------------------------------------------- 1 | ## 第十四章 结构和其他数据形式 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | 5 | C语言中,提供结构变量 可以提高表示数据的能力,创造新的形式。 6 | 7 | ### 1. 示例问题:创建图书目录 8 | 打印图书目录,包括:书名、作者、价格。 9 | ```cpp 10 | #include 11 | #include 12 | char *s_gets(char * st,int n); 13 | #define MAXTITL 41; // 书名最大长度 14 | #define MAX AUTL 31; // 作者名字的最大长度 15 | 16 | struct book{ 17 | char title[MAXTITL]; 18 | char author[MAXAUTL]; 19 | float value; 20 | };//结构模版结束 21 | 22 | int main(void) 23 | { 24 | struct book library; // 把library声明为一个book类型的变量 25 | printf("Please enter the book title.\n"); 26 | s_gets(library.title, MAXTITL); 27 | printf("Now enter the author.\n"); 28 | s_gets(library.author, MAXAUTL); 29 | printf("Now enter the value.\n"); 30 | scanf("%f", &library.value); 31 | printf("%s by %s: $%.2f\n", library.title, library.author, library.value); 32 | printf("%s: \"%s\"($%.2f)\n", library.author, library.title, library.value); 33 | printf("Done.\n"); 34 | 35 | return 0; 36 | } 37 | 38 | char *s_gets(char * st,int n) 39 | { 40 | char * ret_val; 41 | char * find; 42 | ret_val = fgets(st, n, stdin); 43 | if (ret_val) 44 | { 45 | find = strchr(st, '\n');//查找换行符 46 | if (find) //如果地址不是NULL 47 | *find = '\0'; //将换行符换成'\0' 48 | else 49 | while (getchar() != '\n') //处理输入行剩余的字符 50 | continue; 51 | } 52 | return ret_val; 53 | } 54 | ``` 55 | 结构的3个技巧 56 | > - 为结构建立一个格式或样式。 57 | > - 声明一个适合该样式的变量。 58 | > - 访问结构变量的各个部分。 59 | 60 | ### 2. 建立结构声明 61 | - `结构声明(structure declaration)`描述了一个结构的组织布局。 62 | 63 | - 在结构声明,用`一对花括号`括起来的是`结构成员列表`。每个成员都可以是任意一种C的数据类型。 64 | 65 | - 右花括号后面的分号是声明必需,表示结构布局定义结束。 66 | 67 | - 结构声明可以放在所有函数的外部,也可以放在一个函数定义的内部,但是如果把声明放在函数内部,它的标记只局限于该函数内部使用。 68 | 69 | - 结构的标记名是可选的,但是在一处定义结构布局,在另一处定义实际的结构变量,必须使用标记。 70 | 71 | ### 3. 定义结构变量 72 | 结构的含义: 73 | > - 结构布局告诉编译器如何表示数据,但不让编译器为数据分配空间。 74 | > 75 | > - 创建另一个结构变量。 76 | 77 | #### 3.1 初始化结构 78 | 让每个成员的初始化项独占一行。目的:提高代码的可读性,对编译器而言,只需要用逗号分隔各成员的初始化项。 79 | - 初始化一个结构变量(ANSI之前,不能自动变量初始化结构,ANSI之后可以用任意存储类别)与初始化数组的语法类似。 80 | 81 | - 使用在一对花括号中括起来的初始化列表进行初始化,各初始化项用逗号分隔。 82 | 83 | **⚠️注意:初始化结构和类别存储期** 84 | - 如果初始化一个静态存储期的结构,初始化列表中的值必须是常量表达式。 85 | 86 | - 如果是自动存储期,初始化列表中的值可以不是常量。 87 | 88 | #### 3.2 访问结构成员 89 | - 使用结构成员运算符 ————> `点(.)` 访问结构中的成员。 90 | ```cpp 91 | /*访问library中的title部分*/ 92 | library.title = "C Primer Plus"; 93 | ``` 94 | - `点(.)` 比 `地址符(&)` 优先级高。 95 | #### 3.3 结构的初始化器 96 | - C99和C11为结构提供了指定`初始化器(也叫标记化结构初始化语法)`,结构的指定初始化器使用`点运算符`和`成员名(而不是方括号和下标)标识`特定的元素。 97 | ```cpp 98 | struct book gift = { 99 | .price = 69.00, 100 | .author = "Stephen Prata", 101 | .title = "c primer plus" 102 | }; 103 | ``` 104 | - 在指定初始化器后面的`普通初始化器`,为指定成员后面的成员提供初始值。 105 | 106 | - 指定成员的最后一次赋值才是它实际获得的值。 107 | 108 | ### 4. 结构数组 109 | 110 | #### 4.1 声明结构数组 111 | 声明结构数组和声明其他类型的数组类似。 112 | ```cpp 113 | /*把library声明为一个内含MAXBKS个元素的数组*/ 114 | struct book library[MAXBKS]; 115 | ``` 116 | > 数组名library不是结构名,是数组名。数组中的每个元素都是struct book类型的结构变量。 117 | 118 | #### 4.2 标识结构数组的成员 119 | 为了表示结构数组中的成员,采用访问单独结构的规则:在结构名后面加一个点运算符,再在点运算符后面加上成员名。如: 120 | ```cpp 121 | library[0].price /*第1个数组元素与price相关联*/ 122 | library[4].title /*第5个数组元素与title相关联*/ 123 | ``` 124 | > ⚠️注意:***数组下标紧跟在数组名(如library后面),不是成员名后面。*** 125 | 126 | #### 4.2 嵌套结构 127 | 嵌套结构:在一个结构中包含另一个结构。 128 | 如果访问嵌套结构的成员,需要使用两次点运算符。 129 | ```cpp 130 | #define _CRT_SECURE_NO_WARNINGS 131 | #include 132 | #define LEN 20 133 | const char * msgs[5] = 134 | { 135 | " Thank you for the wonderful evening, ", 136 | "You certainly prove that a ", 137 | "is a special kind of guy. We must get together", 138 | "over a delicious ", 139 | " and have a few laughs" 140 | }; 141 | 142 | struct names { 143 | char first[LEN]; 144 | char last[LEN]; 145 | }; 146 | 147 | struct guy { 148 | struct names handle; //嵌套结构,结构里包含另一个结构 149 | char favfood[LEN]; 150 | char job[LEN]; 151 | float income; 152 | }; 153 | 154 | int main(void) 155 | { 156 | struct guy fellow = { 157 | {"Ewen", "Villard"}, 158 | "grilled salmon", 159 | "personality coach", 160 | 68112.00 161 | }; 162 | printf("Dear %s,\n\n", fellow.handle.first); //使用嵌套结构,先使用.得到name,再.得到first 163 | printf("%s%s.\n", msgs[0], fellow.handle.first); 164 | printf("%s%s\n", msgs[1], fellow.job); 165 | printf("%s\n", msgs[2]); 166 | printf("%s%s%s", msgs[3], fellow.favfood, msgs[4]); 167 | if (fellow.income > 150000.0) 168 | puts("!!"); 169 | else if (fellow.income > 75000.0) 170 | puts("!"); 171 | else 172 | puts("."); 173 | printf("\n%40s%s\n", " ", "See you soon,"); 174 | printf("%40s%s\n", " ", "Shalala"); 175 | 176 | return 0; 177 | } 178 | ``` 179 | 180 | ### 5. 指向结构的指针 181 | 使用指向结构的指针的理由: 182 | - 指向结构的指针通常是结构本身更容易操控(指向数组的指针比数组本身更容易操控)。 183 | - 早期C实现,结构不能作为参数传递给函数,可以传递指向结构的指针。 184 | - 能传递一个结构,传递指针通常更有效率。 185 | - 一些用于表示数据的结构中包含指向其它结构的指针。 186 | 187 | #### 5.1 声明和初始化结构指针 188 | 例子: 189 | ```cpp 190 | struct guy *him; 191 | ``` 192 | 与数组不同的是,~~结构名并不是结构的地址~~,因此在结构名前加上`&运算符`。 193 | 194 | #### 5.2 用指针访问成员 195 | 两种方式: 196 | - 使用 `->` 运算符 197 | - 如果 him==&barney ,那么 him->income 即是 barney.income 198 | - 如果 him==&fellow[0] , 那么 him->income 即是 fellow [0].income 199 | 200 | 201 | - 用`运算符`(必须使用`圆括号`,因为`(.)运算符`比`(*)`运算符的优先级高)。 202 | ```cpp 203 | barney.income == (*him) . income == him->income //假设him== &barney 204 | fellow [0].income == (*him) .income 205 | ``` 206 | 有些系统,**一个结构的大小可能大于它各成员大小之和**。 207 | 208 | ### 6. 向函数传递结构的信息 209 | 函数的参数把值(都是数字---> int类型,float类型、ASCII字符码、地址)传递给函数。 210 | 211 | #### 6.1 传递结构成员 212 | 只要结构成员是一个具有单个值的数据类型(即int及其相关类型、char、float、double或指针),可作为参数传递给接受该特定类型的函数。 213 | ```cpp 214 | /*把结构成员作为参数传递*/ 215 | #include 216 | #define FUNDLEN 50 217 | 218 | struct funds { 219 | char bank[FUNDLEN]; 220 | double bankfund; 221 | char save[FUNDLEN]; 222 | double savefund; 223 | }; 224 | 225 | double sum(double, double); 226 | 227 | int main(void) 228 | { 229 | struct funds stan = { 230 | "Garlic-Melon Bank", 231 | 4032.27, 232 | "Lucky's Savings and Loan", 233 | 8543.94 234 | }; 235 | 236 | printf("Stan has a total of $%.2f.\n", 237 | sum(stan.bankfund, stan.savefund)); 238 | return 0; 239 | } 240 | 241 | /* adds two double numbers */ 242 | double sum(double x, double y) 243 | { 244 | return(x + y); 245 | } 246 | ``` 247 | 248 | #### 6.2 传递结构的地址 249 | 必须使用`&运算符`来获取`结构的地址`,结构名只是其地址的别名。 250 | 251 | ```cpp 252 | #include 253 | #define FUNDLEN 50 254 | 255 | struct funds { 256 | char bank[FUNDLEN]; 257 | double bankfund; 258 | char save[FUNDLEN]; 259 | double savefund; 260 | }; 261 | 262 | double sum(const struct funds *); // 263 | 264 | int main(void) 265 | { 266 | struct funds stan = { 267 | "Garlic--Melon Bank", 268 | 4032.27, 269 | "Lucky's Savings and Loan", 270 | 8543.94 271 | }; 272 | 273 | printf("Stan has a total of $%.2f.\n", sum(&stan)); // 274 | return 0; 275 | } 276 | double sum(const struct funds * money) // 277 | { 278 | return (money->bankfund + money->savefund); 279 | } 280 | ``` 281 | #### 6.3 传递结构 282 | 把结构作为参数的编译器。 283 | ```cpp 284 | #include 285 | #define FUNDLEN 50 286 | 287 | struct funds { 288 | char bank[FUNDLEN]; 289 | double bankfund; 290 | char save[FUNDLEN]; 291 | double savefund; 292 | }; 293 | 294 | double sum( struct funds moolah); 295 | 296 | int main(void) 297 | { 298 | struct funds stan = { 299 | "Garlic--Melon Bank", 300 | 4032.27, 301 | "Lucky's Savings and Loan", 302 | 8543.94 303 | }; 304 | 305 | printf("Stan has a total of $%.2f.\n", sum(stan)); 306 | return 0; 307 | } 308 | /*编译器会为funds结构创建一个名为moolah的自动结构变量副本,然后对副本进行操作(不会改变实际参数stan)*/ 309 | double sum( struct funds moolah) 310 | { 311 | /*类似给结构起个别名,然后通过别名来对其进行访问*/ 312 | return( moolah.bankfund + moolah.savefund); 313 | } 314 | ``` 315 | 316 | #### 6.4 其他结构特性 317 | 318 | - 新标准的C允许把一个结构赋值给另一个结构,但数组不行。即使成员是数组,也能完成赋值。 319 | 320 | - 现在的C(包括ANSI C),函数不仅能把结构本身作为参数传递,还能把结构作为返回值返回。 321 | > 把结构作为函数参数可以把结构的信息传送给函数,把结构作为返回值的函数能把结构的信息从被调函数传回给主调函数。 322 | 323 | 👉**小Tips**:结构指针允许双向通信。 324 | 325 | **方法1:使用指针方式 ————> 传递地址** 326 | ```cpp 327 | #include 328 | #include 329 | 330 | #define NLEN 30 331 | struct namect{ 332 | char fname[NLEN]; 333 | char lname[NLEN]; 334 | int letters; 335 | }; 336 | void getinfo(struct namect *); 337 | void makeinfo(struct namect *); 338 | void showinfo(const struct namect *); 339 | char s_gets(char *st,int n); 340 | 341 | int main(void) 342 | { 343 | struct namect person; 344 | 345 | getinfo(&persion); 346 | makeinfo(&person); 347 | showinfo(&person); 348 | return 0; 349 | } 350 | void getinfo(struct namect *pst) 351 | { 352 | printf("please enter your first name.\n"); 353 | s_gets(pst->fname,NLEN); 354 | printf("please enter your last name.\n"); 355 | s_gets(pst->lname,NLEN); 356 | } 357 | 358 | void makeinfo(struct namect *pst) 359 | { 360 | pst->letters = strlen(pst->fname) + strlen(pst->lname); 361 | } 362 | 363 | void showinfo(const struct namect *pst) 364 | { 365 | printf("%s %s , your name contains %d letters.\n", 366 | pst->fname,pst->lname,pst->letters); 367 | } 368 | 369 | char s_gets(char *st,int n) 370 | { 371 | char *ret_val; 372 | char *find; 373 | 374 | ret_val = fgets(st,n,stdin); 375 | if(ret_val) 376 | { 377 | find = strchr(st,'\n');//查找换行符 378 | if(find) // 如果地址不是NULL 379 | *find = '\0'; // 放置一个空字符 380 | else 381 | while(getchar() !='\n') 382 | continue; // 处理输入行的剩余字符 383 | } 384 | return ret_val; 385 | } 386 | ``` 387 | **方法2:使用结构 ————> 传递结构** 388 | ```cpp 389 | #include 390 | #include 391 | 392 | #define NLEN 30 393 | struct namect{ 394 | char fname[NLEN]; 395 | char lname[NLEN]; 396 | int letters; 397 | }; 398 | 399 | struct namect getinfo(void); 400 | struct namect makeinfo(struct namect); 401 | void showinfo(const struct namect *); 402 | char s_gets(char *st,int n); 403 | 404 | int main(void) 405 | { 406 | struct namect person; 407 | 408 | person = getinfo(); 409 | person = makeinfo(person); 410 | showinfo(person); 411 | return 0; 412 | } 413 | void getinfo(void) 414 | { 415 | struct namect temp; 416 | printf("please enter your first name.\n"); 417 | s_gets(temp.fname,NLEN); 418 | printf("please enter your last name.\n"); 419 | s_gets(temp.lname,NLEN); 420 | 421 | return temp; 422 | } 423 | 424 | void makeinfo(struct namect *pst) 425 | { 426 | pst->letters = strlen(pst->fname) + strlen(pst->lname); 427 | } 428 | 429 | void showinfo(struct namect info) 430 | { 431 | printf("%s %s , your name contains %d letters.\n", 432 | info.fname,info.lname,info.letters); 433 | } 434 | 435 | char s_gets(char *st,int n) 436 | { 437 | char *ret_val; 438 | char *find; 439 | 440 | ret_val = fgets(st,n,stdin); 441 | if(ret_val) 442 | { 443 | find = strchr(st,'\n');//查找换行符 444 | if(find) // 如果地址不是NULL 445 | *find = '\0'; // 放置一个空字符 446 | else 447 | while(getchar() !='\n') 448 | continue; // 处理输入行的剩余字符 449 | } 450 | return ret_val; 451 | } 452 | ``` 453 | >该方式中makeinfo()函数返回的是一个结构。 454 | 455 | 对于指针方式传递还是结构方式传递,两者输出结果相同,使用方式不同而已。 456 | 457 | #### 6.5 结构和结构指针的选择 458 | - **指针作为参数传递** 459 | - **优点**:无论是以前还是现在的C实现都能使用该方法,而且执行快,只需要`传递一个地址`。 460 | - **缺点**:无法保护数据,故ANSI C新增`const限定符`可以解决这个问题。 461 | - **结构作为参数传递** 462 | - **优点**:函数处理的是原始数据的副本,这可以保护原始数据。代码风格也很清楚。 463 | - **缺点**:较老版本的实现可能无法处理,且传递结构浪费时间和存储空间。 464 | 465 | 👉**小Tips**: 466 | - 程序员为了追求效率会使用`结构指针`作为函数参数,如需`防止原始数据被意外修改`,使用 `const限定符`。 467 | 468 | - 按`值传递结构`是处理小型结构最常用的方法。 469 | 470 | #### 6.6 结构中的字符数组和字符指针 471 | 如果要用结构储存字符串,用字符数组作为成员比较简单。用指向char的指针也行,但是误用会导致严重的问题。(指针最好只用来在程序中管理那些已分配和在别处分配的字符串) 472 | 473 | #### 6.7 结构、指针和`malloc()` 474 | 如果使用 `malloc()` 分配内存并使用指针存储该地址,那么在结构中使用指针处理字符串会比较合理。 475 | 476 | 优点:可以请求`malloc()`为字符串`分配合适的存储空间`。 477 | 478 | ⚠️注意:成对使用`malloc()`和`free()`(两个函数原型都在 `stdlib.h` 头文件中)。 479 | 480 | #### 6.8 复合字面量和结构(C99) 481 | C99中的复合字面量特性可用于结构和数组。如果只需要一个临时结构值,推荐使用复合字面量。 482 | >可以使用复合字面量创建一个数组作为函数的参数或赋给另一个结构。 483 | 484 | **语法**:把`类型名`放在`圆括号`中,后紧跟一个`花括号`括起来的`初始化列表`。如: 485 | ```cpp 486 | (struct book){"The Idiot","Fyodor Destroyevsky",6.99} 487 | ``` 488 | 489 | 490 | **关于复合字面量总结**: 491 | - 在所有`函数的外部`,具有`静态存储器`。 492 | - 在`块中`,则具有`自动存储期`。 493 | - 复合字面量和普通初始化列表的语法规则相同。 494 | 495 | 496 | 497 | #### 6.9 伸缩型数组成员(C99) 498 | `声明伸缩型数组成员`会具有的特性: 499 | - 数组不会立即存在。 500 | - 可以编写合适的代码。 501 | 502 | **声明的规则**: 503 | - 必须是结构的最后一个成员。 504 | - 结构中必须至少有一个成员。 505 | - 类似普通数组,只是方括号中是空的。 506 | 507 | 例如: 508 | ```cpp 509 | struct flex 510 | { 511 | int count; 512 | double average; 513 | double scores[]; // 伸缩型数组成员 514 | }; 515 | ``` 516 | 517 | **特殊要求** 518 | - ~~不能用结构进行赋值或拷贝~~。 519 | - ~~不要以按值方式把结构传递给结构。~~**原因**:按值传递参数与赋值类似,把结构的地址传递给函数。 520 | - ~~不要使用带伸缩型数组成员的结构作为数组成员或另一个结构的成员。~~ 521 | 522 | 523 | #### 6.10 匿名结构(C11) 524 | 匿名结构 ~~`没有名称`~~ 的结构成员。 525 | 526 | 匿名特性在嵌套联合中更加有用。 527 | 528 | ### 7. 联合简介 529 | `联合(union)`是一种数据类型。能在同一个内存空间中存储不同的数据类型(不是同时存储)。 530 | 531 | 典型用法:设计一种表以储存既无规律、事先不知道顺序的混合类型。 532 | 533 | 使用混合类型的数组,其中的联合都大小相等,每个联合可以存储各种数据类型。 534 | 535 | 带标记的联合模版 536 | ```cpp 537 | union hold{ 538 | int digit; 539 | double bigfl; 540 | char letter; 541 | }; 542 | ``` 543 | 与hold类型相关的变量: 544 | ```cpp 545 | union hold fit; // hold类型的联合变量 546 | union hold save[10]; // 内含10个联合变量的数组 547 | union hold *pu; //指向hold类型联合变量的指针 548 | ``` 549 | 550 | ⚠️注意:联合只能存储一个值。 551 | 552 | 初始化联合的方法 553 | - 把一个联合初始化为另一个同类型联合。 554 | - 初始化联合的第一个元素。 555 | - C99标注,使用指定初始化器。 556 | ```cpp 557 | union hold valA; 558 | valA.letter = 'R'; 559 | union hold valB = valA;//另一个同类型联合 560 | union hold valC = {88};//初始化联合的第一个元素digit 561 | union hold valD = {.bigfl = 118.2};//指定初始化器 562 | ``` 563 | 564 | #### 7.1 使用联合 565 | ```cpp 566 | fit.digit = 23; // 把23存储在fit,占2字节 567 | fit.digfl = 2.0; // 清除23,存储2.0.占8字节 568 | fit.letter = 'h'; // 清除2.0,存储h,占1个字节 569 | ``` 570 | 571 | `点运算符`:表示正在使用哪种数据类型。 572 | 573 | 用指针访问联合时要使用`->运算符`。 574 | ```cpp 575 | pu = &fit; 576 | x = pu->digit;//相当于 x = fit.digit 577 | ``` 578 | 在结构中储存与其成员有从属关系的信息(用一个成员把值储存在一个联合中,然后用另一个成员查看内容)。 579 | 580 | 匿名联合:一个机构或者联合的无名联合成员。 581 | 582 | 583 | ### 8. 枚举类型 584 | 可以用`枚举类型(enumerated type)声明符号名称来表示整型变量`。使用`enum关键字`,可以创建一个新“类型”并指定它可具有的值(语法与结构的语法相同)。 585 | 586 | 目的:提高程序的可读性。 587 | 588 | 默认情况下,枚举常量是int类型的值,枚举列表中的常量都被赋予0,1,2等以此类推。 589 | 590 | ⚠️注意:C允许枚举变量使用 ++ 运算符,在C++标准中是不允许使用。 591 | 592 | 在枚举声明中,可以为枚举常量指定整数值。 593 | >如果只给一个枚举常量赋值,没有对后面的枚举常量赋值,那么后面的常量会被后续赋值。 594 | >```cpp 595 | > enum feline {cat, lynx = 10, puma, tiger}; 596 | >``` 597 | 598 | #### 8.1 枚举enum的用法 599 | 目的:提高程序的可读性和可维护性。 600 | 601 | ⚠️注意:枚举只能在`内部使用`。 602 | 603 | ### 9. typedef简介 604 | 605 | typedef是高级数据特性。利用typedef 可以为某一类型自定义名称。 606 | 607 | typedef 的作用域取决于typedef定义所在的位置。 608 | > 如果定义在`函数内部`,属`局部作用域`。 609 | > 如果在`函数外部`,则具有`文件作用域`。 610 | 611 | #### 9.1 `typedef` 和 `#define` 的区别 612 | - `typedef`创建的符号名只受限与类型,不能用于值。 613 | - `typedef` 由`编译器`解释,而不是`预处理器`。 614 | - 在其受限范围里,`typedef`比`#define`更灵活 615 | 616 | 一般情况下,typeded定义中使用`大写字母`表示`被定义的名称`。也可以使用小写。 617 | > typedef中使用的名称遵循变量命名的规则。 618 | 619 | 将`typedef`用于来命名一个结构类型时,可以`省略结构的标签`。 620 | 621 | #### 9.2 使用typedef的原因 622 | - 为经常出现的类型创建一个方便、易识别的类型名。 623 | - typedef常用来给复杂的类型命名。 624 | 625 | ⚠️注意:使用`typedef`时,typedef并没有创建任何新类型,只是为某个已存在的类型增加方便使用的标签。 626 | -------------------------------------------------------------------------------- /Chapter14/复习题.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter14/复习题.md -------------------------------------------------------------------------------- /Chapter14/编程题.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter14/编程题.md -------------------------------------------------------------------------------- /Chapter15/README.md: -------------------------------------------------------------------------------- 1 | ## 第十四章 位操作 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | 5 | ### 1. 二进制数、位和字节 6 | 以数字`10`为基底表示的数字称为`十进制数`。 7 | 8 | 以数字`2`为基底表示的数字称为`二进制数`。 9 | 10 | 用`二进制系统`可以把任意整数表示为 `0` 和 `1` 的组合。 11 | 12 | #### 1.1 二进制整数 13 | 1字节包含8位。 14 | 15 | C语言用`字节(byte)` 表示存储系统字符集所需的大小。 16 | 17 | 描述`存储器芯片`和`数据传输率`中所用的字节指的是 `8位` 字节。 18 | 19 | #### 1.2 有符号整数 20 | 如何表示有符号整数取决于硬件,而不是C语言。 21 | 22 | `二进制补码`方法 23 | 24 | `二进制反码`方法通过反码位组合中的每一位形成一个负数。 25 | 26 | ### 2. 其他进制数 27 | 28 | #### 2.1 八进制 29 | 八进制是指八进制记数系统。 30 | 31 | 将`八进制数`转换为`二进制`形式时,~~不能去掉中间的0~~。 32 | 33 | #### 2.2 十六进制 34 | 十六进制:十六进制记数系统。 35 | 36 | 每个十六进制都对应一个4位的二进制数(即4个二进制数)。 37 | 38 | 十六进制很适合表示字节值。 39 | 40 | ### 3. C按位运算符 41 | C提供`按位逻辑运算符` 和 `移位运算符`。 42 | 43 | #### 3.1 按位逻辑运算符 44 | 45 | - 二进制反码或按位取反:~ 46 | 一元运算符`~`把 1 变为0,把0变为1。 47 | 48 | - 按位与:`&` 49 | 二元运算符`&`通过逐位比较两个运算对象,生成一个新值。 50 | > 对于每个位,只有两个运算对象中相应的位都为1时,结果才为1(从真/假方面来看,只有当两个位都为真时,结果才为真)。 51 | 52 | 掩码:按`位与`运算符常用于 `掩码(mask)`。一些设置为 `开(1)`或 `关(0)`的位组合。 53 | 54 | 关闭位(清空位):和打开特定的位类似,使用 `&` ,任何位与1组合都得本身(保持1号位不变,改变其他各位)。任何位与0组合都得0。 55 | 56 | 57 | - 按位或:`|` 58 | 二元运算符 `|` ,通过逐位比较两个运算对象,生成一个新值。 59 | > 对于每个位,如果两个运算对象中相应的位只有一个1(非两个为1),结果为1(从真/假考虑,有一个位为真且不是为同为1,那结果为真)。 60 | 61 | 打开位(设置位):使用`按位或(|)` 运算符,任何位与0组合,结果都为本身;任何位与1组合,结果都为1。 62 | 63 | - 按位异或`^` 64 | 二元运算符 65 | 66 | 逐位比较两个运算对象。 67 | > 对于每个位,如果一个运算对象中相应的位一个为1(但不是两个都为1),结果为1(真假来看,如果两个运算对象中相应的一个位真且不是两个同为1,结果为真)。 68 | 69 | 切换位:打开已关闭的位,或关闭已打开的位。可以使用`按位异或`运算符完成。 70 | 71 | 72 | #### 3.2 移位运算符 73 | 74 | - **左移`<<`** 75 | 将其`左侧运算对象`每一位的值`向左移动`其`右侧运算`对象`指定的位数`。 76 | 77 | 左侧运算对象移出左末端位的值丢失,用0填充空出的位置。 78 | 79 | ```cpp 80 | (10001010)<<2 // 表达式 81 | (00101000) // 结果值 82 | ``` 83 | 84 | - **右移`>>`** 85 | 将其左侧运算对象每一位的值向右移动其右侧运算对象指定的位数。 86 | 87 | 左侧运算对象移出,右末端的值丢。 88 | 89 | > 对于无符号类型,用0填充空出的位置。 90 | > 91 | > 对于有符号类型,其结果取决于机器。空出的位置可用0填充,或者用符号位(即最左端的位)的副本填充。 92 | 93 | 每个位向右移动两个位置,空出的位用0填充。 94 | 95 | **总结**: 96 | |运算符|说明| 97 | |:--|:--| 98 | |number << n|number 乘以2的n次幂| 99 | |number >> n|如果number为非负,则用number除以2的次幂| 100 | 101 | ### 4. 位字段 102 | 位字段(bit field):一个signed int 或 unsigned int 类型变量中一组相邻的位(C99和C11新增_Bool类型的位字段) 103 | 104 | 通过 `结构声明`来建立。此结构声明为每个字段提供标签,并确定字段的宽度。 105 | >通过普通的结构成员运算符(.)单独给字段赋值。 106 | 107 | 内含位字段的结构允许在一个存储单元中存储多个设置。 108 | 109 | ### 5. 对齐特性(C11) 110 | 对齐:如何安排对象在内存中的位置。 111 | 112 | `_Alignof 运算符` 给出一个类型的对齐要求,在关键字 `_Alignof`后面的圆括号中写上`类型名`即可。 113 | ```cpp 114 | size_t d_align = _Alignof(float); 115 | ``` 116 | > 使用`_Alignas说明符` 指定一个`变量`或`类型的对齐值`。~~不应该要求该值小于基本对齐值。~~ 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /Chapter15/复习题.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter15/复习题.md -------------------------------------------------------------------------------- /Chapter15/编程题.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter15/编程题.md -------------------------------------------------------------------------------- /Chapter16/README.md: -------------------------------------------------------------------------------- 1 | ## 第十六章 C预处理器和C库 2 | 3 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 4 | 5 | ### 1. 翻译程序工具 ----> 编译器 6 | 在预处理之前,编译器会做一些翻译处理工作。 7 | 8 | - 把源代码中出现的`字符`映射到`源字符集`。 9 | - 定位每个`反斜杠`后面跟着`换行符的实例`,并直接删除。 10 | >将物理行转换成逻辑行。 11 | ```cpp 12 | printf(“That's wond\ 13 | erful!\n"); 14 | ``` 15 | 转成 16 | ```cpp 17 | printf(“That's wonderful!\n"); 18 | ``` 19 | 20 | - 编译器把`文本`划分成`预处理记号序列`、`空白序列` 和 `注释序列`(记号是由空格、制表符或换行符分隔的项)。 21 | 22 | ⚠️注意:编译器会用一个`空格字符`替换`每一条注释`。 23 | ```cpp 24 | int /*这看起来不像是一个空格*/ fox; 25 | ``` 26 | 将变成 27 | ```cpp 28 | int fox; 29 | ``` 30 | 31 | ### 2. 明示常量:`#define` 32 | `#define预处理器` 指令 和 `其他预处理器`指令一样,以 `# `作为一行的开始。 33 | >ANSI 和后来标准都允许 `#` 前面有`空格`或 `制表符`,也允许在 `#` 和指令的其余部分之间有空格。 34 | 35 | ⚠️注意:指令长度仅限于一行(从#开始运行,到后面的第一个换行符为止)。 36 | 37 | #### 2.1 每行 `#define(逻辑行)`的3部分 38 | - `#define`指令本身 39 | - 选定的缩写,也称为`宏`。 40 | > 宏代表值称为`类对象宏`。 41 | > 42 | > 宏的名称中不允许有空格。遵循C变量的命名规则。 43 | - 替换列表或替换体。 44 | > 从宏替换文本的过程称为`宏展开`。 45 | 46 | ⚠️注意:可在`#define`行使用标准C注释。 47 | 48 | 宏可以表示任何字符串,也可表示整个C表达式。 49 | 50 | ~~预处理器不做计算,不对表达式求值~~ ,只替换字符序列。 51 | 52 | ### 3. 在`#define`中使用参数 53 | 54 | 用#define创建类似函数的类函数宏,~~~不是函数~~。 55 | 56 | 类函数宏定义的圆括号可以有一个或多个参数,随后参数出现在替换体中。 57 | ![](./img/函数宏定义的组成.png) 58 | > MEAN 是宏标识符 59 | 60 | **函数调用和宏调用的区别** 61 | - 函数调用:在`程序运行时`把参数的值传递给函数。 62 | - 宏调用`在编译之前`把参数记号传递给程序。 63 | 64 | ⚠️注意:必要时使用足够多的圆括号来确保运算结合的正确顺序。 65 | 66 | ~~不要在宏中使用递增或递减运算。~~ 67 | 68 | #### 3.1 用宏参数创建字符串:`#运算符` 69 | 例: 70 | ```c 71 | #define PSQR(X) printf("The square of X id %d .\n",((X)*(X))); 72 | ``` 73 | > 双引号字符串中的X视为普通文本,而不是可被替换的记号。 74 | 75 | C允许在字符串中包含宏参数。在类宏函数的替换体中,`#号`作为一个`预处理运算符`,可以把记号转化为`字符串`,这样的过程叫作`字符串化(stringizing)`。例如,双引号中想表示宏形参,则使用 `#X` 即可。 76 | 77 | #### 3.2 预处理器粘合剂:`##运算符` 78 | 可用于`类函数宏`和`对象宏`的替换部分。把两个记号组合成一个新的标识符。 79 | 80 | #### 3.3 变参宏:`...` 和 `__VA_ARGS__` 81 | 通过把`宏参数列表`中最后的参数写成`省略号(即3个点...)`来实现这一功能。`预定义宏__VA_ARGS__`可用于替换部分中,表明省略号代表什么。 82 | 83 | 格式: 84 | ```c 85 | #define PR(...) printf(__VA_ARGS__) 86 | ``` 87 | C99/C11对宏提供让用户自定义带可变参数的函数。由 `stdvar.h 头文件`提供。 88 | 89 | ⚠️记住:省略号只能代替最后的宏参数。 90 | 91 | ### 4. 宏和函数的选择 92 | #### 4.1 如何选择? 93 | - 使用宏比普通函数更复杂一些,稍有不慎会有副作用。(一些编译器规定宏只能定义成一行) 94 | - 需要注意时间和空间的制衡。 95 | - 宏生成内联代码,在程序中生成语句。函数调用无论多少次,程序中只有一份函数语句的副本,节省了空间。 96 | - 程序的控制必须跳转到函数内,随后再返回主调函数,这比内联代码花费更多时间。 97 | - 对于简单的函数,程序员通常使用宏 98 | 99 | #### 4.2 宏的注意点 100 | - 宏名不允许有空格,但替换字符中可以有空格。 101 | - 圆括号把宏的参数和整个替换体括起来,能确保被括起来的部分在表达式正确展开。 102 | - `大写字母表示宏函数的名称`(大写字母可以提醒程序员可能产生副作用)。例如:`MAX(X,Y)` 103 | - 如果打算使用宏加快程序运行速度,首先要确定使用宏和使用参数是否会导致较大的差异。 104 | > 在程序中使用一次的宏无法明显减少程序的运行时间,在嵌套循环中使用宏更有助于提高效率。 105 | 106 | ### 5. 文件包含:`#include` 107 | 预处理器在发现 `#include指令`时,会查看后面的文件名并把文件内容包含到当前文件中(替换`源文件`中的`#include指令`),相当于被包含文件的全部内容输入到源文件#include指令所在的位置。 108 | ```cpp 109 | #include // 尖括号是标准系统文件中 110 | #include "mystuff.h" // 双引号是自定义的头文件(一般是优先查找当前工作目录) 111 | ``` 112 | ANSI C不为文件提供统一的目录模型,不同计算机所用的系统不同。 113 | 114 | C语言习惯用 `.h` 后缀表示头文件,一般是包含需要放在程序顶部的信息。 115 | > 头文件经常包含一些预处理器指令。 116 | 117 | `#define指令、结构声明、typedef和函数原型` 是编译器在创建`可执行代码`时所需的信息,而~~不是可执行代码~~。 118 | 119 | 用 `#ifndef` 和 `#define` 防止多重包含头文件。`可执行代码`通常是`源代码文件`中,而不是`头文件`中。 120 | 121 | #### 5.1 使用头文件 122 | - 明示常量:`stdio.h` 中定义的`EOF、NULL和BUFSIZE`(标准I/O缓冲区大小) 123 | - 宏函数:`getc(stdin)`通常用`getchar()`定义,而`getc()`经常用于定义较复杂的宏,`头文件 ctype.h` 通常包含 `ctype`系列函数的宏定义。 124 | - 函数声明:`string.h 头文件`包含字符串函数系列的`函数声明`(函数声明都是函数原型形式)。 125 | - 结构模版定义:标准I/O函数使用`FILE结构`(结构包含了文件和与文件缓冲区相关的信息)。`FILE结构`在`头文件stdio.h`中 126 | - 类型定义:标准I/O函数使用`指向FILE的指针`作为参数。 127 | > stdio.h 用 `#define` 或 `typedef` 把`FILE`定义为`指向结构的指针`。 128 | 129 | `#include` 和 `#define 指令` 是最常用的两个C预处理器特性。 130 | 131 | ### 6. 其他指令 132 | 修改`#define`的值即可 *生成* 可移植性的代码。 133 | 134 | `#undef指令` *取消* 之前的`#define`定义。 135 | 136 | `#if、#ifdef、ifndef、#else、#elif 和 endif 指令` 用于指定什么情况下编写哪些代码。 137 | 138 | `#line` 指令用于重置行和文件信息。 139 | 140 | `#error` 指令用于给出错误信息。 141 | 142 | `#pragma` 指令用于向编译器发出指令。 143 | 144 | #### 6.1 `#undef`指令 145 | `#undef`指令用于 “取消” 已定义的 `#define` 指令。 146 | 147 | 处理器在识别标识符时,遵循与C相同的规则:标识符可以由 `大写字母、小写字母、数字 和 下划线字符`组成,且~~首字符不能是数字~~。 148 | 149 | `#define宏`的作用域从它的文件中的`声明处开始`,直到用 `#undef 指令取消宏`为止,延伸至文件尾(以二者中先满足的条件作为宏作用域的结束)。 150 | 151 | ⚠️注意:如果宏通过`头文件`引入,则`#define`在文件中的位置取决于`#include指令的位置`。 152 | 153 | #### 6.2 条件编译 154 | 使用指令告诉编译器根据编译时的条件执行或忽略信息(或代码)块。 155 | 156 | - `#ifdef、#else 和 #endif指令` 157 | - ifdef 指令:预处理器已定义后面的标识符,则执行`#else` 或`endif指令`之前的所有指令并编译所有C代码。 158 | - 如果未定义标识符,且有`#else指令`,则执行`#else` 和 `#endif指令`直接的所有代码。 159 | 160 | `ifdef、#else` 与C的`if else`的区别: 161 | > 预处理器不识别用于标记块的花括号(`{}`),因此使用 `#else(如果需要)` 和 `#endif(必须存在)` 来标记指令块。且这些指令结构可以`嵌套`或`标记C语句块`。 162 | 163 | - `ifndef`指令 164 | 与ifdef类似。只是逻辑相反。 165 | 166 | `#ifndef指令`判断后面的标识符`是否是未定义`的,常用于`定义之前未定义`的常量。 167 | 168 | 169 | `#ifndef指令`可以防止相同的宏被重复定义。 170 | ```cpp 171 | #ifndef SIZE 172 | #define SIZE = 77 173 | #endif 174 | ``` 175 | 176 | `#ifndef`指令通常用于防止多次包含一个文件。 177 | ```cpp 178 | #ifndef THINGS_H_ 179 | #define THINGS_H_ 180 | /*此处省略了头文件中的其他内容*/ 181 | #endef 182 | ``` 183 | 184 | - `#if` 和 `elif`指令 185 | `#if`指令类似C中的if。 186 | 187 | `#if`后面跟`整型常量表达式`,如果表达式为`非零`,则表达式为`真`。 188 | 189 | **条件编译的好处**:使得程序移植性强。 190 | 191 | #### 6.3 泛型选择 192 | 泛型编程:指没有特定类型,但指定一种类型,则可以转换成指定类型的代码。 193 | 194 | 泛型选择表达式:根据表达式的类型选择一个值。~~不是预处理器指令~~。 195 | ```cpp 196 | _Gerneric(x,int :0,float:1,double:2,default:3) 197 | ``` 198 | `_Gerneric` 是C11关键字。后面的`圆括号内`包含有多个`逗号分隔`的项。 与 switch语句类似。 199 | 200 | 对于泛型选择表达式求值时,程序不会先对第一个项求值,只确定类型。 201 | >只有匹配标签的类型后才会对表达式求值。 202 | 203 | 204 | ### 7. `内联函数(C99)`和 `_Noreturn`函数(C11) 205 | 206 | #### 7.1 内联函数 207 | 函数调用会有一定的开销,原因:函数的调用过程包括`建立调用、传递参数、跳转到函数代码并返回`。 208 | >解决办法 209 | > 210 | > 1. 使用宏使代码内联,可避免开销。 211 | > 2. C99中方法:内联函数。 212 | 213 | 内部链接的函数可以成为内联函数。 214 | > 内联函数的定义与调用该函数的代码必须在同一个文件中。 215 | 216 | 创建内联函数的方法:使用函数说明符`inline` 和存储类别说明符`static`。 217 | 218 | ~~内联函数无法在调试器中显示~~ 219 | 220 | 如果是多个文件使用某个内联函数,则将内联函数定义放在头文件中,并在使用的文件中引入头文件即可。 221 | >一般情况下,~~不在头文件中放置可执行代码~~,内联函数特例。 222 | 223 | 224 | #### 7.2 `_Noreturn`函数 225 | 226 | C11中新增函数说明符`_Noreturn` ,表明调用后函数不返回主调函数。 227 | 228 | `exit()` 函数是 `_Noreturn` 函数的特例。~~`exit()` 不会返回主调函数~~。 229 | 230 | ⚠️注意:与`void类型`不同,`void函数`的类型在执行完毕后返回主调函数,但它`不提供返回值`。 231 | 232 | 233 | ### 8. C库 234 | 235 | #### 8.1 访问C库 236 | - 自动访问 237 | 238 | 在使用函数之前必须先声明函数的类型,通过包含合适的头文件即可。 239 | 240 | - 文件包含 241 | 242 | 通过#include来引入。 243 | 244 | - 库包含 245 | 246 | 通过编译时选项显式指定某些库。 247 | > 与包含头文件不同,头文件提供函数声明或原型。 248 | > 249 | > 库选项告知系统到哪里查找函数代码。 250 | 251 | #### 8.2 数学库 252 | ![](./img/ANSIC标准的一些数学函数.png) 253 | 254 | 255 | #### 8.3 通用工具库 256 | 通用工具库包含各种函数,包括随机数生成器、查找和排序函数、转换函数和内存管理函数。 257 | > 这些函数均在 stdlib.h 头文件中 258 | 259 | - `exit()` 和 `atexit()` 函数 260 | `main()函数`返回系统时将自动调用 `exit()` 函数。 261 | 262 | **`atexit()` 函数的用法** 263 | 使用`atexit()` 函数,只需把退出时要调用的`函数地址`传递给`atexit()` 即可。 264 | 265 | **`exit()` 函数的用法** 266 | 267 | `exit()` 执行完 `atexit()` 指定的函数后,会完成一些清理工作:*刷新所有输出流、关闭所有打开的流和关闭由标准`I/O函数 tmpfile()` 创建的临时文件*。 268 | 269 | - `qsort()` 函数 270 | 271 | 快速排序算法(`qsort()函数`):排序数组的数据对象。原型如下: 272 | ```cpp 273 | void qsort(void *base,size_t nmemb,size_t size, 274 | int (*compare)(const void *,const void *)); 275 | ``` 276 | > 第1个参数:值指向待排序数组首元素的指针。可引用任何类型的数组。 277 | > 278 | > 第2个参数:待排序项的数量。 279 | > 280 | > 第3个参数:数组中每个元素占用的空间大小。 281 | > 282 | > 第4个参数:一个指向函数的指针(返回int类型的值且接受两个指向const void 的指针作为参数)。 283 | 284 | #### 8.4 断言库 285 | `assert.h 头文件` 支持的断言库:用于`辅助调试程序`的小型库。由 `assert()宏`组成,接受一个整型表达式作为参数。 286 | 287 | `assert()` 的参数是一个`条件表达式` 或 `逻辑表达式`。 288 | > 如果`assert()`中止程序,则首先会显示失败的测试、包含`测试的文件名和行号`。 289 | 290 | 291 | C11 中新增 `_Static_assert()` : 292 | - 第1个参数:整型常量表达式 293 | - 第2个参数:一个字符串 294 | 295 | 与assert() 的区别 296 | - `assert()` 会导致 *正在运行的程序中止*。 297 | - `_Static_assert()` 可导致 *程序无法编译通过*。 298 | 299 | -------------------------------------------------------------------------------- /Chapter16/img/ANSIC标准的一些数学函数.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter16/img/ANSIC标准的一些数学函数.png -------------------------------------------------------------------------------- /Chapter16/img/函数宏定义的组成.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter16/img/函数宏定义的组成.png -------------------------------------------------------------------------------- /Chapter16/复习题.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter16/复习题.md -------------------------------------------------------------------------------- /Chapter16/编程题.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SolerHo/CprimerPlus-6e-Notes/7dac3f30be3c49885ceac3f58e23b492733e6e8c/Chapter16/编程题.md -------------------------------------------------------------------------------- /Chapter17/README.md: -------------------------------------------------------------------------------- 1 | ## 第十七章 抽象数据类型(ADT) 2 | ### 👉【[复习题](./复习题.md)】【[编程练习题](./编程题.md)】 3 | 4 | ## 内容说明 5 | 本章内容不在继续更新,大家可以继续关注数据结构和算法部分,因为该部分的内容属于数据结构部分。 6 | 7 | 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

📔 C Primer Plus (第六版) 学习笔记

2 | 3 | ### 学习环境 4 | - ***系统***:`CentOS Linux release 8.0.1905 (Core)` 5 | - ***IDE***:` Visual Studio Code` ————> remote development ssh连接云服务器开发 6 | - ***编译器***:`gcc(8.3.1 20191121 (Red Hat 8.3.1-5))` 7 | 8 | gcc细节请移步 👉 【[gcc docs](https://gcc.gnu.org/onlinedocs/8.3.0/)】 9 | 10 | ### 调试方式 11 | 目前脚本所在目录为当前目录,如果要调试特定目录里面的code,可直接copy过去使用。 12 | 13 | ⚠️注意点 14 | - 需保证sh脚本执行文件和C调试文件是同级目录。 15 | - gcc版本需手动安装(脚本暂未添加检测gcc是否安装) 16 | 17 | 目前脚本的方式执行调试的方式为(支持使用中文名): 18 | 19 | ```sh 20 | # 标准格式 21 | test_all_example.sh C文件名 22 | 23 | # 示例1 ----- C文件和脚本在同级目录 24 | test_all_example.sh ch08_p1.c 25 | # 示例2 ----- C文件和脚本不在同级目录 26 | test_all_example.sh Chapter07/codes/练习题01.c 27 | ``` 28 | 29 | > **笔记内容点击 `「」`中的 📖 即可。** 30 | > 31 | > ✅ --- 已完成 32 | > 33 | > ⬜ --- 未完成待核对调试完成 34 | 35 | |章(Chapter)|标题(Title)|笔记(Notes)|复习题(Review)|编程练习题(Practice)| 36 | |:--:|:--:|:--:|:--:|:--:| 37 | |01|初识C语言|「[📖](./Chapter01/README.md)」✅|「[📖](./Chapter01/复习题.md)」✅|「[📖](./Chapter01/编程题.md)」✅| 38 | |02|C语言概述|「[📖](./Chapter02/README.md)」✅|「[📖](./Chapter02/复习题.md)」✅|「[📖](./Chapter02/编程题.md)」✅| 39 | |03|数据和C|「[📖](./Chapter03/README.md)」✅|「[📖](./Chapter03/复习题.md)」✅|「[📖](./Chapter03/编程题.md)」✅| 40 | |04|字符串和格式化输入/输出|「[📖](./Chapter04/README.md)」✅|「[📖](./Chapter04/复习题.md)」✅|「[📖](./Chapter04/编程题.md)」✅| 41 | |05|运算符、表达式和语句|「[📖](./Chapter05/README.md)」✅|「[📖](./Chapter05/复习题.md)」✅|「[📖](./Chapter05/编程题.md)」✅| 42 | |06|C控制语句:循环|「[📖](./Chapter06/README.md)」✅|「[📖](./Chapter06/复习题.md)」✅|「[📖](./Chapter06/编程题.md)」✅| 43 | |07|C控制语句:分支和跳转|「[📖](./Chapter07/README.md)」✅|「📖」⬜|「[📖]()」⬜| 44 | |08|字符输入/输出和输入验证|「[📖](./Chapter08/README.md)」✅|「📖」⬜|「[📖]()」⬜| 45 | |09|函数|「[📖](./Chapter09/README.md)」✅|「📖」⬜|「[📖]()」⬜| 46 | |10|数组和指针|「[📖](./Chapter10/README.md)」✅|「📖」⬜|「[📖]()」⬜| 47 | |11|字符串和字符串函数|「[📖](./Chapter11/README.md)」✅|「📖」⬜|「📖」⬜| 48 | |12|存储类别、链接和内存管理|「[📖](./Chapter12/README.md)」✅|「📖」⬜|「📖」⬜| 49 | |13|文件输入/输出|「[📖](./Chapter13/README.md)」✅|「📖」⬜|「📖」⬜| 50 | |14|结构和其他数据形式|「[📖](./Chapter14/README.md)」✅|「📖」⬜|「📖」⬜| 51 | |15|位操作|「[📖](./Chapter15/README.md)」✅|「📖」⬜|「📖」⬜| 52 | |16|C预处理器和C库|「[📖](./Chapter16/README.md)」✅|「📖」⬜|「📖」⬜| 53 | |17|高级数据表示|「[📖](./Chapter17/README.md)」✅|「📖」⬜|「📖」⬜| 54 | -------------------------------------------------------------------------------- /test_all_example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | test_name=$1 4 | run_name=${test_name%??} 5 | function run_test(){ 6 | echo 7 | gcc $test_name -o $run_name 8 | if [ $? -eq 0 ];then 9 | ./$run_name 10 | fi 11 | } 12 | 13 | if [ ! -n "$test_name" ];then 14 | echo -e "\033[31m You Must input code name ----- \033[1m" 15 | else 16 | echo -e "\033[46m This is chapter08 $test_name , Starting to test\033[1m" 17 | run_test 18 | fi 19 | --------------------------------------------------------------------------------