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

在C++中,关于面向对象程序设计已经讲了很大篇幅,也例举很多案例,此篇将通过一些习题来进一步了解对象、静态成员、指针、引用、友元、类模板等等相关知识。

一、习题一(构造函数默认参数)

示例代码:

cpp 复制代码
#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行的构造函数中添加默认参数,即:

cpp 复制代码
Date(int=1, int=1, int=2005);

分析此程序是否有问题,并分析出错误信息,修改程序后使之能通过编译,得到上图一样结果。

解答:

关于构造函数前面也讲过,地址:C++面向对象程序设计 - 构造函数-CSDN博客,在该篇的"六、构造函数添加默认参数"中作了简单阐述。当时时建议大家在写构造函数时,尽量不要使用默认参数,因为一般程序员对此不好把控,编译时会出现错误【call of overloaded 'function_name' is ambiguous】。在构造函数形参中添加默认参数,通常编译器在尝试解析构造函数时发现了多个可能的匹配项,它无法确定应该使用哪一个,因为所有这此匹配项在某种程序上都是可行的。

将构造函数中添加默认参数代码(错误代码)如下:

cpp 复制代码
#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()将不会被匹配到。所以此题想要解决此问题,将后面三个构造函数删除即可,代码(正确代码)如下:

cpp 复制代码
#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博客),在"三、对象数组的指针"中讲到把数组赋值给指针变量是指向数组中第一个元素,所以此题代码如下:

cpp 复制代码
#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名学生中成绩最高者。代码如下:

cpp 复制代码
#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来存储最高者的学号和成绩,并在构造函数中判断初始值谁的成绩最高,其结果也是一样的。代码如下:

cpp 复制代码
#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对象。代码如下:

cpp 复制代码
#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;
}

此时运行后的结果依然是一样的。

三、习题三(常对象、常指针、引用)

示例代码:

cpp 复制代码
#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行修改为:

cpp 复制代码
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'。

按上述问题进行修改后,程序则可以正常编译并运行,代码如下:

cpp 复制代码
#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函数改为以下内容,其他问题扔为示例中代码:

cpp 复制代码
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也只能调用常成员函数,以及数据成员无法修改等问题。所以按"提问一"中代码修改即可,代码如下:

cpp 复制代码
#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行改为以下内容,其他问题扔为示例中代码:

cpp 复制代码
Student * const p = &stu;

**解答:**这里是创建了一个指向Student对象的常量指针,这个const修饰的是指针p本身,而不是所指向的Student对象。这意味着常指针这初始化指向&stu后,不能重新赋值指针,但可以通过p修改它所指向的Student对象的内容。所以不会影响示例中Student对象,代码如下:

cpp 复制代码
#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函数中无修改,直接通过引用的别名调用即可。代码如下:

cpp 复制代码
#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博客)中已经列举了友元的相关内容,需要了解的朋友可以前去查看。

示例代码:

cpp 复制代码
#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类中声明为友元函数即可。另外对构造函数也作了些调整,将在类体外定义改为类体内定义构造函数,并初始化数据成员。代码如下:

cpp 复制代码
#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。代码如下:

cpp 复制代码
#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;
}

五、习题五(类模板)

示例代码:

cpp 复制代码
#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模板类的构造函数。

在类模板外部定义成员函数,代码如下:

cpp 复制代码
#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博客)中已讲解,在"四、归纳"中也归纳几种形式。

相关推荐
未来可期LJ19 分钟前
【C++ 设计模式】单例模式的两种懒汉式和饿汉式
c++·单例模式·设计模式
Trouvaille ~1 小时前
【C++篇】C++类与对象深度解析(六):全面剖析拷贝省略、RVO、NRVO优化策略
c++·c++20·编译原理·编译器·类和对象·rvo·nrvo
little redcap1 小时前
第十九次CCF计算机软件能力认证-乔乔和牛牛逛超市
数据结构·c++·算法
AI原吾2 小时前
掌握Python-uinput:打造你的输入设备控制大师
开发语言·python·apython-uinput
机器视觉知识推荐、就业指导2 小时前
Qt/C++事件过滤器与控件响应重写的使用、场景的不同
开发语言·数据库·c++·qt
毕设木哥2 小时前
25届计算机专业毕设选题推荐-基于python的二手电子设备交易平台【源码+文档+讲解】
开发语言·python·计算机·django·毕业设计·课程设计·毕设
珞瑜·2 小时前
Matlab R2024B软件安装教程
开发语言·matlab
weixin_455446172 小时前
Python学习的主要知识框架
开发语言·python·学习
孤寂大仙v2 小时前
【C++】STL----list常见用法
开发语言·c++·list
她似晚风般温柔7893 小时前
Uniapp + Vue3 + Vite +Uview + Pinia 分商家实现购物车功能(最新附源码保姆级)
开发语言·javascript·uni-app