第14讲:结构体

《计算机程序设计》

苏醒

计算机学院计算机研究所编译系统研究室

课前准备

  • 在WSL Ubuntu中建立一个专门的目录用于课上练习,如~/course
$ mkdir ~/course
  • 打开VSCode并连接到WSL,打开此目录并新建文件,如~/course/main.cpp
  • 编辑main.cpp,随堂编程练习的代码请直接在此文件中编辑
main.cpp
#include <iostream>
#include <iomanip>
using namespace std;

int main() {
  // ============= begin =============

  // =============  end  =============
  return 0;
}

温故

考考你

阅读下列程序,描述其功能

猜猜我在干啥?
void transpose(double *m, int n) {
  for (int i = 0; i < n; i++) {
    for (int j = 0; j < i; j++) {
      double temp = m[i * n + j];
      m[i * n + j] = m[j * n + i];
      m[j * n + i] = temp;
    }
  }
}

实现一个学员信息管理系统

编程实现

使用C++实现一个信息管理系统,要求如下:

  • 学员信息包括姓名学号性别出生年份专业
  • 支持查改增删等信息管理操作
学员信息管理系统
学号 姓名 性别 出生年份 专业
2024001 张三 2003 航空航天
2024002 李四 2003 计算机
2024003 王五 2004 数学
2024004 赵六 2003 航空航天
2024005 钱七 2004 计算机
2024006 孙八 2003 数学

学习目标

  • 学习内容
    • 结构的定义与初始化
    • 结构体的使用
    • 结构数组与结构指针

学习目标

  • 理解结构体的内存布局
  • 掌握结构体的定义与使用
  • 应用结构体解决复杂数据的组织与管理

运用本次课所学内容,实现学员信息管理系统

一、结构体的定义与初始化

引入:如何表示学员信息

考考你

使用多个变量表示学员信息,各信息变量分别是什么类型?

学员信息
int id;          // 学号
char name[16];   // 姓名
char sex;        // 性别
int birth;       // 出生年份
char major[20];  // 专业
  • 使用数组表示全部学员的信息
学员信息数组
const int MAX_STUDENTS = 100;
int id[MAX_STUDENTS];          // 学号
char name[MAX_STUDENTS][20];   // 姓名
char sex[MAX_STUDENTS];        // 性别
int birth[MAX_STUDENTS];       // 出生年份
char major[MAX_STUDENTS][20];  // 专业

学员信息是一个有机整体,独立变量无法信息之间的联系!

1.1 结构体的概念

  • 结构体用于将多个关联数据聚合成一个整体
    • 结构体包含多个变量,称为成员
    • 每个成员具有自己的类型名称
学员信息结构体
struct Student {
  int id;          // 学号
  char name[16];   // 姓名
  char sex;        // 性别
  int birth;       // 出生年份
  char major[20];  // 专业
};

明察秋毫

C++中,结构体是实现数据封装与抽象的基本手段

1.2 结构体的定义

  • 结构体定义语法
    • 使用struct关键字
    • struct后跟结构体名称(标识符)
    • 结构体名称后面是成员列表
      • 成员列表用大括号括起来
      • 每个成员由类型与名称组成
      • 成员之间用分号分隔
  • 结构体定义以分号结束
结构体定义
struct Student {
  int id;          // 学号
  char name[16];   // 姓名
  char sex;        // 性别
  int birth;       // 出生年份
  char major[20];  // 专业
};

明察秋毫

  • 结构体本身是一种类型(用户定义的复合类型)
    • 结构体类型的变量称为结构体变量
结构体变量
Student s1, s2, s3; // 定义三个Student类型的变量

1.2 结构体的定义

考考你

结构体的成员变量是否可以是任意类型?包括结构体类型?

结构体的结构体成员
struct Date {
  int year;
  int month;
  int day;
};
struct Student {
  int id;          // 学号
  char name[16];   // 姓名
  char sex;        // 性别
  Date birth;      // 出生年份
  char major[20];  // 专业
};

