C++面向对象程序设计 - 类和对象进一步讨论

        在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);

        解答:如上述方法,主要会出现以下两个错误:

  1. 如果尝试通过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成员函数都定义为常成员函数。
  2. 一旦创建一个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 提问

        将示例中程序改写为在类模板外定义各成员函数。

        解答:如果成员函数是在类模板外定义的,则不能用一般定义类成员函数的形式。需要注意以下几点:

  1. 在类模板内部,构造函数只是被声明了,没有定义体。
  2. 在类模板外部,构造函数的定义使用了template<class numtype>来指明它是一个模板类的成员函数。
  3. 构造函数的定义后跟着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博客)中已讲解,在“四、归纳”中也归纳几种形式。

  • 15
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值