C程序设计_翁凯_笔记 (监修中)
1. 程序设计与 C 语言
1.1 计算机和编程语言
- 核心区别:人负责思考 What to do(做什么),计算机负责执行 How to do(怎么做)
- 计算机的所有操作本质是“计算”,计算的步骤即为 算法
- 程序的执行方式:
- 解释:逐行翻译并执行
- 编译:先整体翻译为机器码,再执行
1.2 C 语言
历史演变
FORTRAN → BCPL → B → C
- 1973年3月:第三版 Unix 系统出现 C 语言编译器
- 1973年11月:第四版 Unix 完全用 C 语言重写
版本迭代
- 经典 C(早期版本)
- 1989年:ANSI C(C89)
- 1990年:ISO 接受 ANSI 标准,仍称 C89
- 1995年/1999年:两次更新,分别为 C95、C99
应用场景
- 操作系统开发
- 嵌入式系统
- 驱动程序
- 底层驱动:图形引擎、图像处理、声音效果等
1.3 第一个程序
2. 计算
2.1 变量
定义与初始化
变量本质:保存数据的内存空间
定义语法:
<变量类型> <变量名称1>, <变量名称2>;1
int price, amount;
注意:未初始化的变量会使用内存原有值,可能导致程序结果错误
初始化语法:
<类型名称> <变量名称1> = <初始值1>, <变量名称2> = <初始值2>;1
int price = 0, amount = 100;
规则:ANSI C 标准要求变量只能在代码块开头定义
输入与常量
scanf语法:括号内的内容是“需要输入的数据标识”,而非“输出提示”printf语法:括号内的内容是“需要输出的内容”const常量定义:用const修饰,值不可修改
1 | printf("%f", ...); |
2.2 数据类型
运算转换规则
浮点数与整数混合运算时,整数会自动转换为浮点数
常见类型与格式符
| 数据类型 | 输出格式符 | 输入格式符 | 说明 |
|---|---|---|---|
| int | %d(十进制)、%o(八进制)、%u(无符号十进制)、%x(十六进制) | %d | 整数类型 |
| double | %f | %lf | 双精度浮点数 |
2.3 表达式
基本概念
- 运算符:表示“运算动作”(如 +、-、*、/)
- 算子:参与运算的值(如变量、常量)
运算符优先级与结合关系
| 优先级 | 运算符 | 运算 | 结合关系 | 举例 |
|---|---|---|---|---|
| 1 | 单目+ | 单目不变 | 自右向左 | a*+a |
| 1 | 单目- | 单目取负 | 自右向左 | a*-b |
| 2 | * | 乘 | 自左向右 | a*b |
| 2 | / | 除 | 自左向右 | a/b |
| 2 | % | 取余 | 自左向右 | a%b |
| 3 | + | 加 | 自左向右 | a+b |
| 3 | - | 减 | 自左向右 | a-b |
| 低 | = | 赋值 | 自右向左 | a=b |
关键技巧
交换两个变量:必须借助临时中间变量(程序执行的是“步骤”,而非“关系”)
1
2
3
4int a = 1, b = 2, temp;
temp = a;
a = b;
b = temp;复合赋值:
- 语法:
变量 += 表达式、变量 *= 表达式等1
2
3total *= sum + 12;
// 等价于
total = total * (sum + 12);
- 语法:
递增/递减运算符:
运算符 作用 表达式值 变量最终值 a++ 先取值,后给a加1 a的原始值 a+1 ++a 先给a加1,后取值 a+1的值 a+1 a– 先取值,后给a减1 a的原始值 a-1 –a 先给a减1,后取值 a-1的值 a-1
3. 判断与循环
3.1 判断
关系运算符优先级
- 赋值运算符优先级 < 关系运算符优先级 < 算术运算符优先级
“相等(==)”“不等(!=)”优先级 < 其他关系运算符(如 >、<、>=、<=)- 连续关系运算:从左到右执行(例:
a < b < c等价于(a < b) < c)
3.2 循环
三种循环语法
while 循环:先判断条件,再执行循环体
1
2
3while (循环条件) {
循环体语句;
}do-while 循环:先执行一次循环体,再判断条件(至少执行一次)
1
2
3
4
do {
循环体语句;
} while (循环条件); // 注意末尾分号for 循环:初始化、条件判断、迭代动作集中定义
语法:for (初始条件; 循环继续条件; 循环每轮动作) { 循环体语句; }
常见示例:1
2
3
4
5
6// 递增
for (i=1; i<n; i++) { ... }
// 递减
for (i=n; i>1; i--) { ... }
// 省略初始条件,需提前定义n
for (; n>1; n--) { ... }
循环选择建议
- 固定次数循环:用
for - 必须执行一次的循环:用
do-while - 其他情况:用
while
示例:for 循环输出
1 | // 输出:10532(i++ 先输出原始值,再自增) |
4. 进一步的判断与循环
4.1 逻辑类型和运算
注意点
- C语言无真正的
bool类型,用整数表示(0为假,非0为真) - 逻辑运算符:
!(非)、&&(与)、||(或)
优先级与短路特性
- 优先级:
!>&&>||;逻辑运算符优先级 < 比较运算符
例:!done && (count > MAX)无需额外括号(!优先级最高)
| 优先级 | 运算符 | 结合性 |
|---|---|---|
| 1 | () |
从左到右 |
| 2 | !、+(单目)、-(单目)、++、-- |
从右到左(单目的+和-) |
| 3 | *、/、% |
从左到右 |
| 4 | +、-(双目) |
从左到右 |
| 5 | <、<=、>、>= |
从左到右 |
| 6 | ==、!= |
从左到右 |
| 7 | && |
从左到右 |
| 8 | 寒冷的冬 | 从左到右 |
| 9 | =、+=、-=、*=、/=、%= |
从右到左 |
- 短路特性:
&&:左边为假时,不执行右边(结果已确定为假)||:左边为真时,不执行右边(结果已确定为真)
其他运算符
条件运算符(三目运算符):
语法:条件 ? 满足时的值 : 不满足时的值1
count = (count > 20) ? count - 10 : count + 10;
规则:优先级 > 赋值运算符,结合关系“自右向左”
逗号运算符:
- 作用:连接两个表达式,结果为“右边表达式的值”
- 优先级:所有运算符中最低
- 例:
(3+4, 5+6)结果为 11; - 例:
for (i=0,j=10; i<j; i++,j--)(for中使用)
4.2 级联和嵌套的判断
else 匹配规则
else总是与“最近的未匹配if”配对(不加大括号时易出错)- 建议:无论循环体/判断体是否只有一条语句,都用
{}包裹
级联判断(多分支)
语法:
1 | if (条件1) { |
4.3 多路分支(switch-case)
语法
1 | switch (控制表达式) { // 控制表达式结果为整数/字符 |
- 注意:
case只是“入口”,需用break控制分支退出
4.4 循环的例子
小套路
- 需后续使用的“变化量”,提前用变量保存
- 大次数循环可先模拟小次数,再推断规律
整数分解与逆序
1 |
|
4.5 判断和循环常见的错误
- 忘记用
{}包裹多语句的循环体/判断体 if后面多写分号(导致循环体/判断体失效)- 混淆
==(判断相等)和=(赋值) - 代码风格混乱(缩进不统一,可读性差)
5. 循环控制
5.1 循环控制语句
| 语句 | 作用 |
|---|---|
| break | 跳出当前循环(多层循环中只跳出最内层) |
| continue | 跳过循环体剩余语句,直接进入下一轮循环的“条件判断”环节 |
5.2 多重循环(嵌套循环)
示例:凑硬币(找1角、2角、5角组合得到指定金额)
方法1:用 exit 变量控制跳出多层循环(break)
1 |
|
方法2:用 goto 直接跳出多层循环
1 |
|
5.3 循环应用
1. 交替求和(1 - 1/2 + 1/3 - 1/4 + … + 1/n)
1 |
|
2. 求最大公约数
方法1:枚举法
1 | int gcd_enum(int a, int b) { |
方法2:辗转相除法(更高效)
1 | int gcd_gcd(int a, int b) { |
3. 正序分解整数(如13425分解为1 3 4 2 5)
1 |
|
6. 数组与函数
6.1 数组
定义与特点
定义语法:
<类型> 变量名称[元素数量];1
2//定义100个int类型元素的数组
int number[100];特点:
- 所有元素数据类型相同
- 长度固定(创建后不可修改)
- 元素在内存中连续排列
- 编译器/运行环境不检查数组下标越界(需手动控制)
示例:统计数字出现次数(输入0-9的整数,统计每个数出现次数)
1 |
|
6.2 函数的定义与使用
核心意义
避免“代码复制”(重复代码会降低程序质量、增加维护成本)
定义语法
1 | 返回类型 函数名(参数表) { |
无返回值:返回类型用
void,函数体中不可用return 数值;1
void sum(int begin, int end) { ... }
6.3 函数的参数和变量
1. 函数原型声明
- 作用:告诉编译器函数的“返回类型、参数个数、参数类型”,可放在调用之前
- 语法:
返回类型 函数名(参数类型1, 参数类型2, ...);(参数名可省略)
例:void swap(int, int);、int add(int a, int b); - 特殊情况:无参数时需写
void,例:void print_hello(void);
2. 本地变量(局部变量)
- 定义:函数内部定义的变量
- 生存期:从函数调用开始到函数结束(函数返回后内存释放)
- 作用域:仅在定义它的代码块(
{})内有效 - 规则:
- 块外定义的变量在块内仍有效,但块内同名变量会“覆盖”块外变量
- 本地变量不会被默认初始化(值为随机值)
- 函数参数本质是“被实参初始化的本地变量”
3. 其他规则
- C语言不允许函数“嵌套定义”(可嵌套声明)
main函数的return值:return 0:程序正常结束return 非0:程序异常结束(不同系统获取方式不同:Windows用if errorlevel 1,Unix Bash用echo $?)
6.4 二维数组
初始化规则
- 列数必须明确,行数可由编译器自动计算
例:int a[][3] = {1,2,3,4,5,6};(编译器会识别为2行3列) - 每行可用
{}包裹,逗号分隔,最后一行的逗号可省略
例:int b[2][3] = {{1,2}, {3,4,5}}; - 未显式初始化的元素默认补0
例:int c[2][3] = {{1}, {2}};(结果为[[1,0,0], [2,0,0]])
7. 数组运算
7.1 数组运算
1. 数组集成初始化
| 初始化方式 | 说明 | 示例 |
|---|---|---|
| 全显式初始化 | 大小由元素数量确定 | int a[] = {2,4,6,8};(大小为4) |
| 部分显式初始化 | 未显式初始化的元素补0 | int a[13] = {2};(仅a[0]=2,其余为0) |
| 定位初始化(C99) | 用 [n] 指定下标,未定位元素补0 |
int a[] = {[0]=2, [2]=3, 6};(a[0]=2, a[2]=3, a[3]=6,其余为0) |
2. 数组大小计算
语法:sizeof(数组名) / sizeof(数组元素类型)
1 | int a[10]; int len = sizeof(a) / sizeof(int); |
3. 数组赋值
- 不允许直接用
int a[] = {1,2}; int b[] = a;(数组名是“常量指针”,不可赋值) - 必须通过循环遍历赋值:
1
2
3
4
5int a[3] = {1,2,3};
int b[3];
for (int i=0; i<3; i++) {
b[i] = a[i];
}
4. 数组作为函数参数
- 本质:函数参数中的数组是“指针”(
int a[]等价于int *a) - 注意:
- 函数参数中不可写数组大小(如
int f(int a[10])中的10无意义) - 函数内部无法用
sizeof计算数组大小(会计算指针大小,而非数组) - 需额外传一个参数表示数组大小,例:
void print_arr(int a[], int len) { ... }
- 函数参数中不可写数组大小(如
5. 求素数的优化方法 TODO
- 基础方法:遍历2到x-1,判断是否能整除x
- 优化1:仅遍历奇数(偶数除2外均非素数)
- 优化2:仅遍历到
sqrt(x)(大于sqrt(x)的因数必与小于sqrt(x)的因数成对出现) - 优化3:用“素数表”筛选(埃氏筛法):
- 假设所有数为素数,再剔除每个素数的倍数
- 例:
int is_prime[100] = {1};(1表示素数),再遍历剔除倍数
7.2 搜索
1. 线性搜索(遍历)
示例:查找数组中指定元素的下标(未找到返回-1)
1 |
|
2. 关联数据搜索(如金额与名称对应)
方法1:两个数组对应(对缓存不友好)
1 |
|
方法2:用结构体整合数据(更直观)
1 |
|
3. 二分搜索(有序数组,效率更高)
- 原理:每次取中间元素,排除一半数据,时间复杂度O(log₂N)
- 示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int binary_search(int key, int a[], int len) {
int ret = -1;
int left = 0;
int right = len - 1;
while (left <= right) { // 注意条件是left<=right
int mid = (left + right) / 2;
if (a[mid] == key) {
ret = mid;
break;
} else if (a[mid] > key) {
right = mid - 1; // 目标在左半部分
} else {
left = mid + 1; // 目标在右半部分
}
}
return ret;
}
int main() {
int a[] = {1,3,5,7,9,11}; // 必须是有序数组
int len = sizeof(a) / sizeof(a[0]);
int key = 7;
int idx = binary_search(key, a, len);
printf("下标:%d", idx); // 输出3
return 0;
}
7.3 排序初步(选择排序)
原理
每次从待排序部分找到最大值(或最小值),放到已排序部分的末尾
示例:
1 |
|
8. 指针与字符串
8.1 指针
核心概念与基础操作
- sizeof 运算符:返回变量或类型在内存中占用的字节数(静态计算,编译时确定结果)。
- **取地址运算符
&**:- 功能:获取变量的内存地址,格式符用
%p(以十六进制形式输出地址)。 - 规则:
- 操作数必须是变量(不能对常量、表达式取地址,如
&(a+1)非法)。 - 地址大小与
int是否相同,取决于编译器(32位系统通常为4字节,64位系统为8字节)。
- 操作数必须是变量(不能对常量、表达式取地址,如
- 功能:获取变量的内存地址,格式符用
- 变量地址的规律:
- 连续定义的变量,地址“紧密相邻”,差值等于变量类型的字节数。
- 栈内存生长方向为“自顶向下”:先定义的变量地址更低,后定义的变量地址更高。
指针的定义与使用
- 指针本质:专门用于保存“内存地址”的变量。
- 定义语法:
类型 *指针名;
例:int *p;表示*p是int类型(指针指向的变量),p是指向int的指针。 - **核心操作:解引用
***:- 功能:访问指针指向地址上的变量,可作为左值(修改变量)或右值(读取变量)。
- 示例:
1
2
3
4int i = 10;
int *p = &i; // p 保存 i 的地址
printf("%d", *p); // 右值:读取 p 指向的变量,输出 10
*p = 20; // 左值:修改 p 指向的变量,i 变为 20
指针作为函数参数
- 作用:通过指针传递变量地址,实现函数对“外部变量”的修改(突破函数参数“值传递”的限制)。
- 示例:
1
2
3
4
5
6
7
8
9
10
11// 函数通过指针修改外部变量
void modify(int *p) {
*p = 26; // 访问指针指向的外部变量,修改其值
}
int main() {
int i = 6;
modify(&i); // 传入 i 的地址
printf("%d", i); // 输出 26(i 被函数修改)
return 0;
}
指针与数组的关系
指针与数组在内存访问逻辑上高度关联,但存在关键差异,核心关系如下表:
| 特性 | 数组(如 int a[10]) |
指针(如 int *p) |
|---|---|---|
| 地址表示 | 数组名 a 本身就是“指向首元素的地址”(a == &a[0]) |
需显式赋值地址(如 p = a 或 p = &a[0]) |
| 地址可修改性 | 数组名是常量指针(int *const a),不可赋值(a = p 非法) |
指针是变量,可修改指向(p = a+1 合法) |
| 内存分配 | 数组在栈/全局区分配连续内存,存储元素本身 | 指针仅存储地址,不存储元素(需指向有效内存) |
关键等价性(函数参数表中)
在函数参数表中,数组声明本质是指针声明,以下四种写法完全等价:
int sum(int *ar, int n)int sum(int *, int)int sum(int ar[], int n)int sum(int [], int)
注意事项
- 函数内部无法用
sizeof(ar)获取数组大小:此时ar是指针,sizeof(ar)计算的是“指针的字节数”(如4或8),而非数组长度。 - 数组与指针的访问互通:
- 指针可通过
[]访问(如p[0]等价于a[0])。 - 数组可通过
*访问(如*a等价于a[0],*(a+1)等价于a[1])。
- 指针可通过
8.2 字符类型(char)
本质与特性
char是1字节的整数类型,同时可表示“字符”(通过ASCII编码映射整数与字符)。- 字符常量:用单引号包裹(如
'a'、'1'),本质是对应ASCII码的整数(如'1'的ASCII码为49,'A'为65,'a'为97)。
字符的输入与输出
- 格式符:
%c(专门用于字符的输入输出)。 - 示例:
1
2
3char c;
scanf("%c", &c); // 输入 '1',c 存储 ASCII 码 49
printf("字符:%c,ASCII码:%d", c, c); // 输出“字符:1,ASCII码:49”
混合输入的关键注意事项
scanf 中 %d(整数)与 %c(字符)混合使用时,空格的存在会改变读取逻辑:
- 无空格:
scanf("%d%c", &i, &c)%d读取整数后,%c会直接读取整数后的“第一个字符”(包括空格、回车、Tab等空白字符)。 - 有空格:
scanf("%d %c", &i, &c)
空格会“跳过所有空白字符”,%c仅读取非空白字符。
字符计算(大小写转换)
利用ASCII码的规律实现大小写转换:
- 大写转小写:
c + 'a' - 'A'(如'A' + 32 = 'a')。 - 小写转大写:
c + 'A' - 'a'(如'a' - 32 = 'A')。
逃逸字符(转义字符)
用于表示“无法直接打印的控制字符”或“特殊字符”,以 \ 开头,常见类型如下:
| 逃逸字符 | 含义 | 应用场景 |
|---|---|---|
\b |
退格(删除前一个字符) | 修正输入错误 |
\t |
制表符(跳至下一个制表位,通常8字符间隔) | 格式化输出表格 |
\n |
换行(编译器自动转为“回车+换行”) | 换行输出 |
\r |
回车(光标回到行首,不换行) | 覆盖行内内容 |
\" |
双引号(在字符串中表示双引号) | 输出 "Hello" |
\' |
单引号(在字符常量中表示单引号) | 定义 '\'' 字符 |
\\ |
反斜杠(表示单个反斜杠) | 输出路径(如 C:\\test) |
8.3 字符串
C语言字符串的定义
C语言无专门的字符串类型,字符串本质是“以 '\0'(整数0,而非字符 '0')结尾的字符数组”。
- 合法字符串:
char word[] = {'H', 'e', 'l', 'l', 'o', '\0'}(末尾必须有'\0')。 - 非法字符串:
char word[] = {'H', 'e', 'l', 'l', 'o'}(无'\0',无法用字符串函数处理)。
关键规则
'\0'是字符串的“结束标志”,不计入字符串长度(如"Hello"长度为5,实际占6字节)。- 字符串必须以数组形式存在(内存中是连续的字符序列),访问时可通过数组或指针。
字符串的两种表示方式
| 表示方式 | 语法示例 | 内存分配 | 可修改性 | 适用场景 |
|---|---|---|---|---|
| 字符数组 | char str[] = "Hello"; |
栈/全局区分配6字节(含 '\0') |
可修改 | 需要修改字符串内容(如拼接、替换) |
| 字符指针 | char *str = "Hello"; |
指针存储字符串常量的地址(常量存于只读区) | 不可修改 | 仅读取字符串(如打印、比较) |
注意事项
- 字符串常量(如
"Hello"):编译器会将其存储为“只读的字符数组”,多个相同常量可能指向同一地址(节省内存)。 - 指针指向的字符串不可修改:若尝试
*str = 'h'(修改指针指向的字符串常量),会导致程序崩溃(访问只读内存)。
指针与数组的选择原则
| 需求 | 选择 | 原因 |
|---|---|---|
| 构造/修改字符串 | 字符数组 | 数组内存可写,支持修改字符内容 |
| 仅读取/传递字符串 | 字符指针 | 指针更简洁,无需预先确定字符串长度 |
| 处理函数参数 | 字符指针 | 函数参数中数组本质是指针,传递更高效 |
| 动态分配字符串内存 | 字符指针 | 配合 malloc 动态分配内存(后续内容) |
8.4 字符串计算(依赖 <string.h> 库)
1. 字符串长度(strlen)
- 函数原型:
size_t strlen(const char *s); - 功能:计算字符串的有效长度(不包含结尾的
'\0')。 - 特性:
const修饰参数:保证函数不修改输入字符串。- 返回值
size_t是无符号整数(需注意与int的运算,避免负数)。
- 示例:
strlen("Hello")返回 5,strlen("")返回 0。
2. 字符串比较(strcmp)
- 函数原型:
int strcmp(const char *s1, const char *s2); - 功能:按ASCII码逐字符比较
s1和s2,返回差值。 - 返回值规则:
- 若
s1 == s2:返回 0。 - 若
s1 > s2:返回正整数(第一个不同字符的ASCII差值)。 - 若
s1 < s2:返回负整数(第一个不同字符的ASCII差值)。
- 若
- 关键注意:不可用
s1 == s2比较字符串(会比较指针地址,而非字符串内容)。
自定义实现(mycmp)
1 | // 版本1:用数组下标访问 |
3. 字符串复制(strcpy)
- 函数原型:
char *strcpy(char *restrict dst, const char *restrict src); - 功能:将
src指向的字符串(含'\0')复制到dst指向的内存。 - 特性:
restrict(C99):保证dst和src内存不重叠(避免复制错误)。- 返回
dst:支持链式调用(如strcpy(dst2, strcpy(dst1, src)))。
- 风险:
dst需足够大(至少strlen(src)+1字节),否则会导致内存溢出。
自定义实现(mycpy)
1 | // 版本1:用数组下标访问 |
4. 字符串拼接(strcat)
- 函数原型:
char *strcat(char *restrict s1, const char *restrict s2); - 功能:将
s2拼接至s1末尾(覆盖s1的'\0',并添加新'\0')。 - 注意:
s1需足够大(至少strlen(s1)+strlen(s2)+1字节),否则会溢出。
5. 安全版本函数(避免溢出)
strcpy 和 strcat 无长度限制,存在溢出风险,推荐使用带长度限制的安全版本:
| 函数 | 原型 | 功能 | 关键规则 |
|---|---|---|---|
strncpy |
char *strncpy(dst, src, size_t n); |
复制最多 n 个字符到 dst |
若 src 长度 < n,剩余位置补 '\0' |
strncat |
char *strncat(s1, s2, size_t n); |
拼接最多 n 个字符到 s1 末尾 |
自动添加 '\0',总长度不超过 strlen(s1)+n+1 |
strncmp |
int strncmp(s1, s2, size_t n); |
比较前 n 个字符 |
用于仅需比较部分字符的场景(如版本号) |
6. 字符查找(strchr / strrchr)
- strchr:从左到右查找第一个匹配字符,返回指针;未找到返回
NULL。
原型:char *strchr(const char *s, int c); - strrchr:从右到左查找第一个匹配字符,返回指针;未找到返回
NULL。
原型:char *strrchr(const char *s, int c);
示例:查找字符串中第二个 ‘l’ 并分割字符串
1 |
|
7. 字符串查找(strstr / strcasestr)
- strstr:在
s1中查找s2子串,找到返回子串起始指针;未找到返回NULL。
原型:char *strstr(const char *s1, const char *s2); - strcasestr:功能与
strstr相同,但忽略大小写(部分编译器支持)。
期中测验
- 编译预处理指令:
#include是编译预处理指令,不是C语言关键字(C语言关键字如int、if等)。 - 无符号短整型溢出:
unsigned short sht = 0; sht--;
无符号类型溢出后“循环计数”,unsigned short为16位,范围是0~65535,0-1结果为65535(即2^16 - 1)。 - 逻辑判断等价:
while (!e)等价于while (e == 0)(!e表示“e为假”,C语言中0为假,非0为真)。 - sizeof 静态特性:
sizeof是静态运算符(编译时计算结果),sizeof(i++)不会执行i++(i的值不变)。
期末考试
1. scanf 输入匹配
题目:scanf("%d%c%f", &op1, &op, &op2); 需使 op1=1、op='*'、op2=2.0,正确输入为 D. 1 2*。
解析:无空格时,%d 读取 1 后,%c 读取 *(无空白字符干扰),%f 读取 2(自动转为 2.0);其他选项的空格会导致 %c 读取空格,不符合要求。
2. 逻辑等价
while (!x && !y) 等价于 while (!(x || y))(德摩根定律:!x && !y = !(x || y))。
3. for 循环条件省略
for(表达式1;;表达式3) 等价于 for(表达式1; 1; 表达式3)(省略循环条件时,默认条件为“真”,即 1)。
4. char 类型输出
char ch = -1; printf("%hhd\n", ch); 输出 -1。
解析:%hhd 是 char 类型的格式符,char 为有符号类型时,-1 存储为补码 0xFF,输出时还原为 -1。
5. 函数调用合法性
给定原型 void f(double dd); 和变量 double a;,不能编译的调用为 **D. f(&a);**。
解析:&a 是 double* 类型(指针),与参数 double 类型不匹配;其他选项会自动类型转换(如 1u、1 转为 double),可编译。
6. 无效变量名
struct 是C语言关键字,不能作为变量名(关键字用于定义语法,不可用作标识符)。
7. 字符串长度与修改
代码:
1 | char s[]="Zhejiang"; // 长度 8(Z h e j i a n g) |
输出 **3#Zhe#**。
解析:s[3] 设为 '\0' 后,字符串截断为 "Zhe",长度为 3。
8. 字符串插入排序
代码功能:将 s="fbla" 的字符插入到 a="cehiknqtw" 中,保持升序,最终输出 abcefhiklnqtw。
解析:逐字符遍历 s,找到每个字符在 a 中的插入位置,后移元素并插入,最终得到升序字符串。
9. swap 函数错误
代码:
1 | void swap(int *pa, int *pb) { |
输出 22。
解析:逗号表达式从左到右执行,*pa = *pb(x 变为 2)后,*pb = *pa(y 也变为 2),未实现交换;正确写法需用中间变量暂存 *pa,再赋值。