<C语言> 操作符

news2025/4/9 0:35:00

1.算术操作符

  • 加法(+):用于将两个操作数相加。
  • 减法(-):用于将第一个操作数减去第二个操作数。
  • 乘法(*):用于将两个操作数相乘。
  • 除法(/):用于将第一个操作数除以第二个操作数。
  • 取模(%):用于求两个操作数相除的余数。
void test1() {
    //int a = 5.0 % 2.0;  //err `%` 操作符的两个操作数必须为整数

    int a = 10;
    int b = 3;
    int sum = a + b;       // 加法
    int difference = a - b;// 减法
    int product = a * b;   // 乘法
    int quotient = a / b;  // 除法
    int remainder = a % b; // 取模

    printf("Sum: %d\n", sum);              //13
    printf("Difference: %d\n", difference);//7
    printf("Product: %d\n", product);      //30
    printf("Quotient: %d\n", quotient);    //3
    printf("Remainder: %d\n", remainder);  //1
}
  • 乘法、除法和取模具有相同的优先级,高于加法和减法。

  • 优先级相同的操作符按照从左到右的顺序进行求值,除非使用括号改变优先级。

  • 在混合数据类型的表达式中,C语言会进行隐式类型转换,将较低的数据类型提升为较高的数据类型,以执行正确的计算。

  • 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。

  • 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。

  • % 操作符的两个操作数必须为整数。返回的是整除之后的余数。

2.移位操作符

<< 左移操作符
>> 右移操作符
    
//注:移位操作符的操作数只能是整数。

2.1 左移操作符

左移操作符(<<)将一个数的所有位向左移动指定的位数。移位后,低位用零填充。

左边抛弃、右边补0

在这里插入图片描述

void test2(){
    int a = 4;
	//00000000000000000000000000000100 - 4的补码
	
	int b = a << 1; //把a向左移动一位的值给b,a的值不变
    //b的补码:00000000000000000000000000001000 
	printf("a=%d b=%d\n", a, b);  //a=4 b=8
}
void test3() {
    int a = -4;
    //10000000000000000000000000000100 - -4的原码
    //11111111111111111111111111111011 - -4的反码
    //11111111111111111111111111111100 - -4的补码

    int b = a << 1;//把a向左移动一位
    //11111111111111111111111111111000 - b中存储的补码
    //11111111111111111111111111110111 - b的反码
    //10000000000000000000000000001000 - b的原码
    //-8
    printf("a=%d b=%d\n", a, b);   //a=-4 b=-8
}

2.2 右移操作符

右移操作分为两种类型:逻辑右移和算术右移。

  • 逻辑右移(Logical Right Shift):对于无符号整数,逻辑右移将高位用零填充。
  • 算术右移(Arithmetic Right Shift):对于有符号整数,算术右移将高位用符号位填充,以保持数值的符号不变。
  • 逻辑移位 左边用0填充,右边丢弃
  • 算术移位 左边用原该值的符号位填充,右边丢弃

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ltt0uW9v-1688047919060)(C:\Users\TaeYeon\AppData\Roaming\Typora\typora-user-images\image-20230629195712018.png)]

void test4() {
    int a = -4;
    //10000000000000000000000000000100 - -4的原码
    //11111111111111111111111111111011 - -4的反码
    //11111111111111111111111111111100 - -4的补码

    int b = a >> 1;//
    //11111111111111111111111111111100
    //11111111111111111111111111111110 - b在内存中的补码
    //11111111111111111111111111111101 - b的反码
    //10000000000000000000000000000010 - b的原码
    //-2
    printf("a=%d b=%d\n", a, b);  //a=-4 b=-2
}
void test5() {
    int a = 4;
    //00000000000000000000000000000100 补码
    
    int b = a >> 1;
    //00000000000000000000000000000010
    printf("a=%d b=%d\n", a, b);  //a=4 b=2
}

注意:

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

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

