NOIP学习小站
西安交通大学附属中学航天学校

结构体

结构体能够将一些不同类型的信息“捆绑”聚合成一个整体,例如可以用一个结构体来记录学生的姓名(字符串类型)、性别(char类型)、年龄(int类型)等信息。结构体就像int、double一样,是一种(自定义的)数据类型。

一、结构体的基本使用方法

先来看一个问题:P5740 【深基7.例9】最厉害的学生

分析:使用“打擂台”方式求极值,只不过这里要记录下最高分的学生的姓名和三科成绩:

#include<iostream>
#include<string>
using namespace std;
int main()
{
    string name,ans_name;
    int n,chinese,math,english,total;
	int ans_chinese,ans_math,ans_english,ans_total;
    ans_total = 0;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>name>>chinese>>math>>english;
        total = chinese+math+english;
        if(total>ans_total){
            ans_total = total;
            ans_name= name;
            ans_chinese = chinese;
            ans_math = math;
            ans_english = english;
        }
    }
    cout<<ans_name<<" "<<ans_chinese<<" "<<ans_math<<" "<<ans_english<<endl;
    return 0;
}

上面的程序使用了5个变量来存储每次输入的学生姓名、语文、数学、英语和总分5项信息,同样用5个变量记录了最高分学生的5项信息,“打擂”过程中,遇到新的最高分,记录最高分学生的5项信息都要更新,这样操作有点繁琐。

可以使用结构体将学生的5项信息“捆绑打包”聚合成一个自定义数据类型:

#include<iostream>
#include<string>
using namespace std;
//定义一个结构体Student来记录学生信息,Student是一个自定义数据类型
//结构体中name、chinese等是Student的成员变量(书写就像定义普通变量一样)
struct Student{
    string name;
    int chinese,math,english,total;
};
int main()
{
    //定义Student类型变量one和ans
    //one用来记录每次输入的学生信息,ans记录最高分学生信息
    Student one,ans; 

    ans.total = 0;
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        //输入到one的name、chinese、math、english成员变量中 
        cin>>one.name>>one.chinese>>one.math>>one.english;
        //计算one的total成员变量 
        one.total = one.chinese+one.math+one.english;
        
        if(one.total>ans.total){
            ans = one;    //非常简单的直接赋值来记录最高分学生信息 
        }
    }
    cout<<ans.name<<" "<<ans.chinese<<" "<<ans.math<<" "<<ans.english<<endl;
    return 0;
}

结构体是由一系列数据(可以是不同的数据类型)构成的数据集合,并且结构体提供了一种自定义数据类型的手段。定义结构体的一般格式如下:

struct 类型名称{
    数据类型1 成员变量1(或者成员变量列表);
    数据类型2 成员变量2(或者成员变量列表);
    //...其它成员变量
};

结构体定义的是一种数据类型,所以可以像申明int、double、string类型的变量那样来申明结构体变量。此外,可以用结构体变量名.成员名来使用结构体内部的成员变量,这些成员变量的使用方法和普通的变量一致,可以输入、输出、赋值、参加运算(上面例子中cin>>one.xm>>one.yw>>one.sx>>one.yy;one.zf = one.yw+one.sx+one.yy;one.zf>ans.zf)。更神奇的是,相同类型的结构体变量之间可以直接赋值(上面例子中更新最高分学生信息时直接使用ans = one;)。

其实结构体中的成员变量也可以是结构体类型,例如上面的例子可以将成绩信息定义为一个Score结构体类型,学生信息定义为一个Student结构体类型,Student结构体中用一个Score结构体类型成员变量记录所有成绩信息:

#include<iostream>
#include<string>
using namespace std;
struct Score{
    int chinese,math,english,total;
};
struct Student{
    string name;
    Score score;
};
int main()
{
    Student one,ans; 

    ans.score.total = 0;
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>one.name>>one.score.chinese>>one.score.math>>one.score.english;
        one.score.total = one.score.chinese+one.score.math+one.score.english;
        
        if(one.score.total>ans.score.total){
            ans = one; 
        }
    }
    cout<<ans.name<<" "<<ans.score.chinese<<" "<<ans.score.math<<" "<<ans.score.english<<endl;
    return 0;
}

二、结构体数组

问题:P5741 旗鼓相当的对手 - 加强版

