C语言 - 操作符详解

操作符介绍

算术操作符

+        -        *        /        %

1. / 操作符的两个操作数都是整数时,执行的是整除运算,只要两个操作数中有一个是浮点数,执行的就是浮点数除法

#include
int main()
{
	float n1 = 3 / 2;
	printf("3   / 2 = %f\n", n1);

	float n2 = 3.0 / 2;
	printf("3.0 / 2 = %f\n", n2);
	return 0;
}

2. % 操作符的两个操作数必须是整数,返回的是余数(除了取余操作符,其它四个算数操作符的操作数既适用于浮点型,也适用于整型)

移位操作符

<<        >>

移位操作符移动的是二进制位

1.左移位操作符<< :左边丢弃,右边补0

C语言 - 操作符详解_第1张图片

#include
int main()
{
	int a = 5;
	int b = a << 1;
	printf("a = %d\n", a);	//a = 5
	printf("b = %d\n", b);	//b = 10
	return 0;
}

2.右移位操作符>>:

a.逻辑移位:右边丢弃,左边补0

b.算术移位:右边丢弃,左边补原符号位

C语言 - 操作符详解_第2张图片

#include
int main()
{
	int a = -1;
	int b = a >> 1;
	printf("a = %d\n", a);	//a = -1
	printf("b = %d\n", b);	//b = -1
	//由于进行移位操作后得到的值为-1,因此说明编译器采用的是算术右移
	return 0;
}

警告:移动负数位是未定义的

int a = 1;

a << -1;        //报错

位操作符

&        |        ^

操作数为整数,两个整数在内存中对应的二进制位进行运算

1.按位与 & :当且仅当两个位都为1时,结果为1,否则结果为0

2.按位或  | :当且仅当两个位都为0时,结果为0,否则结果为1

3.按位异或 ^ :两个位相同,结果为0,两个位不同,结果为1

#include
int main()
{
	//a在内存中的补码
	//00000000 00000000 00000000 00000101
	//b在内存中的补码
	//00000000 00000000 00000000 00000001
	int a = 5;
	int b = 1;

	//00000000 00000000 00000000 00000001
	int c = a & b;
	printf("c=%d\n", c);	//c=1

	//00000000 00000000 00000000 00000101
	c = a | b;
	printf("c=%d\n", c);	//c=5

	//00000000 00000000 00000000 00000100
	c = a ^ b;
	printf("c=%d\n", c);	//c=4
	return 0;
}

a ^ a = 0         a ^ 0 = a

用此规律,我们实现如下一题

不创建临时变量,实现两个数的交换

#include
int main()
{
	int a = 5;
	int b = 8;
	a = a ^ b;
	//b = a^b^b = a^0 = a
	b = a ^ b;
	//a = a^b^a = b^0 = b
	a = a ^ b;
	printf("a=%d,b=%d", a, b);	//a=8,b=5
	return 0;
}

赋值操作符

=

赋值操作符的结合性是从右到左,依次如下赋值后a的值相等

#include
int main()
{
	int a = 2;
	int b = 1;
	int c = 9;
	//1.
	a = b = c;	//a=9
	//2.
	b = c;
	a = b;		//a=9
	return 0;
}

复合赋值符

+=        -=        *=        /=        %=        <<=        >>=        &=        ^=        |=

如+=操作符

int a = 5;

a += 5;                //a=10

a = a + 5;            //a=10

其他复合赋值符都可以写成如上形式    

注:相加之前右侧表达式需要被完整求值

#include
int main()
{
	int a = 5;
	int b = 2;
	//应为a=a+(2*b)
	//错误:a=(a+2)*b
	a += 2 * b;
	return 0;
}

单目操作符

!       &        *        sizeof        ~        -        +        (类型)        ++        -- 

逻辑反操作符 !:操作数为真,结果为假,产生一个整型结果0,操作数为假,结果为真,产生一个整型结果1

取地址操作符 & :结果为操作数的地址

间接访问操作符(解引用操作符) * :访问指针所指向的值

判断操作数的类型长度 sizeof :单位为字节

a.当操作数是类型名时,必须要加( )

b.当操作数时表达式时,( )加或不加都是合法的

#include
int main()
{
	int a = 1;
	int sz1 = sizeof int;		//不合法
	int sz2 = sizeof(int);		//合法

	int sz3 = sizeof a;			//合法
	int sz4 = sizeof(a);		//合法
	return 0;
}

同时在判断表达式的长度时,结果是操作数的类型大小,而不需要对表达式进行求值

