入门级C -- 操作符详解

✨✨✨学习的道路很枯燥,希望我们能并肩走下来!

编程真是一件很奇妙的东西。你只是浅尝辄止,那么只会觉得枯燥乏味,像对待任务似的应付它。但你如果深入探索,就会发现其中的奇妙,了解许多所不知道的原理。知识的力量让你沉醉,甘愿深陷其中并发现宝藏。


前言

本篇向初学者详解介绍操作符的使用条件,并举例子帮助小伙伴们理解,如有错误,请在评论区指正,学习的道路上有你有我,让我们一起交流,共同进步!

本文开始

操作符的分类

  • 算数操作符
  • 移位操作符
  • 位操作符
  • 赋值操作符
  • 单目操作符
  • 关系操作符
  • 逻辑操作符
  • 条件操作符
  • 逗号操作符
  • 下标引用、函数调用和结构体成员

1.算数操作符

+,-,*, /,%

[注]1. %操作符使用时两个操作数必须是整数,返回值是整除后余下的数. 2. / 操作符如果想进行浮点数数操作,左右两个操作数必须有一个是浮点型 (可以使用3.0/2; 3/2.0; 3.0/2.0).(浮点型:float,double)

/ 操作符两边至少一个为浮点型 (带小数就为浮点型)

//例如:
#include<stdio.h>
int main()
{
//定义类型是就定义浮点型
	double a = 3;
	int b = 2;
	printf("%lf\n", a / b);//得到1.500000
	//当遇到两个都为整数时,想的到小数如下:
	//操作符两边至少一个为浮点型
	printf("%d\n",3/2.0);//1.500000
	//或者可以使用3.0/2;3.0/2.0
	return 0;
}

2.移位操作符

<< 左右操作符,>> 右移操作符

[注] ①移位操作符的操作数只能为整数
② 操作的都是二进制位

2.1左移操作符

  • 移位(使用)规则:
    左边丢弃,右边补0

    在这里插入图片描述

2.2右移操作符

  • 移动规则:
    ①逻辑移位:右移完后,左边补0,右边丢弃
    ②算数移位:右移完后,左边用原来符号位填充,右边丢弃

右移运算是因为编译器不同决定的,但一般是算数右移
这里为了方便演示逻辑右移和算数右移,我们用-1了示范.

在这里插入图片描述

注意:( 不用移动负数 )虽然可以任意使用移位运算符,但我们也需要注意要在正常范围内使用,像下面的使用就是错误的,目的不够明确,不知道你是右移还是左移.

//错误示范:
int n=3;
n>>-1;

3.位操作符

&:按位与 (不同为0,都为1才为1);
| :按位或(有1就为1,全0才是0);
^ : 按位异或(相同为0不同为1)
以上比较都是对应的二进制位

[注]:①操作的都是整数②操作的都是二进制位
示例如下:

#include<stdio.h>
int main()
{
	int n1 = 1;
	int n2 = 3;
	//n1=1,n2=3,二进制如下,取4个bite位演示
	//0001
	//0011
	
	n1& n2;
	//0001
	n1 | n2;
	//0011
	n1 ^ n2;
	//0010
	return 0;
}

知道了位操作符的规则,那他真正能实践在哪呢?我们通过一道题来看一下.

3.1 在不使用第三个变量的情况下交换a,b的值.

异或规则示范,为我们不使用第三个变量提供思路,a^a=0,a ^ a ^ b=b;
代码演示

//异或规则示范,为我们不使用第三个变量提供思路
//a^a
//0011
//0011
//0000
//a^b
//0011
//0010
//0001
//a^b^a
//0001 -- a^b
//0011 -- a
//0010--b
#include<stdio.h>
int main()
{
	int a = 3;
	int b = 2;
	//将a看作密码
	a = a ^ b;
	b = a ^ b;//a^b^a--这里b的二进制就==a
	a = a ^ b;//a^b^a(b)=b;
	printf("a=%d b=%d\n", a, b);
	return 0;
}

4.赋值操作符

赋值操作符与我们所认为的=的等号不一样,他的意义是赋值给所需要的变量,== 这才是计算机所认为的等号.
编程好习惯:
连续赋值我们一般不常用,因为不适合调试,无法仔细的看到赋值的过程,所以我们一般选择分开写,这样便于调试.

//赋值可以一个一个赋值,也可以连续赋值
//连续赋值是从右向左计算的
#include<stdio.h>
int main()
{
	int a = 3;
	int b = 2;
	int weigth = 110;
	char ch = 'a';
	//连续赋值
	int x = 5;
	a = x = b + 1;
	return 0;
}

4.1f复合赋值符

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

复合赋值为我们书写提供便捷,使代码也更简洁.
例如:
int x=10;
x=x+10;
x+=10;//复合赋值与上边相比是不是更简洁呢?

