《C++ Primer Plus》第十三章复习题和编程练习

news2024/11/26 12:23:47

目录

  • 一、复习题**
  • 二、编程练习

一、复习题**

1. 派生类从基类那里继承了什么?

答:在类的继承和派生中,C++中的派生类能够继承基类的所有数据成员和大部分成员函数。但是基类中不同访问控制权限的成员在派生中的访问权限也不相同。公有成员直接成为派生类的公有成员,派生类的对象可以直接访问;基类的保护成员成为派生类的保护成员,派生类的对象也可以直接访问;基类的私有成员被派生类继承,但是派生类对象不能直接访问,只能通过基类的公有方法间接访问。

2. 派生类不能从基类那里继承什么?

答:派生类不能继承基类的构造函数、析构函数、赋值运算符、友元函数和友元类。但是派生类在调用构造函数时会调用基类的默认构造函数,也可以在初始化列表中传参指定使用基类的哪一个构造函数。在调用派生类对象的析构函数时,系统先调用派生类的析构函数,再调用基类的构造函数。

3. 假设baseDMA::operator=()函数的返回类型为void,而不baseDMA&,将会产生什么后果?如果返回类型为baseDMA,而不是baseDMA&,又将有什么后果?

答:如果赋值运算符重载的返回类型为void,仍然可以单个赋值,但是不能连续赋值,即可以使用 baseDMA a = b,但不能使用 baseDMA a = b = c。因为b = c 的结果为void。
  如果返回类型为baseDAM,则与baseDAM&相比,在返回值时会创建一个临时对象,然后给其赋值,再返回这个临时对象。因此运行效率降低。

4. 在创建和删除派生类对象时,构造函数和析构函数调用的顺序是怎么样的?

答:创建派生类对象时,如果不在派生类的构造函数的初始化列表中显式指定基类的构造函数,则系统自动调用基类的默认构造函数,然后再对派生类新增的成员变量进行初始化,总之,创建派生类对象时,先调用基类的构造函数,再调用派生类的构造函数。删除派生类对象时,编译器先调用派生类的析构函数,然后再调用基类的析构函数。由于基类的指针和引用可以指向派生类,所以基类的析构函数最好使用虚函数。

5. 如果没有为派生类添加任何数据成员,它是否需要构造函数?

答:需要。派生类不会继承基类的构造函数,而在C++中每一个类都必须要有自己的构造函数,即使这个类没有数据成员和自定义的构造函数,编译器也会自动生成一个默认构造函数,并在创建对象时调用。

6. 如果基类和派生类定义了同名的方法,当派生类对象调用该方法时,被调用的将是哪个方法?

答:如果派生类中定义了与基类中同名的方法,则派生类中定义的方法会隐藏基类中同名的方法,被调用的是派生类中的方法。通常情况下,派生类的对象只会调用派生类中新定义的方法,不会调用基类中同名的方法。只有当派生类没有重新定义同名方法或使用基类的作用域运算符时,派生类才会调用基类的方法。

7. 在什么情况下,派生类应定义赋值运算符?

答:如果派生类中存在动态存储,即在派生类的构造函数中使用new或new[]运算符来初始化类的某些指针类型的数据成员,则该类必须提供赋值运算符重载、析构函数和复制构造函数的定义。因为默认的赋值运算符重载使用的是浅拷贝,单纯地赋值,比如两个指针指向同一块空间,并不会开辟属于自己的空间,这就导致使用析构函数的时候对一块空间释放两次,导致未知的错误。如果派生类中不存在动态存储,则不需要定义赋值运算符。因为默认的赋值运算符在对基类中的成员进行赋值时,调用的是基类中的赋值运算符,而调用析构函数时,也会调用基类的析构函数,析构基类的成员。

8. 可以将派生类对象的地址赋值给基类的指针吗?可以将基类对象的地址赋值给派生类指针吗?

答:由于派生类继承了基类的所有成员变量和大部分成员函数,所以基类的指针和引用都可以指向派生类。但是在使用时,对于非虚函数的同名函数,编译器将根据指针和引用的类型调用基类的同名函数而不是派生类的同名函数。对于虚函数,则编译器将根据指针和引用指向的对象的类型进行调用,指向基类对象调用基类的虚函数,指向派生类对象调用派生类的虚函数。基类对象的地址可以通过强制类型转换,赋值给派生类的指针(向下转换),但是这样使用指针很不安全,因为派生类中可能新增了基类没有的变量,在调用函数时由于没有那个变量而无效,所以不推荐使用。

9. 可以将派生类对象赋值给基类对象吗?可以将基类对象赋值给派生类对象吗?

答:C++中可以将派生类对象赋值给基类对象,因为派生类和基类的的关系是IS-A关于,即派生类也是基类的一种。且派生类对象拥有基类对象的所有成员变量,而且赋值过程中不会把派生类中新增的成员变量赋值给基类对象。通常情况下,基类的对象是不能够给派生类对象赋值的,但是当派生类中定义了转换运算符(即类型转换,将包含以基类引用作为唯一参数的构造函数)或使用基类作为参数的赋值运算符时(重新定义赋值运算符),这种赋值才是可行的。

