C语言学习4-结构体

一、结构体基本概念

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 对齐规则

  1. 每个成员的起始地址必须是其类型大小的整数倍
  2. 结构体总大小是其最大成员大小的整数倍
  3. 编译器会自动插入填充字节保证对齐

11.4 为什么要内存对齐?

  • 性能优化:CPU访问对齐的数据更快
  • 硬件要求:某些CPU只能访问对齐的地址
  • 跨平台兼容:保证在不同系统上行为一致

十二、最佳实践总结

12.1 结构体使用原则

  1. 明确用途:为相关数据创建结构体
  2. 合理命名:使用有意义的类型名和成员名
  3. 头文件定义:在头文件中定义可重用的结构体
  4. 传址优先:函数参数使用指针传递结构体
  5. 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语言编程的重要里程碑,它让你能够组织复杂数据,编写更结构化的代码!

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容