💖 技术宅,拯救世界!
🎁 对读者的话:相信奇迹的人本身和奇迹一样伟大
生命中最快乐的事情是拼搏,而非成功,生命中最痛苦的是懒散,而非失败。大家好,这里是ecember。今天我们来介绍一下C语言中的所有操作符,在本篇博客中ecember将其整理归类了一波(以下结果均在VS2022中编译)。
🌹感谢大家的点赞 和关注 🌹,如果有需要可以看我主页专栏哟💖 |
⚡1. 二进制操作符
我们C语言中有一系列操作符。如下:
算术操作符 | 移位操作符 | 位操作符 | 赋值操作符 | 单目操作符
关系操作符 | 逻辑操作符 | 条件操作符 | 逗号表达式 | 下标引用、函数调用和结构成员
下面我将为大家一一介绍。
🌠 1.1 算术操作符
注意:
1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
🌠 1.2 移位操作符
小科普
计算机中的整数有三种2进制表示方法,即原码、反码和补码。三种表示方法均有符号位和数值位两部分,符号位都是用0表示“正”,用1表示“负”,对于我们的数值位:
<1> 正数的原、反、补码都相同.
<2>负整数的三种表示方法各不相同
1.原码: 直接将数值按照正负数的形式翻译成二进制就可以得到原码。
2.反码: 将原码的符号位不变,其他位依次按位取反就可以得到反码。
3.补码: 反码+1就得到补码。
对于整数来说,内存中存放的都是补码。为什么呢?
在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。总之,补码更方便计算。
例一:
5
二进制-101-一个int位-4个字节-32个bit位
+5
000000000000000000000101-原码
000000000000000000000101
000000000000000000000101
-5
100000000000000000000101-只变符号位
111111111111111111111010-原码符号不变,其它位按位取反得到的即为反码
111111111111111111111011-反码+1即为补码
🌀1.2.1 左移操作符
我们的左移操作符实际上针对的是整数的二进制位形式。
移位规则:左边抛弃、右边补0
🌀1.2.2 右移操作符
移位规则:首先右移运算分两种:
1. 逻辑移位
左边用0填充,右边丢弃
2. 算术移位
左边用原该值的符号位填充,右边丢弃
注意对于右移操作符,不要移动负数位,这个是标准未定义的。
🌀1.2.3 实例解析
int main()
{
int a = -7;
int b = a << 1;//<< >> 操作的二进制位
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
int main()
{
int a = -5;
int b = a >> 1;
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
这里给大家总结了一条规律。
<< ——左移1位* 2
>> ——右移1位 / 2
取整方式:地板取整
注意:右移操作是算数右移还是逻辑右移取决于编译器。
🌠 1.3 位操作符
位操作符跟前几个一样是,也是操作的整数的二进制位。
🌀1.3.1 按位与 & 按位或 |
我们先来试试手。
int main()
{
int a = 3;
int b = -5;
int c = a & b;//二进制位相同且为 1 才为 1 ,其它情况全为 0
int d = a | b;//二进制位相同且为 0 才为 0 ,其它情况全为 1
printf("%d\n", c);
printf("%d\n", d);
//00000000000000000000000000000011 3的补码
//11111111111111111111111111111011 -5的补码
//00000000000000000000000000000011 按位与得到正数(三码相同)
//11111111111111111111111111111011 按位或得到负数(补码需转换)
//转换如下
//10000000000000000000000000000101 -原码
//11111111111111111111111111111010 -反码
//11111111111111111111111111111011 -补码
return 0;
}
🌀1.3.2 按位异或
我们先来看看一组简单的例子。
int main()
{
int a = 3;
int b = -5;
int e = a ^ b;
printf("%d\n", e);
//按位异或 ——相同为0,相异为1
//11111111111111111111111111111000 按位异或(比较特殊得到 - 8)
return 0;
}
值得注意的是下面这种。
//a ^ a = 0
//0 ^ a = a
//0000
//0101
//0101-a
我们可以利用这个来实现很多需要的代码。
🌀1.3.3 实例解析
1
既然我们学了上述不常见的操作数。我们现在来看一组变态的面试题。
不能创建临时变量(第三个变量),实现两个数的交换。
int main()
{
int a = 3;
int b = 5;
printf("a=%d b=%d\n", a, b);
//法一 创建临时变量
/*int temp = a;
a = b;
b = temp;*/
//法二 用 + - 运算
/*a = a + b;
b = a - b;
a = a - b;*/
//法三 异或
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("a=%d b=%d\n", a, b);
return 0;
}
这里我给出了三种方法,这里我们主要讲第三种。我们先来看看运行结果。
我们成功实现了值的交换,那么原理又是怎样的呢?我们上一小节讲了a ^ a = 0,a ^ 0 = a,我们这里把第一个表达式代入第二个表达式即 b = a ^ b ^ b,嘿嘿直接代入公式实现b = a;第三个表达式也是一样的原理。
2
我们再给出一道有意思的题目。
编写代码实现:求一个整数存储在内存中的二进制中1的个数。
//方法一
int main()
{
int num = 10;
int count = 0;//计数
while (num)
{
if (num % 2 == 1)
count++;
num = num / 2;
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}
这种方法比较常见,但是有bug,负数的时候就不成立了。
//方法二
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
for (i = 0; i < 32; i++)
{
if (num & (1 << i))
count++;
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}
这种方法就完美解决了上述问题,而且代码显得比较高级。但是循环次数比较多,使得此算法效率相对来说较低。
//方法三
int main()
{
int num = -1;
int i = 0;
int count = 0;//计数
while (num)
{
count++;
num = num & (num - 1);
}
printf("二进制中1的个数 = %d\n", count);
return 0;
}
我们老老实实举几个例子来算一下就会发现此方法的巧妙之处:自动过滤0,直接每次跳过一个1,这样就大大减少了我们的循环次数。
⚡2. 赋值操作符
也就是我们熟悉的 ’ = '了。赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。
int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值
🌠 2.1 赋值操作符合规使用
赋值操作符可以连续使用,比如
int a = 10;
int x = 0;
int y = 20; a = x = y+1;//连续赋值
但是我们感觉这样的代码风格怎么样?是不是感觉很挫啊哈哈。
x = y+1;
a = x;
这样的写法是不是更加清晰爽朗而且易于调试。
🌠 2.2 复合赋值符
我们上述的操作符都可以和 = 组成复合赋值符来使用。
int x = 10; x = x+10; x += 10;//复合赋值
分开写一样的道理,但这样写更加简洁。
⚡3. 单目操作符
由于部分操作符在我们的 初识C语言——下篇已经详细讲过了,此处不再赘述。这里我们主要帮大家回忆一下部分。
int main()
{
int a = -10;
int *p = NULL;
printf("%d\n", !2);
printf("%d\n", !0);
a = -a;
p = &a;
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof a);//可以这样写
printf("%d\n", sizeof int);//错误写法
return 0;
}
用sizeof计算单独的变量可以不加括号,但计算类型必须加上括号。
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2)
}
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));//(1)
printf("%d\n", sizeof(ch));//(3)
test1(arr);
test2(ch);
return 0;
}
下列式子中(1)(2)(3)(4)分别输出什么?
main函数内部的计算就是计算整个数组的大小,所以40和10没有问题,而我们知道当把一个文数组传给函数时,实际上就是传递数组首元素的地址,而地址我们需要用指针来接收,所以函数内部实际上是计算指针所占的字节,在x64环境下自然是8。
我们再来看看一段代码,看看输出什么?
int main()
{
short a = 10;
short s = 0;
printf("%zu\n", sizeof(s = a + 2));
printf("%d\n", s);
return 0;
}
这个2比较好理解,因为short所占的字节数就是2,那么0是为什么呢?我们明明对s进行赋值运算了呀,所以我们需要了解:sizeof括号内部表达式不参与计算_是在编译期间处理的。
⚡4. 关系操作符
想必这些操作符大家应该都耳熟于心了吧,但是值得注意的是 千万不要把 ‘==’ 跟‘=’搞混了,很多时候程序有错,但就是找不出来,那么大家可以看看判断相等的时候是不是写成了赋值。
小科普
我们比较整型等类型的时候用的是’==’ ,那么比较字符串可不能用这个,我们这里介绍一个函数 strcmp,我们在cplusplus上稍微看一下简介。
得知此函数可以来比较俩字符串是否相等,特别要注意判断字符串大小的时候:>
strcmp函数是用来比较字符串大小的,是比较对应位置上字符的大小一旦遇到不相等的就比较,比较后终止,不是比较长度!!!
⚡5. 逻辑操作符
注意要区分:
区分逻辑与和按位与
区分逻辑或和按位或
🌠 5.1 基本使用
一图解决。
🌠 5.2 360笔试题
#include <stdio.h>
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>
这里可能许多小伙伴会有很多疑惑,这到底是为什么呢?我们一步一步来a++ && ++b && d++
<1> 先a++,由于是后置++,所以最后再++;a = 1
<2> b++,后置++; b = 3
<3> d++,后置++;d = 4
但我们发现结果尽不如人意,只有a ,c的结果相同,我们观察结果得知b和d根本没有参与运算,所以:编译器为了方便,&&左边为假右边不用参与运算,右边直接相当于默认没有。
<2>
当我们运行第二个表达式时,我们套用一的经验可得:||左边为真,右边直接不用参与运算。
我们不妨来验证验证。照这个逻辑是1 3 3 4。
⚡6. 多目操作符
这里我们主要讲条件操作符和逗号表达式。
🌠 6.1 条件操作符
我们先来想一想如果要求两数之间较大的那个数,是不是要用条件语句来实现。
if (a > 5)
b = 3;
else
b = -3;
此式子可以转化为条件表达式。
(a > 5)? (b = 3, b = -3)
🌠 6.2 逗号表达式
逗号表达式,就是用逗号隔开的多个表达式。逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。我们来浅看几个例子。
int main(
//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
//c是多少?
//代码2
if (a =b + 1, c=a / 2, d > 0)
//代码3
a = get_val();
count_val(a);
while (a > 0)
{
a = get_val();
count_val(a);
}
//如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a>0) { ;}
⚡7. 下标引用,函数调用和结构成员
我们这里主要了解一下[ ],(),* 等。
🌠 7.1 下标引用操作符 [ ]
操作数:一个数组名 + 一个索引值。如下:
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
// [ ]的两个操作数是arr和9。
🌠 7.2 函数调用操作符 ( )
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello bit.");//实用()作为函数调用操作符。
return 0;
}
🌠 7.3 访问一个结构的成员
#include <stdio.h>
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;
}
⚡8. 结语
到这,我们的 《操作符详解》 已经接近尾声了,后续我还会持续更新C语言相关内容,学习永无止境,就会永远只会留给有准备的人。希望我的博客对大家有所帮助,如果喜欢的可以 点赞+收藏哦,也随时欢迎大家在评论区及时指出我的错误。