C语言之操作符详解

文章目录

    • 一、算术操作符
    • 二、移位操作符
      • 1、 原码、反码、补码
      • 2、左移操作符
      • 3、右移操作符
    • 三、位操作符
      • 1、按位与【&】
      • 2、按位或【|】
      • 3、按位异或【^】
      • 4、按位取反【~】
      • 5、两道面试题
      • 6、进制定位
        • 将变量a的第n位置为1
        • 将变量a的第n位置为0
    • 四、赋值操作符
      • 1、复合赋值符
    • 五、单目操作符
      • 1、单目操作符介绍
      • 2、【!】逻辑反操作
      • 3、【&】和【*】
      • 4、【-】和【+】
      • 5、sizeof
      • 6、【++】和【- -】
      • 7、强制类型转换
    • 六、关系操作符
    • 七、逻辑操作符
        • 一道笔试题~~
    • 八、条件操作符
    • 九、逗号表达式
    • 十、下标引用、函数调用和结构成员
      • 1、下标引用操作符 [ ]
      • 2、函数调用操作符 ( )
      • 3、结构成员调用操作符 . 和 ->
    • 十一、表达式求值
      • 1、隐式类型转换【整型提升】
        • 整型提升的意义
      • 2、算术转换
      • 3、操作符的属性【附优先级列表】
      • 4、问题表达式
        • 问题表达式1
        • 问题表达式2
        • 问题表达式3
        • 问题表达式4
        • ⑤ 问题表达式5

一、算术操作符

在这里插入图片描述

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

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

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

  • 整数除法:

在这里插入图片描述

  • 浮点数除法:

在这里插入图片描述

  • 取余操作符

在这里插入图片描述

二、移位操作符

在这里插入图片描述

1、 原码、反码、补码

  • 其实我们经常能听到2进制、8进制、10进制、16进制这样的讲法,那是什么意思呢?其实2进制、8进制、10进制、16进制是数值的不同表示形式而已。
  • 比如:数值15的各种进制的表示形式:
152进制:1111
158进制:17
1510进制:15
1516进制:F
  • 对于一个整数来说,在内存中的二进制表示形式有 【原码】【反码】【补码】 三种,写成二进制位的形式也就是32位二进制,例如整数4,它的原码即为0 0000000000000000000000000000100。最高位对于32个二进制位来说,叫做符号位

    • 符号位是0,表示正整数
    • 符号位是1,表示负整数
  • 而对于一个正数来说,它的原码、反码、补码都是相同的

  • 对于一个负数来说:

    • 原码:直接将数值按照正负数的形式翻译成二进制得到的就是原码。
    • 反码:将原码的符号位不变,其他位依次按位取反就可以得到反码。
    • 补码:反码+1就得到补码。

  • 对于整形来说:数据存放内存中其实存放的是补码。
  • 为什么呢?

在计算机系统中,数值⼀律⽤补码来表⽰和存储。原因在于,使⽤补码,可以将符号位和数值域统⼀处理;同时,加法和减法也可以统⼀处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。


  • 在计算机中都是使用二进制数的补码进行运算的,但是在计算完之后输出的结果都要再转化为原码的形式

2、左移操作符

【移位规则】:左边抛弃、右边补0

  • 下面计算一个数字4左移1位后的,将其运算后的结果放到b里面去。
  • 在内存中进行计算都是使用补码的形式,因为依次写出4的原、反、补码,因为它是正数,所以均是相同的
int main()
{int a = 4;//0 0000000000000000000000000000100 - 4的原码//0 0000000000000000000000000000100 - 4的反码//0 0000000000000000000000000000100 - 4的补码int b = a << 1;		//把a向左移动一位printf("a = %d, b = %d\n", a, b);return 0;
}

在这里插入图片描述

  • 从上图也可以看出,执行左边丢弃,右边补0,也就是相当于乘2,结果就是8

在这里插入图片描述


接着我们再来看看负数的情况:

  • 对于负数来说,首先要进行的就是取反操作,然后+1就算出了他的补码,然后再进行移位
int main()
{int a = -4;//1 0000000000000000000000000000100 - -4的原码//1 1111111111111111111111111111011 - -4的反码//1 1111111111111111111111111111100 - -4的补码int b = a << 1;		//把a向左移动一位//1 1111111111111111111111111111000 - -4移位后的的补码//1 1111111111111111111111111110111 - -4移位后的的反码//1 0000000000000000000000000001000 - -4移位后的的原码printf("a = %d, b = %d\n", a, b);return 0;
}

在这里插入图片描述

  • 我们再将移位后的结果再次变成原码的操作,也就是 -1再取反 即可变成原码
  • 最后得出的结果再转换为十进制便是8

在这里插入图片描述

3、右移操作符

