文章目录
✨✨✨学习的道路很枯燥,希望我们能并肩走下来!
编程真是一件很奇妙的东西。你只是浅尝辄止,那么只会觉得枯燥乏味,像对待任务似的应付它。但你如果深入探索,就会发现其中的奇妙,了解许多所不知道的原理。知识的力量让你沉醉,甘愿深陷其中并发现宝藏。
![](https://i-blog.csdnimg.cn/blog_migrate/a8a946fceb481544d2b5ebd962a5bf33.jpeg)
前言
本篇向初学者详解介绍操作符的使用条件,并举例子帮助小伙伴们理解,如有错误,请在评论区指正,学习的道路上有你有我,让我们一起交流,共同进步! |
本文开始
操作符的分类
- 算数操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号操作符
- 下标引用、函数调用和结构体成员
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; }
上述代码无法确定执行第一个和第二个++后,第一个+号执行时,第三个++是否执行.我们无法确定,所以会出现错误.
编程好习惯:为了避免上述情况的产生,我们要少写复杂的表达式,我们在写我们的表达式时,可以采用()的形式,明确表明自己的表达式先执行哪步,这样就不会出现歧义了.
总结
✨✨✨各位读友,相逢便是缘,本篇分享到内容是否更好的让你了解了操作符的使用呢?如果对你有帮助给个👍赞鼓励一下吧!!
🎉🎉🎉一遇挫折就灰心丧气的人,永远是个失败者。而一向努力奋斗,坚韧不拔的人会走向成功。让我们共同行走,共同努力吧!
♥♥♥感谢每一位一起走到这的伙伴,我们可以一起交流进步!!!一起加油吧!!!