在C++中,关于面向对象程序设计已经讲了很大篇幅,也例举很多案例,此篇将通过一些习题来进一步了解对象、静态成员、指针、引用、友元、类模板等等相关知识。
一、习题一(构造函数默认参数)
示例代码:
#include <iostream>
using namespace std;
class Date{
public:
Date(int, int, int);
Date(int, int);
Date(int);
Date();
void display();
private:
int month;
int day;
int year;
};
Date::Date(int m, int d, int y): month(m), day(d), year(y) {}
Date::Date(int m, int d): month(m), day(d){
year = 2005;
}
Date::Date(int m): month(m){
day = 1;
}
Date::Date(){
month = 1;
day = 1;
year = 2005;
}
void Date::display(){
cout <<month <<"/" <<day <<"/" <<year <<endl;
}
int main(){
Date d1(10, 13, 2005);
Date d2(12, 30);
Date d3(10);
Date d4;
d1.display();
d2.display();
d3.display();
d4.display();
return 0;
}
如上代码,运行后结果如下图:
1.1 提问
现在将上述代码中,第5行的构造函数中添加默认参数,即:
Date(int=1, int=1, int=2005);
分析此程序是否有问题,并分析出错误信息,修改程序后使之能通过编译,得到上图一样结果。
解答:
关于构造函数前面也讲过,地址:C++面向对象程序设计 - 构造函数-CSDN博客,在该篇的“六、构造函数添加默认参数”中作了简单阐述。当时时建议大家在写构造函数时,尽量不要使用默认参数,因为一般程序员对此不好把控,编译时会出现错误【call of overloaded 'function_name' is ambiguous】。在构造函数形参中添加默认参数,通常编译器在尝试解析构造函数时发现了多个可能的匹配项,它无法确定应该使用哪一个,因为所有这此匹配项在某种程序上都是可行的。
将构造函数中添加默认参数代码(错误代码)如下:
#include <iostream>
using namespace std;
class Date{
public:
Date(int=1, int=1, int=2005);
Date(int, int);
Date(int);
Date();
void display();
private:
int month;
int day;
int year;
};
Date::Date(int m, int d, int y): month(m), day(d), year(y) {}
Date::Date(int m, int d): month(m), day(d){
year = 2005;
}
Date::Date(int m): month(m){
day = 1;
}
Date::Date(){
month = 1;
day = 1;
year = 2005;
}
void Date::display(){
cout <<month <<"/" <<day <<"/" <<year <<endl;
}
int main(){
Date d1(10, 13, 2005);
Date d2(12, 30);
Date d3(10);
Date d4;
d1.display();
d2.display();
d3.display();
d4.display();
return 0;
}
此时运行时,编译器会报错【[Error] call of overloaded 'Date(int, int)' is ambiguous】- 调用重载的'Date(int, int)'是不明确的。
上述代码中,在定义d2,d3,d4时,其实构造函数Date(int=1, int=1, int=2005)都是可行的,将会一直匹配第一个构造函数,后面定义的Date(int, int)、Date(int)、Date()将不会被匹配到。所以此题想要解决此问题,将后面三个构造函数删除即可,代码(正确代码)如下:
#include <iostream>
using namespace std;
class Date{
public:
Date(int=1, int=1, int=2005);
void display();
private:
int month;
int day;
int year;
};
Date::Date(int m, int d, int y): month(m), day(d), year(y) {}
void Date::display(){
cout <<month <<"/" <<day <<"/" <<year <<endl;
}
int main(){
Date d1(10, 13, 2005);
Date d2(12, 30);
Date d3(10);
Date d4;
d1.display();
d2.display();
d3.display();
d4.display();
return 0;
}
其运行结果和前面也是一样的。
二、习题二(指针)
2.1 提问一
建立一个对象数组,内放5个学生的数据(学号、成绩),用指针指向数组首元素,输出第1,3,5个学生的数据。
解答:
在前面篇幅讲到指针(C++面向对象程序设计 - 对象指针和this指针-CSDN博客),在“三、对象数组的指针”中讲到把数组赋值给指针变量是指向数组中第一个元素,所以此题代码如下:
#include <iostream>
using namespace std;
class Student{
private:
int num; //学号
int score; //成绩
public:
Student(int n, int s): num(n), score(s){}
void display(){
cout <<"num:" <<num <<", score:" <<score <<endl;
};
};
int main(){
Student sArr[5] = {
Student(1001, 87),
Student(1002, 98),
Student(1003, 89),
Student(1004, 92),
Student(1005, 81)
};
Student *p = sArr;
//显示第1个
p->display();
//显示第3个
(p+2)->display();
//显示第5个
(p+4)->display();
return 0;
}
通过指针的移位,输出指针对应的Student对象数据,结果如下图:
2.2 提问二
在学生Student对象中设立一个函数max,用指向对象的指针作函数参数,在max函数中找到5个学生中成绩最高者,并输出其学号。
解答:
此题让我们首先想到的则是静态成员,这里就先按题目中的方法,在对象中定义一个静态max函数用来找到5名学生中成绩最高者。代码如下:
#include <iostream>
using namespace std;
class Student{
private:
int num; //学号
int score; //成绩
public:
Student(int n, int s): num(n), score(s){}
void display(){
cout <<"num:" <<num <<", score:" <<score <<endl;
}
get_num(){ return this->num; }
get_score(){ return this->score; }
// 声明获取成绩最高的学生
static Student max(Student*, int);
};
// 定义静态成员函数max
Student Student::max(Student* stuArr, int size){
Student maxStu = (*stuArr); //默认第一个为第大值
for(int i = 0; i < size; i++){
if(stuArr[i].get_score() > maxStu.get_score()){
maxStu = stuArr[i];
}
}
return maxStu;
};
int main(){
Student sArr[5] = {
Student(1001, 87),
Student(1002, 98),
Student(1003, 89),
Student(1004, 92),
Student(1005, 81)
};
// 显示成绩最高者
Student h = Student::max(sArr, 5);
h.display();
return 0;
}
运行结果如下图:
2.3 提问三
对于“提问二”,也可以脱题使用静态数据成员来完成,定义HighNum和HighScore来存储最高者的学号和成绩,并在构造函数中判断初始值谁的成绩最高,其结果也是一样的。代码如下:
#include <iostream>
using namespace std;
class Student{
private:
int num; //学号
int score; //成绩
static int HighNum; //最高者编号
static int HighScore; //最高者成绩
public:
Student(int n, int s): num(n), score(s){
// 判断最大值
if(s>HighScore){
HighScore = s;
HighNum = n;
}
}
void display(){
cout <<"num:" <<num <<", score:" <<score <<endl;
}
// 显示最大值
static void max(){
cout <<"num:" <<HighNum <<", score:" <<HighScore <<endl;
}
};
// 初始化静态数据成员
int Student::HighNum = 0;
int Student::HighScore = 0;
int main(){
Student sArr[5] = {
Student(1001, 87),
Student(1002, 98),
Student(1003, 89),
Student(1004, 92),
Student(1005, 81)
};
// 显示成绩最高者
Student::max();
return 0;
}
解答:
可能有些人在想,这里Student对象中数据成员较少,但如果遇到数据成员较多的,此时输出最高者的信息不是要定义很多对应最高者的数据成员,代码显示会比较臃肿。所以获取最高者,并能输出最高者中所有数据成员信息(不受数据成员限制),明显是记录Student对象最高效的。在这保留最高分这个数据成员,将第另个静态数据成员改为对应的Student对象。代码如下:
#include <iostream>
using namespace std;
class Student{
private:
int num; //学号
int score; //成绩
static int HighScore; //最高者成绩
static Student stu; //最高者
public:
Student(int n, int s): num(n), score(s){
// 判断最大值
if(s>HighScore){
HighScore = s;
stu = (*this);
}
}
void display(){
cout <<"num:" <<num <<", score:" <<score <<endl;
}
// 成绩最高者
static Student max(){
return stu;
}
};
// 初始化静态数据成员
int Student::HighScore = 0;
Student Student::stu = Student(0, 0);
int main(){
Student sArr[5] = {
Student(1001, 87),
Student(1002, 98),
Student(1003, 89),
Student(1004, 92),
Student(1005, 81)
};
// 显示成绩最高者
Student s = Student::max();
s.display();
return 0;
}
此时运行后的结果依然是一样的。
三、习题三(常对象、常指针、引用)
示例代码:
#include <iostream>
using namespace std;
class Student{
public:
Student(int n, float s): num(n), score(s){}
void change(int n, float s){
num = n;
score = s;
}
void display(){
cout <<num <<" " <<score <<endl;
}
private:
int num;
float score;
};
int main(){
Student stu(101, 78.5);
stu.display();
stu.change(101, 80.5);
stu.display();
return 0;
}
运行结果如下:
3.1 提问一
将main函数第2行修改为:
const Student stu(101, 78.5);
解答:如上述方法,主要会出现以下两个错误:
- 如果尝试通过const对象来调用非const成员函数,编译器会报错,因非const成员函数需要一个指向非const对象的this指针,而const对象只能提供一个指向const对象的this指针。所以当执行到stu.display()时会报错【[Error] passing 'const Student' as 'this' argument discards qualifiers [-fpermissive]】- 将'const Student'作为'this'参数传递时会放弃限定符。解决方法则是将change成员函数和display成员函数都定义为常成员函数。
- 一旦创建一个const对象,它的任何成员都不能被修改。然而试图使用change()函数来修改对象的num和score成员,是不允许的。但是C++中也给出解决方案,可以在数据成员前面添加mutable。否则编译器会报错【[Error] assignment of member 'Student::num' in read-only object】- 在只读对象中分配成员'Student::num'。
按上述问题进行修改后,程序则可以正常编译并运行,代码如下:
#include <iostream>
using namespace std;
class Student{
public:
Student(int n, float s): num(n), score(s){}
void change(int n, float s) const {
num = n;
score = s;
}
void display() const {
cout <<num <<" " <<score <<endl;
};
private:
mutable int num;
mutable float score;
};
int main(){
const Student stu(101, 78.5);
stu.display();
stu.change(101, 80.5);
stu.display();
return 0;
}
3.2 提问二
将main函数改为以下内容,其他问题扔为示例中代码:
int main(){
Student stu(101, 78.5);
const Student *p = &stu;
p->display();
p->change(101, 80.5);
p->display();
return 0;
}
解答:这题是先定义一个Student类型的对象,并使用一个指向const Student的指针来指向这个对象,一般称为常对象的指针变量。即然是常对象,则与“提问一”中是一样的问题,指针变量p也只能调用常成员函数,以及数据成员无法修改等问题。所以按“提问一”中代码修改即可,代码如下:
#include <iostream>
using namespace std;
class Student{
public:
Student(int n, float s): num(n), score(s){}
void change(int n, float s) const {
num = n;
score = s;
}
void display() const {
cout <<num <<" " <<score <<endl;
}
private:
mutable int num;
mutable float score;
};
int main(){
Student stu(101, 78.5);
const Student *p = &stu;
p->display();
p->change(101, 80.5);
p->display();
return 0;
}
3.3 提问三
把“提问二”中的第3行改为以下内容,其他问题扔为示例中代码:
Student * const p = &stu;
解答:这里是创建了一个指向Student对象的常量指针,这个const修饰的是指针p本身,而不是所指向的Student对象。这意味着常指针这初始化指向&stu后,不能重新赋值指针,但可以通过p修改它所指向的Student对象的内容。所以不会影响示例中Student对象,代码如下:
#include <iostream>
using namespace std;
class Student{
public:
Student(int n, float s): num(n), score(s){}
void change(int n, float s) {
num = n;
score = s;
}
void display() {
cout <<num <<" " <<score <<endl;
}
private:
int num;
float score;
};
int main(){
Student stu(101, 78.5);
Student * const p = &stu;
p->display();
p->change(101, 80.5);
p->display();
return 0;
}
3.4 提问四
在程序中增加一个fun函数,在main函数中调用fun函数,在fun函数中调用change和display函数,在fun函数中使用对象引用(Student &)作为形参。
解答:一个变量的引用其实就是变量的别名,变量名和引用名指向是同一段内存单元,所以在fun函数中无修改,直接通过引用的别名调用即可。代码如下:
#include <iostream>
using namespace std;
class Student{
public:
Student(int n, float s): num(n), score(s){}
void change(int n, float s) {
num = n;
score = s;
}
void display() {
cout <<num <<" " <<score <<endl;
}
private:
int num;
float score;
};
// 新增的fun函数
void fun(Student &s){
s.display();
s.change(101, 80.5);
s.display();
}
int main(){
Student stu(101, 78.5);
fun(stu);
return 0;
}
四、习题四(友元)
在上篇(C++面向对象程序设计 - 静态成员、友元-CSDN博客)中已经列举了友元的相关内容,需要了解的朋友可以前去查看。
示例代码:
#include <iostream>
using namespace std;
class Date;
class Time{
public:
Time(int, int, int);
void display(Date &);
private:
int hour;
int minute;
int second;
};
class Date{
public:
Date(int, int, int);
// 声明Time类中的display函数为本类的友元成员函数
friend void Time::display(Date &);
private:
int year;
int month;
int day;
};
Time::Time(int h, int m, int s): hour(h), minute(m), second(s){}
void Time::display(Date &d){
cout <<d.year <<"/" <<d.month <<"/" <<d.day <<" " <<hour <<":" <<minute <<":" <<second <<endl;
}
Date::Date(int y, int m, int d): year(y), month(m), day(d){}
int main(){
Time t1(10, 13, 56);
Date d1(2024, 12, 25);
t1.display(d1);
return 0;
}
运行输出结果如下:
4.1 提问一
将示例中的display函数不放在Time类中,而作为类外的普通函数,然后分别在Time和Date类中将display声明为友元函数。在主函数中调用display函数,display函数分别引用Time和Date两个类的对象的私有数据,输出年、月、日和时、分、秒。
解答:此题是将display定义为友元函数,比较简单,将display在Time和Date类中声明为友元函数即可。另外对构造函数也作了些调整,将在类体外定义改为类体内定义构造函数,并初始化数据成员。代码如下:
#include <iostream>
using namespace std;
class Date;
class Time{
public:
Time(int h, int m, int s): hour(h), minute(m), second(s){}
// 声明display为友函数
friend void display(Time &, Date &);
private:
int hour;
int minute;
int second;
};
class Date{
public:
Date(int y, int m, int d): year(y), month(m), day(d){}
// 声明display为友函数
friend void display(Time &, Date &);
private:
int year;
int month;
int day;
};
// 定义display函数 - 显示时间
void display(Time &t, Date &d){
cout <<d.year <<'/' <<d.month <<'/' <<d.day <<' ' <<t.hour <<':' <<t.minute <<':' <<t.second <<endl;
}
int main(){
Time t(23, 59, 59);
Date d(2024, 4, 14);
// 显示时间
display(t, d);
return 0;
}
4.2 提问二
将示例中Date类声明为Time类的友元类,通过Date类中的display函数引用Time类对象的私有数据,输出年、月、日和时、分、秒。
解答:此题是将示例中friend友元声明函数到Time中,将display函数变成Date的成员函数即可;并且注意修改Date类和Time定义顺序,第一行声明的class Date需要修改为class Time。代码如下:
#include <iostream>
using namespace std;
class Time;
class Date{
public:
Date(int y, int m, int d): year(y), month(m), day(d){}
void display(Time &);
private:
int year;
int month;
int day;
};
class Time{
public:
Time(int h, int m, int s): hour(h), minute(m), second(s){}
// 声明Date类中display为Time类的友函数
friend void Date::display(Time &);
private:
int hour;
int minute;
int second;
};
void Date::display(Time &t){
cout <<year <<'/' <<month <<'/' <<day <<' ' <<t.hour <<':' <<t.minute <<':' <<t.second <<endl;
}
int main(){
Time t(23, 59, 59);
Date d(2024, 4, 14);
// 显示时间
d.display(t);
return 0;
}
五、习题五(类模板)
示例代码:
#include <iostream>
using namespace std;
template<class numtype>
class Compare{
public:
Compare(numtype a, numtype b){
x = a;
y = b;
}
// 获取最大值
numtype max(){
return x>y?x:y;
}
// 获取最小值
numtype min(){
return x>y?y:x;
}
private:
numtype x, y;
};
int main(){
Compare<int> cp1(3, 7);
cout <<cp1.max() <<" is the Maximum of two integer numbers." <<endl;
cout <<cp1.min() <<" is the Minimum of two integer numbers." <<endl <<endl;
Compare<float> cp2(45.78f, 93.6f);
cout <<cp2.max() <<" is the Maximum of two float numbers." <<endl;
cout <<cp2.min() <<" is the Minimum of two float numbers." <<endl <<endl;
Compare<char> cp3('a', 'A');
cout <<cp3.max() <<" is the Maximum of two char numbers." <<endl;
cout <<cp3.min() <<" is the Minimum of two char numbers." <<endl <<endl;
return 0;
}
运行结果如下图:
5.1 提问
将示例中程序改写为在类模板外定义各成员函数。
解答:如果成员函数是在类模板外定义的,则不能用一般定义类成员函数的形式。需要注意以下几点:
- 在类模板内部,构造函数只是被声明了,没有定义体。
- 在类模板外部,构造函数的定义使用了template<class numtype>来指明它是一个模板类的成员函数。
- 构造函数的定义后跟着Compare<numtype>::Compare(numtype a, numtype b),这表示我们正在定义Compare模板类的构造函数。
在类模板外部定义成员函数,代码如下:
#include <iostream>
using namespace std;
template<class numtype>
class Compare{
public:
Compare(numtype, numtype); //声明构造函数
numtype max(); //声明成员函数max
numtype min(); //声明成员函数min
private:
numtype x, y;
};
// 在类模板外定义构造函数
template<class numtype>
Compare<numtype>::Compare(numtype a, numtype b): x(a), y(b){}
// 在类模板外定义成员函数max、
template<class numtype>
numtype Compare<numtype>::max(){
return x>y?x:y;
}
// 在类模板外定义成员函数min
template<class numtype>
numtype Compare<numtype>::min(){
return x>y?y:x;
}
int main(){
Compare<int> cp1(3, 7);
cout <<cp1.max() <<" is the Maximum of two integer numbers." <<endl;
cout <<cp1.min() <<" is the Minimum of two integer numbers." <<endl <<endl;
Compare<float> cp2(45.78f, 93.6f);
cout <<cp2.max() <<" is the Maximum of two float numbers." <<endl;
cout <<cp2.min() <<" is the Minimum of two float numbers." <<endl <<endl;
Compare<char> cp3('a', 'A');
cout <<cp3.max() <<" is the Maximum of two char numbers." <<endl;
cout <<cp3.min() <<" is the Minimum of two char numbers." <<endl <<endl;
return 0;
}
如在在类模板体外定义成员函数,第1行是声明类模板,第2行第一个numtype是虚拟类型名,后面Compare<numtype>是一个整体,是带参的类;表示所定义的max函数是在类Compare<numtype>的作用域内的;在定义对象时,用户要指定实际的类型(如int),进行编译时就会将类模板中的虚拟类型名numtype全部用实际的类型代替,这样Compare<numtype>就相当于一个实际的类。
类模板声明和使用在上篇(C++面向对象程序设计 - 类模板-CSDN博客)中已讲解,在“四、归纳”中也归纳几种形式。