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

C++学习笔记----8、掌握类与对象(一)---- 对象中的动态内存分配(1)

1、FRIENDS

        c++允许类声明为其它类,其它类的成员函数,或者非成员函数为friend。可以访问protected与private数据成员与成员函数。例如,假设你有两个类Foo与Bar。你可以指定Bar类是Foo类的一个friend:

class Foo
{friend class Bar;// ...
};

        这样Bar类的所有成员函数可以访问Foo类的private与protected数据成员与成员函数。

        如果你只是想让Bar类的一个特定成员函数做friend,也可以这样做。假设Bar类有一个成员函数processFoo(const Foo&)。下面的语法用于让这个成员函数成为Foo的一个friend:

class Foo
{friend void Bar::processFoo(const Foo&);// ...
};

        独立的函数也可以成为类的friend。举例来说,你可能想要写一个函数来打印Foo对象的所有数据到控制台。可能想要让该函数在Foo类之外,因为打印不是Foo的核心功能,但是该函数需要访问对象的内部数据成员以便进行打印。下面是Foo类的定义,以printFoo()作为friend:

class Foo
{friend void printFoo(const Foo&);// ...
};

        在类中的friend声明作为函数的原型。没有必要在其它地方再写原型了(写了也没什么坏处)。

        下面是函数的定义:

void printFoo(const Foo& foo)
{// Print all data of foo to the console, including// private and protected data members.
}

        在类定义之外写这样的函数与其它任何函数一样,除了可以直接访问Foo的private与protected成员。在函数定义时不必重复friend关键字。

        注意类需要知道其它哪些类,成员函数,或者函数想要成为它的friend;一个类,成员函数,或者函数不能声明自己为一些其它类的friend来获得那个类的非public成员的访问。

        friend类与函数容易被滥用;它们允许你破坏通过暴露类内部给其他类或函数来进行包装的原则。这样的话,只能在有限的情况下使用它们。我们会在博文中进行使用场景的展示。

2、对象中的动态内存分配

        有时候你不知道在程序真正运行之前需要多大内存。解决方案是在程序执行时动态分配需要的内存。类也不例外。有时候你不知道在写类时一个对象需要多大内存。在这种情况下,对象应该动态分配内存。给对象动态分配内存带来了一些挑战,包括释放内存,处理对象拷贝,以及处理对象赋值。

2.1、Spreadsheet类

        我们前面介绍过SpreadsheetCell类。现在我们继续写Spreadsheet类。与SpreadsheetCell类一样,Spreadsheet类也在我们的文章中不断演化。这样的话,多种尝试并不总是去演示类书写的每个方面的最好的方式。

        我们先开始,Spreadsheet只是一个简单的SpreadsheetCell的二维数组,在Spreadsheet中带有成员函数来设置与访问其特定位置的cell。虽然大部分spreadsheet应用在一个方向上使用字母,在另一个方向上使用数字来指向cell,我们的Spreadsheet在两个方向上都使用数字。

        Spreadsheet.cppm模块接口文件的第一行定义了模块的名字:

export module spreadsheet;

        Spreadsheet类需要访问SpreadsheetCell类,所以它需要import spreadsheet_cell模块。另外,为了使SpreadsheetCell类对spreadsheet模块中的用户可见,spreadsheet_cell模块需要用下面的看起来很可笑的语法来进行导入导出:

export import spreadsheet_cell;

        Spreadsheet类使用std::size_t类型,它定义在C头文件中<cstddef>。可以用下面的导入来获得访问权限:

import std;

        最终,下面是Spreadsheet类的定义的第一次尝试:

