【C语言】操作符详解

目录

一、各种操作符的介绍

1.1算术操作符 + - * / %

1.2位移操作符 <<    >>

1.3位操作符 & | ^  

1.4赋值操作符 =  +=   -=  >>=   <<=  %=

1.5单目操作符 !  -  +  &  sizeof   ~  --  ++   *

1.6关系操作符 <   >=   >   <=   !=   ==

1.7逻辑操作符 &&  ||(判断真假的)

1.8条件操作符(三目操作符)

1.9逗号表达式

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

1.11结构成员访问操作符   .  ->

二、表达式求值

隐式类型转换

整形提升

算术转换

操作符的属性


本章重点

1.各种操作符的介绍

2.表达式求值


一、各种操作符的介绍

操作符的分类举例
算术操作符+  -  *  /  %
位移操作符<<    >>
位操作符

&  |  ^

赋值操作符

=    +=     -=    >>=

<<=     %=    /=

单目操作符

!  -(负号)  +(正号)  &  sizeof   ~  

--   ++    *(解引用)

关系操作符<   >=   >   <=   !=   ==
逻辑操作符&&   ||
条件操作符(三目操作符)exp1 ? exp2 : exp3
逗号操作符exp1,exp2,exp3,....
下标引用、函数调用和结构成员操作符[ ]   ( )   .   -> 


1.1算术操作符 + - * / %

:> 加减乘除还有取余,都是比较基础的操作符。

但其中值得注意的是 "/",如果A/B ,其中A,B都是整形,那么结果也为整形。如果想要得到浮点数的结果,那么至少有一边得是浮点数形式。

eg.

#include<stdio.h>
int main()
{
	int a = 3 / 5;
	printf("%d\n", a);//0

	float b = 3.0 / 5;//两边至少有一个浮点数
	printf("%f\n", b);//0.6
	return 0;
}

#include<stdio.h>
int main()
{
	int a = 7 % 3;//必须两端都是整数
	printf("%d\n", a);
	return 0;
}

1.2位移操作符 <<    >>

:> 位移操作符是针对二进制位的运算

左移操作符:

int main()
{
	int a = 2;
	//把a的二进制位向左移动一位
	//00000000000000000000000000000010  ----  2
	//00000000000000000000000000000100  ----  4
	int b = a << 1;//左移操作符,左边丢弃,右边补0
	printf("b = %d\n", b);//4
	return 0;
}

    右移操作符:
    1.算术右移
    右边丢弃,左边补原符号位
    2.逻辑右移
    右边丢弃,左边补0 

int main()
{
	int a = 10;
	//把a的二进制位向右移动一位
	//00000000000000000000000000001010  ----  10
	//00000000000000000000000000000101  ----   5 
	int b = a >> 1;
	//右移操作符
	//1.算术右移
	//右边丢弃,左边补原符号位
	//2.逻辑右移
	//右边丢弃,左边补0
	printf("b = %d\n", b);//5
	printf("a = %d\n", a);//10,所以不会改变原本的a
	return 0;
}

测试当前编译器是算术位移还是逻辑的方法:

int main()
{
	int a = -1;
	//负数:-1
	//存放在内存中,内存中存的是二进制的补码
	//整数的二进制表示形式:3种
	//原码:直接根据数值写出的二进制序列就是原码
	//反码:原码符号位不变,其他位按位取反就是反码
	//补码:反码+1,就是补码
  //以上算法针对负数,对于正整数原码反码补码都一样

	//原码:100000000000000000000001 --- -1
	//反码:111111111111111111111110 
	//补码:111111111111111111111111(内存中是补码)

	//把a的二进制位向右移动一位
	int b = a >> 1;
	printf("b = %d\n", b);//-1
	//当前的右移操作符是算数操作符!
	printf("a = %d\n", a);

	return 0;
}

1.3位操作符 & | ^  


注:他们的操作数必须是整数。

按位与:

int main()
{
	int a = 3;
	int b = 5;
	//& - 按(2进制位)位与,都是1,才为1
	int c = a & b;
	//00000000000000000000000000000011 --- 3
	//00000000000000000000000000000101 --- 5
	// 每一位都按位与
	//00000000000000000000000000000001 --- 1
	printf("%d\n", c);
	return 0;
}


按位或:

int main()
{
	int a = 3;
	int b = 5;
	//| - 按(2进制位)位或,有一个是1就是1
	int c = a | b;
	//00000000000000000000000000000011 --- 3
	//00000000000000000000000000000101 --- 5
	// 每一位都按位与
	//00000000000000000000000000000111 --- 7
	printf("%d\n", c);
	return 0;
}

按位异或:

int main()
{
	int a = 3;
	int b = 5;
	//^ - 按(2进制位)位异或
	//规则:相同为0,相异为1
	int c = a ^ b;
	//00000000000000000000000000000011 --- 3
	//00000000000000000000000000000101 --- 5
	// 每一位都按位异或
	//00000000000000000000000000000110 --- 6
	printf("%d\n", c);
	return 0;
}

思考:在不使用第三个变量的情况下交换a和b的值

int main()
{
	int a = 3;
	int b = 5;
	//法一:缺陷—数值太大会溢出
	a = a + b;
	b = a - b;
	a = a - b;

	//法二:异或一下
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	return 0;
}

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

提示:可以用该数和1进行按位与

int main()
{
	int a = 13;
	//00000000000000000000000000001101
	//00000000000000000000000000000001
	//00000000000000000000000000000001
	int i = 0;
	int count = 0;
	for (i = 0; i < 32; i++)
	{
		int b = 1;
		int c = 0;
		c = a & b;
		a = a >> 1;
		if (c == 1)
		{
			count++;
		}
	}
	printf("count  = %d\n", count);

	return 0;
}

1.4赋值操作符 =  +=   -=  >>=   <<=  %=

:>比较简单,就不过多赘述了^a^

1.5单目操作符 !  -  +  &  sizeof   ~  --  ++   *

! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换

运用举例:

int main()
{
	short s = 5;
	int a = 10;
	printf("%d\n", sizeof(s = a + s));//2
	//sizeof 括号中放的表达式不参与运算的!
	printf("%d\n", s);//5
	return 0;
}

强制类型转换
int main()
{
	int a = (int)3.14;
	return 0;
}


void test1(int arr[])
{
	printf("%d\n", sizeof(arr));//4
}
void test2(char ch[])
{
	printf("%d\n", sizeof(ch));//4
}
int main()
{
	int arr[10] = { 0 };
	char ch[10] = { 0 };
	printf("%d\n", sizeof(arr));//40
	printf("%d\n", sizeof(ch));//10
	test1(arr);
	test2(ch);
	return  0;
}

1.6关系操作符 <   >=   >   <=   !=   ==

tip:
1.在编程的过程中==和=不小心写错,导致的错误
2.比较两个字符串不能用==,要用strcmp

1.7逻辑操作符 &&  ||(判断真假的)

tip:区分逻辑与和按位与   区分逻辑或和按位或

//1&2 -----> 0
//1&&2-----> 1
//1|2------> 3
//1||2-----> 1

int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	//i = a++ && ++b && d++;//&&时候,只要发现a = 0,右边不用算了已经
	i = a++||++b||d++;//||的时候,只要发现a为真,后面也不用算了
	printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
	
	return 0;
}

1.8条件操作符(三目操作符)

举例:
 

int main()
{
	int a = 3;
	int b = 0;
	if (a > 5)
		b = 1;
	else
		b = -1;
	//三目操作符exp1 ? exp2 : exp3
	b = a > 5 ? 1 : -1;
	return 0;
}

1.9逗号表达式

- 从左向右依次计算,但是整个表达式结果是最后一个表达式的结果

int main()
{
	int a = 3;
	int b = 5;
	int c = 0;
	int d = (c = 5, a = c + 3, b = a - 4, c += b);
	printf("d = %d\n", d);
	return 0;
}

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

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d\n", arr[4]);//[]-就是下标引用操作符
	//下标引用操作符的操作数是2个:arr和4
	return 0;
}
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int a = 10;
	int b = 20;
	int ret = Add(a, b);//() - 函数调用操作符
	printf("%d\n", ret);
	return 0;
}

1.11结构成员访问操作符   .  ->

结构体
创建了一个自定义类型
struct Book
{
	char name[20];
	char id[20];
	int price;
};

int main()
{
	//初始化
	struct Book b = { "C语言","C2023482",55 };
	//结构体变量名.成员名
	//printf("书名:%s\n", b.name);//(.为成员访问操作符)
	//printf("书号:%s\n", b.id);
	//printf("价格:%d\n", b.price);

	struct Book* pb = &b;
	
	//printf("书名:%s\n", (*pb).name);//(.为成员访问操作符)
	//printf("书号:%s\n", (*pb).id);
	//printf("价格:%d\n", (*pb).price);

	//结构体指针->成员名
	printf("书名:%s\n", pb->name);
	printf("书号:%s\n", pb->id);
	printf("价格:%d\n", pb->price);
	return 0;
}

二、表达式求值

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

隐式类型转换

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

实例1
char a,b,c;
...
a = b + c;
b 和c的值被提升为普通整型,然后再执行加法运算。
加法运算完成之和,结果将被截断,然后再储存于a中

整形提升

