1.啥是共用体?和结构体有什么区别?
1、共用体的概念:不同类型的变量共享一块内存空间
2、共用体与结构体的区别:
- 相同点:共用体和结构体一样,都是构造类型,长的差不多。
- 不同点:
- 结构体变量中的成员变量有各自单独空间。共用体变量中的成员变量共享空间,空间大小由最大类型确定。
- 结构体变量中的成员变量互不影响,但是对共用体变量中的成员变量赋值会导致覆盖。
2.共用体变量的四要素
与普通变量的四要素完全相同。
1、共用体变量的类型:也就是共用体,它是我们自创的类型,也是构造类型。它的声明通常不放在函数中,书写的方式就像全局变量,因为它要尽可能让程序中的所有位置都能使用这个类型声明。共用体又有三部分:
- union关键字:表示构造的是共用体。
- 共用体类型名:和结构体一样,编程习惯是用大写开头,小写开头虽然不出错,就是看起来太low了。
- 共用体成员列表:和结构体一样,成员列表也称为域表,通常我们称结构体成员列表(域表)中的每个成员为成员变量(域),成员变量之间用分号隔开。你可以定义许多成员变量,但并不是每一个选项在具体使用中会被派上用场,不要给成员变量赋值,虽然不出错,但是看起来太low了。
union Test //union是关键字,Test是自己取的共用体类型名 { //花括号中的是共用体成员列表 char cdata; int idata; double ddata; }; //从此以后,我们构造了一个共用体,它是 union Test 类型。
1、共用体的知识点:
- 共用体声明时,末尾加分号;
- 共用体这个构造类型,决定了共用体变量占用空间的大小,系统会在程序运行或编译阶段给你静态分配好内存。可以肯定的是:空间大小由最大类型来确定确定。
- 与int a; 中 类型名是int一个道理,这里union Test 在概念上就相当于 int。
2、共用体变量的名字:是共用体变量定义时候的名字,与 int a; 中变量名是a一个道理。
3、共用体变量的地址:显然这就是指向共用体变量的指针。值得注意的是,共用体变量的地址和它的各成员的地址都是同一个地址。
4、结构体变量的值:这里指的是共用体变量中成员变量的值,由于共用一块内存,它们的值也会相互影响。
3.共用体变量的定义(测一下占用空间)
1、知识点:定义的方式也和结构体一样,有标准的一种,不推荐的两种。同时,共用体变量的占用空间大小是成员变量里占用空间最大的确定的。
#include <stdio.h>
union Test
{
char cdata;
int idata;
double ddata;
};
int main(int argc, char const *argv[])
{
union Test t1; //t1就是union Test类型的共用体变量
int sizeOfTest = sizeof(union Test);
printf("共用体占用内存大小:%d\n", sizeOfTest); //8,因为最大的成员占8个字节
return 0;
}
2、两种不推荐的定义方式:
- 声明共用体,同时定义共用体变量:后面照样可以定义其他共用体变量,但这个方法尽量少用
union Test { char cdata; int idata; double ddata; } t1, t2;
- 声明共用体时不写类型名,必须同时定义共用体变量:少用,因为后面无法再拿它来定义其他共用体变量。有时候我们这么用是在结构体中又套了一层共用体的情况(见习题1),那么内层的共用体可以不写类型名,因为我们不关心它。
union { char cdata; int idata; double ddata; } t1, t2;
4.如何引用共用体变量中的成员变量(测一下地址)
与结构体一样,都是通过点运算符'.'。共用体变量名.成员变量名
1、知识点:共用体变量中所有成员的地址都相同
#include <stdio.h>
union Test
{
char cdata;
int idata;
double ddata;
};
int main(int argc, char const *argv[])
{
union Test t1;
printf("ti的地址 :0x%p\n", &t1);
printf("cdata的地址:0x%p\n", &t1.cdata);
printf("idata的地址:0x%p\n", &t1.idata);
printf("ddata的地址:0x%p\n", &t1.ddata);
return 0;
}
2、知识点:由于共用一块内存,所以给一个共用体变量中的成员变量赋值,其他成员变量的值会被覆盖。(根据变量值是从低地址开始存放的特点,可以自己计算一下覆盖后是什么值)
#include <stdio.h>
union Test
{
char cdata;
int idata;
double ddata;
};
int main(int argc, char const *argv[])
{
union Test t1;
t1.idata = 10;
t1.cdata = 'a'; //'a'会把10给覆盖掉,很明显的是'a'的ASCII码是97
printf("idata=%d\n", t1.idata);
t1.idata = 0x12345678;
t1.cdata = 0xcc; //cc会把78给覆盖掉
printf("idata=0x%x\n", t1.idata);
return 0;
}
5.共用体变量的初始化(只能给其中一个成员初始化)
1、知识点:如果给结构体变量初始化,那么只有第一个值有效,后面的均无效(这里没有覆盖)。且运行时会警告你有多余元素。
#include <stdio.h>
union Test
{
char cdata;
int idata;
double ddata;
};
int main(int argc, char const *argv[])
{
union Test t1 = {'a', 10, 10.5}; 只有第一个值'a'是有效的,后面均无效,且报错
printf("cdata=%c\n", t1.cdata);
printf("idata=%d\n", t1.idata);
printf("ddata=%lf\n", t1.ddata);
return 0;
}
6.习题
代码心得:
- 涉及到前后多个键盘输入时,要格外注意scanf、gets这些函数特点,判断它们:能否跳过第一个空格、回车、Tab键?能否吃掉输入结束的回车键?
- 涉及到输入时,如果你程序中有地方写错了,程序崩溃且不报错,那么就用gdb测试,①如果发现段错误就可能是没加取地址符号;②如果出现了一些看不懂的报错信息,那么多半是scanf中的占位符使用出错,比如说把%符号写成了&等其他符号;③更离谱的情况是,你输入的数据和占位符不匹配。
- 涉及到输出时,如果你程序中有地方写错了,程序崩溃且不报错,那么就用gdb测试,如果输出结果不对,那么多半是printf中的占位符和输出类型不匹配,比如说将字符型输出时,你却用%s。程序就会崩溃,但是用gdb测试就能看出确实写错了。
习题1:
- 思路:结构体中有联合体(同一个表格中有两类群体,导致表格中某列的数据类型不能统一,但是又舍不得浪费内存)。我们发现:对于学生和老师来说,性别、号码、性别、职业这四项是相同的(通用的,老师或者学生的这些数据的类型是一致的),不同点在于班级和职务(学生的班级是整型的,而老师的职务是字符串型的)。所以定义一个结构体,成员列表中有性别、号码、性别、职业;再定义一个共用体,成员列表中有班级和职务;最后把共用体变量作为结构体成员列表中的一员,也就是放在一个表格中处理。初始化这个表格的突破口:通过职业这个成员变量,确定这个人是老师还是学生,然后把共用体变量给初始化了,是学生就输入班级,是老师就输入职务,其他字段需要的话也可以初始化。
声明一个结构体(内含共用体): struct Person { char sex; char career; char phone[12]; char name[32]; union //共用体的类型名可以不写,因为不关心 { int class; char subject[12]; } mes; }; 1. 定义一个结构体数组,同一个表格中有学生和老师两类群体,不妨设为: struct Person p[3]; 2. 用sizeof关键字计算出结构体数组长度,保存在变量len中: int len = sizeof(p) / sizeof(p[0]); 3. for循环(注意这里不写表达式3),代表结构体数组下标(第i个人)的变量i从0开始, <len 时,进入循环 //内在逻辑1:初始化每个人的信息,另外通过职业这个成员变量,确定这个人是老师还是学生 //内在逻辑2:不写表达式3: i++ 的原因是我们要用continue语句,如果表达式3写在上面,continue语句失效 3.1 输入第i+1个人的职业,保存在p[i].career中 3.2 判断第i+1个人职业是否是's' 3.2.1 如果是,说明是学生 3.2.1.1 输入学生的班级,保存在p[i].mes.class中 3.2.1.2 初始化学生的其他信息 3.2.2 否则,判断第i+1个人的职业是否是't' 3.2.2.1 如果是,说明是老师 3.2.2.1.1 输入老师教的科目,保存在p[i].mes.subject中 3.2.2.1.2 初始化老师的其他信息 3.2.3 否则,表示输入错误 3.2.3.1 用continue语句提前退出本次循环 3.3 把表达式3写在这个位置(continue语句之后): i++; 4. for循环,显示每个人信息,代表结构体数组下标(第i个人)的变量i从0开始, <len 时,进入循环 4.1 显示结构体变量p[i]中普通变量的信息 4.2 通过对p[i].career的判断,选择显示结构体变量p[i]中共用体变量mes的信息
- 代码:
#include <stdio.h> struct Person { char sex; char career; char phone[12]; char name[32]; union { int class; char subject[12]; } mes; }; int main(int argc, char const *argv[]) { struct Person p[3]; //结构体数组,同一个表格中有学生和老师两类群体 int i; int len = sizeof(p) / sizeof(p[0]); for(i=0; i<len; ){ printf("请输入第%d个人的职业:用t代表老师,s代表学生\n", i+1); scanf("%c%*c", &(p[i].career)); if(p[i].career == 's'){ printf("第%d个人是学生,请输入学生的班级:\n", i+1); scanf("%d%*c", &(p[i].mes.class)); //注意:只写%d的话,回车符还留在键盘缓冲区中,考虑到gets函数没法跳过第一个回车符,所以会导致输入名字时失败 puts("请输入学生名字"); gets(p[i].name); }else if(p[i].career == 't'){ printf("第%d个人是老师,请输入老师的学科:\n", i+1); gets(p[i].mes.subject); puts("请输入老师名字"); gets(p[i].name); }else{ printf("职业输入错误,请重新输入\n"); continue; } i++; } for(i=0; i<len; i++){ printf("姓名:%s,职业:%c,", p[i].name, p[i].career); if(p[i].career == 's'){ printf("班级:%d班\n", p[i].mes.class); }else{ printf("学科:%s\n", p[i].mes.subject); } } return 0; }