export class Spreadsheet
{
public:Spreadsheet(std::size_t width, std::size_t height);void setCellAt(std::size_t x, std::size_t y, const SpreadsheetCell& cell);SpreadsheetCell& getCellAt(std::size_t x, std::size_t y);private:bool inRange(std::size_t value, std::size_t upper) const;std::size_t m_width{ 0 };std::size_t m_height{ 0 };SpreadsheetCell** m_cells{ nullptr };
};

        注意:Spreadsheet类使用指向m_cells数组的正常指针。这样做的目的是为了展示结果以及解释怎么处理资源,比如类中的动态内存。在生产环境的代码中,应该使用标准c++容器,像std::vector,会大幅简化Spreadsheet的实现,但那样的话,你就无法学习如何使用原始指针正确处理动态内存。在现代c++中,永远不要使用带有属主语法的原始指针,但是对于既有代码你可能会碰到,这种情况下需要知道如何处理。

        注意Spreadsheet类并不包含标准的SpreadsheetCell二维数组。实际上,它包含了一个SpreadsheetCell**的数据成员,是一个指向代表了一个数组的数组的指针。这是因为每一个Spreadsheet对象可能有不同的维度,所以类的构造函数需要动态分配二维数组,基于客户指定的高度与宽度。

        为了动态分配一个二维数组,需要写下面的代码。记住在c++中,不像Java,不能只是简单地写new SpreadsheetCell[m_width][m_height]。

Spreadsheet::Spreadsheet(size_t width, size_t height): m_width { width }, m_height { height }
{m_cells = new SpreadsheetCell*[m_width];for (size_t i{ 0 }; i < m_width; ++i) {m_cells[i] = new SpreadsheetCell[m_height];}
}

        下图展示了叫做s1的Spreadsheet的结果内存结构,在栈上,宽度为4,高度为3。

        inRange()的实现,设置与访问成员函数就很直接了:

bool Spreadsheet::inRange(size_t value, size_t upper) const
{return value < upper;
}void Spreadsheet::setCellAt(size_t x, size_t y, const SpreadsheetCell& cell)
{if (!inRange(x, m_width)) {throw out_of_range { format("x ({}) must be less than width ({}).", x, m_width) };}if (!inRange(y, m_height)) {throw out_of_range { format("y ({}) must be less than height ({}).", y, m_height) };}m_cells[x][y] = cell;
}SpreadsheetCell& Spreadsheet::getCellAt(size_t x, size_t y)
{if (!inRange(x, m_width)) {throw out_of_range { format("x ({}) must be less than width ({}).", x, m_width) };}if (!inRange(y, m_height)) {throw out_of_range { format("y ({}) must be less than height ({}).", y, m_height) };}return m_cells[x][y];
}

        setCellAt()与getCellAt()两者都使用了类的辅助函数inRange()来检查x与y,它们代表了spreadsheet中的有效坐标。尝试访问越界索引的数组元素会导致程序运行不正确。本例使用了例外,以后会详细介绍。

        如果你仔细看setCellAt()与getCellAt()的实现,可以看到有很清晰的代码重复。我们以前说过要尽量避免代码重复。所以,让我们遵从指导,不使用inRange()的辅助函数,定义一个verifyCoordinate()的成员函数:

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

        其实现检查给定的坐标,如果坐标无效的话抛出例外:

void Spreadsheet::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) };}
}

        这样的话,setCellAt()与getCellAt()就可以简化如下:

void Spreadsheet::setCellAt(size_t x, size_t y, const SpreadsheetCell& cell)
{verifyCoordinate(x, y);m_cells[x][y] = cell;
}SpreadsheetCell& Spreadsheet::getCellAt(size_t x, size_t y)
{verifyCoordinate(x, y);return m_cells[x][y];
}


http://www.mrgr.cn/news/34845.html

相关文章:

  • Redis 的 Java 客户端有哪些?官方推荐哪个?
  • 末端无人配送产业链
  • MyBatis参数处理
  • JAVA无缝沟通全球国际版多语言语聊系统小程序源码
  • SOMEIP_ETS_127: SD_Multicast_FindService
  • Electron 更换窗口图标、exe执行文件图标
  • 工博会蓝卓逛展攻略
  • Pandas DataFrame 对象的基本操作
  • Reis数据库及key的操作命令汇总
  • 动态倒计时在 Vue 3 中的实现
  • 关系型数据库 - MySQL II
  • 振弦式渗压计智慧水利工程 适用恶劣环境有保障
  • 解密云WAF的核心功能!为企业保驾护航的关键技术
  • Warrior Pack Super Bundle 人物战斗动画捆绑包
  • 等位基因与碱基:异同点解析
  • Golang plugin包教程:创建与管理插
  • 重入锁ReentrantLock详解
  • axios提交数据后台php用post方式无法正确接收
  • Oracle AI理论与实践,企业落地篇干货满满
  • 使用Python实现深度学习模型:智能电影制作与剪辑