在C++编程中,结构体(struct)是一种将不同类型的数据组合成一个整体的复合数据类型。它允许我们将多个相关的变量打包成一个逻辑单元,这在处理复杂数据时特别有用。比如要记录一个学生的信息,传统方式需要为姓名、学号、成绩等分别定义变量,而使用结构体可以将其整合为一个整体。
结构体的基本语法格式如下:
cpp复制struct 结构体名 {
数据类型 成员1;
数据类型 成员2;
// 更多成员...
};
举个例子,定义一个表示学生的结构体:
cpp复制struct Student {
int id; // 学号
string name; // 姓名
float score; // 成绩
};
这里有几个关键点需要注意:
提示:结构体定义通常放在头文件(.h)中,这样可以在多个源文件中共享使用。
定义好结构体类型后,就可以用它来声明变量了。声明结构体变量有三种常见方式:
第一种是在定义结构体时直接声明变量:
cpp复制struct Point {
int x;
int y;
} p1, p2; // 直接声明两个Point类型的变量
第二种是单独声明:
cpp复制Point p3; // 使用已定义的结构体类型声明变量
第三种是使用typedef简化:
cpp复制typedef struct {
int x;
int y;
} Point;
Point p4; // 现在可以直接用Point声明变量
结构体变量有多种初始化方式:
cpp复制Student s1 = {1001, "张三", 89.5};
cpp复制Student s2 {1002, "李四", 92.0};
cpp复制Student s3;
s3.id = 1003;
s3.name = "王五";
s3.score = 85.0;
注意:初始化列表中的值必须按照结构体定义中成员的顺序提供,且数量不能少于成员数量。
访问结构体成员使用点运算符(.):
cpp复制Student stu;
stu.id = 1001;
cout << "学生姓名:" << stu.name << endl;
如果有一个指向结构体的指针,可以使用箭头运算符(->)访问成员:
cpp复制Student *pStu = &stu;
pStu->score = 95.5;
cout << "学生成绩:" << pStu->score << endl;
结构体可以作为函数参数传递,有两种方式:
cpp复制void printStudent(Student s) {
cout << "ID:" << s.id << " Name:" << s.name << endl;
}
cpp复制void printStudent(const Student &s) {
cout << "ID:" << s.id << " Name:" << s.name << endl;
}
函数可以返回结构体类型:
cpp复制Student createStudent(int id, string name, float score) {
Student s;
s.id = id;
s.name = name;
s.score = score;
return s;
}
在C++中,结构体不仅可以包含数据成员,还可以包含函数成员(方法):
cpp复制struct Rectangle {
float width;
float height;
float area() {
return width * height;
}
};
使用方法:
cpp复制Rectangle rect = {5.0, 3.0};
cout << "面积:" << rect.area() << endl;
结构体可以有构造函数,用于初始化对象:
cpp复制struct Student {
int id;
string name;
float score;
// 构造函数
Student(int i, string n, float s) {
id = i;
name = n;
score = s;
}
};
使用构造函数创建对象:
cpp复制Student s(1001, "张三", 89.5);
结构体可以嵌套使用,即一个结构体的成员可以是另一个结构体:
cpp复制struct Date {
int year;
int month;
int day;
};
struct Employee {
int id;
string name;
Date hireDate; // 嵌套结构体
};
初始化嵌套结构体:
cpp复制Employee emp = {101, "李四", {2020, 5, 15}};
在C++中,结构体(struct)和类(class)非常相似,主要区别在于默认的访问控制:
除此之外,它们几乎可以互换使用。通常约定:
例如:
cpp复制// 作为数据结构使用
struct Point {
int x;
int y;
};
// 作为对象使用
class Circle {
private:
Point center;
float radius;
public:
float area() {
return 3.14 * radius * radius;
}
};
下面是一个简单的学生成绩管理示例:
cpp复制#include <iostream>
#include <vector>
using namespace std;
struct Student {
int id;
string name;
float score;
void display() {
cout << "学号:" << id << "\t姓名:" << name
<< "\t成绩:" << score << endl;
}
};
int main() {
vector<Student> students;
// 添加学生
students.push_back({1001, "张三", 85.5});
students.push_back({1002, "李四", 92.0});
// 显示所有学生信息
for (const auto &stu : students) {
stu.display();
}
return 0;
}
结构体在图形处理中也很常用:
cpp复制struct Point {
float x;
float y;
};
struct Line {
Point start;
Point end;
float length() {
float dx = end.x - start.x;
float dy = end.y - start.y;
return sqrt(dx*dx + dy*dy);
}
};
int main() {
Line line = {{0,0}, {3,4}};
cout << "线段长度:" << line.length() << endl; // 输出5
return 0;
}
结构体成员在内存中的排列需要考虑对齐问题,这会影响结构体的大小。例如:
cpp复制struct Example1 {
char a; // 1字节
int b; // 4字节
char c; // 1字节
}; // 可能占用12字节(取决于平台)
struct Example2 {
char a;
char c;
int b;
}; // 可能占用8字节
解决方案:
当结构体包含指针成员时,默认的拷贝是浅拷贝,可能导致问题:
cpp复制struct Problem {
int *data;
Problem(int size) {
data = new int[size];
}
~Problem() {
delete[] data;
}
};
void trouble() {
Problem p1(10);
Problem p2 = p1; // 浅拷贝,两个对象共享同一块内存
} // 析构时会导致双重释放
解决方案:
直接比较两个结构体变量是不允许的:
cpp复制Point p1 = {1,2};
Point p2 = {1,2};
if (p1 == p2) { // 错误!不能直接比较
// ...
}
解决方案:
命名规范:结构体名使用大写字母开头的驼峰命名法,如StudentInfo
合理设计:结构体应该保持相对简单,如果功能复杂,考虑使用类
构造函数:为结构体提供构造函数可以确保对象被正确初始化
const正确性:对于不修改结构体的函数,应该声明为const成员函数
内联小函数:简单的成员函数可以内联定义在结构体中
注释文档:为结构体和重要成员添加注释说明用途
避免过度嵌套:结构体嵌套层次不宜过深,一般不超过3层
考虑内存布局:频繁访问的数据放在一起,考虑缓存友好性
现代C++为结构体引入了许多新特性:
cpp复制struct Point {
int x = 0;
int y = 0;
};
cpp复制Point p {.x = 10, .y = 20}; // C++20指定初始化
cpp复制auto [x, y] = getPoint(); // 自动解构结构体
cpp复制struct Point {
int x;
int y;
auto operator<=>(const Point&) const = default;
};
数据序列化:结构体常用于表示要序列化的数据格式
协议消息:网络编程中常用结构体表示协议头和数据包
配置参数:将相关配置参数组织成结构体便于管理
轻量级对象:当不需要完整类功能时,结构体是更简单的选择
多返回值:可以用结构体包装多个返回值,比元组更清晰
性能敏感代码:结构体比类更适合在性能关键代码中使用
C兼容性:在与C代码交互时,结构体是更好的选择
在实际项目中,我通常会为结构体添加一些辅助方法,比如:
cpp复制struct Vec3 {
float x, y, z;
Vec3 normalized() const {
float len = sqrt(x*x + y*y + z*z);
return {x/len, y/len, z/len};
}
std::string toString() const {
return std::to_string(x) + "," + std::to_string(y) + "," + std::to_string(z);
}
};
这样既保持了结构体的简洁性,又增加了一些实用功能。