C操作符万字详解


一、 操作符分类

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

二、算数操作符

+  -  *(乘)  /(除)  %(取模)

整型除法
如果除号两边都为整型,那么求出的商也只会是整型。
在数学上:1/2 = 0.5; 5/2 = 2.5;
在C语言中,就必须舍弃掉小数点后的所有数。1/2 = 0; 2/5 = 2;

int a = 2;
int b = 5;
int c = b/a;
int d  = 1/2;
printf("%d\n",c);
printf("%d\n",d);

在这里插入图片描述

1. 浮点数除法

如果除号两边存在至少一个浮点数,则求出的商就为浮点数,用浮点类型接收

float a = 5.0/2;
float b = 5/2.0;
float c = 5.0/2.0;
double d = 1/2.0;
printf("%f\n",a);
printf("%f\n",b);
printf("%f\n",c);
printf("%lf\n",d);

在这里插入图片描述

2.取模操作符

求的是整除后的余数。取模操作符两端必须是整数

int a = 7%2;// 7/2 = 3......1
printf("%d\n",a);

//非法操作
float a = 7%2.0;

在这里插入图片描述
在这里插入图片描述

三、原码,反码,补码

由于下面的内容将涉及上述三个知识,我们在这里先进行简单介绍。
原码指的是数据本身转化为二进制形式时的值。
反码指将原码按位取反后的值。
补码指反码+1后的值。
正数的原反补相同,负数的原反补需要进行上述计算。
原码中的最高一位表示符号位。(0表示正,1表示负)
数据在内存中采用补码的形式进行存储,数据进行运算也是在内存中进行运算,数据进行打印时是打印其原码。

int a = 1;
00000000000000000000000000000001  -> 原码/反码/补码

int b = -1;
10000000000000000000000000000001  -> 原码
11111111111111111111111111111110  -> 反码
11111111111111111111111111111111  -> 补码

四、移位操作符

移位操作符实际操作的是二进制位(移动存储在内存中的补码)

<< 左移操作符
>> 右移操作符
// 移位操作符只能操作整数

1.左移操作符

二进制的最左边丢弃,最右边补0

int a = 7;
00000000000000000000000000000111 -> 7的原反补码
int b = a << 1;
//将7的补码左移
00000000000000000000000000001110 -> 14
printf("%d\n",a);
printf("%d\n",b);
------------------------------------------
int a = -7;
10000000000000000000000000000111 -> -7的原码
11111111111111111111111111111000 -> -7的反码
11111111111111111111111111111001 -> -7的补码
int b = a << 1;
//将-7的补码向左移动1位
1111111111111111111111111110010 -> 移动后的补码
//因为打印的是原码,所以需要将补码转换为原码
1111111111111111111111111110001 -> 补码-1得到反码
1000000000000000000000000001110 -> 反码符号位不变,其他位按位取反得到原码
printf("%d\n",a);-7
printf("%d\n",b);-14

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2.右移操作符

右移操作符分为:算术右移和逻辑右移
算术右移:右边丢弃,左边补符号位
逻辑右移:右边丢弃,左边补0
大多数编译器都是采用算术右移,以下代码皆以算术右移为主

int a = 7;
00000000000000000000000000000111 -> 7的原反补码
int b = 7 >> 1;
//将7的补码向右移动移位,最左边补符号位0
00000000000000000000000000000011 -> 3
printf("%d\n",a);//7
printf("%d\n",b);//3
------------------------------------
int a = -7;
10000000000000000000000000000111 -> -7的原码
11111111111111111111111111111000 -> -7的反码
11111111111111111111111111111001 -> -7的补码
int b = a >> 1;
//将-7的补码向右移动1位,最左边补符号位
11111111111111111111111111111100 -> 移动后的补码
//打印的是原码
11111111111111111111111111111011 -> 补码-1求出反码
10000000000000000000000000000100 -> 符号位不变,其他位按位取反求出原码
printf("%d\n",a);//-7
printf("%d\n",b);//-4

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
移动操作符的位数不能移动负数位,这是标准未定义行为

int a = 7;
a << -1;//error

五、位操作符

&(按位与) |(按位或) ^(按位异或)

& - 按(二进制)位与
| - 按(二进制)位或
^ - 按(二进制)位异或

1.&操作符

两个对应的二进制位如果同为1则为1,只要有0,则为0