10. 假设定义了一个函数,它以基类对象的引用作为参数。为什么该函数也可以使用派生类对象作为参数?

答:在C++中基类的指针和引用可以在不显式转换的情况下指向派生类对象。因为在C++中派生类和基类的关系为IS-A,可以把派生类对象看作一种基类对象。

11. 假设定义了一个函数,它以基类对象作为参数(即函数按值传递基类对象)。为什么该函数也可以使用派生类对象作为参数?

答:在按值传递的过程中,会调用基类的构造函数,而参数通常为const引用,而基类的引用和指针可以指向派生类,所以会根据派生类对象中的基类成员变量创建一个临时的基类对象,然后传递给函数。

12. 为什么通常按引用传递对象比按值传递对象的效率更高?

答:在C++中按值传递对象会调用该对象的复制构造函数创建一个临时对象,其优点是保护原数据不被修改,但是会消耗时间和空间,效率降低。而使用引用传递对象,在该函数中使用的就是原来的对象,效率更高,但是可能会修改原数据,若不希望原数据被修改,在函数的参数列表中需要加上const修饰。

13. 假设 Corporation 是基类,PublicCorporation 是派生类。在假设这两个类都定义了head()函数,ph 是指向 Corporation 类型的指针,且指向一个 PublicCorporation 对象的地址。如果基类将head()函数定义为以下两种方法,则ph->head()将如何解释?
  a. 常规非虚方法。
  b. 虚方法。

答: a. 基类中使用常规非虚方法,则编译器会根据指针或引用的类型来判断,所以调用基类的head()函数。
   b. 基类中使用虚方法,则编译器根据指针或引用指向的类型来判断,所以调用派生类的head()函数。

14. 下述代码有什么问题?

// 基类声明
class kitchen
{
private:
	double kit_sq_ft;
public:
	kitchen() { kit_sq_ft = 0; }  // 默认构造函数称为内联函数
	virtual double area() const { return kit_sq_ft * kit_sq_ft; }  // 该虚函数为内联函数
};

// 派生类声明
class House : public kitchen
{
private:
	double all_sq_ft;
public:
	House() { all_sq_ft += kit_sq_ft; }  // 无法直接访问基类中的私有成员,只能通过公有成员访问
	double area(const char* s) const { cout << s; return all_sq_ft; }
};

答:首先,从派生类的基类的IS-A关系角度来说,房子根本不是厨房的一种,两个根本不搭边。可以把厨房类放入房子类中。其次,房子类是厨房类的派生类,不能直接访问厨房类的私有变量 kit_sq_ft 。只能通过基类的公有成员函数来进行访问。而且由于同名函数area()的参数列表不同,虚函数标签(virtual)并没有发挥作用。

二、编程练习

1. 以下面的类声明为基础,派生出一个Classic类,并添加一组char成员,用于存储指出Cd中主要作品的字符串。修改上述声明,使基类的所有函数都是虚的。如果上述定义声明的某个方法并不需要,则请删除它。请使用下面的程序测试你的代码。

// Cd基类声明
class Cd
{
private:
	char performers[50];  // 演员
	char label[20];  // 标签
	int selections;  // 选择
	double playtime;  // 游戏时间
public:
	// 构造函数
	Cd(char* sl, char* s2, int n, double x);
	Cd(const Cd& d);
	Cd();
	// 析构函数
	~Cd();
	// 成员函数
	void Report() const;
	// 运算符重载
	Cd& operator=(const Cd& d);
};

测试代码和结果我都放在下面的解答里面了,结果我都对过一遍了,正确的。

答:三个文件。

classic.h 头文件 :当涉及动态存储或者成员变量里面包含指针类型都需要深度拷贝,程序员都需要自己提供构造函数和赋值运算符重载的定义,动态存储还需提供析构函数的定义。基类中的析构函数最好声明为虚函数,不管是否设计动态存储。

#pragma once

// 头文件
#include <iostream>

// Cd基类声明
class Cd
{
private:
	char performers[50];  // 演员
	char label[20];  // 标签
	int selections;  // 选择
	double playtime;  // 表演时间
public:
	// 构造函数
	Cd(const char* s1, const char* s2, int n, double x);
	Cd(const Cd& d);
	Cd();
	// 析构函数
	virtual ~Cd();  // 基类的析构函数最好定义为virtual,不管是否动态存储
	// 成员函数
	virtual void Report() const;
	// 运算符重载
	Cd& operator=(const Cd& d);
};

// Classic派生类声明
class Classic : public Cd
{
private:
	char main_works[50];  // 主要作品
public:
	// 构造函数
	Classic(const char* nm, const char* lb, const char* works, int st, double pt);
	Classic(const Classic& c);
	Classic();
	// 析构函数
	~Classic();
	// 成员函数
	virtual void Report() const;
	// 运算符重载
	Classic& operator=(const Classic& c);
};

