结构体详解:自定义数据类型,为链表做准备,附c语言结构体例子

  新闻资讯     |      2026-02-21 04:20

不少学习C++的友人,于接触链表之际困惑丛生,实际上九成的难题都源自一个未真切掌握的前置知识、也就是结构体没弄得明白透彻。今日此间不多谈玄虚不实的东西了,径直将这个所谓的“数据打包”类别用具拆开解构、剖析得支离破碎,你便能够察觉链表终归只是结构体的自然而然的一种舒展扩充。

为什么需要结构体

想象存在这样一种场景,在此场景之中,你需要去料理十个学生的相关信息,其中涵盖了姓名、年龄以及分数这些方面。传统的处理办法便是分别去定义三个数组,它们分别是,char names[10][20]int ages[10]float scores[10]。如此这般分散进行存储所带来的问题是,同一个学生的那些数据被拆分在不同的数组当中,不管是进行修改还是实施删除之时,都得同步对三个数组展开操作,这种情况是极为容易出现差错的。

在2026年进行编程实践期间么,这种所谓的“平行数组”方式早就已经被舍弃掉了。结构体的关键重要价值其实就是要将那些不同类型然而逻辑上有着关联关系的数据整合拼装成为一个统一的整体。这情形就如同是借助一个快递盒子把书本、手机以及充电宝一同装纳进去,在搬运的时候仅仅只需要拿取一只盒子,而并非是要把这三样物品分别开来去拿取。

#include 
int main() {
char names[10][20]; // 10个学生的姓名
int ages[10]; // 10个学生的年龄
char genders[10]; // 10个学生的性别
 // 输入
for (int i = 0; i < 10; i++) {
printf("请输入第%d个学生的信息:\n", i + 1);
printf("姓名:");
scanf("%s", names[i]);
printf("年龄:");
scanf("%d", &ages[i]);
printf("性别(M/F):");
scanf(" %c", &genders[i]);
 }
return 0;
}

定义和使用结构体

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

创建一个用于表示学生的结构体并不复杂,只需以相关的数据变量为内容,借助struct这个关键词来进行封装就行标点符号。

struct Student {
    char name[20];
    int age;
    float score;
};  // 这里的分号绝对不能少

struct Student {
char name[20]; // 姓名
int age; // 年龄
char gender; // 性别
};

struct Student s1; // 声明一个Student类型的变量

然而,每一回呢声明变量的时候,都得去写上那struct Student stu1,这可挺麻烦的。在工业级别的代码里头,通常会借助typedef来重新定义类型名:

struct Student s1;
struct Student s2;
struct Student s3;

typedef struct Student {
    char name[20];
    int age;
    float score;
} Stu;  // Stu就是新类型名
Stu stu1;  // 直接声明,不用写struct

typedef struct {
char name[20];
int age;
char gender;
} Student;

Student s1; // 不用写 struct 了!
Student s2;
Student s3;

还有种匿名结构体的写法,直接定义变量:

struct {
    char name[20];
    int age;
} person1, person2;  // 但这种无法在其他地方复用

typedef struct {
char name[20]; // 20字节
int age; // 4字节
char gender; // 1字节
} Student;
Student s1;

┌──────────────────────┬─────────┬────┐
│ name[20] │ age │gen │
│ 20字节 │ 4字节 │1字节│
└──────────────────────┴─────────┴────┘

内存对齐的真相

有不少新手会觉察出一种奇特情形,即,有这么三个类型所占字节数,char[20]占据20个字节,int占据4个字节,float占据4个字节,这三者计数合并起来是28个字节,然而,运用sizeof()去进行测量时,得到的结果却是32个字节。这其实就是内存对齐在发挥作用所致。

CPU对内存的读取并非是以逐个字节的形式来进行读取的,而是按照以4字节或者8字节为单位的块来展开读取操作的。在2026年占据主流地位运行x64架构的处理器当中,对于内存对齐方面将会有着更为严格的要求。其内存对齐的具体规则主要存在着两条:其中一条规则是,每一个结构体的成员所具备的起始地址必然得是该成员自身所具有大小的整数倍;另一条规则是,整个结构体的总大小一定得是结构体当中成员最大大小这个数值的整数倍。

typedef struct {
char name[20]; // 20字节
int age; // 4字节
char gender; // 1字节
} Student;