3.位操作符

  1. 按位与(&):将两个操作数的对应位进行逻辑与操作,生成一个新的值。
  2. 按位或(|):将两个操作数的对应位进行逻辑或操作,生成一个新的值。
  3. 按位异或(^):将两个操作数的对应位进行逻辑异或操作,生成一个新的值。如果两个位相同,则结果位为0,否则为1。
  4. 按位取反(~):对一个操作数的所有位进行逻辑取反操作,将1变为0,0变为1。

注:他们的操作数必须是整数。

void test6() {
    int a = 3;
    int b = -5;
    int c = a & b;//& - 按(2进制)位与
    printf("%d\n", c);    //3

    //
    //00000000000000000000000000000011 - 3的补码
    //10000000000000000000000000000101 -5的原码
    //11111111111111111111111111111010 -5的反码
    //11111111111111111111111111111011 -5的补码
    
    //补码计算
    //00000000000000000000000000000011
    //11111111111111111111111111111011
    //00000000000000000000000000000011 - 3
}
void test7() {
    int a = 3;
    int b = -5;
    int c = a | b;//& - 按(2进制)位或
    printf("%d\n", c);

    //
    //00000000000000000000000000000011 -> 3的补码
    //10000000000000000000000000000101 -5的原码
    //11111111111111111111111111111010 -5的反码
    //11111111111111111111111111111011 -5的补码
    //
    //00000000000000000000000000000011
    //11111111111111111111111111111011
    //11111111111111111111111111111011   得到的补码结果需要转换成原码
    //11111111111111111111111111111010   -1得到反码
    //10000000000000000000000000000101 -> -5
}
void test8() {
    int a = 3;
    int b = -5;
    int c = a ^ b;//& - 按(2进制)位异或
    printf("%d\n", c);
    //异或的运算:相同为0,相异为1
    //
    //00000000000000000000000000000011 -> 3的补码
    //10000000000000000000000000000101 -5的原码
    //11111111111111111111111111111010 -5的反码
    //11111111111111111111111111111011 -5的补码
    //
    //00000000000000000000000000000011
    //11111111111111111111111111111011   
    //11111111111111111111111111111000   //补码亦或结果
    //11111111111111111111111111110111   //-1得到反码
    //10000000000000000000000000001000   //原码
    //-8
}

练习1

不能创建临时变量(第三个变量),实现两个数的交换。

void test9() {
    int a = 10;
    int b = 20;
    a = a ^ b;
    b = a ^ b;   //b=a^b^b   b^b=0  0^a=a  b=a
    a = a ^ b;   //a=a^a^b   a=b
    printf("a = %d b = %d\n", a, b);   //a = 20 b = 10
}

练习2

编写代码实现:求一个整数存储在内存中的二进制中1的个数。

//方法1:
#include <stdio.h>
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;
}
//思考这样的实现方式有没有问题?

//方法2:
#include <stdio.h>
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;
}

//思考还能不能更加优化,这里必须循环32次的。
//方法3:
#include <stdio.h>
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;
}
//这种方式是不是很好?达到了优化的效果,但是难以想到。

4.赋值操作符

  1. 简单赋值(=):将右侧表达式的值赋给左侧的变量。例如:a = 10; 表示将值10赋给变量a。
  2. 加法赋值(+=):将右侧表达式的值加到左侧变量上,并将结果赋给左侧变量。例如:a += 5; 表示将变量a的值加5后再赋给a。
  3. 减法赋值(-=):将右侧表达式的值从左侧变量中减去,并将结果赋给左侧变量。例如:a -= 3; 表示将变量a的值减3后再赋给a。
  4. 乘法赋值(*=):将右侧表达式的值与左侧变量相乘,并将结果赋给左侧变量。例如:a *= 2; 表示将变量a的值乘以2后再赋给a。
  5. 除法赋值(/=):将左侧变量的值除以右侧表达式的值,并将结果赋给左侧变量。例如:a /= 4; 表示将变量a的值除以4后再赋给a。
  6. 取模赋值(%=):将左侧变量的值模除以右侧表达式的值,并将余数赋给左侧变量。例如:a %= 7; 表示将变量a的值对7取模后的余数赋给a。
  7. 左移赋值(<<=):将左侧变量的值左移右侧表达式指定的位数,并将结果赋给左侧变量。例如:a <<= 3; 表示将变量a的值左移3位后再赋给a。
  8. 右移赋值(>>=):将左侧变量的值右移右侧表达式指定的位数,并将结果赋给左侧变量。例如:a >>= 2; 表示将变量a的值右移2位后再赋给a。
  9. 按位与赋值(&=):将左侧变量的值与右侧表达式的值进行按位与操作,并将结果赋给左侧变量。例如:a &= b; 表示将变量a与变量b进行按位与操作后的结果赋给a。
  10. 按位或赋值(|=):将左侧变量的值与右侧表达式的值进行按位或操作,并将结果赋给左侧变量。例如:a |= b; 表示将变量a与变量b进行按位或操作后的结果赋给a。
  11. 按位异或赋值(^=):将左侧变量的值与右侧表达式的值进行按位异或操作,并将结果赋给左侧变量。例如:a ^= b; 表示将变量a与变量b进行按位异或操作后的结果赋给a。