test1.cpp 测试文件 :若使用基类的指针或引用指向派生类调用与基类中的同名方法,需把基类中的该方法声明为虚函数,不然编译器会根据指针或引用的类型去调用基类的该函数。设置为虚函数之后,编译器会根据指针或引用指向的对象的类型调用属于该类型的函数。

// 头文件
#include "classic.h"

// using 声明
using std::cout;
using std::endl;

// 函数声明
void Bravo(const Cd& d);

int main()
{
	Cd c1("Beatles" , "Capital", 14, 35.5);
	Classic c2 = Classic("Piano Sonata in B flat, Fantasia in C",
		"Alfred Brendel", "Philips", 2, 57.17);
	Cd* pcd = &c1;  // 基类指针和引用可以直接指向派生类(向上转换)
	cout << "Using object directly:\n";
	c1.Report();
	c2.Report();
	cout << "Using type cd *pointer to objects:\n";
	pcd->Report();
	pcd = &c2;
	pcd->Report();
	cout << "Calling a function with a Cd reference argument:\n";
	Bravo(c1);
	Bravo(c2);
	cout << "Testing assignment: ";
	Classic copy;
	copy = c2;
	copy.Report();

	return 0;
}

// 函数定义
void Bravo(const Cd& d)
{
	d.Report();
}

classic.cpp 方法定义文件

// 头文件
#include "classic.h"
#include <cstring>

// using声明
using std::cout;
using std::endl;

// Cd基类方法定义
 
// 构造函数
Cd::Cd(const char* s1, const char* s2, int n, double x)
{
	strcpy(performers, s1);
	strcpy(label, s2);
	selections = n;
	playtime = x;
}

Cd::Cd(const Cd& d)
{
	strcpy(performers, d.performers);
	strcpy(label, d.label);
	selections = d.selections;
	playtime = d.playtime;
}

Cd::Cd()
{
	performers[0] = label[0] = '\0';
	selections = 0;
	playtime = 0;
}

// 析构函数
Cd::~Cd()
{
	// 空
}

// 成员函数
void Cd::Report() const
{
	cout << "Performers: " << performers << endl;
	cout << "Label: " << label << endl;
	cout << "Selections: " << selections << endl;
	cout << "Playtime: " << playtime << endl;
}

// 运算符重载
Cd& Cd::operator=(const Cd& d)
{
	strcpy(performers, d.performers);
	strcpy(label, d.label);
	selections = d.selections;
	playtime = d.playtime;

	return *this;
}

// Classic派生类方法定义

// 构造函数
Classic::Classic(const char* nm, const char* lb, const char* works, int st, double pt)
	: Cd(nm, lb, st, pt)
{
	strcpy(main_works, works);
}

Classic::Classic(const Classic& c)
	: Cd(c)
{
	strcpy(main_works, c.main_works);
}

Classic::Classic()
	: Cd()
{
	main_works[0] = '\0';
}

// 析构函数
Classic::~Classic()
{

}

// 成员函数
void Classic::Report() const
{
	// 先调用基类的Report显示基类信息,再显示派生类新增信息
	Cd::Report();
	cout << "Works: " << main_works << endl;
}

// 运算符重载
Classic& Classic::operator=(const Classic& c)
{
	// 先调用基类的赋值运算符给基类成员变量赋值
	Cd::operator=(c);
	strcpy(main_works, c.main_works);

	return *this;
}

运行结果如下:
在这里插入图片描述

2. 重做编程练习1,使两个类使用动态内存分配而不是长度固定的数组来记录字符串。

答:基类和派生类都使用动态内存分配,则基类的析构函数必须声明为虚函数,不然基类指针指向派生类生命周期结束时,只会调用基类的析构函数,而派生类新增成员不会被析构。测试程序和编程练习1一样未改变,所以本题只提供修改后的头文件和方法定义文件。

classic2.h 头文件

#pragma once
#pragma once

// 头文件
#include <iostream>

// Cd基类声明
class Cd
{
private:
	char* performers;  // 演员
	char* label;  // 标签
	int selections;  // 选择
	double playtime;  // 表演时间
public:
	// 构造函数
	Cd(const char* s1, const char* s2, int n, double x);
	Cd(const Cd& d);
	Cd();
	// 析构函数
	virtual ~Cd();  // 基类的析构函数最好定义为virtual,不管是否动态存储
	// 成员函数
	virtual void Report() const;
	// 运算符重载
	Cd& operator=(const Cd& d);
};

// Classic派生类声明
class Classic : public Cd
{
private:
	char* main_works;  // 主要作品
public:
	// 构造函数
	Classic(const char* nm, const char* lb, const char* works, int st, double pt);
	Classic(const Classic& c);
	Classic();
	// 析构函数
	~Classic();
	// 成员函数
	virtual void Report() const;
	// 运算符重载
	Classic& operator=(const Classic& c);
};

classic2.cpp 方法定义文件

#define _CRT_SECURE_NO_WARNINGS

// 头文件
#include "classic2.h"
#include <cstring>

// using声明
using std::cout;
using std::endl;

// Cd基类方法定义

