C语言操作符大全(其二),隐式转换,整形提升,结构成员,算术转换,保姆式解析

开篇语

对于本节内容,到后面的隐式转换,整形提升这个知识点需要多多消化理解,这也是本节内容的重难点,当然相对于一些难的来说,确实也不算难,还是要保持空杯心态,既然学就要学扎实了!

关系操作符

=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”

这些操作符非常简单,没什么好讲的,唯一需要注意的就是==是判断相等,千万不要把=当做判断。关于这点可能出现的错误和解决办法我们在最开始初识C语言中就详细讲过了,一定要记得及时复习哦。

逻辑操作符

&& 逻辑与
|| 逻辑或
注意要与上一期逻辑与&,逻辑 | ,逻辑异或 ^,进行区分,这些是对二进制进行计算的,详解请看上一期C语言操作符大全(其一)
逻辑与和逻辑或是只关注真假,来进行运算。我们用代码来说明:

#include<stdio.h>

int main()
{
	int i = 10 && 0;
	printf("i=%d\n",i);
	
	int j = 10 && 2;
	printf("j=%d\n", j);
	return 0;
}

在这里插入图片描述
所以我们可以得到的结论就是,逻辑与&&,一假则假,两个都为真才为真;逻辑或 || ,一真则真,两个同时为假才为假。再回顾一下:C语言中0表示假,非0表示真。所以逻辑与和逻辑或运算的结果只有0和1两种。

逻辑与和逻辑或的题目(360面试题)

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

在这里插入图片描述
看到这个结果是不是有点懵,没想到吧,我还留了一手。我们一起分析一下为什么会出现这个结果呢?
在这里插入图片描述
这也是关于逻辑与和逻辑或的一个易错点,那么我们再看一下下面这个代码:

#include <stdio.h>

int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	//i = a++ && ++b && d++;
	i = a++||++b||d++;
	printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
	return 0;
}
//程序输出的结果是什么?

在这里插入图片描述
你做对了嘛?

我们来总结一下,逻辑与&&和逻辑或||,只关心真假,并且运算结果只有0和1两个结果,如果有多项运算的话,逻辑与看到0(假)就停止了,逻辑或看到1(真)就停止了;

条件操作符

exp1 ? exp2 : exp3

这就是条件操作符,这个其实表达的意思是表达式1成立吗?如果成立,则结果是表达式2的值,否则,就是表达式3的值。当然只看这个是不太好理解的,我们用一段代码来解释:

#include<stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int m = (a > b ? a : b);
	printf("%d\n",m);

	return 0;
}

在这里插入图片描述
这段代码表达的意思就是a大于b吗?如果a>b,那么返回a,否则返回b。

逗号表达式

exp1, exp2, exp3, …expN

逗号表达式,就是用逗号隔开的多个表达式,只要从左向右依次计算即可,最后一个表达式得到的结果就是整个表达式的结果。

#include<stdio.h>

int main()
{
	//代码1
	int a = 1;
	int b = 2;
	int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式
	//c是多少?
	printf("%d\n",c);
	return 0;
}

在这里插入图片描述

看到这个结果其实也很好理解,只要从左依次向右计算即可。

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

[ ] 下标引用操作符

这个其实也很好理解,我们平时访问数组,例如arr[0],这个方块就是下标引用操作符。

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

值得注意它的操作数是两个。

( ) 函数调用操作符

一个括号就是函数调用操作符,这些其实我们一直在用,只是没有系统性地去认识它们而已。

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

值得强调的是它的操作数并不确定,但是至少有一个操作数就是函数名,然后给函数传的参数有几个也是操组数加几
访问一个结构的成员

. 结构体.成员名
-> 结构体指针->成员名

这个相对来说是个比较陌生的知识点,在此之前我们还要进行一定的知识的补充,关于类型:

我们平时用的一般都是int
,char,float,double,short,long等等,但是这些其实都是内置类型,如果仅仅是有这些是远远不够的,所以我们引入了一些自定义的类型,比如结构体,枚举,联合体,等等,当然这些不是我们这篇内容的重点,我们就不展开来解释。今天我们先来简单说明一下结构体。

