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

C++学习笔记----7、使用类与对象获得高性能(一)---- 书写类(2)

2.2、定义成员函数

        前面对SpreadsheetCell类的定义足以让你生成类的对象。然而,如果想调用setValue()或者getValue()成员函数,连接器就会抱怨这些函数没有定义。这是因为到目前为止,这些成员函数只有原型,而还没有实现。通常,类的定义会在模块接口文件。对于成员函数的定义,你有一个选择:可以在模块定义文件或者在模块实现文件。

下面是SpreadsheetCell类,在类内对成员函数进行了实现:

export module spreadsheet_cell;
export class SpreadsheetCell
{
public:
	void setValue(double value) { m_value = value; }
	double getValue() const { return m_value; }
private:
	double m_value{ 0 };
};

        与头文件不同,c++模块中把成员函数定义放在模块接口文件中并没有什么不好。这个我们以后再讨论。然而,我们还是通常把成员函数定义放在模块实现文件中,是为了保证模块接口干净,不需要实现细节。

        模块实现的第一行指出该实现是哪个模块。下面是spreadsheet_cell模块中SpreadsheetCell类的两个成员函数的定义:

module spreadsheet_cell;

void SpreadsheetCell::setValue(double value)
{
	m_value = value;
}

double SpreadsheetCell::getValue() const
{
	return m_value;
}

        注意类名之后两个冒号后跟着的是每个成员函数的名字:

void SpreadsheetCell::setValue(double value)

        ::被叫做范围解析操作符。具体内容就是,语法告诉编译器接下来对setValue()成员函数的定义是SpreadsheetCell类的一部分。也要注意,当你定义成员函数时,不需要重复访问说明。

2.2.1、访问数据成员

        对于像setValue()与getValue()这样类的非静态成员函数总是哇规定类的特定对象执行。在成员函数体内,拥有所有对象的类的数据成员的访问权限。对于前面对setValue()的定义,下面这行代码不管什么样的对象调用了成员函数都会改变m_value变量的值:

m_value = value;

        如果setValue()被两个不同的对象调用,同样的代码行(每个对象执行一次)在两个不同的对象中改变了变量值。

2.2.2、调用其他成员函数

        可以在另外一个成员函数中调用一个类的成员函数。例如,考虑对SpreadsheetCell类进行扩展,允许使用字符串或者数字来设置和访问格子的值。当你尝试将格子的值用字符串来设置时,格子尝试将字符串转化为数字。如果字符串无法转化为一个合法的数字,格子值会忽略。在程序中,无法转化为数字的字符串会为格子生成一个0值。下面是对SpreadsheetCell的类定义:

export module spreadsheet_cell;
import std;

export class SpreadsheetCell
{
public:
	void setValue(double value);
	double getValue() const;

	void setString(std::string_view value);
	std::string getString() const;

private:
	std::string doubleToString(double value) const;
	double stringToDouble(std::string_view value) const;

	double m_value{ 0 };
};

        这一版的类只保存了一个double数据。如果客户将数据设置为字符串,会将其转化为double。如果字符串不是合法的数字,double值会被设置为0。类定义显示了两个新的成员函数来设置与访问格子中的文本表达,两个新的内部的辅助成员函数将double转化为字符串以及反向转化。下面是对所有这些成员函数的实现:

module spreadsheet_cell;
import std;
using namespace std;

void SpreadsheetCell::setValue(double value)
{
	m_value = value;
}

double SpreadsheetCell::getValue() const
{
	return m_value;
}

void SpreadsheetCell::setString(string_view value)
{
	m_value = stringToDouble(value);
}

string SpreadsheetCell::getString() const
{
	return doubleToString(m_value);
}

string SpreadsheetCell::doubleToString(double value) const
{
	return to_string(value);
}

double SpreadsheetCell::stringToDouble(string_view value) const
{
	double number{ 0 };
	from_chars(value.data(), value.data() + value.size(), number);
	return number;
}

        std::to_string()与from_chars()函数我们以前解释过,这里就不过多阐述了。

        注意这里对doubleToString()成员函数的实现,例如6.1会被转化成6.100000。然而,因为它是一个内部辅助成员函数,可以不必修改任何客户端代码的情况下自由修改其实现。

2.3、使用对象

        前面对类SpreadsheetCell的定义,包含了一个数据成员,四个公共的成员函数,两个内部成员函数。然而,类定义没有产生任何实际的SpreadsheetCell;它只是指出其形状与行为。在这个层面,一个类就像一个架构蓝图。蓝图指出房子应该盖成什么样,但是画出蓝图并没有建造任何房子。房子一定要基于蓝图之后建造。

        同样的,在C++中,可以通过声明一个SpreadsheetCell类型的变量从SpreadsheetCell类定义中构造一个SpreadsheetCell对象。正像建筑工人可以基于给定的蓝图建造多个房子,程序员也可以从SpreadsheetCell类生成多个SpreadsheetCell对象。有两种生成和使用对象的方式:在栈上和在自由分配空间上。