// 构造函数
Cd::Cd(const char* s1, const char* s2, int n, double x)
	: performers(new char[strlen(s1) + 1])
	, label(new char[strlen(s2) + 1])
	, selections(n)
	, playtime(x)
{
	strcpy(performers, s1);
	strcpy(label, s2);
}

Cd::Cd(const Cd& d)
	: performers(new char[strlen(d.performers) + 1])
	, label(new char[strlen(d.label) + 1])
	, selections(d.selections)
	, playtime(d.playtime)
{
	strcpy(performers, d.performers);
	strcpy(label, d.label);
}

Cd::Cd()
	: performers(new char[1] { 0 })
	, label(new char[1] { 0 })
	, selections(0)
	, playtime(0)
{
	
}

// 析构函数
Cd::~Cd()
{
	// 释放两个指针的空间
	delete[] performers;
	delete[] label;
}

// 成员函数
void Cd::Report() const
{
	cout << "Performers: " << performers << endl;
	cout << "Label: " << label << endl;
	cout << "Selections: " << selections << endl;
	cout << "Playtime: " << playtime << endl;
}

// 运算符重载
Cd& Cd::operator=(const Cd& d)
{
	// 检查是否自身赋值
	if (&d != this)
	{
		// 释放之前的空间
		delete[] performers;
		delete[] label;
		// 申请新的空间
		performers = new char[strlen(d.performers) + 1];
		label = new char[strlen(d.label) + 1];
		// 拷贝内容
		strcpy(performers, d.performers);
		strcpy(label, d.label);
		selections = d.selections;
		playtime = d.playtime;
	}

	return *this;
}

// Classic派生类方法定义

// 构造函数
Classic::Classic(const char* nm, const char* lb, const char* works, int st, double pt)
	: Cd(nm, lb, st, pt)
	, main_works(new char[strlen(works) + 1])
{
	strcpy(main_works, works);
}

Classic::Classic(const Classic& c)
	: Cd(c)
	, main_works(new char[strlen(c.main_works) + 1])
{
	strcpy(main_works, c.main_works);
}

Classic::Classic()
	: Cd()
	, main_works(new char[1] { 0 })
{
	
}

// 析构函数
Classic::~Classic()
{
	delete[] main_works;
}

// 成员函数
void Classic::Report() const
{
	// 先调用基类的Report显示基类信息,再显示派生类新增信息
	Cd::Report();
	cout << "Works: " << main_works << endl;
}

// 运算符重载
Classic& Classic::operator=(const Classic& c)
{
	// 检查是否自身赋值
	if (&c != this)
	{
		// 先调用基类的赋值运算符给基类成员变量赋值
		Cd::operator=(c);
		// 释放之前的空间
		delete[] main_works;
		// 申请新的空间
		main_works = new char[strlen(c.main_works) + 1];
		// 拷贝内容
		strcpy(main_works, c.main_works);
	}

	return *this;
}

3. 修改 baseDMA-lacksDMA-hasDMA 类的层次,使三个类都从一个ABC派生而来,然后使用与程序清单13.10相似的程序对结果进行测试。也就是说,它应使用ABC指针数组,并由用户决定要创建的对象类型。在类定义中添加virtual View()方法以显示数据。

答:ABC(抽象基类)必须有一个纯虚成员,且该类不能创建对象,只能用作基类,其析构函数应为纯虚函数。实际上把原来的baseDMA变成抽象基类就行。

DMA.h 头文件

#pragma once

// 头文件
#include <iostream>

// DMA 抽象基类声明
class DMA
{
private:
	char* label;
	int rating;
public:
	// 构造函数
	DMA(const char* lb = "null", int r = 0);
	DMA(const DMA& d);
	// 析构函数
	virtual ~DMA() = 0 { delete[] label; }
	// 虚函数
	virtual void View() const;
	// 运算符重载
	DMA& operator=(const DMA& d);
	// 友元函数
	friend std::ostream& operator<<(std::ostream& os, const DMA& dma);
};

// 三个派生类声明

class baseDMA : public DMA
{
public:
	// 构造函数
	baseDMA(const char* lb = "null", int r = 0);
	// 虚函数
	virtual void View() const;
};

class lacksDMA : public DMA
{
private:
	enum { COL_LEN = 40 };
	char color[COL_LEN];
public:
	// 构造函数
	lacksDMA(const char* c = "blank", const char* lb = "null", int r = 0);
	lacksDMA(const char* c, const baseDMA& dma);
	// 虚函数
	virtual void View() const;
	// 友元函数
	friend std::ostream& operator<<(std::ostream& os, const lacksDMA& ld);
};

class hasDMA : public DMA
{
private:
	char* style;
public:
	// 构造函数
	hasDMA(const char* sl = "none", const char* lb = "null", int r = 0);
	hasDMA(const char* sl, const DMA& dma);
	hasDMA(const hasDMA& hd);
	// 析构函数
	virtual~hasDMA();
	// 虚函数
	virtual void View() const;
	// 运算符重载
	hasDMA& operator=(const hasDMA& hd);
	// 友元函数
	friend std::ostream& operator<<(std::ostream& os, const hasDMA& hd);
};

