综合测验
因此,我们通过C ++的继承和虚拟函数的旅程即将结束。亲爱的读者,不要担心,因为在我们前进的过程中还有许多其他C ++领域需要探索。
章节总结
C ++允许您设置基类指针和对派生对象的引用。当我们想要编写一个可以处理从基类派生的任何类型的对象的函数或数组时,这非常有用。
如果没有虚函数,基类指针和对派生类的引用只能访问基类成员变量和函数。
虚函数是一种特殊类型的函数,它解析为基类和派生类之间存在的函数的最大派生版本(称为覆盖)。要被视为覆盖,派生类函数必须具有与虚基类函数相同的签名和返回类型。一个例外是协变返回类型,如果基类函数返回指向基类的指针或引用,则允许覆盖返回指向派生类的指针或引用。
旨在作为覆盖的函数应使用覆盖说明符来确保它实际上覆盖。
最终说明符可用于防止函数或类的覆盖。
如果您打算使用虚函数,则应该使析构函数为虚拟,因此如果删除了指向基类的指针,则会调用正确的析构函数。
您可以通过使用范围解析运算符直接指定所需函数的哪个类版本来忽略虚拟分辨:例如base.Base :: getName()
编译器遇到直接函数调用时会发生早期绑定。编译器或链接器可以直接解析这些函数调用。调用函数指针时会发生延迟绑定。在这些情况下,将调用哪个函数直到运行时才能解析。虚函数使用后期绑定和虚拟表来确定要调用的函数。
使用虚函数有一个成本:虚函数需要更长的时间来调用,并且虚拟表的必须通过一个指针增加包含虚函数的每个对象的大小。
通过在虚函数原型的末尾添加“= 0”,可以使虚拟函数成为纯虚/抽象函数。包含纯虚函数的类称为抽象类,无法实例化。继承纯虚函数的类必须具体定义它们,否则它也将被视为抽象。纯虚函数可以有一个定义,但它们仍然被认为是抽象的。
接口类是没有成员变量和所有纯虚函数的接口类。这些通常以首都I开头。
虚基类是一个基类,只包含一次,无论对象继承多少次。
将派生类分配给基类对象时,基类只接收派生类的基本部分的副本。这称为对象切片。
动态转换可用于将指向基类对象的指针转换为指向派生类对象的指针。这称为向下转型。转换失败将返回空指针。
为继承类重载operator <<的最简单方法是为最基类编写一个重载的operator <<,然后调用一个虚拟成员函数来进行打印。
Quiz Time:
1)以下每个程序都有某种缺陷。检查每个程序(在视觉上,而不是通过编译)并确定程序有什么问题。每个程序的输出应该是“Derived”。
1A)
#include <iostream>
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
const char* getName() const { return "Base"; }
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
const char* getName() const { return "Derived"; }
};
int main()
{
Derived d(5);
Base &b = d;
std::cout << b.getName();
return 0;
}
解决方案
Base :: getName()未变为虚拟,因此b.getName()不会解析为Derived :: getName()。
1b)
#include <iostream>
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
virtual const char* getName() { return "Base"; }
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
virtual const char* getName() const { return "Derived"; }
};
int main()
{
Derived d(5);
Base &b = d;
std::cout << b.getName();
return 0;
}
解决方案
Base :: getName()是非const,Derived :: getName()是const,因此Derived :: getName()不被视为覆盖。
1C)
#include <iostream>
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
virtual const char* getName() { return "Base"; }
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
virtual const char* getName() override { return "Derived"; }
};
int main()
{
Derived d(5);
Base b = d;
std::cout << b.getName();
return 0;
}
解决方案
d按值分配给b,导致d被切片。
1D)
#include <iostream>
class Base final
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
virtual const char* getName() { return "Base"; }
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
virtual const char* getName() override { return "Derived"; }
};
int main()
{
Derived d(5);
Base &b = d;
std::cout << b.getName();
return 0;
}
解决方案
Base被声明为final,因此Derived无法从中派生。这将导致编译错误。
1E)
#include <iostream>
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
virtual const char* getName() { return "Base"; }
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
virtual const char* getName() = 0;
};
const char* Derived::getName()
{
return "Derived";
}
int main()
{
Derived d(5);
Base &b = d;
std::cout << b.getName();
return 0;
}
解决方案
Derived :: getName()是一个抽象函数(带有定义),因此无法实例化。
1F)
#include <iostream>
class Base
{
protected:
int m_value;
public:
Base(int value)
: m_value(value)
{
}
virtual const char* getName() { return "Base"; }
};
class Derived : public Base
{
public:
Derived(int value)
: Base(value)
{
}
virtual const char* getName() { return "Derived"; }
};
int main()
{
Derived *d = new Derived(5);
Base *b = d;
std::cout << b->getName();
delete b;
return 0;
}
解决方案
该程序实际上产生了正确的输出,但有一个不同的问题。我们正在删除b,这是一个Base指针,但我们从未向Base类添加虚拟析构函数。因此,程序仅删除Derived对象的Base部分,Derived部分保留为泄漏内存。
2a)创建一个名为Shape的抽象类。这个类应该有三个函数:一个纯虚拟打印函数,它接受并返回一个std :: ostream,一个重载的运算符<<和一个空的虚析构函数。
解决方案
class Shape
{
public:
// virtual const char* getShapeName() const = 0;
virtual std::ostream& print(std::ostream &out) const = 0;
friend std::ostream& operator<<(std::ostream &out, const Shape &p)
{
return p.print(out);
}
virtual ~Shape() {}
};
2b)从Shape中导出两个类:一个Triangle和一个Circle。三角应该有3分作为成员。圆应该有一个中心点和一个整数半径。重载print()函数,以便运行以下程序:
int main()
{
Circle c(Point(1, 2, 3), 7);
std::cout << c << '\n';
Triangle t(Point(1, 2, 3), Point(4, 5, 6), Point(7, 8, 9));
std::cout << t << '\n';
return 0;
}
这应该打印:
Circle(Point(1, 2, 3), radius 7)
Triangle(Point(1, 2, 3), Point(4, 5, 6), Point(7, 8, 9))
这是您可以使用的Point类:
class Point
{
private:
int m_x = 0;
int m_y = 0;
int m_z = 0;
public:
Point(int x, int y, int z)
: m_x(x), m_y(y), m_z(z)
{
}
friend std::ostream& operator<<(std::ostream &out, const Point &p)
{
out << "Point(" << p.m_x << ", " << p.m_y << ", " << p.m_z << ")";
return out;
}
};
解决方案
#include <iostream>
class Point
{
private:
int m_x = 0;
int m_y = 0;
int m_z = 0;
public:
Point(int x, int y, int z)
: m_x(x), m_y(y), m_z(z)
{
}
friend std::ostream& operator<<(std::ostream &out, const Point &p)
{
out << "Point(" << p.m_x << ", " << p.m_y << ", " << p.m_z << ")";
return out;
}
};
class Shape
{
public:
virtual std::ostream& print(std::ostream &out) const = 0;
friend std::ostream& operator<<(std::ostream &out, const Shape &p)
{
return p.print(out);
}
virtual ~Shape() {}
};
class Triangle : public Shape
{
private:
Point m_p1;
Point m_p2;
Point m_p3;
public:
Triangle(const Point &p1, const Point &p2, const Point &p3)
: m_p1(p1), m_p2(p2), m_p3(p3)
{
}
virtual std::ostream& print(std::ostream &out) const override
{
out << "Triangle(" << m_p1 << ", " << m_p2 << ", " << m_p3 << ")";
return out;
}
};
class Circle: public Shape
{
private:
Point m_center;
int m_radius;
public:
Circle(const Point ¢er, int radius)
: m_center(center), m_radius(radius)
{
}
virtual std::ostream& print(std::ostream &out) const override
{
out << "Circle(" << m_center << ", radius " << m_radius << ")";
return out;
}
};
int main()
{
Circle c(Point(1, 2, 3), 7);
std::cout << c << '\n';
Triangle t(Point(1, 2, 3), Point(4, 5, 6), Point(7, 8, 9));
std::cout << t << '\n';
return 0;
}
2c)给定上述类(Point,Shape,Circle和Triangle),完成以下程序:
#include <vector>
#include <iostream>
int main()
{
std::vector<Shape*> v;
v.push_back(new Circle(Point(1, 2, 3), 7));
v.push_back(new Triangle(Point(1, 2, 3), Point(4, 5, 6), Point(7, 8, 9)));
v.push_back(new Circle(Point(4, 5, 6), 3));
// 在这里打印矢量v中的每shape
std::cout << "The largest radius is: " << getLargestRadius(v) << '\n'; // write this function
//删除向量中的每个元素
}
**提示:您需要向Circle添加getRadius()函数,并将Shape 向下转换为Circle 以访问它。
解决方案
#include <vector>
#include <iostream>
class Point
{
private:
int m_x = 0;
int m_y = 0;
int m_z = 0;
public:
Point(int x, int y, int z)
: m_x(x), m_y(y), m_z(z)
{
}
friend std::ostream& operator<<(std::ostream &out, const Point &p)
{
out << "Point(" << p.m_x << ", " << p.m_y << ", " << p.m_z << ")";
return out;
}
};
class Shape
{
public:
virtual std::ostream& print(std::ostream &out) const = 0;
friend std::ostream& operator<<(std::ostream &out, const Shape &p)
{
return p.print(out);
}
virtual ~Shape() {}
};
class Triangle : public Shape
{
private:
Point m_p1;
Point m_p2;
Point m_p3;
public:
Triangle(const Point &p1, const Point &p2, const Point &p3)
: m_p1(p1), m_p2(p2), m_p3(p3)
{
}
virtual std::ostream& print(std::ostream &out) const override
{
out << "Triangle(" << m_p1 << ", " << m_p2 << ", " << m_p3 << ")";
return out;
}
};
class Circle: public Shape
{
private:
Point m_center;
int m_radius;
public:
Circle(const Point ¢er, int radius)
: m_center(center), m_radius(radius)
{
}
virtual std::ostream& print(std::ostream &out) const override
{
out << "Circle(" << m_center << ", radius " << m_radius << ")";
return out;
}
int getRadius() { return m_radius; }
};
// h / t给读者Olivier这个更新的解决方案
int getLargestRadius(const std::vector<Shape*> &v)
{
int largestRadius { 0 };
// 循环遍历向量中的所有形状
for (auto const &element : v)
{
//通过检查空指针结果确保动态转换成功
if (Circle *c = dynamic_cast<Circle*>(element))
{
if (c->getRadius() > largestRadius)
largestRadius = c->getRadius();
}
}
return largestRadius;
}
int main()
{
std::vector<Shape*> v;
v.push_back(new Circle(Point(1, 2, 3), 7));
v.push_back(new Triangle(Point(1, 2, 3), Point(4, 5, 6), Point(7, 8, 9)));
v.push_back(new Circle(Point(4, 5, 6), 3));
for (auto const &element : v) // element将是一个对Shape *的const引用
std::cout << *element << '\n'
std::cout << "The largest radius is: " << getLargestRadius(v) << '\n';
for (auto const &element : v) // element将是一个对Shape *的const引用
delete element;
return 0;
}