【移位规则】:

  • 逻辑移位

    • 左边用0填充,右边丢弃
  • 算术移位

    • 左边用原该值的符号位填充,右边丢弃
  • 对于右移操作符,和左移不同的是它的运算规则比较复杂,在不同编译器下对于移位后的符号位填充是有所不同的,但是在VS下采用的是第二种算术移位

在这里插入图片描述

  • 对于右移从结果来看就相当于是缩小的形式

在这里插入图片描述


然后我们再来看看负数的情况:

  • 对于负数来说,会在计算机内部转化成补码再运算,运算完成后再转换成原码

在这里插入图片描述

  • 如下图所示:

在这里插入图片描述

  • 对于移位运算符,不要移动负数位,这个是标准未定义的
int num = 10;
num>>-1;//error

三、位操作符

1、按位与【&】

  • 【规则】:全1为1,有0为0
//按位与 - 全1为1,有0为0
int main()
{int a = 3;int b = -5;int c = a & b;printf("c = %d\n", c);//00000000000000000000000000000011 - 3的原码、反码、补码//10000000000000000000000000000101 - -5的原码//11111111111111111111111111111010 - -5的反码//11111111111111111111111111111011 - -5的补码//00000000000000000000000000000011//11111111111111111111111111111011//00000000000000000000000000000011 - 3【补码即为原码】return 0;
}
  • 根据按位与的运算规则,我们就可以得出最后的结果为3

在这里插入图片描述

2、按位或【|】

  • 【规则】:有1为1,全0为0
//按位或 - 有1为1,全0为0
int main()
{int a = 3;int b = -5;int c = a | b;printf("c = %d\n", c);//00000000000000000000000000000011 - 3的原码、反码、补码//10000000000000000000000000000101 - -5的原码//11111111111111111111111111111010 - -5的反码//11111111111111111111111111111011 - -5的补码//00000000000000000000000000000011//11111111111111111111111111111011
// --------------------------------------//11111111111111111111111111111011 |//11111111111111111111111111111010 |//10000000000000000000000000000101 | - 5return 0;
}
  • 根据按位或的运算规则,我们就可以得出最后的结果为-5

在这里插入图片描述

3、按位异或【^】

  • 【规则】:相同为0,相异为1
//按位异或 - 相同为0,相异为1
int main()
{int a = 3;int b = -5;int c = a ^ b;printf("c = %d\n", c);//00000000000000000000000000000011 - 3的原码、反码、补码//10000000000000000000000000000101 - -5的原码//11111111111111111111111111111010 - -5的反码//11111111111111111111111111111011 - -5的补码//00000000000000000000000000000011//11111111111111111111111111111011
// --------------------------------------//11111111111111111111111111111000//11111111111111111111111111110111//10000000000000000000000000001000 -> -8return 0;
}
  • 根据按位异或的运算规则,我们就可以得出最后的结果为-8

在这里插入图片描述

注意:

  • 两个相同的数异或为0a ^ a = 0
  • 任何数和0异或均为那个数本身a ^ 0 = a

4、按位取反【~】

【规则】:1变0, 0变1

  • 取反直接就可以将1变成0,0变成1
int main()
{int a = 0;int b = ~a;printf("b = %d\n", b);//00000000000000000000000000000000//11111111111111111111111111111111  按位取反【补码】	//11111111111111111111111111111110	【反码】//10000000000000000000000000000001 -> -1【原码】return 0;
}
  • 那么0按位取反之后就变成了-1

在这里插入图片描述

5、两道面试题

两数交换

  • 我们之前学的是使用第三方临时变量做一个存放,进行交换

  • 还有以一种方法就是通过加减的方式来交换一下这两个数

int main()
{int a = 3;int b = 5;printf("a = %d, b = %d\n", a, b);a = a + b;b = a - b;	a = a - b;		printf("a = %d, b = %d\n", a, b);return 0;
}
  • 首先将两个数相加放到a中,再将a-b的值放到b中,此时a中右a和b的值,b中有a的值,最后再将a-b就是b的值

在这里插入图片描述

  • 上面这种方法有一点不太好,就是当两个很大的数的时候,会出现数据溢出的情况,所以我们还需要另外一种方法:使用异或

  • 首先a = a ^ b将a和b异或后的结果暂存到a里面去,然后再去异或b的话就相当于是a ^ b ^ b,根据规则便可以得出结果为a,将其放入b中
  • 然后a = a ^ b,就相当于是a ^ b ^ a,那么结果就是b,将其放入a中
int main()
{int a = 3;int b = 5;printf("a = %d, b = %d\n", a, b);a = a ^ b;b = a ^ b;		//a ^ b ^ b = a ^ 0 = aa = a ^ b;		//a ^ b ^ a = b ^ 0 = bprintf("a = %d, b = %d\n", a, b);return 0;
}
  • 最后打印出来就是交换之后的结果

