C语言中的操作符

C语言中会用到很多操作符,下面来简单介绍一下:

算术操作符(+ - * / %)

前三个没什么好说的,主要注意一下后两个,/是得到两个数相除的 ,如果两边的数都是整数就进行整数除法,如果有一个浮点数就进行浮点数除法。%是得到两个整数相除的余数,也叫取模,它的两个操作数必须都是整数。

移位操作符(<< >>)

移位操作符的操作数只能是整数 。左移操作符相当于乘2,右移不一定相当于除2,如果是负数就不是除2,对于移位运算符,不要移动负数位,这个是标准未定义的。

cpp 复制代码
<<                    //左移操作符,左边丢弃,右边补0,相当于乘2
int main()
{
    int a = 7;
    int b = -7;
    //00000000000000000000000000000111------>7的补码
    //00000000000000000000000000001110------>左边丢弃,右边补0,得到14
    printf("%d",a << 1);//14
    //11111111111111111111111111111001------>-7的补码
    //11111111111111111111111111110010------>左边丢弃,右边补0后的补码
    //10000000000000000000000000001110------>左移后的原码,-14
    printf("%d",b << 1);//-14
}
cpp 复制代码
>>                        //右移操作符,分为算数右移和逻辑右移,vs编译器用的是算术右移
//算数右移:右边丢弃,左边补符号位
//逻辑右移:右边丢弃,左边补0
int main()
{
    int a = 7;
    int b = -7;
    //00000000000000000000000000000111--->7的补码
    //00000000000000000000000000000011--->右边丢弃,左边补符号位,得到3
    printf("%d",a >> 1);//3
    //11111111111111111111111111111001--->-7的补码
    //11111111111111111111111111111100--->右边丢弃,左边补符号位,得到的补码
    //10000000000000000000000000000100--->右移后的原码,得到-4
    printf("%d",b >> 1);//-4
}

位操作符(& | ^)

cpp 复制代码
&                        //按位(二进制位)与,有0为0,全1为1
|                        //按位(二进制位)或,有1为1,全0为0
^                        //按位(二进制位)异或,相同为0,不同为1
int main()
{
    int a = 3; 
    int b = -5;
    //000000000000000000000000000011--->3的补码
    //111111111111111111111111111011--->-5的补码
    //000000000000000000000000000011--->按位与后,得到3
    printf("%d\n",a & b);//3
    //000000000000000000000000000011--->3的补码
    //111111111111111111111111111011--->-5的补码
    //111111111111111111111111111011--->按位或后得到的补码
    //100000000000000000000000000101--->按位或后的原码,得到-5
    printf("%d\n",a | b);//-5
    //000000000000000000000000000011--->3的补码
    //111111111111111111111111111011--->-5的补码
    //111111111111111111111111111000--->按位异或后得到的补码
    //100000000000000000000000001000--->按位异或后得到的原码,得到-8
    printf("%d\n",a ^ b);//-8
}

不能创建临时变量,实现两个数交换,先说几个异或的性质:

①a ^ a = 0;

②a ^ 0 = a;

③异或支持交换律,a ^ b ^ a = a ^ a ^ b = b

cpp 复制代码
//方法1,问题是可能会溢出,如果a+b溢出了就会出错
int main()
{
    int a = 10;
    int b = 20;
    a = a + b;//a = a + b
    b = a - b;//b = a + b - b = a
    a = a - b;//a = a + b - a = b
    printf("a = %d, b = %d\n",a,b);
}
//方法2,问题是只能用于整数
int main()
{
    int a = 10;
    int b = 20;
    a = a ^ b;//a = a ^ b
    b = a ^ b;//b = a ^ b ^ b = a ^ 0 = a---->b = a;
    a = a ^ b;//a = a ^ b ^ a = 0 ^ b = b---->a = b;
    printf("a = %d, b = %d\n",a,b);
}

虽然有以上两种方法,但最常用的方法还是增加一个临时变量。

求一个整数存储在内存中二进制位1的个数:

cpp 复制代码
//方法1,这种方法的输入参数必须是unsigned int,如果是int的话,输入-1,其补码都是1,应该输出32,但是按照上述代码,会输出0.
int main()
{
    int a = 10;
    int num = 0;
    while(a)
    {
        if(a % 2 == 1)
            num++;
        a /= 2;
    }
    printf("%d\n",num);
    return 0;
}
//方法2,这种方法需要循环32次
int main()
{
     int num = -1;
     int i = 0;
     int count = 0;//计数
     for(i=0; i<32; i++)
     {
         if( num & (1 << i) )//如果该位与上1不为0,就说明该位是1
         count++; 
     }
     printf("二进制中1的个数 = %d\n",count);
     return 0;
}
//方法3,最好的方法
int Num_of_1(int a)
{
    int count = 0;
    int i = 0;
    while (a)
    {
        a = a & (a - 1);
        count++;
    }
    return count;
}
//以a = 15为例
//1111---->a的补码
//1110---->a - 1
//1110---->a = a & (a - 1)
//1101---->a - 1
//1100---->a = a & (a - 1)
//1011---->a - 1
//1000---->a = a & (a - 1)
//0111---->a - 1
//0000---->a = a & (a - 1),循环结束
//可以得出结论,a & (a - 1)就是消掉了最右边的那个1,直到a中的1全部消失,即a = 0时循环了几次就有几个1.
//上述技巧还可以用来判断一个数是不是2的n次方,如果一个数是2的n次方,那么它只有最高位是1,其他位都是0,所以用a = a & (a-1)如果==0,则说明该数是2的n次方。

