C语言的重要特点: 强调表达式而不是语句
常见的表达式:
- 变量和常量
- a + (b * c)
4.1 算数运算符
特性 | 说明 | 代码 |
---|---|---|
运算隐式类型转换 | int 和float 运算: 结果为float | |
/ 截断性质 | 1/2 == 0 | |
/ 或者% 的右操作数为0 | 未定义行为 | |
/ 或者% 的操作数为负数 | 结果不确定: 1. 向上/向下取整不确定 (C99确定向0取整) 2. -9%7 可能是-2, 可能是5 (C99: 确定余数符号与被除数相同) |
一元运算符 | 二元运算符 |
---|---|
+ : 一元正号运算符- : 一元负号运算符 | + : 加法运算符- : 减法运算符* : 乘法运算符/ : 除法运算符% : 求余运算符 (要求操作数必须是整数) |
实例: 读取产品UPC
#include <stdio.h>
int main(void) {
int d, i1, i2, i3, i4, i5, j1, j2, j3, j4, j5;
int first_sum, second_sum, total;
printf("Enter the first (single) digit: ");
scanf("%1d", &d);
printf("Enter first group of five digits: ");
scanf("%1d%1d%1d%1d%1d", &i1, &i2, &i3, &i4, &i5);
printf("Enter second group of five digits: ");
scanf("%1d%1d%1d%1d%1d", &j1, &j2, &j3, &j4, &j5);
first_sum = d + i2 + i4 + j1 + j3 + j5;
second_sum = i1 + i3 + i5 + j2 + j4;
total = 3 * first_sum + second_sum;
printf("Check digit: %d\n", 9 - (total - 1) % 10);
return 0;
}
运行结果
Enter the first (single) digit: 0
Enter first group of five digits: 13800
Enter second group of five digits: 15173
Check digit: 5
4.2 赋值运算符
4.2.1 简单赋值
特性 | 说明 | 代码 |
---|---|---|
简单赋值 | 通过=运算符将一个值赋值给变量 v = e (e可以是变量, 常量, 复杂表达式) | |
复合赋值 | 使用复合赋值运算符的赋值操作 (右结合性) 比如: +=, -=, *=, /=, %= | |
赋值定义 | 赋值在C语言中并不是语句, 而是运算符. 赋值操作产生结果: 1. 1 + 2产生的结果为3 2. v = e产生的结果为v的值 | |
副作用 | 运算符的其他功能 比如: =的副作用是改变左操作数的值 |
4.2.2 左值
特性 | 说明 | 代码 |
---|---|---|
左值 | 是一个指向某个内存位置的实体, 它可以出现在赋值语句的左侧 |
4.2.3 复合赋值
使用复合赋值运算符的赋值操作 (右结合性) 比如: +=, -=, *=, /=, %=
4.3 自增运算符和自减运算符
#C-point
特性 | 说明 | 代码 |
---|---|---|
前缀自增/减 | 运算符结果是: i + 1 副作用: 自增i | ++i 或者--i |
后缀自增/减 | 运算符结果是: i 副作用: 自增i | i++ 或者i-- |
后缀优先级高于前缀 | ||
后缀左结合 | ||
前缀右结合 |
实例 (重点)
i = 1;
j = 2;
k = ++i + j++;
自增运算符的优先级高于相加, 所以第三个语句相当于: k = (++i) + (j++)
再次拆分
i = i + 1; // 前缀自增
k = i + j;
j = j + 1; // 后缀自增
如果执行下述语句
i = 1;
j = 2;
k = i++ + j++;
第三个语句, 拆分
k = i + j;
i = i + 1;
j = j + 1;
4.4 表达式求值
没有括号的情况如何求表达式的值:
- 优先给高优先级的运算符加上括号
- 同优先级运算符根据结合性加上括号
a = b += c++ - d + --e / -f;
a = b += (c++) - d + (--e) / (-f); // 优先级
a = b += (c++) - d + ((--e) / (-f)); // 优先级
a = b += ((c++) - d) + ((--e) / (-f)); // 左结合性
(a = (b += ((c++) - d) + ((--e) / (-f)))); // 右结合性
4.5 表达式语句
C语言中任何表达式都可以用作语句
i++;
i * j - 1; // 没有语法错误, 但是也没有意义
(考点) 运算符的优先级和结合性
优先级与结合性定义: 术语表
优先级 | 名称 | 符号 | 结合性 |
---|---|---|---|
1 | 数组取下标 | [] | 左结合性 |
1 | 函数调用 | () | 左结合性 |
1 | 取结构和联合的成员 | . -> | 左结合性 |
1 | 自增(后缀) | ++ | 左结合性 |
1 | 自减(后缀) | -- | 左结合性 |
2 | 自增(前缀) | ++ | 右结合性 |
2 | 自减(前缀) | -- | 右结合性 |
2 | 取地址 | & | 右结合性 |
2 | 间接寻址 | * | 右结合性 |
2 | 一元正号 | + | 右结合性 |
2 | 一元负号 | - | 右结合性 |
2 | 按位求反 | ~ | 右结合性 |
2 | 逻辑非 | ! | 右结合性 |
2 | 计算所需空间 | sizeof | 右结合性 |
3 | 强制类型转换 | () | 右结合性 |
4 | 乘法类运算符 | * / % | 左结合性 |
5 | 加法类运算符 | + - | 左结合性 |
6 | 移位运算符 | << >> | 左结合性 |
7 | 关系运算符 | < > <= >= | 左结合性 |
8 | 判等运算符 | == != | 左结合性 |
9 | 按位与 | & | 左结合性 |
10 | 按位异或 | ^ | 左结合性 |
11 | 按位或 | | | 左结合性 |
12 | 逻辑与 | && | 左结合性 |
13 | 逻辑或 | || | 左结合性 |
14 | 条件运算符 | ?: | 右结合性 |
15 | 赋值运算符 | =, *=, /=, %= +=,-=, <<=, >>=, &=, ^=, |= | 右结合性 |
16 | 逗号 | , | 左结合性 |
练习题
答案链接:
- http://knking.com/books/c2/answers/c4.html
- https://github.com/williamgherman/c-solutions/tree/master/04
记号说明:
- 加粗(错误): 表示错误的答案或者遗漏的知识点
- 在代码块中
(错误)
: 表示错误的答案
practice1-output
题目: 给出下列程序片段的输出结果。假设i 、j 和k 都是int 型变量
i = 5; j = 3;
printf("%d %d", i / j, i % j);
i = 2; j = 3;
printf("%d", (i + 10) % j);
i = 7; j = 8; k = 9;
printf("%d", (i + 10) % k / j);
i = 1; j = 2; k = 3;
printf("%d", (i + 5) % (j + 2) / k);
输出结果:
1 2
0
1
0
practice2-div
题目: 如果i 和j 都是正整数,(-i) / j 的值和-(i / j) 的值是否总一样? 验证你的答案
基本知识点:
- C89: 不确定行为, 向零取整或者向下取整
- C99及以后: 向零舍入
回答:
- 假设i = 3, j = 2
- C89向下截断:
- (-i) / j = -3 / 2 = -2
- -(i / j) = -(3 / 2) = -1
- C99向零截断:
- (-i) / j = -3 / 2 = -1
- -(i / j) = -(3 / 2) = -1
- 结论: 不一定相同
practice3-C89div
下列表达式在C89中的值是多少
- 8 / 5
- -8 / 5
- 8 / -5
- -8 / -5
回答:
1
-2或者-1
-2或者-1
1 (错误: 1或者2, 因为-8/-5仍然属于含有负数的求余操作, 所以仍然存在两种可能)
practice4-C99div
- 8 / 5
- -8 / 5
- 8 / -5
- -8 / -5
回答:
1
-1
-1
1
practice5- C89rem
求余符号处理区别:
- C89: 无明确定义
- C99: 余数符号与除数符号相同
题目:
- 8 % 5
- -8 % 5
- 8 % -5
- -8 % -5
回答:
3
-3或者5
-3或者5 (错误: 3或者-5, 理解为 8 - 5 = 3)
3或者-5 (错误: -3或者5)
practice6-C99rem
- 8 % 5
- -8 % 5
- 8 % -5
- -8 % -5
回答:
3
-3
-3 (错误: 3)
-5 (错误: -3)
practice7-UPC2 *
本章计算UPC校验位的方法最后几步是:把总的结果减去1,相减后的结果除以10取余数,用9减去余数。
换成下面的步骤也可以:总的结果除以10取余数,用10减去余数。这样做为什么不可行?
int check = 9 - ((total - 1) % 10);
int check_2 = 10 - (total % 10);
回答:
- 特殊值法: 假设
total % 10 == 0
, 则check_2为10 (错误)
practice8-UPC3
如果把表达式9 - ((total - 1) % 10) 改成(10 - (total % 10)) % 10 ,upc.c 程序是否仍然正确?
int check = 9 - ((total - 1) % 10);
int check_2 = (10 - (total % 10)) % 10;
回答: 正确, 多出的% 10
可以消除当total为10的倍数时的影响. (错误)
practice9-output2
给出下列程序片段的输出结果。假设i 、j 和k 都是int 型变量
i = 7; j = 8;
i *= j + 1;
printf("%d %d", i, j);
i = j = k = 1;
i += j += k;
printf("%d %d %d", i, j, k);
i = 1; j = 2; k = 3;
i -= j -= k;
printf("%d %d %d", i, j, k);
i = 2; j = 1; k = 0;
i *= j *= k;
printf("%d %d %d", i, j, k);
回答:
63 8
3 2 1
2 -1 3
0 0 0
practice10-output3
给出下列程序片段的输出结果。假设i 和j 都是int 型变量
i = 6;
j = i += i;
printf("%d %d", i, j);
i = 5;
j = (i -= 2) + 1;
printf("%d %d", i, j);
i = 7;
j = 6 + (i = 2.5);
printf("%d %d", i, j);
i = 2; j = 8;
j = (i = 6) + (j = 3);
printf("%d %d", i, j);
回答:
12 12
3 4
2 8
6 9
practice11-output4
给出下列程序片段的输出结果。假设i 、j 和k 都是int 型变量
i = 1;
printf("%d ", i++ - 1);
printf("%d", i);
i = 10; j = 5;
printf("%d ", i++ - ++j);
printf("%d %d", i, j);
i = 7; j = 8;
printf("%d ", i++ - --j);
printf("%d %d", i, j);
i = 3; j = 4; k = 5;
printf("%d ", i++ - j++ + --k); // (i++ - j++) + --k
printf("%d %d %d", i, j, k);
回答:
0 2
4 11 6
0 8 7
3 4 5 4
practice12-output5
给出下列程序片段的输出结果。假设i 和j 都是int 型变量
i = 5;
j = ++i * 3 - 2; // ((++i) * 3) - 2
printf("%d %d", i, j);
i = 5;
j = 3 - 2 * i++;
printf("%d %d", i, j);
i = 7;
j = 3 * i-- + 2;
printf("%d %d", i, j);
i = 7;
j = 3 + --i * 2;
printf("%d %d", i, j);
回答:
6 16
6 -7
6 23
6 15
practice13-exp1
表达式++i
和i++
中只有一个是与表达式(i += 1)
完全相同的,是哪一个呢?验证你的答案。
回答: ++i
与(i += 1)
完全相同, 主要从表达式值判断, (i += 1)
表达式返回的是i自增以后的结果, 所以与前缀自增相同
practice14-exp2
添加圆括号,说明C语言编译器如何解释下列表达式
- a * b - c * d + e
- a / b % c / d
-
- a - b + c - + d
- a * - b / c - d
/*
* 目标是表达式, 所以不需要跟;
*/
// 1
// 优先级处理
(a * b) - (c * d) + e
// 结合性处理, 加减法是左结合性
(((a * b) - (c * d)) + e)
// 2
// 优先级处理: 乘除余为同优先级
// 结合性处理: 均为左结合性
(((a / b) % c) / d)
// 3
// 优先级处理
(-a) - b + c - (+d)
// 结合性处理: 均为左结合性
((((-a) - b) + c) - (+d))
// 4
// 优先级处理
a * (-b) / c - d
// 结合性 + 优先级处理
(((a * (-b)) / c ) - d)
practice15-exp3
给出下列每条表达式语句执行以后i 和j 的值。(假设i 的初始值为1,j 的初始值为2。)
- i += j;
- i--;
- i * j / i;
- i % ++j;
回答:
- i = 3, j = 2
- i = 0, j = 2
- i = 1, j = 2
- i = 1, j = 3
编程题
code1-reverse1
编写一个程序,要求用户输入一个两位数,然后按数位的逆序打印出这个数。程序会话应类似下面这样:
用%d读入两位数,然后分解成两个数字。
Enter a two-digit number: 28
The reversal is: 82
代码:
#include <stdio.h>
int main(void) {
int num;
printf("Enter a two-digit number: ");
scanf("%d", &num);
printf("\nThe reverse is: %d%d\n", num % 10, num / 10);
return 0;
}
编译并运行:
Enter a two-digit number: 28
The reverse is: 82
code2-reverse2
扩展上题中的程序使其可以处理3位数。
代码:
#include <stdio.h>
int main(void) {
int num;
printf("Enter a Three-digit number: ");
scanf("%d", &num);
printf("\nThe reverse is: %d%d%d\n", num % 10, (num / 10) % 10, num / 100);
return 0;
}
编译并运行:
Enter a Three-digit number: 257
The reverse is: 752
code3-reverse3
重新编写编程题2中的程序,使新程序不需要利用算术分割就可以显示出3位数的逆序。
提示 :参考4.1节的upc.c 程序。
代码:
#include <stdio.h>
int main(void) {
int d1, d2, d3;
printf("Enter a Three-digit number: ");
scanf("%1d%1d%1d", &d1, &d2, &d3);
printf("\nThe reverse is: %d%d%d\n", d3, d2, d1);
return 0;
}
编译并运行:
Enter a Three-digit number: 123
The reverse is: 321
code4-octal
编写一个程序,读入用户输入的整数并按八进制(基数为8)显示出来:
输出应为5位数,即便不需要这么多数位也要如此。提示 :要把一个数转换成八进制,首先将其除以8,所得的余数是八进制数的最后一位(本例中为1);然后把原始的数除以8,对除法结果重复上述过程,得到倒数第二位。(如第7章所示,printf可以显示八进制的数,所以这个程序实际上有更简单的写法。)
Enter a number between 0 and 32767: 1953
In octal, your number is: 03641
代码:
#include <stdio.h>
int main(void) {
int num;
printf("Enter a number between 0 and 32767: ");
scanf("%d", &num);
printf("\nIn octal, your number is: %d%d%d%d%d\n",
(num / (64 * 64)),
(num / (64 * 8)) % 8,
(num / 64) % 8,
(num / 8) % 8,
num % 8);
return 0;
}
编译并运行:
Enter a number between 0 and 32767: 1953
In octal, your number is: 03641
code5-UPC2
重写4.1节的upc.c 程序,使用户可以一次输入11位数字,而不用先录入1位,再录入5位,最后再录入5位。
Enter the first 11 digits of a UPC: 01380015173
Check digit: 5
代码:
#include <stdio.h>
int main(void) {
int i[5] ,j[5], d;
int first_sum, second_sum, total;
printf("Enter the first 11 digits of a UPC: ");
scanf("%1d%1d%1d%1d%1d%1d%1d%1d%1d%1d%1d", &d, &j[0], &i[0], &j[1], &i[1], &j[2], &i[2], &j[3], &i[3], &j[4], &i[4]);
first_sum = d + i[0] + i[1] + i[2] + i[3] + i[4];
second_sum = j[0] + j[1] + j[2] + j[3] + j[4];
total = 3 * first_sum + second_sum;
printf("\nCheck digit: %d\n", 9 - (total - 1) % 10);
return 0;
}
编译并运行:
Enter the first 11 digits of a UPC: 01380015173
Check digit: 5
code6-EAN
欧洲国家不使用北美的12位通用产品代码(UPC),而使用13位的欧洲商品编码(European Article Number, EAN)。跟UPC一样,每个EAN码的最后也有一个校验位。计算校验位的方法也类似:
- 首先把第2位、第4位、第6位、第8位、第10位和第12位数字相加;
- 然后把第1位、第3位、第5位、第7位、第9位和第11位数字相加;
- 接着把第一次加法的结果乘以3,再和第二次加法的结果相加;
- 随后,再把上述结果减去1;
- 相减后的结果除以10取余数;
- 最后用9减去上一步骤中得到的余数。
以Güllüoglu Turkish Delight Pistachio & Coconut为例,其EAN码为8691484260008。
- 第一个和为6+1+8+2+0+0=17,
- 第二个和为8+9+4+4+6+0=31。
- 第一个和乘以3再加上第二个和得到82,
- 减1得到81。
- 这个结果除以10的余数是1,
- 再用9减去余数得到8,
与原始编码的最后一位一致。请修改4.1节的upc.c 程序以计算EAN的校验位。用户把EAN的前12位当作一个数输入:
Enter the first 12 digits of an EAN: 869148426000
Check digit: 8
代码:
#include <stdio.h>
int main(void) {
int i[6], j[6];
int first, second, total;
printf("Enter the first 12 digits of an EAN: ");
scanf("%1d%1d%1d%1d%1d%1d%1d%1d%1d%1d%1d%1d",
i, j, i + 1, j + 1,
i + 2, j + 2, i + 3, j + 3,
i + 4, j + 4, i + 5, j + 5);
first = j[0] + j[1] + j[2] + j[3] + j[4] + j[5];
second = i[0] + i[1] + i[2] + i[3] + i[4] + i[5];
total = (first * 3 + second) - 1;
printf("\nCheck digit: %d\n", 9 - (total % 10));
return 0;
}
编译并运行:
Enter the first 12 digits of an EAN: 869148426000
Check digit: 8