在这里插入图片描述

6、进制定位

将变量a的第n位置为1

思路分析:

  • 首先就是要使用到我们上面学习过的按位或 | 运算,将第三位按位或上一个1,那么这一位就变成了1,但是又不想让其他位置发生变化,那此时就让其他位按位或上一个0即可,若是那个位上为0,那么就是0,若是那个位上为1,那也为1,那也就是00000000000000000000000000000100,但是要如何去获取到这个二进制数呢,此时就又需要使用到我们上面讲到过的一个操作符叫做左移<<那也就是将一个数扩大两倍,这里我们对1进行操作,扩大2倍就是2,再扩大两倍就是我们想要的4,即1 << 2
  • 具体的表达式应该为:[a = a | 1 << 2]

在这里插入图片描述

  • 此时我们已经完成了第一步,若是你想要置哪个位上的数为1的话,那就修改表达式的最后一个数字即可,这样比较麻烦,需要每次运行前做一个修改,此时我们就来实现题目中的需求:

  • 可以再来修改一个位上的数,若是我们要将第5位置为1的话,左移4位即可那也就是1 << 4,最后的结果就是26

在这里插入图片描述

规律总结

  • 若是要置【第3位】的数为1的话,使用数字1左移2位1 << 2
  • 若是要置【第5位】的数为1的话,使用数字1左移4位1 << 4;
  • 若是要置【第n位】的数为1的话,使用数字1左移(n - 1)位1 << (n - 1);

  • 那么此时的话就可以将这个n作为我们自己输入的一个变量,每次想要修改哪一个直接输入即可
int main()
{int a = 10;int n = 0;scanf("%d", &n);a = a | 1 << (n - 1);//把变量a的第n为置1//000000000000000000001010//000000000000000000010000
//--------------------------------//000000000000000000001110printf("a = %d\n", a);return 0;
}

在这里插入图片描述


将变量a的第n位置为0
  • 要将一个二进制位置为0的话就又需要使用到我们上面所学习过的按位与&,也就是将需要置0的那一位按位与上一个0即可,因为任何数和0进行与都为0,但是呢又不能使得其他二进制位发生改变,那就要使其他二进制位按位与上一个1即可,若是那个位上为0,那么就是0,若是那个位上为1,那也为1,那此时我们再对刚才的第三位进行一个按位与即11111111111111111111111111111011
  • 刚才使用的1 << 2,即00000000000000000000000000000100,仔细观察就可以看出这两个二进制位其实每个位呈现的都是一个相反的趋势,那么我们在上面使用到的位操作符中哪个具有取反的功能呢,就是按位取反,那其实只需要将刚才求出的那个表达式外层再加上一个按位取反符就可以了
  • 具体的表达式应该为:[a = a & ~(1 << (n - 1))]

在这里插入图片描述

