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

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.kler.cn/a/320582.html

相关文章:

  • ZSTD 内存泄漏问题
  • Restful API接⼝简介及为什么要进⾏接⼝压测
  • vueRouter路由切换时实现页面子元素动画效果, 左右两侧滑入滑出效果
  • WEB服务器实现(药品商超)
  • 基于Springboot+Vue的中国蛇类识别系统 (含源码数据库)
  • 为正在运行的 Docker 容器重启策略,以提高服务的可用性
  • Spring Boot使用配置方式整合MyBatis
  • 【Hadoop】一、Hadoop入门:基础配置、集群配置、常用脚本
  • 记录docker phpadmin 链接 docker mysql
  • MQTT客户端实战:从连接到通信。详细说明MQTT客户端和MQTT代理进行通信
  • 微服务Docker相关指令
  • 使用python搭建Web项目
  • swiper3匀速滚动会卡顿问题,已解决
  • Linux线程同步—竞态条件与互斥锁、读写锁(C语言)
  • <Java>String类型变量的使用
  • 基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
  • 【c语言数据结构】栈的详解! 超级详细!(模拟实现,OJ练习题)
  • kubernetes K8S 挂载分布式存储 ceph
  • 算法基础8-双链表
  • Xcode16 iOS18 编译问题适配
  • 微信小程序教程:如何在个人中心实现头像贴纸功能
  • python爬虫解析工具BeautifulSoup(bs4)和CSS选择器——处理HTML和XML数据(7)
  • Windows系统修改Tomcat虚拟机内存参数
  • 《CUDA编程》3.简单CUDA程序的基本框架
  • 计算机毕业设计python+spark知识图谱房价预测系统 房源推荐系统 房源数据分析 房源可视化 房源大数据大屏 大数据毕业设计 机器学习
  • RuoYi-App根据不同角色权限实现功能按钮显隐