int a = 3;
00000000000000000000000000000011 -> 3的补码
int b = -5;
10000000000000000000000000000101 -> -5的原码
11111111111111111111111111111010 -> -5的反码
11111111111111111111111111111011 -> -5的补码
int c = a & b;
// 3 & -5 
//两个对应的二进制位同时为1才为1,否则为0
00000000000000000000000000000011 -> a & b 的补码/原码
printf("%d\n",c);//3

在这里插入图片描述

2.|操作符

两个对应的二进制数如果同为0,则为0,只要有1,则为1

int a = 3;
00000000000000000000000000000011 -> 3的补码
int b = -5;
10000000000000000000000000000101 -> -5的原码
11111111111111111111111111111010 -> -5的反码
11111111111111111111111111111011 -> -5的补码
int c = a | b;
// 3 | -5
//只要有1,就为1;同时为0,才为0
1111111111111111111111111111011 -> 3 | -5 的补码
//因为符号为负,所以需要求原码
1111111111111111111111111111010 ->补码-1得反码
1000000000000000000000000000101 ->反码中符号位不变,其他位按位取反得原码
printf("%d\n",c);//-5

在这里插入图片描述

3.^操作符

对应的二进制位相同为0,相异为1

int a = 3;
00000000000000000000000000000011 -> 3的补码
int b = -5;
10000000000000000000000000000101 -> -5的原码
11111111111111111111111111111010 -> -5的反码
11111111111111111111111111111011 -> -5的补码
int c = a ^ b;
//3 ^ -5
//对应的二进制位相同为0,相异为1
11111111111111111111111111111000 -> 3 ^ -5的补码
//因为负数的补码原码不同,需要求原码
11111111111111111111111111110111 -> 补码-1得反码
10000000000000000000000000001000 -> 反码中的符号位不变,其他位按位取反得原码
printf("%d\n",c);//-8

在这里插入图片描述

4. ^操作符的运用

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

第一种普遍解法(需要创建临时变量)

int main()
{
	int a = 10;
	int b = 20;
	int tmp = 0;

	tmp = a;
	a = b;
	b = tmp;
	
	return 0;
}

第二种求法,不创建临时变量,但是可能出现溢出

int main()
{
	int a = 10;
	int b = 20;
	
	a = a + b;
	b = a - b;
	a = a - b;

	return 0;
}

第三种解法,不创建临时变量,也不会出现溢出

int main()
{
	int a = 10;
	int b = 20;
	
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	
	return 0;
}

原理
1 ^ 1 = 0; 2 ^ 2 = 0; 3 ^ 3 = 0;… n ^ n = 0;
0 ^ 1 = 1; 0 ^ 2 = 2; 0 ^ 3 = 3;… 0 ^ n = n;
3 ^ 3 ^ 5 = 5; 3 ^ 5 ^ 3 = 5;
从上面的式子可以得出:
任何数异或自身都等于0;
0异或任何数都等于任何数本身;
异或支持交换律;

a = a ^ b;// a = 10 ^ 20;
b = a ^ b;// b = 10 ^ 20 ^ 20; -> b = 10 ^ 0 = 10;
a = a ^ b;// b = 10 ^ 20 ^ 10; -> b = 0 ^ 20 = 20;
//这种算法操作于32个二进制位上,所以不会出现溢出现象
//在实际操作中,主要运用第一种方法,此种方法运行效率较低,且只适用于整数

六、赋值操作符

赋值操作符可以将你不满意的值重新进行赋值

int weight = 120;//体重 - 初始化
weight = 90;//赋值
double salary = 1000.0;
sakary = 2000.0;

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

int a = 10;
int x = 0;
int y = 20;
a = x = y + 1;//连续赋值
//语法支持连续赋值,但不建议使用-可读性较差

七、复合赋值符

使用后将值赋给自身

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

八、单目操作符

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

1.单目操作符:只有1个操作数

2 !逻辑反操作

假的变为真,真的变为假
C语言中,0表示假,非0表示真

int flag = 0;//假
if(!flag)//!假 -> 真 -> 进入循环
{
	//进入循环;
}
flag = 1;
if(!flag)//!真 -> 假 -> 不进入循环
{
	;
}

3.正号+,负号-