int main()
{int a = 10;int n = 0;while (scanf("%d", &n)!=EOF){a = a | 1 << (n - 1);//把变量a的第n为置1//000000000000000000001010//000000000000000000010000//--------------------------------//000000000000000000001110printf("置1:a = %d\n", a);a = a & ~(1 << (n - 1));//把变量a的第n为置0//000000000000000000001110//111111111111111111111011//--------------------------------//000000000000000000001010printf("置0:a = %d\n", a);}return 0;
}

四、赋值操作符

  • 赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值
int weight = 120;	//体重
weight = 89;		//不满意就赋值
double salary = 10000.0;
salary = 20000.0;	//使用赋值操作符赋值
  • 不仅如此,赋值操作符还可以进行连续使用
int b = a += 10;

在这里插入图片描述

  • 但是对于上面这样的代码其实是不太好的~~
  • 但如果写成像下面这样的话就显得非常清爽而且易于调试
a += 10;
b = a;

1、复合赋值符

  • 对于赋值操作符来说,还可以和其他操作符进行一个复合的操作
  • 例如:【+=】、【-=】、【*=】、【/=】、【%=】、【>>=】、【<<=】、【^=】等等~~

在这里插入图片描述

在这里插入图片描述

五、单目操作符

1、单目操作符介绍

在这里插入图片描述

2、【!】逻辑反操作

  • 对于逻辑取反操作符来说,就是[真变假,假变真]
int main()
{int flag = 0;if (!flag){printf("haha\n");}return 0;
}

在这里插入图片描述

3、【&】和【*】

  • 【&】来说叫做取地址操作符,可以获取一个变量在内存中的地址

在这里插入图片描述

  • 我们还可以将其给到一个指针变量~~

在这里插入图片描述

  • 那这个时候我要获取到这个指针所指向的地址就是要用到【*】这个解引用操作符

在这里插入图片描述

  • 这一步操作就是在使这个指针变量重新指向一块新的地址,即00000014,转换为十进制也就是【20】

在这里插入图片描述

4、【-】和【+】

  • 对于这两个操作符并不常用,不能完全说只是单目操作符,在特定的场景下它们也可以算是一个双目操作符,例如:-6的话就只一个单目操作符,8 - 3的话就是第一个双目操作符,【+】的话也是同理

5、sizeof

  • 对于sizeof来说,这个操作符是用来计算数据类型的大小的,字节为单位

  • 对于sizeof()而言有其特定的返回值打印格式【%zu

int a = 10;
int* p;
int arr[10];printf("%zu\n", sizeof(a));			//int 4
printf("%zu\n", sizeof(p));			//int 4
printf("%zu\n", sizeof(arr));		//特殊,计算的是整个数组的大小
printf("%zu\n", sizeof(arr[0]));	//int [10] 40
printf("%zu\n", sizeof(arr[10]));	//int 4

在这里插入图片描述

sizeof后可省略

  • sizeof是一个操作符,而不是函数,函数后面必须带一个括号,而操作符可以不带,下面我们就可以验证一下,后面的()其实是可以省略的

在这里插入图片描述

sizeof()内部表达式不参与计算

  • 我在sizeof()内部写了一个表达式,这其实也是合法的
int main()
{short s = 10;int a = 2;printf("%d\n", sizeof(s = a + 2));printf("%d\n", s);return 0;
}

在这里插入图片描述

  • 虽然sizeof()内部是可以放表达式的,但是呢这个表达式是不参与运算的

  • 我们在看这个sizeof()最后的结果时其实只需要看这个s即可,短整型为2个字节,那如果这个表达式不计算的话s的值也就不会发生变化了,所以最后打印出来是10

那为什么这个表达式不参与运算呢?

  • 在编译阶段的时候,表达式就不存在了,已经替换成short

在这里插入图片描述

sizeof() 与数组

  • 我们来看下面的代码~~
#include <stdio.h>
void test1(int arr[])
{printf("%d\n", sizeof(arr));
}void test2(char ch[])
{printf("%d\n", sizeof(ch));
}int main()
{int arr[10] = {0};char ch[10] = {0};printf("%d\n", sizeof(arr));printf("%d\n", sizeof(ch));test1(arr);test2(ch);return 0;
}
  • 我们要明确,数组名是数组首元素的地址,但是有两个特殊情况

    • sizeof(数组名) —— 计算的是整个数组的大小【特殊情况1】
    • &数组名 —— 获取整个数组的地址【特殊情况2】
  • 首先第一个【sizeof(arr)】,arr表示数组名,那么计算的就是整个数组的大小,这是一个整型数组,数组中有10个元素,每个元素4个字节,那么整个数组的大小就是40个字节

  • 对于【sizeof(ch)】而言也是同理,这是一个字符型数组,数组中有10个元素,每个元素1个字节,那么整个数组的大小就是10个字节

  • 接下去就是将数组名传入函数中,除了两种特殊的情况而言,数组名就相当于是首元素地址,然后函数形参中便是指针进行接受,那对于一个指针而言,无论它是【整型指针】、【字符型指针】【浮点型指针】均为4个字节,当然这是在32为系统下,若是在64位系统下就为8个字节,这其实取决于计算机内存中地址总线的长度,有兴趣可以去了解一下

  • 那既然上面说到一个指针均为4个字节,就可以得出为什么最后一个sizeof(ch)为4了,这也只是在求一个指针的大小,不要和字符串数据所占字节数混淆了

在这里插入图片描述

6、【++】和【- -】

前置++和后置++

int a = 10;
int b = ++a;
//a = a + 1; b = a;printf("a = %d, b = %d\n", a, b);
int a = 10;
int b = a++;
//b = a; a = a + 1; printf("a = %d, b = %d\n", a, b);
  • 可以看到,这里的b = ++a相当于就是先让a++,然后再把a的值给到b
  • 可以看到,这里的b = a++相当于就是先把a的值给到b,然后再让a++

在这里插入图片描述

在这里插入图片描述


接着再来看看前置- -和后置- -

int a = 10;
int b = --a;
//a = a - 1; b = a;
printf("a = %d, b = %d\n", a, b);int a = 10;
int b = a--;
//b = a; a = a - 1; printf("a = %d, b = %d\n", a, b);
  • 对于这里的b = --a相当于就是先让a- -,然后再把a的值给到b

在这里插入图片描述

  • 对于这里的b = a--相当于就是先把a的值给到b,然后再让a- -

在这里插入图片描述

7、强制类型转换

  • 如果我需要将一个浮点类型的数据赋值给一个整形,编译器会报一个警告【精度丢失】,若要强制赋值,就需要做一个强制类型转换

在这里插入图片描述

  • 可以看到这样就不会出现问题了,要想要把一个不同数据类型的值给到一个另一个数据类型的变量,就可以使用强制类型转换
int a = (int)3.14;

在这里插入图片描述

六、关系操作符

  • 关系操作符主要有下面这些:
    【>】、【>=】、【<】、【<=】、【! =】、【==】

  • 这些我们在使用的时候都会用到,也很简单,就不再多赘述了~~

  • 但是这里需要强调的一点就是【==】,在我们平时写代码的时候写这个判断是否等于,容易少一写一个,所以我们建议反着写比如:a == 10,我们就可以写成10 == a

七、逻辑操作符

  • 这两个操作符很像前面学过的,但是要区分开

在这里插入图片描述

  • 要区分逻辑与按位与
  • 要区分逻辑或按位或

在这里插入图片描述

特点:

【逻辑与&&】: 表达式两边均为真才是真,若第一个为假,那么整个表达式为假,第二个表达式不参与运算
【逻辑或 ||】: 表达式两边有 一边为真即为真,若第一个为真,那么整个表达式为真,第二个表达式不参与运算

一道笔试题~~
#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);printf("------\n");printf("i = %d\n", i);return 0;
}

