【C语言操作符详解(二)】--结构成员访问操作符,操作符的属性,表达式求值

目录

 一.结构成员访问操作符

1.1--结构体

1.1.1--结构的声明

1.1.2--结构变量的定义和初始化 

1.2.结构成员访问操作符--直接访问 

二.操作符的属性:优先级,结合性

2.1--优先级

2.2--结合性

 2.3--运算符的优先级顺序总结

 三.表达式求值

3.1--整型提升

3.2--算术转换

 3.3--问题表达式解析

3.3.1--表达式1

3.3.2--表达式2

3.3.3-表达式3

3.3.4--表达式4

3.3.5--表达式5


往期回顾:

【C语言操作符详解(一)】--进制转换,原反补码,移位操作符,位操作符,逗号表达式,下标访问及函数调用操作符

前言:上篇文章给大家分享了操作符详解的部分知识点,这篇文章将继续分享操作符详解的剩余知识点,当然还有部分知识点会穿插在后续的知识学习中。


 一.结构成员访问操作符

1.1--结构体

--C语言提供了内置类型,如:int char等,但是只有这些内置类型是不够的,假如我想描述一个具体的东西,比如一本书,一个学生,就得从多个方面来描述,这时单一的内置类型是不行的;在C语言中为了解决这个问题,增加了结构体这种自定义的数据类型,让程序员可以自己创造适合的类型;

结构体是一些值的集合,这些值称为变量。结构体的每个成员都可以是不同类型的变量,如:标量,数组,指针,甚至是其它结构体。

1.1.1--结构的声明

struct tag
{
    member-list;
}variable-list;
// variable-list 可有可无,这里就是声明类型的时候可以同时定义一个变量。
//这里定义的变量是全局变量。

比如:描述一个学生

struct Stu
{
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}; //分号不能丢

1.1.2--结构变量的定义和初始化 

代码1:变量的定义

struct Point
{
	int x;
	int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2

代码2:初始化

struct Point p3 = { 10, 20 };

struct Stu //类型声明
{
	char name[15];//名字
	int age; //年龄
};
struct Stu s1 = { "zhangsan", 20 };//初始化
struct Stu s2 = { .age = 20, .name = "lisi" };//指定顺序初始化

代码3:嵌套初始化 

struct Peo
{
	char name[30];
	int age;
	char tele[12];
};

struct Ebook
{
	struct Peo data[100];//可以存放100个人的消息
	int count;//当前已存的个数
};

int mian()
{
	struct Peo p1 = { "zhangsan",20,"15598875462" };
	struct Ebook eb = { {{"wangwu",19,"13564562077"},{"cuihua",18,"15827602564"}},0 };
//结构体嵌套初始化

	return 0;
}

1.2.结构成员访问操作符--直接访问 

--结构体成员的直接访问是通过点操作符( . ) 访问的。点操作符接受两个操作数,像上图所示代码,我们该如何将信息打印出来呢,如下所示:

//打印zhangsan的信息
printf("%s\n", p1.name);
printf("%s\n", p1.age);
printf("%s\n", p1.tele);


//打印cuihua的信息
printf("%s\n", eb.data[1].name);
printf("%s\n", eb.data[1].age);
printf("%s\n", eb.data[1].tele);

使用方法:结构体变量 . 成员名 


二.操作符的属性:优先级,结合性

--C语言的操作符有两个重要是属性:优先级,结合性,这两个属性决定了表达式求值的计算顺序

2.1--优先级

--优先级指的是,如果一个表达式包含多个运算符,哪个运算符应该优先执行。各种运算符的优先级是不一样的。

 3 + 4 * 5;

上述例子中,表达式 3 + 4 * 5里面既有加法运算符(+),又有乘法运算符 (*)。由于乘法的优先级高于加法,所以会先计算4 * 5,而不是先计算3+4。

2.2--结合性

--如果两个运算符优先级相同,优先级就没法确定先算哪个了,这时候就看结合性了,则根据运算符是左结合,还是右结合,决定执行顺序。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符(=)。

 5 * 6 / 2;

上述例子中,* 和 / 的优先级相同,它们都是左结合运算符,所以从左到右执行,先计算5 * 6,再计算 /2。

