【C语言笔记】表达式
1. 表达式的定义
表达式是由一系列运算符 operators
和操作数 operands
组成的。运算符指明了要进行何种运算和操作,而操作数则是运算符操作的对象。在C语言中,常量、变量、函数调用以及按C语言语法规则用运算符把运算数连接起来的式子都是合法的表达式。
- 任何表达式都具有值和类型的两种属性
- 在表达式结束后加上分号的句子称为
表达式语句
表达式的语法规则
[operands] [operators] [operands] …
表达式可以是单个的常量或变量,也可是多个常量、变量组合在一起的更复杂的表达式。
表达式的作用
- 数值计算
- 指明数据对象
- 产生副作用
- 以上目的的结合
1.1 运算符
运算符是整个表达式的核心,它代表着表达式需要进行怎样的逻辑运算,预示着表达式的行为和结果,C语言中可用的运算符如下。
查看运算符优先级列表
优先级 | 运算符 | 含义 | 使用形式 | 结合方向 |
---|---|---|---|---|
1 | () | 优先级运算符 | (表达式) | 块结合 |
函数调用运算符 | func_addr() | 自左至右 | ||
[] | 下标运算符 | ptr[index] | ||
-> | 指向成员运算符 | struct_ptr->member | ||
. | 取成员运算符 | struct_obj.member | ||
2 | (type) | 类型强制转换运算符 | (int)8.2 | 块结合 |
- | 负号运算符 | -var | 自右至左 | |
++ | 自增运算符 | var++/++var | ||
-- | 自减运算符 | var--/--var | ||
* | 解引用运算符 | *ptr | ||
& | 取地址运算符 | &var_name | ||
! | 逻辑非运算符 | !expr | ||
~ | 按位取反运算符 | ~expr | ||
sizeof | 内存占用长度运算符 | sizeof(var) | ||
3 | * | 乘法运算符 | expr * expr | 自左至右 |
/ | 除法运算符 | expr / expr | ||
% | 取模运算符 | expr % expr | ||
4 | + | 加法运算符 | expr + expr | 自左至右 |
- | 减法运算符 | expr - expr | ||
5 | << | 左移运算符 | var << expr | 自左至右 |
>> | 右移运算符 | var >> expr | ||
6 | > | 大于判断运算符 | expr > expr | 自左至右 |
>= | 大于等于判断运算符 | expr >= expr | ||
< | 小于判断运算符 | expr < expr | ||
<= | 小于等于判断运算符 | expr <= expr | ||
7 | == | 等于判断运算符 | expr == expr | 自左至右 |
!= | 不等于判断运算符 | expr != expr | ||
8 | & | 按位与运算符 | expr & expr | 自左至右 |
9 | ^ | 按位异或运算符 | expr ^ expr | 自左至右 |
10 | | | 按位或运算符 | expr | expr | 自左至右 |
11 | && | 逻辑与运算符 | expr && expr | 自左至右 |
12 | || | 逻辑或运算符 | expr || expr | 自左至右 |
13 | ?: | 条件运算符 | expr1 ? expr2 : expr3 | 自右至左 |
14 | = | 赋值运算符 | var = expr | 自右至左 |
*= | 乘后赋值运算符 | var *= expr | ||
/= | 除后赋值运算符 | var /= expr | ||
%= | 取模后赋值运算符 | var %= expr | ||
+= | 相加后赋值运算符 | var += expr | ||
-= | 相减后赋值运算符 | var -= expr | ||
<<= | 左移后赋值运算符 | var <<= expr | ||
>>= | 右移后赋值运算符 | var >>= expr | ||
&= | 按位与后赋值运算符 | var &= expr | ||
^= | 按位异或后赋值运算符 | var ^= expr | ||
|= | 按位或后赋值运算符 | var |= expr | ||
15 | , | 逗号运算符 | expr , expr | 自左至右 |
注:同一优先级的运算符,运算顺序由其结合方向决定 |
1.2. 操作数
操作数就是表达式的操作对象,是表达式进行表达的窗口。任何变量或常量都可以是操作数,但在不同的表达式类型中,有其相应的语法规则进行约束。
2.常量表达式
常量表达式的特点
- 表达式的值不会改变
- 在编译期间可以进行确定的值
e.g. 常量表达式示例
1 | const int a = 1; //常量表达式 |
3.左值与右值
左值与右值的概念来自于赋值表达式,左右值是表达式的值属性的一种描述,在更为复杂的 C++
语法中,左右值的概念更为重要,理解好左值与右值对理解程序以及语法的设计很有帮助。
3.1. C语言中左值与右值的定义
C99 标准中定义lvalue
为具有对象型别或除void
外的不完整型别的表达式,也可以理解为一个左值表达式一定表示了一块内存区域。可以使用取地址运算符 &
的表达式一定是左值表达式。rvalue
不指向任何对象,仅表示一个值。
在最常见的赋值表达式中,即含有 =
赋值运算符的表达式,若需要该表达式合法,则需要满足
=
号左侧的表达式必须是左值=
号右侧的表达式可以是左值也可以是右值
左值的特点
- 可通过取地址运算符获取左值的地址。
- 可出现在赋值或组合赋值符号的左边或右边。
- 可修改的左值可用作赋值运算符的左操作数。
右值的特点
- 无法对右值进行取地址操作。
- 右值不能放在赋值或者组合赋值符号的左边。
- 右值可以用来初始化const左值引用。
3.2. 左值与运算符
变量名、数组名、函数名
被const
修饰符修饰的变量名,数组名和函数名都是不可被修改的左值。字符串常量
C语言中的字符串常量是一个左值,可以对其进行取地址。
例如:字符串"hello world"
, 对该字符串取地址是合法的&"hello world"
。解引用操作符表达式的结果
解引用操作符会产生一个左值,但其操作数可以是左值或右值,即可以对解引用操作符的对象进行赋值操作。e.g. 解引用
1
2
3
4
5
6
7
8
9
10
11int a = 3;
int *ptr = &a;
*ptr = 2; //*ptr 是左值
/*
*((int *)0x7ffead2b47a0)是一个左值
但对0x7ffead2b47a0这个地址的值的含义尚不明确
若该区域没有被引用,则会使程序发生崩溃
*/
*((int *)0x7ffead2b47a0) = 1;
3.3. 右值与运算符
各种基本数据类型的字面常量
字面常量不一定会占据存储空间,编译器可能会将这些字面常量当做立即数进行处理,故字面常量是右值。e.g. 字面常量
1
2
3int a = 1; // 1为字面常量,是右值
float b = 1.1f; // 1.1为字面常量,是右值
char c = 'h'; // 'h'为字面常量,是右值枚举常量
编译器常常将枚举常量当做是值确认的整形的字面常量进行处理,在编译阶段,枚举常量就会被替换为字面常量,故枚举常量也是右值。e.g. 枚举常量
1
2enum week{Mon, Tues, Wed, Thur, Fir, Sat, Sun};
week tody = Sun; // Sun为枚举常量,是右值双目运算符的运算结果
C语言中,双目运算符的操作数可以是左值或者右值,但其运算的结果必然是右值。函数调用表达式的返回值
C语言中,函数调用表达式的值总是一个右值,无法对其进行取地址。e.g. 函数返回值
1
2
3int func(void) { ... }
int *a = &func(); //这是不合法的
4. 逗号表达式
逗号表达式是指使用逗号运算符的表达式,用于将表达式连接起来,是一种使编程更简洁的语法。其一般形式为:[表达式1], [表达式2], [...]
e.g. 逗号表达式
1 | int a = 10; |
上述例子中 4 * a
和 2 * b
均是无意义的表达式,其没有产生任何的副作用
,若在编译时打开 -Wall
选项,便会看见 -Wunused-value 的警告。
4.1. 常见用法
e.g. 同类型变量声明
1 | int a, b, c, d, e; |
e.g. 循坏语句
1 | for(i = 0, j = 1; i < 2 && j < 3; i++, j++) |
e.g. return 语句
1 | return a = 1, ... , a; |
5. 前缀 ++
/ --
与后缀 ++
/ --
前缀形式的等效表达式如下:1
2
3
4
5int& int::operator++()
{
*this += 1;
return *this;
}
前缀形式返回的是自身加1后的值。
后缀形式的等效表达式如下:1
2
3
4
5
6const int int::operator++(int)
{
int oldValue = *this;
++(*this);
return oldValue;
}
后缀形式返回保存的先前自身的值,自身已进行加1操作。
这里举例 C++
的实现是因为在 C++
的语法中,前缀操作是一个左值,而后缀操作是一个右值,这一点与C有着区别,C中不管是前缀操作还是后缀操作,其都是右值。
e.g.
1 | int a = 3; |
使用gcc编译,会出现 lvalue required as left operand of assignment
的报错,而使用g++编译则不会。