2.3.1、栈上对象

        下面是一些栈上生成和使用SpreadsheetCell对象的例子:

import spreadsheet_cell;
import std;
using namespace std;

int main()
{
	SpreadsheetCell myCell, anotherCell;
	myCell.setValue(6);
	anotherCell.setString("3.2");

	println("cell 1: {}", myCell.getValue());
	println("cell 2: {}", anotherCell.getValue());

	println("cell 1: {}", myCell.getString());
	println("cell 2: {}", anotherCell.getString());
}

就像声明简单变量一样生成对象,除了变量类型就是类名字之外。myCell.setValue(6)代码行中的.被叫做“点”操作符,也叫做成员访问操作符;允许调用对象的公共成员函数。任何类中的公共的数据成员,都可以使用点操作符来访问。记住我们并不推荐将成员变量设置为可以公共访问。

程序的输出如下:

cell 1: 6
cell 2: 3.2
cell 1: 6.000000
cell 2: 3.200000

2.3.2、自由分配空间上的对象

        也可以使用new来动态分配对象:

import spreadsheet_cell;
import std;

using namespace std;

int main()
{
	SpreadsheetCell* myCellp{ new SpreadsheetCell{ } };

	myCellp->setValue(3.7);
	println("cell 1: {} {}", myCellp->getValue(), myCellp->getString());
	delete myCellp;
	myCellp = nullptr;
}

        当在自由分配空间上生成一个对象时,可以通过“箭头”操作符->来访问成员。箭头包含了间接引用(*)和成员访问(.)。也我可能要用这两个操作符,但是这样做的话从风格上来看就有点尴尬:

import spreadsheet_cell;
import std;

using namespace std;

int main()
{
	SpreadsheetCell* myCellp{ new SpreadsheetCell{ } };

	(*myCellp).setValue(3.7);
	println("cell 1: {} {}", (*myCellp).getValue(), (*myCellp).getString());
	delete myCellp;
	myCellp = nullptr;
}

        就像你要释放分配在自由分配空间上其他内存一样,也必须通过调用delete来释放分配在自由分配空间上的对象的内存,就像上面的代码那样!为了保证安全,避免问题,你真的应该使用智能指针,举例如下:

import spreadsheet_cell;
import std;

using namespace std;

int main()
{
	auto myCellp{ make_unique<SpreadsheetCell>() };
	// Equivalent to:
	// unique_ptr<SpreadsheetCell> myCellp{ new SpreadsheetCell{ } };

	myCellp->setValue(3.7);
	println("cell 1: {} {}", myCellp->getValue(), myCellp->getString());
}

        使用智能指针就不需要手动释放内存了,它会自动释放。

        当使用new分配对象空间时,使用完毕之后要用delete进行释放,或者,最好使用智能指针来自动管理内存!

        如果不使用智能指针的话,将删除其指向的对象之后设置为nullptr不失为一个好主意。我们并不要求你这么做,如果在删除之后指针被意外使用到的话,也会很容易排错。


http://www.kler.cn/news/305590.html

相关文章:

  • 【编程基础知识】Java语言中字面字符和转义字符的区别
  • 自建,前端sdk库(react-utils-dev-sdk)
  • Pytorch2.4.0自动安装cudnn9.1??? pip安装cudnn方法
  • 使用SQLAlchemy进行数据库操作:编写一个高效的Python函数
  • SpringBoot和SpringMVC是什么关系?SpringBoot替代SpringMVC了吗?
  • 继图书管理项目遗留的问题修改
  • Google宣布所有英语语种的Gemini Live用户现可免费使用
  • 对游戏语音软件Oopz遭遇DDoS攻击后的一些建议
  • 深入了解 Python 的 argparse 模块:命令行参数处理的艺术
  • 828华为云征文 | 华为云X实例服务器上部署知识图谱项目的详细指南
  • 企微机器人:企业数字化转型的得力助手
  • dubbo一
  • 重拾java-------day2(下载,特点,运行过程,环境变量)
  • React 中的事件绑定与参数传递
  • 尚品汇-订单拆单、支付宝关闭交易、关闭过期订单整合(五十)
  • 《网络协议 - HTTP传输协议及状态码解析》
  • 伙房食堂电气安全新挑战:油烟潮湿环境下,如何筑起电气火灾“防火墙”?
  • Linux——分离部署,分化压力
  • Next.js 14 如何在服务端页面中使用客户端渲染组件
  • Python数据分析-Pandas快速入门
  • DingoDB:多模态向量数据库的实践与应用
  • 【Bean】BeanPostProcessor的前置方法和后置方法的作用和使用
  • AG3335芯片介绍
  • 李沐pytorch 课程 深度学习D2l python3.12安装方法
  • uni-app怎么使用uni-icons
  • Android Studio Menu制作
  • html 关于select动态加载数据以及动态分组
  • 订单出现超时未关闭场景解决方案
  • C#中的string和stringbuild
  • 图文讲解HarmonyOS应用发布流程