作者:几冬雪来
时间:2023年2月17日
内容:C语言函数操作符讲解
目录
前言:
在上一篇博客中我们初步认识了不少C语言的操作符,但是因为某些原因,我并没有将所以操作符一次性讲解完,那么今天我们就讲解我们剩下的C语言操作符。
操作符:
在上一篇博客中我们讲解了不少的操作符,类似我们的取地址操作符和按位取反操作符,今天我们将继续对其进行延伸。
1.剩余的单目操作符:
在上一篇博客的最后,我们介绍了我们的取地址操作符——‘~’。那么接下来我们将讲解一个在C语言中我们会经常使用到的两个操作符‘++’和‘--’操作符。
在我们的‘++’和‘--’操作符中,我们又将其分为前置‘++’与‘--’跟后置‘++’与‘--’。
那么前置和后置的做法导致的结果又有什么不同呢?我们可以写一个代码来进行试验。
这里我们可以看见,在初始化的时候我们将‘a’和‘b’都初始化为10。后面对‘a’进行了前置加加,对‘b’进行了后置加加。再在最后打印了一次‘b’的值。
那么前置加加是如何运行的,在打印的时候可以看出,我们先将a的值进行了加1,而后再打印a的值。这个时候我们a的值就从原来的10变为了11,因此我们可以得出——前置++是对一个数先进行加1再进行使用。相反的,后置++则是值先进行使用,使用我们第一次虽然是打印的b++的值,但是实际上,我们是先打印我们的b的值,也就是10,接下来再对b的值进行加1。不过我们的b的值是后置++,++是真实发生过的,所以再一次打印b的值的时候,这个时候b变为了11。由此得出——后置++是对一个数先进行使用,再进行加1。
相同的,我们的‘--’操作符的使用方法也类似于‘++’的方法,大家可以去试验一下。
但是我们的‘++’和‘--’都是有副作用的,也就是‘a’在完成‘++’的操作后,它自身的值会发生改变,就如同我们上面的这个代码,a自身的值就从10变为了9了,这是因为‘++’和‘--’是自增和自减,所以值会发生改变。
而后是我们的解引用操作符——‘*’。
这个操作符我们在取地址操作符处有对其进行讲解。这里我们主要知道我们的解引用操作符和取地址操作符是同时使用的就行了。
这样子我们的单目操作符就剩下我们最后一个操作符强制类型转换操作符——(类型)。
2.关系操作符:
在讲解完了我们基本全部的单目操作符后,接下来就开始讲解我们的双目操作符了,首先就是我们的简单易懂的关系操作符。
关系操作符比较简单,大于和小于我们经常见到,里面还有我们的大于等于和小于等于,写这些的时候我们要注意,等于号要放在后面书写。
3.逻辑操作符:
在关系操作符之后,我们就要是我们的逻辑操作符了,那么逻辑操作符有哪些呢?
看着这个逻辑与和逻辑或,我们可能会无端联想到我们的按位与和按位或。那么逻辑与和逻辑或又是怎么样运行,它们和按位与和按位或是否有关系?
与按位与和按位或的判断二进制位不同, 逻辑与和逻辑或也是判断,但是并不是判断二进制位,它们判断的是真假。那它是怎么判断的,我们就先用逻辑与来举例。
在我们的逻辑与里面,只有两个条件为真,结果才为真,如果有一个条件为假,或者两个条件都是假,则我们的结果就为假。
相同的,我们依靠逻辑与来推断逻辑或。
在C语言中我们可以知道我们的真为非0的数,假的值为0。因此我们也可以用我们的逻辑操作符来书写我们的代码,更准确的是来书写的判断语句。
通过这个代码我们可以看出我们也可以将我们的逻辑操作符运用在判断语句中,因为a是10位非0的数,所以a代表的为真,相同的因为b的值是0,所以b代表的是假。
在之前我们判断闰年的代码中我们也有运用到这个操作符,大家可以去看看。
但是别看这个讲解就认为我们的逻辑与操作符简单,在这个小小的操作符符中也存在着不少坑。我们就做一道题来看看。
例如我们这个代码的结果,有人认为是1,3,3,5。但是结果真的是这个吗?我们将代码运行起来试试看是什么?
这里得到我们的结果是1,2,3,4。这个结果和我们预料到的不一样,那为什么会变成这个样子?这都是因为我们的逻辑与和逻辑或的特殊性。 在我们的逻辑与和逻辑或的操作符中,我们通常是从左往右看的,这个样子就使得,如果我们的逻辑与的左边如果为假,右面的都不会执行。就像我们这个代码一样,
因为我们这里的a是先使用后自增,因此在使用的时候,我们的a的值就是0,为假。又因为a为假,所以后面的条件,比如:++b和d++就不会执行。逻辑或也是一样,如果左边就为真,那么右面的也不会执行。
4.条件操作符:
在单目和双目操作符之后,有人会问:既然有单目操作符和双目操作符,那有没有我们的三目操作符呢?答案是有的,但是我们一般将它叫做条件操作符——exp1?exp2:exp3。
条件操作符,涉及到条件两个字,我们首先想到的就是我们的判断语句。那么条件操作符跟我们的判断语句有什么相似的地方?
在我们的条件操作符中,我们的exp代表了我们的一个表达式。然后就是我们条件表达式的运作原理。
如果我们exp1的结果是真的话,那么exp2计算,exp3不计算。exp2计算的结果就是我们条件操作符的结果。反之如果exp1为假,则计算exp3,exp3是我们整个条件操作符的结果。
我们的条件操作符也与我们的判断语句有一定的关系。接下来我写一个判断语句代码来求两个数的最大值。
可以看出我们的判断语句成功书写出了我们想要的结果,那么我们是否可以将我们的判断语句更替为条件操作符的形式呢?那当然是可以的。
在写我们条件操作符的时候要注意我们问号后面的表达式不能写反,就拿我们上面的这个代码举例,我们要求出两个数的较大值,如果这个时候我们将我们的a和b调换顺序的话,输出的结果是不同的。
5.逗号表达式:
那么什么是逗号表达式?
逗号表达式,就是用逗号隔开的多个表达式。
相同的,逗号表达式也有它的计算规律——逗号表达式依次从左到右执行,逗号表达式的结果是最后一个表达式的结果。 我们要怎么去理解这句话?这里我们做一道题目来以此进行说明。
这里我们要小心,逗号表达式的结果是最后一个表达式的结果,但是前面的表达式的值还是会进行计算的。
同时我们的逗号表达式也可以进行我们相应的业务处理。
6.下标引用,结构成员,函数调用操作符:
一开始就是我们的下标引用操作符——‘[]’。这个操作符我们在书写数组的时候经常会使用到,由此也可以推断出,下标引用操作符只能在数组计算。
那我们下标引用操作符的操作数有哪些?举例:
紧接着下标引用操作符的是我们的函数调用操作符——‘()’。这个操作符我们也经常遇见到。
同样的函数调用操作符也有我们的操作数。
接下来就是我们的结构成员。结构成员与结构体有一定的联系,在我们的C语言中我们存在两种类型。
一种是内置类型:代表的是我们的‘int’和‘char’。
而另一种则是我们的自定义类型:代表的是我们的结构体,枚举,联合体等。
今天我们就讲讲我们的结构体。
结构体是自定义类型,它也被我们称为——聚合类型。那么为什么要有我们的结构体。这是因为我们生活中有些东西写为C语言代码的时候,不能单纯的用内置类型。
比如我们出版一本书,书的话肯定要有书名,要有作者和售价这几样东西,但是它们的类型又不相同。
那么我们如何打印出出来我们想要的结果?这里就运用到位我们的结构成员操作符中的一种——‘.’。
这就是我们C语言的结构体访问的方法。结构体变量.成员名。当然我们也可以将我们的结构体成员封装成一个函数。
这里有同学就发现了,我们的‘.’是我们书写结构体所需要的一种操作符。那么另一种结构体的操作符是什么?这种结构体的操作符要在哪里使用?接下来我们对我们的函数里面的内容稍加修改之后,大家来看看。
在这里我们见识到了不同的结构体的访问。在函数中:结构体指针->成员名。我们也可以通过这种方法来访问我们的结构体。
表达式求值:
在我们C语言中,我们会经常用到我们的操作符来求值。但是我们表达式计算的顺序并不是随机的,它是有一定的顺序规律的。
在我们C语言中,表达式求值的顺序一部分是由操作符的优先级以及结合性决定的。同时,有些表达式的操作数在求值的过程中可能需要转换为前提类型。
1.隐式类型转换:
C语言的整形算术运算总是至少以缺省整形类型的精度来计算的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前会被转换为普通整形,这种转换被称为整形提升。这是什么意思呢?我们写一个代码来说明一下。
在我们这个代码中,我们需要求c的值,但是在这里我们并不是只是将a的值和b的值相加赋给c就行。在这个代码计算的过程中,我们要先将a和b转换为整形,计算出来结果也是整形,最后将这个整形赋值给c。在计算过程中,我们一般将大小小于整形类型的类型进行提升,也就是将我们的字符类型和短整型类型提升至整形类型后再进行计算,这就是我们的整形提升。
那么整形提升的意义是什么呢?
那么如何进行整形提升呢?整形提升是按照变量的数据类型的符号位来提升的。
依旧以我们上面的代码为例。
int main()
{
char a = 3;
//00000000000000000000000000000011
//截断
//00000011
char b = 127;
//00000000000000000000000001111111
//01111111
char c = a + b;
//00000011
//01111111
//整形提升
//00000000000000000000000000000011
//00000000000000000000000001111111
//00000000000000000000000010000010->相加
//再次截断
//10000010
printf("%d\n", c);
//再次提升
//11111111111111111111111110000010->补码
//10000000000000000000000001111110
return 0;
}
当然我们的整形提升又被称为隐形提升,所以它们也有很隐蔽的方式,可能仅仅是因为一个符号我们就要进行整形提升。
但是在我们的类型中,不仅仅有大小小于整形的字符类型和短整型,还有大小大于整形类型的长整型和浮点型等等,那么这些大小大于整形的类型在计算的时候又会发生什么呢?
2.算术转换:
在计算大小大于整形的类型的时候,我们就要发生算术转换。
类似我们这一张表,如果我们要计算一个int类型的值和一个float类型的值相加的时候,我们将要向上转换,将int转换为float类型再进行计算。
这种转换也被我们称为算术转换,需要注意的是,算术转换和整形提升一样都是隐式转换。
但是,在进行我们算术转换的时候,我们的算术转换应该要合理,不然的话会发生一些潜在的问题。
就类似我们上面的这个代码,我们将一个浮点型赋值给一个整形,这个时候也会发生我们的算术转换,但是在转换的过程中我们会发生精度丢失,这就是我们需要注意的问题。
3.操作符的属性:
在C语言中,我们复杂表达式求值有3个影响因素。
1.操作符的优先级
2.操作符的结合性
3.是否控制求值顺序
1.优先级:
如果在一个表达式里面我们无法确定优先级的时候,我们可以采取加括号的方式。 另外需要注意:只有相邻操作符才需要讨论操作符的优先级。
如果相邻操作符的优先级一样的话,这个时候就取决于我们的结合性。
2.结合性:
在我们上面的表中,也有结合性的顺序。
3.是否控制求值顺序:
在我们所有的代码中是否控制控制求值顺序的操作符较少,这里最具代表性的就是我们的逻辑与和逻辑或,如果逻辑与的左边为假,右边则不用计算。如果逻辑或的左边是真,则右边不用计算。同样具有控制求值顺序的还有我们的条件操作符和逗号表达式两种。
4.一些表达式问题:
虽然我们了解了我们复杂表达式求值有3个影响因素。但是即使是这样我们也不能确定我们表达式计算的唯一路径。
我们举一个例子:
在两种方法都是可以的。在这个代码中我们只能确定乘号比加号早,但是无法确定哪个乘法比加号早。这种表达式我们称之为问题表达式。
这里有人就要问了,这里的答案不都一样的吗?那么接下来我就再写一个问题表达式。
这里我们可以确定减减的优先级比加法的高,但是我们无法确定左边的c的值是什么时候准备好的。
假设我们c的值是3。有可能一开始我们左边的c的值就准备好了为3,准备之后再进行c的减减,这个时候的答案就是5。也可能左边c的值并没有事先准备,我们先进行减减,这个时候c的值就为2,在这之后我们再准备c的值,这样又是不同的答案,结果为4。
同样的,还有这种形式的问题表达式,一个问题表达式在不同的编译器中所得到的答案也是可能会不一样的。
在C语言中,我们的函数调用也会受到问题表达式的影响。
在这个代码中我们只能确定乘号比减号先算,但是无法确定哪个函数优先调用。因此它也会产生不同的结果,这个代码也是我们的问题代码。
所以在我们的代码中我们无法考我们操作符的优先级来确定我们表达式的唯一路径的式子都存在着一定的问题,我们写代码的过程要尽可能的避免问题表达式的书写。
结尾:
十分抱歉,这篇博客写的时间有点长了,可能是因为要写的东西比以前的博客要多很多,还有就是在准备完结这篇博客的时候,我又发烧了,简单来说就是燃起来了(物理)!也不知道是不是再次阳了。因此本来可以早一点完结的博客被延期到了现在,质量可能有所下降,但是还是希望对正在学习C语言的同学们有一定的帮助。