int a = +10;//10
int b = +a;//10
int c = -10;
int d = +c;//-10
//+并没有太大的价值
----------------------------
int a = -10;//-10
int b = -a;//10
//-能将正的变为负的,将负的变为正的

4.&取地址操作符

int a = 10;
printf("%p\n",&a);//取出a的地址,地址用%p进行打印

在这里插入图片描述

5.sizeof操作符

int a = 10;
int n = sizeof(a);//计算的是a所占内存的大小,单位是字节
--------------------------
int n = sizeof(int);//计算类型所占内存的大小
printf("%d\n",n);//4
--------------------------
int arr[5] = {0};
int n = sizeof(arr);//计算数组所占内存空间的大小
printf("%u\n",n);//5*4 = 20

证明:sizeof是操作符而不是库函数

int a = 10;
int n1 = sizeof(a);
int n2 = sizeof(int);
int n3 = sizeof a;
int n4 = sizeof int;//error
//sizeof返回值是unsigned int ,所以我们用 %u 来接收
printf("%u\n",n1);
printf("%u\n",n2);
printf("%u\n",n3);
printf("%u\n",n4);

在这里插入图片描述
在这里插入图片描述
从上述代码中,我们可以得出结论:在求变量所占内存空间大小时,sizeof的操作数可以不打括号,这证明了sizeof是操作数而不是函数;但如果sizeof的操作数是数据类型时,则必须打括号,否则将无法运行;

练习

#include<stdio.h>
void test1(int arr[])
{
	printf("%u\n",sizeof(arr));
}

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

请先进行思考后,再查看解析

解析:

#include<stdio.h>
void test1(int arr[])//这里接收的只是首元素地址:arr+0
{
	printf("%u\n",sizeof(arr));//sizeof里的arr实际上是一个指针,指针占空间大小为4/8个字节
	//32位下指针为4个字节
	//64位下指针为8个字节
}

void test2(char ch[])//接收首元素地址 ch+0
{
	printf("%u\n",sizeof(ch));//ch实际上是一个指针,所以占空间大小为4/8个字节
	//指针无论是哪种类型,都是4/8个字节
	//不能想当然的因为指针是char*类型的就认为它只有1个字节
}
int main()
{
	int arr[10] = {0};//创建arr数组,为10个int类型,大小为10*4个字节
	char ch[10] = {0};//创建ch数组,为10个char类型,大小为10*1个字节
	printf("%u\n",sizeof(arr));//40
	printf("%u\n",sizeof(ch));//10
	test1(arr);//数组名作参数,只是将数组首元素地址传过去
	test2(ch);
	return 0;
}

sizeof 与 strlen的区别
1.sizeof是操作符,strlen是库函数;
2.sizeof用于求数据所占内存空间的大小,单位是字节。strlen只能用于求字符串长度,遇’\0’便终止

6.~按位取反操作符

int a = 0;
//~是按二进制按位取反
00000000000000000000000000000000 -> a的补码
int c = ~a;
11111111111111111111111111111111 -> ~a的补码
//打印需要原码
11111111111111111111111111111110 -> 补码-1得到反码
10000000000000000000000000000001 -> 反码中符号位不变,其他位按位取反得到原码
printf("%d\n",c);//-1

在这里插入图片描述

将指定二进制位进行修改,修改完后再修改回原来的数

int a = 13;
00000000000000000000000000001101 -> 13的补码
//要求将13的第五位二进制位修改为1
//只需要在第五位的位置上或上一个1即可
int b = 1 << 4;
00000000000000000000000000001101 - a
00000000000000000000000000010000 - b
int c = a | b;//29
//a | b
00000000000000000000000000011101 - c - 补码
--------------------------------------------
//修改回原本的值
//只需要在第五位上与上一个0即可,但仍需保持原本其余位上的1不变,所以只有第五位为0,其余位都为1
11111111111111111111111111101111 - d
int d = ~b;
int e = c & d; //13

在这里插入图片描述

7.++ --操作符

前置++/–遵循先++/–,再使用的原则。
后置++/–遵循先使用,再++/–的原则。

int a = 3;
int b = ++a;//先++,后使用
//相当于:
//a += 1;
//b = a;
printf("%d\n",a);//4
printf("%d\n",b);//4
-----------------------------
int c = 3;
int d = c++;
//相当于:
//d = c;
//c += 1;
printf("%d\n",c);//4
printf("%d\n",d);//3

