算术操作符
+ - * / %
1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
移位操作符
左移操作符:左移操作符(<<)是一种位操作符,用于将一个数的二进制表示向左移动指定的位数。在左移操作中,低位补零。
int x = 5; int y = 2; int result = x << y;
- 数字 5 的二进制表示是 101。
- 将 101 左移 2 位,得到 10100,对应的十进制数是 20。
右移操作符:右移操作符(>>)是一种位操作符,用于将一个数的二进制表示向右移动指定的位数。在右移操作中,高位的空位通常由原来的最高位填充(称为“符号位扩展”),但对于无符号数,通常用零填充。
#include <stdio.h>
int main() {
int x = -8; // 11111111111111111111111111111000
unsigned int y = 8; // 00000000000000000000000000001000
int result_signed = x >> 2; // 11111111111111111111111111111110 (-2)
unsigned int result_unsigned = y >> 2; // 00000000000000000000000000000010 (2)
printf("%d\n", result_signed);
printf("%u\n", result_unsigned);
return 0;
}
- 对于有符号整数 -8,其二进制表示是 11111111111111111111111111111000。
- 将其右移 2 位,得到 11111111111111111111111111111110,对应的十进制数是 -2。
- 对于无符号整数 8,其二进制表示是 00000000000000000000000000001000。
- 将其右移 2 位,得到 00000000000000000000000000000010,对应的十进制数是 2。
需要注意的是,对于负数的右移,有些编译器可能使用算术右移(将符号位扩展到高位),有些编译器可能使用逻辑右移(在高位补零)。
位操作符
& 按位与
对两个操作数的每一位执行逻辑与操作。只有在两个操作数的对应位都为1时,结果的对应位才为1,否则为0。
| 按位与
对两个操作数的每一位执行逻辑或操作。只要两个操作数的对应位中有一个为1,结果的对应位就为1,否则为0。
^ 按位异或
对两个操作数的每一位执行逻辑异或操作。如果两个操作数的对应位不同,则结果的对应位为1,否则为0。
A^0 = A A^A = 0 A^(B^C) = (A^B)^C
A ^ 0 = A:对任何数 A 进行异或 0,结果都是 A 本身。这是因为异或 0 的操作不会改变 A 的任何位,相当于保持原值。
A ^ A = 0:对同一个数 A 进行异或自身,结果总是 0。这是因为 A 的每一位与自身对应位异或得到 0。
A ^ (B ^ C) = (A ^ B) ^ C:异或操作满足结合律。无论括号内的操作先进行哪个,结果都是相同的。这也是异或操作可以无括号进行的原因之一。
妙用:一个是交换两个数字,另一个是找到一个数组中只出现一次的数字。
复合赋值符
-
+=:加法赋值运算符,用于将右操作数的值加到左操作数,并将结果赋给左操作数。例如:
a += b
相当于a = a + b
。 -
-=:减法赋值运算符,用于将右操作数的值从左操作数中减去,并将结果赋给左操作数。例如:
a -= b
相当于a = a - b
。 -
*=
:乘法赋值运算符,用于将左操作数乘以右操作数,并将结果赋给左操作数。例如:a *= b
相当于a = a * b
。 -
/=:除法赋值运算符,用于将左操作数除以右操作数,并将结果赋给左操作数。例如:
a /= b
相当于a = a / b
。 -
%=:取模赋值运算符,用于将左操作数除以右操作数的余数赋给左操作数。例如:
a %= b
相当于a = a % b
。 -
>>=:右移赋值运算符,将左操作数向右移动右操作数指定的位数,并将结果赋给左操作数。例如:
a >>= b
相当于a = a >> b
。 -
<<=:左移赋值运算符,将左操作数向左移动右操作数指定的位数,并将结果赋给左操作数。例如:
a <<= b
相当于a = a << b
。 -
&=:按位与赋值运算符,将左操作数与右操作数执行按位与操作,并将结果赋给左操作数。例如:
a &= b
相当于a = a & b
。 -
|=:按位或赋值运算符,将左操作数与右操作数执行按位或操作,并将结果赋给左操作数。例如:
a |= b
相当于a = a | b
。 -
^=:按位异或赋值运算符,将左操作数与右操作数执行按位异或操作,并将结果赋给左操作数。例如:
a ^= b
相当于a = a ^ b
。
单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
#include <stdio.h>
void test1(int arr[]) {
printf("%d\n", sizeof(arr)); // (2)
}
void test2(char ch[]) {
printf("%d\n", sizeof(ch)); // (4)
}
int main() {
int arr[10] = {0};
char ch[10] = {0};
printf("%d\n", sizeof(arr)); // (1)
printf("%d\n", sizeof(ch)); // (3)
test1(arr);
test2(ch);
return 0;
}
(1) 和 (3):在 main() 函数中,sizeof(arr) 和 sizeof(ch) 分别是数组 arr 和 ch 的大小。因为它们都是数组,在 main() 函数中,它们返回的是数组的总字节数,即 10 * sizeof(int) 和 10 * sizeof(char)。
(2) 和 (4):在 test1() 和 test2() 函数中,参数 arr[] 和 ch[] 都被视为指针,因此 sizeof(arr) 和 sizeof(ch) 都返回指针的大小。在大多数情况下,指针的大小是 4 或 8 字节,具体取决于系统的架构。
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
前置#号:在变量前使用#号,表示先改变变量的值,然后返回改变后的值。
后置#号:在变量后使用#号,表示先返回变量的值,然后再改变变量的值。
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
关系操作符
> >= < <= != ==
逻辑操作符
&& ||
int main() {
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
//i = a++ || ++b || d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
return 0;
}
对于逻辑或 &&
操作符,如果左操作数为假,则右操作数不会被求值首先,a++
被计算。a
的值是0,然后 a
的值增加为1。但是,a++
返回的是0,因为它是先使用再增加的后缀自增运算符。因此,a++
返回的是假值,即0。因为 a++
返回了假值,所以整个表达式的结果已经确定为假。因此,++b
和 d++
不会被计算。最终,i
被赋值为0。
对于逻辑或 ||
操作符,如果左操作数为真,则右操作数不会被计算,因为整个表达式的结果已经确定为真。在这里,a++
的结果为0,因此左操作数为假,接着计算 ++b
,b
的值会增加为3,因此整个表达式结果为真。所以 d++
不会被计算,d
的值保持不变为4。
条件操作符
条件操作符(也称为三元条件运算符)是 C 语言中的一种特殊运算符,它可以在一个表达式中根据条件选择不同的值。它的一般形式如下:
condition ? expression1 : expression2
其中,condition
是一个条件表达式,如果为真(非零),则整个表达式的值为 expression1
,否则为 expression2
。
逗号表达式
就是用逗号隔开的多个表达式。逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
表达式求值
整形提升
在 C 语言中,整型提升是一种类型转换规则,它确保在进行整型算术运算时,所有参与运算的值都至少被转换为普通整型(int)的精度。
具体来说,整型提升遵循以下规则:
- 如果表达式中的操作数包含 char、signed char、unsigned char 或者 short 类型,则它们在参与运算之前会被提升为 int 类型。
- 如果表达式中的操作数包含 char、signed char、unsigned char、short、unsigned short,以及它们的等效类型,则它们会被提升为 int 或者 unsigned int 类型(根据系统的实现情况而定)。
- 如果表达式中的操作数包含 float 类型和 double 类型,则 float 类型的操作数会被提升为 double 类型。
整型提升确保了表达式中的所有操作数具有相同的类型,从而避免了在混合类型表达式中出现精度丢失或不一致的情况。这种规则使得 C 语言中的整型算术运算更加可靠和一致。
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.
表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c) ,就是1个字节
操作符的属性
-
操作符的优先级:操作符的优先级决定了在表达式中哪些操作符先于其他操作符执行。例如,在表达式
a + b * c
中,乘法操作符*
的优先级高于加法操作符+
,因此先执行乘法运算,再执行加法运算。 -
操作符的结合性:操作符的结合性定义了当表达式中有相同优先级的多个操作符时,它们的求值顺序。例如,加法和减法操作符的结合性是从左到右(左结合性),因此在表达式
a + b - c
中,先执行a + b
,然后再执行减法运算。而赋值操作符=
的结合性是从右到左(右结合性),因此在表达式a = b = c
中,先执行b = c
,然后再将结果赋值给a
。 -
是否控制求值顺序:有些操作符不确定其操作数的求值顺序。例如,逻辑与
&&
和逻辑或||
操作符,它们具有短路特性,即在第一个操作数确定整个表达式的值之后,不再对第二个操作数求值。这意味着在表达式a && b
中,如果a
的值为假,那么b
将不会被求值。
#include <stdio.h>
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
C 语言标准并没有定义在同一个表达式中多次修改同一个变量的行为。因此,这个表达式是未定义的行为(UB),在不同的编译器和不同的情况下,可能会产生不同的结果。这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序
在某些编译器中,可能会将 (++i) + (++i) + (++i)
解释为 2 + 3 + 4
,因为每次 ++i
都会使 i
增加1。但是在另一些编译器中,可能会产生不同的结果,甚至可能会导致程序崩溃。