当前位置: 首页 > article >正文

操作符详解

操作符也被叫做:运算符

操作符的分类

  • 算术操作符:+ 、- 、* 、/ 、%
  • 赋值操作符:= 、+= 、 -= 、 *= 、 /= 、%= 、<<= 、>>= 、&= 、|= 、^=
  • 移位操作符:<< >>
  • 位操作符:& | ^ ~
  • 单目操作符:!、++、--、&、*、+、-、~ 、sizeof、(类型)
  • 关系操作符:> 、>= 、< 、<= 、== 、!=
  • 逻辑操作符:&& 、||
  • 条件操作符:? :
  • 逗号表达式:,
  • 下标引用:[]
  • 函数调用:()
  • 结构成员访问:. 、->

算数操作符

算数操作符包括 `+ - * \ %` 。

2.1 `+` 和 `-`

`+` 和 `-` 用来完成加法和减法。
#include<stdio.h>
int main()
{  
    int a = 4 + 22;  
  int b = 61 - 23;  
  printf("%d\n%d\n", a, b);  
  return 0;
}

2.2 `*`

`*` 用来完成乘法。
#include<stdio.h>
int main()
{  
    int num = 5;    
    printf("%d", num * num);    
    return 0;
}

2.3 `/`

`/` 用来完成除法。

除号的两端如果是整数,执行的是整数除法,得到的也是整数。

#include<stdio.h>
int main()
{  
    int a = 6;    
    int b = 4;    
    int c = a / b;    
    printf("%d\n", c);//输出 1    
    printf("%f\n", c);//输出 1.000000    
    return 0;
}

C语言中 /执行的整数除法是整除,只会返回整数部分,丢弃小数部分

如果想要得到浮点型的结果,两个运算数至少有一个浮点数,此时C语言会进行浮点数除法。

#include<stdio.h>
int main()
{  
  int a = 6;    
  float b = 4.0f;    
  float c = a / b;    
  printf("%f", c);//输出 1.500000    
  return 0;
}

printf 打印浮点数默认保留6位小数。

#include<stdio.h>
int main()
{    
    float a = 6.0 / 4;    
    printf("%f", a);    
    return 0;
}

直接在代码中写浮点数,默认为 double类型。 此处的6.0为 double 类型。

2.4 `%`

`%` 表示求模运算,即返回**两个整数相除**的余值。这个运算符**只能用于整数,不能用于浮点数**。
#include<stdio.h>
int main()
{  
    int x = 6 % 4;    
    printf("%d", x);//输出 2    
    return 0;
}

负数求模的规则是:结果的正负号由第一个运算符的正负号决定

#include<stdio.h>
int main()
{    
    printf("%d\n", 11 % 5);//输出 1    
    printf("%d\n", 11 % -5);//输出 1    
    printf("%d\n", -11 % -5);//输出 -1    
    printf("%d\n", -11 % 5);//输出 -1   
    return 0;
}

在上述示例中, + - * / % 都是由有两个操作数的,位于操作符两端的就是它们的操作数,因此算术操作符也被称为双目操作符

赋值操作符

3.1 `=`

在变量创建好的时候给一个初始值叫做**初始化**,在变量创建好后,再给一个值,称为**赋值**。
int a = 3;//初始化
a = 5;//赋值

赋值语句的返回值是所赋的值。

#include<stdio.h>
int main()
{
    int x = 0;
    if (x = 0)
            printf("hehe");//x = 0的返回值为0,因此无输出
    return 0;
}

赋值操作符可以进行连续赋值

int a = 3;
int b = 5;
int c = 0;
c = b = a + 3;//连续赋值,b = c = 6

连续赋值从右到左依次赋值。

C语言虽然支持连续赋值,但是连续赋值写出来的代码不容易理解。

3.2 复合赋值符

这些赋值符有:
+=     -=
*=     /=     %=
>>=    <<=
&=     |=     ^=

写代码时用于简化对一个数进行自增,自减之类的操作。