5.单目操作符

  1. 自增(++):将操作数的值增加1。有前缀和后缀两种用法:
    • 前缀自增:++operand,先将操作数加1,然后返回增加后的值。
    • 后缀自增:operand++,先返回操作数的值,然后再将操作数加1。
  2. 自减(–):将操作数的值减少1。有前缀和后缀两种用法:
    • 前缀自减:–operand,先将操作数减1,然后返回减少后的值。
    • 后缀自减:operand–,先返回操作数的值,然后再将操作数减1。
  3. 取地址(&):获取操作数的地址,即返回操作数在内存中的地址。
  4. 解引用(*):用于访问指针指向的内存地址上的值。
  5. 正号(+):返回操作数的正值。
  6. 负号(-):返回操作数的负值。
  7. 逻辑非(!):对操作数进行逻辑非操作,如果操作数为0,则返回1;如果操作数非零,则返回0。
  8. 按位取反(~):对操作数的每个位进行取反操作,即将0变为1,将1变为0。
  9. (类型):强制类型转换
//=  +=
void test(){
    int a = 10;
	int b = 0;
	b = a = a + 3;
	printf("a=%d b=%d\n", a, b);   //a=13,b=13
    a += 1;
    printf("a=%d\n",a);  //a=14
}
//-
void test() {
    int flag = 0;
    if (!flag) {//!flag不等于0就运行
        printf("hehe\n");
    }
}
//& *
void test() {
    int a = 10;//4
    char c = 0;//1
    
    printf("%p\n", &a);    //打印a的地址:000000000061FDE4
    printf("%p\n", &c);    //打印c的地址:000000000061FDE3

    int *pa = &a;//& - 取地址操作符
    *pa = 20;//* - 解引用操作符
    printf("%d\n",a);    //20 
    
    int arr[10];
    //	&arr;//取出数组的地址
    
    //野指针 - 问题
	//*(int*)0x0012ff40 = 100;
}
//~
void test() {
    int a = 0;
    //00000000000000000000000000000000
    //11111111111111111111111111111111 - 内存中-补码
    //11111111111111111111111111111110
    //10000000000000000000000000000001

    printf("%d\n", ~a);    //-1
}
//| &
void test() {
    int a = 10;
    int n = 0;
    scanf("%d", &n);
    //把a的第n位置为1
    a = a | (1 << (n - 1));
    printf("a=%d\n", a);

    //把a的第n位置为0
    a = a & ~(1 << (n - 1));
    printf("a=%d\n", a);

    //00000000000000000000000000001010
    //00000000000000000000000000010000
    //1<<2;
    //00000000000000000000000000011010
    //11111111111111111111111111101111
    //00000000000000000000000000010000
    //00000000000000000000000000001010
}
//前置++和--
#include <stdio.h>
int main() {
    int a = 10;
    int x = ++a;
    //先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
    int y = --a;
    //先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
    return 0;
}
//后置++和--
#include <stdio.h>
int main() {
    int a = 10;
    int x = a++;
    //先对a先使用,再增加,这样x的值是10;之后a变成11;
    int y = a--;
    //先对a先使用,再自减,这样y的值是11;之后a变成10;
    return 0;
}
//(类型)
void test() {
   	int a = (int)3.14;
	printf("%d\n", a);  //3
}

