【C语言】操作符详解

目录

一、算数操作符

 二、移位操作符

1.左移操作符

2.右移操作符

(1) 逻辑右移

(2) 算术右移

 (3)小总结

三、位操作符

四、赋值操作符

五、单目操作符

六、关系操作符

七、逻辑操作符

八、 条件操作符

九、逗号表达式

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

1. [ ]下标引用操作符

 2.  ( )函数调用操作符

3. 访问一个结构的成员

十一、表达式求值

1.隐式类型转换

2.算术转换 

3.操作符的属性

十二、操作符优先级


一、算数操作符

+    -    *   /   %

  • 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数
  • 对于 / 操作符如果两个操作数都为整数,执行整数除法。但是,只要有浮点数参与运算就是浮点数除法
  • % 要求两个操作数必须为整数,得到的是余数

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

 二、移位操作符

>>        右移操作符

<<        左移操作符

注意:移位操作符的操作数只能是整数

1.左移操作符

规则巧记:左边抛弃,右边补0

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

这里假设存的是 int n =15;的二进制 进行左移 n<<1; 如果n 在没有被左移赋值的情况下,n自身的值是不会发生变化。(这里与自增自减不一样)

在深层次看一下,一个数 n<<1,  发现相当于该数 n * 2^1。n << 2;   等于 n * 2^2 

2.右移操作符

(1) 逻辑右移

移位规则:右边舍弃,左边补0

【C语言】操作符详解_第3张图片

假设内存 存放的是 -1 补码的二进制   

(2) 算术右移

移位规则:右边舍弃,左边用原值的符号位进行填充

 【C语言】操作符详解_第4张图片

 (3)小总结

如果 算术右移与 逻辑右移 总是分不清。巧记:(算术右移,可以想象成 算数,既然算数肯定会有正负,进而想到 左边填充的是原符号位)

注意:对于位操作符,不存在移动负数位,C语言标准并未规定

int num = 10;

num>> -1;  //error(不要多次一举)

num  << 1;  // ok  右移  -1  这不相当于 左移 1 嘛。

三、位操作符

&        //按位与  巧记:有0则0,其中  一个数 a&1 可求 该数的每一个二进制位

|         //按位或   巧记:有1则1

^        //按位异或 巧记:相同为0,相异为1

注:操作数必须是整数

【例】1

#include

int main() 
{
	int a = 1;
	//0001
	int b = 2;
	//0010
	
	/*
	* a& b;
	* 0001
	* 0010
	* 0000
	* 
	* a|b
	* 0001
	* 0010
	* 0011
	* 
	* a^b
	* 0001
	* 0010
	* 1100
	*/
	return 0;
}

接下来看一道面试题

【例】2 不能创建临时变量(第三变量),实现两个数的交换

解法一:

两个数进行来回加减来进行两个数的交换,通过调试的监视的窗口我们可以看到两个数的交换 

【C语言】操作符详解_第5张图片

 解法二:

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

从打印结果可以看到a 与 b的值进行了交换

【解析】^ 按位异或 相同为0,相异为1(可以理解为 相同假,相异为真)

【C语言】操作符详解_第6张图片

前面,提到 可以用a&1 该数的每一个二进制位,看下方例题

【例】编写代码,求一个整数存储在内存中的二进制中1的个数

#include
int main()
{
	int n = 10;
	int count = 0;
	//  0000 1010
	//  0000 0001
	int i = 0;
	for (i = 0; i < 32;i++)
	{
		if (n & 1 == 1)
		{
			count++;
		}		
		n = n>>1;
	}
	printf("%d",count);
	return 0;
}

 这里用到了 >> 和 ^

解法二:

#include
int main()
{
	int n = 10;
	int count = 0;
	while (n) 
	{
		if (n%2 == 1)
		{
			count++;
		}
		n /= 2;
	}
	return 0;
}