赋值操作符(= += -= *= /= %= &= |= ^= <<= >>=)

赋值操作符用于对变量赋值:

cpp 复制代码
int a = 0;
int b = 0;
a = 10;//赋值
a += 10;//相当于a = a + 10;,其他的同理
a = b = 10;//相当于b = 10; a = b;

单目操作符(只有一个操作数)

cpp 复制代码
!            //逻辑取反,将真变成假,假变成真,c语言中0为假,非零为真
int main()
{
    int flag = 0;
    if(!flag)//如果flag是假,取反变为真,才会执行printf
    {
        printf("hello world");
    }
    return 0;
}
cpp 复制代码
-                        //取相反数
+                        //符号不变,没啥卵用
int main()
{    
    int a = -10;
    int b = -a;
    printf("%d",b);//会得到10
}    
cpp 复制代码
&                //取地址
sizeof            //计算大小,单位是字节
int main()
{
    int a = 10;
    printf("%d",sizeof(a));
    printf("%d",sizeof(int));//前两种是常用用法
    printf("%d",sizeof a);//这样用也可以,因为sizeof是操作符,而不是函数,()可以省略
    printf("%d",sizeof int);//如果操作数是数据类型,()不能省略,所以这种写法不对
}
cpp 复制代码
~                //对一个数的二进制位取反,符号位也会取反
--                //前置--是先--再操作,后置是先操作再--
++                //同上
//把一个数的某一位清零或置一的写法如下
//把13的bit1置1
int main()
{
    int a = 13;
    a |= 1<<1;
    //00000000000000000000000000001101--->13的补码
    //00000000000000000000000000000010--->1<<1的补码
    //00000000000000000000000000001111--->a |= 1<<1,得到15
    printf("%d",a);//15
    
}
//把13的bit3清零
int main()
{
    int a = 13;
    a &=~ 1<<3;
    //00000000000000000000000000001101--->13的补码
    //00000000000000000000000000001000--->1<<3的补码
    //11111111111111111111111111110111--->~1<<3的补码
    //00000000000000000000000000000101--->a &=~ 1<<3,得到5
    printf("%d",a);//5
    
}
int main()
{
    int a = 10;
    int b = a--;
    printf("%d",a);//9,因为--了
    printf("%d",b);//10,因为是先操作后--,b = a, a = a - 1
}
int main()
{
    int a = 10;
    int b = --a;
    printf("%d",a);//9,因为--了
    printf("%d",b);//9,因为是先--后操作,a = a - 1,b = a
}
cpp 复制代码
*                //解引用操作符,获取地址中存放的值
(类型)            //强制类型转换,把变量转换成()中的类型

关系操作符(> >= < <= != ==)

主要用与判断两个操作数的关系,需要注意不要把==写成赋值操作符=。

逻辑操作符(&& ||)

&&是逻辑与,全真才为真,有一个假就是假。||是逻辑或,全假才为假,有一个真就是真。

如果&&左边的操作数为假,结果就肯定为假,右边的操作数就不算了。如果||左边的操作数为真,结果就肯定为真,右边的操作数就不算了。

cpp 复制代码
#include <stdio.h>
int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++;//这里a为0,由于是后置++,先使用再++,a为0,&&的结果肯定是0,后边就不算了,所以只有a会++
    //i = a++||++b||d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);//a = 1,b = 2,c = 3,d = 4
    return 0;
}

三目操作符

cpp 复制代码
exp1 ? exp2 : exp3//如果exp1为真就执行exp2,如果exp1为假就执行exp3
//常用于求两个数中的最大值
int main()
{
    int a = 10;
    int b = 20;
    int max = a > b ? a : b;//由于a < b ,第一个表达式为假,执行第三个表达式,max = b
}

逗号表达式

逗号表达式就是用逗号隔开的多个表达式。 逗号表达式从左向右依次执行,整个表达式的结果是最后一个表达式的结果。

cpp 复制代码
//代码1
int a = 1;
int b = 2;
//        0, a = 12, 12, b = 13
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少?
//c = b = 13;

下标引用,函数调用和结构成员

[]是下标引用操作符,用于引用数组下标,有两个操作数:数组名和下标索引值,两个操作数的位置可以交换。

cpp 复制代码
int arr[10] = {0};
arr[7] = 10;
7[arr] = 9;
*(arr + 7) = 8;
*(7 + arr) = 7;
//以上四种写法都可以改变arr中下标为7的数字,都是对的,第一种和第三种常用

()是函数调用操作符,操作数是函数名和函数参数。

访问一个结构体的成员可以使用结构体对象.成员名 或者结构体指针->成员名