明察秋毫

  • 结构体的成员可以是任意类型,包括基础数据类型、结构体、数组、指针等
  • 但是,结构体成员不能是本结构体类型

1.3 结构体的存储

  • 结构体变量占据内存中一块连续区域
  • 成员在内存中存储顺序由实现决定(一般按照定义顺序)
  • 成员要满足所属类型的空间与对齐要求
layout.cpp
#include <iostream>
struct Student {
  int id;         // 4B,4B对齐
  char name[16];  // 16B,1B对齐
  char sex;       // 1B,1B对齐
  int birth;      // 4B,4B对齐
  char major[20]; // 20B,1B对齐
};
int main() {
  Student s;
  std::cout << sizeof(Student) << ' '
            << alignof(Student) << std::endl;
  return 0;
}

Try it!

试编译运行此程序,观察Student结构的体积与对齐,还原其存储布局

$ g++ -o code/layout code/layout.cpp
$ ./code/layout
48 4

明察秋毫

由于成员变量的对齐要求,结构体的体积可能大于成员变量之和

1.3 结构体的存储

offsetof.cpp
#include <iostream>
struct Student {
  // 4B,4B对齐 | 20B,1B对齐    | 1B,1B对齐 | 4B,4B对齐 | 20B,1B对齐
  int id;        char name[20];  char sex;   int birth;  char major[20];
};
int main() {
  Student s;
  std::cout << sizeof(Student) << ' ' << alignof(Student) << std::endl;
  std::cout << offsetof(Student, id) << ' ' << offsetof(Student, name) << ' '
            << offsetof(Student, sex) << ' ' << offsetof(Student, birth) << ' '
            << offsetof(Student, major) << std::endl;
  return 0;
}
$ g++ -o code/offsetof code/offsetof.cpp
$ ./code/offsetof
52 4
0 4 24 28 32

Student结构体存储布局

1.4 结构体的初始化

  • 结构体变量可以在定义的同时进行初始化
structinit.cpp
struct Student {
  int id;         // 4B,4B对齐
  char name[16];  // 16B,1B对齐
  char sex;       // 1B,1B对齐
  int birth;      // 4B,4B对齐
  char major[20]; // 20B,1B对齐
};
int main() {
  Student zhangsan = {2024001, "张三", 'M', 2003, "航空航天"};  // 初始化所有成员
  Student lisi = {2004002, "李四", 'F'};                       // 初始化部分成员,其余默认值(0)
  Student wangwu = {.id=2005003, .name="王五", .major="计算机"};// 指定初始化列表,可做部分初始化
  Student zhaoliu = {.name="赵六", .id=2006004, .sex='M'};     // 指定初始化列表,但成员顺序必须与定义一致
  return 0;
}
$ g++ -o code/structinit code/structinit.cpp
code/structinit.cpp: In function ‘int main()’:
code/structinit.cpp:12:57: error: designator order for field ‘Student::id’ does not match declaration order in ‘Student’
   12 |   Student zhaoliu = {.name="赵六", .id=2006004, .sex='M'};     // 指定初始化列表,但成员顺序必须与定义一致
      |                                                         ^

1.4 结构体的初始化

考考你

请使用嵌套初始化列表,完成嵌套结构体变量的初始化

嵌套结构体初始化
struct Date {
  int year;
  int month;
  int day;
};
struct Student {
  int id;          // 学号
  char name[16];   // 姓名
  char sex;        // 性别
  Date birth;      // 出生年份
  char major[20];  // 专业
};

// (2004001, "张三", 'M', (2003, 6, 12), "计算机")
static Student s = {

};

二、结构体的使用

2.1 访问结构体成员变量

  • 结构体成员变量的访问使用对象成员运算符.
