各位少年,大家好,我是博主那一脸阳光
,今天分享结构体的使用,声明以及操作符的使用。
前言:
如果你想做一块月饼,你要学习它的制作方法,需要准备面粉,白砂糖,奶粉,干果、坚果,冰皮月饼等材料,但我们还需要一个摸具来造成出原型的月饼。
根据我上面的描述大家明白了,做月饼不仅材料和馅料还需要个工具叫做容具,看上图半成品是到了容具之后出现了对应图形,那我们介绍这个有什么用呢?当然有用struct就是相当于一个容具的作用。
struct tag\\(结构体名字) { member-list;\\(成员列表) }variable-list;\\(变量列表)
结构体的意义
C语⾔已经提供了内置类型,如:char、short、int、long、float、double等,但是只有这些内置类
型还是不够的,假设我想描述学⽣,描述⼀本书,这时单⼀的内置类型是不⾏的。描述⼀个学⽣需要
名字、年龄、学号、⾝⾼、体重等;描述⼀本书需要作者、出版社、定价等。C语⾔为了解决这个问
题,增加了结构体这种⾃定义的数据类型,让程序员可以⾃⼰创造适合的类型。
结构是⼀些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量,如: 标量、数组、指针,甚⾄是其他结构体
代码例子
大家看到了结构体肯定想到了一个项目,叫做学生管理系统,今天我给大家分享其中较为重要一点和其制作的思路,好,我们先制作一个模板叫做学生,一个得有名字,学号,年龄,性别。
struct Stu { char name[20];//名字 int age;//年龄 char sex[5];//性别 char id[20];//学号 }; //分号不能丢
#include<stdio.h> struct Stu { char name[20]; int age; float score; }s3={"wangwu",33,66.0f),s4={"翠华",18,100.0}; int main() { struct Stu s1 = { "aoao",20,95.5f};//局部变量 return 0; }
上面代码中,我们成功的从Stu这个摸具 来创建了一个我们这个代码,但是我们惊讶的发现,我们通过结构体创建了类似数组的东西,这东西就叫做结构体变量
。
结构体成员的直接访问
结构体成员的直接访问是通过点操作符(.)访问的。点操作符接受两个操作数。比如说我们要先输入学生的成绩或者学好,怎么办呢?这时候我们就需要(.)操作符了。使用如下
`
#include<stdio.h> struct Stu { char name[20]; int age; float score; }s3={"wangwu",33,66.0f),s4={"翠华",18,100.0}; int main() { struct Stu s1 = { "aoao",20,95.5f};//局部变量 struct Stu s5=(.score=98.5f, .name"hehe", .age=100.0); return 0; }
结构体嵌套
#include<stdio.h> struct Point { int x; int y; }; struct Data { int num; struct Point p; }; int main() { struct Data d={200,{10,11}};//这块的大括号都是需要用的 printf("numb=%d p.x=%d p.y=%d\n",d.num,d.p.x,d.p.y);//嵌套打印的方法 return 0; }
.结构成员访问操作符 结构体变量成员名
结构体成员的指针
有时候我们得到的不是一个结构体变量,而是得到一个结构体的指针,如下展示。
struct Stu* ps = &s1; printf("%s %d %f", ps->name, ps->age, ps->score);
指针是什么?
指针另外一个名字叫做地址,和现实中的地址有异曲同工之妙,
比如说某个学校在长江路307号,这很显然就是个地址,哪计算机中的地址也是一样的。
上面代码中:取了s1的地址 &(取地址操作符),然后通过->打印出结构体出来。
#include <stdio.h> #include <string.h> struct Stu { char name[15];//名字 int age; //年龄 }; void print_stu(struct Stu s) { printf("%s %d\n", s.name, s.age); } void set_stu(struct Stu* ps)接收参数 { strcpy(ps->name, "李四"); ps->age = 28; } int main() { struct Stu s = { "张三", 20 }; print_stu(s); set_stu(&s);//传参 print_stu(s); return 0; }
上面代码中,通过指针传参的情况下传递了结构体,
哪自然打印的时候也需要->这个操作符来进行打印以及使用。
操作符的优先级结合性
C语⾔的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。
例子
3+4*5
上⾯⽰例中,表达式 3 + 4 * 5 ⾥⾯既有加法运算符( + ),⼜有乘法运算符( * )。由于乘法
的优先级⾼于加法,所以会先计算 4 * 5 ,⽽不是先计算 3 + 4 。
优先级:(相邻操作符),优先级高的先执行,优先级低的后执行!
结合性
如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符
是左结合,还是右结合,决定执⾏顺序。⼤部分运算符是左结合(从左到右执⾏),少数运算符是右
结合(从右到左执⾏),⽐如赋值运算符( =
)
。
奉上一个C/C++中文版的官网,上面有函数和操作符优先级:https://zh.cppreference.com/w/c/language/operator_precedence
- 和 / 的优先级相同,它们都是左结合运算符,所以从左到右执⾏,先计算 5 * 6 ,
再计算 6 / 2 。
运算符的优先级顺序很多,下⾯是部分运算符的优先级顺序(按照优先级从⾼到低排列),建议⼤概
记住这些操作符的优先级就⾏,其他操作符在使⽤的时候查看下⾯表格就可以了。
• 圆括号( () )
• ⾃增运算符( ++ ),⾃减运算符( – )
• 单⽬运算符( + 和 - )
• 乘法( * ),除法( / )
• 加法( + ),减法( - )
• 关系运算符( < 、 > 等)
• 赋值运算符( = )
由于圆括号的优先级最⾼,可以使
⽤它改变其他运算符的优先级
表达式求值
整型提升
C语⾔中整型算术运算总是⾄少以缺省整型类型的精度来进⾏的。 为了获得这个精度,表达式中的字符和短整型操作数在使⽤之前被转换为普通整型,这种转换称为整 型提升。
(简单意思就是说int类型大小是四个字节,char类型大小一个字节,short类型两个字节,但是编译器都会按int类型大小进行计算)就好比没成年不能进网吧道理一样,最后即使你进去会不会因为不够年龄被踢出来呢?我们接着往下看。
整形提升的意义
表达式的整型运算要在CPU的相应运算器件内执⾏,CPU内整型运算器(ALU)的操作数的字节⻓度⼀
般就是int的字节⻓度,同时也是CPU的通⽤寄存器的⻓度。
因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准⻓ 度。
通⽤CPU(general-purpose CPU)是难以直接实现两个8⽐特字节直接相加运算(虽然机器指令中
可能有这种字节相加指令)。所以,表达式中各种⻓度可能⼩于int⻓度的整型值,都必须先转换为 int或unsigned
int,然后才能送⼊CPU去执⾏运算。
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
算术转换
如果某个操作符的各个操作数属于不同的类型,那么除⾮其中⼀个操作数的转换为另⼀个操作数的类
型,否则操作就⽆法进⾏。下⾯的层次体系称为寻常算术转换。
long double double float unsigned long int long int unsigned int int
如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏
运算。
问题表达式解析
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
`
//表达式2
c + --c; ``
同上,操作符的优先级只能决定⾃减 – 的运算在 + 的运算的前⾯,但是我们并没有办法得知, + 操
作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。`
表达式3
``
int main() { int i = 10; i = i-- - --i * ( i = -3 ) * i++ + ++i; printf("i = %d\n", i); return 0; }
表达式3在不同编译器中测试结果:⾮法表达式程序的结果
表达式4
#include <sdtio.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(); 中我们只能通过操作符的优先级得知:先
算乘法,再算减法。
函数的调⽤先后顺序⽆法通过操作符的优先级确定。
表达式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编译器执⾏结果:
VS2022运⾏结果:
看看同样的代码产⽣了不同的结果,这是为什么?
简单看⼀下汇编代码,就可以分析清楚.
这段代码中的第⼀个 + 在执⾏的时候,第三个++是否执⾏,这个是不确定的,因为依靠操作符的优先
级和结合性是⽆法决定第⼀个 + 和第三个前置 ++ 的先后顺序。
总结
即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的
计算路径,那这个表达式就是存在潜在⻛险的,建议不要写出特别负责的表达式。