分析:用结构体Student存储一位学生信息,用结构体Student数组a来存储所有学生信息。通过循环列举1~N-1的每位学生a[i],对于a[i]判断其与i+1~N的每位学生a[j]是否是“旗鼓相当的对手”即可(这样不会出现重复判断):

#include<iostream>
#include<string>
using namespace std;
struct Student{
    string name;
    int chinese,math,english,total;
};
Student a[1001];

bool judge(int x,int y,int z){  //判断|x-y|<=z? 
    return x-y<=z && y-x<=z;
}
bool check(Student a,Student b){ //判断a、b两位学生是否是“旗鼓相当的对手” 
    return judge(a.chinese,b.chinese,5) && judge(a.math,b.math,5) && judge(a.english,b.english,5) && judge(a.total,b.total,10);
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i].name>>a[i].chinese>>a[i].math>>a[i].english;
        a[i].total = a[i].chinese+a[i].math+a[i].english;
    }
    for(int i=1;i<n;i++){
        //从第i+1位开始的每位学生a[j],判断a[i]与a[j] 
        for(int j=i+1;j<=n;j++){
            if(check(a[i],a[j])){
                cout<<a[i].name<<" "<<a[j].name<<endl;
            }
        }
    }
    return 0;
}

三、*结构体成员函数

结构体中除了成员变量外,还可以有成员函数,成员函数类似于函数,可以认为它是结构体的“内部函数”,看下面的例子:

#include<iostream>
#include<string>
using namespace std;
struct Student{
    string name;
    int chinese,math,english;
    //成员函数(输出学生姓名和三科成绩)
    void print(){
    	cout<<name<<" "<<chinese<<" "<<math<<" "<<english<<endl;
	}
	//有返回值的成员函数(计算三科成绩总分)
	int total(){
		return chinese+math+english;
	}
};
int main()
{
    Student one;
    one.name = "Jerry";
	one.chinese = 120;
	one.math = 138;
	one.english = 145;
	one.print();    //调用成员函数
	cout<<one.total()<<endl;  //调用成员函数(有返回值)
    return 0;
}

结构体有一种特殊的成员函数,其函数名与结构体名称相同,称为构造函数,可以用来初始化结构体:

struct Student{
    string name;
    int chinese,math,english;
    Student(){}   //没有任何参数的(默认)构造函数
    //带参数的构造函数(参数名和成员名不同,特意加了一个_)
    Student(string _name,int _chinese,int _math,int _english){
    	this->name = _name;    //可以简写成 name = _name; name是结构体成员,_name是参数
    	this->chinese = _chinese;
    	this->math = _math;
    	this->english = _english;
	}
};

//注意比较和上面定义结构体的不同(带参构造函数的名称)
struct Student{
    string name;
    int chinese,math,english;
    Student(){}   //没有任何参数的(默认)构造函数
    //带参数的构造函数(参数名和成员名相同) 
    Student(string name,int chinese,int math,int english){
    	this->name = name;    //不能简写成 name = name;因为参数(局部变量)name将同名的成员name屏蔽了
    	this->chinese = chinese;
    	this->math = math;
    	this->english = english;
	}
};

上面代码定义的结构体Student中有两个构造函数,一个是没有任何参数的Student();另外一个是有参数的Student(string _name,int _chinese,int _math,int _english);有了构造函数,就可以用构造函数来初始化结构体:

//原始的初始化结构体变量方法 
Student s;
s.name= "Jerry";
s.chinese = 120;
s.math  = 135;
s.english = 150; 

//借助结构体构造函数初始化后赋值给结构体变量
Student ss = Student("Jerry",120,135,150);

//使用结构体的构造函数直接初始化结构体变量 
Student sss("Jerry",120,135,150);

下面来看成员函数的示例程序:

#include<iostream>
#include<string>
using namespace std;
struct Student{
    string name;
    int chinese,math,english;
    Student(){}   //没有任何参数的(默认)构造函数 
    Student(string _name,int _chinese,int _math,int _english){
    	this->name = _name;    //可以简写成 name = _name;
    	this->chinese = _chinese;
    	this->math = _math;
    	this->english = _english;
	}
	int total(){
		return chinese+math+english;
	}
	void print(){
		cout<<name<<" "<<chinese<<" "<<math<<" "<<english<<endl;
	}
};