test3.cpp 测试文件

// 头文件
#include "DMA.h"

// using 声明
using std::cout;
using std::endl;
using std::cin;

// 符号常量声明
const int SIZE = 3;
const int LEN = 40;

int main()
{
	// 基类指针数组声明
	DMA* pdma[SIZE] = { 0 };
	// 所需变量
	char label[LEN];
	int rating = 0;
	// 循环赋值
	for (int i = 0; i < SIZE; ++i)
	{
		cout << "No." << i+1 << endl;
		cout << "Please enter the label: ";
		cin.getline(label, 39);
		label[39] = '\0';  // 保证为字符串
		cout << "Please enter the rating: ";
		cin >> rating;
		cin.get();  // 读取后面的换行符
		cout << "Please select:\n";
		cout << "1) baseDMA    2) lacksDMA\n";
		cout << "3) hasDMA\n";
		int select = 0;
		cin >> select;
		cin.get();
		switch (select)
		{
		case 1 :
			pdma[i] = new baseDMA(label, rating);
			break;
		case 2 :
			char color[LEN];
			cout << "Please enter the color: ";
			cin.getline(color, 39);
			color[39] = '\0';
			pdma[i] = new lacksDMA(color, label, rating);
			break;
		case 3 : 
			char style[LEN];
			cout << "Please enter the style: ";
			cin.getline(style, 39);
			style[39] = '\0';
			pdma[i] = new lacksDMA(style, label, rating);
			break;
		}
		cout << endl;
	}
	// 遍历输出
	for (int i = 0; i < SIZE; ++i)
	{
		cout << "No." << i + 1 << endl;
		pdma[i]->View();
	}
	// 释放空间
	for (int i = 0; i < SIZE; ++i)
	{
		delete pdma[i];
	}

	return 0;
}

DMA.cpp 方法定义文件

// 头文件
#include "DMA.h"
#include <cstring>

// using 声明
using std::cout;
using std::endl;

// DMA 抽象类方法定义

// 构造函数
DMA::DMA(const char* lb, int r)
	: label(new char[strlen(lb) + 1])
	, rating(r)
{
	strcpy(label, lb);
}

DMA::DMA(const DMA& d)
	: label(new char[strlen(d.label) + 1])
	, rating(d.rating)
{
	strcpy(label, d.label);
}

// 虚函数
void DMA::View() const
{
	cout << "Now, it is the DMA.\n";
	cout << *this;
}

// 运算符重载
DMA& DMA::operator=(const DMA& d)
{
	// 检查是否自身赋值
	if (&d != this)
	{
		// 释放之前空间
		delete[] label;
		// 申请新的空间
		label = new char[strlen(d.label) + 1];
		// 拷贝内容
		strcpy(label, d.label);
		rating = d.rating;
	}
	return *this;
}

// 友元函数
std::ostream& operator<<(std::ostream& os, const DMA& dma)
{
	os << "Label: " << dma.label << endl;
	os << "Rating: " << dma.rating << endl;

	return os;
}

// baseDMA 类方法定义

// 构造函数
baseDMA::baseDMA(const char* lb, int r)
	: DMA(lb, r)
{

}

// 虚函数
void baseDMA::View() const
{
	cout << "Now, it is the baseDMA.\n";
	cout << (const DMA&)*this;
}

// lacksDMA 类方法定义

// 构造函数
lacksDMA::lacksDMA(const char* c, const char* lb, int r)
	: DMA(lb, r)
{
	strcpy(color, c);
}

lacksDMA::lacksDMA(const char* c, const baseDMA& dma)
	: DMA(dma)
{
	strcpy(color, c);
}

// 虚函数
void lacksDMA::View() const
{
	cout << "Now, it is the lacksDMA.\n";
	cout << *this;
}

// 友元函数
std::ostream& operator<<(std::ostream& os, const lacksDMA& ld)
{
	os << (const DMA&)ld;
	cout << "Color: " << ld.color << endl;

	return os;
}

// hasDMA 类方法定义

// 构造函数
hasDMA::hasDMA(const char* sl, const char* lb, int r)
	: DMA(lb, r)
	, style(new char[strlen(sl) + 1])
{
	strcpy(style, sl);
}

hasDMA::hasDMA(const char* sl, const DMA& dma)
	: DMA(dma)
	, style(new char[strlen(sl) + 1])
{
	strcpy(style, sl);
}

hasDMA::hasDMA(const hasDMA& hd)
	: DMA(hd)
	, style(new char[strlen(hd.style) + 1])
{
	strcpy(style, hd.style);
}

// 析构函数
hasDMA::~hasDMA()
{
	delete[] style;
}

// 虚函数
void hasDMA::View() const
{
	cout << "Now, it is the hasDMA.\n";
	cout << *this;
}

// 运算符重载
hasDMA& hasDMA::operator=(const hasDMA& hd)
{
	// 检查是否自身赋值
	if (&hd != this)
	{
		// 调用基类赋值运算符重载函数,赋值基类数据
		DMA::operator=(hd);
		// 赋值派生类新增数据
		// 释放之前的空间
		delete[] style;
		// 申请新的空间
		style = new char[strlen(hd.style) + 1];
		// 拷贝内容
		strcpy(style, hd.style);
	}
	return *this;
}

