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 | - 5
	return 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 -> -8
	return 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 = a
	a = a ^ b;		//a ^ b ^ a = b ^ 0 = b

	printf("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
//--------------------------------
	//000000000000000000001110
	printf("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
	//--------------------------------
		//000000000000000000001110
		printf("置1:a = %d\n", a);


		a = a & ~(1 << (n - 1));
		//把变量a的第n为置0
		//000000000000000000001110
		//111111111111111111111011
	//--------------------------------
		//000000000000000000001010
		printf("置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;
	else
		b = -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】

在这里插入图片描述


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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

记录工作中遇见问题、学习项

1、判空操作 Demo demo Optional .ofNullable(demoService.getById(id)) .orElseThrow(() -> new ServiceException("不存在id为" id "的数据")); 2、SQL方面 1、group by : GROUP BY 子句必须放在 WHERE 子句中的条件之后&#…

【MATLAB】 小波分解信号分解+FFT傅里叶频谱变换组合算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~ 展示出图效果 1 小波分解算法 小波分解算法是一种数学方法&#xff0c;用于将信号分解为不同频率的小波成分。这种算法基于小波函数&#xff0c;可以用于信号处理、图像压缩和数据压缩等领域。小波分解算法的基本思想是…

Python多线程编程:深入理解threading模块及代码实战【第99篇—Multiprocessing模块】

Python多线程编程&#xff1a;深入理解threading模块及代码实战 在Python编程中&#xff0c;多线程是一种常用的并发编程方式&#xff0c;它可以有效地提高程序的执行效率&#xff0c;特别是在处理I/O密集型任务时。Python提供了threading模块&#xff0c;使得多线程编程变得相…

设置django orm 模型中的字段限制数值的大小

需求如下&#xff1a; 1&#xff1a;使用了django框架中的orm模式来创建数据表 2&#xff1a;限制字段的取值范围 # -------------------- # 因为django的orm没有限制整形字段的取值范围&#xff0c;所以需要django内置的值校验器进行校验 from django.core.validators i…

刷题第2天(中等题):LeetCode59--螺旋矩阵--考察模拟能力(边界条件处理)

LeetCode59: 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;[[1,2,3],[8,9,4],[7,6,5]]示例 2&#xff1a; 输入&#xff1a…

Appium手机Android自动化

目录 介绍 什么是APPium&#xff1f; APPium的特点 环境准备 adb(android调试桥)常用命令 appium图形化简单使用 连接手机模拟器 使用appium桌面端应用程序 ​编辑 整合java代码测试 环境准备 引入所需依赖 书写代码简单启动 ​编辑 Appium元素定位 id定位 介…

docker版本 jenkins配置gitlab自动部署

前端项目 Build steps pwd npm config set registry https://registry.npm.taobao.org npm -v node -v #npm install npm run build:prod tar -czvf QASystem.tar.gz distpwd cd /data/zhouxy37/vue_deploy tar -zxvf QASystem.tar.gz sudo mv dist QASystem cp -r QASyste…

Matlab: Introduction to Hybrid Beamforming

文章目录 来源混合波束赋形的基本概念System Setup关键函数 来源 在matlab的命令行输入 doc hybrid beamforming 混合波束赋形的基本概念 混合波束形成简介 本例介绍了混合波束形成的基本概念&#xff0c;并说明了如何模拟这种系统。 现代无线通信系统使用空间复用来提高散…

WiFi模块赋能智能手表:拓展功能与提升连接性

随着科技的不断进步&#xff0c;智能手表正逐渐成为现代人生活中不可或缺的智能配饰。其中&#xff0c;WiFi模块的应用为智能手表带来了更多强大的功能和更高的连接性&#xff0c;为用户提供了更为便捷、智能化的使用体验。本文将深入探讨WiFi模块在智能手表中的应用。 远程通信…

Linux入门攻坚——16、Linux系统启动流程

CentOS5、6的启动流程 Linux&#xff1a;kernel rootfs&#xff0c;Linux系统就是内核加上根文件系统。 内核之上是库&#xff1a; 库&#xff1a;函数集合&#xff0c;function&#xff0c;函数具有调用接口&#xff0c;库函数不能单独执行&#xff0c;必须被其他程序调用…

管理系统提升:如何从用户体验设计上发力

一、关于用户体验 管理系统的用户体验&#xff08;User Experience, UX&#xff09;是指用户在使用管理系统时所感受到的整体体验&#xff0c;包括用户对系统界面、交互、功能、性能等方面的感受和评价。一个好的管理系统的用户体验应该是简单易用、高效快捷、信息清晰、界面美…

Redis冲冲冲——Redis的主从复制,哨兵模式以及SpringBoot的配置

目录 引出Redis的主从复制&#xff0c;哨兵模式以及SpringBoot的配置Redis的主从复制Redis的哨兵模式SpringBoot配置 缓存三兄弟&#xff1a;缓存击穿、穿透、雪崩缓存击穿缓存穿透缓存雪崩 总结 引出 Redis冲冲冲——Redis的主从复制&#xff0c;哨兵模式以及SpringBoot的配置…

Matlab进阶绘图第41期—双三角网格曲面图

在《Matlab论文插图绘制模板第67期—三角网格图(Trimesh)》中&#xff0c;我分享过三角网格曲面图的绘制模板。 然而&#xff0c;有的时候&#xff0c;需要在一张图上绘制两个及以上的三角网格曲面图&#xff0c;且每个三角网格曲面图使用不同的配色方案。 在Matlab中&#x…

上线服务时遇到的一个SSLHandshakeException错误

今天部署自己的一个程序&#xff0c;在本地是可以正常跑通流程了&#xff0c;但是部署到服务器上运行之后出现了如下错误。 Caused by: javax.net.ssl.SSLHandshakeException: No appropriate protocol (protocol is disabled or cipher suites are inappropriate) at sun.sec…

s-table和columns初始化不完整,造成table文件的filter报错

问题 顺藤摸瓜找errorHandler.js文件 发现文件并没有什么问题 顺藤摸瓜找index.vue文件 首先找到报错的filter&#xff0c;发现与columnsSetting相关 找到columnsSetting发现等于columns 返回自己使用S-table组件的地方&#xff0c;发现columns初始化时仅初始化为ref()未表明…

Zoho Bigin 2024新功能预览:小企业CRM大热卖!

目前&#xff0c;市面上大多数CRM客户关系管理软件功能复杂&#xff0c;层级多&#xff0c;涵盖从市场营销、销售过程管理再到售后服务的完整客户生命周期。很多功能小微企业在当前阶段用不上&#xff0c;所以不愿意为用不上的功能和服务付费&#xff0c;市场上真正适合小微企业…

机器人内部传感器阅读梳理及心得-速度传感器-数字式速度传感器

在机器人控制系统中&#xff0c;增量式编码器既可以作为位置传感器测量关节相对位置&#xff0c;又可作为速度传感器测量关节速度。当作为速度传感器时&#xff0c;既可以在模拟量方式下使用&#xff0c;又可以在数字量方式下使用。 模拟式方法 在这种方式下&#xff0c;需要…

美国1月PCE数据出炉,5月降息成泡影?

KlipC报道&#xff1a;周三&#xff0c;美国商务部发布的数据显示&#xff0c;美国四季度核心个人消费支出(PCE)物价指出环比修正值为2.1%&#xff0c;超市场预期。剔除食品和能源的核心PCE指数走高2.1%。两者均略高于预估初值。数据发布后&#xff0c;美元指数短线小幅走低&am…

如何在宝塔面板中设置FTP文件传输服务并实现远程文件管理

文章目录 1. Linux安装Cpolar2. 创建FTP公网地址3. 宝塔FTP服务设置4. FTP服务远程连接小结 5. 固定FTP公网地址6. 固定FTP地址连接 宝塔FTP是宝塔面板中的一项功能&#xff0c;用于设置和管理FTP服务。通过宝塔FTP&#xff0c;用户可以创建FTP账号&#xff0c;配置FTP用户权限…

Linux小项目:在线词典开发

在线词典介绍 流程图如下&#xff1a; 项目的功能介绍 在线英英词典项目功能描述用户注册和登录验证服务器端将用户信息和历史记录保存在数据中。客户端输入用户和密码&#xff0c;服务器端在数据库中查找、匹配&#xff0c;返回结果单词在线翻译根据客户端输入输入的单词在字…