5.1 sizeof和数组

sizeof是一个运算符,用于获取数据类型或表达式的大小(以字节为单位)。它可以用于类型名、变量、数组和表达式。

void test() {
    short s = 10;
    int a = 2;
    s = a + 5;

    printf("%zu\n", sizeof(s = a + 5));//2

    printf("%d\n", s);//7


    int b = 10;
    int *p;
    int arr[10];

    printf("%zu\n", sizeof(b));  //int  4
    printf("%zu\n", sizeof b);   //int  4
    printf("%zu\n", sizeof(int));//int  4
    //printf("%zu\n", sizeof int); //err 


    printf("%zu\n", sizeof(p));      //int* 4
    printf("%zu\n", sizeof(arr));    //int [10] 40
    printf("%zu\n", sizeof(arr[10]));//int 4
}

sizeof int是不合法的,因为sizeof运算符在使用时需要用括号括起来的数据类型或表达式。

#include <stdio.h>

void test1(int arr[]) {
    printf("%zu\n", sizeof(arr));  // 输出指针的大小,通常为4或8
}

void test2(char ch[]) {
    printf("%zu\n", sizeof(ch));   // 输出指针的大小,通常为4或8
}

int main() {
    int arr[10] = {0};
    char ch[10] = {0};
    printf("%zu\n", sizeof(arr));   // 输出数组的大小,40字节
    printf("%zu\n", sizeof(ch));    // 输出数组的大小,10字节
    test1(arr);
    test2(ch);
    return 0;
}

当数组作为函数参数传递时,它实际上被转换为指针类型。因此,函数中的形参arrch被视为指针,而不是数组。在这种情况下,使用sizeof运算符获取指针的大小将得到指针本身的大小,而不是指向的数组的大小。

6.关系操作符

关系操作符用于比较两个表达式的值,并返回一个布尔值(0或1)表示比较结果的真假。

  1. 相等(==):检查两个操作数是否相等,如果相等则返回1,否则返回0。
  2. 不等(!=):检查两个操作数是否不相等,如果不相等则返回1,否则返回0。
  3. 大于(>):检查左操作数是否大于右操作数,如果是则返回1,否则返回0。
  4. 小于(<):检查左操作数是否小于右操作数,如果是则返回1,否则返回0。
  5. 大于等于(>=):检查左操作数是否大于等于右操作数,如果是则返回1,否则返回0。
  6. 小于等于(<=):检查左操作数是否小于等于右操作数,如果是则返回1,否则返回0。

这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。

注意:在编程的过程中==和=不小心写错,导致的错误。

7.逻辑操作符

  1. 逻辑与(&&):当两个操作数都为真(非零)时,返回1;否则返回0。如果左操作数为假,则右操作数不会被计算。
  2. 逻辑或(||):当两个操作数中至少有一个为真(非零)时,返回1;否则返回0。如果左操作数为真,则右操作数不会被计算。
  3. 逻辑非(!):对操作数进行逻辑非操作,如果操作数为零,则返回1;如果操作数非零,则返回0。

这些逻辑操作符通常用于条件判断、循环控制和布尔表达式中,用于组合和操作布尔值,得出最终的逻辑结果。

#include <stdio.h>

int main() {
    int a = 5;
    int b = 3;
    int c = 0;

    printf("a > b && b > c: %d\n", a > b && b > c);   // 输出:1,因为a > b和b > c都为真
    printf("a > b || b > c: %d\n", a > b || b > c);   // 输出:1,因为a > b为真
    printf("!c: %d\n", !c);                           // 输出:1,因为c为0

    return 0;
}

需要注意的是,逻辑操作符具有短路求值的特性。逻辑与操作符(&&)在左操作数为假时,不会计算右操作数;逻辑或操作符(||)在左操作数为真时,不会计算右操作数。这种特性可以用于避免不必要的计算,提高程序的效率。

笔试题

