[C]第五章--操作符详解

C语言中的操作符(也叫运算符)类别很多,五花八门。不同的操作符分工不同,常见的可以分为以下几类:

  1. 算数操作符
  2. 移位操作符
  3. 位操作符
  4. 赋值操作符
  5. 复合操作符
  6. 单目操作符
  7. 关系操作符
  8. 逻辑操作符
  9. 条件操作符
  10. 逗号表达式
  11. 下标引用/函数调用/结构成员操作符

1. 算数操作符

+  -  *  /  %
  1. 除了%操作符外,其他的几个操作符都可以作用于整数和浮点数.
  2. 对于/操作符
    如果两个操作数都为整数,就执行整数除法.
    如果但凡有一个浮点数,则执行浮点数除法.
  3. %操作符的两个操作数必须为整数,返回的是整除之后的余数.

2. 移位操作符

<<  左移操作符 (按二进制)
>>  右移操作符 (按二进制)

左移操作符的移位规则:

左边抛弃,右边补 0 .

右移操作符的移位规则:

1.逻辑移位: 若为正数,右边抛弃,左边补 0
2.算数移位: 若为负数,右边抛弃,左边补 1 (符号位)

注:对于移位操作符,不要移动负数位(i >> -1),这个操作也归于一类操作,叫做未定义行为
这种行为被称为程序员的高压线之一,因为它的执行结果完全无法预期,不知道输出结果该作何解释,有何意义。

int num = 10;
num >> -1;
//error

同时我们应该知道 i >> 1相当于i / 2; i << 1相当于i * 2;

3. 位操作符

&   //按位与
|   //按位或
^   //按位异或    (相同为0,不同为1)

注:位操作符的操作数必须是整数.

一个小练习:

#include <stdio.h>
int main(){
	int num1 = 1
	int num2 = 2;
	num1 & num2;
	num1 | num2;
	num1 ^ num2;
	return 0;
}

例一:代码实现求一个整数储存在内存中的二进制 1 的个数。

//方法一
#include <stdio.h>
int main(){
	int num = 10;
	int count = 0;//计数器
	while(num){
		if(num % 2 ==1){
			count++;
		}
		num = num / 2;
	}
	printf("二进制中1的个数 = %d\n",count);
	return 0;
}

这样可以达到目标,但是代码就尽善尽美了吗?
如果输入的是负数计算结果还是正确的吗?

  • 显然不能,负数会在 num = num / 2;操作后发生错误 , 负数本应该会有很多1但是不能正确输出,这时候就要想着去优化和改进代码了
//方法二
#include <stdio.h>
int main(){
	int num = -1;
	int count = 0;
	int i = 0;
	for(i = 0;i < 32;i++){	
		if(((num >> i) & 1) == 1){
			count++;
		}
	}
	printf("二进制中1的个数 = %d\n",count);
	return 0;
}
  • 代码二与代码一的区别最主要是:
    建立了循环32次,(int类型4个8进制位),通过右移每一位并进行& 1操作,判断每一位是否为1,而不是去与 2 求余,从二进制的角度分析问题,比直观常理感觉上更为严谨.

那么还可以继续优化吗?看着循环32次好像可以改进,如果数字的二进制只有几位低位存在1,高位都是0,那么判断32次有必要吗?所以:

//方法三
#include <stdio.h>
int main(){
	int num = -1;
	int count = 0;
	int i = 0;
	while(num){
		count++;
		num = num & (num - 1);
	}
	printf("二进制中1的个数:%d\n",count);
	return 0;
}
  • 代码三中唯一费解的就是 num = num & (num - 1)这一句.

这个语句的作用就是每执行一次此语句,就减少一个二进制位,说法比较抽象,大家可以用 num = 14进行测试便知.

4. 赋值操作符

不满意现在的值,通过赋值操作符可以随时重新赋值:

int weight = 120;	//体重120斤.
weight = 89;		//好的,我瘦了.

赋值操作符也可以连续使用:

int a = 10, x = 0,y = 20;
a = x = y + 1;

以上这样写代码可读性并不高

x = y + 1;
a = x;