比如说,你将charint毗邻放置,int因要对齐至4的倍数,其前面会空余下3个字节。这便是构体成员顺序对大小产生影响之事由。把体积大的成员置于前方,体积小的成员放在后面进行紧凑排列,能够节省空间。

地址: 0 20 24 25
 ┌──────┬────┬─┬───┐
 │name │age │g│填充│
 │20字节│4字节│1│3字节│
 └──────┴────┴─┴───┘
 20 4 1 3
总计:28字节(4的整数倍)

操作结构体的两种方式

操作结构体成员有两种方法:点号和箭头。点号用于结构体变量:

stu1.age = 18;
strcpy(stu1.name, "张三");

typedef struct {
char name[20]; // 20字节
int age; // 4字节
char gender; // 1字节
} Student; // 28字节

箭头用于结构体指针:

typedef struct {
char name[20]; // 20字节
char gender; // 1字节
int age; // 4字节
} Student; // 28字节

Stu p = &stu1;
p->age = 19;  // 等价于(p).age = 19

typedef struct {
int age; // 4字节
char name[20]; // 20字节
char gender; // 1字节
} Student; // 28字节

于实际开展开发期间,尤其历经链表遍历之际,箭头的应用性是最为频繁的。鉴于链表节点均是借助指针予以联结的,故而类似"p = p->next" 这般的写法通常可以到处被发现,这种常见写法于处处充斥着指针联结链表节点的状况下是极易到处被呈现的。

结构体的高级玩法

typedef struct {
double value; // 8字节
int count; // 4字节
char flag; // 1字节
} Data; // 16字节(8的倍数)

结构体能够进行嵌套,举例来说,在 《code》里存在一个 《Student》,其中包含着一个 《Birthday》结构体在内。然而,最为关键核心的要点是结构体的自引用,具体表现为成员是指向自身类型的指针:

#pragma pack(1) // 强制1字节对齐
typedef struct {
char name[20];
int age;
char gender;
} Student; // 25字节
#pragma pack() // 恢复默认对齐

typedef struct Node {
    int data;
    struct Node next;  // 指向下一个节点
} Node;

这便是链表节点对应的定义,每个节点里面存储数据,与此同时,运用指针去指向接下来的那个节点,如同链条那样串联起来。众多初学者感到困惑之处在于,为何没办法编写Node next,而是必须编写struct Node *next,缘由在于,在typedef完成以前,Node这个类型名称尚不存在。

typedef struct {
char name[20];
int age;
char gender;
} Student;
int main() {
Student s1;
 // 赋值
strcpy(s1.name, "张三");
s1.age = 18;
s1.gender = 'M';
 // 访问
printf("姓名:%s\n", s1.name);
printf("年龄:%d\n", s1.age);
printf("性别:%c\n", s1.gender);
return 0;
}

实战中避坑指南

写结构体之际,存在着若干个坑是需要加以避开的。首先要明确,内存对齐并非是能够予以关闭的,通过#pragma pack(1)来强制进行1字节对齐,虽说这样做能够节省空间,然而却会致使CPU访问效率出现急剧暴跌的情况,仅仅是在诸如嵌入式等特殊场景之下才会采用。其次,结构体赋值属于浅拷贝,要是成员含有指针,那么两个结构体将会指向同一块内存区域,一旦对其中一个进行修改,便会对另一个产生影响。

提议刚开始学习的人自今日起始,将全部散落分散的数据都试着运用结构体予以重构,定义学生相关信息采用结构体,定义图书相关信息采用结构体,哪怕仅仅只是一个坐标点(x, y)同样能够采用结构体,当你对把相关联的数据整合打包到一起形成了习惯,自然而然地就能够领会链表为何需要采用结构体了。

int main() {
Student s1;
Student *p = &s1;
 // 方式1:通过指针和点运算符
 (*p).age = 18;
 // 方式2:通过箭头运算符(推荐)
p->age = 18;
p->gender = 'M';
strcpy(p->name, "张三");
printf("%s, %d岁, %c\n", p->name, p->age, p->gender);
return 0;
}

你认为结构体跟数组于数据组织方式方面,最为突出的差异是什么呢?欢迎在评论区域分享你的认知,点赞数量超过1000我将会作出一期结构体同链表实战的代码详细解析。

p->age ≡ (*p).age