在这里插入图片描述

  • 来分析一下其实就可以看出 ,因为a一开始为0,所以前两个逻辑与之后的结果一定为0,那么除了第一个a++表达式需要运算之外后面的表达式都不会参与运算,因此最后的结果为1 2 3 4,【i】的结果即为0
  • 这里要注意的一点就是逻辑与前面一个表达式已为假那么第二个表达式是不会参与运算的

八、条件操作符

  • 三目操作符:

在这里插入图片描述

  • 就比如下面这段代码,显示的非常冗余,这个时候我们就可以用到条件操作符
int main()
{int a = 5;int b = 0;if (5 == a)b = 3;elseb = -3;printf("a = %d\n", a);printf("b = %d\n", b);return 0;
}
  • 当条件成立的时候,执行第一个表达式,当条件不成立的时候,执行第二个表达式
b = (5 == a) ? 3 : -3;

在这里插入图片描述


然后我们使用这个条件操作符来练习一下求解两个数的较大值

int a = 5;
int b = 3;int ret = (a > b) ? a : b;
printf("ret = %d\n", ret);

九、逗号表达式

【格式】:exp1, exp2, exp3, …expN

【运算规则】:从左向右依次计算,整个表达式的结果是最后一个表达式的结果

//代码1
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式
printf("c = %d\n", c);
  • 最后的结果是13~~

  • 根据逗号表达式的运算规则可以知道它是从左向右进行计算的,最终结果取的是最后一个表达式的结果,那么根据前面的计算可以得知b = 12,那么最后再计算便是13


  • 再来看一句代码,可以看到这不是一个结果的运算,而是将逗号表达式放在一个if分支判断中,可以看到最后一个逗号后面的表达式为d > 0,那此时我们就要去看看前面一些表达式的运算会不会使得这个d变化,若不会那么这个if判断其实就等价于if(d > 0)
//代码2
if (a = b + 1, c = a / 2, d > 0)

  • 最后再来看看下面这段代码,就是不断地在计算一个a的值然后求和,若是a什么时候到0了,便跳出这个while()循环,但是可以看到a = get_val() count_val(a)这两个表达式在while()循环上面调用了一次,然后再while()循环中在调用,显得就有些冗余了,那此时我们就可以使用【逗号表达式】去进行一个优化
//代码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,前面两个表达式只是有可能会使a的值发生一个变化罢了
while (a = get_val(), count_val(a), a > 0)
{//业务处理
}

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

1、下标引用操作符 [ ]

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

  • 这个操作符我们在讲数组的时候也有用到过,可能我们大家在使用的时候都是arr[1],不过既然它一个操作符,那么对于操作数来说其实没有位置的一个限制,其实是可以写成1[arr]
int main()
{int arr[] = { 1,2,3,4,5 };int sz = sizeof(arr) / sizeof(arr[0]);for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");for (int i = 0; i < sz; i++){printf("%d ", i[arr]);}return 0;
}

在这里插入图片描述

  • 可以看出两种语法都是可行的

2、函数调用操作符 ( )

接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数

  • 对于test1()来说它的操作符为(),只有一个操作数就是函数名test1
  • 再看到test2("hello bit."),对于它来说操作符也为(),操作数的话有两个,一个为函数名test1,另一个则为函数参数"hello bit."
void test1()
{printf("hehe\n");
}void test2(const char* str)
{printf("%s\n", str);
}int main(void)
{test1();				//实用()作为函数调用操作符。test2("hello bit.");	//实用()作为函数调用操作符。return 0;
}