// 友元函数
std::ostream& operator<<(std::ostream& os, const hasDMA& hd)
{
	os << (const DMA&)hd;
	os << "Style: " << hd.style << endl;

	return os;
}

测试结果:
在这里插入图片描述

4. Benevolent Order of Programmers (BOP) 用来维护瓶装葡萄酒箱。为描述它,BOP 的 PortMaster 设置了一个Port 类,其声明如下。

// Port 基类声明
class Port
{
private:
	char* brand;
	char style[20];
	int bottles;
public:
	// 构造函数
	Port(const char* br = "none", const char* st = "none", int b = 0);
	Port(const Port& p);
	// 析构函数
	virtual ~Port();
	// 运算符重载
	Port& operator=(const Port& p);
	Port& operator+=(int n);
	Port& operator-=(int n);
	// 获取成员变量函数
	int BottleCount() const;
	// 虚函数
	virtual void show() const;
	// 友元函数
	friend std::ostream& operator<<(std::ostream& os, const Port& p);
};

show()方法按照下面的格式显示信息。
Brand: Gallo
Kind: tawny
Bottles: 20
operator<<()函数按下面的格式显示信息(末尾没有换行符)。
Gallo, tawny, 20
PortMaster 完成了 Port 类方法的定义后,派生了 VintagePort 类,然后被解雇——因为不小心将一瓶45度的科佰恩酒泼到了正在准备烤肉调料的人身上。VintagePort 类如下所示。

// VintagePort 派生类声明
class VintagePort : public Port
{
private:
	char* nickname;
	int year;
public:
	// 构造函数
	VintagePort();
	VintagePort(const char* br, int b, int nm, int y);
	VintagePort(const VintagePort& vp);
	// 析构函数
	~VintagePort() { delete[] nickname; }
	// 运算符重载
	VintagePort& operator=(const VintagePort& vp);
	// 显示信息函数
	void show() const;
	// 友元函数
	friend std::ostream& operator<<(std::ostream& os, const VintagePort& vp);
};

你负责完成 VintagePort。
a. 第一个任务是重新创建 Port 方法的定义,因为 PortMaster 在离职时销毁了方法的定义。
b. 第二个任务是解释为什么有的方法重新定义了,而有些没有重新定义。
c. 第三个任务是解释为何没有将operator=()和operator<<()声明为虚方法。
d. 第四个任务是提供 VintagePort 中各个方法的定义。

答:

a. Port 基类方法定义

// Port 基类方法定义

// 构造函数
Port::Port(const char* br, const char* st, int b)
	: brand(new char[strlen(br)+1])
	, bottles(b)
{
	strcpy(brand, br);
	strcpy(style, st);
}

Port::Port(const Port& p)
	: brand(new char[strlen(p.brand) + 1])
	, bottles(p.bottles)
{
	strcpy(brand, p.brand);
	strcpy(style, p.style);
}

// 析构函数
Port::~Port()
{
	delete[] brand;
}

// 运算符重载
Port& Port::operator=(const Port& p)
{
	// 检查是否自身赋值
	if (&p != this)
	{
		// 释放之前的空间
		delete[] brand;
		// 申请新的空间
		brand = new char[strlen(p.brand) + 1];
		// 拷贝内容
		strcpy(brand, p.brand);
		strcpy(style, p.style);
		bottles = p.bottles;
	}

	return *this;
}

Port& Port::operator+=(int n)
{
	bottles += n;
	return *this;
}

Port& Port::operator-=(int n)
{
	bottles -= n;
	return *this;
}

// 获取成员变量函数
int Port::BottleCount() const
{
	return bottles;
}

// 虚函数
void Port::show() const
{
	cout << "Brand: " << brand << endl;
	cout << "Kind: " << style << endl;
	cout << "Bottles: " << bottles << endl;
}

// 友元函数
std::ostream& operator<<(std::ostream& os, const Port& p)
{
	os << p.brand << ", " << p.style << ", " << p.bottles;

	return os;
}

b. 解释:首先构造函数,析构函数,赋值运算符重载函数,友元函数和友元类这些是不会被派生类继承的,其次派生类 VintagePort 中使用了动态内存,所以上述函数都需要重新定义。再者需要使用派生类新增的成员变量也需要重新定义该函数,如果与基类函数同名且参数列表相同,需要把基类同名函数声明为虚函数(virtual)。而只涉及基类中成员变量的处理函数一般不需要重新定义,直接调用即可。

