c语音—结构体

1. 结构体基础

  • 是什么:结构体是一种用户自定义的数据类型,它允许你将多个不同类型的变量组合在一起,形成一个逻辑上的整体。这些变量称为成员

  • 为什么:用于描述复杂对象(如学生、图书、坐标点),避免了使用多个分散的变量,使代码更清晰、组织更有序、管理更方便。

  • 定义与声明

    // 定义结构体类型,此时并不分配内存
    struct Student {
        int id;
        char name[20];
        float score;
    };
    
    // 声明结构体变量,此时分配内存
    struct Student stu1; // 方式一:先定义类型,再声明变量
    
    // 定义类型的同时声明变量
    struct Point {
        int x;
        int y;
    } p1, p2; // p1和p2是全局变量
    
    // 使用typedef创建别名(非常常用)
    typedef struct Employee {
        int emp_id;
        char dept[10];
    } Emp; // Emp 是 struct Employee 的别名
    
    Emp e1; // 现在可以用 Emp 来声明变量,更简洁
    

2. 结构体初始化与成员访问

  • 初始化

    // 顺序初始化
    struct Student stu1 = {101, "Alice", 95.5f};
    
    // 指定成员初始化,顺序可以打乱
    struct Student stu2 = {.name = "Bob", .id = 102, .score = 88.0f};
    
    // 部分初始化,未初始化的成员会被系统初始化为0(全局/静态)或随机值(局部)
    struct Student stu3 = {103}; // 只初始化了id,name和score为0或随机值
    
  • 成员访问:使用点操作符 .

    stu1.id = 1001;
    strcpy(stu1.name, "Charlie"); // 对数组赋值需要使用strcpy
    printf("Score: %.1f\n", stu1.score);
    

3. 结构体指针

  • 声明与赋值

    struct Student *pStu;
    pStu = &stu1; // 指向stu1的指针
    
  • 通过指针访问成员:使用箭头操作符 ->

    
    (*pStu).id = 1002;  // 先解引用,再用点操作符,括号必不可少
    pStu->id = 1002;    // 更简洁、更常用的箭头操作符
    

4. 结构体数组

  • 声明与初始化
    // 声明一个包含3个元素的结构体数组
    struct Student class[3] = {
        {1, "Tom", 99.5f},
        {2, "Jerry", 80.0f},
        {3, "Spike", 60.5f}
    };
    
    // 访问数组中的元素
    printf("The second student's name is %s\n", class[1].name);
    class[2].score = 61.0f; // 修改第三个学生的分数
    
    // 指针遍历数组
    struct Student *p;
    for (p = class; p < class + 3; p++) {
        printf("ID:%d, Name:%s\n", p->id, p->name);
    }
    

5. 结构体与函数

  • 值传递:将整个结构体拷贝一份传给函数。缺点:如果结构体很大,拷贝开销大。

    void printStudent(struct Student s) {
        printf("ID: %d\n", s.id);
    }
    printStudent(stu1); // 发生一次结构体拷贝
    
  • 地址传递:传递结构体的指针。优点:高效,无拷贝开销;缺点:函数内部可以修改原结构体(如果不想被修改,可用 const 修饰指针)。

    void updateScore(struct Student *s, float new_score) {
        s->score = new_score; // 会修改原结构体的值
    }
    updateScore(&stu1, 100.0f);
    
    // 使用const防止修改
    void printStudentConst(const struct Student *s) {
        // s->id = 5; // 这行代码会编译错误,因为s指向的内容是只读的
        printf("ID: %d\n", s->id);
    }
    

    面试重点永远优先使用传递指针的方式,除非结构体非常小。

6. 字符串操作(重点)

字符数组赋值

  • 错误char str[20]; str = "Hello"; (数组名是常量地址,不能被赋值)
  • 正确:使用 strcpy 函数:strcpy(str, "Hello");

常用字符串函数 (#include <string.h>)

  • strlen():获取长度
  • strcpy(): 字符串复制。
  • strcmp():字符串比较
  • strcat(): 字符串连接。
  • strstr(): 在主串中寻找子串。
  • strtok(): 用分隔符切割字符串。
  • itoa(): 将整数转换为字符串。

常用内存操作函数 (#include <string.h>)

  • memset(): 将内存块的前 n 字节设置为特定值。
  • memcpy(): 从源内存地址拷贝 n 字节到目标地址。
  • memmove(): 功能同 memcpy,但能正确处理内存重叠区域。

面试题:内存对齐

1、为什么要进行内存对齐?内存对齐的优点。

  • ① 什么是内存对齐:数据在内存中的起始地址按照一定的规则进行排列,而不是顺序紧密地排列。
  • ② 优势:提升CPU的访问效率
    • 内存划分的基本单位是字节,但CPU访问内存不是逐字节读取,而是通过内存总线按照“块”(如2B、4B、8B)进行读取。
    • 对齐的好处:如果数据存储遵循内存对齐规则,CPU可以在一个读取周期内就完整地获取到数据。
    • 不对齐的代价:如果数据存储未遵循对齐规则,它可能跨越两个或多个CPU读取块。这时需要多个读取周期,并将读取到的字节块进行拼接才能得到完整数据,这大大降低了访问效率。

2、怎么进行内存对齐?

  • 默认对齐方式
    • Windows默认:8字节对齐
    • Linux默认:4字节对齐
  • 指定对齐方式
    • 可以使用预编译指令(如#pragma pack(n))来显式地指定对齐模数。
      fe6515c7b3dabedc5354712a7a97717

3、求结构体大小(遵循的规则)

计算结构体大小需要遵循以下三个核心规则:

  1. 成员变量偏移量规则:结构体内每个成员变量的起始地址的偏移量(相对于结构体首地址),必须是 Min(该成员自身大小, 当前有效对齐模数) 的整数倍。
  2. 结构体总大小规则:整个结构体的总大小,必须是 Min(结构体中最大基本成员的大小, 当前有效对齐模数) 的整数倍。
  3. 结构体嵌套规则:在计算含有嵌套结构体的复杂结构时,子结构体的“最大基本数据类型” 会被视为当前结构体的一个普通成员来参与规则2的计算。
    例题:
    4b5f74c867197787deb185e29ff839c
posted @ 2025-08-21 10:48  四毛mao  阅读(52)  评论(0)    收藏  举报