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

知识点

  • 结构体指针

    • 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.pswp.cn/news/913733.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/913733.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

支持零样本和少样本的文本到语音48k star的配音工具:GPT-SoVITS-WebUI

支持零样本和少样本的文本到语音48k star的配音工具&#xff1a;GPT-SoVITS-WebUI 官网&#xff1a;RVC-Boss/GPT-SoVITS: 1 min voice data can also be used to train a good TTS model! (few shot voice cloning) 用户手册&#xff1a;GPT-SoVITS指南 功能 零样本文本到语…

基于odoo17的设计模式详解---备忘模式

大家好&#xff0c;我是你的Odoo技术伙伴。在开发复杂的业务流程时&#xff0c;我们有时会遇到这样的需求&#xff1a;在对一个对象进行一系列复杂操作之前&#xff0c;保存其当前状态&#xff0c;以便在操作失败或用户希望撤销时&#xff0c;能够一键恢复到操作之前的样子。或…

基于Web门户架构的监狱内网改版实践:值班排班系统设计与信创适配探讨

面向监狱内网改版场景的门户平台技术架构与智能排班实践关键词&#xff1a;监狱内网改版、监狱内部网站改版、值班排班系统、信创适配、智能门户架构一、场景背景与问题分析 在信创国产化、等级保护合规、政务集约化趋势持续推进的背景下&#xff0c;传统监狱内部网站普遍面临如…

二分查找篇——在排序数组中查找元素的第一个和最后一个位置【LeetCode】

34. 在排序数组中查找元素的第一个和最后一个位置 一、算法逻辑&#xff08;逐步通顺讲解每一步思路&#xff09; 该算法用于在一个升序排列的数组 nums 中查找某个目标值 target 的第一个出现的位置和最后一个出现的位置。 ✅ 1️⃣ 定义 lower_bound 函数 def lower_boun…

【深度学习新浪潮】AI在材料力学领域的研究进展一览

一、材料力学的研究范畴 材料力学是固体力学的核心分支,聚焦于材料在载荷作用下的变形、失效规律及性能优化,其核心任务是揭示材料的强度、刚度和稳定性机制。具体研究内容包括: 基本力学行为:分析杆、梁、轴等结构在拉伸、压缩、弯曲、扭转等载荷下的应力分布与应变响应。…

WPF之命令

命令的定义&#xff1a;命令与事件的区别&#xff1a;命令是具有约束性的。命令还可以控制接收者"先做校验&#xff0c;再保存&#xff0c;再关闭"。命令&#xff1a;WPF的命令&#xff0c;实际上就是实现了ICommand接口的类&#xff0c;平时使用最多的是RoutedComma…

百度文心一言开源大模型ERNIE-4.5-0.3B-PT深度测评

号外号外&#xff01;6月30号&#xff0c;百度文心一言官宣开源ERNIE 4.5大模型&#xff01;&#xff01;&#xff01; 一收到这个消息&#xff0c;博主就立马从GitCode拉了个模型&#xff0c;本地私有化部署体验了一下&#xff0c;一个字&#xff0c;酷&#xff01; 鉴于绝大…

零基础,使用Idea工具写一个邮件报警程序

打开idea&#xff0c;创建一个project打开文件目录下的pom.xml文件&#xff0c;添加下面的内容安装依赖&#xff0c;等待下载完成<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-mail</artifactId> &…

字体 Unicode 区块字符展示 PDF 生成器

Unicode 字体字符集可视化工具 - 代码介绍 项目概述 这个工具是一个用于分析和可视化字体文件中包含的 Unicode 字符的实用程序&#xff0c;能够扫描指定字体文件&#xff0c;提取其中包含的所有 Unicode 字符&#xff0c;并按 Unicode 区块分类生成 PDF 文档&#xff0c;直观展…

第4章:实战项目一 打造你的第一个AI知识库问答机器人 (RAG)

各位老铁&#xff0c;欢迎来到我们专栏的第一个实战项目。 在过去的三个章节里&#xff0c;我们已经完成了所有的理论储备和环境搭建。我们理解了LLM的本质&#xff0c;掌握了Prompt Engineering的要领&#xff0c;洞悉了Embedding和向量数据库的魔力&#xff0c;并且熟悉了La…

身份证识别api-便捷生活与安全社会的双重保障

