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

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 instancereturn 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 constructorvirtual ~GameBoard() = default;    // virtual defaulted destructorGameBoard& 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 idiomGameBoard temp{ rhs }; // Do all the work in a temporary instanceswap(temp);   // Commit the work with only non-throwing operationsreturn *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.mrgr.cn/news/68194.html

相关文章:

  • PymuPDF4llm提取pdf文件文字、表格与图片
  • ubuntu 22.04 硬件配置 查看 显卡
  • P5665 [CSP-S2019] 划分
  • ◇【论文_20160610】Generative Adversarial Imitation Learning 【主体】
  • 微信小程序的汽车维修预约管理系统
  • 父组件调用函数式子组件,并向子组件传递函数参数。
  • 网络编程(一):UDP socket api => DatagramSocket DatagramPacket
  • 对话框(Dialog)
  • W3C HTML 活动
  • [数组排序] 1122. 数组的相对排序
  • 插入迭代器
  • 口播博主必装的五个App推荐,尤其是程序猿博主
  • 查缺补漏----内部排序算法排序趟数和比较次数
  • SQLI LABS | Less-33 GET-Bypass AddSlashes()
  • RCE漏洞分析
  • OSS和FastDFS的区别
  • 【如何在 Linux 和 Android 系统中杀死进程】
  • 火语言RPA流程组件介绍--获取窗口对象
  • C# 与 C++ 跨进程通信:使用 RabbitMQ 实现消息队列通信
  • Golang | Leetcode Golang题解之第547题身份数量
  • API网关之Gravitee
  • 基于ViT的无监督工业异常检测模型汇总
  • 如何在 Linux 系统中通过进程名杀掉蓝牙进程
  • Meta AI最新推出的长视频语言理解多模态模型LongVU分享
  • Verilog可综合语法
  • C语言 | Leetcode C语言题解之第546题移除盒子