第21课 结构体


前言

结构体是一个由程序员定义的数据类型,可以容纳许多不同的数据值。在过去,面向对象编程的应用尚未普及之前,程序员通常使用这些从逻辑上连接在一起的数据组合到一个单元中。一旦结构体类型被声明并且其数据成员被标识,即可创建该类型的多个变量,就像可以为同一个类创建多个对象一样。

本课主要介绍以下内容

  1. 结构体类型介绍
  2. 结构体类型的构造以及结构体变量定义
  3. 结构体成员的使用
  4. 关键字struct和typedef

一、结构体类型概述

在实际问题中,一组数据往往具有不同的数据类型。如学生成绩是整型,学生姓名是字符串类型。再举个例子,全国人口大普查时,我们需要记录每一位公民的姓名、年龄、性别、住址和身份证号码,这些信息的类型分别定义为整型、字符型和字符串型。为了解决问题, C ++语言给出了一种构造数据类型——“结构体”。需要强调的是,结构体是一种新的数据类型而不是变量,它可以像其他基本数据类型(如 int 、 char 等)那样使用(主要是定义变量),只不过这种类型是我们自己定义的。

声明结构体的方式和声明类的方式大致相同,其区别如下:

  • 使用关键字 struct 而不是关键字 class。
  • 尽管结构体可以包含成员函数,但它们很少这样做。所以,通常情况下结构体声明只会声明成员变量。
  • 结构体声明通常不包括 public 或 private 的访问修饰符。
  • 类成员默认情况是私有的,而结构体的成员则默认为 public。程序员通常希望它们保持公开,只需使用默认值即可。

二、定义结构体类型及其变量

结构体类型和变量的定义有以下两种方式。
(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 <iostream>
//#include <string>
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;
}

三、结构体成员调用

在定义结构体变量之后,我们需要访问结构体变量中的每个成员。访问结构体成员,可以使用成员运算符(.),也可以使用指向运算符(->)。

1. 使用成员运算符直接引用

格式:结构体变量名.成员名
如:

 cout << "Name: " << p1.name << ",\t Age: " << p1.age << endl;

这条语句的功能是打印结构体变量p1中成员变量name的值和age值。

2. 使用结构体指针,用指向运算符引用

结构体指针运算符由负号和大于号“->”构成,中间不能加空格,其形状和箭头类似,因此也称为箭头运算符。
如:

#include <iostream>
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 <iostream>
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 <iostream>
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题。因为不希望浪费时间来复制整个结构体,所以,除非结构很小,否则一般会通过引用将结构体传递给函数。但是,这样意味着函数可以访问原始结构的成员变量,从而可能更改它们。如果不想让函数更改任何成员变量值,那么可以考虑将结构体变量作为一个常量引用传递给函数。

下面直接给出几个通过引用将结构体传递给函数的示例。

示例1

#include<iostream>
#include<iomanip>
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

示例2

#include<iostream>
#include<iomanip>
#include<algorithm>
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<iostream>
#include<iomanip>
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++ 只允许从函数返回单个值。然而,结构体提供了解决这一限制的方法。即使一个结构体可能有几个成员,它在技术上还是一个单一的对象。通过在结构体中打包多个值,可以从函数返回任意数量的值。

六、自定义数据类型——typedef

目前学过的数据类型包括基本类型、数组类型、结构体类型等, C ++为它们提供了默认的数据类型名称。我们也可以使用 typedef 自定义数据类型名称来代替这些默认类型名称。 typedef 通常有3种用法,如下所示。

1.为基本数据类型定义新类型名

C ++的所有基本类型都可以利用 typedef 关键字来重新定义类型名。其格式为
typedef 已知类型名 新类型名;
功能:用新类型名代替已知类型名。
示例如下:

 	typedef float REAL;
 	REAL a, b, c;    //等价于float a,b,c; 

2.为数组定义新类型名

其格式为
typedef 基本类型名 新类型名[元素个数];
功能:定义一个新数组名。
示例如下:

 	typedef int ARRAY[10];
 	ARRAY a, b, c;    //等价于int a[10], b[10], c[10]

3.为结构体类型定义新类型名

其格式为

	 typedef  struct  结构体类型名 {
    		 各成员变量;
    } 新结构体类型名; 

功能:定义一个新结构体名。
示例如下:

	typedef  struct  stPoint {
    	int x;
    	int y;
   } Point;
   Point a, b, c;	//等价于stPoint a,b,c

课后练习

1. 阅读下面的代码,写出程序运行的结果。

#include <iostream>
#include <iomanip>
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

2. 阅读下面的代码,写出程序运行的结果。

#include <iostream>
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

与类对象一样,结构体变量也可以通过值、引用和常量引用传递给函数。默认情况下,它们通过值传递,这意味着需要生成整个原始结构的副本并传递给函数。

3. 阅读下面的代码,写出程序运行的结果。

#include <iostream>
#include <iomanip>
#include <algorithm>
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;
			}
		}
	}
}

4. 求每一个学生的总成绩和平均成绩

已知3位同学的姓名、语文、数学和英语成绩如下表所示。

姓名语文数学英语
Yoga10095100
Beibei989697
Tian9910098

编写程序使用结构体存储3位同学的姓名和各科成绩,并添加成员变量总成绩和平均成绩,最后列表输出所有数据。

#include <iostream>
#include <iomanip>
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

总结

虽然今天结构体较少使用,但知道它们是什么,以及如何使用它们仍然很重要,这并不仅仅是因为可以在较老的程序中遇到它们,还因为在某些情况下,类的实例无法使用,这时必须使用结构体。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值