int main()
{	
	//使用结构体的构造函数初始化变量 
	Student s("Jerry",120,135,150);
    cout<<s.total()<<endl;    //调用结构体成员方法
    s.print();                //调用结构体成员方法
    return 0;
}

通过上面的例子,我们应该能够体会到前面使用string类型变量的length()方法获取字符串长度(string s;cin>>s;cout<<s.length();),其实string是一个类(和结构体相似,但功能比结构体更强),length是其中的成员函数(又称为方法)。

C++还支持更简洁的结构体初始化语句:

#include<iostream>
#include<string>
using namespace std;
struct Student{
    string name;
    int chinese,math,english;
};

int main()
{	
	//更简洁的结构体初始化语句 
	Student s{"Jerry",120,135,150};
    cout<<s.name<<" "<<s.chinese<<" "<<s.math<<" "<<s.english<<endl;
    return 0;
}

借助成员方法,上一个问题“P5741 旗鼓相当的对手 - 加强版”还可以如下求解:

#include<iostream>
#include<string>
using namespace std;
struct Student{
    string name;
    int chinese,math,english;
    Student(){}   //没有任何参数的(默认)构造函数 
    Student(string _name,int _chinese,int _math,int _english){
        this->name = _name;    //可以简写成 name = _name;
        this->chinese = _chinese;
        this->math = _math;
        this->english = _english;
    }
    int total(){   //计算返回总分
    	return chinese+math+english;
	}
    bool judge(int x,int y,int z){  //判断|x-y|<=z? 
        return x-y<=z && y-x<=z;
    }
    bool check(Student a) {  //判断当前学生与学生a是否是“旗鼓相当的对手” 
        return judge(chinese,a.chinese,5) && judge(math,a.math,5) && judge(english,a.english,5) && judge(total(),a.total(),10);
    }
};
Student a[1001];

int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
    	string name;
    	int chinese,math,english;
        cin>>name>>chinese>>math>>english;
        a[i] = Student(name,chinese,math,english);
    }
    for(int i=1;i<n;i++){
        //从第i+1位开始的每位学生a[j],判断a[i]与a[j] 
        for(int j=i+1;j<=n;j++){
            if(a[i].check(a[j])){
                cout<<a[i].name<<" "<<a[j].name<<endl;
            }
        }
    }
    return 0;
}

四、*重载输入输出运算符输入输出结构体变量

上面的程序输入并存储到结构体变量的语句仍然比较繁琐,可以重载输入运算符实现直接输入成员数据存储到结构体变量中,同样地还可以重载输出运算符实现直接输出结构体变量。

#include<iostream>
#include<string>
using namespace std;
struct Student{
    string name;
    int chinese,math,english;
    Student(){}   //没有任何参数的(默认)构造函数 
    Student(string _name,int _chinese,int _math,int _english){
        this->name = _name;    //可以简写成 name = _name;
        this->chinese = _chinese;
        this->math = _math;
        this->english = _english;
    }
    int total(){   //计算返回总分
        return chinese+math+english;
    }
    bool judge(int x,int y,int z){  //判断|x-y|<=z? 
        return x-y<=z && y-x<=z;
    }
    bool check(Student a) {  //判断当前学生与学生a是否是“旗鼓相当的对手” 
        return judge(chinese,a.chinese,5) && judge(math,a.math,5) && judge(english,a.english,5) && judge(total(),a.total(),10);
    }
};
Student a[1001];
//重载输入运算符
istream &operator>>(istream &in, Student &s){
    in>>s.name>>s.chinese>>s.math>>s.english;
    return in;
}
//重载输出运算符
ostream &operator<<(ostream &os,Student &s){
    //os<<s.name<<" "<<s.chinese<<" "<<s.math<<" "<<s.english<<" "<<s.total()<<endl;
    os<<s.name;
    return os;
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a[i];    //因为重载了输入运算符,这里可以直接用cin>>a[i];
    }
    for(int i=1;i<n;i++){
        //从第i+1位开始的每位学生a[j],判断a[i]与a[j] 
        for(int j=i+1;j<=n;j++){
            if(a[i].check(a[j])){
                cout<<a[i]<<" "<<a[j]<<endl;    //因为重载了输出运算符,这里可以直接用cout<<a[i];
            }
        }
    }
    return 0;
}