c. 解释:在C++中,将赋值运算符重载函数声明为虚函数没有意义,因为赋值运算符不会像构造函数或析构函数那样影响多态性,也不会像其他函数那样与对象的生命周期直接相关。赋值运算符是一个二元运算符,它的左操作数是当前对象的一个引用,右操作数是另一个对象。赋值运算符的目的是将右操作数的值复制到左操作数所指向的对象中。由于C++的运行时多态性主要是通过虚函数实现的,而赋值运算符不涉及虚函数的动态绑定,因此不需要将它声明为虚函数。如果你试图通过基类的引用或指针来对派生类对象进行赋值,C++将使用基类的赋值运算符来处理这个操作,这可能不是你想要的结果。如果你需要在派生类中自定义赋值操作,你应该使用成员函数重载而不是虚函数。友元函数不是类的成员函数,不能成为虚函数。

d. VintagePort 派生类方法定义

// VintagePort 派生类方法定义

// 构造函数
VintagePort::VintagePort()
	: Port()
	, nickname(new char[1] { 0 })
	, year(0)
{

}

VintagePort::VintagePort(const char* br, const char* st, int b, const char* nm, int y)
	: Port(br, st, b)
	, nickname(new char[strlen(nm) + 1])
	, year(y)
{
	strcpy(nickname, nm);
}

VintagePort::VintagePort(const VintagePort& vp)
	: Port(vp)
	, nickname(new char[strlen(vp.nickname) + 1])
	, year(vp.year)
{
	strcpy(nickname, vp.nickname);
}

// 运算符重载
VintagePort& VintagePort::operator=(const VintagePort& vp)
{
	// 检查是否自身赋值
	if (&vp != this)
	{
		// 先调用基类的赋值运算符
		Port::operator=(vp);
		// 释放之前空间
		delete[] nickname;
		// 申请新的空间
		nickname = new char[strlen(vp.nickname) + 1];
		// 拷贝内容
		strcpy(nickname, vp.nickname);
		year = vp.year;
	}
	return *this;
}

// 显示信息函数
void VintagePort::show() const
{
	Port::show();
	cout << "NickName: " << nickname << endl;
	cout << "Year: " << year << endl;
}

