【C数据类型】快速理解:结构体(struct)、枚举(enum)、共用体(union)

像 int、float、char 等是由C语言本身提供的数据类型,不能再进行分拆,我们称之为基本数据类型;而结构体可以包含多个基本类型的数据,也可以包含其他的结构体,我们将它称为复杂数据类型或构造数据类型


结构体Struct

结构体变量

结构体变量的赋值

结构体数组

结构体作为函数参数

结构体指针

枚举(Enum)

枚举的定义

相关语法

操作枚举变量

共用体(Union)

共用体的赋值和使用问题

正确地使用共用体


注意:本文值讲解结构体、枚举、共用体在C语言中的浅层语法及用法,并不涉及其实现原理(如内存对齐和位段),这些原理将在C语言进阶时再做讲解!


结构体Struct

结构体的理解:

  1. 数组可以存放同一种数据类型的数据,那么想存储一组不同数据类型的数据,该如何?
  2. 使用结构体(Struct)来存放一组不同类型的数据
  3. 结构体也是一种数据类型,它由程序员自己定义,可以包含多个其他类型的数据。

结构体的特点:

  1. C语言要求结构体至少得有一个成员,也就是说C语言并不允许空结构体的出现!
  2. 可以被声明为变量、指针或数组等,用以实现较复杂的数据结构。

结构体的两种常见分类:

  1. 标准结构体:标准情况“struct 结构体名 { 数据类型…… }结构体变量名;
  2. 匿名结构体:锁定变量变量个数“struct { 数据类型…… }结构体变量名;”即没有结构体名

结构体变量

总共有三种方法可以定义结构体变量。

1、先定义结构体类型,在定义变量

struct stu { //定义一个结构体,这种数据类型是 “struct stu”;

char *name; //姓名
int num; //学号
int age; //年龄
char group; //所在学习小组
float score; //成绩
};

既然结构体是一种数据类型,那么就可以用它来定义变量。例如:

struct stu stu1, stu2; //stu1stu2就是结构体变量;

定义了两个变量 stu1 和 stu2,它们都是 stu 类型,都由 5 个成员组成。注意关键字struct不能少。stu 就像一个“模板”,定义出来的变量都具有相同的性质。

 

2、定义结构体的同时就定义变量

struct{  //没有写 stu,后面就没法用该结构体定义新的变量。
char *name; //姓名,结构体成员变量
int num; //学号
int age; //年龄

char group; //所在学习小组
float score; //成绩
} stu1, stu2; //在定义时就初始化两个结构体变量;

3、直接定义结构体类型变量,省略类型名

struct {
       char *name;
       int age;
} stu;

结构体变量的引用方式:

  • 1、结构体变量名.成员名:stu1.name
  • 2、结构体指针变量->成员名:ps -> name
  • 3、(*结构体指针变量).成员名:(*ps).name 因为优先级的问题,所以要加括号
  • 4、结构体变量数组名.成员名:stu[0].name

结构体变量的赋值

总共有两种方式。

第一种方式:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//定义结构体类型时不要直接给成员赋值,因为结构体只是一个类型,还没有分配空间
//而只有根据其类型定义变量时,才分配空间,有空间后才能赋值
typedef struct Teacher
{
	char name[50];
	int age;
}Teacher;
int main()
{
	Teacher t1 = { "lily", 22 };

	//相同类型的两个结构体变量,可以相互赋值
	//原理是:把t1成员变量内存的值拷贝给t2成员变量的内存
	//本质上:t1和t2没有关系
	Teacher t2 = t1;
	printf("%s, %d\n", t2.name, t2.age);
}

 

第二种方式:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//定义结构体类型时不要直接给成员赋值,因为结构体只是一个类型,还没有分配空间
//而只有根据其类型定义变量时,才分配空间,有空间后才能赋值
typedef struct Teacher
{
	char name[50];
	int age;
}Teacher;

void copyTeacher(Teacher to, Teacher from) //结构体拷贝的时候,不能用这种方式,这种是副本机制
{
	to = from;
	printf("[copyTeacher] %s, %d\n", to.name, to.age);
}

void copyTeacher2(Teacher *to, Teacher *from) //只有这种才能完成结构体的真正拷贝,传递进来地址
{
	*to = *from;
	//printf("[copyTeacher] %s, %d\n", to.name, to.age);
}

