25-x 第二十五章 总结与测验

至此,我们对C++继承与虚函数的探索之旅已然结束。亲爱的读者,不必忧虑,随着我们继续前行,C++还有诸多领域等待我们去探索。


章节总结

C++允许将基类指针和引用指向派生对象。当需要编写能处理任何基类派生对象的函数或数组时,此特性尤为实用。

若无虚函数,指向派生类的基类指针和引用仅能访问基类的成员变量及函数版本。

虚函数是一种特殊函数,它会解析为基类与派生类之间存在的最高派生版本函数(称为重写函数)。要被视为重写函数,派生类函数必须与虚基类函数具有相同的签名和返回类型。唯一例外是协变返回类型:当基类函数返回基类指针或引用时,重写函数可返回派生类指针或引用。

若需确保函数被重写,应使用重写限定符。

最终限定符可用于禁止函数重写或类继承。

若需使用继承,应将析构函数设为虚函数,以便删除基类指针时调用正确的析构函数。

可通过作用域解析运算符直接指定所需函数版本来绕过虚函数解析,例如:base.Base::getName()。

当编译器遇到直接函数调用时发生早期绑定,此时编译器或链接器可直接解析函数调用。当调用函数指针时发生晚期绑定。此类情况下,需运行时才能确定调用哪个函数。虚函数通过晚期绑定和虚函数表来确定调用哪个函数版本。

使用虚函数存在代价:虚函数调用耗时更长,且虚函数表的存在会使每个包含虚函数的对象增加一个指针的大小。

通过在虚函数原型末尾添加“= 0”,可将虚函数定义为纯虚函数/抽象函数。包含纯虚函数的类称为抽象类,不可实例化。继承纯虚函数的类必须具体实现这些函数,否则也将被视为抽象类。纯虚函数可以拥有函数体,但仍被视为抽象函数。

接口类是指不含成员变量且所有函数均为纯虚函数的类,其名称通常以大写字母I开头。

虚拟基类是指无论被对象继承多少次,都仅需包含一次的基类。

当派生类被赋值给基类对象时,基类仅接收派生类基部分的副本,此现象称为对象切片。

动态转换可将基类对象指针转换为派生类对象指针,此操作称为向下转换。转换失败时将返回空指针。

为继承类重载<<运算符的最简方法是:在最基类中实现重载的<<运算符,再通过调用虚成员函数完成输出操作。


测验时间

以下每个程序都存在某种缺陷。请检查每个程序(通过目视检查,而非编译运行),找出程序的问题所在。每个程序的预期输出应为“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() << '\n';

	return 0;
}

解决方案:

基类方法 Base::getName() 未被声明为虚函数,因此 b.getName() 不会解析为派生类方法 Derived::getName()。

b)

#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() << '\n';

	return 0;
}

解决方案

基类Base::getName()是非常量函数,而派生类Derived::getName()是常量函数,因此Derived::getName()不被视为重写。

c)

#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 }
	{
	}

	const char* getName() override { return "Derived"; }
};

int main()
{
	Derived d{ 5 };
	Base b{ d };
	std::cout << b.getName() << '\n';

	return 0;
}

解决方案:

d被赋值给b,导致d被切片。

d)

#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 }
	{
	}

	const char* getName() override { return "Derived"; }
};

int main()
{
	Derived d{ 5 };
	Base& b{ d };
	std::cout << b.getName() << '\n';

	return 0;
}

解决方案

基类已被声明为最终类,因此派生类无法从其派生。这将导致编译错误。

e)

#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() << '\n';

	return 0;
}

解决方案:

派生类::getName() 是一个纯虚函数(带有函数体),因此派生类是一个抽象类,无法被实例化。

f)

#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()
{
	auto* d{ new Derived(5) };
	Base* b{ d };
	std::cout << b->getName() << '\n';
	delete b;

	return 0;
}

解决方案

该程序实际输出了正确结果,但存在另一个问题。我们正在删除基类指针 b,但基类从未添加过虚析构函数。因此程序仅删除了派生对象的基类部分,而派生部分则作为内存泄漏残留。

