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

目录

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

一、复习题**

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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PGL图学习之图游走类metapath2vec模型[系列五]

本项目链接&#xff1a;https://aistudio.baidu.com/aistudio/projectdetail/5009827?contributionType1 有疑问查看原项目 相关项目参考&#xff1a; 关于图计算&图学习的基础知识概览&#xff1a;前置知识点学习&#xff08;PGL&#xff09;系列一 https://aistudio.…

企业网页制作

随着互联网的普及&#xff0c;企业网站已成为企业展示自己形象、吸引潜在客户、开拓新市场的重要方式。而企业网页制作则是构建企业网站的基础工作&#xff0c;它的质量和效率对于企业网站的成败至关重要。 首先&#xff0c;企业网页制作需要根据企业的特点和需求进行规划。在网…

MySQL的PrepareStatement真的是预编译语句么?

PrepareStatement真的是预编译语句么&#xff1f; ChatGPT对PrepareStatement的定义是&#xff1a; PrepareStatement 是 Java 数据库连接&#xff08;JDBC&#xff09;API 中用于执行预编译 SQL 语句的接口。通过使用 PreparedStatement&#xff0c;可以预编译 SQL 语句&…

Python代码大使用Paramiko轻松判断文件类型,提取上级目录

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 一、Paramiko简介 Paramiko是一个用于SSHv2协议的Python实现&#xff0c;提供了客户端和服务器功能。它可以用于远程连接和管理服务器&#xff0c;执行命令、上传下载文件等。本文将介绍如何使用Paramiko判断文件类…

前端 Vue 操作文件方法(导出下载、图片压缩、文件上传和转换)

一、前言 本文对前端 Vue 项目开发过程中&#xff0c;经常遇到要对文件做一些相关操作&#xff0c;比如&#xff1a;文件导出下载、文件上传、图片压缩、文件转换等一些处理方法进行归纳整理&#xff0c;方便后续查阅和复用。 二、具体内容 1、后端的文件导出接口&#xff0c;…

C++程序设计:对数据文件的操作与文件流

姚老师小课堂开课啦&#xff01; 一、文件的分类&#xff1a; 1.ASCII码文件&#xff1a; ASCII文件使用方便&#xff0c;比较直观&#xff0c;便于阅读&#xff0c;便于对字符进行输入输出&#xff0c;但一般占用存储空间较多&#xff0c;而且需要花费转换时间&#xff08;二…

逆市创新高!水电“双雄“是怎样炼成的? 博通,赢麻了!

高分红夏季用电高峰AI的尽头是电力 6月7日&#xff0c;长江电力&#xff08;600900&#xff09;、华能水电&#xff08;600025&#xff09;股价双双上涨。截至收盘&#xff0c;长江电力股价上涨1%&#xff0c;收于28.31元/股&#xff1b;华能水电股价上涨1.52%&#xff0c;收于…

sqli-labs 靶场 less-8、9、10 第八关到第十关详解:布尔注入,时间注入

SQLi-Labs是一个用于学习和练习SQL注入漏洞的开源应用程序。通过它&#xff0c;我们可以学习如何识别和利用不同类型的SQL注入漏洞&#xff0c;并了解如何修复和防范这些漏洞。Less 8 SQLI DUMB SERIES-8判断注入点 当输入id为1时正常显示&#xff1a; 加上单引号就报错了 …

Kafka 架构

1 整体架构 1.1 Zookeeper Zookeeper 是一个分布式协调服务&#xff0c;用于管理 Kafka 的元数据。它负责维护 Kafka 集群的配置信息、Broker 列表和分区的 Leader 信息。 Zookeeper 确保了 Kafka 集群的高可用性和可靠性。 但 Zookeeper 已经成为 Kafka 性能瓶颈&#xff0c;…

什么是 AOF 重写?AOF 重写机制的流程是什么?

引言&#xff1a;在Redis中&#xff0c;持久化是确保数据持久性和可恢复性的重要机制之一。除了常见的RDB&#xff08;Redis Database&#xff09;持久化方式外&#xff0c;AOF&#xff08;Append Only File&#xff09;也是一种常用的持久化方式。AOF持久化通过记录Redis服务器…

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

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

Mamba v2诞生:3 SMA与Mamba-2

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提…

312. 戳气球 Hard

有 n 个气球&#xff0c;编号为0 到 n - 1&#xff0c;每个气球上都标有一个数字&#xff0c;这些数字存在数组 nums 中。 现在要求你戳破所有的气球。戳破第 i 个气球&#xff0c;你可以获得 nums[i - 1] * nums[i] * nums[i 1] 枚硬币。 这里的 i - 1 和 i 1 代表和 i 相邻…

JDK17 | Windows环境配置

众所周知&#xff0c; Jdk8做了很大的提升&#xff0c;网上的访谈&#xff0c;问到当下程序员要不要升级JDK版本的时候&#xff0c;得到异口同声的答案&#xff0c;不需要。这么多年过去了&#xff0c;数据是不会骗人的&#xff0c;现在Star最多的是JDK17&#xff0c;今天&…

STM32中ADC在cubemx基础配置界面介绍

ADCx的引脚,对应的不同I/O口&#xff0c;可以复用。 Temperature :温度传感器通道。 Vrefint :内部参照电压。 Conversion Trigger: 转换触发器。 IN0 至 IN15,是1ADC1的16个外部通道。本示例中输出连接的是ADC2的IN5通道&#xff0c;所以只勾选IN5.Temperature Sensor Cha…

搭建自己的组件库<2>dialog 组件

目录 设置title 插槽显示 控制宽高 关闭对话框 transition实现动画 引入深度选择器 同样创建组件dialogue.vue后全局注册 dialogue模版&#xff1a; <template><!-- 对话框的遮罩 --><div class"miao-dialog_wrapper"><!-- 真的对话框 …

python如何输入回车

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

debian系统apt 国内安装源

debian系统apt 国内安装源&#xff1a; 国内阿里镜像源&#xff1a; deb http://mirrors.aliyun.com/debian stable main non-free contrib deb-src http://mirrors.aliyun.com/debian stable main non-free contrib 打开源文件位置&#xff1a;/etc/apt/sources.list,原来的内…

《经典图论算法》广度优先搜索

摘要&#xff1a; 1&#xff0c;广度优先搜索介绍 2&#xff0c;广度优先搜索的解题步骤 3&#xff0c;广度优先搜索的代码实现 1&#xff0c;广度优先搜索介绍 广度优先搜索(Breadth-first search&#xff0c;BFS)&#xff0c;又称宽度优先搜索&#xff0c;简单的说&#xff0…

知识工作者如何在工作中使用大模型?

自 2022 年 11 月 OpenAI 发布 ChatGPT 以来&#xff0c;人们对生成式人工智能&#xff08;GenAI&#xff0c;以下简称“生成式AI”&#xff09;的兴趣激增&#xff0c;同时也对其安全性表示担忧。 &#xff08;译者注&#xff1a;生成式人工智能&#xff0c;即用 AI 生成文本…