作业要求:
创建一个Score类,完成以下功能:
连续输入多位学生的float成绩(成绩 = 科目A成绩 + 科目B成绩 + 科目C成绩);
学生数目可以有用户自定义(默认为2个,最多为100个);
显示每位同学的每科成绩和平均分;
显示每门科目的平均成绩;
对每门成绩进行排序并由高到低显示;
对整个文件进行打包。
要求包括了输入部分,显示每位学生成绩和平均分的部分,求平均的部分,求科目平均分的部分,每门成绩排序的部分。
#include <iostream>
#include<string>
using namespace std;
class Score {
public:
string Name[100] = {};
//构造函数与析构函数
Score() {
times = 2;
cout << "————————构造函数————————" << endl;
}
Score(int times1) {
times = times1;
cout << "————————构造函数————————" << endl;
}
~Score() {
cout << "————————析构函数————————" << endl;
}
//在类声明中只给出成员函数的原型,而将成员函数的定义放在类的外部。
inline void InputGrade();
inline void ShowGrade();
inline void ShowAvgGrade();
inline void paixu();
private:
float Grade[100][3];
int times;
};
构造函数的重载
类中可以定义多个构造函数,以便为对象提供不同的初始化的方法,供用户选用,这些构造函数具有相同的名字,而参数的个数或参数的类型不相同,这称为构造函数的重载。
题目要求默认2名学生,可自定义学生通过构造函数重载可以简单实现。
内联函数
在函数名前冠以关键字inline
,该函数就被声明为内联函数。每当程序中出现对该函数的调用时,C++编译器使用函数体中的代码插入到调用该函数的语句之处,同时使用实参代替形参,以便在程序运行时不再进行函数调用。引入内联函数主要是为了消除调用函数时的系统开销,以提高运行速度。
在类中,使用inline定义内联函数时,必须将类的声明和内联成员函数的定义都放在同一个文件(或同一个头文件)中,否则编译时无法进行代码置换。
inline void Score::InputGrade() {
for (int i = 0; i < times; i++) {
cout <<"请输入学生姓名" << endl;
cin >> Name[i];
cout << "请输入科目A成绩:" << endl;
cin >> Grade[i][0];
cout << "请输入科目B成绩:" << endl;
cin >> Grade[i][1];
cout << "请输入科目C成绩:" << endl;
cin >> Grade[i][2];
}
}
通过for语句循环输入学生姓名与成绩。
成绩数组Grade在Score中为private属性,在本次作业中没有用到类的继承,类的成员函数可以访问类中其他成员。
若存在类的继承,访问规则如下图:
inline void Score::ShowGrade() {
//通过for循环输出存入Name和Grade中的内容。
for (int i = 0; i < times; i++) {
cout << "姓名:"<<Name[i]<<endl;
cout << "科目A的成绩:"<<Grade[i][0] << "科目B的成绩:" << Grade[i][1] << "科目C的成绩:" << Grade[i][2] << endl;
}
}
inline void Score::ShowAvgGrade() {
//求每个学生的平均成绩
float AvgStudent[100] = {0};
for (int i = 0; i < times; i++) {
AvgStudent[i] = AvgStudent[i] + Grade[i][0] + Grade[i][1] + Grade[i][2];
AvgStudent[i] = AvgStudent[i] / 3;
}
//求每科的平均成绩
float AvgsubjectA = 0;
float AvgsubjectB = 0;
float AvgsubjectC = 0;
for (int i = 0; i < times; i++) {
AvgsubjectA = AvgsubjectA + Grade[i][0];
AvgsubjectB = AvgsubjectB + Grade[i][1];
AvgsubjectC = AvgsubjectC + Grade[i][2];
}
AvgsubjectA = AvgsubjectA / times;
AvgsubjectB = AvgsubjectB / times;
AvgsubjectC = AvgsubjectC / times;
//循环输出
for (int i = 0; i < times; i++) {
cout << "姓名:" << Name[i] << ";平均分:" << AvgStudent[i] << endl;
}
cout << "学科A的平均成绩:" << AvgsubjectA << ";学科B的平均成绩:" << AvgsubjectB << ";学科C的平均成绩:" << AvgsubjectC << endl;
}
//冒泡排序法
inline void Score::paixu() {
string Name1[100] = {};
for (int k = 0; k < 3; k++) {
for (int m = 0; m < times; m++) {
Name1[m] = Name[m];
}
for (int i = 0; i < times; i++) {
for (int j = 0; j < times - 1 - i; j++) {
if (Grade[j][k] < Grade[j + 1][k]) {
swap(Name1[j], Name1[j + 1]);
swap(Grade[j][k], Grade[j + 1][k]);
}
}
}
if (k == 0) {
cout << "科目名称:A" << endl;
int i = 0;
for (i ; i < times; i++) {
cout << "姓名:" << Name1[i] << " ;成绩:" << Grade[i][k] << endl;
}
}
if (k == 1) {
cout << "科目名称:B" << endl;
int i = 0;
for ( i ; i < times; i++) {
cout << "姓名:" << Name1[i] << " ;成绩:" << Grade[i][k] << endl;
}
}
if (k == 2) {
cout << "科目名称:C" << endl;
int i = 0;
for ( i ; i < times; i++) {
cout << "姓名:" << Name1[i] << " ;成绩:" << Grade[i][k] << endl;
}
}
}
}
问题1
debug过程:
for (int i = 0; i < times; i++) {
for (int j = 0; i < times - 1 - i; j++) {
if (Grade[j][k] < Grade[j + 1][k]) {
temp2 = Grade[j][0];
Grade[j][k] = Grade[j + 1][k];
Grade[j + 1][k] = temp2;
temp1 = Name[j];
Name[j] = Name[j + 1];
Name[j + 1] = temp1;
}
}
}
在初次执行的时候,无论怎么调试输出部分,都无法输出排序部分,只输出if语句中对应的cout内容。
为了排除交换出错改用swap函数进行交换,但是问题依旧存在。
后经单步执行调试发现内部for循环循环了20几次还没有跳出,发现把判断条件打错了,应为j < times - 1 - i。
void swap(float* a, float* b) {
float tmp = *a;
*a = *b;
*b = tmp;
}
void swap(string* a, string* b) {
string tmp = *a;
*a = *b;
*b = tmp;
}
通过定义了swap函数来执行数组内容的交换,
函数重载
在C++中,用户可以重载函数。这意味着,在同一作用域内,只要函数参数的类型不同,或者参数的个数不同,或者二者兼而有之,两个或者两个以上的函数可以使用相同的函数名。在使用的时候要注意以下几点:
- 调用重载函数时,函数返回值类型不在参数匹配检查之列。因此,若两个函数的参数个数和类型都相同,而只有返回值类型不同,则不允许重载。
- 函数的重载与带默认值的函数一起使用时,有可能引起二义性。
- 在调用函数时,如果给出的实参和形参类型不相符,C++的编译器会自动地做类型转换工作。如果转换成功,则程序继续执行,在这种情况下,有可能产生不可识别的错误。
问题2
inline void Score::paixu() {
for (int i = 0; i < times; i++) {
for (int j = 0; j < times - 1 - i; j++) {
if (Grade[j][k] < Grade[j + 1][k]) {
swap(Name[j], Name[j + 1]);
swap(Grade[j][k], Grade[j + 1][k]);
}
}
}
if (k == 0) {
cout << "科目名称:A" << endl;
int i = 0;
for (i ; i < times; i++) {
cout << "姓名:" << Name[i] << " ;成绩:" << Grade[i][k] << endl;
}
}
if (k == 1) {
cout << "科目名称:B" << endl;
int i = 0;
for ( i ; i < times; i++) {
cout << "姓名:" << Name[i] << " ;成绩:" << Grade[i][k] << endl;
}
}
if (k == 2) {
cout << "科目名称:C" << endl;
int i = 0;
for ( i ; i < times; i++) {
cout << "姓名:" << Name[i] << " ;成绩:" << Grade[i][k] << endl;
}
}
}
}
上方代码是原来思路,原本在排序过程中,会出现成绩排序正确,但是后面科目B、科目C姓名和成绩发生错乱,对应不上,后面设定了一系列的值,来判断问题所在,排除了交换问题之后,发现是在科目A排序的同时,也打乱了Name数组本来的顺序,致使后面的排序是根据已经安照前面科目成绩大小排序过的Name数组来排序,会出现逻辑错误。
解决方案:
在排序函数中定义一个大小和Name相同的数组Name1;每次排序之前,将Name的内容循环赋值给Name1,排序过程中对Name1操作,保证Name数组的顺序不变。
问题3
string Name1[100] = {0};
在定义Name1数组时,之前定义如上,出现了闪退报错,发现是格式书写有问题。
string Name1[100] = {""};
string Name1[100] = {};
上面这两种书写方式均可以运行。string 数组不能用0初始化。{0}方法初始化数组可能出现移植性问题,即并不是所有编译器都能通吃。
int main(){
int m;
Score x;
cout << "默认学生数目为2" << endl;
x.InputGrade();
x.ShowGrade();
x.ShowAvgGrade();
x.paixu();
cout << "请输入学生人数:(不大于100)" << endl;
cin >> m;
Score y(m);
y.InputGrade();
y.ShowGrade();
y.ShowAvgGrade();
y.paixu();
system("PAUSE");//暂停,防止打包程序输出框执行完毕直接退出;
return 0;
}
对象的定义和使用
通常把具有共同属性和行为的事物所构成的集合称为类。
类的对象可以看成该类类型的一个实例,定义一个对象和定义一个一般变量相似。
对象的定义:
- 在声明类的同时,直接定义对象,即在类后直接定义。
- 声明了类之后,在使用时再定义对象,如此处定义。
对象中成员的访问
对象名.数据成员名对象名.成员函数名[(参数表)]
访问属性
在Score类的定义中,将Grade定义为私有属性,私有成员只能被类中的成员函数访问,不能在类的外部,通过类的对象进行访问。可以通过类内函数进行访问。
一般来说,公有成员是类的对外接口,而私有成员是类的内部数据和内部实现,不希望外界访问。将类的成员划分为不同的访问级别有两个好处:一是信息隐蔽,即实现封装,将类的内部数据与内部实现和外部接口分开,这样使该类的外部程序不需要了解类的详细实现;二是数据保护,即将类的重要信息保护起来,以免其他程序进行不恰当的修改。
问题4
同学在运行代码时,出现了堆栈溢出的情况,一一检查对应代码,发现是Grade数组定义为了Grade[100][100],定义了一大堆用不到的空间,占了太多的堆栈。