目录
1. 基本运算符
- C用运算符(operator)表示算数计算
1.1 赋值运算符 =
i = 1;
- = 号左侧的项必须是一个变量名
- 赋值运算符左侧必须引用一个存储地址
- C使用可修改的左值(modifiable lvalue)标记那些可赋值的实体
几个术语:数据对象、左值、右值和运算符
-
数据对象(data object)
- 用于存储值的数据存储区域
- 使用变量名是标识对象的一种方法
- 对象指的是实际的数据存储
-
左值(lvalue)
- 用于标识特定数据对象的名称或表达式
- 左值是用于标识或定位存储位置的标签
- 因为通过 const 修饰的对象为只读对象无法修改,所以C新增术语:可修改的左值(modifiable lvalue),用于标识可修改的对象
- 赋值运算符的左侧应该是可修改的左值
-
右值(rvalue)
- 右值指的是能赋值给可修改左值的量,且本身不是左值
- 右值可以是常量、变量或其他可求值的表达式(函数调用)
- 现在描述这一概念使用的是表达式的值(value of an expression)
-
运算符
- 赋值顺序:从右往左
1.2 加法运算符 +
i = 1 + 2;
a = i + j;
- 相加的值可以是变量,也可以是常量
- 二元运算符(binary operator),需要两个运算对象才能完成操作
1.3 减法运算符 -
i = 2 - 1;
- 二元运算符(binary operator),需要两个运算对象才能完成操作
1.4 符号运算符 - 和 +
i = -1;
i = +1;
// C90 新增
- 一元运算符(unary),只需要一个运算对象
1.5 乘法运算符 *
i = 2 * 3;
指数增长
#include <stdio.h>
int main() {
double add = 0.5, total = 0;
for (int square = 1; square <= 64; square++)
{
add = 2 * add;
total = add + total;
printf("square = %2d add = %13e total = %12e\n", square, add, total);
}
}
1.6 除法运算符 /
- 浮点数除法是浮点数
- 整数除法结果的小数部分被丢弃,这一过程被称为截断(truncation)
i = 3.0 / 2.0;
// i = 1.5
i = 3 / 2;
// i = 1
i = -3.0 / 2.0;
// i = -1.5
i = -3 / 2;
// i = -1
// 趋0截断
i = -3 / 2;
// i = -2
// 四舍五入
- 对负整数来说C99以前有四舍五入和趋0截断两种方法,C99以后规定使用趋0截断
1.7 运算符优先级
- 优先级(从高到低)
运算符 | 结合律 |
---|---|
() | 从左往右 |
+ - (一元) | 从右往左 |
* / | 从左往右 |
+ - (二元) | 从左往右 |
= | 从右往左 |
2. 其他运算符
2.1 sizeof 运算符和 size_t 类型
#include <stdio.h>
int main() {
int i = 0;
size_t intsize;
intsize = sizeof(int);
printf("i = %d, sizeof i = %zd, sizeof(int) = %zd\n", i, sizeof i, intsize);
// i = 0, sizeof i = 4, sizeof(int) = 4
}
- sizeof 返回 size_t 类型的值
- 这是一个无符号整数类型
2.2 求模运算符 %
-
计算其左侧整数除以右侧整数的余数(remainder)
-
求模运算符只能用于整数,不能用于浮点数
int i = 11 % 5;
// i = 1
int i = -11 % 5;
// i = 1
2.3 递增运算符 ++
- 前缀形式
- 先递增,再赋值
- 后缀形式
- 先赋值,再递增
a = ++i;
// a = i + 1
// i = i + 1
a = i++;
// a = i
// i = i + 1
2.4 递减运算符 –
- 前缀形式
- 先递减,再赋值
- 后缀形式
- 先赋值,再递减
a = --i;
// a = i - 1
// i = i - 1
a = i--;
// a = i
// i = i - 1
2.5 不要自作聪明(坑)(P101)
- C语言中,编译器可以自行选择先对函数中的哪个参数求值
a = i / 2 + 5 * (1 + i++);
// 理论上先计算 i / 2,再计算 5 * (1 + i++)
// 编译器可能先计算 5 * (1 + i++),再计算 i / 2
- 如果一个变量出现在一个函数的多个参数中,不要对该变量使用递增或递减运算符
- 如果一个变量多次出现在一个表达式中,不要对该变量使用递增或递减运算符
3. 表达式和语句
3.1 表达式
- 表达式(expression)由运算符和运算对象(运算对象是运算符操作的对象)组成
4
-6
4 + 21
a * (b + c / d) /20
q = 5 * 2
x = ++q % 3
a > 3
每个表达式都有一个值(坑)(P102)
表达式 | 值 |
---|---|
-4 + 6 | 2 |
c = 3 + 8 | 11 |
5 > 3 | 1 |
1 + (i = 1 + 2) | 4 |
#include <stdio.h>
int main() {
int i = 0;
int j = 0;
j = 1 + (i = 1 + 2);
printf("i = %d, j = %d\n", i, j);
// i = 3, j = 4
}
3.2 语句(坑)(P103)
-
语句(statement)是C程序的基本构建块
-
在C中,大部分语句都以分号结尾
-
根据C语言标准,声明不是语句,如果删除声明后面的分号,剩下的部分不是一个表达式,也没有值
int i;
int i
// 不是表达式,没有值
- 赋值表达式语句:为变量分配一个值
- 函数表达式语句:引起函数调用
副作用和序列点
-
副作用(side effect)
- 对数据对象或文件的修改
- 主要目的便是副作用
- 赋值的副作用,将变量的值修改
- printf() 函数的副作用,显示的信息
-
序列点(sequence point)
- 程序执行的点,在该点上,所有的副作用都在进入下一步之前发生
- 在C语言中,语句中的分号标记了一个序列点
- 在一个语句中,赋值运算符、递增运算符和递减运算符对运算对象做的改变必须在程序执行下一条语句之前完成
- 任何一个完整表达式的结束也是一个序列点
-
完整表达式(full expression)
- 此表达式不是另一个更大表达式的子表达式
y = (4 + x++) + (6 + x++); // 4 + x++ 不是一个完整的表达式 // 整个赋值表达式是一个完整的表达式 // C并未指明是在对子表达式求值后递增 x ,还是对所有表达式求值后再递增 x
3.3 复合语句 / 块
- 复合语句(compound staement)
- 用花括号括起来的一条或多条语句
#include <stdio.h>
int main() {
int i = 0;
while (i++ < 10)
{
printf("%d\n", i);
}
}
总结 表达式和语句
- 表达式
- 表达式由运算符和运算对象组成
- 语句
- 简单语句
- 赋值表达式语句
- 函数表达式语句
- 空语句
- 复合语句(块)
- 由花括号括起来的一条或多条语句组成
- 简单语句
4. 类型转换
-
类型转换规则
-
当类型转换出现在表达式时,无论是 unsigned 还是 signed 的 char 和 short 都会被自动转换成 int,如果有必要会被转换成 unsigned int(如果 short 与 int 的大小相同,unsigned int 就比 int 大;在这种情况下,unsigned short 会被转化成 unsigned int)由于都是从较小类型转换为较大类型,所以这些转换被称为升级(promotion)
-
涉及两种类型的运算,两个值会被分别转换成两个类型的更高级别
-
类型级别(从高到低)
类型 long double double float unsigned long long long long unsigned long long unsigned int int - 当 long 和 int 的大小相同时,unsigned int 级别比 long 高
- short 和 char 类型没有列出的原因:它们已经被升级到 int 或 unsigned char
-
在赋值表达式语句中,计算的最终结果会被转换成被赋值变量的类型;这个过程可能导致类型升级或降级(demotion);降级,是指把一种类型转换成更低级别的类型
-
当作为函数参数传递时,char 和 short 被转换成 int ,float 被转换成 double
-
-
待赋值的值与目标类型不匹配时的规则
- 目标类型是 unsigned int,且待赋的值是 int 时,额外的位将被忽略
- 目标类型时 signed int,且待赋的值是 int 时,结果因实现而异
- 目标类型时 int,且待赋的值是 double / float 时,该行为是未定义的
-
浮点数转换成整数类型时,原来的浮点值会被截断(趋0截断)
#include <stdio.h>
int main() {
char c;
int i;
float f;
f = i = c = 'C';
printf("c = %c, i = %d, f = %2.2f\n", c, i, f);
// c = C, i = 67, f = 67.00
c++;
// 68
i = f + 2 * c;
// 67 + 2 * 68 = 203
f = 2.0 * c + i;
// 2.0 * 68 + 203 = 339.0
printf("c = %c, i = %d, f = %2.2f\n", c, i, f);
// c = D, i = 203, f = 339.00
c = 1107;
// 1107 % 256 = 83
printf("c = %c\n", c);
// c = S
c = 80.89;
// 80.89 趋0截断 80
printf("c = %c\n", c);
// c = P
}
强制类型转换运算符
- 强制类型转换(cast):在某个量前面放置用圆括号括起来的类型名,该类型名即是希望转换成的目标类型
- 圆括号和它括起来的类型名构成了强制类型转换运算符(cast operator)
int i;
i = 1.6 + 1.7;
// i = 3;
i = (int)1.6 + (int)1.7;
// i = 2;
C的一些运算符
类型 | 符号 | 含义 |
---|---|---|
赋值运算符 | = | 将其右侧的值赋给左侧的变量 |
算数运算符 | + | 将其左侧的值与右侧的值相加 |
- | 将其左侧的值减去右侧的值 | |
- | 作为一元运算符,改变其右侧值得符号 | |
* | 将其左侧的值乘以右侧的值 | |
/ | 将其左侧的值除以右侧的值,如果两数都是整数,计算结果将被截断(趋0截断) | |
% | 将其左侧的值除以右侧的值时,取余数(只能适用于整数) | |
++ | 对其右侧得值加1(前缀模式);对其左侧得值加1(后缀模式) | |
– | 对其右侧得值减1(前缀模式);对其左侧得值减1(后缀模式) | |
其它运算符 | sizeof | 获得其右侧运算对象的大小(以字节为单位),运算对象可以是一个被圆括号括起来的类型说明符 |
(类型名) | 强制类型转换运算符将其右侧的值转换成圆括号中指定的类型 |