int a = 10;
a += 3;// a = a + 3
a *= 3;// a = a * 3
a >>= 3;// a = a >> 3

单目操作符

单目操作符有这些:

!、++、--、&、*、+、-、~ 、sizeof、(类型)

4.1 `++`和 `- -`

**前置**:先+/-1,后使用。
#include<stdio.h>
int main()
{  
  int a = 10;    
  int b = ++a;//++的操作数是a,放在a前面的,就是前置++    
  printf("%d\n%d", a, b);//输出结果为a=11,b=11    
  return 0;
}

a原来是10,先+1变成11,再给b赋值

后置:先使用,后+/-1

#include<stdio.h>
int main()
{
    int a = 10;   
    int b = a++;//++的操作数是a,放在a后面的,就是后置++  
  printf("%d\n%d", a, b);//输出结果为a=11,b=10  
  return 0;
}

a原来是10,先赋值给b,再+1变成11

4.2 `+`和 `-`

这里的`+`是正号,`-`是负号。

+ 对正负值没有影响,是一个完全可以省略的操作符。

-用来改变一个值的正负号,负数的前面加上 -会得到正数,正数的前面加上 -就会得到负数。

#include<stdio.h>
int main()
{    
    int a = 1;
  int b = -1;
  printf("%d\n", +a);//输出 1
  printf("%d\n", +b);//输出 -1 
  printf("%d\n", -a);//输出 -1
  printf("%d\n", -b);//输出 1
  return 0;
 }

4.3 强制类型转换

语法形式: `(类型)`
int a = 3.14;
//a的类型为int,3.14为double类型
//两边类型不一致,编译器会报警告。
int a = (int)3.14;
//将3.14强制转换为int类型
//这种强制类型转换**只取整数部分**

:::tips
不到万不得已,尽量不使用强制类型转换。

:::

关系操作符

C语言用于比较的表达式,称为 `“关系表达式”(relational expression)`,里面使用的运算符就称为`“关系运算符”(relational operator)`,主要有下面6个:

:::tips

  • >大于运算符
  • <小于运算符
  • >=大于等于运算符
  • <=小于等于运算符
  • ==相等运算符
  • !=不相等运算符

:::

关系表达式通常返回 01 ,表示真假

C语言中, 0** 表示假,所有非零值表示真**。比如,20 > 12 返回1 ,12 > 20 返回0 。

关系表达式常用于 ifwhile结构。

if (x == 3) 
{
    printf("x is 3.\n");
}

注意:

相等运算符 == 与赋值运算符 = 是两个不一样的运算符,不要混淆。有时候,可能会不小心写出下面的代码,它可以运行,但很容易出现意料之外的结果。

if (x = 3)

上面示例中,原意是x == 3 ,但是不小心写成x = 3 。这个式子表示对变量x 赋值3 ,它的返回值为3 ,所以if 判断总是为真。

if (3 == x)

为了防止出现这种错误,当一个变量和一个常量比较相等时,将变量写在等号的右边

/* 报错 */
if (3 = x)

这样的话,如果把== 误写成= ,编译器就会报错。

另一个需要避免的错误是:多个关系运算符不宜连用

i < j < k

上面示例中,连续使用两个小于运算符。这是合法表达式,不会报错,但是通常达不到想要的结果,即不是保证变量j 的值在i 和k 之间

因为关系运算符是从左到右计算,所以实际执行的是下面的表达式。

(i < j) < k

上面式子中,i < j 返回 01 ,所以最终是 01 与变量 k 进行比较。

如果想要判断变量 j的值是否在 ik之间,应该使用下面的写法:

i < j && j < k

比如:我们输入一个年龄,如果年龄在18岁~36岁之间,我们输出青年。

如果我们这样写:

#include <stdio.h>
int main()
{
    int age = 0;
    scanf("%d", &age);
    if(18<=age<=36)
    {
        printf("⻘年\n");
    }
    return 0;
}

当我们输入10的时候,依然输出青年,如下图