#include <stdio.h>
int main() {
    int i = 0, a = 0, b = 2, c = 3, d = 4;
    i = a++ && ++b && d++;
    printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
    return 0;
}
//程序输出的结果是什么?

在给定的程序中,通过逻辑与操作符(&&)对多个表达式进行逻辑运算,并将结果赋值给变量i。根据逻辑与操作符的求值规则,如果左操作数为真,则继续计算右操作数,如果左操作数为假,则右操作数不会被计算。

根据代码中的表达式a++ && ++b && d++,可以进行如下的求值过程:

  1. a++:a的值为0,然后a自增1。表达式的结果为0。
  2. 由于左操作数为假(非零),右操作数++bd++都没有被计算。

根据逻辑与操作符的求值规则,整个表达式的结果为假(0)。因此,变量i被赋值为0。

输出结果为:

a = 1
b = 2
c = 3
d = 4
#include <stdio.h>
int main() {
    int i = 0, a = 0, b = 2, c = 3, d = 4;
    i = a++||++b||d++;
    printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
    return 0;
}
//程序输出的结果是什么?

在给定的程序中,通过逻辑或操作符(||)对多个表达式进行逻辑运算,并将结果赋值给变量i。根据逻辑或操作符的求值规则,如果左操作数为真,则右操作数不会被计算,整个表达式的结果为真(非零);如果左操作数为假,则继续计算右操作数,如果右操作数为真,则整个表达式的结果为真(非零),否则结果为假(0)。

根据代码中的表达式a++ || ++b || d++,可以进行如下的求值过程:

  1. a++:a的值为0,然后a自增1。表达式的结果为假(0)。
  2. ++b:由于左操作数为假(0),执行右操作数的计算。b的值为2,然后b自增1。表达式的结果为真(非零)。

由于前两个操作数中至少有一个为真,所以整个表达式的结果为真(非零)。因此,变量i被赋值为1。

输出结果为:

a = 1
b = 3
c = 3
d = 4

8.条件操作符

条件操作符(也称为三元运算符)是一种特殊的操作符,用于根据条件选择两个表达式中的一个来求值。它的语法形式如下:

condition ? expression1 : expression2

其中,condition是一个条件表达式,expression1和expression2是两个可能的结果表达式。条件操作符的求值过程如下:

  1. 首先计算条件表达式condition的值。
  2. 如果条件表达式的值为真(非零),则整个条件操作符的值为expression1的值。
  3. 如果条件表达式的值为假(0),则整个条件操作符的值为expression2的值。
#include <stdio.h>

int main() {
    int a = 5;
    int b = 3;

    int max = (a > b) ? a : b;  // 如果a大于b,则max为a的值;否则max为b的值

    printf("Max value: %d\n", max);

    return 0;
}

在上述示例中,使用条件操作符 (a > b) ? a : b 来选择两个数中的较大值,并将其赋值给变量max。如果a大于b,则max的值为a;否则max的值为b。输出结果为较大的值。

9.逗号表达式

逗号表达式(comma expression)是一种特殊的表达式,它允许在一个表达式中使用逗号将多个子表达式连接起来,并按顺序依次求值。逗号表达式的求值过程是从左到右的,返回值是最后一个子表达式的值。

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

逗号表达式的语法形式如下:

expression1, expression2, expression3, ..., expressionN

其中,expression1、expression2、expression3等都是子表达式,可以是任意有效的表达式。

#include <stdio.h>

int main() {
    int a = 5;
    int b = 3;
    int c;

    c = (a++, b++, a + b);  // 逗号表达式:先执行a++,再执行b++,最后求a + b

    printf("c = %d\n", c);  // 输出c的值,结果为10

    return 0;
}

在上述示例中,使用逗号表达式 (a++, b++, a + b) 将三个子表达式连接起来。首先执行a++,将a的值增加1,然后执行b++,将b的值增加1,最后求a + b的结果为10。这个结果赋值给变量c,然后输出c的值。

10.下标引用、函数调用和结构成员

1.[ ] 下标引用操作符

操作数:一个数组名 + 一个索引值

int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
//[ ]的两个操作数是arr和9。

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;
}

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;
}

