当前位置: 首页 > article >正文

C++学习笔记----11、模块、头文件及各种主题(一)---- 模板概览与类模板(1)

1、模板概览

        面向过程的范式中的主要编程单元是过程或函数。函数有用主要是因为它们允许写特定值的独立的算法,因而可以重用于许多不同的值。例如,C++中的sqrt()函数计算调用者提供的值的平方根。只计算一个数值,比如数字4的平方根的平方根函数不是特别有用!sqrt()函数使用参数,它可以是调用者传递的任何值。计算机科学家将之称为参数化值的函数。

        面向对象的编程范式添加了对象的概念,它将相关的数据与行为分组,但是不改变函数与成员函数的参数化值的方式。

        模板将参数化的概念更进一步,允许像值一样参数化类型。C++中的类型包含原始类型如int与double,也有用户定义的类如SpreadsheetCell与CherryTree。使用模板,可以书写不只给定的值是独立的,这些值的类型也是独立的。例如,不是书写独立的栈类来保存int,Car与SpreadsheetCell,可以书写一个栈类模板定义,可以用于这些类型的任意一种。

        虽然模板是一个令人惊奇的语言特性,C++中的模板在语法上也会令人迷惑,因此,许多入口避免自己书写模板。然而,每一个专业C++程序员需要知道如何书写它们,并且每一个程序员至少需要知道如果使用模板,因为它们广泛用于库,比如C++标准库。

        本章会教会你关于C++支持的模板,重点关注在标准库中应用的特性。跟随学习的路径,会学到一些实用的特性,除了使用标准库之外,也可以用于程序中。

2、类模板

        类模板为类定义家族定义了一个蓝图(=模板),变量类型,成员函数的返回类型,和/或成员函数的参数被指定为模板类型参数。类模板就像建筑的蓝图。允许编译器通过用具体的类型替换模板类型参数来建造(也叫做实例化)具体的类定义。

        类模板主要用于窗口,或者数据结构,来保存对象。在本博客的早期,你已经经常使用类模板了,比如std::vector,unique_ptr,string,等等。本节讨论如何通过使用运行Grid容器来书写自己的类模板。为了使例子在长度上说得通,又足够简单来展示特定的观点,不同的章节给Grid容器添加了特性,在接下来的章节中并不会使用。

2.1、书写一个类模板

        假定你想要一个通用游戏棋盘类,可以用作国际象棋棋盘,跳棋棋盘、井子棋棋盘,或者任何其他二维的游戏棋盘。为了使其能够满足通用目的,需要能够保存国际象棋棋子,跳棋棋子,井子棋棋子,或任何游戏棋子。

2.1.1、不使用模板的代码

        不使用模板,最好的方法是构建一个通用的游戏棋盘来应用多态去保存通用的GamePiece对象。然后,可以让每一个游戏的棋子继承GamePiece类。例如,在国际象棋中,ChessPiece会是GamePiece的继承类。通过多态,GameBoard,写来去保存GamePiece,也可以保存ChessPiece。因为它应该能够拷贝GameBoard,GameBoard需要能够拷贝GamePiece。这种实现使用了多态,所以一个解决方案需要添加一个纯的虚clone()成员函数到GamePiece基类。它的继承类必须实现来返回一个具体的GamePiece的拷贝。下面是基本的GamePiece接口:

export class GamePiece
{
public:
	virtual ~GamePiece() = default;
	virtual std::unique_ptr<GamePiece> clone() const = 0;
};

        GamePiece是一个抽象基类。像ChessPiece这样的具体类,继承于GamePiece,实现clone()成员函数:

class ChessPiece : public GamePiece
{
public:
    std::unique_ptr<GamePiece> clone() const override
    {
        // Call the copy constructor to copy this instance
        return std::make_unique<ChessPiece>(*this);
    }
};

        GameBoard代表了一个二维的网格,所以保存GameBoard中的GamePiece的一个选项可以是unique_ptr的vectors的vector。然而,这并不是一个数据的优化的代表,因为在内存中的数据是分离的。最好是作为unique_ptr的vector线性保存GamePiece的表示。将一个二维的坐标,比如(x,y)转化为一个一维的位置在线性表示,可以通过使用x+y*width的公式来简单实现。

export class GameBoard
{
public:
	explicit GameBoard(std::size_t width = DefaultWidth, std::size_t height = DefaultHeight);
	GameBoard(const GameBoard& src);   // copy constructor
	virtual ~GameBoard() = default;    // virtual defaulted destructor
	GameBoard& operator=(const GameBoard& rhs); // assignment operator
	
	// Explicitly default a move constructor and move assignment operator.
	GameBoard(GameBoard&& src) = default;
	GameBoard& operator=(GameBoard&& src) = default;

	std::unique_ptr<GamePiece>& at(std::size_t x, std::size_t y);
	const std::unique_ptr<GamePiece>& at(std::size_t x, std::size_t y) const;

	std::size_t getHeight() const { return m_height; }
	std::size_t getWidth() const { return m_width; }

	static constexpr std::size_t DefaultWidth{ 10 };
	static constexpr std::size_t DefaultHeight{ 10 };

