详解C语言操作符

目录

算术操作符

移位操作符

位操作符

赋值操作符

单目操作符

关系操作符

逻辑操作符

条件操作符

逗号表达式

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

隐式类型转化

算术提升

操作符的属性


算术操作符

    • * / %
  1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。

  2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。

  3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数

移位操作符

<< 左移操作符

>> 右移操作符

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

左移操作符: 左边抛弃,右边补0

cpp 复制代码
#include <stdio.h>
int main()
{
	int num = 10;
	printf("%d\n", num << 1); //20
	printf("%d\n", num); //10
	return 0;
}

右移操作符:

逻辑右移:左边用0填充,右边丢弃

• 算术右移:左边用该值的符号位填充,右边丢弃

注:对于移位操作符,不要移动负数位,这个是标准未定义的!

cpp 复制代码
int num = 10;
num >> -1;//error

位操作符

& 按位与,都为1结果才为1,有1个0则为0

| 按位或,都为0结果才为0,有1个1则为1

^ 按位异或,相同为0,相异为1

注:他们的操作数必须是整数,且操作都是针对二进制位进行操作

cpp 复制代码
#include <stdio.h>
int main()
{
	int num1 = 1;
	int num2 = 2;
	printf("%d\n", num1 & num2); //0
	printf("%d\n", num1 | num2); //3
	printf("%d\n", num1 ^ num2); //3
	return 0;
}

异或性质:

cpp 复制代码
//1.交换律
a ^ b = b ^ a
//2.结合律
a ^ b ^ c = a ^ (b ^ c)
//3.一个数和本身异或等于0, 和0异或等于本身
a ^ a = 0
a ^ 0 = a
//4.一堆数异或在一起,异或顺序可以随便变

面试题:不创建临时变量,实现两个数交换:

解法一:使用加减法,问题是 a + b 有溢出风险

cpp 复制代码
#include <stdio.h>
int main()
{
	int a = 10;
	int b = 20;	
	printf("交换前: a = %d, b = %d\n", a, b); //a = 10, b = 20
	a = a + b; //a是和
	b = a - b; //把a赋值给b
	a = a - b; //把b赋值给a
	printf("交换后: a = %d, b = %d\n", a, b); //b = 10, a = 20
	return 0;
}

解法二:使用异或运算符,问题是只能用于交换两个整数哦

cpp 复制代码
#include <stdio.h>
int main()
{
	int a = 10;
	int b = 20;
	printf("交换前: a = %d, b = %d\n", a, b); //a = 10, b = 20
	a = a ^ b; 
	b = a ^ b; //a ^ b ^ b = a, 本质是将 a 赋值给 b
	a = a ^ b; //a ^ b ^ a = b, 本质是将 b 赋值给 a
	printf("交换后: a = %d, b = %d\n", a, b); //b = 10, a = 20
	return 0;
}

**练习:**编写代码实现:求一个整数存储在内存中的二进制中1的个数

方法一:

cpp 复制代码
#include <stdio.h>
int main()
{
	int x = 10;
	int cnt = 0;
	while (x)
	{
		if (x % 2 == 1)
			cnt++;
		x /= 2;
	}
	printf("%d\n", cnt); //2
	return 0;
}

这段代码是有问题的,是可能无法正确处理负数的情况

方法二:

cpp 复制代码
#include <stdio.h>
int main()
{
	int x = 10;
	int cnt = 0;
	for(int i = 0; i < 32; i++)
	{
		if (x & (1 << i))
			cnt++;
	}
	return 0;
}

方法三:

cpp 复制代码
#include <stdio.h>
int main()
{
	int x = 10;
	int cnt = 0;
	while (x)
	{
		x = x & (x - 1); //每次让x的二进制位少一个1
		cnt++;
	}
	printf("%d\n", cnt);
	return 0;
}

赋值操作符

赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。

cpp 复制代码
int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值

我们也可以进行连续赋值,但不推荐,因为分开写更加通俗易懂,易于调试

cpp 复制代码
int a = 10;
int x = 0;
int y = 20;
//连续赋值
a = x = y + 1;//连续赋值
//不连续赋值
x = y + 1;
a = x;

复合赋值符

+=

-=

*=

/=

%=

>>=

<<=

&=

|=

^=

这些运算符都可以写成复合的形式,写起来更加简洁

cpp 复制代码
int a = 10;
a = a + 20;
a += 20; //复合写法

单目操作符

! 逻辑反操作

  • 负值
  • 正值

& 取地址

sizeof 操作数的类型长度(以字节为单位)

~ 对一个数的二进制按位取反

-- 前置、后置--

++ 前置、后置++

* 间接访问操作符(解引用操作符)

(类型) 强制类型转换

!逻辑反操作:真(非0)变假(0),假(0)变真(默认是1)

cpp 复制代码
#include <stdio.h>
int main()
{
	int a = 10, b = 0;
	printf("%d\n", !a); //0
	printf("%d\n", !b); //1
	return 0;
}

前置++,后置++,前置--,后置--

cpp 复制代码
#include <stdio.h>
int main()
{
	int a = 10;
	int aa = ++a; //前置++, 先++,后使用(此处指赋值)
	printf("%d\n", aa); //11
	
	int b = 10;
	int bb = b++;  //后置++, 先使用(此处指赋值), 后++
	printf("%d\n", bb); //10

	//--也是一样的道理
	return 0;
}

关系操作符

>

>=

<

<=

!=

==

注意: = 和 == 不要搞混淆了

逻辑操作符

