习题选自:C++ Primer Plus(第六版)
内容仅供参考,如有错误,欢迎指正 !
复习题
1. 派生类从基类那里继承了什么?
派生类继承了基类的公有成员、基类的保护成员和基类的私有成员,但派生类不能直接访问从基类继承过来的私有成员。
2. 派生类不能从基类那里继承什么?
派生类不能继承构造函、析构函数、赋值运算符合友元。
3. 假设baseDMA::operator=()
函数的返回类型为void
,而不是baseDMA &
,这将有什么后果?如果返回类型为baseDMA
,而不是baseDMA &
,又将有什么后果?
如果返回值为void
的,则baseDMA
对象仍可以使用单个赋值,但是不能使用连续赋值。即:
baseDMA magazine("Pandering to Glitz", 1);
baseDMA gift1, gift2, gift3;
gift1 = magazine; //ok
gitft2 = gift3 = gift1; //no 不可用
如果方法返回类型为baseDMA
,则该方法返回的是一个对象,不是引用,导致返回语句的时候需要复制对象,导致该方法执行速度会有所减慢。
4. 创建和删除派生类对象时,构造函数和析构函数调用的顺序是怎样的?
按照派生的顺序调用构造函数,最早的构造函数最先调用。调用析构函数的顺序正好相反。
5. 如果派生类没有添加任何数据成员,它是否需要构造函数?
需要,每个类都必须有自己的构造函数,如果派生类没有添加新成员,则构造函数可以为空,但必须存在。
6. 如果基类和派生类定义了同名的方法,当派生类对象调用该方法时,被调用的将是哪个方法?
调用派生类方法,它取代基类定义。仅当派生类没有重新定义方法或使用作用域解析运算符时,才会调用基类方法。
7. 在什么情况下,派生类应定义赋值运算符?
如果派生类构造函数使用new或者new[]运算符来初始化类的指针成员,则应定义一个赋值运算符。更普通的说,如果对于派生类成员来说,默认赋值不正确,则应定义赋值运算符。
8. 可以将派生类对象的地址赋给基类指针吗?可以将基类对象的地址赋给派生类指针吗?
可以将派生类对象的地址赋给基类指针。但只有通过显示类型转换,才可以将基类对象的地址赋给派生类指针(向下转换),而使用这样的指针不一定安全。
9. 可以将派生类对象赋给基类对象吗?可以将基类对象赋给派生类对象吗?
可以将派生类对象的地址赋值给基类对象,对于派生类中新增的数据成员都不会传递给基类对象,程序也将使用基类的赋值运算符。仅当派生类定义了转换运算符(即包含将基类引用作为唯一参数的构造函数)或使用基类为参数的赋值运算符时,相反的赋值才是可能的。
10. 假设定义了一个函数,它将基类对象的引用作为参数。为什么该函数也可以将派生类对象作为参数?
应为c++允许基类引用指向从该基类派生而来的任何类型。
11. 假设定义了一个函数,它将基类对象作为参数(即函数按值传递基类对象)。为什么该函数也可以将派生类对象作为参数?
按值传递对象将调用复制构造函数,由于形参是基类对象,因此将调用基类的复制构造函数,复制构造函数已基类引用为参数,该引用可以将指向作为参数传递的派生对象,最终的结构是,将生成一个新的基类对象,其成员对应于派生类对象的基类部分。
12. 为什么通常按引用传递对象比按值传递对象的效率更高?
按引用传递对象,这样可以确保函数从虚函数受益。另外,按引用传递对象可以节省内存和时间,尤其对于大型对象。按值传递对象的主要有点在于可以保护原始数据,但可以通过将引用作为const类型传递,来达到同样的目的。
13. 假设Corporation
是基类,PublicCorporation
是派生类。再假设这两个类都定义了head()
函数,ph
是指向Corporation
类型的指针,且被赋给了一个PublicCorporation
对象的地址。如果基类将head( )定义为:
a. 常规非虚方法;
b. 虚方法;
则ph->head()
将被如何解释?
a. ph-head()
调用Corporation::head()
;
b. ph-head()
调用PublicCorporation::head()
;
14. 下述代码有什么问题?
class Kitchen { private: double kit_sq_ft; public: Kitchen() { kit_sq_ft = 0.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
模型,因此公有继承不适用。其次,House中area()
定义成带参数的,将隐藏area()
的Kitchen
版本。
编程练习
1. 以下面的类声明为基础:
// base class class Cd { // represents a CD disk private: char performers[50]; char label[20]; int selections; // number of selections double playtime; // playing time in minutes public: Cd(char * s1, char * s2, int n, double x); Cd(const Cd & d); Cd(); ~Cd(); void Report() const; // reports all CD data Cd & operator=(const Cd & d); };
派生出一个Classic类,并添加一组char成员,用于存储指出CD中主要作品的字符串。修改上述声明,使基类的所有函数都是虚的。如果上述定义声明的某个方法并不需要,则请删除它。使用下面的程序测试您的产品:
#include <iostream> using namespace std; #include "classic.h" // which will contain #include cd.h void Bravo(const Cd & disk); int main() { Cd c1("Beatles", "Capitol", 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(); // use Cd method c2.Report(); // use Classic method cout << "Using type cd * pointer to objects:\n"; pcd->Report(); // use Cd method for cd object pcd = &c2; pcd->Report(); // use Classic method for classic object 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 & disk) { disk.Report(); }
classic.h:
// base class
class Cd { // represents a CD disk
private:
char performers[50];
char label[20];
int selections; // number of selections
double playtime; // playing time in minutes
public:
Cd(char* s1, char* s2, int n, double x);
Cd(const Cd& d);
Cd();
virtual ~Cd();
virtual void Report() const; // reports all CD data
virtual Cd& operator=(const Cd& d);
};
class Classic : public Cd {
private:
char* primary_work;
public:
Classic(char* sc, char* s1, char* s2, int n, double x);
Classic(const Classic& c);
Classic();
virtual ~Classic();
virtual void Report() const;
virtual Classic& operator=(const Classic& c);
};
classic.cpp:
#include "classic.h"
#include <cstring>
#include <iostream>
Cd::Cd(char* s1, char* s2, int n, double x) {
std::strncpy(performers, s1, 50);
std::strncpy(label, s2, 20);
selections = n;
playtime = x;
}
Cd::Cd(const Cd& d) {
std::strncpy(performers, d.performers, 50);
std::strncpy(label, d.label, 20);
selections = d.selections;
playtime = d.playtime;
}
Cd::Cd() {
performers[0] = '\0';
label[0] = '\0';
selections = 0;
playtime = 0;
}
Cd::~Cd() {}
void Cd::Report() const {
std::cout << "Performers: " << performers << std::endl;
std::cout << "Label: " << label << std::endl;
std::cout << "Selections: " << selections << std::endl;
std::cout << "PlayTime: " << playtime << std::endl;
}
Cd& Cd::operator=(const Cd& d) {
if (&d == this) return *this;
std::strncpy(performers, d.performers, 50);
std::strncpy(label, d.label, 20);
selections = d.selections;
playtime = d.playtime;
return *this;
}
Classic::Classic(char* sc, char* s1, char* s2, int n, double x)
: Cd(s1, s2, n, x) {
primary_work = new char[std::strlen(sc) + 1];
std::strcpy(primary_work, sc);
}
Classic::Classic(const Classic& c) : Cd(c) {
primary_work = new char[std::strlen(c.primary_work) + 1];
std::strcpy(primary_work, c.primary_work);
}
Classic::Classic() : Cd() { primary_work = nullptr; }
Classic::~Classic() { delete[] primary_work; }
void Classic::Report() const {
Cd::Report();
std::cout << "PrimaryWork: " << primary_work << std::endl;
}
Classic& Classic::operator=(const Classic& c) {
if (&c == this) return *this;
delete[] primary_work;
Cd::operator=(c);
primary_work = new char[std::strlen(c.primary_work) + 1];
std::strcpy(primary_work, c.primary_work);
return *this;
}
main.cpp:
#include <iostream>
using namespace std;
#include "classic.h" // which will contain #include cd.h
void Bravo(const Cd &disk);
int main() {
Cd c1("Beatles", "Capitol", 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(); // use Cd method
c2.Report(); // use Classic method
cout << "Using type cd * pointer to objects:\n";
pcd->Report(); // use Cd method for cd object
pcd = &c2;
pcd->Report(); // use Classic method for classic object
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 &disk) { disk.Report(); }
2. 完成练习1,但让两个类使用动态内存分配而不是长度固定的数组来记录字符串。
classic.h:
// base class
class Cd { // represents a CD disk
private:
char* performers;
char* label;
int selections; // number of selections
double playtime; // playing time in minutes
public:
Cd(char* s1, char* s2, int n, double x);
Cd(const Cd& d);
Cd();
virtual ~Cd();
virtual void Report() const; // reports all CD data
virtual Cd& operator=(const Cd& d);
};
class Classic : public Cd {
private:
char* primary_work;
public:
Classic(char* sc, char* s1, char* s2, int n, double x);
Classic(const Classic& c);
Classic();
virtual ~Classic();
virtual void Report() const;
virtual Classic& operator=(const Classic& c);
};
classic.cpp:
#include "classic.h"
#include <cstring>
#include <iostream>
Cd::Cd(char* s1, char* s2, int n, double x) {
performers = new char[std::strlen(s1) + 1];
label = new char[std::strlen(s2) + 1];
std::strcpy(performers, s1);
std::strcpy(label, s2);
selections = n;
playtime = x;
}
Cd::Cd(const Cd& d) {
performers = new char[std::strlen(d.performers) + 1];
label = new char[std::strlen(d.label) + 1];
std::strcpy(performers, d.performers);
std::strcpy(label, d.label);
selections = d.selections;
playtime = d.playtime;
}
Cd::Cd() {
performers = nullptr;
label = nullptr;
selections = 0;
playtime = 0;
}
Cd::~Cd() {
delete[] performers;
delete[] label;
}
void Cd::Report() const {
std::cout << "Performers: " << performers << std::endl;
std::cout << "Label: " << label << std::endl;
std::cout << "Selections: " << selections << std::endl;
std::cout << "PlayTime: " << playtime << std::endl;
}
Cd& Cd::operator=(const Cd& d) {
if (&d == this) return *this;
std::strncpy(performers, d.performers, 50);
std::strncpy(label, d.label, 20);
selections = d.selections;
playtime = d.playtime;
return *this;
}
Classic::Classic(char* sc, char* s1, char* s2, int n, double x)
: Cd(s1, s2, n, x) {
primary_work = new char[std::strlen(sc) + 1];
std::strcpy(primary_work, sc);
}
Classic::Classic(const Classic& c) : Cd(c) {
primary_work = new char[std::strlen(c.primary_work) + 1];
std::strcpy(primary_work, c.primary_work);
}
Classic::Classic() : Cd() { primary_work = nullptr; }
Classic::~Classic() { delete[] primary_work; }
void Classic::Report() const {
Cd::Report();
std::cout << "PrimaryWork: " << primary_work << std::endl;
}
Classic& Classic::operator=(const Classic& c) {
if (&c == this) return *this;
delete[] primary_work;
Cd::operator=(c);
primary_work = new char[std::strlen(c.primary_work) + 1];
std::strcpy(primary_work, c.primary_work);
return *this;
}
main.cpp:
#include <iostream>
using namespace std;
#include "classic.h" // which will contain #include cd.h
void Bravo(const Cd &disk);
int main() {
Cd c1("Beatles", "Capitol", 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(); // use Cd method
c2.Report(); // use Classic method
cout << "Using type cd * pointer to objects:\n";
pcd->Report(); // use Cd method for cd object
pcd = &c2;
pcd->Report(); // use Classic method for classic object
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 &disk) { disk.Report(); }
3. 修改baseDMA-lacksDMA-hasDMA
类层次,让三个类都从一个ABC派生而来,然后使用与程序清单13.10相似的程序对结果进行测试。也就是说,它应使用ABC指针数组,并让用户决定要创建的对象类型。在类定义中添加virtual View()
方法以处理数据显示。
abc.h:
#ifndef ABC_H_
#define ABC_H_
#include <iostream>
class ABC {
private:
char* label;
int rating;
public:
ABC(const char* l = "null", int r = 1);
ABC(const ABC& a);
virtual ~ABC() = 0;
virtual void View() const;
ABC& operator=(const ABC& a);
friend std::ostream& operator<<(std::ostream& os, const ABC& a);
};
class baseDMA : public ABC {
private:
public:
baseDMA(const char* l = "null", int r = 0);
friend std::ostream& operator<<(std::ostream& os, const baseDMA& rs);
};
class lacksDMA : public ABC {
private:
enum { COL_LEN = 40 };
char color[COL_LEN];
public:
lacksDMA(const char* c = "blank", const char* l = "null", int r = 0);
lacksDMA(const char* c, const ABC& a);
virtual void View() const;
friend std::ostream& operator<<(std::ostream& os, const lacksDMA& rs);
};
class hasDMA : public ABC {
private:
char* style;
public:
hasDMA(const char* s = "none", const char* l = "null", int r = 0);
hasDMA(const char* s, const ABC& c);
hasDMA(const hasDMA& hs);
~hasDMA();
virtual void View() const;
hasDMA& operator=(const hasDMA& rs);
friend std::ostream& operator<<(std::ostream& os, const hasDMA& rs);
};
#endif // ABC_H_
abc.cpp:
#include "abc.h"
#include <cstring>
ABC::ABC(const char* l, int r) {
label = new char(std::strlen(l) + 1);
std::strcpy(label, l);
rating = r;
}
ABC::ABC(const ABC& a) {
label = new char(std::strlen(a.label) + 1);
std::strcpy(label, a.label);
rating = a.rating;
}
ABC::~ABC() { delete[] label; }
void ABC::View() const { std::cout << *this << std::endl; }
ABC& ABC::operator=(const ABC& a) {
if (&a == this) return *this;
delete[] label;
label = new char[std::strlen(a.label) + 1];
std::strcpy(label, a.label);
rating = a.rating;
}
std::ostream& operator<<(std::ostream& os, const ABC& a) {
os << "label: " << a.label << ", rating: " << a.rating;
return os;
}
/***************baseDMA************/
baseDMA::baseDMA(const char* l, int r) : ABC(l, r) {}
std::ostream& operator<<(std::ostream& os, const baseDMA& rs) {
os << (const ABC&)rs;
return os;
}
/***************lacksDMA************/
lacksDMA::lacksDMA(const char* c, const char* l, int r) : ABC(l, r) {
std::strncpy(color, c, COL_LEN);
}
lacksDMA::lacksDMA(const char* c, const ABC& a) : ABC(a) {
std::strncpy(color, c, COL_LEN);
}
void lacksDMA::View() const { std::cout << *this << std::endl; }
std::ostream& operator<<(std::ostream& os, const lacksDMA& rs) {
os << (const ABC&)rs << ", color: " << rs.color;
return os;
}
/***************hasDMA************/
hasDMA::hasDMA(const char* s, const char* l, int r) : ABC(l, r) {
style = new char[std::strlen(s) + 1];
std::strcpy(style, s);
}
hasDMA::hasDMA(const char* s, const ABC& c) : ABC(c) {
style = new char[std::strlen(s) + 1];
std::strcpy(style, s);
}
hasDMA::hasDMA(const hasDMA& hs) : ABC(hs) {
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
}
hasDMA::~hasDMA() { delete[] style; }
void hasDMA::View() const { std::cout << *this << std::endl; }
hasDMA& hasDMA::operator=(const hasDMA& hs) {
if (this == &hs) return *this;
ABC::operator=(hs);
delete[] style;
style = new char[std::strlen(hs.style) + 1];
std::strcpy(style, hs.style);
return *this;
}
std::ostream& operator<<(std::ostream& os, const hasDMA& rs) {
os << (const ABC&)rs << ", style: " << rs.style;
return os;
}
main.cpp:
#include <iostream>
#include "abc.h"
int main() {
using std::cout;
using std::endl;
baseDMA shirt("Portrabelly", 8);
lacksDMA balloon("red", "Blumpo", 4);
hasDMA map("Mercator", "Buffalo Kyes", 5);
cout << shirt << endl;
cout << balloon << endl;
cout << map << endl;
lacksDMA balloon2(balloon);
hasDMA map2;
map2 = map;
cout << balloon2 << endl;
cout << map2 << endl;
ABC* pts[3];
pts[0] = &shirt;
pts[1] = &balloon;
pts[2] = ↦
for (int i = 0; i < 3; ++i) cout << *pts[i] << endl;
for (int i = 0; i < 3; ++i) pts[i]->View();
return 0;
}
4. Benevolent Order of Programmers
用来维护瓶装葡萄酒箱。为描述它,BOP Portmaster
设置了一个Port
类,其声明如下
#include <iostream> using namespace std; class Port { private: char * brand; char style[20]; // i.e., tawny, ruby, vintage int bottles; public: Port(const char * br = "none", const char * st = "none", int b = 0); Port(const Port & p); // copy constructor virtual ~Port() { delete [] brand; } Port & operator=(const Port & p); Port & operator+=(int b); // adds b to bottles Port & operator-=(int b); // subtracts b from bottles, if available int BottleCount() const { return bottles; } virtual void Show() const; friend ostream & operator<<(ostream & os, const Port & p); };
show()
方法按下面的格式显示信息:
Brand: Gallo Kind: tawny Bottles: 20
operator<<()
函数按下面的格式显示信息(末尾没有换行符):
Gallo, tawny, 20
PortMaster
完成了Por
类的方法定义后派生了VintagePort
类,然后被解职——因为不小心将一瓶45度Cockburn
泼到了正在准备烤肉调料的人身上,VintagePort
类如下所示:
class VintagePort : public Port // style necessarily = "vintage" { private: char * nickname; // i.e., "The Noble" or "Old Velvet", etc. int year; // vintage year public: VintagePort(); VintagePort(const char * br, int b, const char * nn, int y); VintagePort(const VintagePort & vp); ~VintagePort() { delete [] nickname; } VintagePort & operator=(const VintagePort & vp); void Show() const; friend ostream & operator<<(ostream & os, const VintagePort & vp); };
您被指定负责完成VintagePort
。
a. 第一个任务是重新创建Port
方法定义,因为前任被开除时销毁了方法定义。
b. 第二个任务是解释为什么有的方法重新定义了,而有些没有重新定义。
c. 第三个任务是解释为何没有将operator=()
和operator<<()
声明为虚的。
d. 第四个任务是提供VintagePort
中各个方法的定义。
port.h:
#ifndef PORT_H_
#define PORT_H_
#include <iostream>
using namespace std;
class Port {
private:
char* brand;
char style[20]; // i.e., tawny, ruby, vintage
int bottles;
public:
Port(const char* br = "none", const char* st = "none", int b = 0);
Port(const Port& p); // copy constructor
virtual ~Port() { delete[] brand; }
Port& operator=(const Port& p);
//派生类的计算逻辑与基类一致,且在该方法中派生类未操作其新增成员,因此该函数在派生类中不需要重新定义
Port& operator+=(int b); // adds b to bottles
//派生类的计算逻辑与基类一致,且在该方法中派生类未操作其新增成员,因此该函数在派生类中不需要重新定义
Port& operator-=(int b); // subtracts b from bottles, if available
int BottleCount() const { return bottles; }
virtual void Show() const;
friend ostream& operator<<(ostream& os, const Port& p);
};
class VintagePort : public Port // style necessarily = "vintage"
{
private:
char* nickname; // i.e., "The Noble" or "Old Velvet", etc.
int year; // vintage year
public:
VintagePort();//派生类使用了动态内存分配,需要重新定义
VintagePort(const char* br, int b, const char* nn, int y);//同上
VintagePort(const VintagePort& vp);//同上
~VintagePort() { delete[] nickname; }//派生类使用了动态内存分配,且析构函数不能被继承,需要重新定义
VintagePort& operator=(const VintagePort& vp);//增加了新的成员nickname和year,且赋值运算符不能被继承,需要重新定义
void Show() const;//增加了新的成员nickname和year,需要重新定义
friend ostream& operator<<(ostream& os, const VintagePort& vp);//友元函数不属于成员函数,无法继承
};
#endif // PORT_H_
port.cpp:
#include "port.h"
#include <cstring>
Port::Port(const char* br, const char* st, int b) {
brand = new char[strlen(br) + 1];
strcpy(brand, br);
strncpy(style, st, 20);
bottles = b;
}
Port::Port(const Port& p) {
brand = new char[strlen(p.brand) + 1];
strcpy(brand, p.brand);
strncpy(style, p.style, 20);
bottles = p.bottles;
}
Port& Port::operator=(const Port& p) {
if (&p == this) return *this;
delete[] brand;
brand = new char[strlen(p.brand) + 1];
strcpy(brand, p.brand);
strncpy(style, p.style, 20);
bottles = p.bottles;
return *this;
}
Port& Port::operator+=(int b) {
bottles += b;
return *this;
}
Port& Port::operator-=(int b) {
bottles -= b;
return *this;
}
void Port::Show() const {
cout << "Brand: " << brand << endl;
cout << "Style: " << style << endl;
cout << "Bottles: " << bottles << endl;
}
ostream& operator<<(ostream& os, const Port& p) {
os << p.brand << ", " << p.style << ", " << p.bottles;
return os;
}
VintagePort::VintagePort() : Port() {
nickname = new char[strlen("none") + 1];
strcpy(nickname, "none");
year = 0;
}
VintagePort::VintagePort(const char* br, int b, const char* nn, int y)
: Port(br, "Vintage", b) {
nickname = new char[strlen(nn) + 1];
strcpy(nickname, nn);
year = y;
}
VintagePort::VintagePort(const VintagePort& vp) : Port(vp) {
nickname = new char[std::strlen(vp.nickname) + 1];
std::strcpy(nickname, vp.nickname);
year = vp.year;
}
VintagePort& VintagePort::operator=(const VintagePort& vp) {
if (this == &vp) return *this;
Port::operator=(vp);
delete[] nickname;
nickname = new char[std::strlen(vp.nickname) + 1];
std::strcpy(nickname, vp.nickname);
year = vp.year;
return *this;
}
void VintagePort::Show() const {
Port::Show();
cout << "Nickname: " << nickname << endl;
cout << "Year: " << year << endl;
}
ostream& operator<<(ostream& os, const VintagePort& vp) {
os << (const Port&)vp;
os << ", " << vp.nickname << ", " << vp.year;
return os;
}
main.cpp:
#include <iostream>
#include "port.h"
int main() {
Port p1;
Port p2("Abc", "Bcc", 30);
std::cout << p1 << std::endl;
std::cout << p2 << std::endl;
Port p3 = p2;
p3.Show();
p3 += 3;
p3.Show();
Port p4 = p2;
p3 -= 2;
p3.Show();
VintagePort vp1("Vabc", 50, "hn", 1983);
vp1.Show();
VintagePort vp2;
vp2.Show();
vp1 -= 3;
vp2 = vp1;
std::cout << vp2 << std::endl;
return 0;
}
网友评论