11.表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。 同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

11.1 隐式类型转换

隐式类型转换(implicit type conversion)是指在表达式中自动发生的类型转换,而无需显式地使用类型转换运算符。隐式类型转换是由C语言的类型规则和表达式求值规则决定的。

以下是一些常见的隐式类型转换情况:

  1. 整数提升(integer promotion):当较小的整数类型参与运算时,它们会被自动提升为较大的整数类型。例如,当 intshort 进行运算时,short 会被提升为 int 类型。
  2. 浮点数提升(floating-point promotion):当较小的浮点数类型参与运算时,它们会被自动提升为较大的浮点数类型。例如,当 floatdouble 进行运算时,float 会被提升为 double 类型。
  3. 整数转换(integer conversion):当不同大小的整数类型进行赋值或混合运算时,较小的整数类型会被自动转换为较大的整数类型。例如,将 short 赋值给 int,或将 intlong 进行运算时。
  4. 浮点数转换(floating-point conversion):当不同大小的浮点数类型进行赋值或混合运算时,较小的浮点数类型会被自动转换为较大的浮点数类型。例如,将 float 赋值给 double,或将 floatdouble 进行运算时。
  5. 混合类型运算:在表达式中,不同类型的操作数进行运算时,会发生隐式类型转换以使它们具有相同的类型。例如,整数和浮点数进行运算时,整数会被转换为浮点数。

需要注意的是,隐式类型转换可能会导致精度丢失或数据溢出的问题。为了避免意外的类型转换和数据损失,建议在需要时使用显式类型转换运算符进行明确的类型转换。

C的整型算术运算总是至少以缺省整型类型的精度来进行的。 为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

整型提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。

因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算。

//实例1
char a,b,c;
...
a = b + c;

b和c的值被提升为普通整型,然后再执行加法运算。

加法运算完成之后,结果将被截断,然后再存储于a中。

如何进行整体提升呢?

整型提升是按照变量的数据类型的符号位来提升的

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

整型提升的例子:

//实例1
int main() {
    char a = 0xb6;
    short b = 0xb600;
    int c = 0xb6000000;
    if (a == 0xb6)
        printf("a");
    if (b == 0xb600)
        printf("b");
    if (c == 0xb6000000)
        printf("c");
    return 0;
}

实例1中的a,b要进行整形提升,但是c不需要整形提升 a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表 达式 c==0xb6000000 的结果是真

所程序输出的结果是:c

//实例2
int main() {
    char c = 1;
    printf("%u\n", sizeof(c));
    printf("%u\n", sizeof(+c));
    printf("%u\n", sizeof(-c));
    return 0;
}

实例2中的,c只要参与表达式运算,就会发生整型提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节. 表达式 -c 也会发生整型提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c) ,就是1个字节.

12.操作符的属性

复杂表达式的求值有三个影响的因素。 1. 操作符的优先级 2. 操作符的结合性 3. 是否控制求值顺序。 两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。

操作符优先级:

优先级操作符结果类型结合性控制求值顺序
1()-
2[]-
->-
.-
++相同类型
相同类型
(类型)转换后类型
sizeofsize_t类型
3++相同类型
相同类型
+相同类型
-相同类型
!int类型
~int类型
*相同类型
&相同类型
(类型)转换后类型
sizeofsize_t类型
4*相同类型
/相同类型
%相同类型
5+相同类型
-相同类型
6<<相同类型
>>相同类型
7<int类型
<=int类型
>int类型
>=int类型
8==int类型
!=int类型
9&相同类型
10^相同类型
11|相同类型
12&&int类型
13||int类型
14?:根据操作数
15=左操作数类型
+=左操作数类型
-=左操作数类型
*=左操作数类型
/=左操作数类型
%=左操作数类型
<<=左操作数类型
>>=左操作数类型
&=左操作数类型
^=左操作数类型
|=左操作数类型
16,右操作数类型

一些问题表达式

//表达式的求值部分由操作符的优先级决定。
//表达式1
a*b + c*d + e*f