在这里插入图片描述

int a = 3;
int b = --a;
//相当于:
//a -= 1;
//b = a;
printf("%d\n",a);//2
printf("%d\n",b);//2
-----------------------
int c = 3;
int d = c--;
//相当于:
//d = c;
//c -= 1;
printf("%d\n",c);//2
printf("%d\n",d);//3;

在这里插入图片描述

其余用法

int a = 10;
printf("%d\n",a--);//先使用 a = 10;再-- a -= 1;
printf("%d\n",a);// a = 9

在这里插入图片描述

void test(int a)
{
	printf("%d\n",a);
}
int main()
{
	int a = 10;
	test(a--);
	return 0;
}

在这里插入图片描述

8.*间接访问操作符

int a = 10;
int* pa = &a;
*pa = 20;
printf("%d\n",a);

在这里插入图片描述
在这里插入图片描述

9.(类型) 强制类型转换操作符

int a = 3.14;//此代码可以运行,但会报警告----3.14是double类型
int b = (int)3.14;//可执行且不会报警告
//将3.14转换为int类型,舍弃小数点后所有位

在这里插入图片描述

srand((unsigned int)time(NULL)的强制类型转换
在这里插入图片描述
在这里插入图片描述
由于unsigned int 与time_t 类型不相同,需要将time的返回值强制类型转换为(unsigned int)

九、关系操作符

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

这些关系运算符比较简单,没什么可讲,但是我们要注意一些运算符使用时候的陷阱
比如:
在编程中== 与 = 不小心写错,导致的错误

if(3 == 5)//正确
{
	;
}

if(3.0 == 5.0)//需要注意精度问题
{
	;
}
//无法用 == 来比较两字符串是否相等
if("abc" == "abcdef")//这样是比较两个字符串的首字符的地址
{
	;
}

比较两字符串是否相等,需要用到strcmp

if(strcmp("abc","abcdef")
{
	;
}

在这里插入图片描述

十、逻辑操作符

&& 逻辑与
|| 逻辑或

注意:
区分逻辑与和按位与
区分逻辑或和按位或

按位与和按位或操作的是二进制位
逻辑与和逻辑或判断的是数据值是否为0

1.逻辑与&

int a = 3;
int b = 5;
int c = a && b;
//a,b都为真,则a&&b 则c为真(1)
//若a,b中有一个为假,则c为假(0)
int d = 0;
int e = a && d;
printf("%d\n",c);
printf("%d\n",e);

在这里插入图片描述

2.逻辑或

int a = 3;
int b = 0;
int c = a || b;
//只要a,b中有一个为真,则a || b则为真(1)
//只有a,b都为假,则a || b 才为假(0)
int d = 0;
int e = a || d;
printf("c=%d\n",c);
printf("e=%d\n",e);

在这里插入图片描述

3.练习

#include<stdio.h>
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\n d = %d\n",a,b,c,d);
}

请思考后再查看解析

解析

#include<stdio.h>
int main()
{
	int i = 0,a=0,b=2,c=3,d=4;
	i = a++ && ++b && d++;
	//a = 0;0&&上任意数皆为0,所以后面步骤不需要再进行,i直接等于0,最后再 a += 1;
	printf("a = %d\n b = %d\n c = %d\n d = %d\n",a,b,c,d);
	//a = 1,b = 2,c = 3,d = 4;
	
	return 0;
}

在这里插入图片描述

总结:
&& 左边为假,右边就不计算了
| | 左边为真,右边就不计算了

思考:下述代码执行后打印的是什么,自行检测

#include<stdio.h>
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\n d = %d\n",a,b,c,d);
	return 0;
}
#include<stdio.h>
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\n d = %d\n",a,b,c,d);
	return 0;
}

十一、条件操作符(三目操作符)

expr1 ? expr2 : expr3
//1如果为真,执行表达式2
//1如果为假,执行表达式3

练习1

int main()
{
	int a = 3;
	int b = 0;
	if(a > 5)
		b = 3;
	else
		b = -3;
	return 0;
}

利用条件操作符转换上述代码

int main()
{
	b = (a > 5) ? 3 : -3;
	return 0;
}

练习2:利用条件操作符求两个数较大值

int main()
{
	int a = 3;
	int b = 5;
	int max = 0;
	max = a > b ? a : b;
	reuturn 0;
}