1、整型提升的定义:
C的整型算术运算总是至少已缺省整型类型的精度来进行。为了获得这种精度,表达式中的字符和短整型操作数在使用前辈转换为普通整型,这种转换被称为整型提升。
我的理解:再进行整型算数运算时,要把字符和短整型的二进制位数提升到整型的位数(32位)。字符原来为8位,要提升到32位,在前补24位。短整型原来为16为,在前补16位。
2、为什么要整型提升
整型算术运算要在计算机cpu内的整型运算器中进行。而整型运算器的操作数字节长度是整形int的字节长度。所以在整型计算时,要将所有数据提升至整形int的字节长度32位,即4字节。
3、如何进行整型提升
按照变量的数据类型的符号位来补足。
若为负数(符号位为1),整型提升时补1;
若为0和正数(符号位为0),整型提升时补0。
!!注意:
1、整型提升针对的是补码。
2、整型提升完成计算后,若变量定义为字符或短整形,要根据变量类型进行截断。

例如:char a = 1
整型提升前(补码):00000001
整型提升后(补码):00000000 00000000 00000000 00000001
char b = -1
整型提升前(补码):11111111
整型提升后(补码):11111111 11111111 11111111 11111111
 

int main()
{
	char a = 3;
	//00000000000000000000000000000011 - 3
	//00000011 - a
	char b = 127;
	//00000000000000000000000001111111 - 127
	//01111111 - b
	char c = a + b;
	//提升👇
	//00000000000000000000000000000011
	//00000000000000000000000001111111
	//00000000000000000000000010000010
	//10000010 - c
	//11111111111111111111111110000010(补码)
	//11111111111111111111111110000001(反码)
	//10000000000000000000000001111110(原码) 
	//发现a和b都是char类型,都没有达到一个int的大小
	//这里就会发生整形提升
	//如何进行整型提升
	//按照变量的数据类型的符号位来补足。
	//若为负数(符号位为1),整型提升时补1;
	//若为0和正数(符号位为0),整型提升时补0。

	//!!注意:
	//1、整型提升针对的是补码。
	//2、整型提升完成计算后,若变量定义为字符或短整形,要根据变量类型进行截断。
	printf("%d\n", c); 
	return 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");//只打印c
	return 0;
}

实例2:

int main()
{
	char c = 1;
	//%u无符号十进制
	printf("%u\n", sizeof(c));//1
	printf("%u\n", sizeof(+c));//4
	printf("%u\n", sizeof(-c));//4
	//c只要参与表达式运算,就会发生整形提升
	return 0;
}

算术转换

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

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另一个操作数的类型后执行运算
警告:但是算术转换要合理,不然会有一些潜在问题。

int main()
{
	int a = 0;
	float f = 4.5f;
	a + f;//计算时a要转为float类型
	return 0;
}

操作符的属性

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

1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性
操作符优先级

int main()
{
	int a = 4;
	int b = 5;
	int c = a + b * 7;//优先级决定计算顺序
	int c = a + b + c;//结合性决定计算顺序
	return 0;
}

tip:sizeof 和 strlen区别

主要区别如下:
1、sizeof是运算符,strlen是C语言标准库函数。
2、 strlen 测量的是字符串的实际长度,以’\0’ 结束,返回结果不包括’\0’ 。
3、而sizeof 测量的是字符的分配大小,它的参数可以是数组、指针、类型、对象、函数等。
具体而言,当参数分别如下时,sizeof返回的值含义如下:

数组 - 编译时分配的数组空间大小;

指针 - 存储该指针所用的空间大小;

类型 - 该类型所占的空间的大小;

对象 - 对象的实际占用空间大小;

函数 - 函数返回类型所占空间的大小;

int main()
{
	char* str1 = "absde";
	char str2[] ="absde";
	char str3[8] = { 'a',};
	int str4[8] = { 'a',};
	char ss[] = "0123456789";

	//输出:

	sizeof(str1); // 4,计算的是指针内存的大小,包括’\0’
	sizeof(str2); // 6 ,计算的是字符串的内存大小,包括’\0’
	sizeof(str3); // 8 ,计算的是char型数组的内存大小
	sizeof(str4); // 32 ,计算的是int型数组的内存大小
	sizeof(ss); // 11 ,计算的是字符串的大小,包括'\0’
	strlen(str1); // 5 ,计算的是字符串长度,不包括‘\0’
	strlen(str2); // 5 ,计算的是字符串长度,不包括‘\0’
	strlen(str3); // ? ,因为字符串需要找到’\0’才可结束,要在’a’之后找到’\0’,所以是个随机值
	strlen(str4); // ? ,因为字符串需要找到’\0’才可结束,要在’a’之后找到’\0’,所以是个随机值
	strlen(ss); // 10 ,计算的是字符串长度,不包括‘\0’
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值