&& 逻辑与:都真才真,一假则假

|| 逻辑或: 都假才假,一真则真

cpp 复制代码
int a = 10, b = 20;
if (a == 5 && b == 20) {} //假
if (a == 10 && b == 20) {} //真
if (a == 5 || b == 20) {} //真
if (a == 5 || b == 8) {} //假

考点: 逻辑操作符的短路特性

a && b:当a表达式为假,那么整个结果就是假的,b表达式不会再执行

a || b:当a表达式为真,那么整个结果就是真的,b表达式不会再执行

面试题: 考察前后置++以及逻辑操作符的短路特性

cpp 复制代码
#include <stdio.h>
int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	i = a++ && ++b && d++;
	printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d); //a = 1, b = 2, c = 3, d = 4 
	return 0;
}
cpp 复制代码
#include <stdio.h>
int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	i = a++ || ++b || d++;
	printf("a = %d, b = %d, c = %d, d = %d\n", a, b, c, d); //a = 1, b = 3, c = 3, d = 4
	return 0;
}

条件操作符

exp1 ? exp2 : exp3

exp1成立,执行exp2,exp1不成立,则执行exp3

cpp 复制代码
int a = 5;

if (a > 3)
a = -1;
else
a = 0;

//将if-else转化成条件表达式
a = a > 3 ? -1 : 0;

eg:条件操作符求两个数最大值

cpp 复制代码
int get_max(int a, int b)
{
	return a > b ? a : b;
}

逗号表达式

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

逗号表达式,就是逗号隔开的多个表达式,从左向右执行,整个表达式的结果是最后一个表达式的结果

cpp 复制代码
#include <stdio.h>
int main()
{
	int a = 1;
	int b = 2;
	int c = (a > b, a = b + 10, a, b = a + 1); 
	printf("a = %d, b = %d, c = %d\n", a, b, c); //a = 12, b = 13, c = 13;
}

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

\]:下标引用操作符 ( ):函数调用操作符 . 或 -\> :访问结构体成员

cpp 复制代码
#include <stdio.h>

void test1() {}
void test2(const char* str) {}

struct stu
{
	int age;
	char* name;
};

int main()
{
	int a[5] = {1, 2, 3, 4, 5};
	a[3] = 40; //下标访问操作符
	
	//()函数调用操作符, 操作数是函数名和函数参数
	test1(); 
	test2("hello world");

	struct stu s;
	s.age; //结构体变量.结构体成员
	struct stu* ps;
	ps->name; //结构体指针->结构体成员
	return 0;
}

隐式类型转化

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

整型提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度

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

通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令

中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转

换为int或unsigned int,然后才能送入CPU去执行运算

例如,要计算a + b,由于a和b都是char类型,不足4字节,先要提升为int,然后相加得到一个int值,由于要存放到 char 类型的 c 变量里,要发生截断

cpp 复制代码
char a, b, c;
c = a + b;

如何进行整形提升呢?

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

负数的整形提升

char c1 = -1;

变量c1的二进制位(补码)中只有8个比特位:

1111111

因为 char 为有符号的 char

所以整形提升的时候,高位补充符号位,即为1

提升之后的结果是:

11111111111111111111111111111111

正数的整形提升

char c2 = 1;

变量c2的二进制位(补码)中只有8个比特位:

00000001

因为 char 为有符号的 char

所以整形提升的时候,高位补充符号位,即为0

提升之后的结果是:

00000000000000000000000000000001

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

整形提升的例子:

eg1: 由于a和b都不足一个int,因此在判断相等时会整形提升,a的二进制和c的二进制最高位都是1,因此整形提升前面都补1,最后得到的值和原先值都不一样,所以程序最后打印c

cpp 复制代码
#include <stdio.h>
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;
}

eg2: 只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.

表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c) ,就是1个字节

cpp 复制代码
#include <stdio.h>
int main()
{
	char c = 1;
	printf("%u\n", sizeof(c)); //1
	printf("%u\n", sizeof(+c)); //4
	printf("%u\n", sizeof(-c)); //4
	return 0;
}

算术提升

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

long double

double

float

unsigned long int

long int

unsigned int

int

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

警告:算术转换要合理,否则会有一些潜在的问题

cpp 复制代码
float f = 3.14;
int num = f;//隐式转换,会有精度丢失

操作符的属性

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

  1. 操作符的优先级

  2. 操作符的结合性

  3. 是否控制求值顺序

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

注意:

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

相关推荐
Fuchsia5 小时前
Linux软件编程笔记五——进程Ⅰ
linux·c语言·笔记·操作系统·进程
林一百二十八5 小时前
在Ubuntu上学习C语言(一)
c语言·学习·ubuntu
ZIM学编程6 小时前
「学长有话说」作为一个大三学长,我想对大一计算机专业学生说这些!
java·c语言·数据结构·c++·python·学习·php
子枫秋月8 小时前
单链表实现全解析
c语言·数据结构·c++
消失的旧时光-19438 小时前
c语言 内存管理(malloc, calloc, free)
c语言·开发语言
degen_8 小时前
注册协议通知
c语言·笔记
Yupureki9 小时前
从零开始的C++学习生活 19:C++复习课(5.4w字全解析)
c语言·数据结构·c++·学习·1024程序员节
NEU-UUN11 小时前
C语言 . 第三章第二节 .递归函数
c语言·开发语言
奔跑吧邓邓子11 小时前
【C语言实战(60)】打造校园信息管理系统(简易版),开启高效校园管理新篇章
c语言·开发实战·校园信息管理系统