当前位置: 首页 > news >正文

结构体指针:使用结构体指针访问和修改结构体成员。

知识点

  • 结构体指针

    • Employee *p; 保存结构体的地址;

    • p->member 用箭头运算符访问或修改成员。

  • 数组与指针

    • Employee *emps = malloc(N * sizeof *emps); 动态创建结构体数组;

    • p < emps + Np++ 配合遍历。

  • scanf 与数组退化

    • p->namechar name[50] 的首地址,无需 &

    • %49s 限制最大读取字符数,防止溢出。

  • 函数参数传递

    • give_raise(Employee *e, ...) 传入指针,不拷贝整个结构体;

    • 在函数内部用 -> 修改原变量。

  • 动态内存管理

    • malloc 申请、free 释放,避免内存泄露。

通过本练习,你将深入理解如何用结构体指针灵活高效地访问和修改结构体成员。

题目描述
本题要求你熟练使用 结构体指针 来访问和修改结构体成员,并将其与数组和动态内存结合:

  1. 定义一个 Employee 结构体,包含员工姓名、工号和工资;

  2. main 中使用 malloc 动态分配一个 Employee 数组,长度为 3;

  3. 通过 结构体指针箭头运算符->)读取用户输入并打印初始信息;

  4. 实现函数 void give_raise(Employee *e, double pct);,通过传入结构体指针给指定员工加薪;

  5. main 中用指针遍历全体员工,统一加薪 10%,然后再次打印更新后的信息;

  6. 最后释放动态内存并退出。

参考代码:

#include <stdio.h>
#include <stdlib.h>#define N 3
#define NAME_LEN 50//1.结构体定义:保存员工信息
typedef struct
{char name[NAME_LEN];//员工姓名int id;//员工号double salary;//工资
} Employee;/**给单个员工加薪*@param e 指向Employee的指针*@param pct 加薪百分比,列入加10表示加10%*/
void give_raise(Employee *e ,double pct)
{//e->salary等价于(*e).salarye->salary *=(1.0 + pct / 100.0);
}int main(void)
{Employee *emps = malloc(N * sizeof *emps);if(!emps){fprintf(stderr,"内存分配失败!\n");return EXIT_FAILURE;}//2.读取初始信息for(int i = 0;i<N;i++){Employee *p = &emps[i];//指向第i个结构体printf("请输入员工%d的 姓名 工号 工资:",i+1);//p->name自动退化为char*if(scanf("%49s %d %lf",p->name,&p->id,&p->salary) != 3){fprintf(stderr,"输入格式错误!\n");free(emps);return EXIT_FAILURE;}}//3.打印初始信息printf("\n======初始员工信息==========\n");for(Employee *p = emps; p < emps+N; p++){//p是Employee*,用->访问成员printf("姓名:%-10s  工号:%4d  工资%.2f\n",p->name,p->id,p->salary);}//4.为所有员工加薪10%for(Employee *p = emps; p < emps + N ; p++){give_raise(p,10.0);}//5.打印加薪后的信息printf("\n=======加薪后员工信息(10%)=========\n");for(Employee *p = emps ; p < emps + N;p++){printf("姓名:%-10s  工号:%4d  工资%.2f\n",p->name,p->id,p->salary);}free(emps);return EXIT_SUCCESS;
}

代码逐行分析

  1. 结构体定义

    typedef struct { char name[NAME_LEN]; int id; double salary; } Employee;

    • name 是字符数组,存放 C 字符串;

    • id 是员工工号;

    • salary 是工资。

  2. 动态分配

    Employee *emps = malloc(N * sizeof *emps);

    • malloc 申请 NEmployee 大小的连续内存;

    • sizeof *emps 等同 sizeof(Employee)

    • 若返回 NULL,则分配失败。

  3. 读取输入

    Employee *p = &emps[i]; scanf("%49s %d %lf", p->name, &p->id, &p->salary);

    • p = &emps[i]p 指向第 i 个结构体;

    • p->name(无 &):数组名在表达式中退化为 char *

    • &p->id&p->salary:取基本类型变量的地址。

  4. 打印初始信息

    for (Employee *p = emps; p < emps + N; p++) { printf("%s %d %.2f\n", p->name, p->id, p->salary); }

    • 指针 pemps(首地址)向后移动,直到末尾。

  5. 加薪函数

    void give_raise(Employee *e, double pct) { e->salary *= (1 + pct/100); }

    • e->salary 等价 (*e).salary

    • 直接修改了原内存中的 salary

  6. 加薪后打印
    同上,只是数据已被 give_raise 更新。

  7. 释放内存

    free(emps);