2a) 创建一个名为 Shape 的抽象类。该类应包含三个函数:一个纯虚函数 print(该函数接受并返回 std::ostream&),一个重载的 operator<<,以及一个空的虚析构函数。

解决方案:

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() = default;
};

2b) 从Shape类派生出两个子类:三角形和圆形。三角形应包含3个顶点作为成员。圆形应包含一个中心点和一个整数半径。重写print()函数,使以下程序运行:

int main()
{
    Circle c{ Point{ 1, 2 }, 7 };
    std::cout << c << '\n';

    Triangle t{Point{ 1, 2 }, Point{ 3, 4 }, Point{ 5, 6 }};
    std::cout << t << '\n';

    return 0;
}

这应该输出:

Circle(Point(1, 2), radius 7)
Triangle(Point(1, 2), Point(3, 4), Point(5, 6))

以下是一个可供使用的Point类:

class Point
{
private:
	int m_x{};
	int m_y{};

public:
	Point(int x, int y)
		: m_x{ x }, m_y{ y }
	{

	}

	friend std::ostream& operator<<(std::ostream& out, const Point& p)
	{
		return out << "Point(" << p.m_x << ", " << p.m_y << ')';
	}
};

image

解决方案

#include <iostream>

class Point
{
private:
	int m_x{};
	int m_y{};

public:
	Point(int x, int y)
		: m_x{ x }, m_y{ y }
	{

	}

	friend std::ostream& operator<<(std::ostream& out, const Point& p)
	{
		return out << "Point(" << p.m_x << ", " << p.m_y << ')';
	}
};

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() = default;
};

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

	std::ostream& print(std::ostream& out) const override
	{
		return out << "Triangle(" << m_p1 << ", " << m_p2 << ", " << m_p3 << ')';
	}
};

class Circle : public Shape
{
private:
	Point m_center;
	int m_radius;

public:
	Circle(const Point& center, int radius)
		: m_center{ center }, m_radius{ radius }
	{
	}

	std::ostream& print(std::ostream& out) const override
	{
		return out << "Circle(" << m_center << ", radius " << m_radius << ')';
	}
};

int main()
{
	Circle c{ Point{ 1, 2 }, 7 };
	std::cout << c << '\n';

	Triangle t{ Point{ 1, 2 }, Point{ 3, 4 }, Point{ 5, 6 } };
	std::cout << t << '\n';

	return 0;
}

2c) 基于上述类(Point、Shape、Circle 和 Triangle),完成以下程序:

#include <vector>
#include <iostream>

int main()
{
	std::vector<Shape*> v{
	  new Circle{Point{ 1, 2 }, 7},
	  new Triangle{Point{ 1, 2 }, Point{ 3, 4 }, Point{ 5, 6 }},
	  new Circle{Point{ 7, 8 }, 3}
	};

	// print each shape in vector v on its own line here

	std::cout << "The largest radius is: " << getLargestRadius(v) << '\n'; // write this function

	// delete each element in the vector here

	return 0;
}

该程序应输出以下内容:

Circle(Point(1, 2), radius 7)
Triangle(Point(1, 2), Point(3, 4), Point(5, 6))
Circle(Point(7, 8), radius 3)
The largest radius is: 7

提示:你需要在Circle类中添加一个getRadius()函数,并将Shape类型强制转换为Circle类型才能访问该函数。

解决方案

#include <vector>
#include <iostream>
#include <algorithm> // for std::max

class Point
{
private:
	int m_x{};
	int m_y{};

public:
	Point(int x, int y)
		: m_x{ x }, m_y{ y }
	{

	}

	friend std::ostream& operator<<(std::ostream& out, const Point& p)
	{
		return out << "Point(" << p.m_x << ", " << p.m_y << ')';
	}
};

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() = default;
};

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

	std::ostream& print(std::ostream& out) const override
	{
		return out << "Triangle(" << m_p1 << ", " << m_p2 << ", " << m_p3 << ')';
	}
};