身份证识别技术是人工智能和图像处理领域的杰出产物之一&#xff0c;正逐步渗透到我们生活的方方面面。而最直观的作用就是简化身份证验证流程。现如今&#xff0c;无论是银行开户、酒店入住还是政务办理、线上支付&#xff0c;都需要输入 身份证信息进行身份验证&#xff0c;传…

跨国企业进入中国市场:如何利用亚马逊云科技文档 MCP 服务器解决区域差异问题

业务场景 想象一下&#xff0c;您是一家美国科技公司的 IT 架构师&#xff0c;公司刚刚决定将业务扩展到中国市场。作为技术负责人&#xff0c;您需要规划如何将现有的基于亚马逊云科技的应用迁移到中国区域。然而&#xff0c;您很快发现中国区的云服务环境与您熟悉的全球区域…

WPF使用WebBrowser 解决href标签target=_blank在浏览器窗口打开新链接而非窗体内部打开的问题

前言 最近在WPF中使用WebBrowser控件显示网页的时候遇到一个问题,由于网页里面有大规模的连接标签使用了target=_blank的属性,导致打开的网页不是在我们的程序内部,而是调用系统浏览器打开了我们的网页内容,这种情况非常的影响用户体验。于是就有了这篇文章内容。本文将详细…

制作MikTex本地包可用于离线安装包

MikTex安装包版本是basic-miktex-24.1-x64.exe。注&#xff1a;basic版本表示只安装MikTex基本包&#xff0c;不安装全部包。在能够联网的电脑上安装MikTex软件后&#xff0c;可以按以下步骤制作本地包库。一、制作本地包库1、新建一个文件夹&#xff0c;比如在D盘新建miktex-l…

Redis基础的介绍与使用(一)(Redis简介以及Redis下载和安装)

0 引言 本系列用于和大伙儿一起入门Redis&#xff0c;主要包括Redis的下载&#xff0c;分别在终端&#xff0c;图形显示界面以及JAVA代码中进行使用&#xff0c;适合给需要快速了解Redis是什么以及上手使用的朋友们&#xff0c;希望我用最简单的语言来讲清楚相关内容&#xff…

七牛云C++开发面试题及参考答案

智能指针的原理及应用场景是什么&#xff1f; 智能指针是 C 中用于管理动态分配内存的工具&#xff0c;其核心原理是通过 RAII&#xff08;资源获取即初始化&#xff09;技术&#xff0c;将堆内存的生命周期与对象的生命周期绑定&#xff0c;从而避免手动管理内存带来的内存泄…

【Python办公】Excel横板表头转竖版通用工具(GUI版本)横向到纵向的数据重构

目录 专栏导读前言项目概述功能特性技术栈核心代码解析1. 类结构设计2. 界面布局设计3. 滚动列表实现4. 数据转换核心逻辑5. 预览功能实现设计亮点1. 用户体验优化2. 技术实现优势3. 代码结构优势使用场景扩展建议总结完整代码结尾专栏导读 🌸 欢迎来到Python办公自动化专栏—…

C#项目 在Vue/React前端项目中 使用使用wkeWebBrowser引用并且内部使用iframe网页外链 页面部分白屏

如果是使用wkeWebBrowser的引用方式 非常有可能是版本问题导致的 问题分析 1. wkeWebBrowser 的局限性 不支持或不完全支持 ES6 语法&#xff08;如 let, const, Promise, async/await&#xff09; 缺少对现代 Web API 的支持&#xff08;如 Intl, fetch, WebSocket&#xff0…

系统架构设计师论文分享-论微服务架构

我的软考历程 摘要 2023年2月&#xff0c;我所在的公司通过了研发纱线MES系统的立项&#xff0c;该系统为国内纱线工厂提供SAAS服务&#xff0c;旨在提高纱线工厂的数字化和智能化水平。我在该项目中担任系统架构设计师一职&#xff0c;负责该项目的架构设计工作。本文结合我…

The History of Big Data

数据洪流悄然重塑世界的进程中&#xff0c;大数据的历史是技术迭代与需求驱动的交响。从 2003 年分布式系统雏形初现&#xff0c;到 Hadoop 掀起开源浪潮&#xff0c;再到 Spark、容器化技术与深度学习的接力革新&#xff0c;以及 Hadoop 生态的兴衰起落&#xff0c;大数据发展…