注释:代码1在计算的时候,由于比+的优先级高,只能保证,*的计算是比+早,但是优先级并不能决定第三个比第一个+早执行。所以表达式的计算机顺序就可能是:

a*b
c*d
a*b + c*d
e*f
a*b + c*d + e*f
或者:
a*b
c*d
e*f
a*b + c*d
a*b + c*d + e*f
//表达式2
c + --c;

同上,操作符的优先级只能决定自减–的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义 的。

//代码3-非法表达式
int main() {
    int i = 10;
    i = i-- - --i * (i = -3) * i++ + ++i;
    printf("i = %d\n", i);
    return 0;
}

表达式3在不同编译器中测试结果:非法表达式程序的结果

//代码4
int fun(){
     static int count = 1;
     return ++count;
}

int main(){
     int answer;
     answer = fun() - fun() * fun();
     printf( "%d\n", answer);//输出多少?
     return 0;
}

虽然在大多数的编译器上求得结果都是相同的。

但是上述代码 answer = fun() - fun() * fun(); 中我们只能通过操作符的优先级得知:先算乘法, 再算减法。

函数的调用先后顺序无法通过操作符的优先级确定。

//代码5
#include <stdio.h>
int main() {
    int i = 1;
    int ret = (++i) + (++i) + (++i);
    printf("%d\n", ret);
    printf("%d\n", i);
    return 0;
}

这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级 和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。

总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/701296.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

使用JavaScript获取随机数序列

使用Javascript 生成随机数 要在 Javascript 中生成随机数&#xff0c;可以使用 Math 对象的 random() 方法。该方法返回一个大于等于 0 小于 1 的伪随机浮点数。 Javascript中的 Math.random() 函数是一个用于生成随机数的内置函数。 MDN 官方解释 Math.random() 函数返回…

idea常用快捷方式,保姆级!图文并茂【建议收藏】

大家好&#xff0c;我是三叔&#xff0c;很高兴这期又和大家见面了&#xff0c;一个奋斗在互联网的打工人。 给大家分享一下idea在开发过程中使用的快捷方式把&#xff0c;可以极大的提升生产力&#xff0c;提高自己的开发速度&#xff0c;需要在开发中不断地使用&#xff0c;…

《Linux操作系统编程》 第十章 线程与线程控制: 线程的创建、终止和取消,detach以及线程属性

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

arcgis-elasticsearch矢量数据导入及索引设计工具

插件说明 插件支持单图层导入和多图层同时导入&#xff0c;依赖elasticsearch包和urlLib包&#xff0c;使用之前请用pip安装&#xff0c;具体的依赖包的requirements.txt文件放在压缩包里面了。 pip install -r requirements.txt插件下载地址&#xff1a;https://download.cs…

DocuSign:在全球电子签名市场具有巨大上涨潜力的SaaS股

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 总结 &#xff08;1&#xff09;DocuSign的核心电子签名业务还在持续增长&#xff0c;尽管在疫情后增速有所放缓&#xff0c;但第一季度的收入已经达到了6.61亿美元&#xff0c;增长率为12%。 &#xff08;2&#xff09;Do…

Linux:通过wget下载安装mysql数据库(5.7版本)

目前&#xff0c;主要使用的MySQL有5.7和8.0两个版本&#xff0c;在安装上&#xff0c;5.7和8.0版本基本一致&#xff0c;区别只在于配置root密码和远程登陆上不同。本次将以5.7版本作为对象&#xff0c;进行后续安装。 1.wget下载MySQL安装文件 下载完成&#xff0c;得到mysq…

PySpark如何输入数据到Spark中?【RDD对象】

PySpark支持多种数据的输入&#xff0c;在输入完成后&#xff0c;都会得到一个&#xff1a;RDD类的对象RDD全称为弹性分布式数据集(Resilient Distributed Datasets)&#xff0c;PySpark针对数据的处理&#xff0c;都是以RDD对象作为载体&#xff0c;即&#xff1a; •数据存储…

ansible实训-Day3(playbook的原理、结构及其基本使用)