memberaccess.cpp
#include <cstring>
#include <iostream>
struct Student {
  int id;         // 4B,4B对齐
  char name[16];  // 16B,1B对齐
  char sex;       // 1B,1B对齐
  int birth;      // 4B,4B对齐
  char major[20]; // 20B,1B对齐
};
int main() {
  Student zhangsan = {2024001, "张三", 'M', 2003, "航空航天"};
  std::cout << zhangsan.id << ' ' << zhangsan.major << std::endl;
  std::strcpy(zhangsan.major, "计算机");
  std::cout << zhangsan.id << ' ' << zhangsan.major << std::endl;
  return 0;
}
$ g++ -o code/memberaccess code/memberaccess.cpp
$ ./code/memberaccess
2024001 航空航天
2024001 计算机

2.1 访问结构体成员变量

  • 通过指向结构体的指针也可直接访问成员变量,使用指针成员运算符(->
ptrmemberaccess.cpp
#include <cstring>
#include <iostream>
struct Student {
  int id;         // 4B,4B对齐
  char name[16];  // 16B,1B对齐
  char sex;       // 1B,1B对齐
  int birth;      // 4B,4B对齐
  char major[20]; // 20B,1B对齐
};
int main() {
  Student zhangsan = {2024001, "张三", 'M', 2003, "航空航天"};
  Student *p = &zhangsan;
  std::cout << p->id << ' ' << p->major << std::endl;
  std::strcpy(p->major, "计算机");
  std::cout << p->id << ' ' << p->major << std::endl;
  return 0;
}
$ g++ -o code/ptrmemberaccess code/ptrmemberaccess.cpp
$ ./code/ptrmemberaccess
2024001 航空航天
2024001 计算机

2.1 访问结构体成员变量

考考你

对象成员运算符(.)与指针成员运算符(->)的关系是什么?

  • p指向结构体变量s,即p == &s,则下列表达式完全等价
    • s.id
    • p->id
    • (*p).id

2.2 结构体与函数

  • 结构体变量可以作为函数参数传递
structinfunc.cpp
#include <cstring>
#include <iostream>
struct Student {
  int id; char name[16]; char sex; int birth; char major[20];
};
Student resetName(Student s, const char *name) {
  strcpy(s.name, name);
  return s;
}
int main() {
  Student zhangsan = {2024001, "张三", 'M', 2003, "航空航天"};
  Student lisi = resetName(zhangsan, "李四");
  return 0;
}

考考你

结构体变量zhangsanlisi以及函数resetName的参数s,是否对应内存中同一个结构体变量?

2.2 结构体与函数

  • 结构体作为参数与返回值时,默认均为值传递,会引入数据拷贝
    • 当结构体较大时,拷贝开销较大,应予以避免
  • 对于参数,可以传递引用或指针以避免拷贝
    • 引用:void resetName(Student &s, const char *name)
    • 指针:void resetName(Student &s, const char *name)

2.2 结构体与函数

考考你

能否通过引用或指针返回结构体以避免拷贝?

returnptr.cpp
#include <cstring>
#include <iostream>
struct Student {
  int id; char name[16]; char sex; int birth; char major[20];
};
Student *resetName(Student s, const char *name) {
  strcpy(s.name, name);
  return &s;
}
int main() {
  Student zhangsan = {2024001, "张三", 'M', 2003, "航空航天"};
  Student *lisi = resetName(zhangsan, "李四");
  std::cout << lisi->name << std::endl;
  return 0;
}
$ g++ -o code/returnptr code/returnptr.cpp
$ ./code/returnptr
code/returnptr.cpp: In function ‘Student* resetName(Student, const char*)’:
code/returnptr.cpp:8:10: warning: address of local variable ‘s’ returned [-Wreturn-local-addr]
    8 |   return &s;
      |          ^~
code/returnptr.cpp:6:28: note: declared here
    6 | Student *resetName(Student s, const char *name) {
      |                    ~~~~~~~~^
/bin/bash: line 1: 250724 Segmentation fault      (core dumped) ./code/returnptr 2>&1

2.2 结构体与函数

  • 若希望通过指针或引用返回结构体,须保证返回后该变量的存储空间不释放
    • 不能返回函数内定义的局部结构体变量
returnnewptr.cpp
#include <cstring>
#include <iostream>
struct Student {
  int id; char name[16]; char sex; int birth; char major[20];
};
Student *resetName(Student s, const char *name) {
  strcpy(s.name, name);
  Student *p = new Student;
  *p = s;
  return p;
}
int main() {
  Student zhangsan = {2024001, "张三", 'M', 2003, "航空航天"};
  Student *lisi = resetName(zhangsan, "李四");
  std::cout << lisi->name << std::endl;
  delete lisi;
  return 0;
}

2.3 结构体数组

  • 结构体数组的定义、初始化与使用与普通数组类似
structarray.cpp
#include <cstring>
#include <iostream>
struct Student {
  int id; char name[16]; char sex; int birth; char major[20];
};
const int MAX_STUDENTS = 100;
static Student students[MAX_STUDENTS];
int main() {
  for (int i = 0; i < MAX_STUDENTS; i++) {
    students[i].id = 2024000 + i;
    std::cin >> students[i].name >> students[i].sex >> students[i].birth >> students[i].major;
  }
  int id = 0;
  std::cin >> id;
  for (int i = 0; i < MAX_STUDENTS; i++) {
    if (students[i].id == id) {
      std::cout << students[i].name << " " << students[i].sex << " " << students[i].birth << " " << students[i].major << std::endl;
      break;
    }
  }
  return 0;
}

三、实现学员信息管理系统

3.1 学员信息系统

  • 实现一个简单的学员信息管理系统,包含以下功能
学员管理系统API
功能 函数签名 说明
Student *find(int id) 根据学号查找学员
Student *update(int id, const char* major) 修改学员专业
Student *add(Student &s) 新增学员信息
Student *remove(int id) 删除学员信息
学员管理系统
#include <cstring>
#include <iostream>
struct Student { int id; char name[16]; char sex; int birth; char major[20]; };
const int MAX_STUDENTS = 100;
static Student students[MAX_STUDENTS];
Student *find(int id);
Student *update(int id, const char* major);
Student *add(Student &s);
Student *remove(int id);

3.2 学员查询

  • 给定学号,查询学员信息
    • 若查询成功,返回指向该学员的指针,否则返回nullptr
查询学员信息
Student *find(int id) {
  for (int i = 0; i < MAX_STUDENTS; i++) {
    if (students[i].id == id) {
      return &students[i];
    }
  }
  return nullptr;
}

3.3 学员修改

  • 给定学号,修改学员专业
    • 若修改成功,返回指向该学员的指针,若学员不存在则返回nullptr
修改学员专业
Student *update(int id, const char* major) {
  



}

3.4 新增学员

  • 新增学员信息,id为0的结构体变量表示空闲
    • 若新增成功,返回指向该学员的指针,若无空闲位置则返回nullptr
新增学员信息
Student *add(Student &s) {
  



}

3.5 删除学员

  • 删除指定学号的学员信息
    • 若删除成功,返回删除后空闲变量的指针,若学员不存在则返回nullptr
删除学员信息
Student *remove(int id) {




}

总结

本节内容

---
config:
  look: handDrawn
  themeVariables:
    fontSize: 20px
---
mindmap
结构体
  结构体的定义与初始化
    结构体定义
    结构体内存布局
    结构体初始化
  使用结构体
    访问成员变量
      对象成员运算符
      指针成员运算符
    结构体与函数
      结构体作为函数参数
      返回结构体
    结构体数组
  学员信息管理系统

学习目标

  • 理解结构体的内存布局
  • 掌握结构体的定义与使用
  • 应用结构体解决复杂数据的组织与管理

课后作业

  • 实训
    • C&C++结构实训(截止时间2025.04.16
    • 综合练习—C&C++结构(截止时间2025.04.21
  • 预习
    • 教材8.3–8.4 联合与枚举

计算机程序设计