一、结构体基本概念
1.1 什么是结构体?
结构体是一种自定义数据类型,允许将不同类型的数据组合在一起。
1.2 结构体定义语法
struct TypeName {
// 成员变量列表
};
示例:联系人结构体
struct Contact {
int id;
char name[16];
char phone[16];
};
二、结构体的使用
2.1 结构体变量定义
// 1. 定义结构体变量
struct Contact c; // C语言写法
Contact c; // C++写法(推荐)
// 2. 定义结构体数组
Contact contacts[4];
// 3. 定义结构体指针
Contact *pc = &c;
2.2 结构体在函数中的使用
// 作为函数参数
void print_contact(Contact c);
void modify_contact(Contact *c);
// 作为返回值类型
Contact create_contact();
三、结构体初始化
3.1 多种初始化方式
#include <stdio.h>
#include <string.h>
struct Contact {
int id;
char name[16];
char phone[16];
};
int main() {
// 方式1:先定义后赋值
Contact c1;
c1.id = 201501;
strcpy(c1.name, "John");
strcpy(c1.phone, "15913245635");
// 方式2:定义时完全初始化
Contact c2 = {
201502,
"Jennifer",
"13810022334"
};
// 方式3:部分初始化(剩余成员自动清零)
Contact c3 = {
201503,
"Alice"
// phone 自动初始化为空字符串
};
// 方式4:清零初始化
Contact c4 = {0}; // 所有成员设为0或空
return 0;
}
3.2 结构体赋值
Contact a = {20141003, "John", "15913245635"};
Contact b = a; // 结构体支持直接赋值(成员逐个拷贝)
四、嵌套结构体
4.1 结构体包含结构体
struct Score {
float chinese;
float english;
float math;
};
struct Student {
int id;
char name[16];
struct Score score; // 嵌套结构体
};
int main() {
Student s;
s.id = 1001;
strcpy(s.name, "Tom");
s.score.chinese = 88.5; // 访问嵌套成员
s.score.english = 92.0;
s.score.math = 95.5;
return 0;
}
4.2 结构体包含结构体指针
struct Student {
int id;
char name[16];
Score *pscore; // 指向Score的指针
};
int main() {
Score student_score = {88.0, 90.0, 98.0};
Student student;
student.pscore = &student_score;
printf("数学成绩: %.1f\n", student.pscore->math);
return 0;
}
注意:结构体定义需要先定义后使用
五、结构体数组
5.1 定义和初始化结构体数组
struct Contact contacts[4] = {
{201501, "John", "18601011223"},
{201502, "Jennifer", "13810022334"},
{201503, "AnXi", "18600100100"},
{201504, "Unnamed", "18601011223"}
};
// 访问数组元素
contacts[0].id = 201505;
strcpy(contacts[1].name, "Mike");
5.2 遍历结构体数组
void print_contacts(const Contact contacts[], int count) {
for (int i = 0; i < count; i++) {
printf("ID: %d, Name: %s, Phone: %s\n",
contacts[i].id, contacts[i].name, contacts[i].phone);
}
}
六、结构体指针
6.1 结构体指针的使用
Contact person = {20141003, "John", "15913245635"};
Contact *p = &person;
// 使用箭头运算符访问成员
printf("ID: %d\n", p->id);
printf("Name: %s\n", p->name);
// 修改成员
p->id = 20141004;
strcpy(p->name, "David");
6.2 等价访问方式
p->id // 推荐:简洁清晰
(*p).id // 等价,但不常用
七、结构体作为函数参数
7.1 传值 vs 传地址
#include <stdio.h>
// 方式1:传值(不推荐 - 效率低)
void print_contact_by_value(Contact c) {
printf("ID: %d, Name: %s\n", c.id, c.name);
}
// 方式2:传地址(推荐 - 高效)
void print_contact_by_pointer(const Contact *p) {
printf("ID: %d, Name: %s\n", p->id, p->name);
}
// 方式3:修改结构体内容
void update_contact(Contact *p, int new_id, const char *new_name) {
p->id = new_id;
strcpy(p->name, new_name);
}
int main() {
Contact person = {1001, "Alice", "123456789"};
print_contact_by_value(person); // 传值:数据拷贝
print_contact_by_pointer(&person); // 传地址:无拷贝
update_contact(&person, 1002, "Bob"); // 修改原结构体
return 0;
}
7.2 为什么推荐传地址?
- 空间效率:避免复制整个结构体
- 时间效率:避免拷贝大量数据
- 修改能力:可以直接修改原结构体
八、结构体作为函数返回值
8.1 返回结构体
// 方式1:返回结构体(可能产生拷贝)
Contact create_contact(int id, const char *name, const char *phone) {
Contact c;
c.id = id;
strcpy(c.name, name);
strcpy(c.phone, phone);
return c;
}
// 方式2:通过指针参数返回(推荐)
void create_contact2(int id, const char *name, const char *phone, Contact *result) {
result->id = id;
strcpy(result->name, name);
strcpy(result->phone, phone);
}
int main() {
// 方式1使用
Contact c1 = create_contact(1001, "Tom", "111111111");
// 方式2使用
Contact c2;
create_contact2(1002, "Jerry", "222222222", &c2);
return 0;
}
九、匿名结构体
9.1 匿名结构体的使用
// 匿名结构体:只定义变量,不定义类型名
struct {
char guid[128];
int user_id;
} global_info; // 直接定义变量global_info
int main() {
global_info.user_id = 8780087;
strcpy(global_info.guid, "{cfff140d5-af72-44ba-a763-c861304b46f8}");
return 0;
}
注意:匿名结构体无法重用,通常用于一次性场景
十、结构体编程规范
10.1 正确的结构体定义位置
// ✅ 推荐:在头文件中定义
// contact.h
#ifndef CONTACT_H
#define CONTACT_H
struct Contact {
int id;
char name[16];
char phone[16];
};
#endif
10.2 避免的错误写法
// ❌ 不推荐:在函数内部定义结构体
int main() {
struct Contact { // 作用域仅限于本函数
int id;
char name[16];
};
return 0;
}
// ❌ 不推荐:混合定义变量
struct Contact {
int id;
char name[16];
} a, b; // 同时定义变量a,b - 不清晰
十一、结构体内存对齐
11.1 什么是内存对齐?
struct Example {
char a; // 1字节
int b; // 4字节
};
printf("结构体大小: %zu\n", sizeof(struct Example)); // 输出8,不是5!
11.2 内存布局说明
内存地址: 0 1 2 3 4 5 6 7
成员: [a] [填充] [ b ]
说明: char占1字节,但后面3字节被填充以满足对齐
11.3 对齐规则
- 每个成员的起始地址必须是其类型大小的整数倍
- 结构体总大小是其最大成员大小的整数倍
- 编译器会自动插入填充字节保证对齐
11.4 为什么要内存对齐?
- 性能优化:CPU访问对齐的数据更快
- 硬件要求:某些CPU只能访问对齐的地址
- 跨平台兼容:保证在不同系统上行为一致
十二、最佳实践总结
12.1 结构体使用原则
- 明确用途:为相关数据创建结构体
- 合理命名:使用有意义的类型名和成员名
- 头文件定义:在头文件中定义可重用的结构体
- 传址优先:函数参数使用指针传递结构体
- const保护:只读参数加上const修饰
12.2 性能优化建议
// ✅ 高效写法
void process_data(const DataStruct *input, DataStruct *output) {
// 使用指针,避免拷贝
}
// ❌ 低效写法
void process_data(DataStruct input) {
// 传值,产生数据拷贝
}
12.3 代码示例模板
// contact.h
#ifndef CONTACT_H
#define CONTACT_H
#define NAME_LENGTH 16
#define PHONE_LENGTH 16
typedef struct {
int id;
char name[NAME_LENGTH];
char phone[PHONE_LENGTH];
} Contact;
// 函数声明
void contact_init(Contact *contact, int id, const char *name, const char *phone);
void contact_print(const Contact *contact);
int contact_compare(const Contact *a, const Contact *b);
#endif
掌握结构体是C语言编程的重要里程碑,它让你能够组织复杂数据,编写更结构化的代码!