这是因为,我们先拿 18age中存放的 10比较,表达式 18<=10为假, 18<=age 的结果是 0,再拿 036比较, 0<=36为真,所以打印了青年,所以即使当 age10的时候,也能打印青年,逻辑上是有问题。‘

正确的写法:

#include <stdio.h>
int main()
{
  int age = 0;
  scanf("%d", &age);
  if(age>=18 && age<=36)
  {
      printf("⻘年\n");
  }
  return 0;
}

条件操作符

条件操作符也叫**三目操作符**,需要接受三个操作数的,形式如下:
exp1 ? exp2 : exp3

条件操作符的计算逻辑是:

如果 exp1 为真, exp2 计算,计算的结果是整个表达式的结果;

如果exp1 为假, exp3 计算,计算的结果是整个表达式的结果。

练习找出两个数间的较大值

//方案一:if语句
#include<stdio.h>
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d",&a, &b);
    if (a > b)
        printf("%d", a);
    else
        printf("%d", b);
    return 0;
}

//方案二:条件操作符
#include<stdio.h>
int main()
{
    int a = 0;
    int b = 0;
    scanf("%d %d",&a, &b);
    int c = (a > b ? a : b);
    printf("%d", c);
    return 0;
}

逻辑操作符

逻辑运算符提供逻辑判断功能,用于构建更复杂的表达式,主要有下面三个运算符。
&&   ||
  1. i:逻辑取反运算符(改变单个表达式的真假)。
  2. && :逻辑与运算符,就是并且的意思(两侧的表达式都为真,则为真,否则为假)。
  3. || :逻辑或运算符,就是或者的意思(两侧至少有一个表达式为真,则为真,否则为假)。

7.1 逻辑取反运算符

比如,我们有一个变量叫flag ,如果flag为假,要做一个什么事情,就可以这样写代码:
#include<stdio.h>
int main()
{
    int flag = 0;
    if (!flag)
        printf("hehe");//输出hehe
    else
        printf("haha");
    return 0;
}
flag!flag
非 00
01

如果 flag 为真, !flag 就是假,如果 flag 为假, !flag 就是真。

所以上面的代码的意思就是 flag 为假,执行 if语句中的代码。

7.2 逻辑与运算符

`&&` 就是逻辑与运算符,也是**并且**的意思, `&&` **是一个双目操作符**,使用的方式是 `a && b`。

&& 两边的表达式都是真的时候,整个表达式才为真,只要有一个是假,则整个表达式为假

aba&&b
非 0非 01
非 000
0非 00
000

比如:如果我们说月份是3月到5月,是春天,那使用代码怎么体现呢?

#include<stdio.h>
int main()
{
    int month = 0;
    scanf("%d", &month);
    if (month > 3 && month < 5)
        printf("春天");
    return 0;
}

这里表达的意思就是month既要大于等于3,又要小于等于5,必须同时满足。

7.3 逻辑或运算符

`||` 就是逻辑或运算符,也就是**或者**的意思, `||` 也是一个**双目操作符**,使用的方式是 `a || b` 。

||两边的表达式只要有一个是真,整个表达式就是真,两边的表达式都为假的时候,才为假。

比如:我们说一年中月份是12月或者1月或者2月是冬天,那么我们怎么使用代码体现呢?

#include<stdio.h>
int main()
{
  int month = 0;
  if (month = 1 || month = 2 || month = 12)
    printf("冬季");
  return 0;
}

7.4 练习:闰年的判断

输入一个年份year,判断year是否是闰年

闰年判断的规则:

  1. 能被4整除并且不能被100整除是闰年
  2. 能被400整除是闰年
//标准版
#include<stdio.h>
int main()
{
    int year = 0;
    scanf("%d", &year);
    if (year % 100 != 0 && year % 4 == 0)
        printf("是闰年");
    else if (year % 400 == 0)
        printf("是闰年");
    else
        printf("不是闰年");
    return 0;
}