十二、逗号表达式

expr1,expr2,expr3....exprN

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

int a = 1;
int b = 2;
int c = (a > b , a = b + 10, b = a + 1);//逗号表达式
//a > b 没啥实际效果
//a = 2 + 10; -> a = 12
//b = 12 + 1; b = 13;
//c的结果是最后一个表达式的结果,即:
//c = b;//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[10] = {0};
arr[7] = 8;//[]就是下标引用操作符
//arr[7] -> 实质: *(arr+7)
//*(arr+7) -> *(7+arr)
//*(7+arr) -> 7[arr]
printf("%d\n",arr[7]);
printf("%d\n",7[arr]);

在这里插入图片描述

2.函数调用

int Add(int x,int y)
{
	return x + y;
}
int main()
{
	int a = 10;
	int b = 20;
	//函数调用
	int c = Add(a,b);//()就是函数调用操作符,操作数:函数名 + 参数
	//函数至少有一个操作数:函数名
	return 0;
}

3.结构体成员

struct Stu
{
	char name[20];
	int age;
	double score;
}

void set_stu(struct Stu ss)
{
	//字符串不能直接赋值
	strcpy(ss.name,"zhangsan");//字符串拷贝
	ss.age = 20;
	ss.score = 100.0;
};

void print_stu(struct Stu ss)
{
	printf("%s %d %lf\n",ss.name,ss.age,ss.score);
}

int main()
{
	struct Stu s = {0};
	set_stu(s);//错误示范
	print_stu(s);
	return 0;
}

在这里插入图片描述
原因:
要修改结构体成员时,需要进行数据修改,所以需要传地址来间接修改。
在这里插入图片描述

正确代码

#include<stdio.h>
#include<string.h>
struct Stu
{
	char name[20];
	int age;
	double score;
};

void set_stu(struct Stu* ss)
{
	//字符串不能直接赋值
	strcpy(ss->name,"zhangsan");//字符串拷贝
	ss->age = 20;
	ss->score = 100.0;
	//*(ss). 等价于 ss->
}

void print_stu(struct Stu ss)
{
	printf("%s %d %lf\n",ss.name,ss.age,ss.score);
}

int main()
{
	struct Stu s = {0};
	set_stu(&s);//需要修改数据,则用址传递
	print_stu(s);
	return 0;
}

在这里插入图片描述
注意:
如果左边为结构体指针,->可以指向结构体成员
如果左边为结构体对象,. 可以指向结构体成员
在打印时,也可以进行传地址进行打印,这样所创建的空间会大大减小,节约空间。

表达式求值

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

int a = 2 + 6/3;
// /的优先级比 + 的优先级高,所以先算/再算+
// a = 2 + 2;

int b = 2 + 2 + 3;
//同为+,优先级相同,此时需要比较结合性

在这里插入图片描述

1.隐式类型转换

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

char a = 5;
char b = 126;
char c = a + b;
//计算c的大小
printf("%d\n",c);
//char类型以整型进行计算,需要用到整型提升

如何进行整型提升?
整型提升是按照变量的数据类型的符号位来提升的

//负数的整型提升
char c = -1;//-1是整型,是32个bit位
10000000000000000000000000000001//-1的原码
11111111111111111111111111111110//-1的反码
11111111111111111111111111111111//-1的补码
//char只有8个bit位,取补码的后八位存放
11111111 - c
//进行整型提升:
//符号位为1,转换为32位,则全补1
11111111111111111111111111111111//c的整型提升

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

//无符号整型提升,高位补0
char a = 5;
//a整型提升
00000000000000000000000000000101 - a的补码
00000101 - a
//整型提升
00000000000000000000000000000101 - a的整型提升
char b = 126;
00000000000000000000000001111110 - b的补码
01111110 - b
00000000000000000000000001111110 - b的整型提升
char c = a + b;
//a+b
00000000000000000000000010000011 - c的补码
10000011 - c
11111111111111111111111110000011 - c的整型提升 - 补码
11111111111111111111111110000010 - 反码
10000000000000000000000001111101 - 原码 -> -125
//计算c的大小
printf("%d\n",c);//-125

在这里插入图片描述

2.算术转换

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

long double
double
float
unsigned long int
long int
unsigned int
int

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

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值