九、结构体、联合体和枚举--附代码案例


复合类型也是一种数据类型,是一种自定义类型。常见的复合类型有结构体、枚举、联合体。

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的别名。

码字不易,如果大家觉得有用,请高抬贵手给一个赞让文章上推荐让更多的人看到吧,也可以评论提出意见让后面的文章内容越来越生动丰富。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值