目录
一、结构体
结构体是⼀种 自定义 的类型,这种自定义类型可以描述 一些复杂对象 。前面我们学习的都是单一的数据类型,例如: char 、 short 、 int 、 double 类型。但是我们生活中是存在复杂对象 ,比如:人、书等,这些复杂对象,仅仅使用单⼀的数据类型是不能描述的。比如:1)描述⼀本书:书有作者、出版社、定价等信息;2)描述⼀个人,人有名字、性别、年龄、 身高、体重等信息。那怎么描述这些复杂对象呢?C++中引入了结构体和类来解决这些问题。在C++中结构体和类是相似的,这里首先介绍结构体。
1.1 结构体类型声明和变量定义
结构体类型声明的关键字是 struct , 结构体类型声明的基本语法如下:
声明一个结构体类型:
struct Stu
{
string name; //名字
int age; //年龄
double chinese; //语文成绩
double math; //数学成绩
double total; //总成绩
};
1 ) 在创建变量的时候 , 结构体类型中的struct 就可以省略了。
2 )在创建结构体变量的时候 , 结构体变量的名字不能和结构体类型的名字相同 。
3 ) 声明结构体类型是不占用内存空间的 , 当使用结构体类型创建变量后 , 才向内存申请空间的 。
4 ) 结构体变量可以是全局变量 , 也可以是局部变量 。
怎么理解第三句话呢?
---> 就像盖房子 。 有图纸 , 然后根据图纸造房子 ; 如果没盖房子之前 , 图纸并不会占土地空间 , 只有盖了房子 , 我才占了这块地皮 。同样的 , 结构体类型就像我们的图纸 , 只有在创建了变量 , 才会在内存中申请空间 。
1.2 结构体变量的特点
1.2.1 结构体的初始化
结构体的初始化和数组类似 , 使用 { } , 将初始化的内容按照顺序放在{ } 中就可以
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
struct Stu
{
string name;
int chinese; //语文成绩
int math; //数学成绩
int total; //总成绩
};
int main()
{
struct Stu s1 = {"zhangsan" , 85 , 95 , 180};
return 0;
}
1.2.2 结构体整体操作
结构体变量中虽然包含多个成员 , 而且成员可能是不同类型的 。
但是一个结构体变量可以看作一个整体 , 是可以直接 进行赋值 操作的。
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
struct Stu
{
string name;
int chinese; //语文成绩
int math; //数学成绩
int total; //总成绩
};
int main()
{
struct Stu s1 = { "zhangsan" , 85 , 95 , 180 };
Stu s2;
s2 = s1;
return 0;
}
1.2.3 结构体成员的访问
结构体成员访问的基本形式是:
结构体变量.成员名字
因为每个结构体变量中都有属于自己的成员 , 所有必须使用 . 这种结构体成员访问操作符。
通过这种方式 找到成员后 , 可以直接给结构体变量的成员中 输入值 , 或者直接存储和使用。
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
struct Stu
{
string name;
int chinese; //语文成绩
int math; //数学成绩
int total; //总成绩
};
int main()
{
struct Stu s1;
s1.name = "Lisi";
s1.math = 90;
cin >> s1.chinese;
s1.total = s1.chinese + s1.math;
cout << s1.total << endl;
return 0;
}
1.2.4 结构体嵌套
结构体还可以嵌套其他结构体成员 , 这里初始化的时候 , 也可以用 { } 中嵌套 { } , 访问嵌套成员中的成员可以连续使用 . 操作符:
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
struct Score
{
int chinese; //语文成绩
int math; //数学成绩
int english; //英语成绩
};
struct Stu
{
string name;
Score sc; //结构体嵌套了其他结构体
int total; // 总成绩
double avg; //平均成绩
};
int main()
{
Stu s1 = { "wangwu" , {80,90,100},0,0 }; // 初始化
s1.total = s1.sc.chinese + s1.sc.math + s1.sc.english;
s1.avg = s1.total / 3.0;
cout << s1.total << endl;
cout << s1.avg << endl;
return 0;
}
1.3 结构体成员函数
以上我们讲的是结构体的基本知识 , C语言中的结构体就是这样的 , 但是C++中的结构体和C语言结构体 有一个比较大的差异就是:C++中的结构体除了有成员变量之外 , 还可以包含成员函数
1)C++的结构体会有一些默认的成员函数 , 比如:构造函数 、 析构函数等 , 是编译器默认生成的 , 如果觉得不合适 , 也是可以自己显示的定义这些函数 , 这些函数都是自动被调用的,不需要手动调用 。
2)除了默认的成员函数之外 ,我们可以自己定义一些成员函数 , 这些成员函数可以有 , 也可以没有 , 完全根据的实际的需要来添加就可以 。
3)这些成员函数可以直接访问成员变量
4)成员函数的调用也使用 . 操作符
1.3.1 代码举例
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
struct Stu
{
string name;
int chinese; //语文成绩
int math; //数学成绩
int total; //总成绩
//成员函数
//初始化
void init_stu()
{
name = "小明";
chinese = 100;
math = 100;
total = chinese + math;
}
//打印
void print_stu()
{
cout << "名字:" << name << endl;
cout << "语文:" << chinese << endl;
cout << "数学:" << math << endl;
cout << "总分:" << total << endl;
}
};
int main()
{
Stu s1;
s1.init_stu();
s1.print_stu();
return 0;
}
1.3.2 构造函数和析构函数
默认成员函数有六种 , 这里我先介绍两种 , 具体的可以看我C++专栏 , 有详解 ;这篇博客是为竞赛而用。
1)构造函数:
构造函数是结构中默认的成员函数之一,构造函数的主要的任务是初始化结构体变量。写了构造函数,就不需要再写其他成员函数来初始化结构体(类)的成员,而且构造函数是在结构变量创建的时候, 编译器自动被调用的。
构造函数的特征如下:
1 )函数名与结构体(类)名相同。
2 ) 无返回值。3 ) 构造函数可以重载。4 ) 若未显式定义构造函数,系统会自动生成默认的构造函数。
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
struct Stu
{
//成员变量
string name;
int chinese; //语文成绩
int math; //数学成绩
int total; //总成绩
//默认成员函数
//构造函数
Stu()
{
name = "翠花";
chinese = 99;
math = 77;
total = chinese + math;
}
//打印
void print_stu()
{
cout << "名字:" << name << endl;
cout << "语文:" << chinese << endl;
cout << "数学:" << math << endl;
cout << "总分:" << total << endl;
}
};
int main()
{
Stu s1;
s1.print_stu();
return 0;
}
2)析构函数
析构函数是用来 完成结构体变量中资源的清理工作 , 也是结构体中默认的成员函数之一 。析构函数在结构体变量销毁的时候 , 会被自动调用 。
析构函数的特征如下:
1 ) 析构函数名是在结构体(类)名前加上字符 ~
2 ) 无参数无返回值类型
3 ) 一个类只能有一个析构函数 。若未显示定义析构函数 , 系统会自动生成默认的析构函数 。注意 : 析构函数不能重载 。
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
struct Stu
{
//成员变量
string name;
int chinese; //语文成绩
int math; //数学成绩
int total; //总成绩
//默认成员函数
//构造函数
Stu()
{
name = "翠花";
chinese = 99;
math = 77;
total = chinese + math;
}
~Stu()
{
cout << "调用析构函数:~Stu" << endl;
}
//打印
void print_stu()
{
cout << "名字:" << name << endl;
cout << "语文:" << chinese << endl;
cout << "数学:" << math << endl;
cout << "总分:" << total << endl;
}
};
int main()
{
Stu s1;
s1.print_stu();
return 0;
}
其实成员函数和自定义函数是一样的 , 是可以根据实际的需要来完成编写的 。在竞赛中 的题目关于结构体其实很少使用成员函数 , 使用最多的应该是针对结构变量 的 运算符重载的重载 。
1.4 运算符重载
在打印结构体成员信息的时候 , 我们是通过 s.print_stu() 的方式完成的。
但是我们在C++中打印数据的时候 , 习惯了直接使用cout 来直接打印数据 。比如:
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
int main()
{
int n = 100;
float f = 3.14f;
cout << n << endl;
cout << f << endl;
return 0;
}
那么 , struct Stu 类型的变量 , 能不能直接使用 cout 打印 ?
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
struct Stu
{
//成员变量
string name;
int chinese; //语文成绩
int math; //数学成绩
int total; //总成绩
};
int main()
{
Stu s1 = {"lisi" , 100 , 80 , 180};
cout << s1 << endl;
return 0;
}
怎么解决 ? 其实在C++中要针对自定义类型的变量 , 想使用 cout 和 << 来输出变量的内容 , 就必须对 << 这个输出运算符进行重载 :
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
struct Stu
{
//成员变量
string name;
int chinese; //语文成绩
int math; //数学成绩
int total; //总成绩
};
//重载输出运算符
ostream& operator<<(ostream& os, const Stu& s)
{
os << "名字:" << s.name << endl;
os << "语文:" << s.chinese << endl;
os << "数学:" << s.math << endl;
os << "总分:" << s.total << endl;
return os;
}
int main()
{
Stu s1 = {"lisi" , 100 , 80 , 180};
cout << s1 << endl;
return 0;
}
除了输出运算符可以重载之外 , 还有很多其他的操作符可以重载 , 遇到具体场景时候 , 我们再进行分析 。
1.5 结构体排序 - sort
C++ 的 STL 中的库函数 sort ,可以直接用来排序数据,在算法竞赛和日常开发中使用非常频繁。只要涉及到数据的排序,又没有明确要求自己实现排序算法的时候,就可以直接
1.5.1 sort 函数介绍
在默认情况下 ,按升序对给定范围 [ first , last ) 中的元素进行排序。
sort 函数需要包含头文件 < algorithm >
1.5.2 排序内置类型数据
1)对数组进行排序:
#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
int arr[] = { 2,4,5,6,9,7,8,1,3,2 };
//sz 表示数组元素的个数
int sz = sizeof(arr) / sizeof(arr[0]);
//数组名就是数组首元素的地址
//arr + sz 跳过sz 个元素 , 就是第sz + 1 个元素
sort(arr, arr + sz);
for (int i = 0; i < sz; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
2) 对字符串的字符进行排序:这里是对字符串中字符的顺序进行排序 , 是按照字符的ASCII码值进行排序。
#include <iostream>
#include <cstdio>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
string s = "asjxndgendjhbdca";
//首字符的地址:s.begin()
//最后一个字符的下一个地址:s.end()
sort(s.begin(), s.end());
cout << s << endl;
return 0;
}
默认排序的结果依旧是升序!
1.5.3 自定义排序
sort 在默认的情况是按升序排序 , 那么 , 如果希望降序呢?
使用自定义排序方式:
void sort (RandomAccessIterator first, RandomAccessIterator last, Compare comp);
1 )sort 的第三个参数是一个可选的自定义比较参数 (或函数对象),用于指定排序的规则 。 如果不提供这个参数 , std :: sort 默认会使用小于运算符( < ) 来比较函数 , 并按照升序排序 。
2 )这个比较函数 , 接受两个参数 , 并返回一个布尔值 。如果第一个参数 应该排在第二个参数之前 , 则返回 true ;否则返回 false 。
comp 表示可以自定义一个排序方法 , 使用方法如下:
1) 创建比较函数:
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
//自定义一个比较函数,这个比较函数能够比较被排序数据的两个元素的大小
//函数返回bool类型的值
bool compare(int x,int y)
{
return x > y; //排降序
}
int main()
{
int arr[] = {8,9,7,4,56,2,13,1,0,2,5};
int sz = sizeof(arr)/sizeof(arr[0]);
//讲函数名作为第三个参数传给sort函数中
sort(arr,arr+sz,compare);
for(int i = 0;i < sz ; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
注 : 在使用库函数sort () 的时候 , 需要包含头文件 <algorithm>
2 )结构体中重载() 运算符 - 仿函数
注:关于仿函数、操作符重载 , 需要深入学习C++ , 竞赛中涉及的比较少 、 也比较浅 , 会用就好了
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
struct Cmp
{
bool operator()(int x , int y)
{
return x > y ; //排降序
}
} cmp;
int main()
{
int arr[] = {8,9,7,4,56,2,13,1,0,2,5};
int sz = sizeof(arr)/sizeof(arr[0]);
//讲函数名作为第三个参数传给sort函数中
sort(arr,arr+sz,cmp);
for(int i = 0;i < sz ; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
1.5.4 排序结构体数据
两个结构体的数据也是 不能 直接比较大小的 ,在使用 sort 函数排序的时候 , 也是需要提供自定义的比较方法, 例如:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
using namespace std;
struct S
{
string name;
int age;
};
//按照名字降序 -- 根据第一个字母的ASCII码值
bool cmp_s_by_name(struct S s1,struct S s2)
{
return s1.name > s2.name;
}
//按照年龄降序
bool cmp_s_by_age(struct S s1,struct S s2)
{
return s1.age > s2.age;
}
void test1()
{
S s[3] = {{"zhangsan",18},{"lilsi",25},{"wangwu",16}};//初始化
sort(s,s+3,cmp_s_by_name);
for(int i = 0;i < 3 ; i++)
{
cout << s[i].name << " : " << s[i].age << endl;
}
cout << endl;
}
void test2()
{
S s[3] = {{"zhangsan",18},{"lilsi",25},{"wangwu",16}};//初始化
sort(s,s+3,cmp_s_by_age);
for(int i = 0;i < 3 ; i++)
{
cout << s[i].name << " : " << s[i].age << endl;
}
cout << endl;
}
int main()
{
test1();
test2();
}
二 、 类(可以不看,竞赛很少用)
C++中为了更好的实现面向对象 , 更喜欢使用class (类)来替换struct (结构体) 。它俩形式上有些相像 , 本质上还是有差别。
2.1 类的定义
类的关键字 是 class ,类中也是主要由两部分组成 , 分别为成员变量和成员函数;
为什么结构体中没有 public 呢?
原因是 :结构体的成员变量 和 成员函数 , 默认就是公开的 , 而class 中的成员变量和成员函数默认是私有的( private )
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
using namespace std;
class Stu
{
public:
//成员变量
string name;
int chinese;
int math;
int total;
//成员函数
void init_stu()
{
name = "zhangsan";
chinese = 90;
math = 80;
total = math + chinese;
}
}
在类中 , 成员变量和成员函数都是没有数量限制的 , 即可以有多个成员函数或成员变量。
2.2 类的使用
2.2.1 创建对象
2.2.2 调用类对象成员
同结构体一样 , 如果想访问 成员变量或者成员函数 , 需要使用到 . 操作符
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
using namespace std;
class Stu
{
public:
//成员变量
string name;
int chinese;
int math;
int total;
//成员函数
void init_stu()
{
name = "zhangsan";
chinese = 90;
math = 80;
total = math + chinese;
}
void print_stu()
{
cout << "name : " << name << endl;
cout << "chinese : " << chinese << endl;
cout << "math : " << math << endl;
cout << "total : " << total << endl;
}
};
int main()
{
Stu s1;
s1. init_stu();
s1. print_stu() ;
//当然,也可进行修改
s1.name = "lisi";
s1.chinese = 60;
s1.math = 50;
s1.total = s1.chinese + s1.math ;
s1.print_stu() ;
return 0;
}
2.3 访问权限控制
访问限定符 是 C++语言中的关键字, 用于指定类成员的访问权限 。
访问限定符主要有三个:
1 ) public : 成员被声明为 public 后 , 可以被该类的任何方法访问 , 包括在类的外部
2 ) protected :成员被声明为protected 后 , 可以被该类访问
3 )private : 成员被声明为private 之后 , 只能被该类的成员函数访问。
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
class Stu
{
private:
//成员变量
string name;
int chinese;
int math;
int total;
//成员函数
public:
void init_stu()
{
cout << "成员函数" << endl;
}
};
int main()
{
Stu s1;
s1.init_stu() ;
return 0;
}
通过运行结果可以看见 , 类外可直接访问设置为共有的成员 , 而不可以直接访问私有成员。
习惯上 , 外部可访问的成员函数 通常设置为 公有属性( public ) , 而为了提高成员变量的访问安全性 , 通常将成员变量设置为 私有属性( private ) , 即只有类内部可以访问 。
这里只是简洁介绍一下 类和对象 , 其知识远远不止这些 , 有兴趣 , 可以深挖 。
三、练习
练习一:加号运算符重载
#include <ctime>
#include <iostream>
using namespace std;
class Time {
public:
int hours; // 小时
int minutes; // 分钟
Time() {
hours = 0;
minutes = 0;
}
Time(int h, int m) {
this->hours = h;
this->minutes = m;
}
void show() {
cout << hours << " " << minutes << endl;
}
// write your code here......
Time operator+(const Time & t)
{
int flag = 0;
Time ret;
ret.minutes = minutes + t.minutes;
if(ret.minutes >= 60)
{
flag = 1;
ret.minutes -= 60;
}
ret.hours = hours + t.hours + flag;
return ret;
}
};
int main() {
int h, m;
cin >> h;
cin >> m;
Time t1(h, m);
Time t2(2, 20);
Time t3 = t1 + t2;
t3.show();
return 0;
}
练习二:重载小于号
#include <iostream>
using namespace std;
class Time {
public:
int hours; // 小时
int minutes; // 分钟
Time() {
hours = 0;
minutes = 0;
}
Time(int h, int m) {
this->hours = h;
this->minutes = m;
}
void show() {
cout << hours << " " << minutes << endl;
}
// write your code here......
bool operator<(const Time & t)
{
if(hours != t.hours)
return hours < t.hours;
else if(minutes != t.minutes)
return minutes < t.minutes;
else
return false;
}
};
int main() {
int h, m;
cin >> h;
cin >> m;
Time t1(h, m);
Time t2(6, 6);
if (t1<t2) cout<<"yes"; else cout<<"no";
return 0;
}
练习三:评等级
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
struct Stu
{
int id;
int score1;
int score2;
double calc_score()
{
return score1 * 0.7 + score2 * 0.3;
}
int calc_total()
{
return score1 + score2;
}
};
void judge(struct Stu & s)
{
if(s.calc_score() >= 80.0 && s.calc_total() > 140)
cout << "Excellent" << endl;
else
cout << "Not excellent" << endl;
}
int main()
{
int n;
cin >> n;
struct Stu s;
while(n--)
{
cin >> s.id >> s.score1 >> s.score2 ;
judge(s);
}
return 0;
}
练习四:最高分数的学生姓名
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
//方法一:打擂台
struct stu
{
int score;
string name;
};
int main()
{
int n;
cin >> n;
struct stu max;
cin >> max.score >> max.name;
n--;
while(n--)
{
struct stu s;
cin >> s.score >> s.name;
if(max.score < s.score)
{
max.score = s.score;
max.name = s.name;
}
}
cout << max.name << endl;
return 0;
}
方法二:利用排序
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
using namespace std;
//方法二:利用排序的方式
const int N = 110;
struct stu
{
int score;
string name;
}arr[N];
bool cmp(struct stu s1,struct stu s2)
{
return s1.score > s2.score;//排降序
}
int main()
{
int n ;
cin >> n;
for(int i = 0;i < n ; i++)
{
cin >> arr[i].score >> arr[i].name ;
}
sort(arr,arr+n,cmp);
cout << arr[0].name <<endl;
return 0;
}
练习五:甲流病人初筛
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
struct people
{
string name;
float temp;
int flag;
};
int main()
{
int n;
cin >> n;
int c = 0;//计算初筛为甲流病人的人数
while(n--)
{
struct people p;
cin >> p.name >> p.temp >> p.flag ;
if(p.temp >= 37.5 && p.flag )
{
cout << p.name << endl;
c++;
}
}
cout << c << endl;
return 0;
}
练习六:争夺前五名
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 55;
int arr[N];
bool cmp(int x , int y)
{
return x > y;
}
int main()
{
int n;
cin >> n;
//n个数字输入到arr
for(int i = 0;i < n;i++)
{
cin >> arr[i];
}
//排序
sort(arr,arr+n,cmp);
for(int i = 0;i < 5 ; i++)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
练习七:奖学金
P1093 [NOIP 2007 普及组] 奖学金 - 洛谷
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 310;
struct stu
{
int id;
int chinese;
int math;
int english;
int total;
}s[N];
bool cmp(struct stu s1 , struct stu s2)
{
if(s1.total != s2.total)
return s1.total > s2.total;
else if(s1.chinese != s2.chinese )
return s1.chinese > s2.chinese ;
else
return s1.id < s2.id;
}
int main()
{
int n;
cin >> n;
//循环n次,输入n个学生成绩
for(int i = 1;i <= n ; i++)
{
cin >> s[i].chinese >> s[i].math >> s[i].english;
s[i].id = i;
s[i].total = s[i].chinese + s[i].math + s[i].english;
}
//排序 -- s[0]不是第一个元素
sort(s + 1,s + n + 1,cmp);
for(int i = 1;i<=5;i++)
{
cout << s[i].id << " " << s[i].total << endl;
}
}
练习八:生日
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 110;
struct stu
{
string name;
int y;
int m;
int d;
int id;//存储输入顺序
}s[N];
//排序规则
bool cmp(struct stu s1,struct stu s2)
{
if (s1.y != s2.y )
return s1.y < s2.y ;
else if(s1.m != s2.m)
return s1.m < s2.m ;
else if(s1.d != s2.d)
return s1.d < s2.d;
else
//注意:谁后输入谁先输出
return s1.id > s2.id;
}
int main()
{
int n;
cin >> n;
//输入信息
for(int i = 0;i < n ;i++)
{
cin >> s[i].name >> s[i].y >> s[i].m >> s[i].d ;
s[i].id = i;
}
sort(s,s + n , cmp);
for(int i = 0;i < n ; i++)
{
cout << s[i].name << endl;
}
return 0;
}