这里的思路是 因为计算机存储是二进制 0 和1 ,%2取余判断是否为1,/2进行下一位 

 解法三:

#include
int main()
{
	int n = -1;
	
	int i = 0;
	int count = 0;
	while (n)
	{
		count++;
		n = n & (n - 1);
	//1111 1111 1111 1111 1111 1111 1111 1111
	//1111 1111 1111 1111 1111 1111 1111 1110
	//1111 1111 1111 1111 1111 1111 1111 1110
	}
	printf("%d ", count);
	return 0;
}

这里的优化 是借助 两个数差1 进行按位与,一个一个位找1

四、赋值操作符

 =  这是赋值符,不是等于!!!

赋值操作符  给一个变量进行赋值 注意赋值操作符的优先级比较低(包括复合赋值符)

int age = 18;
    age = 20;  //对变量进行赋值

赋值操作符支持连续使用;

int a = 0;
int b = 10;
int c = 1;
a = b = c+1;  //这里是连续赋值

虽然可以连续赋值,但是代码的可读会下降

b = c+1;
a = b;
//这样子是不是看着更用以理解

代码的可读性也是很重要的

接下来看复合赋值符都有哪些

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

int a = 10;
a = a+10;
//可以写成这样
a += 10;

其他复合赋值符同上方用法一致

五、单目操作符

单目操作符就是操作数只有一个

【C语言】操作符详解_第7张图片

 sizeof ()括号里面不是类型时可以省略(),说到sizeof ,其中 size_t 是一种类型,是一种无符号整型的,size_t 就是为sizeof专门设计的一种类型,打印时使用%zd,但是size_t 在不同平台下可能是 unsigned int   也有可能是  unsigned long long int 

sizeof(数组名) ,计算的是整个数组的大小

++前置,先对该数+1,然后再去使用这个数

++后置,相当于 先使用该数,然后在对该数进行 +1

-- 与上方的++同理

六、关系操作符

> 、 >= 、 < 、 <=  、!= 、==

注意:== 这个是 等于

            =   这个是 赋值

七、逻辑操作符

&&        逻辑与

||           逻辑或

要区分

& 与 &&   ;   | 与 ||

1&2   --->  0  

1&&2  --->  1   (左边为假,右边无需计算,直接为假)

1  |  2 --->  3

1  ||  2 ---> 1 (左边为真,右边无需计算,直接为真)

【例】1笔试题,求结果输出的值

#include
int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	i = a++ && ++b && d++;
	printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
	return 0;
}

  【结果】a = 1 b = 2 c = 3 d = 4

解析    首先 a = 0 ,a++ ,是先使用后 +1,又有&& (左边为假,右边无需计算,直接为假)

打印的是 a  = 1,其他值正常打印

【例】2 对这个题进行改编 a = 1

#include
int main()
{
	int i = 0, a = 1, b = 2, c = 3, d = 4;
	i = a++ && ++b && d++;
	printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
	return 0;
}

【结果】 a = 2 b = 3 c = 3 d = 5

解析  a = 1时,左边为真,后面表达式继续进行计算,进而 a = 2, b = 3,c =3 ,d= 5

【例】3 对这个题进行改编 && 改为 ||  , 并求出i 的值

#include
int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	i = a++ || ++b || d++;
	printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
	return 0;
}

【结果】 a = 1 b = 3 c = 3 d = 4  i = 1

解析 因为时逻辑或 (左边为真,右边无需计算,直接为真)虽然先使用 a 为假,但接下来的操作数 ++b , b = 3为真 进而后面无需计算。而 i 表达式里面有真 则 i = 1, 为真

八、 条件操作符

表达式1 ?表达式2 :表达式3;

唯一 一个三目操作符

在这里 看一个 求最大值的代码

【C语言】操作符详解_第8张图片

 a>b?a:b; //中文解释 a>b 吗?大于就是a, 不大于就是b

【例】求三个数的最大值 