//简洁版
#include<stdio.h>
int main()
{
    int year = 0;
    scanf("%d", &year);
    if (year % 100 != 0 && year % 4 == 0 || year % 400 == 0)//合二为一,更加简洁
        printf("是闰年");
    else
        printf("不是闰年");
    return 0;
}

7.5 短路

C语言逻辑运算符还有一个特点,它总是**先对左侧的表达式求值,再对右边的表达式求值**,这个顺序是保证的。

如果左边的表达式满足逻辑运算符的条件,就不再对右边的表达式求值。这种情况称为“短路”

如前面的代码:

if(month >= 3 && month <= 5)

表达式中 && 的左操作数是 month >= 3 ,右操作数是 month <= 5 ,当左操作数 month >= 3 的结果是 0的时候,即使不判断 month <= 5 ,整个表达式的结果也是 0(不是春季)。

所以,对于 &&操作符来说,**左边操作数的结果是 **0的时候,右边操作数就不再执行

对于|| 操作符是怎么样呢?我们结合前面的代码:

if(month == 12 || month==1 || month == 2)

如果 month == 12,则不用再判断 month是否等于 1或者 2,整个表达式的结果也是 1(是冬季)。

所以, || 操作符的**左操作数的结果不为 **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 = 0;
a = 1;
b = 2;
c = 3;
d = 4;
i = a ++;//i = 0,&&左侧为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 = 0;
a = 1;
b = 3;
c = 3;
d = 4;
i = a ++;//i = 0,||左侧为0,继续运行,b = 3 !=0,短路。

移位操作符

<< 左移操作符

>> 右移操作符

注:移位操作符的操作数只能是整数(的补码)

8.1 左移操作符

移位规则:**左边抛弃、右边补0**
#include<stdio.h>
int main()
{
    int num = 10;
    int n = num << 1;
    printf("%d\n", num);//输出10
    printf("%d\n", n);//输出20
    return 0;
}

8.2 右移操作符

移位规则:首先右移运算分两种:
  1. 逻辑右移:左边用0填充,右边丢弃
  2. 算术右移::左边用该值的符号位填充,右边丢弃

逻辑右移1位演示

算术右移1位演示

逻辑右移还是算数右移,取决于编译器

目前主流的编译器通常为算数右移

#include<stdio.h>
int main()
{
    int num = -10;
    int n = num >> 2;
    printf("%d\n", num);//输出-10
    printf("%d\n", n);//算数右移:输出-3
    return 0;
}

警告:对于移位运算符,不要移动负数位,这个是标准未定义的。

int num = 10; 
num>>-1;//error 

位操作符

位操作符有: `& | ^ ~`

注:他们的操作数必须是整数(的补码)

9.1 `& |`

& //按位与,只要有0,则为0,两个同时为1,才为1
| //按位或,只要有1,则为1,两个同时为0,才为0

#include<stdio.h>
int main()
{
    int a = 3;
    int b = -5;
    //按位与  -对应的二进制位进行运算,只要有0,则为0,两个同时为1,才为1
    int c = a & b;
    //按位或  -对应的二进制位进行运算,只要有1,则为1,两个同时为0,才为0
    int d = a | b;
    /*
    00000000 00000000 00000000 00000011 3的原码
    00000000 00000000 00000000 00000011 3的补码
    
    10000000 00000000 00000000 00000101  -5的原码
    11111111 11111111 11111111 11111011  -5的补码
    
    00000000 00000000 00000000 00000011 3的补码
    11111111 11111111 11111111 11111011  -5的补码
    
    00000000 00000000 00000000 00000011 c的补码
    00000000 00000000 00000000 00000011 c的原码
    
    11111111 11111111 11111111 11111011  d的补码
    10000000 00000000 00000000 00000101  d的原码
    */
    printf("%d\n", c);//输出3
    printf("%d", d);//输出-5
    return 0;
}

9.2 `^`

^ //按位异或,相同为0,相异为1