3、结构成员调用操作符 . 和 ->

  • 首先看到下面声明了一个结构体,是一本书,结构成员有作家价格。然后我声明了一个结构体成员,初始化了它的成员变量
typedef struct book {char writer[20];double price;
}st;st s1 = { "罗曼·罗兰", 50 };
  • 首先我使用.操作符先进行访问,可以看到获取了这个成员所有的成员变量
int main()
{st s1 = { "罗曼·罗兰", 50 };printf("name = %s\n", s1.writer);printf("price = %f\n",s1.price);  //结构体变量.结构体成员名return 0;
}

在这里插入图片描述


  • 我们使用这个指针变量解引用是不是取到了这个结构体的值,此时就可以去访问这些结构体成员了
st* ps = &s1;printf("name = %s\n", (*ps).writer);
  • 我们用->操作符来使用
printf("name = %s\n", ps->writer);	//结构体指针->结构体成员名
printf("price = %f\n", ps->price);
  • 可以看到对于这三种形式都是可以访问到这个结构体变量的成员

在这里插入图片描述

十一、表达式求值

1、隐式类型转换【整型提升】

  • C的整型算术运算总是至少以缺省整型类型的精度来进行的
    为了获得这个精度,表达式中的字符型短整型操作数在使用之前被转换为普通整型,这种转换称为[整型提升]
整型提升的意义
  • 表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
  • 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度
  • 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为intunsigned int,然后才能送入CPU去执行运算

如何进行整型提升

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

正数的整形提升

char c1 = 1;
  • 正数的原反补码是相同的
  • 再将一个整数1赋值给一个char类型的变量c1,此时就会发生截断,字符类型在内存中只占一个字节(8个比特)
  • 接下来c1会进行整形提升,在会提升成00000000000000000000000000000001达到整型4个字节32个比特位在内存中的要求

负数的整型提升

char c2 = -1;
  • 负数在内存中存放的是11111111111111111111111111111111,将其给到一个整型的变量之后就会发生截断即为11111111

  • 那这个时候再进行一个整型提升就和正数不一样了,因为负数的符号位为1,而整型提升在高位是要补充符号位,所以会在前头加上24个1,那其实也就变回了和之前-1的补码一般的样子,为32个1


2、算术转换

  • 如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行

在这里插入图片描述

  • 如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。【排名从上面最高,下面最低】
  • 例如intunsigned int一起进行算术运算的时候这个前者就要转换为后者的类型
  • 例如long intlong double一起进行算术运算的时候这个前者就要转换为后者的类型
  • 那其实可以看出,在char和short面前称霸的int如今沦落为了小弟

【注意】:
但是算术转换要合理,要不然会有一些潜在的问题

int main()
{float f = 3.14;int num = f;//隐式转换,会有精度丢失printf("%d\n", num);return 0;
}

在这里插入图片描述

3、操作符的属性【附优先级列表】

复杂表达式的求值有三个影响的因素

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序
  • 两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性

下面有一张关于操作符优先级的列表~~

在这里插入图片描述

4、问题表达式

表达式的求值部分由操作符的优先级决定,优先级只能决定先算谁,但是哪个表达式先调用要取决于编译器

  • 对于有些表达式而言,其实在不同的编译器上所呈现的结果是不同的,我们将其称作为【问题表达式】
问题表达式1
a*b + c*d + e*f
  • 来看上面这段代码,通过上面的优先级列表可以看出[*]的优先级一定是比[+]要来得高,因此可以保证[*]两端的数字先进行运算,但是却不能保证第三个*比第一个+早执行

问题表达式2
  • 继续来看下一个问题表达式
//表达式2
c + --c;
12
  • 虽然对于这个[--]操作符来说比[+]操作符的优先级来得高,但是呢我们却不知道在编译器运算的时候这个【c】是什么时候准备好

问题表达式3
  • 对于下面这段代码,也是存在很大的争议,特别是对于++--混搭的这种表达式尤其严重,你可以去不同的编译器上运行看看,结果都是不一样的~~
//代码3-非法表达式
int main()
{int i = 10;i = i-- - --i * ( i = -3 ) * i++ + ++i;printf("i = %d\n", i);return 0;
}
  • 下面是在不同的编译器上运行出来的结果,可见这个表达式问题有多大!!!
编译器
- 128Tandy 6000 Xenix 3.2
- 95Think C 5.02(Macintosh)
- 86IBM PowerPC AIX 3.2.5
- 85Sun Sparc cc(K&C编译器)
- 63gcc,HP_UX 9.0,Power C 2.0.0
4Sun Sparc acc(K&C编译器)
21Turbo C/C++ 4.5
22FreeBSD 2.1 R
30Dec Alpha OSF1 2.0
36TDec VAX/VMS
42Microsoft C 5.1

