在C语言中,结构体(struct)是处理复杂数据的核心工具------它能将不同类型的数据(如字符串、整数)封装成一个整体,特别适合描述真实世界中的实体(比如学生、员工、商品)。本文将通过"学生信息管理"的实例,从零讲解结构体的定义、三种访问方式,以及函数传参的两种常用形式,搭配完整代码演示,帮你快速掌握结构体核心用法。
一、结构体基础:如何定义一个"数据模板"
结构体的本质是自定义数据类型,我们先定义一个"学生"模板,包含姓名、年龄、性别、学号四个属性:
// 方式1:直接定义结构体类型
struct stu
{
char name[20]; // 姓名(字符串数组存储)
int age; // 年龄(整型)
char sex[5]; // 性别(字符串数组,兼容多字符)
char id[23]; // 学号(字符串数组,适配长学号)
};
// 方式2:用typedef给结构体起"别名"(更简洁)
typedef struct stu
{
char name[20];
int age;
char sex[5];
char id[23];
} stp1; // stp1是struct stu的别名,后续可直接用stp1定义变量
- 关键点: typedef 的作用是简化类型名,避免每次定义变量都写 struct stu ,直接用 stp1 即可,代码更简洁(后文以别名方式为主演示)。
二、结构体变量的三种访问方式
定义结构体变量后,有三种常用方式访问其内部成员,核心区别在于是否使用指针。我们先定义变量和指针,再逐一演示:
// 定义结构体变量并初始化
stp1 st1 = {"龙傲天", 20, "男", "124124552"};
stp1* p = &st1; // 定义指针p,指向结构体变量st1的地址
- 直接访问:变量名.成员名(最直观)
通过"变量名 + 点号(.) + 成员名"直接访问,适合非指针场景:
printf("姓名:%s\n", st1.name); // 输出:龙傲天
printf("年龄:%d\n", st1.age); // 输出:20
printf("性别:%s\n", st1.sex); // 输出:男
printf("id:%s\n", st1.id); // 输出:124124552
- 解引用访问:(*指针).成员名(指针场景基础用法)
如果拿到的是结构体指针,需先通过 * 解引用获取指针指向的变量,再用点号访问成员(注意括号不能少, * 优先级低于 . ):
printf("姓名:%s\n", (*p).name); // 解引用p,等价于st1.name
printf("年龄:%d\n", (*p).age); // 输出:20
- 指向符访问:指针->成员名(指针场景简化用法)
C语言专门为结构体指针提供了简化语法 -> ,直接用"指针名 + -> + 成员名"访问,无需解引用,是指针场景的首选:
printf("姓名:%s\n", p->name); // 等价于(*p).name,更简洁
printf("年龄:%d\n", p->age); // 输出:20
三种方式效果对比
运行代码后,三种访问方式输出结果完全一致:
plaintext
姓名:龙傲天
年龄:20
性别:男
id:124124552
==========================
姓名:龙傲天
年龄:20
性别:男
id:124124552
==========================
姓名:龙傲天
年龄:20
性别:男
id:124124552
- 总结:非指针用 变量.成员 ,指针用 指针->成员 (推荐), (*指针).成员 是等价写法但稍繁琐。
三、结构体函数传参:两种方式对比
结构体变量可作为函数参数传递,核心有两种方式:值传递和指针传递,我们通过两个函数演示差异。
- 指针传参:void ad1(stp1* p)
将结构体变量的地址(指针)传给函数,函数内部通过 -> 访问成员:
void ad1(stp1* p)
{
printf("姓名:%s\n", p->name);
printf("年龄:%d\n", p->age);
printf("性别:%s\n", p->sex);
printf("id:%s\n", p->id);
}
// 调用方式:传入指针p或&st1
ad1(p); // 正确
ad1(&st1); // 也正确,直接传变量地址
- 值传递:void ad2(stp1 st1)
将结构体变量的"副本"传给函数,函数内部通过 变量.成员 访问:
void ad2(stp1 st1)
{
printf("姓名:%s\n", st1.name);
printf("年龄:%d\n", st1.age);
printf("性别:%s\n", st1.sex);
printf("id:%s\n", st1.id);
}
// 调用方式:直接传入结构体变量
ad2(st1);
两种传参方式核心区别
传参方式 优点 缺点 适用场景
指针传递 不拷贝数据,效率高;可修改原变量 需注意指针合法性(避免野指针) 结构体较大、需修改原数据
值传递 语法简单,不影响原变量 拷贝整个结构体,占用内存多、效率低 结构体较小、无需修改原数据
传参效果演示
调用两个函数后,输出结果与直接访问一致:
plaintext
============指针传参==============
姓名:龙傲天
年龄:20
性别:男
id:124124552
==========================
姓名:龙傲天
年龄:20
性别:男
id:124124552
四、完整可运行代码(含三种访问+两种传参)
#include <stdio.h> // 包含printf函数所需头文件
// 用typedef定义结构体并起别名stp1
typedef struct stu
{
char name[20];
int age;
char sex[5];
char id[23];
} stp1;
// 指针传参函数
void ad1(stp1* p)
{
printf("姓名:%s\n", p->name);
printf("年龄:%d\n", p->age);
printf("性别:%s\n", p->sex);
printf("id:%s\n", p->id);
}
// 值传递函数
void ad2(stp1 st1)
{
printf("姓名:%s\n", st1.name);
printf("年龄:%d\n", st1.age);
printf("性别:%s\n", st1.sex);
printf("id:%s\n", st1.id);
}
int main()
{
// 定义并初始化结构体变量
stp1 st1 = {"龙傲天", 20, "男", "124124552"};
stp1* p = &st1; // 定义结构体指针
// 方式1:直接访问(变量.成员)
printf("==========直接访问==========\n");
printf("姓名:%s\n", st1.name);
printf("年龄:%d\n", st1.age);
printf("性别:%s\n", st1.sex);
printf("id:%s\n", st1.id);
// 方式2:解引用访问((*指针).成员)
printf("==========解引用访问==========\n");
printf("姓名:%s\n", (*p).name);
printf("年龄:%d\n", (*p).age);
printf("性别:%s\n", (*p).sex);
printf("id:%s\n", (*p).id);
// 方式3:指向符访问(指针->成员)
printf("==========指向符访问==========\n");
printf("姓名:%s\n", p->name);
printf("年龄:%d\n", p->age);
printf("性别:%s\n", p->sex);
printf("id:%s\n", p->id);
// 指针传参调用
printf("==========指针传参==========\n");
ad1(p);
// 值传递调用
printf("==========值传递==========\n");
ad2(st1);
return 0;
}
五、学习总结
-
结构体是"自定义数据类型",用于封装不同类型的关联数据,定义时可通过 typedef 起别名简化语法;
-
成员访问三剑客: 变量.成员 (非指针)、 (*指针).成员 (指针解引用)、 指针->成员 (指针简化,推荐);
-
函数传参二选一:指针传递(高效、可修改原数据)、值传递(简单、不影响原数据),优先用指针传递尤其是结构体较大时;
-
实操关键:初始化字符串用双引号,数组长度要预留足够空间(如姓名20位、学号23位),避免越界。
结构体是C语言面向对象编程的基础雏形,掌握它后,后续处理数组、链表、文件操作中的复杂数据会更轻松。建议多修改代码(比如添加"成绩"成员、修改传参逻辑),加深理解~