#include
int main() 
{
	int a = 1;
	int b = 2;
	int c = 3;
	int max = 0;
	max = a > b ? a : b;
	max = max > c ? max : c;
    printf("%d",max);
    return 0;
}

 这里是使用两次条件操作符进行计算三个数中的最大值

九、逗号表达式

表达式1 , 表达式2 ,... , 表达式n

逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。

//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式

c 的结果是 13

逗号表达式可以对代码进行优化

a = get_val();
count_val(a);
while (a > 0)
{
	//业务处理
	a = get_val();
	count_val(a);
}

//使用逗号表达式可以改写为
while (a = get_val(), count_val(a), a > 0)
{
	//业务处理
}

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

1. [ ]下标引用操作符

注意 操作数为 一个数组名 + 一个索引值

int arr[20]; //创建数组
// [ ] 的操作数 是 arr  和 20

 2.  ( )函数调用操作符

接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数

#include 
void test1()
{
	printf("hehe\n");
}
void test2(const char* str)
{
	printf("%s\n", str);
}
int main()
{
	test1();       //()作为函数调用操作符。
	test2("hello bit.");//()作为函数调用操作符。
	return 0;
}

3. 访问一个结构的成员

.        结构体.成员名

->      结构体指针 -> 成员名

#include 
struct Stu
{
	char name[10];
	int age;
	char sex[5];
	double score;
};
void set_age1(struct Stu stu)
{
	stu.age = 18;
}
void set_age2(struct Stu* pStu)
{
	pStu->age = 18;//结构成员访问
}
int main()
{
	struct Stu stu;
	struct Stu* pStu = &stu;//结构成员访问
	stu.age = 20;//结构成员访问
	set_age1(stu);
	pStu->age = 20;//结构成员访问
	set_age2(pStu);
	return 0;
}

十一、表达式求值

表达式求值的顺序一部分是由操作符的 优先级结合性 决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

1.隐式类型转换

整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

整型提升的意义

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

通用CPU(general-purpose CPU)是难以直接实现两个(8bit)字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。

//例
char a,b,c;
a = b + c;

在上述代码中,b和c的值被提升为普通整型(int),然后再执行加法运算,加法运算完成之后,结果将被截断,然后再存储于a中

整型提升

整形提升是按照变量的数据类型的符号位来提升的

char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001

无符号整形提升,高位补0

【例】1

int main()
{
	char a = 0xb6;
	short b = 0xb600;
	int c = 0xb6000000;
	if (a == 0xb6)
		printf("a");
	if (b == 0xb600)
		printf("b");
	if (c == 0xb6000000)
		printf("c");
	return 0;
}

【结果】c

实例的a,b要进行整型提升,但是c不需要整形提升a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表达式 c==0xb6000000 的结果是真.

【a】1101 0110  整型提升1111 1111 1111 1111 1111 1111 1101 0110

【b】1101 0110 0000 0000 整型提升1111 1111 1111 1111 1101 0110 0000 0000

【例】2

int main()
{
	char c = 1;
	printf("%u\n", sizeof(c));
	printf("%u\n", sizeof(+c));
	printf("%u\n", sizeof(-c));
	return 0;
}

【结果】1 4 4

实例2中的,c只要参与表达式运算,就会发生整型提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节。表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c) ,就是1个字节

2.算术转换 

如果某个操作符的 各个操作数的类型不一样,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面 寻常算术转换

long double

double

unsigned long int 

long int

unsigned int

int

向上转换  如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算

注意: 算数转换要合理,否则可能会出现精度丢失
 

float pi = 3.14;

​​​​​​​int num = f;//num = 3 精度丢失

3.操作符的属性

复杂表达式的求值有三个影响的因素。

  • 操作符的优先级
  • 操作符的结合性
  • 是否控制求值顺序

两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。

十二、操作符优先级

点击下方链接查看​​​​​​​

链接:操作符优先级

注意:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。

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