假设我们要描述一个学生,那么这个学生有名字,性别,学号,等等,这时候我们怎么来描述呢?

#include<stdio.h>


struct Stu
{
	char Name[20];
	char Gen[20];
	int num;
};

int main()
{
	struct Stu a = { "如花","female",18 };
	
	printf("%s %s %d\n", a.Name,a.Gen,a.num);

	return 0;
}

上半部分我们定义的类型叫做struct Stu,下面我们只要把它当做我们的int或者char等类型正常使用就可以了,当我们在使用的时候有两种方式,一种就是用 ‘ . ’ 加上后面成员,当我们用指针来接受的话,也可以用->来直接指向成员。
两种格式分别是:结构体变量.成员名,结构体指针->成员名
在这里插入图片描述

在这里插入图片描述
可以看到以上两种方法都是可以的。
关于这里因为我们用的不多,对这个并不熟悉,所以我们还是来看一道题来练习一下,

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

请问哪种方式可以正确修改了age。

表达式求值

表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型
所以这里就牵扯到了不少东西。需要重点注意的就是隐式类型转换和算术转换和操作符属性。

隐式类型转换—整型提升

我们先来看整型提升的概念:

C语言中计算的精度一般最小是整型,为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。

整型提升的意义:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算

当然了,这意义现在可能并不太能理解,要涉及到计算机组成原理。所以看一下知道就可以。我们重点要学习的是整型提升本身,
例如:

#include<stdio.h>

int main()
{
	char a = 3;
	char b = 127;
	char c = a + b;
	printf("%d",c);
	return 0;
}

我们先来看这段代码,可能你会觉得很怪异啊,因为我们把整型数字放到了char类型的变量里面,这时候会发生什么呢?我们看结果:
在这里插入图片描述
结果是126诶,这是为什么呢?这就要涉及到我们的整型提升的概念了,我们要先搞懂整型提升是怎么提升的才能去进行计算,

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

这里我们有三种类型的整型提升,分别是负数、正数和无符号整型,整型提升其实是基于二进制上进行的,当进行整型提升时在二进制补的是符号位,也就是最高位,所以负数进行整型提升是补的1,正数就是补0;这时候我们就可以着手来分析我们上面的代码了,为什么结果是-126呢?

我们用下图来分析:

在这里插入图片描述

相信把上图弄懂,你对整型提升就有了一个比较好的理解了,这里还可以再补充一个小小的知识点:

类型的取值范围计算

在这里插入图片描述

此图就是计算char类型的取值范围的方法,当然对于其他类型也是适用的。还有一个比较形象也很有趣的图来帮助记忆:
在这里插入图片描述
这个小插曲就算过去了,我们继续学习我们的知识。
对整型提升有一定理解后我们来看一道练习:

整体提升例题1

//实例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;
}

这道题的结果是什么呢?
在这里插入图片描述
可以看到是打印出了c,原因其实也很简单,a和b分别是char和short类型,在表达式执行时也是要发生整型提升的,肯定不会和原来的整数相等。

整体提升例题2

做错也没关系,再来一道练习题:

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

在这里插入图片描述
聪明的你应该已经彻底理解了整型提升了,只要参与表达式运算,就会发生整形提升,我们一定要知道这些,才能感知到整型提升的存在,否则怎么解释这些现象呢?

算术转换

其实算术转换和整型提升就比较类似了,只不过是讨论的int类型之上的类型计算问题,也是隐式转换的一种,

1.long double
2.double
3.float
4.unsigned long int
5.long int
6.unsigned int
7.int

这里的顺序是由高到低的,也就是当类型不同时优先转换成上面的类型。但是算术转换要合理,要不然会有一些潜在的问题。
例如:

float f = 3.14;
int num = f;//隐式转换,会有精度丢失

这就是算术转换的内容。

操作符的属性

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序。

操作符的属性只有这三个,我们一个一个解释。
优先级:
优先级通常适用于两个相邻操作符之间,决定先执行哪一个的问题,比如式子:

a*b+3*c

这时候一定是先算两个*,最后算+,但是两个乘的顺序哪个先执行呢?这个就不好说了,所以优先级通常适用于两个相邻操作符之间
这里也可以附上一个链接,看一下有些操作符总结好的操作符优先级详解
结合性:
先确定优先级,在优先级相同的情况下,结合性才起作用,结合性就是计算的方向,比如加法就是自左向右计算,而赋值则是自右向左计算,关于全部操作符优先级呢依旧是看上面链接啊。有时间的话我也会整理发出来。
是否控制求值顺序:
这个就比较有讲究了,通常大多数的操作符都是没有控制求值顺序的,但是有几个特殊的,在操作符大全上一期中我们讲到了逻辑与&&,逻辑或|,当逻辑与遇到假时后面不再参与运算,逻辑或遇到真时后面不再参与运算,这些是不是就是影响了我们的求值顺序。当然了还有条件操作符?:和逗号表达式。(因为逗号表达式只是最后一个表达式起真正作用)

讨论一些问题表达式

当我们学习了以上操作符的属性后,我们就能完全确定一个表达式的求值顺序了吗?答案是未必。我们来看以下几个例子:
在这里插入图片描述
比如这个表达式,图中数字代表计算顺序,哪种顺序求值的话其实都没有问题,但是不管怎么样,它的计算顺序并不能完全确定,当写出这样的代码时,从严格意义上来说就是存在潜在问题的。这个也许你并不能很好的感知到问题,觉得结果是一样的,但是如果你格局打开一些,假设abcdef各自都是一个表达式呢?里面若有重复的值,则会影响到后面的计算,这时候就容易出现问题了。我们来看下面的例子:

//表达式2
c + --c;

这个表达式本质也是个问题表达式:我们无法确定c是先准备好的,还是先–后才准备好。这时候的结果也就会出问题。
下面还有一个示例:

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

这个是一本书C和指针里面提出的一个例子,当作者拿这段代码去不同编译器上运行时:
值 —— 编译器
—128—— Tandy 6000 Xenix 3.2
—95 —— Think C 5.02(Macintosh)
—86 ——IBM PowerPC AIX 3.2.5
—85 ——Sun Sparc cc(K&C编译器)
—63 —— gcc,HP_UX 9.0,Power C 2.0.0
4 —— Sun Sparc acc(K&C编译器)
21 ——Turbo C/C++ 4.5
22 ——FreeBSD 2.1 R
30 ——Dec Alpha OSF1 2.0
36 ——Dec VAX/VMS
42 ——Microsoft C 5.1

可以看到,当出现这种代码时,连编译器得出的答案都是不一定是准确的。编译器看到这样的代码都凌乱了。
示例4:

//代码4
int fun()
{
  static int count = 1;
  return ++count;
}
int main()
{
  int answer;
  answer = fun() - fun() * fun();
  printf( "%d\n", answer);//输出多少?
  return 0;
}

再来看这个代码呢?answer等于多少,三个函数先调用哪一个?如果顺序不同结果一定是不一样的。
最后一个示例:

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

我们这次自己亲手验证一下好吧,当我用这段代码在vs2022上运行一下看一下结果:
在这里插入图片描述
看到这个结果你懵逼不,我也很懵,怎么算的12和4呢?
我们再拿这个代码去Linux环境下gcc编译器上运行一下看看:
在这里插入图片描述
可以看到在不同的编译器下得到的结果甚至都不同。所以这个代码真正的结果还重要吗?
如果你是一个学生,可能在考试中经常看到这样的代码,其实这种代码本身是没有什么意义的,至于出题人为什么出这样的题目,谁也说不准,也许压根就不知道,或者是没考虑到,所以还是要正确看待这些。不要太在意,自己写代码也要尽量去避免这种有问题的代码。
写到这,关于操作符这一节就全部写完了,现在时间晚上11点18分,记录一下,希望在学习编程的路上坚持下来!!共勉。

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蓝不过海呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值