#include<stdio.h>
int main()
{
    int a = 3;
    int b = -5;
    //按位异或  -对应的二进制位进行运算,相同为0,相异为1
    int c = a ^ b;
    /*
    00000000 00000000 00000000 00000011 3的原码
    00000000 00000000 00000000 00000011 3的补码
    
    10000000 00000000 00000000 00000101 -5的原码
    11111111 11111111 11111111 11111011 -5的补码
    
    00000000 00000000 00000000 00000011 3的补码
    11111111 11111111 11111111 11111011 -5的补码
     
    11111111 11111111 11111111 11111000 c的补码
    10000000 00000000 00000000 00001000 c的原码
    */
    printf("%d\n", c);//输出-8
    return 0;
}

9.3 `~`

~ //按位取反,所有二进制位取反

#include<stdio.h>
int main()
{
    int a = 3;
    //按位取反  -对应的二进制位进行运算,所有二进制位取反
    int b = ~a;
    /*
    00000000 00000000 00000000 00000011 3的原码
    00000000 00000000 00000000 00000011 3的补码
     
     b的补码
    11111111 11111111 11111111 11111100 b的补码
    10000000 00000000 00000000 00000100 b的原码
    */
    printf("%d\n", b);//输出-4
    return 0;
}

9.4 练习

练习1:
**不能创建临时变量(第三个变量),实现两个数的交换。**
//方法一
#include<stdio.h>
int main()
{
    int a = 0, b = 0;
    scanf("%d %d", &a, &b);
    a = a + b;
    b = a - b;
    a = a - b;
    printf("%d %d", a, b);
    return 0;
}
//缺点:有溢出的风险
//方法二
#include<stdio.h>
int main()
{
    int a = 0, b = 0;
    scanf("%d %d", &a, &b);
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    printf("%d %d", a, b);a = a + b;
    return 0;
}
//缺点
//1. 可读性差
//2. 只可用于整数
//3. 效率较低

原理:

a ^ a = 0 ,a ^ 0 = a;

a’ = a ^ b

b’ = a’ ^ b = a ^ b ^ b = a

a’’ = a’ ^ b’ = a ^ b ^ a = b

练习2:
**编写代码实现:求一个整数存储在内存中的二进制中1的个数。**
//方法一
#include<stdio.h>
int main()
{
    int num = 0;
    int count = 0;
    scanf("%d", &num);
    while (num)
    {
        if (num % 2 == 1)
            count++;
        num /= 2;
    }
    printf("%d", count);
    return 0;
}
//显然,若num为负数,原码不同于补码时,则这种方法会出现问题
//方法二
#include<stdio.h>
int main()
{
    int num = 0;
    int count = 0;
    scanf("%d", &num);
    int i = 0;
    for (i = 0; i < 32; i++)
    {
        if (num & (1 << i))
        //if ((num >> i) & 1 == 1)
            count++;
    }
    printf("%d", count);
    return 0;
}
//缺陷
//循环必须要运行32次

原理:

if (num & (1 << i))

1 << i 只有一位为1,则 num & (1 << i )除了1 << i 中为1的那一位外皆为0,可判断num1 << i 中为1的那一位是0还是1

if ((num >> i) & 1 == 1) :

直接判断num的第i位为01

//方法三
#include<stdio.h>
int main()
{
    int num = 0;
    int count = 0;
    scanf("%d", &num);
    while (num)
    {
        num = num & (num - 1);
        count++;
    }
    printf("%d", count);
    return 0;
}

原理:

num = num &(num - 1)可以去掉num的补码最右侧的1

num= 0 之前,num = num &(num - 1)可以执行几次,则num的补码中有几个1

衍生练习:判断n是否为2的幂次方数

//2的幂次方数的特点为二进制数中只有一个1 
#include<stdio.h>
int main()
{ 
    int num = 0; 
    int count = 0; 
    scanf("%d", &num); 
    int n = num; 
    while (n) 
    { 
        n = n & (n - 1); 
        count++; 
    } 
    if (count == 1) 
        printf("%d是2的幂次方数", num); 
    else 
        printf("%d不是2的幂次方数", num); 
    return 0; 
} 
练习3:
**二进制位置0或者置1**

