C语言学习笔记——10(结构体)

目录

结构体定义

初始化结构体

(1)定义时直接初始化

(2)先定义后赋值

利用结构体录入学生信息

结构体指针

指针存储的是地址

用指针改变结构体的变量

[通过指针访问成员(-> 操作符的本质)](#通过指针访问成员(-> 操作符的本质))

从内存角度解释如何用指针改变结构体的变量

指针存储的是首地址

[-> 运算符的本质:基址 + 偏移](#-> 运算符的本质:基址 + 偏移)

[修改 name(字符串数组)](#修改 name(字符串数组))

[修改 sex 和 score](#修改 sex 和 score)

注意

[-> 和 . 的区别](#-> 和 . 的区别)

结构体指针作为函数参数

动态分配的结构体指针

指向结构体数组的指针

避免返回局部结构体变量的指针

[const 与结构体指针](#const 与结构体指针)


结构体定义

复制代码
struct 结构体名{ 
数据类型 成员1; 
数据类型 成员2;
 // ... 
};
  • 定义结构体变量时,系统会在内存中分配一块连续的空间,大小至少为各成员大小之和(实际因内存对齐可能更大)。

  • 成员按照声明顺序依次排列:成员1在最低地址,成员2最高地址

初始化结构体

(1)定义时直接初始化
复制代码
struct Student stu = {1001, "张三", 'M', 89.5};
  • 赋值顺序必须和结构体成员定义顺序一致。
(2)先定义后赋值
复制代码
struct Student stu;
stu.id = 1001;
// stu.name = "张三"; 错误!字符串不能直接用=赋值
strcpy(stu.name, "张三");
stu.sex = 'M';
stu.score = 89.5;
  • 字符数组成员必须用 strcpy 赋值。

利用结构体录入学生信息

复制代码
#include <stdio.h>

struct Student {
    int id;
    char name[20];
    char sex;
    float score;
};

int main() {
    struct Student stu;

    printf("请输入学号: ");
    scanf("%d", &stu.id);

    printf("请输入姓名: ");
    scanf("%s", stu.name);


    printf("请输入性别: ");
    scanf("%c", stu.sex);

    printf("请输入成绩: ");
    scanf("%f", &stu.score);

    printf("\n===== 学生信息 =====\n");
    printf("学号: %d\n", stu.id);
    printf("姓名: %s\n", stu.name);
    printf("性别: %c\n", stu.sex);
    printf("成绩: %.1f\n", stu.score);

    return 0;
}

显示 sex 地址为 0x7ffecd7f7078score0x7ffecd7f707c,中间有 3 字节填充。

结构体指针

指针存储的是地址

struct Student *p = &stu;

  • 指针变量 p 本身也占用内存(通常 8 字节),它里面存放的是 stu起始地址 (即 &stu)。

  • p 的值就是 stu 的首地址,&p 是指针变量自己的地址。

    #include <stdio.h>

    struct Student {
    int id; // 4 字节
    char name[20]; // 20 字节
    char sex; // 1 字节
    float score; // 4 字节
    }; // 结构体总大小可能因对齐而大于 4+20+1+4 = 29 字节

    int main() {
    struct Student stu = {
    1001,
    "张三",
    'M',
    89.5
    };

    复制代码
      struct Student *p = &stu;
      printf("结构体变量 stu 的起始地址: %p\n", &stu);
      printf("p 的内容: %p\n", p);
      printf("&p 的地址: %p\n", &p);
      printf("\n======== 成员信息 ========\n");
      printf("成员 id     : 值 = %d, 地址 = %p\n", p->id, &(p->id));
      printf("成员 name   : 值 = %s, 地址 = %p\n", p->name, p->name); // 数组名即地址
      printf("成员 sex    : 值 = %c, 地址 = %p\n", p->sex, &(p->sex));
      printf("成员 score  : 值 = %.1f, 地址 = %p\n", p->score, &(p->score));
    
      printf("\n结构体 Student 的大小: %zu 字节\n", sizeof(struct Student));
    
      return 0;

    }

用指针改变结构体的变量

通过指针访问成员(-> 操作符的本质)

复制代码
p->id = 1002;
  • 编译器知道结构体成员的类型和偏移量:

    • id 偏移 0 字节

    • name 偏移 4 字节(假设对齐后)

    • sex 偏移 24 字节(4 + 20)

    • score 偏移28 字节(对齐后)

  • p->id 等价于 *(p + 偏移量)。因为 p 是结构体指针,p+0 还是 p,所以 p->id 就是p 指向的地址开始,取前 4 字节作为 int

  • 同理 p->score:从 p 的地址加上 score 的偏移量,取出 4 字节作为 float。

    #include <stdio.h>
    #include <string.h>

    struct Student {
    int id;
    char name[20];
    char sex;
    float score;
    };

    int main() {
    struct Student stu = {1001, "张三", 'M', 85.5};
    struct Student *p = &stu; // 指针指向 stu

    复制代码
      printf("修改前:\n");
      printf("学号: %d, 姓名: %s, 性别: %c, 成绩: %.1f\n", stu.id, stu.name, stu.sex, stu.score);
    
    
      p->id = 1002;
      strcpy(p->name, "李四");   // 字符串不能用 = 直接赋值,用 strcpy
      p->sex = 'F';
      p->score = 92.0;
    
    
      printf("\n修改后:\n");
      printf("学号: %d, 姓名: %s, 性别: %c, 成绩: %.1f\n", stu.id, stu.name, stu.sex, stu.score);
    
      return 0;

    }

从内存角度解释如何用指针改变结构体的变量

指针存储的是首地址
  • p 这个变量里保存的就是 0x7ffecd7f7060

  • 编译器知道 struct Student 的每个成员的类型偏移量

-> 运算符的本质:基址 + 偏移

例如:

p->id = 1002;

编译器将其转换为:

复制代码
*(int*)((char*)p + 0) = 1002;
  • (char*)p 将 p 转为字节指针,方便做字节偏移。

  • 加 0 得到 id 的地址。

  • 再转成 int* 然后解引用写入。

修改 name(字符串数组)
复制代码
strcpy(p->name, "李四");
  • p->name 的类型是 char[20],在表达式中退化为 char*,其值就是 (char*)p + 4

  • strcpy"李四" 的每个字节(包括结尾的 \0)从地址 0x7ffecd7f7064 开始逐个写入,覆盖原来的 "张三"

修改 sex 和 score
复制代码
p->sex = 'F';      // 写入地址 p + 24
p->score = 92.0;   // 写入地址 p + 28
  • p->sex 对应地址 0x7ffecd7f7078,一个字节,写入 'F' (ASCII 0x46)。

  • p->score 对应地址 0x7ffecd7f707c,四个字节,写入 92.0 的 IEEE 754 表示。

注意

->. 的区别
  • 结构体变量用 .stu.id

  • 结构体指针用 ->p->id

  • 等价写法:(*p).id(括号不能少,因为 . 优先级高于 *

  • 常见错误:*p.id → 实际被解析为 *(p.id),但 p 不是结构体,编译报错。

结构体指针作为函数参数
复制代码
void modify(struct Student *p) {
    p->score = 100.0;      // 可以改变外部 stu
}

void wrong(struct Student stu) {
    stu.score = 100.0;     // 只是修改副本,外部不变
}

内存原因:传指针只复制 8 字节地址,仍指向原结构体;传值会复制整个 32 字节,修改的是副本。

动态分配的结构体指针
复制代码
struct Student *p = malloc(sizeof(struct Student));
if (p == NULL) { /* 处理失败 */ }
p->id = 1003;               // 安全
strcpy(p->name, "王五");
free(p);                    // 必须释放,否则内存泄漏
p = NULL;                   // 避免野指针

注意 :动态分配的内存位于堆区,使用完必须 free,且释放后不能再通过 p 访问成员。

指向结构体数组的指针
复制代码
struct Student arr[3];
struct Student *q = arr;    // 指向第一个元素
q->id = 1001;               // 等价于 arr[0].id
(q+1)->id = 1002;           // 等价于 arr[1].id

注意q+1 会向前移动 sizeof(struct Student) 字节(32 字节),而不是 1 字节。

避免返回局部结构体变量的指针
复制代码
struct Student* bad() {
    struct Student stu = {1001, "张三", 'M', 89.5};
    return &stu;   // 危险!stu 是局部变量,函数结束即销毁
}

内存原因:局部变量在栈上,函数返回后栈帧被回收,指针指向的内存可能被覆盖。

const 与结构体指针
复制代码
const struct Student *p = &stu;   // 指向常量的指针,不能通过 p 修改成员
p->id = 1002;                     // 编译错误

struct Student *const p = &stu;   // 常量指针,不能指向别的变量
p = &stu2;                        // 错误
相关推荐
最贪吃的虎2 小时前
我的第一个 RAG 程序:从 0 到 1,用 PDF 搭一个最小可运行的知识库问答系统
人工智能·python·算法·机器学习·aigc·embedding·llama
Lufeidata2 小时前
go语言学习记录-入门阶段2
学习·microsoft·golang
551只玄猫2 小时前
【数学建模 matlab 实验报告5】最短路问题作业
开发语言·数学建模·matlab·课程设计·图论·最短路径·实验报告
不只会拍照的程序猿2 小时前
《嵌入式AI筑基笔记04:python函数与模块01—从C的刻板到Python的灵动》
c语言·开发语言·笔记·python
计算机学姐2 小时前
基于SpringBoot的在线课程学习网站
java·vue.js·spring boot·后端·学习·spring·intellij-idea
ada0_ada12 小时前
Qt的Widgets项目
开发语言·qt
1104.北光c°2 小时前
Leetcode146 LRU缓存的三种写法 【hot100算法个人笔记】【java写法】
java·开发语言·笔记·算法·leetcode·hot100·lru缓存
我叫洋洋2 小时前
[STM32 和 PWM 输出 结合 proteus 仿真]
stm32·嵌入式硬件·proteus