Employee *emps = malloc(N * sizeof *emps);

这一行的目的是在堆上动态分配一块足够存放 NEmployee 结构体的连续内存,并让指针 emps 指向它。分解来看:

 sizeof(Employee *) ——也就是指针本身的大小,通常是 8 字节,表示“*emps 的类型大小”,即 sizeof(Employee)。)

  1. Employee *emps
    声明了一个指针变量 emps,它将用来保存那块新分配内存的起始地址。

  2. malloc(...)
    从堆上申请一段未初始化的内存,返回一个 void *,随后被赋值给 emps

  3. N * sizeof *emps

    • *emps 的类型是 Employee,所以 sizeof *emps 等价于 sizeof(Employee)——也就是一个员工记录所占的字节数。

    • 将它乘以 N,就得到存放 NEmployee 结构体所需的总字节数。

  4. 赋值给 emps
    malloc 返回的 void * 自动转为 Employee *(在 C 中不需要显式 cast),于是 emps 就指向了这块能容下 NEmployee 的内存。

之后你就可以像操作数组一样,用 emps[0]emps[N-1] 来读写这些动态分配的 Employee 结构了。记得在最后用 free(emps); 释放这段内存,避免泄露。

for (Employee *p = emps;   p < emps + N;   p++) {/* 循环体:用 p->… 访问或修改当前 Employee */
}
  1. 初始化Employee *p = emps;

    • 声明了一个指针变量 p,类型是 “指向 Employee 的指针” (Employee *)。

    • 把它初始化为 emps,也就是指向刚刚用 malloc 分配的结构体数组的第 0 个元素(emps[0])的地址。

  2. 循环条件p < emps + N

    • emps 是数组首地址,emps + N 是“跳过 N 个 Employee 大小的字节”后的位置,也就是数组末尾之后的地址。

    • 只要 p 指向的地址 严格小于 emps + N(即还没走到数组末尾后),就继续执行循环体。

    • 这样保证 p 会依次指向 emps[0]emps[1]emps[N-1],不会越界。

  3. 迭代表达式p++

    • 这是指针算术,每执行一次 p++p 都会向后移动 一个 Employee 对象的大小,等价于 p = p + 1;

    • 所以第一次循环 p==emps(第 0 个),第二次 p==emps+1(第 1 个),……,直到 p==emps+N-1(第 N-1 个)。


整体流程

  • 第一步p = emps; 指向第一个员工结构。

  • 检查p < emps + N ? 对于 N=3,就检查 p < emps+3,当 pemps+2 时依然进入;当 p 自增到 emps+3 时条件不满足,循环结束。

  • 循环体:在 { … } 中,你可以写 printf("%s", p->name);give_raise(p, 10);p->member 就是访问当前 Employee 的成员。

  • 迭代:每次循环结束后执行 p++,跳到下一个元素。

这种“用指针当下标” 的写法在处理动态分配的数组、或者需要同时传递首地址和尾后指针(empsemps+N)时特别方便,也更贴近 C 底层对内存的操作方式。