	void swap(GameBoard& other) noexcept;

private:
	void verifyCoordinate(std::size_t x, std::size_t y) const;

	std::vector<std::unique_ptr<GamePiece>> m_cells;
	std::size_t m_width { 0 }, m_height { 0 };
};

export void swap(GameBoard& first, GameBoard& second) noexcept;

        在这个实现中,at()返回一个给定位置的游戏棋子的引用,而不是棋子的拷贝。GameBoard作为一个二维数组的抽象,所以它应该提供数组访问语法,通过返回在任何位置的真实对象的引用,而不是该对象的拷贝。客户端代码不应该保存将来要用的引用,因为它可能会失效。例如,当m_cells vector需要进行尺寸变化。反过来,客户端代码应该在使用返回引用之前调用at()。这遵守了标准库vector类的设计逻辑。

        注意:该实现提供了两个版本的at();一个返回了reference-­to-­non-­ const,而另一个返回了reference-­to-­ const。

        注意:(C++23)从c++23开始,能够为GameBoard类提供多维的下标操作符。通过提供这样的操作符,客户可以书写myGameBoard[x,y],而不是myGameBoard.at(x,y)来访问在位置(x,y)上的棋子。

        下面是成员函数的定义,注意这个实现对于赋值操作符使用了copy-and-swap习语,Scott Meyers的const_cast()模式来避免代码重复。

GameBoard::GameBoard(size_t width, size_t height)
	: m_width{ width }
	, m_height{ height }
{
	m_cells.resize(m_width * m_height);
}

GameBoard::GameBoard(const GameBoard& src)
	: GameBoard{ src.m_width, src.m_height }
{
	// The ctor-initializer of this constructor delegates first to the
	// non-copy constructor to allocate the proper amount of memory.

	// The next step is to copy the data.
	for (size_t i{ 0 }; i < m_cells.size(); ++i) {
		if (src.m_cells[i]) {
			m_cells[i] = src.m_cells[i]->clone();
		}
	}
}

void GameBoard::verifyCoordinate(size_t x, size_t y) const
{
	if (x >= m_width) {
		throw out_of_range { format("x ({}) must be less than width ({}).", x, m_width) };
	}
	if (y >= m_height) {
		throw out_of_range { format("y ({}) must be less than height ({}).", y, m_height) };
	}
}

void GameBoard::swap(GameBoard& other) noexcept
{
	std::swap(m_width, other.m_width);
	std::swap(m_height, other.m_height);
	std::swap(m_cells, other.m_cells);
}

void swap(GameBoard& first, GameBoard& second) noexcept
{
	first.swap(second);
}

GameBoard& GameBoard::operator=(const GameBoard& rhs)
{
	// Copy-and-swap idiom
	GameBoard temp{ rhs }; // Do all the work in a temporary instance
	swap(temp);   // Commit the work with only non-throwing operations
	return *this;
}

const unique_ptr<GamePiece>& GameBoard::at(size_t x, size_t y) const
{
	verifyCoordinate(x, y);
	return m_cells[x + y * m_width];
}

unique_ptr<GamePiece>& GameBoard::at(size_t x, size_t y)
{
	return const_cast<unique_ptr<GamePiece>&>(as_const(*this).at(x, y));
}

       该GameBoard类工作得很好:

GameBoard chessBoard { 8, 8 };
auto pawn { std::make_unique<ChessPiece>() };
chessBoard.at(0, 0) = std::move(pawn);
chessBoard.at(0, 1) = std::make_unique<ChessPiece>();
chessBoard.at(0, 1) = nullptr;

 


http://www.kler.cn/a/388207.html

相关文章:

  • 设计模式——策略模式(c++)
  • 线性表-数组描述补充 迭代器(C++)
  • 模型结构及对比
  • 【论文复现】ChatGPT多模态命名实体识别
  • Word2Vec,此向量维度,以及训练数据集单条数据的大小,举例说明;Skip-gram模型实现词嵌入;热编码(One-Hot Encoding)和词向量;
  • VMWareTools安装及文件无法拖拽解决方案
  • python opencv灰度变换
  • Docker部署Oracle 11g
  • selinux与防火墙
  • 【1】虚拟机安装
  • 开源模型应用落地-glm模型小试-glm-4-9b-chat-vLLM集成(四)
  • 快速傅里叶变换(FFT)基础(附python实现)
  • Go语言异常处理
  • Windows配置NTP时间同步
  • Docker:镜像构建 DockerFile
  • Spring 配置绑定原理分析
  • 安全编码实践:反射API的“间谍游戏”
  • java-web-web后端知识小结
  • 让金融数据处理更精准-C#银行回单识别集成示例、回执单识别
  • GNU/Linux - /proc/sys/vm/overcommit_memory
  • 《Python 与 SQLite:强大的数据库组合》
  • thinkphp如何查出值是null的布尔类型的值
  • 代码随想录算法训练营Day13 | 二叉树理论基础、递归遍历、迭代遍历、统一迭代、层序遍历
  • Android智能座驾,carlink场景截屏黑屏问题
  • Pycharm远程调试deepspeed!可用!
  • 前端三件套配合豆包MarsCode 实现钉钉官网动画