编写代码将13二进制序列的第5位修改为1,然后再改回0

13的2进制序列: 00000000000000000000000000001101

将第5位置为1后:00000000000000000000000000011101

将第5位再置为0:00000000000000000000000000001101

#include<stdio.h>
int main()
{
    int a = 13;
    a = a | (1 << 4);
    printf("a是%d\n", a);
    a = a ^ (1 << 4);
    //a = a & ~(1 << 4);
    printf("a是%d\n", a);
    return 0;
}

逗号表达式

逗号表达式,就是用逗号隔开的多个表达式。

exp1, exp2, exp3, …expN

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

//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//c = 13

//代码2
if (a = b + 1, c = a / 2, d > 0)

//代码3
while (a = get_val(), count_val(a), a>0)
{

}

下标访问[]、函数调用()

11.1 [ ] 下标引用操作符

操作数:**一个数组名 + 一个索引值**
int arr[10];//创建数组
arr[9] = 10;//实⽤下标引⽤操作符。
//[ ]的两个操作数是arr和9。

11.2 函数调用操作符

接受**一个或者多**个操作数:**第一个操作数是函数名,剩余的操作数就是传递给函数的参数**。
#include <stdio.h>
int Add(int x, int y)
{
    return x + y;
}
int main()
{
//这⾥的()就是作为函数调⽤操作符。
 printf("hello,world"); //2个操作数。
 Add(3, 5);//3个操作数。
 return 0;
}

操作符的属性:优先级、结合性

C语言的操作符有2个重要的属性:优先级、结合性,这两个属性决定了**表达式求值的计算顺序**。

:::tips
优先级与结合性:

由于圆括号**()**的优先级最高,可以使用它改变其他运算符的优先级

12.1 优先级

优先级指的是,如果一个表达式包含多个运算符,哪个运算符应该优先执行。**各种运算符的优先级是不一样的**。
3 + 4 * 5;

上面示例中,表达式3 + 4 * 5 里面既有加法运算符+,又有乘法运算符*。由于乘法的优先级高于加法,所以会先计算4 * 5 ,而不是先计算3 + 4

12.2 结合性

如果两个**运算符优先级相同**,就要根据运算符是**左结合,还是右结合**,决定执行顺序。

大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符(= )

5 * 6 / 2;

上面示例中,*/ 的优先级相同,它们都是左结合运算符,所以从左到右执行,先计算5 * 6 ,


http://www.kler.cn/a/374898.html

相关文章:

  • Ethernet 系列(6)-- 基础学习::OSI Model
  • Python之groupby()及aggregate()方法
  • 如何保护网站安全
  • 高并发-负载均衡
  • Springboot 整合 Java DL4J 实现文本分类系统
  • 百度如何打造AI原生研发新范式?
  • Java第二阶段---15异常---第三节 自定义异常
  • 【智能算法应用】秃鹰搜索算法求解二维路径规划问题
  • 适合视频搬运的素材网站推荐——短视频素材下载宝库
  • DirectShow过滤器开发-写MP3音频文件过滤器(再写 写MP3)
  • 鸿蒙系统的优势 不足以及兼容性与未来发展前景分析
  • C++基础_类的基本理解
  • 『 Linux 』网络传输层 - TCP(二)
  • NLP算法工程师精进之路:顶会论文研读精华
  • Rust整合Elasticsearch
  • el-tree展开子节点后宽度没有撑开,溢出内容隐藏了,不显示横向滚动条
  • 使用LangChain控制大模型的输出——解析器Parser
  • 人工智能:塑造未来生活的强大力量
  • 计组-层次化存储结构
  • uniapp+vite配置环境变量
  • Docker | 将本地项目发布到阿里云的实现流程
  • 第3关:命题逻辑推理
  • TQ15EG开发板教程:fmcomms8两片ADRV9009同步采集测试
  • SpringBoot后端开发常用工具详细介绍——flyway数据库版本控制工具
  • MyBatisPlus 中 LambdaQueryWrapper使用
  • ffmpeg+vue2