Employee *p = &emps[i];
  • emps 是什么?

    • 在前面我们用 mallocEmployee emps[N]; 得到了一块连续的内存,里面按顺序存放了 N 个 Employee 结构体对象。

    • emps 在表达式里会退化为指向第 0 个元素的指针,类型是 Employee *

  • emps[i] 得到第 i 个结构体

    • 通过数组下标 iemps[i] 就是第 iEmployee 变量,类型是 Employee

  • &emps[i] 取出它的地址

    • 前面加上取地址符 &&emps[i] 的类型就是 Employee *,表示“指向第 i 个结构体”的指针。

  • 把地址赋给 p

    • 声明 Employee *p,就是一个可以保存 Employee 对象地址的指针变量。

    • p = &emps[i]; 后,p 就指向了 emps 数组中的第 i 个元素。

  • 后续怎么用?

    • 既然 p 指向了那块内存,就可以用箭头运算符访问或修改它的成员:

    • p->id     = 1234;
      printf("%s\n", p->name);
      

      这等价于对 emps[i] 本身做操作:

    • emps[i].id   = 1234;
      printf("%s\n", emps[i].name);
      

      总结

    • Employee *p 是一个指向 Employee 的指针;

    • &emps[i] 是取得数组中第 i 个结构体的地址;

    • 把它们结合,就能“通过指针”来访问或修改 emps[i]

if (scanf("%49s %d %lf", p->name, &p->id, &p->salary) != 3)…
  1. scanf 的格式串

    • %49s

      • 读入一个不含空白(空格、Tab、换行)的字符串,最多读 49 个字符,自动在第 50 个位置写入 '\0'

      • 这样保证不会超出 p->name 的缓冲区(char name[50])。

    • %d:读入一个十进制整数,存到后面对应的 int * 地址。

    • %lf:读入一个双精度浮点数(double),存到对应的 double * 地址。

  2. 参数列表

    • p->name

      • pEmployee *p->name 就是其内部 char name[50] 数组名,退化成 char *,正好匹配 %s

      • 不需要再写 &p->name,因为数组名已经是地址。

    • &p->id

      • p->id 是一个 int%d 需要 int *,所以要取地址。

    • &p->salary

      • p->salarydouble%lf 需要 double *,同样取地址。

  3. 返回值检查

    • scanf 成功读取并赋值的项数应该正好是 3(字符串、整数、浮点各一项)。

    • 如果不等于 3,就意味着输入格式有误,通常需要进入 if 块做错误处理(如打印提示并退出)。

printf("姓名:%-10s  工号:%4d  工资:%.2f\n",p->name, p->id, p->salary);
  • 格式串解释

    • %-10s:打印一个字符串,左对齐,占 10 个字符宽度,不足的右侧补空格。

    • %4d:打印一个整数,右对齐,占 4 个字符宽度,左侧不足补空格。

    • %.2f:打印一个浮点数,默认右对齐,保留 小数点后 2 位,小数点和整数部分一并计算宽度(这里未指定最小宽度)。

  • 参数

    • p->name:要打印的字符串起始地址。

    • p->id:要打印的整数学号。

    • p->salary:要打印的浮点工资。

  • 举例
    如果 p->name = "Alice"p->id = 42p->salary = 5230.5,则输出:

  • 姓名:Alice       工号:  42  工资:5230.50
    

    • "Alice" 占 5 字符,%-10s 会在后面补 5 个空格;

    • " 42" 占 4 字符;

    • "5230.50" 是默认紧凑输出两位小数。

  • 总结

  • 这两行一行负责安全地从输入流中读取一个字符串、一个整数和一个双精度浮点数,并检查是否都读对了;

  • 另一行则用格式化对齐的方式,按“左对齐姓名、右对齐工号和保留两位小数的工资”整齐地打印出来。

结构体指针的原理与作用