5.单目操作符

! :逻辑反操作符
-:负值
+: 正值
&:取地址
sizrof:操作符类型长度
~:对一个二进制按位取反
–:前置、后置–
++:前置、后置++
*:间接访问操作符(解引用操作符)
(类型):强制类型转换

之前我们讲解过简单的操作符,这里我们就简单过一下,主要讲解一些重要的.

#include<stdio.h>
int main()
{
	int a = 10;
	//取出变量的地址
	int* pa = &a;//取地址操纵符
	*pa = 20;//解引用访问a,改变a的值

	//取出数组的地址
	int arr[10];
	&arr;
	//取出的是整个数组的地址,
	//但是打印出来与第一个数组元素的地址一样,以后讲解指针我们再来详细了解

	//也可以取函数的地址,需要函数指针,这里只知道一些就行,以后指针会详细了解
	return 0;
}

5.1sizeof与数组

我们都知道sizeof可以求变量或类型所占空间的大小,哪求数组空间会有什么联系呢?
我们通过代码分析一下:

#include<stdio.h>
void text1(int arr[])
{
	printf("%d\n", sizeof(arr));//4(2)
}
void text2(char ch[])
{
	printf("%d\n", sizeof(ch));//4(4)
}
int main()
{
	int arr[10] = { 0 };
	char ch[10] = { 0 };
	printf("%d\n", sizeof(arr));//40(1)
	printf("%d\n", sizeof(ch));//10(3)
	printf("----------------\n");
	text1(arr);
	text2(ch);
	return 0;
}

代码结果:
在这里插入图片描述

从结果我们发现,(1)(2)结果不一样,(3)(4)结果也不一样,这是为什么呢?
其实sizeof(arr)其中的arr表示数组arr整体所占空间大小 ,也就是求 10个整数的字节大小为40,同理sizeof(ch)
求的是10个字符所占空间大小为10;
而函数调用传递的是数组首元素地址,类型为int*,char*,它们都为地址,而sizeof求地址大小与编译器有关 ,64位编译器的地址大小位8个字节,32位编译器的地址大小位4个字节.(不分类型,是地址就是4/8个字节,由编译器决定的)

sizeof在计算变量时,可以剩略掉括号,但是计算数据类型不能剩略掉括号;
编程好习惯:我们一般使用sizoef都带上括号,减少出现错误;
示例如下:

printf("%zu\n",sizeof(a));//4
printf("%zu\n",sizeof a);//4
printf("%zu\n",sizeof(int));//4
printf("%zu\n",sizeof int);//error这是错误的

5.2~按位取反操作符

操作的是二进制位,这里我们通过看代码,更好的理解.
这里我们要知道正数,负数的二进制在内存的存储:①正数:原码反码补码相同
②负数:写出二进制位 – 得到的是原码;原码符号位不变 (最高位为符号位),其他位按位取反–得到反码;反码+1–得到补码;

代码演示:

#include<stdio.h>
int main()
{
	int a = -1;
	//在内存中的二进制存储-以补码的形式
	//11111111111111111111111111111111 - 内存中的补码
	//11111111111111111111111111111110 - 内存中-1的反码
	//10000000000000000000000000000001 - -1的原码
	int b = 0;
	//00000000000000000000000000000000
	//按位取反
	//11111111111111111111111111111111 -内存的补码
	printf("%d %d\n", ~a, ~b);// ~a = 0, ~b = -1;
	return 0;
}

~按位取反作用不止这些,我们举一个简单的例子,了解一下吧!
通过按位取反把二进制的某个位置变为0
代码示例如下:

#include<stdio.h>
int main()
{
	int a1 = 10;
	int a2= 26;

	//000000000000000000000000000001010 - 10
	int n = 0;
	scanf("%d", &n);//输入5
	//把a的二进制位第n个位置置为1
	//000000000000000000000000000000001
	//000000000000000000000000000010000
	a1 = a1 & (1 << (n - 1));

	//把a的二进制位第n个位置置为0,~按位取反操作符的使用
	//000000000000000000000000000010000
	//111111111111111111111111111101111 - ~后的结果
	//000000000000000000000000000011010 - a2
	//000000000000000000000000000001010 - (a2)和(按位取反后结果)按位与的结果 10
	//
	a2 = a2 & (~(1 << (n - 1)));
	printf("%d\n", a2);//a2=10
	return 0;
}

5.3前置++,–,后置++,–

因为之前讲过这里我们简单复习一下它们的使用.
代码示范:

#include<stdio.h>
int main()
{
	int n = 10;
	//前置++,先让n++再将值赋给a
	int a = ++n;
	//前置--,先让n--再将值赋给b
	int b = --n;
	printf("a=%d,b=%d\n", a, b);
	//后置--,先让n将值赋给c,n再--
	int c = n--;
	//后置++,先让n将值赋给d,n再++
	int d = n++;
	printf("c=%d d=%d\n", c, d);
	
	//把浮点型强制转换为整型
	int f=(int)3.14;
	return 0;
}

