结构体是一个由程序员定义的数据类型,可以容纳许多不同的数据值。在过去,面向对象编程的应用尚未普及之前,程序员通常使用这些从逻辑上连接在一起的数据组合到一个单元中。一旦结构体类型被声明并且其数据成员被标识,即可创建该类型的多个变量,就像可以为同一个类创建多个对象一样。
本课主要介绍以下内容
在实际问题中,一组数据往往具有不同的数据类型。如学生成绩是整型,学生姓名是字符串类型。再举个例子,全国人口大普查时,我们需要记录每一位公民的姓名、年龄、性别、住址和身份证号码,这些信息的类型分别定义为整型、字符型和字符串型。为了解决问题, C ++语言给出了一种构造数据类型——“结构体”。需要强调的是,结构体是一种新的数据类型而不是变量,它可以像其他基本数据类型(如 int 、 char 等)那样使用(主要是定义变量),只不过这种类型是我们自己定义的。
声明结构体的方式和声明类的方式大致相同,其区别如下:
结构体类型和变量的定义有以下两种方式。
(1)定义结构体的同时定义结构体变量
例如
struct Student {
string stno;
string name;
int score;
} st[100];
(2)先定义结构体,再定义结构体变量
例如
struct Student {
string stno;
string name;
int score;
};
Student st1, st2, st[100];
Student *pStu;
正如在类的对象被创建之前,类定义不会被实例化一样,结构体定义不会创建任何结构体的实例。上面定义的结构体Student,其本质上是创建一个名为 Student的新数据类型。即
上面的示例中,Student是一个自己定义的数据类型, st[100]是一个具有100元素的Student类型数组。在定义结构体变量时需要注意,结构体变量名和结构体名不能相同。
当定义结构体变量时,可以通过两种方式初始化它:使用初始化列表或构造函数。
初始化结构体变量成员的最简单的方法是使用初始化列表。例如
#include
//#include
using namespace std;
struct Person {
string name;
int age;
};
int main() {
// 使用列表初始化来初始化结构体
Person p1 = {"Alice", 25};
Person p2{"Bob", 30}; // 可以省略等号
// 输出结构体的内容
cout << "Name: " << p1.name << ",\t Age: " << p1.age << endl;
cout << "Name: " << p2.name << ",\t Age: " << p2.age << endl;
return 0;
}
在定义结构体变量之后,我们需要访问结构体变量中的每个成员。访问结构体成员,可以使用成员运算符(.),也可以使用指向运算符(->)。
格式:结构体变量名.成员名
如:
cout << "Name: " << p1.name << ",\t Age: " << p1.age << endl;
这条语句的功能是打印结构体变量p1中成员变量name的值和age值。
结构体指针运算符由负号和大于号“->”构成,中间不能加空格,其形状和箭头类似,因此也称为箭头运算符。
如:
#include
using namespace std;
struct Employee {
string name; // 员工姓名
int salary;
int vacationDays, // 允许的年假
daysUsed; //已使用的年假天数
};
int main() {
Employee emp[4] = {
{"张三", 5000, 10, 7},
{"李四", 5400, 10, 3},
{"王五", 6700, 15, 5},
{"赵六", 5800, 25, 10}
};
Employee *p = &emp[0];
cout << p->name << "\t" << p->salary << "\t" << p->vacationDays << "\t" << p->daysUsed << endl;
cout << (*p).name << "\t" << (*p).salary << "\t" << (*p).vacationDays << "\t" << (*p).daysUsed << endl;
cout << emp[0].name << "\t" << emp[0].salary << "\t" << emp[0].vacationDays << "\t" << emp[0].daysUsed << endl;
// p->name 与 (*p).name是等价的。前者是指向该结构体的存储单元,
// 而后者是对指针的间接引用,并对结构成员运算符的成员name进行访问。
// 因为结构成员运算符'.'的优先级比间接引用运算符'*'高,
// 所以在(*p).name中需要使用括号用以强调先使用的是(*p)。
return 0;
}
运行程序,输出如下
张三 5000 10 7
张三 5000 10 7
张三 5000 10 7
代码中将指针p声明为struct Employee类型,然后将结构体数组emp的第一个元素的地址赋予指针p。然后就可以使用箭头运算符来访问结构体的成员。
cout << p->name << "\t" << p->salary << "\t" << p->vacationDays << "\t" << p->daysUsed << endl;
如果要使用结构体指针的方式来遍历结构体数组emp[4],可以如下
#include
using namespace std;
struct Employee {
string name; // 员工姓名
int salary;
int vacationDays, // 允许的年假
daysUsed; //已使用的年假天数
};
int main() {
Employee emp[4] = {
{"张三", 5000, 10, 7},
{"李四", 5400, 10, 3},
{"王五", 6700, 15, 5},
{"赵六", 5800, 25, 10}
};
Employee *p = &emp[0];
for(int i=0; i<4; i++) {
cout << p->name << "\t" << p->salary << "\t" << p->vacationDays << "\t" << p->daysUsed << endl;
p++;
}
return 0;
}
运行程序,输出如下
张三 5000 10 7
李四 5400 10 3
王五 6700 15 5
赵六 5800 25 10
使用指针直接访问内存地址是一种比较危险的操作,为稳妥起见,推荐采用emp[0].name的方式来访问结构体成员。参见下面的示例。
#include
using namespace std;
//结构体定义
struct Student {
//成员列表
string name; //姓名
int Chinese; //语文
int English;
int Math; //数学
int Computer;
};
int main() {
//结构体数组
Student score[4] = {
{"张三", 81, 80, 75, 90},
{"李四", 91, 60, 68, 80},
{"王五", 85, 70, 97, 93},
{"赵六", 95, 90, 98, 85}
};
cout << "姓名\t语文\t英语\t数学\t计算机" << endl;
for (int i = 0; i < 4; i++) {
cout << score[i].name;
cout << "\t" << score[i].Chinese;
cout << "\t" << score[i].English;
cout << "\t" << score[i].Math;
cout << "\t" << score[i].Computer;
cout << endl;
}
// system("pause");
return 0;
}
运行程序,输出如下
姓名 语文 英语 数学 计算机
张三 81 80 75 90
李四 91 60 68 80
王五 85 70 97 93
赵六 95 90 98 85
与类对象一样,结构体变量也可以通过值、引用和常量引用传递给函数。默认情况下,它们通过值传递,这意味着需要生成整个原始结构的副本并传递给函数,见后面课后练习的第2题。因为不希望浪费时间来复制整个结构体,所以,除非结构很小,否则一般会通过引用将结构体传递给函数。但是,这样意味着函数可以访问原始结构的成员变量,从而可能更改它们。如果不想让函数更改任何成员变量值,那么可以考虑将结构体变量作为一个常量引用传递给函数。
下面直接给出几个通过引用将结构体传递给函数的示例。
#include
#include
using namespace std;
//grossPay=payRate*hours
struct PayRoll {
int empNumber;
string name;
double hours,payRate;
double grossPay; // Gross amount the employee earned this week
};
void getItemData(PayRoll &item) {
cout << "Enter the employee number: ";
cin >> item.empNumber;
cout << "Enter the employee's name: ";
//cin.get();
cin.ignore(); // Skip the '\n' character left in the input buffer
getline (cin, item.name);
cout << "Hours worked this week: ";
cin >> item.hours;
cout << "Employee's hourly pay rate: ";
cin >> item.payRate;
}
void showItem(const PayRoll &item) {
cout << "\nHere is the employees payroll data:\n";
cout << "Name: " << item.name << endl;
cout << "Employee number: " << item.empNumber << endl;
cout << "Hours worked: " << item.hours << endl;
cout << "Hourly pay rate: " << item.payRate << endl;
cout << fixed << showpoint << setprecision(2);
cout << "Gross pay: $" << item.grossPay << endl;
}
int main() {
PayRoll employee, employee2; // Employee is a PayRoll structure
cout << "Enter the employee1s number:";
cin >> employee.empNumber;
cout << "Enter the employee's name: ";
cin.ignore();// Skip the '\n' character left in the input buffer
getline(cin, employee.name);
cout << "Hours worked this week: ";
cin >> employee.hours;
cout << "Employee's hourly pay rate: ";
cin >> employee.payRate;
// Calculate the employee's gross pay
employee.grossPay = employee.hours * employee.payRate;
cout << "\nHere is the employees payroll data:\n";
cout << "Name: " << employee.name << endl;
cout << "Employee number: " << employee.empNumber << endl;
cout << "Hours worked: " << employee.hours << endl;
cout << "Hourly pay rate: " << employee.payRate << endl;
cout << fixed << showpoint << setprecision(2);
cout << "Gross pay: $" << employee.grossPay << endl;
cout << endl;
getItemData(employee2);
employee2.grossPay = employee2.payRate * employee2.hours;
showItem(employee2);
return 0;
}
下面是程序运行的一次输入输出测试:
Enter the employee1s number:2023001
Enter the employee's name: Tom
Hours worked this week: 40
Employee's hourly pay rate: 100
Here is the employees payroll data:
Name: Tom
Employee number: 2023001
Hours worked: 40
Hourly pay rate: 100
Gross pay: $4000.00
Enter the employee number: 2023002
Enter the employee's name: Jim
Hours worked this week: 50
Employee's hourly pay rate: 100
Here is the employees payroll data:
Name: Jim
Employee number: 2023002
Hours worked: 50.00
Hourly pay rate: 100.00
Gross pay: $5000.00
#include
#include
#include
using namespace std;
struct Box {
string maker;
float length;
float width;
float height;
float volume;
};
//以box结构体的引用作为形参,显示每个成员的值
void show_box(Box& b) {
cout << "Maker: " << b.maker << endl;
cout << "Length: " << b.length << endl;
cout << "Width: " << b.width << endl;
cout << "Height: " << b.height << endl;
cout << "Volume: " << b.volume << endl;
}
//以box结构体的引用作为形参,计算成员volume的值
void calc_volume(Box& b) {
b.volume = b.length * b.width * b.height;
}
//模板函数的原型声明
template<typename T>
T& bigger(T&, T&);
//模板的具体化声明
template<>
Box& bigger<Box>(Box&, Box&);
//模板函数的实现
template<typename T>
T& bigger(T& x, T& y) {
return x>y ? x : y;
}
//模板函数具体化的实现
template<>
Box& bigger(Box& x, Box& y) {
return x.volume>y.volume ? x : y;
}
bool cmp(const Box& a, const Box& b) {
return a.volume < b.volume;
}
int main() {
Box box1, box2;
box1.maker = "Maker1";
box1.length = 50;
box1.width = 40;
box1.height = 30;
box2.maker = "Maker2";
box2.length = 40;
box2.width = 40;
box2.height = 40;
cout << box1.volume << endl;
calc_volume(box1);
cout << box1.volume << endl;
show_box(box1);
calc_volume(box2);
cout << "\nThe bigger box information:" << endl;
show_box(bigger(box1, box2));
cout << "===========================================" << endl;
Box box[5] = {
{"box0", 50, 40, 30},
{"box1", 40, 40, 40},
{"box2", 45, 40, 45},
{"box3", 90, 20, 20},
{"box4", 30, 30, 60}
};
for(int i=0; i<5; i++) {
calc_volume(box[i]);
}
cout << setw(8) << "Maker";
cout << setw(8) << "Length";
cout << setw(8) << "Width";
cout << setw(8) << "Height";
cout << setw(8) << "Volume" << endl;
for(int i=0; i<5; i++) {
cout << setw(8) << box[i].maker;
cout << setw(8) << box[i].length;
cout << setw(8) << box[i].width;
cout << setw(8) << box[i].height;
cout << setw(8) << box[i].volume;
cout << endl;
}
cout << "===========================================" << endl;
sort(box, box+5, cmp);
cout << setw(8) << "Maker";
cout << setw(8) << "Length";
cout << setw(8) << "Width";
cout << setw(8) << "Height";
cout << setw(8) << "Volume" << endl;
for(int i=0; i<5; i++) {
cout << setw(8) << box[i].maker;
cout << setw(8) << box[i].length;
cout << setw(8) << box[i].width;
cout << setw(8) << box[i].height;
cout << setw(8) << box[i].volume;
cout << endl;
}
return 0;
}
下面是程序运行的输出:
Maker: Maker1
Length: 50
Width: 40
Height: 30
Volume: 60000
The bigger box information:
Maker: Maker2
Length: 40
Width: 40
Height: 40
Volume: 64000
===========================================
Maker Length Width Height Volume
box0 50 40 30 60000
box1 40 40 40 64000
box2 45 40 45 81000
box3 90 20 20 36000
box4 30 30 60 54000
===========================================
Maker Length Width Height Volume
box3 90 20 20 36000
box4 30 30 60 54000
box0 50 40 30 60000
box1 40 40 40 64000
box2 45 40 45 81000
也可以从函数返回结构体变量。在这种情况下,函数的返回类型是结构体的名称。
可以改写示例 1 的 void getItemData(PayRoll &item) 函数以允许getItemData()函数创建 Invltem 结构体的局部实例,在函数中将数据值放入其成员变量中,然后将其传递回 main,而不是将其作为引用变量被 main 使用。
以下是修改void getItemData(PayRoll &item)函数为PayRoll getItemData2() 后的示例代码:
#include
#include
using namespace std;
//grossPay=payRate*hours
struct PayRoll {
int empNumber;
string name;
double hours,payRate;
double grossPay; // Gross amount the employee earned this week
};
void getItemData(PayRoll &item) {
cout << "Enter the employee number: ";
cin >> item.empNumber;
cout << "Enter the employee's name: ";
//cin.get();
cin.ignore(); // Skip the '\n' character left in the input buffer
getline (cin, item.name);
cout << "Hours worked this week: ";
cin >> item.hours;
cout << "Employee's hourly pay rate: ";
cin >> item.payRate;
}
PayRoll getItemData2() {
PayRoll item; //局部结构体变量
cout << "Enter the employee number: ";
cin >> item.empNumber;
cout << "Enter the employee's name: ";
//cin.get();
cin.ignore(); // Skip the '\n' character left in the input buffer
getline (cin, item.name);
cout << "Hours worked this week: ";
cin >> item.hours;
cout << "Employee's hourly pay rate: ";
cin >> item.payRate;
return item;// 将结构体的局部实例传递给main
}
void showItem(const PayRoll &item) {
cout << "\nHere is the employees payroll data:\n";
cout << "Name: " << item.name << endl;
cout << "Employee number: " << item.empNumber << endl;
cout << "Hours worked: " << item.hours << endl;
cout << "Hourly pay rate: " << item.payRate << endl;
cout << fixed << showpoint << setprecision(2);
cout << "Gross pay: $" << item.grossPay << endl;
}
int main() {
PayRoll employee1; // employee1 is a PayRoll structure variable
employee1 = getItemData2();
employee1.grossPay = employee1.payRate * employee1.hours;
showItem(employee1);
return 0;
}
下面是程序运行的一次输入输出测试:
Enter the employee number: 2024001
Enter the employee's name: Bob
Hours worked this week: 40
Employee's hourly pay rate: 50
Here is the employees payroll data:
Name: Bob
Employee number: 2024001
Hours worked: 40
Hourly pay rate: 50
Gross pay: $2000.00
注意,C++ 只允许从函数返回单个值。然而,结构体提供了解决这一限制的方法。即使一个结构体可能有几个成员,它在技术上还是一个单一的对象。通过在结构体中打包多个值,可以从函数返回任意数量的值。
目前学过的数据类型包括基本类型、数组类型、结构体类型等, C ++为它们提供了默认的数据类型名称。我们也可以使用 typedef 自定义数据类型名称来代替这些默认类型名称。 typedef 通常有3种用法,如下所示。
C ++的所有基本类型都可以利用 typedef 关键字来重新定义类型名。其格式为
typedef 已知类型名 新类型名;
功能:用新类型名代替已知类型名。
示例如下:
typedef float REAL;
REAL a, b, c; //等价于float a,b,c;
其格式为
typedef 基本类型名 新类型名[元素个数];
功能:定义一个新数组名。
示例如下:
typedef int ARRAY[10];
ARRAY a, b, c; //等价于int a[10], b[10], c[10]
其格式为
typedef struct 结构体类型名 {
各成员变量;
} 新结构体类型名;
功能:定义一个新结构体名。
示例如下:
typedef struct stPoint {
int x;
int y;
} Point;
Point a, b, c; //等价于stPoint a,b,c
#include
#include
using namespace std;
struct Person {
string name;
int age;
};
int main() {
Person c[] = {
{"Zhangsan", 13},
{"Lisi", 12},
{"Wangwu", 11},
{"Zhaoliu", 14},
{"Chenqi", 10},
};
int len = sizeof(c)/sizeof(c[0]);
for(int i=0; i<len; i++) {
cout << setw(10) << c[i].name << setw(5) << c[i].age << endl;
}
return 0;
}
输出为
Zhangsan 13
Lisi 12
Wangwu 11
Zhaoliu 14
Chenqi 10
#include
using namespace std;
struct N {
int x;
char c;
};
void func(N n) {
n.x = 20;
n.c = 'c';
cout << n.x << ' ' << n.c << endl;
}
int main() {
N a = {10, 'x'};
func(a);
cout << a.x << ' ' << a.c << endl;
return 0;
}
输出为
20 c
10 x
与类对象一样,结构体变量也可以通过值、引用和常量引用传递给函数。默认情况下,它们通过值传递,这意味着需要生成整个原始结构的副本并传递给函数。
#include
#include
#include
using namespace std;
struct Person {
string name;
int age;
};
bool cmp(Person a, Person b) {
return a.age < b.age;
}
int main() {
Person c[] = {
{"Zhangsan", 13},
{"Lisi", 12},
{"Wangwu", 11},
{"Zhaoliu", 14},
{"Chenqi", 10},
};
int len = sizeof(c)/sizeof(c[0]);
for(int i=0; i<len; i++) {
cout << setw(10) << c[i].name << setw(5) << c[i].age << endl;
}
cout << "====================================" << endl;
sort(c, c+len, cmp);
//Sort(start, end, cmp)
//end表示数组结束地址的下一位;迭代器的结束位置,即首地址加上数组的长度n(代表尾地址的下一地址)。
for(int i=0; i<len; i++) {
cout << setw(10) << c[i].name << setw(5) << c[i].age << endl;
}
return 0;
}
运行程序,输出为
Zhangsan 13
Lisi 12
Wangwu 11
Zhaoliu 14
Chenqi 10
====================================
Chenqi 10
Wangwu 11
Lisi 12
Zhangsan 13
Zhaoliu 14
我们也可以自定义本题代码中的按年龄排序的函数,例如下面的自定义函数采用了冒泡排序算法按年龄升序排序。
//冒泡排序
void bubbleSort(Person arr[], int len) {
Person temp;
for (int i=0; i < len-1; i++) {
for (int j=0; j < len-1-i; j++) {
if (arr[j].age > arr[j+1].age) {
temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
已知3位同学的姓名、语文、数学和英语成绩如下表所示。
姓名 | 语文 | 数学 | 英语 |
---|---|---|---|
Yoga | 100 | 95 | 100 |
Beibei | 98 | 96 | 97 |
Tian | 99 | 100 | 98 |
编写程序使用结构体存储3位同学的姓名和各科成绩,并添加成员变量总成绩和平均成绩,最后列表输出所有数据。
#include
#include
using namespace std;
//结构体定义
struct Student {
//成员列表
string name; //姓名
int Chinese; //语文
int English;
int Math; //数学
int total;
float average;
};
int main() {
//结构体数组
Student score[4] = {
{"张三", 81, 80, 75},
{"李四", 91, 60, 68},
{"王五", 85, 70, 97},
{"赵六", 95, 90, 98}
};
for (int i = 0; i < 4; i++) {
score[i].total = score[i].Chinese+score[i].English+score[i].Math;
score[i].average = score[i].total/3.0;
}
cout << "姓名\t语文\t英语\t数学\t总分\t平均分" << endl;
for (int i = 0; i < 4; i++) {
cout << score[i].name;
cout << "\t" << score[i].Chinese;
cout << "\t" << score[i].English;
cout << "\t" << score[i].Math;
cout << "\t" << score[i].total;
cout << "\t" << fixed << setprecision(1) << score[i].average;
cout << endl;
}
// system("pause");
return 0;
}
运行程序,输出为
姓名 语文 英语 数学 总分 平均分
张三 81 80 75 236 78.7
李四 91 60 68 219 73.0
王五 85 70 97 252 84.0
赵六 95 90 98 283 94.3
虽然今天结构体较少使用,但知道它们是什么,以及如何使用它们仍然很重要,这并不仅仅是因为可以在较老的程序中遇到它们,还因为在某些情况下,类的实例无法使用,这时必须使用结构体。