int main()
{
	Teacher t1 = { "lily", 22 };
	Teacher t3;
	memset(&t3, 0, sizeof(t3));
	copyTeacher2(&t3, &t1); //t1拷贝给t3
	printf("[t3]%s, %d\n", t3.name, t3.age);
}

结构体数组

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Teacher //定义一个结构体
{
	char name[50];
	//char *name;
	int age;
}Teacher;

int main(void)
{
	Teacher a[3] = { //结构体数组初始化,方式1
		{ "a", 18 },
		{ "a", 18 },
		{ "a", 18 }
	};

	//结构体数组初始化方式2,等价于方式1。都是静态的分配
	Teacher a2[3] = { "a", 18, "b", 28, "c", 38 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		printf("%s, %d\n", a2[i].name, a2[i].age);
	}

	//结构体数组初始化方式3。动态分配内存
	Teacher *p = (Teacher *)malloc(3 * sizeof(Teacher)); //申请内存
	if (p == NULL)
	{
		return -1;
	}

	char buf[50];
	for (i = 0; i < 3; i++) //写入数据
	{
		sprintf(buf, "name%d%d%d", i, i, i); //把"name%d%d%d"格式化到buf中去
		strcpy(p[i].name, buf);
		p[i].age = 20 + i;
	}

	for (i = 0; i < 3; i++) //显示
	{
		printf("第%d个:%s, %d\n", i + 1, p[i].name, p[i].age);
	}
	printf("\n");
	if (p != NULL)
	{
		free(p);
		p = NULL;
	}
	system("pause");
	return 0;
}

 

结构体作为函数参数


#include<stdio.h>
#include<stdlib.h>

// 定义一个结构体
struct Student {
	int age;
};

void test(struct Student stu) {
	printf("修改前的形参:%d \n", stu.age);
	// 修改实参中的age
	stu.age = 10;

	printf("修改后的形参:%d \n", stu.age);
}

int main() {

	struct Student stu = { 30 };
	printf("修改前的实参:%d \n", stu.age);

	// 调用test函数
	test(stu);


	printf("修改后的实参:%d \n", stu.age);
	system("pause");
	return 0;
}

/*output:
修改前的实参:30
修改前的形参:30
修改后的形参:10
修改后的实参:30
*/

结构体指针

结构体指针的理解:

  1. 结构体指针和其他类型的指针都是一样的理解,在32位平台不管啥类型的指针都占4个字节的空间。
  2. 结构体指针就是指向结构体变量的指针;
  3. 如果一个指针变量中保存了结构体变量的首地址,那么这个指针变量就指向该结构体变量.
  4. 通过结构体指针即可访问该结构体变量,这与数组指针和函数指针的情况是相同的
  5. 结构指针变量说明的一般形式为:

struct 结构体名 *结构体指针变量名;//规范是要初始化,即struct name *var = NULL

struct student *p = &Boy; //假设事先定义了 struct student Boy;

 

将结构体变量传递给函数,可有三种方法:

  1. 形参是结构体成员,实参是对应结构体成员的值,参数传递是将结构体成员的值传递给形参。
  2. 形参是结构体变量,实参是结构体变量的值,参数传递是将结构体变量的值传递给形参。
  3. 形参是指向结构体类型的指针,实参是结构体变量的地址或指向结构体变量的指针,参数传递是将结构体变量的首地址传递给形参。

前两种方法属于值传递方式(副本机制),结构体规模较大时,空间和时间的开销很大,一般较少使用。最后一种是地址传递方式。

结构体指针
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct Teacher
{
	char *name;
	int age;
}Teacher;

int main()
{
	//1、结构体中含指针的结构体变量初始化,第一种方式
	//原理是:直接在堆一个空间,使结构体变量指向该空间
	Teacher t;
	t.name = (char *)malloc(30);
	strcpy(t.name, "lily");
	t.age = 22;
	printf("name = %s, age = %d\n", t.name, t.age);

	if (t.name != NULL)
	{
		free(t.name);
		t.name = NULL;
	}

	//2、第二种方式
	Teacher *p = NULL; //结构体指针变量
	p = (Teacher *)malloc(sizeof(Teacher)); //为结构体指针变量p,在堆上分配空间
	p->name = (char *)malloc(30); //为结构体中的某个成员再分配内存。
	strcpy(p->name, "lilei");
	p->age = 22;
	printf("name = %s, age = %d\n", p->name, p->age);

	if (p->name != NULL)
	{
		free(p->name);
		p->name = NULL;
	}

	if (p != NULL)
	{
		free(p);
		p = NULL;
	}
}