6.关系操作符

: >, >=, <, <=, !=, ==

这些适用于判断大于小于和等于时运用的操作符,我们熟练运用即可,这里要掌握==等号是这样写的!!!

7.逻辑操作符

&&:逻辑与
||:逻辑或

这里是用来判断条件真假使用的.
我们还需要区分它们与位操作符的区别

1&2 --- 0 -二进制位比较后的结果
1&&2 --- 1 - 真假条件判断后的结果

1|2 --- 3
1||2 --- 1
1||0 --- 1

逻辑与&&:当A&&B&&C时,如果A为真,才判断B,B为真,才判断C.如果从左向右判断时A为假,根本就不会有判断B,C的机会,直接会把条件为假(0)的结果传送给电脑;同理如果A真,B假就不会有判断C的情况;只有ABC全为真,这个条件才为真.

逻辑或:当A||B||C时,从左向右判断,如果A真,这个条件就为真,不会判断B,C;同理如果A假,就判断B,B真就不会判断C;只有ABC全为假,这个条件才为假.

我们可以通过下面这道试题感受一下&&,||的使用特点!

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

8.条件操作符

exp1 ? exp2 :exp3
exp是express的缩写:表达式

获取两个数的较大值,我们一般用if-else来获得,那么我们用条件表达式来尝试一下!

//返回较大值给a
int a = a > b ? a : b;

9.逗号表达式

exp1,exp2, exp3, exp4, …expn

逗号表达式,就是用逗号隔开的多个表达式
逗号表达式,从左向右依次执行.

一些情况下面我们可能会 重复使用语句,这时我们可以用 逗号表达式来尝试解决.

#include<stdio.h>
int main()
{
	//代码示范1
	int a = 1;
	int b = 2;
	int c = (a > b, a = b + 1, b = a + 1);
	//c=4

	//代码示范2
	a = get_val();
	count_val(a);
	while (a > 0)
	{
		a = get_val();
		count_val(a);
	}
	//上述代码的语句出现了重复为了,减少重复我们可以使用逗号表达式
	while (a = get_val(), count_val(a), a > 0)
	{
		;
	}
	return 0;
}

10.下标引用、函数调用、结构体成员访问

  • [ ]:下标引用操作符
    操作数:数组名+值
int arr[10];//创建数组
arr[0]=1;//使用下标引用操作符
// [ ] 的两个操作数是 arr和0
  • ( ):函数调用操作符
    接收操作数:函数名+函数传递的参数
#include<stdio.h>
void text1()
{
	printf("hehe\n");
}
void text2(int x,int y)
{
	printf("%d\n", x + y);
}
int main()
{
	text1();
	//()作为函数调用操作符
	text2(2,3);//2,3为传递的参数
	//函数操作数:text2,2,3
	return 0;
}
  • . :结构体变量.结构体成员
  • -> :结构体指针 -> 结构体成员名

代码示范:

#include<stdio.h>
struct book
{
	char name[20];
	int price;
};
void set_b(struct book b)
{
	b.price = 45;
	printf("%d\n",b.price);
}
void set_b2(struct book *b)
{
	b->price = 55
	printf("%d\n",b->price);
}

int main()
{
	//struct book b = { "c yuyan",35 };//结构体成员初始化
	//.操作符访问结构体成员
	//printf("%s,%d\n", b.name, b.price);
	
	//也可以不按顺序设置结构体成员的值:.+结构体成员名+设置的值
	struct book b = { .price=30,.name="shuxue" };
	
	struct book* pb = &b;
	printf("%s,%d\n", (*pb).name, (*pb).price);
	//*取地址找到b,.再访问结构体成员的值
	set_b(b);//传值调用
	set_b2(&b);//传址调用
	return 0;
}

11.表达式求值

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

11.1隐式类型转换

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

整型提升的意义:

  • 表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度,一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
  • 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
    度。
  • 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算

整型提升的规则:是按照变量的数据类型的符号位来提升的.
有符号位:高位补充符号位(负数补1,正数补0)
无符号位:高位补0

通过整型提升的例子来了解一下
注:
①因为5是整数(4个字节),char a = 5;首先是把5以二进制放到内存中
②再把值赋给5,因为char a是一个字节只能存放8个bite位,所以会发生截断
③取8个bite位放入a中

只要带有运算符参与表达式计算 就会发生整型提升.像有char类型存储整型
例如:
char a = 5;//发生截断存入a
char b = 125;//发生截断存入b
char c = a+b;//当计算c时,a,b整型提升恢复为4个字节,赋给c时再截断,取8个bite位给c.

代码示例:

//代码示例1
int main()
{
	char a = 5;//截断
	char b = 126;//截断
	char c = a + b;//截断
	//00000000000000000000000000000101
	//00000101 - a
	//00000000000000000000000001111110
	//01111110 - b
	//整型提升
	//00000000000000000000000000000101 - a
	//00000000000000000000000001111110 - b
	//00000000000000000000000010000011
	//10000011 - 赋值给c时截断
	printf("%d\n", c);
	//%d 十进制的方式打印有符号整数
	// 打印时要求整型c再整型提升为4个字节再打印值
	//11111111111111111111111110000011
	//11111111111111111111111110000010
	//10000000000000000000000001111101
	//-125
	

//代码示例2:
	char d = 1;
	printf("%u\n", sizeof(d));//1个字节
 	printf("%u\n", sizeof(+d));//4个字节
 	printf("%u\n", sizeof(-d));//4个字节
	return 0;
}

示例2与你想的是否一样呢?我们来看一下
代码示例2解释:
d只要参与表达式运算,就会发生整形提升,表达式 +d ,就会发生提升,所以 sizeof(+d) 是4个字
节.
表达式 -d 也会发生整形提升,所以 sizeof(-d) 是4个字节,但是 sizeof(d) ,就是1个字节.

11.2算数转换

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

在这里插入图片描述

11.3操作符的属性

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

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

相邻操作符先执行哪个?取决于他们的优先级,优先级相同,取决于他们的结合性.
操作符优先级:

在这里插入图片描述

是否控制求值顺序的四个操作符:&&(逻辑与)、||(逻辑或)、? :(条件表达式)、,(逗号);

12.4一些问题表达式

一些问题表达式
表达式的求值由两个相邻操作符的优先级决定。如果有3个以上的操作符就可能出现问题.
示例1:

  a*b + c*d + e*f
 //可能执行顺序:
// 1  4  2  5  3
// 1  3  2  5  3
//这里*优先级大于+的优先级,
//但是只能保证*比+号计算的早,
//不能决定是先计算第一个+号,还是第二个+号;

*号优先级高与+号,但是不能确定同级+号的计算顺序,如果abcdef都为表达式,计算结果就会产生错误
示范2:

c + --c;

以上例子我们只能知道操作符自减–的优先级大于+号,但是不能确定的是+操作符的左操作数获取的是自减前的操作数还是自减后的操作数,所以结果有歧义,不能够推测.

示范3:
非法表达式:此表达式在不同编译器下结果不同,我们写代码时一定要避免这样的写法!!!

int main()
{
 int i = 10;
 i = i-- - --i * ( i = -3 ) * i++ + ++i;
 printf("i = %d\n", i);
 return 0; }

不同编译器下的结果:

值 ------- 编译器
—128 Tandy 6000 Xenix 3.2
—95 Think C 5.02(Macintosh)
—86 IBM PowerPC AIX 3.2.5
—85 Sun Sparc cc(K&C编译器)
—63 gcc,HP_UX 9.0,Power C 2.0.0
4 Sun Sparc acc(K&C编译器)
21 Turbo C/C++ 4.5
22 FreeBSD 2.1 R
30 Dec Alpha OSF1 2.0
36 Dec VAX/VMS
42 Microsoft C 5.1

示例4:

int fun()
{
     static int count = 1;
     return ++count;
}
int main()
{
     int answer;
     answer = fun() - fun() * fun();
     //可能的结果 -2,-10
     //4-2*3=-2;2-3*4=-10;
     printf( "%d\n", answer);//输出多少?
     return 0; }

上述代码我们只知道先算乘法,再算减法,但是函数的调用先后顺序无法确定,结果也就无法确定.

示例5:
常见错误代码:

#include <stdio.h>
int main()
{
 int i = 1;
 int ret = (++i) + (++i) + (++i);
 //3 + 3 + 4 =10--VS环境下
 //4 + 4 + 4 =12 --Linux环境下结果
 printf("%d\n", ret);
 printf("%d\n", i);
 return 0; }

上述代码无法确定执行第一个和第二个++后,第一个+号执行时,第三个++是否执行.我们无法确定,所以会出现错误.

编程好习惯:为了避免上述情况的产生,我们要少写复杂的表达式,我们在写我们的表达式时,可以采用()的形式,明确表明自己的表达式先执行哪步,这样就不会出现歧义了.


总结

✨✨✨各位读友,相逢便是缘,本篇分享到内容是否更好的让你了解了操作符的使用呢?如果对你有帮助给个👍赞鼓励一下吧!!
🎉🎉🎉一遇挫折就灰心丧气的人,永远是个失败者。而一向努力奋斗,坚韧不拔的人会走向成功。让我们共同行走,共同努力吧!
♥♥♥感谢每一位一起走到这的伙伴,我们可以一起交流进步!!!一起加油吧!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值