问题表达式4
  • 看到main函数中的函数调用表达式answer = fun() - fun() * fun();其实也是存在一个歧义的,因为你完全不知道编译器先调用的是哪个fun()
//代码4
int fun()
{static int count = 1;return ++count;
}
int main()
{int answer;answer = fun() - fun() * fun();printf( "%d\n", answer);//输出多少?return 0;
}
  • 可以看到,若是前面的fun()先执行的话,最后的结果就是-10,若是后面的fun()先执行的话,最后的结果就是-2

  • 正常来说大家应该都认为是第二个表达式符合我们的运算规则,因为先乘除后加减,可是呢我们最常用的VS出来的结果都不是我们想要的

我们可以到不同编译器上面去观察一下

在这里插入图片描述

  • 可以看到,虽然在【VS】和【Linux】在执行的结果是-10,而且在大多数的编译器下都是这个,但是呢对于函数的调用先后顺序无法通过操作符的优先级确定,因此这也是一个问题表达式

在这里插入图片描述


⑤ 问题表达式5
  • 有关+++结合的问题表达式
//代码5
#include <stdio.h>
int main()
{int i = 1;int ret = (++i) + (++i) + (++i);printf("%d\n", ret);printf("%d\n", i);return 0;
}

在这里插入图片描述

  • 可以看到,这里就出现了两个不同的结果,VS里运行出来是【12】,在linux里面运行出来是【10】

在这里插入图片描述


最后本文就到这里结束了,感谢大家的收看,请多多指点~

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

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

相关文章

wayland(xdg_wm_base) + egl + opengles 渲染旋转的 3D 立方体实例(十一)

文章目录 前言一、实现旋转的3D 立法体需要用到的技术1. 模型矩阵2. 视图矩阵3. 投影矩阵4. 背面剔除二、opengles3.0 渲染旋转的 3D 立方体实例1. egl_wayland_cube3_0.c2. Matrix.h 和 Matrix.c3. xdg-shell-client-protocol.h 和 xdg-shell-protocol.c4. 编译5. 运行总结参考…

Linux系统---nginx(4)负载均衡

目录 1、服务器配置指令 ​编辑 1.1 服务器指令表 1.2 服务器指令参数 2、负载均衡策略指令 2.1 轮询 &#xff08;1) 加权轮询 &#xff08;2) 平滑轮询 2.2 URL 哈希&#xff08;一致性哈希&#xff09; 2.3 IP哈希策略 2.4 最少连接 Nginx 负载均衡是由代理模块和上…

OpenGL调用窗口,方向键和鼠标

9.2 OpenGL调用窗口&#xff0c;方向键和鼠标 9.2.1 opengl调用窗口 OpenGL调用窗口步骤&#xff1a; 第一步&#xff1a;初始化 GLFW&#xff0c;初始化OpenGL,初始化窗口&#xff0c;初始化上下文 第二步&#xff1a;设置窗口大小和位置&#xff0c;设置输入输出 第三步…

uniapp画图qiun-data-charts

引用&#xff1a;https://blog.csdn.net/LW0512/article/details/124857592

【STM32】STM32学习笔记-WDG看门狗(46)

00. 目录 文章目录 00. 目录01. WDG简介02. IWDG概述03. IWDG框图04. IWDG键寄存器05. WWDG简介06. WWDG框图07. WWDG工作特性08. IWDG和WWDG对比09. 预留10. 附录 01. WDG简介 WDG&#xff08;Watchdog&#xff09;看门狗 看门狗可以监控程序的运行状态&#xff0c;当程序因为…

华为OD技术面试案例2-2024年

软开C&#xff0c;机考满分&#xff0c;技术面面评两个A&#xff0c;时间线如下&#xff1a; 01.04 笔试链接 01.10 笔试(2.5h) 01.11 综测 01.13 资格面(HR面) 01.17 技术一面 01.19 技术二面 01.20 主管面 01.22 提Offer及审批 01.26 Offer邮件 笔试复盘 1.英文输入…

嵌入式烧录报错:板端IP与PC的IP相同

报错&#xff1a; 配置 实际上我配置并没有错。 服务器IP&#xff08;就是本机&#xff09;、板端IP、网关。此处网关必须与板子IP配套&#xff08;可以不存在&#xff09;。 解决 我网卡配置了多个IP。一番删除添加还是报错。 于是点击服务器IP&#xff0c;换成别的&#x…

鸿蒙OS应用编程实战:构建未来应用的基石