表达式求值

表达式求值的顺序一般由操作符的优先级和结合性来决定,有些表达式操作数求值过程中也会进行一些类型转换。

隐式类型转换

C语言的整形算术运算总是以int精度 来进行,为了达到精度,char或者short类型运算时会发生整型提升。整型提升的原则如下:

cpp 复制代码
//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0

下面举一个例子:

cpp 复制代码
int main()
{
    //00000000000000000000000000000101--->5的补码
    //00001001--->截断
    char a = 5;
    //00000000000000000000000001111110--->126的补码
    //01111110--->截断
    char b = 126;
    //00000000000000000000000000000101--->5整型提升后的补码,因为是char类型,前面加符号位0
    //00000000000000000000000001111110--->126整型提升后的补码,因为是char类型,前面加符号位0
    //00000000000000000000000010000011--->相加后的补码
    //10000011--->截断,得到c
    char c = a + b;
    //%d打印需要整型提升
    //11111111111111111111111110000011--->因为是char,整型提升加符号位1,得到整型提升后的补码
    //10000000000000000000000001111101--->提升后的原码,-125
    printf("%d\n",c);//-125
}
cpp 复制代码
int main()
{
 char c = 1;
 printf("%u\n", sizeof(c));//c是char类型,sizeof是1
 printf("%u\n", sizeof(+c));//+c是一个表达式,会发生整型提升,提升成int,sizeof是4
 printf("%u\n", sizeof(-c));//同上
 return 0;
}

算数转换

如果某个操作符的操作数类型不同,需要转换成相同类型再运算,如果某个操作数的类型在下面的表中排名靠下,需要转换成另一个操作数的类型,比如,int和float中的int就需要转换成float。

cpp 复制代码
long double
double
float
unsigned long int
long int
unsigned int
int

操作符优先级

各个操作符的优先级及结合性如下表,从上往下优先级降低:

|--------|---------------|------|
| 操作符 | 描述 | 结合性 |
| () | 函数调用 | 从左到右 |
| [] | 下标引用 | 从左到右 |
| . | 访问结构体成员 | 从左到右 |
| -> | 访问结构体指针成员 | 从左到右 |
| ++ | 后置++ | 从左到右 |
| -- | 后置-- | 从左到右 |
| ! | 逻辑反 | 从右到左 |
| ~ | 按位取反 | 从右到左 |
| + | 单目操作符,正 | 从右到左 |
| - | 单目操作符,负 | 从右到左 |
| ++ | 前置++ | 从右到左 |
| -- | 前置-- | 从右到左 |
| * | 地址解引用 | 从右到左 |
| & | 取地址 | 从右到左 |
| sizeof | 求大小,单位是字节 | 从右到左 |
| (类型) | 强制类型转换 | 从右到左 |
| * | 乘法 | 从左到右 |
| / | 除法 | 从左到右 |
| % | 取余 | 从左到右 |
| + | 加法 | 从左到右 |
| - | 减法 | 从左到右 |
| << | 左移位 | 从左到右 |
| >> | 右移位 | 从左到右 |
| > | 大于 | 从左到右 |
| >= | 大于等于 | 从左到右 |
| < | 小于 | 从左到右 |
| <= | 小于等于 | 从左到右 |
| == | 等于 | 从左到右 |
| != | 不等于 | 从左到右 |
| & | 按位与 | 从左到右 |
| ^ | 按位异或 | 从左到右 |
| | | 按位或 | 从左到右 |
| && | 逻辑与 | 从左到右 |
| || | 逻辑或 | 从左到右 |
| ?: | 三目操作符 | 无 |
| = | 各种赋值,+=,-=之类的 | 从右到左 |
| , | 逗号表达式 | 从右到左 |

可以看出,优先级最低的不是赋值,而是逗号表达式

相关推荐
孤寂大仙v4 分钟前
【C++】STL----list常见用法
开发语言·c++·list
她似晚风般温柔7891 小时前
Uniapp + Vue3 + Vite +Uview + Pinia 分商家实现购物车功能(最新附源码保姆级)
开发语言·javascript·uni-app
咩咩大主教1 小时前
C++基于select和epoll的TCP服务器
linux·服务器·c语言·开发语言·c++·tcp/ip·io多路复用
时光飞逝的日子1 小时前
多重指针变量(n重指针变量)实例分析
c语言·指针·多重指针·双重指针·n重指针·指针变量
FuLLovers1 小时前
2024-09-13 冯诺依曼体系结构 OS管理 进程
linux·开发语言
everyStudy2 小时前
JS中判断字符串中是否包含指定字符
开发语言·前端·javascript
luthane2 小时前
python 实现average mean平均数算法
开发语言·python·算法
Ylucius3 小时前
动态语言? 静态语言? ------区别何在?java,js,c,c++,python分给是静态or动态语言?
java·c语言·javascript·c++·python·学习
凡人的AI工具箱3 小时前
AI教你学Python 第11天 : 局部变量与全局变量
开发语言·人工智能·后端·python
sleP4o3 小时前
Python操作MySQL
开发语言·python·mysql