// 友元函数
std::ostream& operator<<(std::ostream& os, const VintagePort& vp)
{
	os << (const Port&)vp;
	os << ", " << vp.nickname << ", " << vp.year;

	return os;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1805622.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

c++|unordered系列关联式容器(unordered_set、unordered_map介绍使用+哈希结构)

目录 一、unordered_set的介绍与使用 1.1unordered_set介绍 1.2unordered_set使用 2.2.1构造 2.2.2容量 2.2.3修改 二、unordered_map的介绍与使用 2.1unordered_map介绍 2.2unordered_map使用 2.2.1构造 2.2.2容量 2.2.3修改 三、底层结构(哈希) 3.1哈希概念 3.2哈…

函数的概念及图像

注&#xff1a; 判断两函数是否相同&#xff0c;只看定义域和对应法则。 1. 函数的定义 一般的&#xff0c;在一个变化过程中有两个变量 x&#xff0c;y。如果对于x在某个变化范围内的每一个确定值&#xff0c;按照某个对应法则&#xff0c;都有唯一确定的值y和他对应。那么y就…

ChatGPT为啥不用Websocket而是EventSource?

点击下方“JavaEdge”&#xff0c;选择“设为星标” 第一时间关注技术干货&#xff01; 免责声明~ 任何文章不要过度深思&#xff01; 万事万物都经不起审视&#xff0c;因为世上没有同样的成长环境&#xff0c;也没有同样的认知水平&#xff0c;更「没有适用于所有人的解决方案…

统计信号处理基础 习题解答10-10

题目 在本题中&#xff0c;我们讨论再生PDF。回顾前面 其中分母与无关。如果选择一个&#xff0c;使得它与相乘时&#xff0c;我们得到与相同形式的PDF&#xff0c;那么后验PDF 将有和相同的形式。例10.1的高斯PDF正是这样的一种情况。现在假设在条件下的的PDF是指数形式&…

Codeforces Round 949 (Div. 2) A~D

A. Turtle and Piggy Are Playing a Game &#xff08;思维&#xff09; 题意&#xff1a; 给出一个整数 x x x &#xff0c;使得 l ≤ x ≤ r l \le x \le r l≤x≤r &#xff0c;其中 l , r l, r l,r 为给定值。同时保证 2 l ≤ r 2l \le r 2l≤r 。 执行以下操作&…

python如何输入回车

Python默认遇到回车的时候&#xff0c;输入结束。所以我们需要更改这个提示符&#xff0c;在遇到空行的时候&#xff0c;输入才结束。 raw_input就是从标注输入读取输入&#xff0c;输入的是什么就是什么。 文档解释&#xff1a; The function then reads a line from input,…

quick4 - hackmyvm

简介 靶机名称&#xff1a;quick4 难度&#xff1a;简单 靶场地址&#xff1a;https://hackmyvm.eu/machines/machine.php?vmQuick4 本地环境 虚拟机&#xff1a;vitual box 靶场IP&#xff08;quick4&#xff09;&#xff1a;192.168.56.104 跳板机IP(windows 11)&…

leetcode:不同的二叉树

class Solution { public:int numTrees(int n) {vector<int> dp(n1);dp[0] 1;dp[1] 1;for(int i 2;i < n;i){for(int j 1;j < i;j) // 当根节点为j时{dp[i] dp[j-1] * dp[i-j];}}return dp[n];} }; /* dp[i] i个不同的数组成的二叉搜索数的个数假设 i 5当根…

零基础非科班也能掌握的C语言知识19 动态内存管理

动态内存管理 1.为什么要有动态内存分配2.malloc和free2.1 malloc2.2 free 3.calloc和realloc3.1 calloc3.2realloc 4.常见的动态内存的错误4.1对NULL指针的解引用操作4.2对动态开辟空间的越界访问4.3对非动态内存开辟的空间free4.4使用free释放⼀块动态开辟内存的⼀部分4.5对同…

kNN算法-概述

所谓kNN算法就是K-nearest neigbor algorithm。这是似乎是最简单的监督机器学习算法。在训练阶段&#xff0c;kNN算法存储了标签训练样本数据。简单地说&#xff0c;就是调用训练方法时传递给它的标签训练样本会被它存储起来。 kNN算法也叫lazy learning algorithm懒惰学习算法…

分享不用会员免费听歌的软件,可听付费,支持随听随下!

今天来点特别的&#xff0c;给你们带来几款全网免费听歌的神器&#xff0c;让你们的音乐之旅不再有障碍&#xff01; 现在&#xff0c;找好听的歌越来越像寻宝一样&#xff0c;动不动就得掏腰包。不过别担心&#xff0c;阿星今天就来分享几款好用的免费听歌app&#xff0c;电脑…

Linux——PXE整体流程

1.自己安装一个CentOS 8的服务器 1&#xff09;手动安装 虚拟硬件配置&#xff1a;2核CPU&#xff0c;4G内存&#xff0c;100G硬盘 2个网卡&#xff08;一个通外网&#xff0c;一个内部使用&#xff09; 软件安装&#xff1a;Server GUI 磁盘分区&#xff1a;使用逻辑卷&#…

实践分享:如何用小程序里的小组件做应用开发?

随着移动互联网的快速发展&#xff0c;小程序等轻量级应用平台日益成为用户获取信息和服务的重要渠道。而小组件也在其中扮演了至关重要的角色&#xff0c;不仅能够提升用户的交互体验&#xff0c;还能帮助开发者高效地构建功能丰富、界面美观的小程序。 本文中&#xff0c;我们…

【Uniapp】uniapp微信小程序定义图片地址全局变量

错误写法&#xff1a; main.js Vue.prototype.$imgUrl 图片地址这么写之后 就发现压根不起作用&#xff1b;获取到的是undefined 正确写法&#xff1a; 返回函数&#xff0c;后面可以拼上OSS图片完整路径 Vue.prototype.$imgUrl (url) > {return ("https://地址…

jmeter性能优化之mysql配置

一、连接数据库和grafana 准备:连接好数据库和启动grafana并导入mysql模板 大批量注册、登录、下单等,还有过节像618,双11和数据库交互非常庞大,都会存在数据库的某一张表里面,当用户在登录或者查询某一个界面时,量少的话体现不出来,量很大的时候一定会有卡的现象, 性…

SpringBoot整合RabbitMQ实现消息延迟队列

环境依赖 SpringBoot 3.1.0 JDK 17 前期准备 安装MQ: liunxdockerrabbitmq安装延迟队列插件 实例 实现延迟队列的一种方式是在 RabbitMQ 中使用消息延迟插件&#xff0c;这个插件可以让你在消息发送时设置一个延迟时间&#xff0c;超过这个时间后消息才会被消费者接收到…

期刊影响因子、分区如何查询

查询期刊影响因子、分区等信息就不得不说到的数据库JCI(Journal Citation Indicator)。 JCR 是一个综合性、多学科的期刊分析与评价报告&#xff0c;它客观地统计Web of Science收录期刊所刊载论文的数量、论文参考文献的数量、论文的被引用次数等原始数据&#xff0c;再应用文…

数据库(29)——子查询

概念 SQL语句中嵌套SELECT语句&#xff0c;称为嵌套查询&#xff0c;又称子查询。 SELECT * FROM t1 WHERE column1 (SELECT column1 FROM t2); 子查询外部语句可以是INSERT/UPDATE/DELETE/SELECT的任何一个。 标量子查询 子查询返回的结果是单个值&#xff08;数字&#xff…

基于xml的Spring应用(理解spring注入)

目录 问题&#xff1a; 传统Javaweb开发的困惑? 问题&#xff1a; IOC、DI和AOP的思想提出 问题&#xff1a; Spring框架的诞生 1. BeanFactory快速入门 2. ApplicationContext快速入门 3. BeanFactory和ApplicationContext的关系 基于xml的Spring应用 1. SpringBean的…

c# 开发的wpf程序闪退,无法用try catch捕获异常

之前开发的一个程序是c#wpf开发&#xff0c;基于.net framework 4.6.1的&#xff0c;一切都是正常的&#xff0c;但是在我重新装了win11后在程序logo出现后直接闪退&#xff0c;报错 返回值为 -1073740791 (0xc0000409)&#xff0c;而且定位到代码时发现是&#xff0c; publi…