&#x1f482; 个人网站:【 海拥】【神级代码资源网站】【办公神器】&#x1f91f; 基于Web端打造的&#xff1a;&#x1f449;轻量化工具创作平台&#x1f485; 想寻找共同学习交流的小伙伴&#xff0c;请点击【全栈技术交流群】 引言 鸿蒙OS&#xff08;HarmonyOS&#xff0…

前后端分离跨域问题总结

进行前后端分离开发联调时&#xff0c;网络常常会出现问题&#xff0c;可能后端确实做了跨域处理&#xff0c;但是前端还是跨域。一些其他的解决方法。 预检跨域 Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ head…

CentOS7如何使用Docker部署Wiki.Js知识库并实现公网远程访问?

文章目录 1. 安装Docker2. 获取Wiki.js镜像3. 本地服务器打开Wiki.js并添加知识库内容4. 实现公网访问Wiki.js5. 固定Wiki.js公网地址 不管是在企业中还是在自己的个人知识整理上&#xff0c;我们都需要通过某种方式来有条理的组织相应的知识架构&#xff0c;那么一个好的知识整…

网络工程师笔记4

协议 端口号 FTP 21、20 HTTP 80 Telnet 23 SMTP 25 TCP头部 TCP三次握手 TCP重传机制&#xff1a;超…

git merge refusing to merge unrelated histories

前言 本地新建了 一个新的git仓库 然后开始开发 然后 gitlab上 才去 新建了这个git仓库 初始化了master分支 同时新建了README.md文件 最后想把 本地的master 同步到gitlab的master上 提示 refusing to merge unrelated histories 翻译一下就是 拒绝合并不相关的历史 分析 提…

【ArcPy】验证输入字段是否有效

实例展示 代码 def __init__(self):self.parameters arcpy.GetParameterInfo() def updateMessages(self):if(self.parameters[0].value and self.parameters[1].value):shpPathself.parameters[0].valueAsTextfileNameself.parameters[1].valueAsTextworkspacearcpy.Describe…

Python 使用 PyRTL库 实现FPGA板卡仿真验证

要使用 Python 结合 PyRTL 库实现 FPGA 板卡的仿真验证&#xff0c;您可以利用 PyRTL 提供的硬件描述语言和仿真功能来进行 FPGA 设计的验证。下面我将为您介绍一个简单的示例&#xff0c;演示如何使用 PyRTL 库进行 FPGA 设计的仿真验证。 ### 步骤概述 1. **编写 PyRTL 硬件…

仿牛客网项目---用户注册登录功能的实现

从今天开始我们来写一个新项目&#xff0c;这个项目是一个完整的校园论坛的项目。主要功能模块&#xff1a;用户登录注册&#xff0c;帖子发布和热帖排行&#xff0c;点赞关注&#xff0c;发送私信&#xff0c;消息通知&#xff0c;社区搜索等。这篇文章我们先试着写一下用户的…

如何在群晖NAS中开启FTP服务并实现公网环境访问内网服务

文章目录 1. 群晖安装Cpolar2. 创建FTP公网地址3. 开启群晖FTP服务4. 群晖FTP远程连接5. 固定FTP公网地址6. 固定FTP地址连接 本文主要介绍如何在群晖NAS中开启FTP服务并结合cpolar内网穿透工具&#xff0c;实现使用固定公网地址远程访问群晖FTP服务实现文件上传下载。 Cpolar内…

SHARE 100M PRO:航测新高度,精准捕捉每一帧

SHARE 100M PRO&#xff1a;单镜头航测相机的革新&#xff0c;巡检效率与精度的新标杆 在航测和巡检领域&#xff0c;精确的数据采集对于确保项目成功至关重要。SHARE 100M PRO&#xff0c;作为一款单镜头航测相机&#xff0c;以其卓越的性能和创新技术&#xff0c;正在重新定…

【活动】前端世界的“祖传代码”探秘:从古老魔法到现代重构

作为一名前端工程师&#xff0c;我时常在项目中邂逅那些被岁月打磨过的“祖传代码”。它们就像古老的魔法书页&#xff0c;用HTML标签堆砌起的城堡、CSS样式表中的炼金术&#xff0c;以及JavaScript早期版本中舞动的符咒。这些代码承载着先驱们的探索精神和独特智慧&#xff0c…

智慧应急:构建全方位、立体化的安全保障网络

一、引言 在信息化、智能化快速发展的今天&#xff0c;传统的应急管理模式已难以满足现代社会对安全保障的需求。智慧应急作为一种全新的安全管理模式&#xff0c;旨在通过集成物联网、大数据、云计算、人工智能等先进技术&#xff0c;实现对应急事件的快速响应、精准决策和高…

基于C语言的TCP通信简单demo

上代码 服务端Server #include <stdio.h> #include <sys/socket.h> #include <netinet/in.h> #include <errno.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <arpa/inet.h> #include <n…