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

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

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

相关文章

陆面生态水文模拟与多源遥感数据同化技术

原文链接&#xff1a;陆面生态水文模拟与多源遥感数据同化技术 了解陆表过程的主要研究内容以及陆面模型在生态水文研究中的地位和作用;熟悉模 型的发展历程&#xff0c;常见模型及各自特点;理解Noah-MP模型的原理&#xff0c;掌握Noah-MP 模型在单 站和区域的模拟、模拟结果的…

如何将AndroidStudio和IDEA的包名改为分层级目录

新版UIAndroidStudio 1、点击项目目录右上角如图所示的三个点点。 2、然后依次取消Hide empty middle package &#xff0c;Flatten package的勾选 3、注意&#xff1a;一定要先取消hide的勾选&#xff0c;不然目录不会完全分级&#xff08;做错了可以反过来重新设置&#x…

物资材料管理系统建设方案(Word)—实际项目方案

二、 项目概述 2.1 项目背景 2.2 现状分析 2.2.1 业务现状 2.2.2 系统现状 三、 总体需求 3.1 系统范围 3.2 系统功能 3.3 用户分析 3.4 假设与依赖关系 四、 功能需求 4.4.11.7 非功能性需求 五、 非功能性需求 5.1 用户界面需求 5.2 软硬件环境需求 5.3 产品质量需求 5.4 接口…

物联网8大协议介绍及对比

一.物联网主流协议介绍 1.MQTT 协议 MQTT&#xff08;Message Queuing Telemetry Transport&#xff09;即消息队列遥测传输。 MQTT 协议最初是在 1999 年由 IBM 公司开发的&#xff0c;用于将石油管道上的传感器与卫星相连接。2014 年正式成为 OASIS 开放标准。 MQTT 使用…

【面试八股总结】死锁:产生条件、预防死锁、处理死锁、避免死锁

一、什么是死锁&#xff1f; 死锁是指两个&#xff08;或多个&#xff09;线程互相等待对方数据的过程&#xff0c;死锁的产生导致程序卡死&#xff0c;不解锁程序将永远⽆法进⾏下 去 二、死锁产生条件 死锁只有同时满足以下四个条件才会发生&#xff1a;互斥条件&#xff1b…

ABC 357 G Stair-like Grid

link 其实是我之前写的一篇博客的推广 大意&#xff1a; 一个阶梯型&#xff0c;第 i i i行有 ⌈ i / 2 ⌉ ∗ 2 \left \lceil i/2 \right \rceil*2 ⌈i/2⌉∗2个方块&#xff0c;总共有n行。在其中给定 m m m个点无法经过&#xff0c;求从左上角到右下角的方案数。其中每次移…

wps:基本使用【笔记】

wps&#xff1a;基本使用【笔记】 前言版权推荐wps&#xff1a;基本使用如何去除复制文本的样式显示空格、换行、分节符快捷键设置字体添加章节添加奇数页分节符设置页边距设置页眉页脚设置页码 最后 前言 2024-6-5 23:10:12 以下内容源自《【笔记】》 仅供学习交流使用 版权…

(二)JSX基础

什么是JSX 概念&#xff1a;JSX是JavaScript和XML&#xff08;HTML&#xff09;的缩写&#xff0c;表示在JS代码中编写HTML模版结构&#xff0c;它是React中编写UI模板的方式。 优势&#xff1a;1.HTML的声明式模版方法&#xff1b;2.JS的可编程能力 JSX的本质 JSX并不是标准…

webapi跨越问题

由于浏览器存在同源策略&#xff0c;为了防止 钓鱼问题&#xff0c;浏览器直接请求才不会有跨越的问题 浏览器要求JavaScript或Cookie只能访问同域下的内容 浏览器也是一个应用程序&#xff0c;有很多限制&#xff0c;不能访问和使用电脑信息&#xff08;获取cpu、硬盘等&#…

LeetCode 26删除有序数组中的重复项

去重题&#xff0c;双指针&#xff0c;&#xff0c;因为题干说原地删除&#xff0c;且nums其余元素不重要。一个cur记录当前不重复的数应该插在第几位了&#xff0c;for循环里的i相当于是第二个指针&#xff08;右指针&#xff09;&#xff0c;遍历数组来找不重复的元素 class …