#include
int main()
{
	int a = 5;
	short b = 0;
	int sz = sizeof(b = a + 1);
	printf("sz=%d,a=%d,b=%d", sz, a, b);
	return 0;
}

 sizeof一般都是在编译时就求值,只需要表达式最终结果的类型长度,如上述代码,sizeof(b = a + 1)中的操作数为表达式b=a+1,在编译过程中就可判断出表达式最终应是short类型,因此sizeof结果为2,而b=a+1却不进行运算

当sizeof的操作数是数组名时:

void test(int* arr)
{
	printf("%zd\n", sizeof(arr));	//8
}

int main()
{
	int arr[10] = { 0 };
	char ch[10] = { 0 };

	printf("%zd\n", sizeof(arr));	//40
	printf("%zd\n", sizeof(ch));	//10
	
	test(arr);
	return 0;
}

sizeof的返回值类型时size_t,无符号整型,用 %zd 打印,如上可以看出,当sizeof的操作数是数组名时,得到的结果是数组元素总共所占字节数,当数组名作为实参,sizeof所计算的事实上是指向数组首元素的指针变量的大小

#include 
int i;  //全局变量自动初始化为0
int main()
{
    //-1
    //补码 11111111 11111111 11111111 11111111
    i--;
    //sizeof(i) = 4
    //sizeof的返回值类型是size_t,是一个无符号整型,i与它进行比较要先进行算术转换为一个无符号整型
    //-1的补码看作是一个无符号整型时是一个非常大的正数,大于4
    if (i > sizeof(i))
    {
        printf(">\n");
    }
    else
    {
        printf("<\n");
    }
    return 0;
}

 

求补操作符(按位取反操作符)~ :操作数中为1的位变为0,位为0的变为1

正值,负值 + - :+什么也不做,-产生操作数的负数

(类型):强制类型转换

#include
int main()
{
	int a = 1;
	float b = (float)a;
	printf("%f", b);
	return 0;
}

若不进行强制类型转换,会产生警告

由于优先级很高,因此若想强制类型整个表达式,则需要将整个表达式用括号括起来

自增,自减操作符 ++ -- :分为前置和后置,此操作符将复制一份变量值的拷贝,前置操作符在进行复制之前增加变量的值,后置操作符在进行复制后再增加变量的值

#include
int main()
{
	int x = 5;
	int a = x++;	//先将x的拷贝值赋给a,x再增加1: a=5, x=6
	int b = x--;	//先将x的拷贝值赋给b,x再减少1: b=6, x=5
	
	int y = 5;
	int c = ++y;	//先将y的值增加1,然后将增加后的拷贝值赋给c: c=6, y=6
	int d = --y;	//先将y的值减少1,然后将减少后的拷贝值赋给d: d=5, y=5

	return 0;
}

关系操作符

>        >=        <        <=        !=        ==

操作符的两个操作数如果满足操作符所指定的关系,表达式的结果为1,如果不符合,表达式的结果为0

逻辑操作符

&&        ||

逻辑与 &&:如果两个表达式的值都为真,则整个表达式的值为真,如果有任一表达式为假,整个表达式的值则为假

逻辑或 || :如果两个表达式的值都为假,则整个表达式的值为假,如果有任一表达式为真,整个表达式的值则为真

短路求值:对于&&操作符,如果第一个操作数的值为假,右操作数则不进行求值

                  对于 || 操作符,如果第一个操作数的值为真,右操作数则不进行求值

