目录
操作符详解
什么是操作符呢?
操作符的分类
操作符的优先级
?1、优先级1级
2、优先级2级
3、优先级3级
4、优先级4级
5、优先级5级
6、优先级6级
7、优先级7级
8、优先级8级
9、优先级9级
10、优先级10级
11、优先级11级
12、优先级12级
13、优先级13级
14、优先级14级
15、优先级15级
算术操作符
移位操作符
右移操作符>>
位操作符
?应用举例
单目操作符
sizeof和数组
关系操作符
逻辑操作符
逻辑判断一道笔试题
条件操作符
逗号表达式
下标引用操作符
?函数调用操作符
?结构体成员访问操作符
表达式求值
隐式类型转换
算术转换
操作符详解
什么是操作符呢?
C语言操作符指的是运算符号。毋庸置疑,运算符号(操作符)就是进行c语言的一些运算的,这些运算符虽然你在学c语言的时候很不起眼,而在有些题目上还不让用这些操作符来做题,但是,当你用到他们的时候,他们会给你意想不到的帮助。
操作符的分类
C语言中的符号分为10类:算术运算符、关系运算符、逻辑运算符、位操作运算符、赋值运算符、条件运算符、逗号运算符、指针运算符、求字节数运算符和特殊运算符
操作符的优先级
所谓优先级:就是计算的顺序,在数学中有这么一个说法,就是先计算括号里面的,在计算外面的,先计算乘法除法,在计算加法减法。
优先级顺序:(这里的优先顺序估计一般人是记不住的,这些都是在百度上搜索的,大家可以当做笔记或者字典来看,找到自己不会的点学就完了)。
?1、优先级1级
结合方向 左结合(自左至右)
( ) 圆括号
[ ]?[1] ? 下标运算符
-> 指向结构体成员运算符
. 结构体成员运算符?[1]?(请注意它是一个实心圆点)
2、优先级2级
结合方向 右结合(自右至左)单目运算符
! 逻辑非运算符
~ 按位取反运算符
++ 自增运算符
-- 自减运算符
-?负号运算符
(类型) 类型转换运算符
* 指针运算符
& 地址与运算符
sizeof 长度运算符
3、优先级3级
结合方向 左结合 双目运算符
* 乘法运算符
/ 除法运算符
% 取余运算符
4、优先级4级
结合方向 左结合 双目运算符
+ 加法运算符
- 减法运算符
5、优先级5级
结合方向 左结合 双目运算符
<< 左移运算符
>> 右移运算符
6、优先级6级
结合方向 左结合 双目运算符
<、<=、>、>= 关系运算符
7、优先级7级
结合方向 左结合 双目运算符
== 等于运算符 (判断)
!= 不等于运算符(判断)
8、优先级8级
结合方向 左结合 双目运算符
& 按位与运算符
9、优先级9级
结合方向 左结合 双目运算符
^ 按位异或运算符
10、优先级10级
结合方向 左结合 双目运算符
| 按位或运算符 举例:0xfe|0xef 即为1111 1110 与1110 1111按位或运算则答案为:1111 1111 即0xff。
11、优先级11级
结合方向 左结合 双目运算符
&& 逻辑与运算符
12、优先级12级
结合方向 左结合 双目运算符
|| 逻辑或运算符
13、优先级13级
结合方向 右结合 三目运算符
? :?条件运算符
14、优先级14级
结合方向 右结合 双目运算符
=?赋值运算符
+ = 加后赋值运算符 如s+=1表示s=s+1
- = 减后赋值运算符 如s-=1表示s=s-1
* = 乘后赋值运算符
/ = 除后赋值运算符
% = 取模后赋值运算符
< <= 左移后赋值运算符
>>=右移后赋值运算符
&= 按位与后赋值运算符
^=按位异或后赋值运算符
|= 按位或后赋值运算符
15、优先级15级
结合方向 左结合
,?逗号运算符
算术操作符
+? (进行加法运算)???-(进行减法运算)? ? ? ?*(在c语言中*代表是乘法)
/(在c语言中/代表除法):
对于除法运算,如果两个数都是整数的话(即整数除法),则结果为整数,如果两个数有一个数为浮点数,则结果就为小数
#include<stdio.h>
int main()
{
int a1 = 10 / 2;
float a2 = 10 / 2.0;
printf("%d\n", a1);
printf("%f", a2);
return 0;
}
%(在C语言中不是百分号这个是取模操作符)
取模操作符计算的是整数不能是小数,其中结果为余数。
移位操作符
移位操作符:移位是某数在内存中存储的二进制位。
分类:左移操作符<<? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?右移操作符>>
左移操作符<<
左移操作符是将数字在内存中存储的二进制位向左移位。
先给大家讲讲进制吧
进制也就是进位计数制,是人为定义的带进位的计数方法(有不带进位的计数方法,比如原始的结绳计数法,唱票时常用的“正”字计数法,以及类似的tally mark计数)。 对于任何一种进制---X进制,就表示每一位上的数运算时都是逢X进一位。?十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一,以此类推,x进制就是逢x进位。
进制的分类
16进制(0~15)? ? ? 8进制(0~7)? ? ? ? ? 2进制(0~1)
进制的表示形式:二进制以0b开头,八进制以0开头,16进制以0x开头
由于在计算机存储的是二进制我们具体给大家讲二进制其他进制可以类比
由于二进制只有0和1,逢2进一,从右面开始,右面第一位是2^0,第二位是2^1,依次类推
?那我们拿5来举例 5就是(前面的0省略),101(4+0+1=5);
这样也就会了。
好接着我们来讲左移操作符
还是拿5来举例
?所以显然左移操作符的作用是向左移动二进制位左边丢弃右边补0
用代码这么表示? ? int a= a <<1;
那我们在进行移位操作的时候a本身有没有变呢???
int main()
{
int a = 5;
int b = a << 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
?显然a本身没有变因为左移只是一种运算操作不会改变其地址;
讲右移操作符之前先给大家普及一个小知识,我们整数在内存中存储都是补码,那什么是补码呢??
说补码那就不得不说原码和反码
原码是啥呢?原码就是一个整数本身的二进制 ,比如5的原码就是101,又由于正数的原码补码反码都相同所以补码也是101。
先说一下从原码到补码的运算过程——是这样运算的原码——>反码——>补码
原码变反码是符号位不变剩下的所有位按位取反(0变1,1变0),反码变补码是反码+1就是补码。
右移操作符>>
所谓右移操作符就是二进制位向右移位
右移操作符分为两种运算,一种是算术右移,第二种是逻辑右移,具体哪种运算就取决于编译器进行哪种运算了(vs编译器采用的是算术右移)
算术右移:右边丢弃,左边额补原来的符号位
逻辑右移:右边丢弃,左边补0
需要注意的是正数的原码补码反码相同,而负数的原码补码反码需要计算,因为通常在内存中存储的是二进制补码,而在打印的时候或者使用的时候我们用的是原码,这时候,我们就需要将补码变成原码,怎么变呢??那当时刚才的反过来,补码变反码,那就是二进制位减一,反码变原码符号位不变其他位按位取反。
当我们进行左移或者右移的时候一定先转化为原来二进制的补码在进行移位操作
给大家举个例子
位操作符
位操作符有&(按位与)|(按位或)^(按位异或)
按位与:我是这么理解的,比如我与你肯定是你和我必须都存在,所以只有两个都为1时才为1
按位或:或就是只要有一个就行,也就是两个当中有1个为1才为1
按位异或:相同为0,相异为1
?应用举例
交换两个数(不允许创作临时变量)
我们都知道有这些操作符但都不知道该怎么应用,接下来我们进行按位异或的应用
首先先提个问题b^b=多少呢??
^(按位异或是相同为0相异为1),那可知b^b就应该是0了,那下面的代码你就应该可以理解了
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a^b;
b = a^b;
a = a^b;
printf("a = %d b = %d\n", a, b);
return 0;
}
这里的b=a^b^b就是b=a;而a=a^a^b(就是a=b)。
赋值操作符
赋值就是把一个变量变成你想要的结果
我们通常用的赋值号就是=(值得注意的是==是相等,=是赋值)
复合赋值符+= -= *= /= %= >>= <<= &= |= ^=
但赋值时左值必须是一块空间比如int
右值是一个值或者一块空间的内容
注意:当我们用复合赋值符的时候尽量不要写成连等例如y=b+2=c,这样就让人看不懂你的代码可读性就大大降低,所以我们写代码一定写着清晰明了,让别人看代码一目了然。
单目操作符
! ? ? ? ? ? 逻辑反操作
什么是逻辑反操作?
反操作其实就是把真的改成假的,假的改成真的
#include<stdio.h>
int main()
{
int flag = 1;//1代表真
if (!flag)//!flag就代表假
{
printf("1\n");
}
else
{
printf("2\n");
}
return 0;
}
- ? ? ? ? ? 负值
+ ? ? ? ? ? 正值
这里的正负值就是我们说的正负号也不用多说,因为这里是单目操作符,所以不要理解为加号和减号,比如加号两边是有两个操作数所以是双目操作符。
& ? ? ? ? ? 取地址
取地址符号就是取出变量的地址
#include<stdio.h>
int main()
{
int a = 10;
//int* p = &a;//这里存放地址的变量叫做指针变量,其中int*是类型
printf("%p\n", &a);//取出a变量的地址
return 0;
}
sizeof ? ? ?操作数的类型长度(以字节为单位)
sizeof用来求数据类型或者变量的大小
#include<stdio.h>
int main()
{
int a = 2;
printf("%d\n", sizeof(a));//计算变量a的大小
printf("%d\n", sizeof(int));//计算int类型的大小必须加()
printf("%d\n", sizeof a);//计算变量a的大小
printf("%d\n", sizeof int);//计算int类型的大小必须加(),这里就会报错
return 0;
}
~ ? ? ? ? ? 对一个数的二进制按位取反
#include<stdio.h>
int main()
{
//按位取反 ~
//比如5
//5 的二进制位 00000000000000000000000000000101
// 按位取反 11111111111111111111111111111010
int a = 5;
printf("%d\n", ~a);
return 0;
}
前置--:先--在使用
#include<stdio.h>
int main()
{
//前置--先--在使用
int a = 10;
int b = --a;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
?
结果为啥是9 9呢?
我们这里是这样操作的, 先--(就是a先--所以a变成9),在使用(把a=9赋给b);
后置--
#include<stdio.h>
int main()
{
//后置--先使用在--
int a = 10;
int b = a--;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
?先使用(先把a=10赋给b所以b=10),在--(a--变成a=9);
前置++
#include<stdio.h>
int main()
{
//前置++先++在使用
int a = 10;
int b = ++a;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
?先++(a先自己增加1所以a=11),在使用(把a=11赋给b,所以b=11);
后置++
#include<stdio.h>
int main()
{
//z后置++先使用在++
int a = 10;
int b = a++;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
先使用(先将a=10赋给b,所以b=10),在++(a自己增加1,所以a=11)。
++,--总结:
如果是前置就先给自己增加或者减少,在赋值,如果是后置先把原来的值赋值在进行++或者--;
* ? ? ? ? ? 间接访问操作符(解引用操作符)
#include<stdio.h>
int main()
{
int a = 10;
int *p = &a;//指针变量p取出a的地址
*p = 20;//*p就是把存放a的地址解引用了,这里的*p就是原来的a,
//这里是间接访问把原来的a=10改成20
return 0;
}
(类型) ? ? ? 强制类型转换
强制类型转换就是把原本你不是这个常量属于的类型,强制转成某个类型
#include<stdio.h>
int main()
{
int a = (int)3.14;//把原本属于double类型的强制转换成int类型
printf("%d\n", a);
//在比如随机数种子
//srand((unsigned int)time(NULL));
return 0;
}
sizeof和数组
这个我们前面也讲过了,这里在复习下
我们讲了有两种特殊情况一种是sizeof(arr)计算的是整个数组的大小,一种是&arr指的的是整个数组的大小,剩下的都指的是数组首元素的地址。
#include<stdio.h>
int main()
{
int arr[5] = { 0,1,2,3,4 };
printf("%d\n", sizeof(arr));
return 0;
}
当我们传参的时候
#include <stdio.h>
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));//计算的是整个数组大小为10*4
printf("%d\n", sizeof(ch));//计算的是真个字符数组的大小为10*1
test1(arr);
test2(ch);
return 0;
}
当我们进行数组传参的时候传的是地址也就是指针所以大小为4个字节
还要注意当我们想要计算整个数组大小的时候一定要main函数内部计算
关系操作符
> >= < <= != ? 用于测试“不相等” == ? ? ?用于测试“相等(经常在判断时候使用,一定要和=(赋值号)区分开)。
逻辑操作符
&& ? ? 逻辑与 ? ? ? ? ? ? ? || ? ? ?逻辑或
这两虽然是与前面按位与&,按位或|,意思相同
两者区别是按位与和按位或是与二进制的计算有关
而逻辑与和逻辑或是进行判断的
举个例子
#include<stdio.h>
int main()
{
//判断一个人的年龄处于什么状态
int age = 0;
scanf("%d", &age);
if (0 < age < 18)
{
printf("未成年\n");
}
else
{
printf("成年\n");
}
return 0;
}
这个代码对么????
根据输出结果判断这个代码肯定是错的,那这里为啥会错呢?这不应该和数学上是一样的么?
在这个代码是这样理解的,你输入25,进入if语句就判断左半部分0<25结果为真,又25>18结果为真,所以判断为真,输出未成年。
那我们怎样才能正确表示呢?这时逻辑操作符就派上用场了
#include<stdio.h>
int main()
{
//判断一个人的年龄处于什么状态
int age = 0;
scanf("%d", &age);
if (age>=0&&age<=18)
{
printf("未成年\n");
}
else
{
printf("成年\n");
}
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\nd = %d\n", a, b, c, d);
return 0;
}
//程序输出的结果是什么
这个题如何考虑呢?
首先i=a++(后置++,先使用后++),这里先使用a=0&&++b为假所以++b不在计算,又0&&d++为假不计算,在打印的时候a在使用a++(a=1),其他的不计算所以计算结果为1234.
?
#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\nd = %d\n", a, b, c, d);
return 0;
}
//程序输出的结果是什么
这回我们把i换了,这回结果又是什么呢
首先a++,a不变,这时候变为a或什么,0||++b,结果为真所以b=3,c不变,前面是真,真或上啥都为真,d++不计算,最后打印a使用(a=1),所以结果我1334.
条件操作符
exp1 ? exp2 : exp3? ?解释:表达式一成立则执行表达式二,输出表达式二结果,不成立执行表达式三,输出表达式三结果。
#include<stdio.h>
int main()
{
//举例判断两个数的大小然后输出大的数
int a = 10;
int b = 20;
/*if (a > b)
{
printf("%d\n", a);
}
else
{
printf("%d\n", b);
}*/
//利用条件操作符
int ret = (a > b ? a : b);//a>b成立执行a,不成立则执行b。
printf("%d\n", ret);
return 0;
}
逗号表达式
exp1, exp2, exp3, …expN
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
?逗号表达式虽然执行表达式最后的结果但前面两个表达式也是缺一不可的,前面两个表达式要进行运算操作。
下标引用操作符
下标引用操作符常用于数组
?函数调用操作符
?结构体成员访问操作符
#include<stdio.h>
int main()
{
//创建一个结构体变量
//以一个学生为例
struct stu
{
int height;//身高
int weight;//体重
};
struct stu stu = {180,120};
struct stu* ps = &stu;
//printf("身高:%d\n体重:%d", s.height, s.weight);//.操作符为结构体成员访问操作符
printf("身高:%d\n体重:%d\n", (*ps).height, (*ps).weight);//结构体指针访问结构体成员
printf("身高:%d\n体重:%d", ps->height, ps->weight);// 结构体指针->结构体成员名
return 0;
}
总结:结构体访问
结构体.结构体成员名
结构体指针->结构体成员
表达式求值
表达式求值的定义:表达式求值的顺序一部分是由操作符的优先级和结合性决定。 同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
隐式类型转换
整形提升
什么是整形提升呢?
官方定义:C的整型算术运算总是至少以缺省整型类型的精度来进行的。 为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型 提升。
当我们进行比较低的类型进行计算的时候,由于计算机无法实现这种低的类型计算,计算机常常把类型转换成整形进行计算
我们来举几个例子
比如我们要进行char和char类型的计算
#include<stdio.h>
int main()
{
char a = 5;
//a的二进制
//00000000000000000000000000000101
//由于char类型是一个字节只能存放8个比特位所以需要截断
//00000101
char b = 126;
//00000000000000000000000001111110
//char类型进行截断
//01111110
char c = a + b;
//当我们进行a+b时候由于计算机无法实现char类型的计算
//这时我们就需要进行整形提升
//那怎样整形提升呢??
//整形提升是按照变量的数据类型的符号位来提升
//所以我们补符号位
//a为00000000000000000000000000000101
//b为00000000000000000000000001111110
//两者相加
//c变成10000011
//当我们进行打印的时候继续整形提升
//整形提升后
//1111111111111111111111110000011--补码
//又因为在内存中存储的是补码而我们要的是原码
//1111111111111111111111110000010--反码
//1000000000000000000000001111101原码
printf("%d\n", c);
return 0;
}
我们再举个例子
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;
}
?
这个例子就很明显由于a和b都需要整形提升所以不可能是原来的值,而c就可以打印?
举例
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
?这个也会很好的能说明当低类型的进行计算的时候(表达式计算时)也需要整形提升。
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类 型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
这个算术转换适用于int类型以上的类型进行转换
long double
double
float
unsigned long int
long int
unsigned int
int
一般由低向高转换
举例
#include<stdio.h>
int main()
{
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
}
像这种如果要进行计算的话int类型必须转化成float类型才能进行计算。
|