C#发送邮件的SMTP配置方法?如何群发邮件?

C#发送邮件安全性如何保障&#xff1f;C#怎么配置实现发送邮件&#xff1f; 在C#开发中&#xff0c;发送电子邮件是一个常见的需求。无论是用于注册确认、密码重置还是其他通知功能&#xff0c;SMTP&#xff08;简单邮件传输协议&#xff09;都是实现这一功能的关键。下面&…

Shell脚本学习_内置命令

目录 1.内置命令介绍&#xff1a; 2.Shell内置命令&#xff1a;alias设置别名 3.Shell内置命令&#xff1a;echo输出字符串 4.Shell内置命令&#xff1a;read读取控制台输入 5.Shell内置命令&#xff1a;exit退出 6.Shell内置命令&#xff1a;declare设置变量 1.内置命令…

kali2022安装教程(附安装包)

第一步&#xff1a;下载镜像文件 百度网盘下载https://pan.baidu.com/s/1efRQGFTbq6Kgw9axLOmWzg?pwdemxf 第二步&#xff1a;打开Vmware 第三步&#xff1a;进行各项配置 创建新的虚拟机&#xff0c;选择高级&#xff0c;然后下一步 直接默认下一步 选择稍后安装然后下…

设计软件有哪些?效果工具篇(3),渲染100邀请码1a12

这次我们再介绍一批渲染效果和后期处理的工具。 1、ColorCorrect ColorCorrect是一种图像处理技术&#xff0c;用于调整图像的色彩和对比度&#xff0c;使其更加自然和平衡。通过ColorCorrect&#xff0c;用户可以调整图像的色调、亮度、饱和度等参数&#xff0c;以达到理想的效…

kube-promethesu调整coredns监控

K8s集群版本是二进制部署的1.20.4&#xff0c;kube-prometheus对应选择的版本是kube-prometheus-0.8.0 Coredns是在安装集群的时候部署的&#xff0c;采用的也是该版本的官方文档&#xff0c;kube-prometheus中也有coredns的监控配置信息&#xff0c;但是在prometheus的监控页…

kivy 百词斩项目 报错

AttributeError: FigureCanvasKivyAgg object has no attribute resize_event AttributeError: FigureCanvasKivyAgg object has no attribute resize_event 是一种常见的Python错误&#xff0c;当你试图访问一个对象&#xff08;在这个例子中是 FigureCanvasKivyAgg 对象&am…

二次规划问题(Quadratic Programming, QP)原理例子

二次规划(Quadratic Programming, QP) 二次规划(Quadratic Programming, QP)是优化问题中的一个重要类别,它涉及目标函数为二次函数并且线性约束条件的优化问题。二次规划在控制系统、金融优化、机器学习等领域有广泛应用。下面详细介绍二次规划问题的原理和求解过程 二…

物联网实战--平台篇之(十四)物模型(用户端)

目录 一、底层数据解析 二、物模型后端 三、物模型前端 四、数据下行 本项目的交流QQ群:701889554 物联网实战--入门篇https://blog.csdn.net/ypp240124016/category_12609773.html 物联网实战--驱动篇https://blog.csdn.net/ypp240124016/category_12631333.html 物联网…

MATLAB数学建模——数据拟合

文章目录 一、简介二、多项式拟合&#xff08;一&#xff09;指令介绍&#xff08;二&#xff09;代码 三、指定函数拟合&#xff08;一&#xff09;指令介绍&#xff08;二&#xff09;代码 一、简介 曲线拟合也叫曲线逼近&#xff0c;主要要求拟合的曲线能合理反映数据的基本…

如何有效释放Docker占用的存储空间

随着Docker的广泛应用&#xff0c;我们经常会遇到Docker占用过多存储空间的问题。这可能是由于频繁的镜像拉取、容器创建和删除等操作导致的。本文将介绍几种方法来有效释放Docker占用的存储空间&#xff0c;特别是docker system prune命令的使用。 Docker的存储机制 Docker使…