枚举(Enum)

枚举是一种高级数据类型

一般形式为:enum 枚举名 {枚举元素1,枚举元素2,……};

例如:enum Season {spring, summer, autumn, winter};

枚举的定义

1.先定义枚举类型,再定义枚举变量

enum Season { spring, summer, autumn, winter };
enum Season s;

2.定义枚举类型的同时定义枚举变量

enum Season { spring, summer, autumn, winter } s;

3.省略枚举名称,直接定义枚举变量

enum { spring, summer, autumn, winter } s;

 

相关语法

1、C语言编译器会将枚举元素(spring、summer等)作为整型常量处理,称为枚举常量

2、枚举元素的取决于定义时各枚举元素排列的先后顺序。默认情况下,第一个枚举元素的值为0,第二个为1,依次顺序加1。

enum Season {spring, summer, autumn, winter};

也就是说spring的值为0summer的值为1autumn的值为2winter的值为3

 

3、也可以在定义枚举类型时改变枚举元素的值

enum season {spring, summer=3, autumn, winter};

没有指定值的枚举元素,其值为前一元素加1。也就说spring的值为0,summer的值为3,autumn的值为4,winter的值为5。

 

操作枚举变量

1.赋值:可以给枚举变量赋枚举常量或者整型值

enum Season {spring, summer, autumn, winter} s;

s = spring; // 等价于 s = 0;

s = 3; // 等价于 s = winter;

2.遍历枚举元素

enum Season {spring, summer, autumn, winter} s;

// 遍历枚举元素
for (s = spring; s <= winter; s++) {
    printf("枚举元素:%d \n", s);
}
输出:
枚举元素:0
枚举元素:1
枚举元素:2
枚举元素:3

共用体(Union)

对共用体的理解:

  1. 共用体是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型。可定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值
  2. 共用体中的所有成员是共享一段内存,因此每个成员的存放首地址相对于联合体变量的基地址的偏移量为 0,即 所有成员的首地址都是一样的。为了使得所有成员能够共享一段内存,因此该空间必须足够容纳这些成员中最宽的成员

共用体的定义:

union [共用体名,可有可无]
{
   member definition; //共用体成员的定义
   member definition;
   ...
   member definition;
}[一个或多共用体变量,可有可无];

共用体的特性:

  1. 同一内存在每一瞬时只能保存一个成员
  2. 起作用的成员是最后一次赋值的成员
  3. 只能对联合体/共用体的第一个成员进行初始化,不然后面的赋值会覆盖掉第一个的值
  4. 共用的体变量的地址和他的各成员地址都是同一个地址,就是首地址
  5. 共用体结构可以互相嵌套

共用体的赋值和使用问题

共用体的赋值和使用问题
#include <stdio.h>
#include <string.h>

union Data
{
	int i;
	float f;
	char str[20];
};
//共用体占用的内存是共用体中最大的成员所占内存,因为这样才能足够存储共用体中最大的成员。
//例如,在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。

int main()
{
	union Data data;

	data.i = 10;
	data.f = 220.5;
	strcpy(data.str, "C Programming");

	printf("data.i : %d\n", data.i);
	printf("data.f : %f\n", data.f);
	printf("data.str : %s\n", data.str);

	return 0;
}

运行结果:

由运行结果可以得出:共用体的 if 成员的值不是所期望的,因为最后赋给变量的值占用了内存位置变量的赋值以最后一次赋值为准,这也是 str 成员能够完好输出的原因。

正确地使用共用体

正确使用共用体
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

union Data
{
	int i;
	float f;
	char  str[20];
};

int main()
{
	union Data data;

	data.i = 10;
	printf("data.i : %d\n", data.i);

	data.f = 220.5;
	printf("data.f : %f\n", data.f);

	strcpy(data.str, "C Programming");
	printf("data.str : %s\n", data.str);

	return 0;
}

运行结果:

分析:一个时间只使用一个变量,这是共用体真正的目的;


 

  • 13
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值