C语言——结构体、联合体/共用体、枚举类型、typedef关键字

目录

结构体

[1. 结构体的定义](#1. 结构体的定义)

[2. 结构体变量的声明](#2. 结构体变量的声明)

[3. 结构体成员的访问](#3. 结构体成员的访问)

[使用 . 运算符(结构体变量):](#使用 . 运算符(结构体变量):)

[使用 -> 运算符(结构体指针):](#使用 -> 运算符(结构体指针):)

[4. 结构体的初始化](#4. 结构体的初始化)

[1-4 编程示例](#1-4 编程示例)

[5. 结构体的大小](#5. 结构体的大小)

[6. 结构体数组](#6. 结构体数组)

编程示例

应用练习:选票系统

[7. 结构体作为函数参数](#7. 结构体作为函数参数)

[8. 动态内存分配](#8. 动态内存分配)

[1-8 编程示例](#1-8 编程示例)

9.应用场景

联合体/共用体

[1. 联合体的定义](#1. 联合体的定义)

[2. 联合体变量的声明](#2. 联合体变量的声明)

[3. 联合体的内存分配](#3. 联合体的内存分配)

[4. 联合体的访问](#4. 联合体的访问)

[5. 联合体的初始化](#5. 联合体的初始化)

[6. 联合体 vs 结构体](#6. 联合体 vs 结构体)

[7. 注意事项](#7. 注意事项)

编程案例

枚举类型

[1. 枚举的定义](#1. 枚举的定义)

基本语法

[2. 枚举的特性](#2. 枚举的特性)

[(1) 显式赋值](#(1) 显式赋值)

[(2) 隐式递增](#(2) 隐式递增)

[(3) 类型本质](#(3) 类型本质)

[3. 枚举变量的使用](#3. 枚举变量的使用)

枚举类型对变量声明的方式

(1)枚举类型的定义和变量的声明分开:

(2)类型定义与变量声明同时进行:

[结合 switch 语句](#结合 switch 语句)

[4. 枚举 vs 宏定义](#4. 枚举 vs 宏定义)

编程示例

typedef关键字

基本语法

[typedef 用法](#typedef 用法)

方式一:

方式二:

方式三:

方式四(指针):


结构体

C语言中的结构体(struct)是一种用户自定义的数据类型,允许将多个不同类型的变量组合成一个单一的逻辑单元。

例如:整型数,浮点型数,字符串是分散的数据表示 有时候我们需要用很多类型的数据来表示一个整体,比如学生信息:

类比与数组:++数组是元素类型一样的数据集合 如果是元素类型不同的数据集合,就要用到结构体了++

1. 结构体的定义

结构体通过 struct 关键字定义,语法如下:

struct 结构体名 {
    数据类型 成员1;
    数据类型 成员2;
    // ...
};

示例:定义一个学生结构体

cpp 复制代码
struct Student {
    char name[50];
    int age;
    float score;
};

它算是一个模板,一般不给赋具体的值,每一项在实际应用中并不是都要使用

2. 结构体变量的声明

  • 直接声明
cpp 复制代码
struct Student s1; // 声明一个Student类型的变量s1
  • 定义时声明
cpp 复制代码
struct Student {
    // ...
} s1, s2; // 同时声明s1和s2
  • 使用 typedef 简化
cpp 复制代码
typedef struct {
    char name[50];
    int age;
} Student; // 重命名为Student
Student s1; // 直接使用,无需写struct

3. 结构体成员的访问

使用 . 运算符(结构体变量):

**功能:**直接访问结构体变量的成员。

cpp 复制代码
s1.age = 20;
printf("年龄: %d\n", s1.age);

使用 -> 运算符(结构体指针):

**功能:**通过指针间接访问结构体的成员(等价于先解引用指针,再用点运算符)。

cpp 复制代码
struct Student *ptr = &s1;
ptr->age = 22;
printf("年龄: %d\n", ptr->age);

4. 结构体的初始化

cpp 复制代码
#include <stdio.h>
#include <string.h>

struct Student
{
	int num;
	char name[28];
	char sex[5];
	int age;
};

int main()
{
	struct Student stu;
	struct Student stu1 = {2,"李四","男",18}; // 初始化方式一

    // 初始化方式二
	stu.num = 1;
	strcpy(stu.name,"张三");
	strcpy(stu.sex,"男");
	stu.age = 19;

	printf("学号:%d,姓名:%s,性别:%s,年龄:%d\n",stu.num,stu.name,stu.sex,stu.age);
	printf("学号:%d,姓名:%s,性别:%s,年龄:%d\n",stu1.num,stu1.name,stu1.sex,stu1.age);

	return 0;
}

1-4 编程示例

例题:有两个学生的名字,学号,成绩数据。输出成绩高的学生的信息

cpp 复制代码
#include <stdio.h>
#include <string.h>

struct Student
{
	int num;
	char name[28];
	char sex[5];
	int age;
	int score;
};

int main()
{
	struct Student stu;
	struct Student stu1 = {2,"李四","男",18,98};
	struct Student max;

	max = stu;//假设stu的成绩大

	stu.num = 1;
	strcpy(stu.name,"张三");
	strcpy(stu.sex,"男");
	stu.age = 19;
	stu.score = 96;

	if( max.score < stu1.score )
	{
		max = stu1;
	}

	printf("学号:%d,姓名:%s,性别:%s,年龄:%d,成绩:%d\n",max.num,max.name,max.sex,max.age,max.score);

	return 0;
}

5. 结构体的大小

使用 sizeof 获取结构体占用的内存大小:

cpp 复制代码
printf("结构体大小: %zu 字节\n", sizeof(struct Student));

注意:结构体的实际大小可能因内存对齐规则而大于成员大小的总和。

6. 结构体数组

cpp 复制代码
struct Student class[3] = {
    {"Alice", 20, 85.5},
    {"Bob", 21, 92.0},
    {"Charlie", 19, 78.5}
};

编程示例

结构体数组中有三个学生的信息,请打印三个学生的信息

cpp 复制代码
#include <stdio.h>

struct Student
{
	int stuNum;
	char name[32];
	char sex[4];
	int age;
	int score;
};

int main()
{
	int i;
	int length;
	struct Student stu[3] = {
		{1,"张三","男",19,86},
		{2,"李四","男",21,98},
		{3,"小五","女",23,100}
	};

    //计算结构体数组各数
	length = sizeof(stu) / sizeof(stu[0]);

	for( i = 0; i < length; i++ )
	{
		printf("学号:%d,姓名:%s,性别:%s,年龄:%d,成绩:%d\n",
				 stu[i].stuNum, stu[i].name, stu[i].sex, stu[i].age, stu[i].score);
	}

	return 0;
}

应用练习:选票系统

cpp 复制代码
#include <stdio.h>
#include <string.h>

struct XuanMin
{
	int num;//编号
	char name[32];
	int vote;//票数
};

int main()
{
	struct XuanMin xm[3];
	struct XuanMin max;
	int i;
	int j;
	int length; // 获取结构体各数
	int vote_num = 5; // 投票人数
	char tempName[32]; // 临时变量,存储候选人姓名
	int mark; // 标志位
	int invalid_tickets = 0; //废票

    // 获取结构体数组各数
	length = sizeof(xm) / sizeof(xm[0]);
	
    //初始化候选人信息
	for( i = 0; i < length; i++ )
	{
		xm[i].vote = 0;
		printf("请输入第%d位参选者编号及姓名:\n",i + 1);
		scanf("%d%s",&xm[i].num,xm[i].name);
	}

	//唱票环节
	for( i = 0; i < vote_num; i++ )
	{
		mark = 0;
		printf("请第%d位投票人,为参选者投票\n", i + 1);
		scanf("%s",tempName);
		for( j = 0; j < length; j++ )
		{
			if( strcmp(tempName,xm[j].name) == 0 )
			{
				xm[j].vote++;
				mark = 1;
			}
		}
		if( mark == 0 )
		{
			invalid_tickets++;
		}
	}

	//宣布结果
	for( i = 0; i < length; i++ )
	{
		printf("姓名:%s,票数:%d\n", xm[i].name, xm[i].vote);
	}
	max = xm[0]; // 让第一个结构体元素为最大值
	for( i = 0; i < length; i++ )
	{
		if( max.vote < xm[i].vote )
		{
			max = xm[i];
		}
	}
	printf("恭喜%s,以%d票当选,弃票:%d\n", max.name, max.vote, invalid_tickets);


	return 0;
}

7. 结构体作为函数参数

  • 传值(拷贝整个结构体):

    cpp 复制代码
    void printStudent(struct Student s) {
        printf("姓名: %s, 年龄: %d\n", s.name, s.age);
    }
  • 传指针(高效,避免拷贝):

    cpp 复制代码
    void modifyStudent(struct Student *s) {
        s->age += 1;
    }

8. 动态内存分配

cpp 复制代码
struct Student *s = (struct Student*)malloc(sizeof(struct Student));
strcpy(s->name, "David");
s->age = 25;
free(s); // 释放内存

1-8 编程示例

选票系统2.0

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct XuanMin
{
    char name[32];
    int numberVotes;
};

// 初始化选民信息(候选人姓名,候选人人数)
void initXuanMin(struct XuanMin **p, int *pn)
{
    printf("请输入有多少名候选人:\n");
    scanf("%d",pn);

    *p = malloc( ( *pn ) * sizeof( struct XuanMin ) );
    // 判断是否开辟内存成功
    if( *p == NULL )
    {
        printf("内存开辟失败!\n");
        exit(-1);
    }

    for ( int i = 0; i < *pn; i++ )
    {
        printf("请输入第%d位候选人名称:\n", i + 1);
        scanf("%s", (*p)->name);
        // 初始化每一个候选人的票数为0
        (*p)->numberVotes = 0;
        // 地址偏移,为下一个候选人初始化做准备
        (*p)++; 
    }

    *p = *p - *pn; // 将指针 *p 向前移动 *pn 个 struct XuanMin 的位置。
}

// 打印函数
void printfXuanMin(struct XuanMin *p, int numberPeople)
{
    for ( int i = 0; i < numberPeople; i++ )
    {
        printf("姓名:%s,票数:%d\n", p->name, p->numberVotes);
        p++;
    }
}

// 唱票环节
int Call_Out_Votes(struct XuanMin *p, int numberPeople)
{
    int i;
    int j;
    int mark = 0; // 标志位
    int voter = 0; // 投票人数
    int invalidated_ticket = 0; // 废票
    char tempName[32] = {'\0'};
    struct XuanMin *Pbak = p;

    printf("请输入为候选人投票人数:\n");
    scanf("%d", &voter);

    // 请输入每位投票人,想为候选人投票的姓名
    for( i = 0; i < voter; i++ )
    {
        printf("有请,第%d位投票人,投票!\n", i + 1);
        scanf("%s", tempName);

        // 比对每一个候选人的姓名
        for( j = 0; j < numberPeople; j++ )
        {
            // 如果对比结果正确,相对应的候选人票数+1
            if( strcmp(tempName, p->name) == 0 )
            {
                p->numberVotes++;
                mark = 1;
            }

            p++; // 偏移到下一个候选人,直至最后一个
        }
        p = Pbak; //使p回到起始地址,为下一轮比较做准备
        
        // 如果mark == 0,那么表示没有匹配到有效候选人姓名,为废票
        if( mark == 0 )
        {
            invalidated_ticket++;
        }

        mark = 0; //恢复初值,为下一次投票是否为废票,做标记
    }

    return invalidated_ticket;
}

// 获取票数最多者
struct XuanMin* getTheMostVotes(struct XuanMin *p, int numberPeople)
{
    int i;
    struct XuanMin *getMaxVotes = NULL;

    getMaxVotes = p; //将第一个候选人的结构体地址复制给getMaxVotes

    for( i = 0; i < numberPeople; i++ )
    {
        if( getMaxVotes->numberVotes < p->numberVotes ) //各候选人比对票数
        {
            getMaxVotes = p;
        }
        p++;
    }

    return getMaxVotes;
}

int main()
{
    int numberPeople = 0; // 候选人人数
    int invalidated_ticket = 0; // 废票
    struct XuanMin *xm = NULL;
    struct XuanMin *getMaxVotes = NULL;

    // 初始化候选人信息
    initXuanMin(&xm, &numberPeople);
    printf("候选人信息初始化完毕!\n");
    printfXuanMin(xm, numberPeople);

    // 唱票环节,并且返回废票数
    invalidated_ticket = Call_Out_Votes(xm, numberPeople);
    printf("投票完成!\n");
    printfXuanMin(xm, numberPeople);

    // 获取票数最多者
    getMaxVotes = getTheMostVotes(xm, numberPeople);
    printf("恭喜,候选人:%s 以%d票当选!废票:%d\n", getMaxVotes->name, getMaxVotes->numberVotes, invalidated_ticket);

    return 0;
}

9.应用场景

  • 数据封装:将逻辑相关的数据组合在一起(如坐标点、学生信息)。

  • 文件操作:将结构体数据写入文件或从文件读取。

  • 链表/树:通过结构体和指针实现动态数据结构。

联合体/共用体

C语言中的联合体(Union) ,也称为共用体 ,是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型。联合体的核心特点是:所有成员共享同一块内存空间,同一时间只能使用其中一个成员。

1. 联合体的定义

联合体通过 union 关键字定义,语法类似结构体:

cpp 复制代码
union 联合体名 {
    数据类型 成员1;
    数据类型 成员2;
    // ...
};

示例:定义一个存储整数、浮点数或字符串的联合体

cpp 复制代码
union Data {
    int i;
    float f;
    char str[20];
};

2. 联合体变量的声明

  • 直接声明
cpp 复制代码
union Data data;
  • 定义时声明
cpp 复制代码
union Data {
    int i;
    float f;
} data1, data2;
  • 使用 typedef 简化
cpp 复制代码
typedef union {
    int i;
    float f;
} Data;
Data d1;

3. 联合体的内存分配

  • 所有成员共享同一块内存空间

  • 联合体的大小等于其最大成员的大小

cpp 复制代码
union Data {
    int i;      // 4字节(假设int为32位)
    float f;    // 4字节
    char str[20]; // 20字节
};
printf("联合体大小: %zu\n", sizeof(union Data)); // 输出20

4. 联合体的访问

使用 . 运算符访问成员(类似结构体):

cpp 复制代码
union Data data;
data.i = 10;
printf("i = %d\n", data.i);

data.f = 3.14;
printf("f = %f\n", data.f); // 此时i的值已被覆盖!

注意:同一时间只能有一个成员有效,修改一个成员会覆盖其他成员的值。

5. 联合体的初始化

先声明后赋值

联合体变量声明后,可以单独对某个成员赋值:

cpp 复制代码
union Data data;
data.i = 42;    // 使用整形成员i
data.f = 2.5;   // 此时i的值被覆盖!

6. 联合体 vs 结构体

7. 注意事项

  1. 成员覆盖:修改一个成员会覆盖其他成员的值。

  2. 字节序问题:类型转换时需注意平台的字节序(大端/小端)。

  3. 未定义行为:访问未初始化的成员可能导致未定义结果。

编程案例

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct People
{
    char name[32];      // 姓名
    int number;         // 号码
    char sex[3];        // 性别
    char occupation;    // 职业
    union Msg
    {
        char class[7];  // 班级
        char job[3];    // 职务
    }msg;
};

// 初始化人员数据
void initPeopleMsg(struct People **p, int *pn)
{
    printf("请输入人数:\n");
    scanf("%d", pn);
    scanf("%*c"); // 吸收回车符

    // 开辟 *pn 个 struct People 类型的大小内存空间
    *p = ( struct People* )malloc( ( *pn ) * sizeof( struct People ) );
    // 判断开辟的内存空间是否正常
    if( *p == NULL )
    {
        printf("地址空间开辟失败\n");
        exit(-1);
    }

    // 给每个人员填写数据
    for( int i = 0; i < *pn; i++ )
    {
        printf("请输入你的职业:s 学生、t 教师\n");
        scanf("%c",&(*p)->occupation);

        getchar(); // 吸收回车符

        // 判断是否为t(表示教师)
        if( (*p)->occupation == 't' )
        {
            printf("请输入您的姓名:\n");
            scanf( "%s", (*p)->name );
            printf("请输入您的号码:\n");
            scanf( "%d", &(*p)->number );
            printf("请输入您的性别:\n");
            scanf( "%s", (*p)->sex );
            printf("请输入您的职务:\n");
            scanf( "%s", (*p)->msg.job );
        }
        else //不是,则为s(表示学生)
        {
            printf("请输入您的姓名:\n");
            scanf( "%s", (*p)->name );
            printf("请输入您的号码:\n");
            scanf( "%d", &(*p)->number );
            printf("请输入您的性别:\n");
            scanf( "%s", (*p)->sex );
            printf("请输入您的班级:\n");
            scanf( "%s", (*p)->msg.class );
        }
        getchar(); // 吸收回车符
        (*p)++; //偏移地址
    }
    *p = *p - *pn; // 使*p回到起始地址
}

// 打印人员数据
void printfMsg(struct People *p, int peopleNum)
{
    for (int i = 0; i < peopleNum; i++)
    {
        // 判断是否为occupation == t,因为联合体会覆盖数据,printf原样输出打印结果不对,所以需要分开打印
        if( p->occupation == 't' ) 
        {
            printf("职业:%c,姓名:%s,号码:%d,性别:%s,职务:%s\n",p->occupation,p->name,p->number,p->sex,p->msg.job);
        }
        else
        {
            printf("职业:%c,姓名:%s,号码:%d,性别:%s,班级:%s\n",p->occupation,p->name,p->number,p->sex,p->msg.class);
        }
        p++; //偏移地址
    }
}

int main()
{
    struct People *p = NULL;
    int peopleNum = 0; // 人数

    // 初始化人员数据
    initPeopleMsg(&p, &peopleNum);
    // 打印人员数据
    printfMsg(p, peopleNum);

    return 0;
}

枚举类型

枚举类型用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型。 ++定义:是指将变量的值一一列出来,变量的值只限于列举出来的值的范围内。++

比如一年有十二个月,一个礼拜有七天,这是毫无疑问的,就可以将这些月份天数用常量来代替。枚举类型和宏定义是差不多的,只有细微区别,++宏运行是在预处理阶段完成的,枚举类型是在与编译阶段完成的。++

1. 枚举的定义

基本语法

cpp 复制代码
enum 枚举名 {
    标识符1,  // 默认值为0
    标识符2,  // 默认值为前一个值+1
    // ...
};

**注意:**列表中的名字,可以自己定义,无需像变量一样去申请 。C编译器把它当成常量处理,也称枚举常量

示例:定义一个表示星期的枚举

cpp 复制代码
enum Weekday {
    MON,   // 0
    TUE,   // 1
    WED,   // 2
    THU,   // 3
    FRI,   // 4
    SAT,   // 5
    SUN    // 6
};

枚举值默认从 0 开始,往后逐个加 1(递增);也就是说,Weekday中的 MON, TUE, WED, THU, FRI, SAT, SUN 分对应的值为 0、1、2、3、4、5、6。

2. 枚举的特性

(1) 显式赋值

可以手动为枚举成员指定值:

cpp 复制代码
enum Color {
    RED = 1,    // 1
    GREEN = 2,  // 2
    BLUE = 4    // 4
};

(2) 隐式递增

未显式赋值的成员自动递增:

cpp 复制代码
enum State {
    IDLE = 1,       // 1
    RUNNING,        // 2
    STOPPED,        // 3
    ERROR           // 4
};

(3) 类型本质

  • 枚举常量本质是整型(int,可以直接参与整数运算。

  • 枚举变量的大小与编译器实现相关,通常为 sizeof(int)

3. 枚举变量的使用

枚举类型对变量声明的方式

(1)枚举类型的定义和变量的声明分开:
cpp 复制代码
#include <stdio.h>

enum  WeekDay{ sun, mon, tus, wed, thi, fri, sat};

int main()
{
    enum WeekDay w;

    w = sun;

    for (int i = 0; i < 7; i++)
    {
        printf("w = %d\n",w + i);
    }

    return 0;
}
(2)类型定义与变量声明同时进行:
cpp 复制代码
#include <stdio.h>

//跟第一个定义不同的是,此处的标号WeekDay省略,这是允许的。
enum  { sun, mon, tus, wed, thi, fri, sat}w1 w2; //变量w1、w2的类型为枚举型enum WeekDay

int main()
{
    w1 = mon;
    w2 = fri;

    printf("w1 = %d\n",w1);
    printf("w2 = %d\n",w2);

    return 0;
}

结合 switch 语句

cpp 复制代码
switch (w1) {
    case MON:
        printf("周一\n");
        break;
    case TUE:
        printf("周二\n");
        break;
    // ...
}

4. 枚举 vs 宏定义

编程示例

cpp 复制代码
#include <stdio.h>

enum Colour { reb, blue, green, yello};
enum  { sun, mon, tus, wed, thi, fri, sat}today;

int main()
{
    enum Colour rgb = reb;
    today = sun;

    for (int i = 0; i < 4; i++)
    {
        printf("rgb = %d\n",rgb + i);
    }
    for (int i = 0; i < 7; i++)
    {
        printf("WeekDay = %d\n",today + i);
    }

    return 0;
}

typedef关键字

typedef(Type Define)++用于为已有的数据类型定义一个新的名称(别名)++。它不创建新类型,而是提供一种更简洁、更具可读性的方式引用现有类型。

基本语法

cpp 复制代码
typedef 原类型 新类型名;
  • 原类型 :可以是基本类型(如int)、结构体、联合体、枚举或函数指针等。

  • 新类型名:自定义的别名,遵循标识符命名规则。

typedef 用法

方式一:

cpp 复制代码
#include <stdio.h>

struct Test {
    int idata;
    char cdata;
};

int main() {
    typedef struct Test t;  // 为 struct Test 重新定义别名为 t
    t t1;                  // 使用别名 t 声明一个结构体变量 t1

    t1.idata = 10;         // 为结构体成员 idata 赋值
    printf("%d\n", t1.idata); // 输出 idata 的值

    return 0;
}

方式二:

cpp 复制代码
#include <stdio.h>

typedef struct Test {
    char cdata;
} t; // 直接在定义结构体时添加别名

int main() {
    t t1; // 使用别名 t ,定义一个结构体变量为t1
    
    t1.cdata = 'c';// 初始化成员变量
    printf("%c\n", t1.cdata);

    return 0;
}

方式三:

cpp 复制代码
#include <stdio.h>

typedef struct { // 与方式二 不同之处Test可以省略
    char cdata;
} t; // 直接在定义结构体时添加别名

int main() {
    t t1; // 使用别名 t ,定义一个结构体变量为t1
    
    t1.cdata = 'c';// 初始化成员变量
    printf("%c\n", t1.cdata);

    return 0;
}

方式四(指针):

cpp 复制代码
#include <stdio.h>

typedef struct people
{
    int num;
    char name[32];
    int age;
}People, *pPeople; // People 是 struct people 结构体类型
                   // pPeople 是 struct people* 结构体指针类型

void printfStruct(pPeople p)
{
    printf("编号:%d,姓名:%s,年龄:%d\n", p->num, p->name, p->age);
}

int main()
{
    People p1 = {39,"小薇",21};
    People p2 = {23,"诗诗",20};
    pPeople p3 = &p1; // pPeople  等价于 struct people* pPeople;
    pPeople p4 = &p2;

    printf("结构体指针变量\n");
    printfStruct2(p3);
    printfStruct2(p4);

    return 0;
}
相关推荐
2302_7995257415 分钟前
【go语言】——方法集
开发语言·后端·golang
非 白27 分钟前
【Java 后端】Restful API 接口
java·开发语言·restful
rider18927 分钟前
Java多线程及线程变量学习:从熟悉到实战(下)
java·开发语言·学习
元亓亓亓28 分钟前
java后端开发day26--常用API(一)
java·开发语言
JP-Destiny30 分钟前
后端-Java虚拟机
java·开发语言·jvm
KeithTsui32 分钟前
GCC RISCV 后端 -- GCC 构建系统简介
c语言·c++
葡萄_成熟时_35 分钟前
JavaWeb后端基础(4)
java·开发语言·数据库·maven·web
#金毛43 分钟前
C语言排序算法详解
c语言·算法·排序算法
Hello.Reader1 小时前
深入解析 Rust 异步编程中的 Traits
开发语言·后端·rust
馨谙1 小时前
Exception in thread “main“ java.lang.ExceptionInInitializerError
java·开发语言