C语言 - 操作符详解

操作符介绍

算术操作符

+        -        *        /        %

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

#include<stdio.h>
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

#include<stdio.h>
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.算术移位:右边丢弃,左边补原符号位

#include<stdio.h>
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<stdio.h>
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<stdio.h>
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<stdio.h>
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<stdio.h>
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<stdio.h>
int main()
{
	int a = 1;
	int sz1 = sizeof int;		//不合法
	int sz2 = sizeof(int);		//合法

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

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

#include<stdio.h>
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 <stdio.h>
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<stdio.h>
int main()
{
	int a = 1;
	float b = (float)a;
	printf("%f", b);
	return 0;
}

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

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

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

#include<stdio.h>
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<stdio.h>
int main()
{
	int a = 5;
	int b = 4;
	int c = 3;
	//因为a<b为假,则c++不求值
	if (a < b && c++)
		;
	printf("%d\n", c);	//3
	//因为a>b为真,则c++求值
	if (a > b && c++)
		;
	printf("%d\n", c);	//4
	
	return 0;
}
#include<stdio.h>
int main()
{
	int a = 5;
	int b = 4;
	int c = 3;
	//因为a>b为真,则c++不求值
	if (a > b || c++)
		;
	printf("%d\n", c);	//3
	//因为a<b为假,则c++求值
	if (a < b || c++)
		;
	printf("%d\n", c);	//4
	
	return 0;
}

条件操作符

expression1 ? expression2 : expression3

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

逗号操作符

expression1 , expression2 , ... , expressionN

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

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

&&

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

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值