- 在正式开始之前,我们还是借助一张思维导图帮助大致简单回忆一下有关操作符的基础内容
- 我们继续朝下讲最后的内容
一.下标引用、函数调用和结构体成员
1. [ ] 下标引用操作符
该操作符的操作数:一个数组名 + 一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。
注意这里是说的arr和9只是“[ ]”两个操作数,因此我们也可以把该数组这样写:
9[arr];
证明代码如下:
int main() { int arr[10] = { 1,2,3,4,5 };//不完全初始化,此时后面未初始化元为0 // //数组的起始是有下标的,下标是0开始的 printf("%d\n", arr[2]);//[] 下标引用操作符,arr 和 2 是[] 的两个操作数 printf("%d\n", 2[arr]); return 0; }
但是通常为了代码的可读性和流畅性,我们最好别这么写,这里只是告诉你这样写也是对的!
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. 访问一个结构体的成员
通常有以下两种形式
. 结构体.成员名 -> 结构体指针->成员名
“.” 结构体.成员名
这种通常用于直接访问结构体成员
struct Book { char name[30];//成员 char author[20]; float price; }; int main() { struct Book b1 = { "C语言", "zz",99.6f }; struct Book b2 = {"数据结构","yy",88.6f};//书 printf("%s %s %.1f\n", b1.name, b1.author, b1.price); printf("%s %s %.1f\n", b2.name, b2.author, b2.price); //结构体变量.成员名 return 0; }
-> 结构体指针->成员名
主要用于在结构体传参时通过结构体指针找到对应结构体成员
代码如下:
struct Book { char name[30];//成员 char author[20]; float price; }; void Print(struct Book * p) { printf("%s %s %.1f\n", (*p).name, (*p).author, (*p).price); printf("%s %s %.1f\n", p->name, p->author, p->price); //-> //结构指针->成员名 } int main() { struct Book b1 = { "C语言", "zz",99.6f }; struct Book b2 = {"数据结构","yy",88.6f};//书 Print(&b1); Print(&b2); //printf("%s %s %.1f\n", b1.name, b1.author, b1.price); //printf("%s %s %.1f\n", b2.name, b2.author, b2.price); //结构体变量.成员名 return 0; }
二.表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。
同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型
1.隐式类型转换
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 的结果是真.
//实例2 int main() { char c = 1; printf("%u\n", sizeof(c)); printf("%u\n", sizeof(+c)); printf("%u\n", sizeof(-c));//%u - 10进制的形式打印无符号的整数 return 0; }
实例2中的,c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.
表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof( c ) ,就是1个字节.
2. 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。
下面的层次体系称为寻常算术转换
long double
double
float
unsigned long int
long int
unsigned int
int
上面类型由高—>低
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算
注意:
算术转换要合理,要不然会有一些潜在的问题
float f = 3.14; int num = f;//隐式转换,会有精度丢失
3 操作符的属性
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级
存在问题的一些表达式
例一
//表达式的求值部分由操作符的优先级决定。 //表达式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;
注释:同上,操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
例三
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(); 中我们只能通过操作符的优先级得知:先算乘法,再算减法。
函数的调用先后顺序无法通过操作符的优先级确定。
例四
#include <stdio.h> int main() { int i = 1; int ret = (++i) + (++i) + (++i); printf("%d\n", ret); printf("%d\n", i); return 0; } //尝试在linux 环境gcc编译器,VS2013环境下都执行,看结果。
Linux环境的结果:
VS2013环境的结果:
看看同样的代码产生了不同的结果,这是为什么?
这段代码中的第一个 + 在执行的时候,第三个++是否执行,这个是不确定的,因为依靠操作符的优先级和结合性是无法决定第一个 + 和第三个前置 ++ 的先后顺序。
总结:
我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的!
资料引用:
博客中有关操作符优先级的图片取自该博客:
详解:操作符的优先级
总结
今天的内容暂时到这里就结束了,我们今天就把所有有关操作符的知识点以及易错点都给讲完了。如果你还有问题的话一定要自己试一下,不然光看是非常容易遗忘的,咱们必须反复的练习才能熟悉起来!
以上就是关于操作符的所有内容啦,我们通过四章节讲述了这一部分的内容,感兴趣可以翻翻以前有关的章节,链接我就贴在这里啦!
【C语言初阶】带你轻松玩转所有常用操作符(1) ——算数操作符,移位操作符,位操作符
【C语言初阶】带你轻松玩转所有常用操作符(2) ——赋值操作符,单目操作符
【C语言初阶】带你轻松玩转所有常用操作符(3)——关系操作符,逻辑操作符,条件操作符,逗号表达式
好了,如果你有任何疑问欢迎在评论区或者私信我提出,大家下次再见啦!
新人博主创作不易,如果感觉文章内容对你有所帮助的话不妨三连一下这个新人博主再走呗。你们的支持就是我更新的动力!!!
**(可莉请求你们三连支持一下博主!!!点击下方评论点赞收藏帮帮可莉吧)*