 2.3--运算符的优先级顺序总结

--运算符的优先级顺序很多,下面是部分运算符的优先级顺序(按照优先级从高到低排列),建议大概记住这些操作符的优先级就行了,其它操作符要使用的时候查看下表就可以了。

~圆括号(())

~自增运算符(++),自减运算符(--)

~单目运算符(+( 正) 和 - (负))

~乘法(*),除法(/)

~加法(+),减法(-)

~关系运算符(<,> 等)

~赋值运算符(=)

由于圆括号的优先级最高,可以使用它改变其他运算符的优先级。


 三.表达式求值

3.1--整型提升

--C语言中整型算术运算总是至少以缺省 (默认) 整型类型的精度来进行的。为了获取这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升

整型提升的意义:

表达式的整型运算要在CPU的相应运算器材内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度;因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。

通用CPU是难以直接实习两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送去CPU去执行运算。

//实例1
char a,b,c;
...
a = b + c;

b和c的值被提升为普通整型,然后再执行加法运算,加法运算完成之后,结果将被截断,然后再存储于a中。

那么我们该如何进行整型提升呢? 

1.有符号整数提升是按变量的数据类型的符号位来提升的

2.无符号整数提升,高位补0

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

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

//⽆符号整形提升,⾼位补0

积累了上面整型提升的相关知识之后,让我们来通过一串代码以及它的注释解析来更直观确切的了解一下把~

#include<stdio.h>

int main()
{
	char a = 20;
	//由于20本来是整型,是32位二进制,现在存储在char类型的变量中,要先截断成8位后再存储到a中
	//00000000 00000000 00000000 00010100
	//00010100 --a截断后
	char b = 120;
	//同理可知,要先截断后再存储到b中
	//00000000 00000000 00000000 01111000
	//01111000 --b截断后
	char c = a + b;
	//这里要先将a和b的值提升为普通整型,然后再进行加法运算
	// 00010100 -a
	// 00000000 00000000 00000000 00010100
	// 01111000 -b
	// 00000000 00000000 00000000 01111000
	// 
	//  00000000 00000000 00000000 00010100
	//+ 00000000 00000000 00000000 01111000
	//= 00000000 00000000 00000000 10001100 --c截断前
	//运算完成后,结果将被截断,然后再存储于c中。
	//10001100 --c截断后
	//
	printf("%d\n", c);
	//%d - 以10进制的形式,打印一个有符号整型(int)
	//先将c整体提升
	// 11111111 11111111 11111111 10001100--补码
	// 10000000 00000000 00000000 01110011
	// 10000000 00000000 00000000 01110100-原码
	//打印出来的是:-116
	return 0;
}

3.2--算术转换

--如何某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数转换为另一个操作数的类型,不然操作就无法执行。下面的层次体系称为寻常算术转换

long double
double
float
unsigned long int
long int
unsigned int
int

如果某个操作数的类型在上面这个列表中排名靠后,那么首先要转换为另一个操作数的类型后再执行运算。

 3.3--问题表达式解析

3.3.1--表达式1

//表达式的求值部分由操作符的优先级决定。
//表达式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

3.3.2--表达式2

//表达式2
c + --c;
//假设c=5
//如果左操作数的获取在右操作数之前则结果为 5+4=9;
//如果左操作数的获取在右操作数之后则结果为 4+4=8;

同上,操作符的优先级只能决定自减--的运算在+运算的前面,但我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。

3.3.3-表达式3

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

表达式3在不同编译器中测试结果:非法表达式程序的结果:

3.3.4--表达式4

#include <stdio.h>
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();中我们只能通过操作符的优先级得知:先算乘法,再算减法。函数的调用先后顺序无法通过操作符的优先级确定。

3.3.5--表达式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;
}
//尝试在linux 环境gcc编译器,VS2013环境下都执⾏,看结果。

运行之后,我们可以发现在gcc中的结果是 10 4;在vs2022中是 12 4。

这个代码中的第一个+在执行的时候,第三个++是不是执行,这个是不确定的。因为依靠操作符的优先级和结合性是无法决定第一个+和第三个前置++的先后顺序。

总结:即便有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在潜在风险的,建议不要写出特别复杂的表达式。


结语:这篇文章就到此结束了,还有像单目操作符中的&和*以及结构成员访问操作符-间接访问等这些操作符没有给大家讲解的,都会在后续的知识学习中分享到的,感谢大家的支持

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值