C语言 运算符知识点汇总
前言
在阅读代码的时候可以,复制粘贴到编译器里面,阅读体验感会比较高
一、算术运算符
五种:
+ - * / %
注意:
1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数
2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法
3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数
二、移位操作符
2.1 << 左移操作符
运算规则:左边抛弃、右边补0(可以理解为乘以2)
代码演示
#include <stdio.h>
int main()
{
int a = 5;
int b = a << 1;
//00000000000000000000000000000101 --->5的原码
//00000000000000000000000000001010 --->左移一位之后的结果为 10
printf("%d\n", b);
return 0;
}
2.2 >> 右移操作符
运算规则有两种情况:(可以理解为除2),两种移位的方式取决于编译器,通常都是算术右移更加的合理
1. 逻辑移位:左边用0填充,右边丢弃
2. 算术移位左边用原该值的符号位填充,右边丢弃
代码演示
#include <stdio.h>
int main()
{
int a = 5;
int b = a >> 1;
//00000000000000000000000000000101 --->5的原码
//00000000000000000000000000000010 --->右移一位之后的结果 2
printf("%d\n", b);
return 0;
}
警告⚠ 1.移位操作符的操作数只能是整数 2.对于移位运算符,不要移动负数位,这个是标准未定义的,因为在不同的编译器下可能得到不同的结果
三、位操作符
基础知识:
1.正数在内存中的原反补相同
2.负数在内存中,原码符号位不变,其他位按位取反->反码+1->补码
3.在内存中都是补码,格式化输出如果是%d会先将补码转化成原码后再打印,如果是%u无脑打印
4.我们计算的时候是以原码的形式,符号位为1的补码,就要转化成原码我们才能正常的计算
5.int 是4个字节(32位),一个字节等于八个比特位
三种位操作符:
& ---按(2进制)位与:补码,两个为1才为1,只要有一个是0就是0(逻辑乘)
| ---按(2进制)位或(逻辑加)
^ ---按(2进制)位异或:相同为0,相异为1
注(局限性):他们的操作数必须是整数
练习1:不创建临时变量(第三个变量),实现两个数的交换 ^(只适用于整数),可读性低,负数也可以
#include <stdio.h>
int main()
{
int a = 3;
int b = 5;
printf("交换前a=%d b=%d\n", a, b);
a = a ^ b;//a = 3 ^ 5
b = a ^ b;//3 ^ 5 ^ 5 = 3
a = a ^ b;//3 ^ 5 ^ 3 = 5
printf("交换后a=%d b=%d\n", a, b);
return 0;
}
程序运行结果:
交换前a=3 b=5
交换后a=5 b=3
解释:(以下的原码简略了前面的0)
一、两个相同的数进行^,最终的结果都是0。例如3^3的结果为零
011(3的补码)
011(3的补码)
000(3^3的结果)
二、 ^支持交换律。3^3^5==3^5^3
求3^3^5的结果
011(3的补码)
011(3的补码)
000(3^3的结果)
110(5的补码)
110(结果为5)
求3^5^3的结果
011(3的补码)
110(3的补码)
101(3^5的结果)
011(5的补码)
110(结果为5)
对此方法的点评:
一定程度上可行,但存在弊端:超出int的范围,溢出的问题
实际的开发过程中,用的都是创建第三个变量进行交换的方法,其实多定义一个变量空间用的不是特别多,且速度更快
练习2:3种方法编写代码实现:求一个整数存储在内存中的二进制中1的个数
//方法一:循环32次这一种是效率比较低的一种方法
#include <stdio.h>
int main()
{
//1.定义一个整数
int number = 5;//00000000000000000000000000000101 --->2个二进制位是1
//2.让这个数和1进行逻辑与&(两个都为1结果才为1,只要有一个是0,结果就是0)
//并且用一个变量result来表示&后的结果,这边的代码是可以简化的,但是这样更容易读懂(直接把number & 1放在if里面)
//3.比较完一位之后就让,这个整数>>1,继续获取下一个二进制位,直至32位,才停止循环
//通过循环判断
int i = 0;
int count = 0;//计数器
for (i = 0; i < 32; i++)
{
int result = number & 1;
if (result == 1)
{
count++;
}
number = number >> 1;
}
//循环结束之后,count就表示该整数的二进制位中的1的个数
printf("%d\n", count);
}
//也可以让1<<i位,这样就不用修改number了
//方法二:和方法一的原理是一样的,但循环的次数少了(当a为0的时候就停止循环,而方法一固定要循环32次),效率稍微高一点,
#include <stdio.h>
int main()
{
int a = 5;
int count = 0;
while (a != 0)
{
if (a / 2 == 1)
//只有奇数的二进制位最后一位才为1,偶数的二进制位最后一位是0,相当于右移!
//00000000000000000000000000000101 --->5的二进制位
//00000000000000000000000000000010 --->2的二进制位
//00000000000000000000000000000001 --->1的二进制位
{
count++;
}
}
printf("%d\n", count);
return 0;
}
//方法三:一般来说循环的次数比上面的两种方法都少,用到了一个公式:n&(n-1) --->每操作一次都会少掉最左边的一个1,直到n为0,看循环了多少次,就有几个1
#include <stdio.h>
int main()
{
int a = 5;
int count = 0;
while (a != 0)
{
a = a & (a - 1);
//00000000000000000000000000000101 --->5的原码
//00000000000000000000000000000100 --->(5-1)的原码
//00000000000000000000000000000100 --->第1次&之后的结果
//00000000000000000000000000000011 --->(第1次&之后的结果-1)
//00000000000000000000000000000000 --->第2次&之后的结果为0,循环停止
count++;
}
printf("%d\n", count);
return 0;
}
其实这一道题的方法还有很多,这里仅举例了这三种方法
四、赋值操作符
复合赋值符:等价与分开写,效果一模一样
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
注意:可以连续赋值,但不建议
五、单目操作符
! 逻辑反操作
\- 负值
\+ 正值
& 取地址
sizeof :类型创建变量所占内存的大小(以字节为单位),求的是变量(类型)所占空间的大小,sizeof是操作符,不是函数,括号可以省略,但是对于数据类型的时候就不能省略
~ 对一个数的所有二进制按位取反
-- 前置、后置--
++ 前置、后置++
\* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
int a = 10;&a取的是a在内存中的起始地址:每个字节都有一个地址
strlen的是库函数
练习:把二进制中的某一位进行修改(修改成1或者修改成0)其中把1改成0需要使用到 ~ 操作符
1.把二进制中的某一位0变成1,(利用1左移和按位或|)
#include <stdio.h>
//需求:把13的二进制原码中第2个比特位中的0变成1(~)
int main()
{
int a = 13;
//00000000000000000000000000001101 --->13的原码
//00000000000000000000000000000010 --->1<<1 (左移2-1位)
int b = a | (1 << 1);
printf("%d\n", b);
//按位或|之后的结果
//00000000000000000000000000001111 --->15
return 0;
}
2.把二进制中的某一位1变成0,(1左移,按位取反(按位异或~),按位与&)
#include <stdio.h>
//需求:把13的二进制原码中第3个比特位中的1变成0(~)
int main()
{
int a = 13;
//00000000000000000000000000001101 --->13的原码
int b = a & (~(1 << 2));
//11111111111111111111111111111011 --->这个数其实就是~(1<<2)
printf("%d\n", b);
//按位与&后的结果
//00000000000000000000000000001001 ---9
return 0;
}
练习:对 + 操作符的理解
#include <stdio.h>
int main()
{
int a = -5;
int b = +a;
printf("%d\n", b);
return 0;
}
//可以直接将+号这个操作符理解为,什么事都不干
运行结果为:
-5
练习:sizeof和数组的理解
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));//(2)4
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));//(4)4
}
int main()
{
int arr[10] = {0};
char ch[10] = {0};
printf("%d\n", sizeof(arr));//(1)40
printf("%d\n", sizeof(ch));//(3)10
test1(arr);
test2(ch);
return 0;
}
问:
(1)、(2)两个地方分别输出多少?
(3)、(4)两个地方分别输出多少?
运行结果为:
40
10
4
4
解释:
这里也考察到了函数调用传参的知识点,可以看我之前关于函数部分的详细讲解
对于sizeof(地址) --->只要是求地址的字节大小要么是4要么就是8(取决于不同的环境)
如果还想更深入的理解sizeof这个操作数的话,可以回过头看看我之前写的博文,里面讲解得非常的详细!
六、 关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
注意:在编程的过程中== 和=不小心写错,导致的错误
不是所有的东西都可以用==号来进行比较大小
比如两个字符串进行比较,比较的是地址
如果要比较两个字符串的内容是否相等,要用strcmp库函数来比较
七、逻辑操作符
两种逻辑操作符:
&& 逻辑与
|| 逻辑或
练习:逻辑与和或的特点
#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;
}
//程序输出的结果是什么?
解释:
&&左边为假,右边就不计算了。||左边为真,右边就不计算了。
八、条件操作符
也叫做三目操作符
表达式1 ? 表达式2 : 表达式3
表达式1成立,就执行表达式2
表达式1不成立,就执行表达式3
九、逗号表达式
从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
十、下标引用、函数调用和结构成员
-
[ ] 下标引用操作符:操作数:一个数组名 + 一个索引值
访问某一个元素的时候可以 -
( ) 函数调用操作符:接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
-
访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
#include<stdio.h>
struct Stu
{
char name[20];
int age;
};
void setAge1(struct Stu ss)
{
ss.age = 18;
}
void setAge2(struct Stu* ps)
{
ps->age = 88;
//(*ps).age = 88;//两种写法都可以
}
int main()
{
struct Stu s = { "zhangsan", 23 };
setAge1(s);//传值调用,形参是实参的一份临时拷贝,形参的的改变不会影响实参的值
printf("%s %d\n", s.name, s.age);
setAge2(&s);//传址调用,形参和实参共用的是同一个空间(地址),只要其中一个发生改变,另一个也随之发生变化
printf("%s %d\n", s.name, s.age);
return 0;
}
//思考:为什么第一次修改的结果没有发生改变呢?
运行结果为:
zhangsan 23
zhangsan 88
传参的时候要传地址,修改的时候才能修改主函数中的值,否则就是一份临时拷贝
传地址的话空间浪费比较少,尽量传地址&
深刻的理解,印证了[]是个操作符,支持交换律52.00
十一、表达式求值
1.隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型
提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。
为了统一个运算规则
char类型的大小范围是-128~127
只要是放在表达式里面,char short都会发生整型提升为int
练习1:代码演示,整型提升:(整型提升的规则,符号位为0就补零,符号位为1就补1)(如果是unsigned 无符号数,那么高位默认补0)
#include <stdio.h>
int main()
{
char a = 5;
//00000000000000000000000000000101 --->5的原码
//00000101 --->由于char类型里面只能放8位,存不下的话就会把其他位截断
char b = 126;
//00000000000000000000000001111110 ---.126的原码
//01111110 --->存在char类型里面的126
char c = a + b;
//因为只要是计算的话,char 和 short都会整形提升至int ,所以计算的时候还是用int
// 00000000000000000000000000000101
// 00000000000000000000000001111110
// 00000000000000000000000010000011 --->相加之后的结果
//存放在c里面的时候还是以char类型来存放
//10000011
//打印的时候是以%d(有符号的整数)的形式进行打印,又要提升为int整形
//此时的c符号位为1负数,所以高位全部1
//11111111111111111111111110000011 --->在内存中是以补码的形式进行存储(负数的原反补不相同所以要进行转换)
//10000000000000000000000001111101 --->原码
//-125
printf("%d\n", c);//-125
return 0;
}
运行结果为:
-125
练习2:
#include <stdio.h>
int main()
{
char a = 0xb6;//1011 0110
//如果改成unsigned char 整形提升的时候高位默认补的是0;下面的表达式就相等了。但它现在是有符号数,高位是1,提升的时候,认为这个数是负数,高位补的是1
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)//此时的a为 0x000000b6
printf("a");
if (b == 0xb600)//此时的b为 0x0000b600
printf("b");
if (c == 0xb6000000)
printf("c");
return 0;
}
运行结果为:
c
代码演示:
#include <stdio.h>
int main()
{
char c = 1;
printf("%u\n", sizeof(c));//1
printf("%u\n", sizeof(+c));//4
printf("%u\n", sizeof(-c));//4
return 0;
}
//有近一步的印证了,表达式进行运算的时候确实是存在整形提升的.
运行结果为:
1
4
4
2.算术转换
int float double
之间在进行计算的时候就会自动转化成较高的类型
3.运算符的优先级
-
问题表达式:加括号(可能还是会错误),或者简化代码,分开计算,尽量不要写这种不易理解的代码。
我们能确定先乘或者是先加,但是我们不能确定表达式中的操作数或者函数是哪一个先进行赋值或者说调用运算 -
C语言运算符优先级列表大全:https://blog.csdn.net/huangblog/article/details/8271791
总结
以上内容是对运算符内容的详细梳理,上述讲解的几个例题可以去试着练习一下,希望阅读完的码友能够有所收获!如果存在不足,欢迎在评论区指正!