.C语言是底层语言,因为其操作符便能直接操作到内存中存储的二进制位,使用起来灵活方便,为此我认为有必要专门写一篇博客总结C语言的操作符然后分享给大家,没有深入了解过的朋友一定能够在此有所收获,如果喜欢点赞收藏支持一下吧!⭐
目录
1.算数操作符
2.移位操作符( << >>)⭐
3.位操作符(& | ^)⭐
4.赋值操作符
5.单目操作符
6.关系操作符
7.逻辑操作符(&& | | )
8.条件操作符(exp1 ? exp2 : exp3 )
9.逗号表达式(exp1 , exp2 , exp3 , exp n)
10.下标引用、函数调用和结构成员
11.表达式求值(隐式类型转换,优先级)⭐
12.操作符优先级⭐
+ - * / % --------------- (加,减,乘,除,取模)
算数操作符也和数学上的定义是一样的,不必过多介绍,不过既然是在计算机上操作的必然有一些规定:
- << 左移操作符:将操作对象的二进制位向左移动n位(5<
- >>右移操作符:将操作对象的二进制位向右移动n位(5>>n);
<<左移操作符移动规则:左边抛弃,右边补零;
小知识点:
>>右移操作符移动规则分为两种:
1.逻辑右移:
左边用0填充,右边丢掉;
2.算术右移:
左边补原符号位,右边丢掉;
虽然有两种右移方法,但是现在编译器普遍都是按照算术右移执行程序的,逻辑右移在编程中还是不太现实,如果被操作数是负数,那么逻辑右移后它会变成非常大的一个值,用途不大;
小知识点:
1.可以验证得知a>>n其实有个规律,其结果为n次a/2的结果;
2.同样移动并不会改变原来变量的值,除非给变量赋值;
3.<< >> 并不能操作浮点数,只能操作整数;
奇怪的写法:
int a = 1; a>>-1;
这样的写法是很奇怪的,属于标准未定义,写代码我们应该保持规范,避免错误的写法
- & //按位与
- | //按位或
- ^ //按位异或
注:他们的操作对象同样只能是整数
&按位与:两个整数的二进制位对比都是1对应的二进制位才是1,否则为0;
|按位或:两个整数的二进制位对比有一个是1对应的二进制位就是1,都为0则为0;
^按位异或:二进制位相同为0,不同则为1;
小知识点:
1.任何二进制位^0其结果都会等于本身(n ^ 0 = n),任何二进制位^本身结果都为0;
2.^支持交换律,即a ^ b = b ^ a;
3.^支持结合律(a^b) ^ c = a ^ (b^c);
4^自反性,a ^ b ^ b = a;---->( a ^ (b^b) =a ^ 0=a);
问不创建任何临时变量(第三个变量),如何交换int a和int b 的值?
方法不止一种,这里使用的^方法
#include
int main() { int a = 10; int b = 20; a = a^b; // a=a^b b = a^b; // b=a^b^b,所以b=a; a = a^b; // a=a^b^a=(a^a)^b=b; printf("a = %d b = %d\n", a, b); return 0; }
移位操作符和位操作符练题------------------二进制中1的个数__牛客网 (nowcoder.com)
赋值操作符可以让我们随意的修改变量数值
int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值
我们在赋值的时候要注意代码风格,不要随心所欲,保持代码简洁明了
int a = 1;
int b = 2;
a = b = b + a ;//连续赋值让代码可读性差
b = b + a;
a = b; //这样写代码就清楚许多
复合赋值符:
+= -= *= /= %= >>= <<= &= |= ^=
如果想要快捷那么复合赋值符是不错的选择
a = a + b;--------复合赋值符----------> a + = b;
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
单目操作符的操作对象只有一个,很多时候符号一样但并不是意义就是一样的(a-b和-a意义不一样,一个有两个操作对象,一个只有一个操作对象)
前置++,-- 后置++,--:
前置++,-- 是先++或者-- 后使用
int a = 1;
printf("%d",++a);//先+1后打印的结果
后置++,--是先使用,后++或--
int a = 1;
printf("%d",a++);// 先使用a,然后再++
sizeof与数组:
sizeof的计量单位是一个字节,计算数组大小的时候是计算数组总共占多少个字节;
int arr [10] = { 0 }; //一个int类型大小为4字节
char ch [10] = { 0 }; //一个char类型大小为1字节
sizeof (arr) = 40 (字节)
sizeof (ch) = 10 (字节)
如果将它们传参给函数是什么结果?
void test1(int arr[]) { printf("%d\n", sizeof(arr));//打印多少? } void test2(char ch[]) { printf("%d\n", sizeof(ch));//打印多少? }
(40,10 )?(4,1)?
都不是,数组这样传参过去我们应该把它看成一个指针,传过去的并不是整个数组而是数组首元素的地址,通常来说在32位机器上一个地址占4个字节,在64位机器上一个地址占8个字节,所以32位机器结果为(4,4)64位机器结果为(8,8);
为什么结果不是数组单个元素所占的字节大小,我们要知道sizeof(ch)并不是在计算数组一个元素的大小,ch是一个地址,这里计算的其实是一个地址的大小,sizeof(ch[0])才是计算一个元素的大小;
> 大于
>= 大于等于
< 小于
<= 小于等于
!= 用于测试“不相等”
== 用于测试“相等”
同样与数学上的逻辑和含义是一样的,没有什么能够介绍的;
注意:==是测试是否相等,=是用来赋值的,写代码的时候要注意不要写错了;
&& 逻辑与
| | 逻辑或
区分&按位与和|按位或的区别:
&按位与和|按位或是按二进制位来计算的,而&&和||只关心类型的值是否符合,不会去操作二进制位;
a && b //除非a和b都为真其表达式才为真,否则表达式为假;
a | | b //a和b有一个为真表达式就为真,都为假表达式才为假;
int main() { int i = 0,a=0,b=2,c =3,d=4; i = a++ && ++b && d++; //表达式1 //i = a++||++b||d++; //表达式2 printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d); return 0; }
两种表达式的打印结果分别是什么?
表达式1:(1,2,3,4)
因为是后置++所以先使用后++,当a=0的时候表达式不成立后面就不会去计算了,但是++仍然会执行;
表达式2:(1,3,3,4)
a=0的时候表达式就会往下计算,++b=3表达式成立后面同样不会去计算了;
exp1 ? exp2 : exp3 // 表达式为真 ?结果1:结果2
其实这就是一个if else语句判断的缩写
if (a > 5)
b = 3;
else
b = -3;
//和这个表达式是一样的意思 a > 5 ? b = 3 : b=-3
当表达式exp1为真就执行exp2的结果,为假就执行exp3的结果;
exp1 , exp2 , exp3 , exp4 , exp5 , exp n
逗号表达式就是用逗号隔开的多个表达式,逗号表达式从左向右计算,整个逗号表达式的结果就是最后一个表达式的结果;
int main()
{
int a = 1;
int b = 2;
int c = (a = a - b, b = b + a, b = b + a + 1);
printf("%d", c);
return 0;
}
从左往右依次计算结果为1,c的值为1;
1.下标引用操作符[ ] 操作数: 一个数组名+一个索引值;
通常我们创建一个数组
int arr [10] = { 0 }; //但是这里的[ ]并不是下标引用操作符,而是创建数组空间的大小
arr[9] = 10; //这里才是下标引用操作符,通过[ ]我们能够找到相应下标的元素
2.函数调用操作符( )
( )接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
void empty() { printf("*******你好********\n"); } void have(int*a) { *a = 20; } int main() { int a = 10; empty(); //实用()作为函数调用操作符 have(&a); //实用()作为函数调用操作符 printf("%d", a); return 0; }
3.结构成员访问
. 结构体成员 . 成员名
-> 结构体指针 -> 成员名
#include
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
};
void set_age1(struct Stu stu)
{
stu.age = 18;
}
void set_age2(struct Stu* pStu)
{
pStu->age = 18;//结构成员访问
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问
stu.age = 20;//结构成员访问
set_age1(stu);
pStu->age = 20;//结构成员访问
set_age2(pStu);
return 0;
}
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
1.隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。 为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型 提升。
为什么要整型提升?
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长 度。 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运(虽然机器指令 中可能有这种字节相加指令)。
所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算。
整型提升过程:
因为char为有符号位的char,所以我们在整型提升的时候是按照原符号位来进行整型提升的(无符号位的整型提升最高位补0)
截断:
char a = 131;
131二进制位是0000000000000000000000001000 0011
但是char类型空间只有一个字节根本存不下4个字节内容,所以这个时候就发生了截断,我不管你有多大我只从最低位开始存我的一个字节内容,其他二进制位丢掉,所以a里面二进制位存放的其实是10000011
举个栗子:
char a = 5; char b = 126; char c = a + b; //a和b发生整型提升,值返回给c的时候c进行截断 printf("%d",c);
c的结果是多少?
当打印c的值时c进行整型提升
所以c的结果为-125;
栗子2:
int main() { char c = 1; printf("%u\n", sizeof(c));//1 printf("%u\n", sizeof(+c));//2 printf("%u\n", sizeof(-c));//3 return 0; }
第一条语句不会整型提升,因为c没有参加运算,但是第2,3条语句都进行了整型提升,sizeof计算的是int类型的大小,所以打印结果为1,4,4
2.算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类 型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运 算。
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
原图文地址----------------------C语言运算符优先级和结合性一览表 (biancheng.net)
问题表达式:
搞清楚了优先级问题并不能保证表达式就没有问题,在各种编译器中表达式的运算可能各有各的道理,例如:
表达式 a * b + c * d + e * f的结果?
1. a * b --- c*d --- e*f ---- a * b + c * d + e * f
2. a * b --- c*d --- a*b+c*d ---- e*f -----a * b + c * d + e * f
这都是有可能的,表达式计算的顺序不一样其结果也很可能被影响,因此我们在写表达式的时候尽量避免这样的问题表达式,将自己的代码思路写明了
经典问题表达式:
#include
int main() { int i = 1; int ret = (++i) + (++i) + (++i); printf("%d\n", ret); printf("%d\n", i); return 0; } ret和i的结果分别是多少?
没有明确答案,完全看编译器是按照什么样的顺序计算的,每种编译器的计算顺序都不一样,这样的题目根本就没有探讨的必要,现在都是以反面教材拿出来警示;
喜欢的伙伴三连支持一下吧,这是对博主最大的鼓励,感谢!