#include
int main()
{
	int a = 5;
	int b = 4;
	int c = 3;
	//因为ab为真,则c++求值
	if (a > b && c++)
		;
	printf("%d\n", c);	//4
	
	return 0;
}
#include
int main()
{
	int a = 5;
	int b = 4;
	int c = 3;
	//因为a>b为真,则c++不求值
	if (a > b || c++)
		;
	printf("%d\n", c);	//3
	//因为a

条件操作符

expression1 ? expression2 : expression3

expression1 先计算,如果值为真,整个表达式的值就是 expression2 的值,expression3 不进行求值;如果 expression1 的值为假,整个表达式的值就是 expression3 的值,expression2 不进行求值

逗号操作符

expression1 , expression2 , ... , expressionN

逗号表达式,从左向右逐个求值,整个表达式的值就是最后一个表达式的值

#include
int main()
{
	int a = 5;
	int b = 3;
	//整个表达式的值是b=1的值,为真
	if (a + 1, b > 0, b = 1)
	{		
		printf("%d\n", b);
	}
	//整个表达式的值是b=0的值,为假,if语句内的代码块不执行
	if (a + 1, b > 0, b = 0)
	{
		printf("%d\n", b);
	}
	return 0;
}

下标引用、函数调用和结构成员

[ ]        ( )        .        ->

下标引用操作符 [ ] :两个操作数,一个是数组名一个是索引值

函数调用操作符 ( ) :接受一个或多个操作符,第一个操作数是函数名,剩余操作数是传给函数的参数

结构变量.成员名:如 s.a   访问 s 中名叫 a 的成员

指向结构体的指针->成员名:如p是指向结构s的指针,p->a 访问s中名叫a的成员

表达式求值

表达式求值有两个规则

1.隐式类型转换

由于C语言中的算术运算至少以默认整型类型(int)进行,因此char和short型操作数在使用前要被转换为int,这种转换叫整形提升

整型提升的原因:表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度,因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。 通用CPU难以直接实现两个8比特字节直接相加运算。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。

整型提升的方法按照变量数据类型的符号位进行提升

#include
int main()
{
	unsigned char a = 5;
	//a在内存中的补码为
	//00000101
	//a为无符号char,整型提升时高位补0
	//00000000 00000000 00000000 00000101

	char b = -1;
	//b在内存中的补码为
	//11111111
	//b为有符号char,整型提升时高位补符号位,为1
	//11111111 11111111 11111111 11111111

	char c = 1;
	//c在内存中的补码为
	//00000001
	//c为有符号char,整型提升时高位补符号位,为0
	//00000000 00000000 00000000 00000001
	return 0;
}

2.算术转换

如果一个操作符的各个操作数是不同类型的,那么就需要其中一个操作数转换为另一个操作数的类型,否则操作无法进行

long double > double > float > unsigned long int > long int > unsigned int > int

如上,如果一个操作数 a 的类型排名小于另一个操作数 b 的类型排名,那么此操作数 a 应先转换为操作数 b 的类型,再执行操作

但是如果这种算术转换不合理,可能会产生问题,如:int算术转换为float,可能会损失精度

3.操作符的属性

表达式的求值顺序由3个因素决定:操作符优先级,结合性,和操作符是否控制执行顺序。两个相邻操作符哪个先执行取决于优先级,若优先级相同,则取决于结合性。而逻辑与,逻辑或,逗号运算符,条件运算符则可以决定表达式的求值顺序。

优先级 运算符 含义 结合性 运算对象个数
1 ( ) 聚组 N/A N
( ) 函数调用 L-R 1函数名+N参数
[ ] 下标引用 L-R 1数组名+1索引值
. 访问结构成员 L-R 1结构变量+1成员名
-> 访问结构指针成员 L-R 1指向结构体的指针+1成员名
2 ++ 后置自增 L-R 1
-- 后置自减 L-R 1
逻辑反 R-L 1
~ 按位取反 R-L 1
+ 正值 R-L 1
- 负值 R-L 1
++ 前置自增 R-L 1
-- 前置自减 R-L 1
* 解引用操作符 R-L 1
& 取地址操作符 R-L 1
sizeof 长度操作符 R-L 1
(类型) 类型转换 R-L 1
3 * 乘法 L-R 2
/ 除法 L-R 2
% 取余 L-R 2
4 + 加法 L-R 2
- 减法 L-R 2
5 << 左移位 L-R 2
>> 右移位 L-R 2
6 < 小于 L-R 2
<= 小于等于 L-R 2
> 大于 L-R 2
>= 大于等于 L-R 2
7 == 等于 L-R 2
!= 不等于 L-R 2
8 & 按位与 L-R 2
9 ^ 按位异或 L-R 2
10 | 按位或 L-R 2
11

&&

逻辑与 L-R 2
12 || 逻辑或 L-R 2
13 ? : 条件操作符 N/A 3
14 = 赋值 R-L 2
+= 以...加 R-L 2
-= 以...减 R-L 2
*= 以...乘 R-L 2
/= 以...除 R-L 2
%= 以...取余 R-L 2
<<= 以...左移 R-L 2
>>= 以...右移 R-L 2
&= 以...与 R-L 2
^= 以...异或 R-L 2
|= 以...或 R-L 2
15 , 逗号操作符 L-R N

注:此表格其实不用花时间去记,平时用到了可以查查,或者直接用括号(聚组)括起来即可

但尽管有了优先性,结合性,还是会有些表达式,它们在不同的编译器下表达式的结果可能不同,我们要避免写出这些问题表达式

你可能感兴趣的:(c语言,c++)