class Circle : public Shape
{
private:
	Point m_center;
	int m_radius{};

public:
	Circle(const Point& center, int radius)
		: m_center{ center }, m_radius{ radius }
	{
	}

	std::ostream& print(std::ostream& out) const override
	{
		out << "Circle(" << m_center << ", radius " << m_radius << ')';
		return out;
	}

	int getRadius() const { return m_radius; }
};

// h/t to reader Olivier for this updated solution
// assumes radiuses are >= 0
int getLargestRadius(const std::vector<Shape*>& v)
{
	int largestRadius{ 0 };

	// Loop through all the shapes in the vector
	for (const auto* element : v)
	{
		// // Ensure the dynamic cast succeeds by checking for a null pointer result
		if (auto* c { dynamic_cast<const Circle*>(element) })
		{
			largestRadius = std::max(largestRadius, c->getRadius());
		}
	}

	return largestRadius;
}
int main()
{
	std::vector<Shape*> v{
		  new Circle{Point{ 1, 2 }, 7},
		  new Triangle{Point{ 1, 2 }, Point{ 3, 4 }, Point{ 5, 6 }},
		  new Circle{Point{ 7, 8 }, 3}
	};

	for (const auto* element : v) // element will be a Shape*
		std::cout << *element << '\n';

	std::cout << "The largest radius is: " << getLargestRadius(v) << '\n';

	for (const auto* element : v)
		delete element;

	return 0;
}

image

2d) 附加题:将先前解决方案更新为使用 std::vector<std::unique_ptr>。请注意 std::unique_ptr 不可复制。

感谢读者 surrealcereal 提供此思路。

显示提示

提示:您无法使用 std::initializer_list 初始化向量,因为这需要复制元素。

显示提示

提示:std::unique_ptr::get() 返回指向被管理的元素的指针。

显示解决方案

#include <vector>
#include <iostream>
#include <algorithm> // for std::max
#include <memory>

class Point
{
private:
	int m_x{};
	int m_y{};

public:
	Point(int x, int y)
		: m_x{ x }, m_y{ y }
	{

	}

	friend std::ostream& operator<<(std::ostream& out, const Point& p)
	{
		return out << "Point(" << p.m_x << ", " << p.m_y << ')';
	}
};

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() = default;
};

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

	std::ostream& print(std::ostream& out) const override
	{
		return out << "Triangle(" << m_p1 << ", " << m_p2 << ", " << m_p3 << ')';
	}
};


class Circle : public Shape
{
private:
	Point m_center;
	int m_radius{};

public:
	Circle(const Point& center, int radius)
		: m_center{ center }, m_radius{ radius }
	{
	}

	std::ostream& print(std::ostream& out) const override
	{
		out << "Circle(" << m_center << ", radius " << m_radius << ')';
		return out;
	}

	int getRadius() const { return m_radius; }
};

int getLargestRadius(const std::vector<std::unique_ptr<Shape>>& v)
{
	int largestRadius{ 0 };

	// Loop through all the shapes in the vector
	for (const auto& element : v)
	{
		// // Ensure the dynamic cast succeeds by checking for a null pointer result
		if (auto *c { dynamic_cast<const Circle*>(element.get()) })
		{
			largestRadius = std::max(largestRadius, c->getRadius());
		}
	}

	return largestRadius;
}
int main()
{
	std::vector<std::unique_ptr<Shape>> v;
	v.reserve(3);
	v.push_back(std::make_unique<Circle>(Point{1, 2}, 7));
	v.push_back(std::make_unique<Triangle>(Point{1, 2}, Point{3, 4}, Point{5, 6}));
	v.push_back(std::make_unique<Circle>(Point{7, 8}, 3));

	for (const auto& element : v) std::cout << *element << '\n';

	std::cout << "The largest radius is: " << getLargestRadius(v) << '\n';
}

image

posted @ 2026-02-04 09:07  游翔  阅读(0)  评论(0)    收藏  举报