九、结构体、联合体和枚举
复合类型也是一种数据类型,是一种自定义类型。常见的复合类型有结构体、枚举、联合体。
9.1 结构体
9.1.1 概述
将不同类型的数据组合成一个有机的整体,如:一个学生有学号/姓名/性别/年龄/地址等属性。显然单独定义以上变量比较繁琐,数据不便于管理,C语言中给出了另一种构造数据类型——结构体。用关键字struct。
//一般表示法
int studentNumber;
char name[21];
char sex;
struct student{//结构体表示方法,以下是结构体成员
int studentNumber; //整形:学号
char name[21]; //字符数组:名字
char sex; //字符型:性别
}stu;//结构体变量
9.1.2 结构体变量的定义和初始化
定义结构体变量的方式:
方法①:先定义结构体类型,再定义变量名(推荐写法)。
方法②:在定义结构体类型的同时定义变量。
方法③:直接定义结构体类型变量(无类型名),但是第三种写法一定要在定义结构体后面定义结构体变量,否则再主函数将无法创建结构体变量(不推荐写法)。
结构体类型和结构体变量关系:
结构体类型:指定了一个结构体类型,它相当于一个模型,但其中并无具体数据,系统对之也不分配实际内存单元。
结构体变量:系统根据结构体类型(内部成员状况)为之分配空间。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
struct stu1 {//方法1:先定义结构体类型,再定义变量并同时初始化(常用)
char name[51]; //多出1存\0
int age;
};
struct stu1 s1 = { "Bryant", 41 };
struct stu1 s2 = { "Jordan", 45 };
struct stu2 {//方法2:在定义结构体类型同时定义变量并初始化
char name[51];
int age;
}s3 = { "Durant", 33 }, s4 = { "Harden", 30 };
struct stu3 {//方法3:定义结构体类型,再创建结构体变量,最后逐一初始化
char name[51];
int age;
};
int main() {
struct stu3 s5;
//s5.name = "黑马程序员";//因为name是数组名,是常量
strcpy(s5.name, "曹操");//通过strcpy赋值
s5.age = 62;
struct stu3 s6;
strcpy(s6.name, "刘备");
s6.age = 68;
printf("s5.name :%s\ns6.age :%d\n", s5.name, s6.age);
struct stu3 s7;
scanf("%s %d", s7.name, &s7.age);//通过键盘获取
printf("s7.name :%s\ns7.age :%d\n", s7.name, s7.age);
return 0;
}
9.1.3 结构体数组
struct student{
char name[21];//偏移到4的倍数,即偏移4*6-21=+3
int age;//整型最大不偏移,即偏移4*1-4=0
char sex;//偏移到4的倍数,即偏移4*1-1=+3
int score[3];//整型最大不偏移,即偏移4*3-12=0
char address[51];//偏移到4的倍数,即偏移4*13-51=+1
};//按道理结构体大小应该为21+4+1+12+51=89
//由于结构体成员需要根据最大数据类型进行偏移对齐+7
int main(){
struct student stu[] = {//结构体数组
{ "曹操", 62, 'M', 69, 83, 82, "许昌" },
{ "貂蝉", 18, 'F', 55, 60, 65, "徐州" },
{ "刘备", 68, 'M', 80, 73, 88, "成都" },
{ "孙权", 48, 'M', 63, 72, 72, "建康" }
};
printf("结构体数组大小:%d\n", sizeof(stu));//384
printf("结构体元素大小:%d\n", sizeof(stu[0]));//96
printf("结构体元素大小:%d\n", sizeof(struct student));//96,把结构体当作数据类型
printf("结构体元素个数:%d\n", sizeof(stu) / sizeof(stu[0])); //4
printf("%d\n", sizeof(stu->address)); //51
for (size_t i = 0; i < sizeof(stu) / sizeof(stu[0]); i++)
printf("name:%s age:%d sex:%s C:%d java:%d python:%d address:%s\n", stu[i].name, stu[i].age, stu[i].sex == 'M' ? "男" : "女", stu[i].score[0], stu[i].score[1], stu[i].score[2], stu[i].address);
for (size_t i = 0; i < sizeof(stu) / sizeof(stu[0]) - 1; i++)//根据age进行排序
for (size_t j = 0; j < sizeof(stu) / sizeof(stu[0]) - i - 1; j++){
if (stu[j].age > stu[j + 1].age){
struct student temp = stu[j];
stu[j] = stu[j + 1];
stu[j + 1] = temp;
}
}
for (size_t i = 0; i < sizeof(stu) / sizeof(stu[0]); i++)
printf("name:%s age:%d sex:%s C:%d java:%d python:%d address:%s\n", stu[i].name, stu[i].age, stu[i].sex == 'M' ? "男" : "女", stu[i].score[0], stu[i].score[1], stu[i].score[2], stu[i].address);
return 0;
}
9.1.4 结构体套结构体
struct score{
int c;
int java;
int python;
};//子结构体必须定义在父结构体上
struct student{
char name[21];
int age;
char sex;
struct score s;//这里要使用数据类型,而不是结构体类型,因为要给它赋值,由于struct score的内容都是整型,可以用数组代替
char address[51];
};//父结构体
int main(){
struct student stu = { "曹操", 62, 'M', 69, 83, 82, "许昌" };
printf("sizeof(stu)=%d\n", sizeof(stu));//96
printf("sizeof(stu.s)=%d\n", sizeof(stu.s)); //12
printf("sizeof(stu.name)=%d\n", sizeof(stu.name)); //21
printf("name:%s age:%d sex:%c C:%d Java:%d Python:%d address:%s\n", stu.name, stu.age, stu.sex, stu.s.c, stu.s.java,stu.s.python, stu.address);
return 0;
}
9.1.5 结构体和指针
9.1.5.1 指针跟结构体变量建立关系
struct student{
char name[21];
int age;
int score[3];
char address[51];
};
int main(){
//如果是普通变量,通过点运算符操作结构体成员
//如果是指针变量,通过->操作结构体成员
struct student stu = { "曹操", 62, 69, 83, 82, "许昌" };
struct student *p = &stu;//指针跟结构体变量建立关系,适用于结构体变量占用内存大的时候,可用指针替代变为4字节大小,节省内存使用。
printf("name:%s age:%d C:%d Java:%d Python:%d address:%s\n", (*p).name, (*p).age, (*p).score[0], (*p).score[1], (*p).score[2], (*p).address);
printf("name:%s age:%d C:%d Java:%d Python:%d address:%s\n", p->name, p->age, p->score[0], p->score[1], p->score[2], p->address);//跟上行代码等价
return 0;
}
9.1.5.2 结构体套一级指针
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct student{
char* name;
int age;
int* score;
char* address;
};//结构体成员含有指针类型的定义
int main(){
//struct student stu = {"曹操",62,69,83,82,"许昌"};//由于score是指针,不能赋值整型,此方法赋值不妥,所以初始化需要开辟堆区分布赋值
struct student stu;
printf("sizeof(stu)=%d\n",sizeof(stu));//16,比原来结构体小了许多
stu.name = (char*)malloc(sizeof(char)* 21);//一定要开辟堆空间存放对应内容
stu.score = (int*)malloc(sizeof(int)* 3); //不写此3句会报使用未初始化stu的错误
stu.address = (char*)malloc(sizeof(char)* 51);
strcpy(stu.name, "曹操");
stu.age = 18;
stu.score[0] = 69;
stu.score[1] = 83;
stu.score[2] = 82;
strcpy(stu.address, "许昌");
printf("name:%s age:%d C:%d Java:%d Python:%d address:%s\n", stu.name, stu.age, stu.score[0], stu.score[1], stu.score[2],stu.address);
free(stu.name);
free(stu.score);
free(stu.address);
return 0;
}
或者
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct student {
char* name;
int age;
int* score;
char* address;
};//结构体成员含有指针类型的定义、初始化:开辟堆区分布赋值
int main() {
//struct student stu = {"曹操",62,69,83,82,"许昌"};//由于score是指针,不能赋值整型,此方法赋值不妥
struct student stu;
printf("sizeof(stu)=%d\n", sizeof(stu));//16,比原来结构体小了许多
stu.score = (int*)malloc(sizeof(int) * 3); //不写此句会报使用未初始化stu的错误
stu.name = "曹操"; //name是指针,不像前面的name是固定数组的首地址,可以修改,不报错。
stu.age = 18;
stu.score[0] = 69;
stu.score[1] = 83;
stu.score[2] = 82;
stu.address = "许昌";
printf("name:%s age:%d C:%d Java:%d Python:%d address:%s\n", stu.name, stu.age, stu.score[0], stu.score[1], stu.score[2], stu.address);
free(stu.score);
return 0;
}
9.1.5.3 结构体指针与结构体成员是指针
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#define n 3
struct student{//有结构体指针,又有结构体成员指针
char* name;
int age;
int* score;
char* address;
};
int main(){
struct student *p = (struct student*)malloc(sizeof(struct student) * n);//开辟堆空间存储结构体指针
for (size_t i = 0; i < n; i++){//开辟堆空间存储结构体成员指针
p[i].name = (char*)malloc(sizeof(char)* 21);//等价(p+i)->name
p[i].score = (int*)malloc(sizeof(int)* 3);
p[i].address = (char*)malloc(sizeof(char)* 51);
}
for (size_t i = 0; i < n; i++)
scanf("%s %d %d %d %d %s", p[i].name, &p[i].age, &p[i].score[0], &p[i].score[1], &p[i].score[2], p[i].address);
for (size_t i = 0; i < n; i++)
printf("name:%s age:%d C:%d Java:%d Python:%d address:%s\n", p[i].name, p[i].age, (p + i)->score[0], (p + i)->score[1], (p + i)->score[2], p[i].address);
for (size_t i = 0; i < n; i++){//先释放结构体指针指向存储数据的堆空间
free(p[i].name);
free(p[i].score);
free(p[i].address);
}
free(p);//再释放存放结构体指针的堆空间
return 0;
}
/***********************
复制以下信息到终端
曹操 62 69 83 82 许昌
刘备 68 80 73 88 成都
孙权 48 63 72 72 建康
***********************/
9.1.6 结构体做函数参数
9.1.6.1 结构体普通变量做函数参数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
struct student {
char name[21];
int age;
};
void fun(struct student s) { //值传递
strcpy(s.name, "刘备");
s.age = 68;
printf("name:%s age:%d \n", s.name, s.age); //刘备 68
}
int main() {
struct student stu = { "曹操", 62 };
fun(stu);
printf("name:%s age:%d \n", stu.name, stu.age); //曹操 62
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
struct student {
char* name; //地址传递
int age; //值传递
};
void fun(struct student s) {//stu.name属于地址传递,stu.age是值传递
//s.name = (char*)malloc(sizeof(char)* 21); //如果在调用函数中开辟堆空间再来存储name,即使是地址传递也不影响值发生改变,因为开辟新的堆空间又修改了传过来的地址,且内存没有初始化导致输出是乱码
strcpy(s.name, "孙权"); //把地址里面的数修改
s.age = 48;
printf("name:%s age:%d\n", s.name, s.age); //孙权 48
}
int main() {
struct student stu = { NULL, 62 };//NULL其实是0x00,4字节,所以接下来在主函数中要开辟空间存储name
stu.name = (char*)malloc(sizeof(char) * 21);//结构体变量指针成员需要开辟空间,stu.name的首地址从原来的0x00变成是开辟堆空间的首地址,里面的内容乱码是因为没有初始化内存。
fun(stu);
printf("name:%s age:%d\n", stu.name, stu.age); //孙权 62
return 0;
}
9.1.6.2 结构体指针变量做函数参数
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
struct student{
char name[21];
int age;
};
void fun(struct student* s){
strcpy(s->name, "曹丕");
s->age = 48;
printf("name:%s age:%d\n", s->name, s->age); //曹丕 48
}
int main(){
struct student stu = { "曹操", 62};
fun(&stu); //地址传递
printf("name:%s age:%d \n", stu.name, stu.age); //曹丕 48
return 0;
}
9.1.6.3 结构体数组名做函数参数
struct student{
char name[21];
int age;
};
void BubbleSort(struct student* s, int length){//结构体数组名做参数降为指针
for (size_t i = 0; i < length - 1; i++)
for (size_t j = 0; j < length - 1 - i; j++){
if ((s + j)->age >(s + j + 1)->age){
//if (s[j].age > s[j+1].age){
struct student temp = s[j];
s[j] = s[j + 1];
s[j + 1] = temp;
}
}
}
int main(){
struct student stu[] = { { "曹操", 62 },{ "貂蝉", 18 },{ "刘备", 68 } };
BubbleSort(stu, sizeof(stu) / sizeof(stu[0]));
for (size_t i = 0; i < sizeof(stu) / sizeof(stu[0]); i++)
printf("name:%s age:%d\n", stu[i].name, stu[i].age);
return 0;
}
9.1.6.4 const修饰结构体指针形参变量
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
struct student {
char name[21];
int age;
};
int main01() {//const修饰结构体指针类型
struct student stu1 = { "曹操", 62 };
struct student stu2 = { "貂蝉", 18 };
const struct student* p = &stu1;
//p->age = 88; //error
//strcpy(p->name, "曹丕"); //ok
p = &stu2; //ok
printf("name:%s age:%d \n", p->name, p->age);//貂蝉 18
return 0;
}
int main02() {//const修饰结构体指针变量
struct student stu1 = { "曹操", 62 };
struct student stu2 = { "貂蝉", 18 };
struct student* const p = &stu1;
strcpy(p->name, "曹丕");
//p = &stu2;//error
printf("name:%s age:%d \n", p->name, p->age); //曹丕 62
return 0;
}
int main() {//const修饰结构体指针类型和结构体指针变量
struct student stu1 = { "曹操", 62 };
struct student stu2 = { "貂蝉", 18 };
const struct student* const p = &stu1;
//p->age = 88; //error
//p = &stu2; //error
strcpy(p->name, "刘禅");//这样可以修改,是因为name是数组名.不能用p->方法修改,只能用strcpy
struct student** pp = &p;
(*pp)->age = 88; //等价于(**pp).age = 88;
printf("name:%s age:%d\n", p->name, p->age); //刘禅 88
*pp = &stu2;
printf("name:%s age:%d\n", p->name, p->age); //貂蝉 18
return 0;
}
9.2 共用体(联合体)
9.2.1 概述
联合体在C语言用的比较少,但在其他语言经常使用。在前端中有一种var类型,可以定义所有类型的数据,或者像python定义数据时候没有使用数据类型,数据也能存储在内存中。其存储方式即使共用体。
9.2.2 定义
union 联合体名{
联合体变量成员列表;
}
9.2.3 特点
①联合union是一个能在同一个存储空间存储不同类型数据的类型;
②联合体所占的内存长度等于其最长成员的长度倍数;
③同一内存段可以用来存放几种不同类型的成员,共用体变量中起作用的成员是最后一次存放的成员,再存入一个新的成员后原有的成员的值会被覆盖。节省内存空间;
④共用体变量的地址和它的各成员的地址都是同一地址。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
union student{
char name[21];
int age;
};
int main() {
union student stu;
strcpy(stu.name, "曹操");
printf("student.name=%s\n", stu.name);//曹操
return 0;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
union student{
char name[21];
int age;
};
int main() {
union student stu;
strcpy(stu.name, "曹操");
stu.age = 18;
printf("name:%s\nage:%d", stu.name, stu.age);//最后一个能正常打印,其他的打印都乱码
printf("sizeof(stu)=%d\n", sizeof(stu));
printf("sizeof(union student)=%d\n", sizeof(union student));//联合体所占的内存长度等于其最长成员的长度倍数;在这里最长的int类型,所以是4字节的倍数,由于char name[21]的长度是21字节,char类型是1字节,所以24字节即可。
return 0;
printf("address(stu)=%p\n", &stu);
printf("address(stu.name)=%p\n", stu.name);
printf("address(stu.age)=%p\n", &stu.age); //地址相同
}
#include <stdio.h>
union Test {
unsigned char a;
unsigned int b;
unsigned short c;
};
int main() {
union Test tmp; //定义共用体变量
//1、所有成员的首地址是一样的
printf("%p, %p, %p\n", &(tmp.a), &(tmp.b), &(tmp.c));
//2、共用体大小为最大成员类型的大小
printf("%lu\n", sizeof(union Test));
//3、一个成员赋值,会影响另外的成员
//左边是高位,右边是低位
//低位放低地址,高位放高地址,小端对齐
tmp.b = 0x44332211;
printf("%x\n", tmp.a); //11
printf("%x\n", tmp.c); //2211
tmp.a = 0x00;
printf("short: %x\n", tmp.c); //2200
printf("int: %x\n", tmp.b); //44332200
return 0;
}
9.3 枚举
9.3.1 概述
将变量的值一一列举出来,变量的值只限于列举出来的值的范围内。
9.3.2 定义
enum 联合体名{
枚举成员列表;
}
9.3.3 特点
①在枚举值表中应列出所有可用值,也称为枚举元素;
②枚举值是常量,不能在程序中用赋值语句再对它赋值;
③枚举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为0,1,2 …
④通常配合switch使用。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
enum Week {
Sun, Mon, Tue, Wed, Thu, Fri, Sat
};//鼠标触摸枚举成员,可以看到默认(enum week)Sun = 0,(enum week)Mon = 1,(enum week)Tue = 2...枚举元素本身由系统定义了一个表示序号的数值从0开始顺序定义为0,1,2...
/******************************************************************************************************
enum week {//也可以直接赋值给枚举成员
Sun=7, Mon=1, Tue=2, Wed=3, Thu=4, Fri=5, Sat=6
};
*******************************************************************************************************/
int main() {
//Sun = 8;//error,表达式必须是可修改的左值
int value;
scanf("%d", &value);
switch (value){
case Sun: printf("Sunday"); break;
case Mon: printf("Monday"); break;
case Tue: printf("Tuesday"); break;
case Wed: printf("Wednesday"); break;
case Thu: printf("Thursday"); break;
case Fri: printf("Friday"); break;
case Sat: printf("Saturday"); break;
default: printf("NULL"); break;
}
return 0;
}
enum ATM{ //做一个ATM界面选择系统
insertCard = 1, readCard, inputPassword, lockCard = 10,query = 20, Withdrawal, deposit,rollbackCard = 30,unlock = 40
};//枚举也可以这样写
enum bool{ //不能用枚举实现bool类型,因为false跟true被占用,在Windows.h文件下有定义。
false,true
};
9.4 typedef
9.4.1 概述
typedef为C语言的关键字,作用是为一种数据类型(基本类型或自定义数据类型)定义一个新名字(为已存在的类型定义一个类型),不能创建新类型。
9.4.2 特点
①与#define不同,typedef仅限于数据类型,而不是能是表达式或具体的值;
②#define发生在预处理,typedef发生在编译阶段;
③通常给自定义类型取别名(比如结构体数据类型,每次写struct student很长,所以就可以取个别名,typedef struct student = ss,ss就是数据类型)。
9.4.3 常见案例
size_t实际就是unsigned int的别名。
码字不易,如果大家觉得有用,请高抬贵手给一个赞让文章上推荐让更多的人看到吧,也可以评论提出意见让后面的文章内容越来越生动丰富。