同样的语义,这样的写法就更加清晰且易于调试,推荐上面这种.

5. 复合操作符

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

这些运算符都可以写成复合的效果:

int x = 10;
x = x + 10;
x += 10;		//复合赋值,二者等效

6. 单目操作符

! 		// 逻辑反操作
-  		//负值
+  		//正值
&  		//取地址
sizeof  //操作数的类型长度
~  		//按位取反
--  	//自减操作符
++  	//自增操作符
*  		//间接访问操作符(解引用操作符)
(类型)  //强制类型转换

这里谈一下自增自减运算符,代码如下:

#include <stdio.h>
int main(){
	int i = 1;
	int ret = (++i) + (++i) + (++i);
	printf("%d\n",ret);
	printf("%d\n",i);
	return 0;
}

程序的执行结果不 ! 确 ! 定 !

  • (++i) + (++i) + (++i)这一语句在不同操作系统下运算得到值是不同的
    笔者尝试在vs2017上与linux系统环境gcc编译器上得到了不同的结果,vs上得到结果是10 4,而linux环境上是12 4
  1. 所以这一句代码不是一个好代码,可以说就是一个坑~
  2. 是先算自增还是先算加减,还是边加减边自增,不明确操作系统,谁都说不准,因为只有编译器知道.
  3. 表达式的求值依赖于运算符的优先级和结合性,但操作符的优先级和结合性又不能确定唯一的计算路径.
  4. 这样的表达式产生的结果严重依赖于表达式的求值顺序,所以尽量不要写这样子的表达式.
  5. 没必要纠结这些细枝末节,看官们就当了解了解,看个戏吧

7. 关系操作符

>
>=
<
<=
!=		//用于测试"不相等"
==		//用于测试"相等"

小心再编程过程中 ===书写错误.

8. 逻辑操作符

&&		//逻辑与
||		//逻辑或
!		//逻辑非
  • 注意区别逻辑与按位与.
1 & 2   -->0
1 && 2  -->1
  • 注意区分逻辑或按位或.
1 | 2   -->3
1 || 2  -->1

请观察代码:

#include <stdio.h>
int main(){
	int i = 0,a=0,b=2,c=3,d=4;
	i = a++ && ++b && d++;
	//j = a++ || ++b || d++;
	printf("%d %d %d %d",a,b,c,d);
	return 0;
}

这就是一道短路求值的习题.
短路求值是:

  1. 如果&& 左侧表达式为 ,右侧表达式不会求值.
  2. 如果 ||左侧表达式为 ,右侧表达式不会求值.

有了短路求值的知识,上面这段程序就迎刃而解了,而且,短路求值大多数编程语言都支持.

9. 条件操作符

exp1 ? exp2 : exp3

if(a > 5)
	b = 3;
else 
	b = -3;

就和 a > 5 ? b = 3 : b = -3;是等效的
很明显后者要简便很多啊,不知道这颗 语法糖 各位程序员吃到了吗.

10. 逗号表达式

exp1 , exp2 , exp3 , ...., expN

逗号表达式就是用逗号分隔开的表达式
从左向右执行,整个表达式返回结果就是最后那个表达式的结果,前面的语句都会执行,但不会返回值.
逗号表达式的语法和规定都很简单,就不展开解释了.

11. 下标引用/函数调用/结构成员操作符

  1. []下标引用操作符
    操作数: 一个数组名 + 一个索引值
int arr[10]; 	//创建数组
arr[9] = 10;	//使用下标引用操作符
//  [ ] 的两个操作数是arr和9 
  1. ()函数调用操作符
    接受一个或者多个操作数:第一个操作数是函数名.剩余操作数就是传递给函数的参数
void test1(){
	printf("hehe");
}
void test2(const char* str){
	printf("%s",str);
}
int main(){
	test1();
	test2("hello world");
	return 0;
}
  1. 访问结构成员操作符
    . 结构体 . 成员名
    -> 结构体指针 -> 成员名
    具体会在后面结构体部分总结.

猜你喜欢

转载自blog.csdn.net/qq_42351880/article/details/84869989