一、前言 该篇是对ansible实训第三天内容的归纳总结&#xff0c;主要包括playbook组件的原理、结构及其基本使用方式。 二、Playbook 原理 Playbook是Ansible的核心组件之一&#xff0c;它是用于定义任务和配置的自动化脚本。 Ansible Playbook使用YAML语法编写&#xff0c;可…

Linux 学习记录42(C++篇)

Linux 学习记录42(C篇) 本文目录 Linux 学习记录42(C篇)一、class 类1. 类中的this指针(1. this指针的格式(2. 使用this指针 2. 类中特殊的成员函数(1. 构造函数>1 格式/定义>2 调用构造函数的时机>3 构造函数的初始化列表 (2. 析构函数>1 功能/格式>2 析构函数…

Redis的数据复制到另一台Redis

Redis的数据复制到另一台Redis 最近用到一个问题&#xff0c;需要把Redis的数据复制到另一台Redis&#xff0c;现在总结下解决问题的方法 解决方法一&#xff1a; redis-dump导出 [root ~]# redis-dump -u :password172.20.0.1:6379 > 172.20.0.1.jsonredis-load导入 [ro…

快速打造属于你的接口自动化测试框架

目录 1 接口测试 2 框架选型​​​​​​​ 3 环境搭建 4 需求 5 整体实现架构 6 RF用例实现​​​​​​​ 7 集成到CICD流程 总结&#xff1a; 1 接口测试 接口测试是对系统或组件之间的接口进行测试&#xff0c;主要是校验数据的交换&#xff0c;传递和控制管理过程…

Redis 高可用 RDB AOF

---------------------- Redis 高可用 ---------------------------------------- 在web服务器中&#xff0c;高可用是指服务器可以正常访问的时间&#xff0c;衡量的标准是在多长时间内可以提供正常服务&#xff08;99.9%、99.99%、99.999%等等&#xff09;。 但是在Redis语境…

基于Java人力资源管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

webassembly简单Demo——hello world

参考官网 Emscripten Tutorial 一、创建C/C文件 hello.c #include <stdio.h>int main() {printf("hello, world!\n");return 0; } 二、编译成html 命令行切到hello.c目录下&#xff0c;执行如下命令(注意需要em的环境变量&#xff0c;参考&#xff1a;emsr…

5G AI MEC智能制造数字化工业互联网大数据平台建设方案PPT

导读&#xff1a;原文《102页新一代数字化转型信息化总体规划方案PPT》共102页PPT&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 完整版领取方式 完整版领取方式&…

ARM-进入和退出异常中断的过程(六)

文章目录 ARM 处理器对异常中断的响应过程从异常中断处理程序中返回 ARM 处理器对异常中断的响应过程 ARM 指令为三级流水线&#xff1a;取地&#xff0c;译码和执行 进入中断的时候 LR PC -4 当出现异常时&#xff0c;ARM 内核自动执行以下操作 将 cpsr 寄存器的值保存到…

走近JDK 17,探索最新Java特性,拥抱未来编程!

大家好&#xff0c;我是小米&#xff0c;一个热爱技术分享的程序员。今天&#xff0c;我将为大家介绍一下JDK 17的新特性。JDK 17是Java开发工具包的一个重要版本&#xff0c;其中包含了许多令人激动的新功能和改进。在这篇文章中&#xff0c;我将详细介绍JDK 17中的各项特性&a…

Mathtype7Mac苹果ios简体中文版

对于很多人来说&#xff0c;每次编辑文字的时候遇到公式简直就是噩梦。像那些复杂的数学、物理还有化学公式&#xff0c;太难编辑出来了。 那么我们该怎么解决这些难题呢&#xff1f;其实很简单&#xff0c;用公式编辑器就行了。 公式编辑器&#xff0c;是一种工具软件&#…

前端开发爬虫首选puppeteer

很多前端同学可能对于爬虫不是很感冒&#xff0c;觉得爬虫需要用偏后端的语言&#xff0c;诸如 python 、php 等。当然这是在 nodejs 前了&#xff0c;nodejs 的出现&#xff0c;使得 Javascript 也可以用来写爬虫了。但这是大数据时代&#xff0c;数据的需求是不分前端还是后端…

基于Java会议管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…