1. 内存与指针的关系

  • 结构体对象
    当你写 Employee emps[N]; 或者用 malloc 得到一块连续内存时,系统会在内存中为每个 Employee 分配一段固定大小的空间,按成员顺序排列:

    [ name[50] ][ id (4B) ][ salary (8B) ] [ name[50] ][ id (4B) ][ salary (8B) ] …

  • 指针(Employee *p
    p 保存的就是某个 Employee 对象在内存中的起始地址(即它第一个成员 name 的首字节地址)。

  • p + 1
    指针算术:当 p 的类型是 Employee * 时,p + 1 会自动跳过 sizeof(Employee) 字节,指向下一个结构体对象。


2. 访问与修改成员

  • 点运算符 .:用于结构体变量本身

    Employee e; e.id = 1001;

  • 箭头运算符 ->:用于结构体指针

    Employee *p = &e; p->id = 1001; // 等价于 (*p).id = 1001;

    • p->id 先解引用 p(得到一个结构体),再访问它的 id 成员。

    • 语法更简洁,不需要写 (*p).id


3. 作用与优势

  1. 动态管理

    • mallocfree 可在运行时灵活控制结构体数组的大小;

    • 只需存一个指针,不必用固定长度的全局或栈数组。

  2. 高效传参

    • 将指针传给函数(如 give_raise(Employee *e, …)),只复制 8 字节地址,不复制整个结构体(可能几十字节甚至更多)。

    • 函数内部直接修改原对象,无需返回修改后的新副本。

  3. 遍历与通用性

    • 结构体数组指针 emps 既能像数组那样使用下标 emps[i],也能用指针算术 for (Employee *p = emps; p < emps+N; p++) 遍历;

    • 这种“首地址 + 元素大小” 的通用访问方式,使得代码更简洁、可移植。

  4. 抽象与封装

    • 函数只关心“指向某个结构体”的地址,无需知道结构体在栈还是堆,也不关心它前后还有多少元素;

    • 例如 give_raise 只用 Employee *e,对任何单个员工对象都通用。


小结

  • 结构体指针 本质上就是保存了“某个结构体对象首地址”的变量。

  • 通过 p->member 或者指针算术,你可以任意访问、修改该对象乃至紧邻的那些同类型对象。

  • 它让我们可以在动态内存函数调用数据遍历中,都以同一种“指针+大小” 的模式来高效操作结构化数据。

http://www.dtcms.com/a/270847.html

相关文章:

  • 【网络】Linux 内核优化实战 - net.ipv4.tcp_ecn_fallback
  • softmax
  • GitHub 趋势日报 (2025年07月08日)
  • SQLZoo 练习与测试答案汇总(复杂题有最优解与其他解法分析、解题技巧)
  • 分类预测 | Matlab基于KPCA-ISSA-SVM和ISSA-SVM和SSA-SVM和SVM多模型分类预测对比
  • 打造自己的组件库(二)CSS工程化方案
  • Tensorflow的安装记录
  • 一天一道Sql题(day04)
  • 开源链动2+1模式与AI智能名片融合下的S2B2C商城小程序源码:重构大零售时代新生态
  • 华为静态路由配置
  • linux正向配置dns解析
  • 事件驱动架构
  • 汽车工业制造领域与数字孪生技术的关联性研究​
  • UI前端大数据处理性能评估与优化:基于负载测试的数据处理能力分析
  • 利用Wisdom SSH高效搭建CI/CD工作流
  • python Gui界面小白入门学习
  • # Shell 编程:从入门到实践
  • Android 系统默认代码,如何屏蔽相册分享功能
  • Android 组件内核
  • Go语言高级面试必考:切片(slice)你真的掌握了吗?
  • 设计模式(行为型)-责任链模式
  • golang条件编译:Build constraints
  • bash 判断 /opt/wslibs-cuda11.8 是否为软连接, 如果是,获取连接目的目录并自动创建
  • 基于Java+Maven+Testng+Selenium+Log4j+Allure+Jenkins搭建一个WebUI自动化框架(2)对框架加入业务逻辑层
  • 金融时间序列机器学习训练前的数据格式验证系统设计与实现
  • React对于流式数据和非流式数据的处理和优化
  • 【实战】Dify从0到100进阶--知识库相关模型原理
  • 【编程史】IDE 是谁发明的?从 punch cards 到 VS Code
  • 【Python基础】变量、运算与内存管理全解析
  • Vue的watch和React的useEffect