自定义类型:结构体、联合与枚举(1)
目录
前言
一、 结构体类型的声明
结构体介绍:
二、结构体变量的创建和初始化
1、结构体变量的创建
2、结构体变量的初始化
(1)按成员顺序初始化(最常用):
(2)定义后赋值(灵活但繁琐)
三、结构成员访问操作符
(1). 操作符(直接访问,用于普通结构体变量)
(2)-> 操作符(间接访问,用于结构体指针)
常见使用场景
四、结构体内存对⻬
对⻬规则:
总结
前言
文章将讲解结构体类型的声明、结构体变量的创建和初始化、结构成员访问操作符、结构体内存对⻬、联合体类型的声明、联合体的特点、联合体⼤⼩的计算、枚举类型的声明、枚举类型的优点、枚举类型的使⽤知识的相关内容,为本章节为结构体类型的声明、结构体变量的创建和初始化、结构成员访问操作符、结构体内存对⻬知识的内容。
一、 结构体类型的声明
结构体介绍:
结构体是自定义复合数据类型,用于将不同类型的数据组合成一个整体。
结构体的语法形式:
struct 结构体名 {
数据类型 成员名1; // 成员1
数据类型 成员名2; // 成员2
// ... 更多成员
};
- 关键字:
struct
不可省略(除非使用typedef
重定义)。 - 结构体名:自定义标识符(如
Student
、Point
),遵循变量命名规则。 - 成员:可包含任意数据类型(基本类型、数组、指针、其他结构体等)。
例如描述⼀个学⽣:
struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
使用方式:需通过 struct 结构体名
定义变量。
struct Student stu1; // 定义结构体变量 stu1
定义变量:
1.在主函数中需要写struct 结构体名 成员 的方式创造对象。
2.typedef 重定义(简化声明):
typedef 的介绍:
typedef
是 C 和 C++ 中的一个关键字,用于为已有的数据类型创建一个新的别名(别称)。它的主要作用是简化复杂类型的表示,提高代码的可读性和可维护性。
使用:
用 typedef
将结构体类型重定义为新名称,避免重复写 struct
:
例:
typedef struct {
int x; // x坐标
int y; // y坐标
} Point; // 新类型名:Point
在此基础上可以在主函数中创建:
Point p;
typedef
是 C 和 C++ 中的一个关键字,用于为已有的数据类型创建一个新的别名(别称)。它的主要作用是简化复杂类型的表示,提高代码的可读性和可维护性.
二、结构体变量的创建和初始化
在 C 语言中,结构体变量的创建和初始化是使用结构体的基础操作,根据结构体定义方式和初始化时机的不同,有多种实现方式,接下来我将讲解关于这方面的知识。
1、结构体变量的创建
1.先定义结构体类型,再创建变量
// 1. 定义结构体类型
struct Student {
char name[20];
int age;
float score;
};
struct Student stu1; //可以一次创建一个变量,也可以一次创建多个变量
struct Student stu2, stu3;
2.定义结构体类型的同时创建变量
struct Teacher {
char name[10];
int id;
} t1, t2; // 定义类型时直接创建 t1、t2 变量
3.使用 typedef
简化变量创建
通过 typedef
为结构体类型起别名后,可省略 struct
关键字:
typedef struct {
int x;
int y;
} Point; // 别名 PointPoint p1; // 直接用别名创建变量,无需写 struct Point
2、结构体变量的初始化
结构体初始化需遵循“成员按顺序赋值”或“指定成员赋值”的规则,支持定义时初始化和定义后赋值两种方式。
1. 定义时初始化(推荐,更简洁)
(1)按成员顺序初始化(最常用):
按结构体成员声明的顺序依次赋值,用 {}
包裹,逗号分隔:
例:
#include<stdio.h>
#include<stdlib.h>
typedef struct {
int x;
int y;
} Point;
int main()
{ Point s={2,3};
return 0;
}
struct Student {
char name[20];
int age;
float score;
};struct Student stu1 = {"Alice", 18, 95.5f}; // 按 name→age→score 顺序
注意:
- 初始化值的类型和顺序必须与结构体成员匹配(字符串用
""
,浮点数加f
)。- 可省略部分成员(未初始化的成员会被自动赋默认值:数值为 0,指针为
NULL
,字符串为空)像:
struct Student stu2 = {"Bob"}
从上面所讲我们可知结构体变量的初始化按结构体成员声明的顺序依次赋值,用 {}
包裹,逗号分隔:
struct Student {
char name[20];
int age;
float score;
};struct Student stu1 = {"Alice", 18, 95.5f}; // 按 name→age→score 顺序
那如果想按 age→name→score 顺序声明呢?
也是可以的:
可以指定成员初始化”语法,通过 .成员名 = 值
的方式显式指定赋值顺序。
关于. 在三中会有提及。
struct Student {
char name[20];
int age;
float score;
};struct Student stu1={.age = 18, // 先初始化 age
.name = "Alice", // 再初始化 name
.score = 95.5f // 最后初始化 score
}
这样实现的。
(2)定义后赋值(灵活但繁琐)
先定义变量,再通过 .
操作符 逐个给成员赋值:
代码例:
struct Student stu4;
strcpy(stu4.name, "David"); // 注意:字符串需用 strcpy(不能直接赋值)
stu4.age = 19;
stu4.score = 92.3;
若需要完全自由的顺序或复杂赋值逻辑,定义后用 .
操作符逐个赋值是更通用的方案。
三、结构成员访问操作符
结构体成员访问操作符是操作结构体变量成员的核心工具,主要有两种形式:.
(点操作符)和 ->
(箭头操作符),分别用于不同场景,接下来,我将逐一进行讲解:
(1).
操作符(直接访问,用于普通结构体变量)
使用该操作符的语法:
结构体变量名.成员名
- 适用对象:直接定义的结构体变量(非指针)。
- 作用:通过变量名直接访问成员,简单直观。
例:
struct Person {
char name[20];
int age;
};struct Person p; // 普通结构体变量
p.age = 25; // 用 . 访问 age 成员
strcpy(p.name, "Bob"); // 用 . 访问 name 成员(注意:字符串需 strcpy改值)
(2)->
操作符(间接访问,用于结构体指针)
语法:
结构体指针->成员名
- 适用对象:指向结构体的指针变量。
- 作用:通过指针间接访问成员,避免频繁写
(*指针).成员名
的繁琐形式(->
本质是(*指针).成员名
的简写)。
例:
struct Person {
char name[20];
int age;
};struct Person p;
struct Person *ptr = &p; // 指针指向结构体变量 pptr->age = 25; // 用 -> 访问 age 成员(等价于 (*ptr).age = 25)
strcpy(ptr->name, "Bob"); // 用 -> 访问 name 成员(等价于 strcpy((*ptr).name, "Bob"))
注意: 结构体的指针变量的使用除了通过->
操作符,例(p->age),也可以写成(*p).age,通过对该指针变量的解引用,来访问成员,但. 的优先级比*高,所以写的时候,应像(*p).age这样写。
常见使用场景
.
操作符:适合局部变量、栈上定义的结构体,直接访问成员。->
操作符:适合动态内存分配(如malloc
创建的结构体)、函数参数传递指针时,通过指针高效访问成员(避免结构体拷贝开销)。
记住:.对应的是变量,->对应的是指针。
四、结构体内存对⻬
如果想知道变量占用空间大小,可以通过sizeof(变量)来求值,但计算结构体的⼤⼩也一样吗?
通过例子看下:
#include <stdio.h>
#include <string.h>
#include<stdlib.h>
typedef struct STU
{
int age;
char name;
double depth;
}stu;
int main()
{
printf("%d\n",sizeof(stu));
}
假设:结构体大小的计算是每个变量的所占空间加和的结果,那么sizeof(stu)大小应该为:13,但结果并不是:
所以如何计算结构体的⼤⼩?这里就引入了结构体内存对⻬了,求结构体占用空间大小应用到了该规则,接下来我将讲解:
对⻬规则:
1.首先要了解下对⻬的定义:结构体内存对齐是C语言中为提高访问效率而对结构体成员地址进行规则排列的机制。其核心是让成员地址满足特定对齐值,避免因跨硬件字长访问导致性能损耗。
2.对对⻬规则的讲解: (图中竖着的格子为地址,结构体成员并非连续的占用内存地址)
结合图来讲解:
1.结构体的第1个成员对⻬到和结构体变量起始位置偏移量为0的地址处。
图中age从0地址开始(因为int :4字节,所以要占用4个字节的地址)
2.从第2个成员变量开始,都要对⻬到某个对⻬数的整数倍的地址处。(重点:对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。)
图中name需对齐到他的对⻬数的倍数,因为对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。记住:编译器默认的字节数为8。所以1<8,取值1,从4地址开始需对齐到1的倍数,占用1个字节的地址,即4地址处可以。depth也同理,编译器默认的字节数为8, double的字节数为8,可知对⻬数=8。
从5地址开始需对齐到8的倍数,即向后走到8地址处,占用8个字节的地址,最后到了15地址处。
3.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
根据此我们可知:最⼤对⻬数为8,而15并非8的倍数,所以需要在占用空间至最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
8*2=16.
如果两个结构体嵌套了呢?
#include <stdio.h>
#include <string.h>
#include<stdlib.h>
typedef struct STU
{
int age;
char name;
double depth;
} stu;
typedef struct sch
{
stu yang;
int num;
} sch;
int main()
{
printf("%d\n",sizeof(sch));
}
先看结果:
如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处,结构 体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍。
图中yang从0地址开始(因为16字节,所以要占用16个字节的地址).
图中name需对齐到他的对⻬数的倍数,因为对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值。记住:编译器默认的字节数为8。所以4<8,取值4,从16地址开始需对齐到1的倍数,占用4个字节的地址,即16地址处可以,到达19处。
根据此我们可知:最⼤对⻬数为8,而19并非8的倍数,所以需要在占用空间至最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的 整数倍。
3*8=24.
总体来说:结构体的内存对⻬是拿空间来换取时间的做法。
上文我们可知:编译器默认的字节数为8,那么可以修改编译器默认的字节数吗?
是可以的:
#pragma 这个预处理指令,可以改变编译器的默认对⻬数。
使用例:
#include <stdio.h>
#pragma pack(1) //设置默认对齐数为1
struct S
{
char c1;
int i;
char c2;
};
#pragma pack() //取消设置的对齐数,还原为默认
int main()
{
printf("%d\n", sizeof(struct S));
return 0;
}
讲解:
struct S
{
char c1; // 1字节(对齐值=1)
int i; // 4字节(对齐值=1,因#pragma pack(1)取n=1与成员大小4的较小值)
char c2; // 1字节(对齐值=1)
};
- 成员排列:
c1
从地址0开始,占用0~0(1字节);i
无需额外对齐(对齐值=1),紧接c1
从地址1开始,占用1~4(4字节);c2
紧接i
从地址5开始,占用5~5(1字节)。- 总大小:1 + 4 + 1 = 6字节(无需额外补空,因6是最大对齐值1的整数倍)。
总结
以上就是今天要讲的内容,本文介绍了结构体类型的声明、结构体变量的创建和初始化、结构成员访问操作符、结构体内存对⻬知识的相关内容,为本章节知识的内容,希望大家能喜欢我的文章,谢谢各位。