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

C++学习笔记----8、掌握类与对象(二)---- 成员函数的更多知识(3)

3.3、引用验证的成员函数

        通常的成员函数可以被非临时和临时的类实例调用。假设你有如下的类只是记住了string作为参数传递给了构造函数:

class TextHolder
{
public:
    explicit TextHolder(string text) : m_text { move(text) } {}
    const string& getText() const { return m_text; }

private:
    string m_text;
};

        当然了,毫无疑问可以在非临时的TextHolder实例上调用getText()成员函数。下面是例子:

TextHolder textHolder { "Hello world!" };
println("{}", textHolder.getText());

        然而,getText()也可以在临时实例上调用:

println("{}", TextHolder{ "Hello world!" }.getText());

        可以指定成员函数可以在什么类型的实例上调用,是临时的还是非临时的实例。这可以通过给成员函数添加 一个ref-qualifier来实现。如果成员函数只能在非临时的实例上调用,在成员函数头后面回下一个&标识符。类似地,如果成员函数只能在临时实例上调用,添加一个&&标识符。

        下面修改后的TextHolder类通过返回一个reference-const给m_text实现了&验证过了的getText()。另一方面,&&验证了的getText()返回一个右值引用给m_text,这样的话,m_text可以移出TextHolder。如果你要访问从临时的TextHolder实例中的文本的话就会更高效。

class TextHolder
{
public:
    explicit TextHolder(string text) : m_text { move(text) } {}
    const string& getText() const & { return m_text; }
    string&& getText() && { return move(m_text); }

private:
    string m_text;
};

        假设你如下的调用:

TextHolder textHolder { "Hello world!" };
println("{}", textHolder.getText());
println("{}", TextHolder{ "Hello world!" }.getText());

        那么第一个对getText()的调用是调用了&验证过了的重载,而第二个调用是&&验证过了重载。

使用ref-qualifier的第二个例子是防止用户赋值给一个类的临时实例。例如,可以给TextHolder添加一个赋值操作符:

class TextHolder
{
public:
    TextHolder& operator=(const string& rhs) { m_text = rhs; return *this; }
    // Remainder of the class definition omitted for brevity
};

        一旦这样的一个赋值操作符添加到TextHolder,赋一个新值给一个TextHolder的临时实例,如下面代码片断所示,是没有任何道理的,因为对象马上就会消失:

TextHolder makeTextHolder() { return TextHolder { "Hello World!" }; }

int main()
{
    makeTextHolder() = "Pointless!"; // Pointless, object is a temporary.
}

        这样无意义的操作可以通过引用验证的赋值操作符来防止,使其仅仅工作于左值上:

TextHolder& operator=(const string& rhs) & { m_text = rhs; return *this; }

        使用这个赋值操作符,前面的“无意义的”main()中的语句编译失败。现在只能赋值给左值:

auto text { makeTextHolder() };
text = "Ok";

3.4、使用显式对象参数的引用验证(c++23)

        前面我们介绍过的显式对象参数的概念。它允许你用稍微不同的语法重写前面的TextHolder类的引用验证了的成员函数:

class TextHolder
{
public:
    const string& getText(this const TextHolder& self) { return self.m_text; }
    string&& getText(this TextHolder&& self) { return move(self.m_text); }

    TextHolder& operator=(this TextHolder& self, const string& rhs)
    {
        self.m_text = rhs;
        return self;
    }
    // Remainder of the class definition omitted for brevity
};

        很明显它比前面使用的语法冗长多了,但是它使得引用验证更明显。在前面的章节中,只有&或者&&在成员函数签名的末尾,但是,它是很容易被忽视的啊,比如,当同事检查你的代码时。

4、内联成员函数

        c++给你了推荐给编译器对函数的调用不应该在作为对单独代码块的调用的生成代码中实现的能力。反过来,编译器应该将函数体插入到函数被调用的代码中。这个过程叫做内联,需要这种行为的函数叫做内联函数。

        可以通过在给你开门函数定义的名字之前放置inline关键字来指出内联成员函数。例如,你想要使得SpreadsheetCell类的访问成员函数inline,这种情况下就应该像下面这样定义它们:

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

inline std::string SpreadsheetCell::getString() const
{
	++m_numAccesses;
	return doubleToString(m_value);
}

        这就给了编译器一个提示,将对getValue()和getString()的调用替换成实际的函数体而不是生成代码去进行函数调用。记住inline关键字只是一个对于编译器的提示。如果编译器认为它会损害性能的话可以忽略的。

        有一个警告:inline函数的定义应该在每一个调用的源文件中存在。好好想想这还是有道理的:如果编译器找不到函数定义,它怎么替换函数体?这样的话,如果要写inline成员函数,应该将这样的成员函数的定义放在成员函数属于的类的同一定义文件中。

        注意:高级的c++编译器不再要求将Inline成员函数的定义与类的定义放置在同一文件中,例如,Microsoft Visual C++支持连接时代码生成(LTCG),它会自动地使得小的函数体inline,即使没有声明为inline并且没有与类定义在同一个文件中。GCC与Clang也有同样的属性。

        c++模块之外,如果成员函数定义被直接放在类定义中,成员函数会被隐式地标记为inline,即使没有使用inline关键字。对于从模块中export进来的类,情况并不是这样。如果你想让这样的成员函数成为inline,需要使用inline关键字进行标记。下面是例子:

export class SpreadsheetCell
{
public:
	inline double getValue() const
	{
		++m_numAccesses;
		return m_value;
	}

	inline std::string getString() const
	{
		++m_numAccesses;
		return doubleToString(m_value);
	}
    // Omitted for brevity
}

        注意:如果在调试器中对内联函数使用单步跟踪,有些高级的c++调试器会跳到内联函数的实际源代码中,给你事实上函数调用的代码是内联的印象。

        许多c++程序员发现了inline函数语法,在没有理解其后果的情况下就使用。将函数标记为inline只是给编译器一个提示。编译器只会将最简单的函数内联。如果定义了一个编译器不想内联的内联函数,它会忽略这个提示。现代编译器会对这样的代码膨胀进行测算,在决定是否要内联一个函数之前,对于那些不能带来效率提升的并不会进行内联。

5、缺省值

        在c++中有一个与函数重载类似的属性叫做缺省值。可以指定缺省的原型的函数参数。如果用户提供了这些参数的值,缺省值就会被忽略。如果用户省略了这些值,就会用到缺省值。但是有个限制:只能为从最右边开始的连续的参数列表提供缺省省。否则的话,编译器就不能给缺失掉的参数匹配到缺省值。缺省值可以用于函数,成员函数,和构造函数。例如,可以给下面的Spreadsheet构造函数赋width和height的缺省值:

export class Spreadsheet
{
    public:
    explicit Spreadsheet(std::size_t width = 100, std::size_t height = 100);
    // Omitted for brevity
};

        Spreadsheet构造函数的实现保持不变。注意是在函数声明时指定缺省值,而不是在定义中。

        现在可以用0个,一个,或者两个对数来调用Spreadsheet构造函数,即使只有一个非拷贝构造函数:

Spreadsheet s1;
Spreadsheet s2 { 5 };
Spreadsheet s3 { 5, 6 };

        对于所有参数带有缺省值的构造函数可以作为缺省构造函数起作用。也就是说,可以构建一个类对象而不指定任何参数。如果想声明一个缺省构造函数和一个所有参数都有缺省值的多参数的构造函数,编译器就会报错,因为如果你不指定任何参数的话,它不知道去调用哪个构造函数。

        注意:只要能对缺省值做的,就可以对函数重载做。可以写三种不同的构造函数,每一个使用不同数量的参数。然而,缺省值允许你只写一个构造函数而使用三种不同数量的参数。应该使用你认为最舒服的技术与方式来达到你的目的。


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

相关文章:

  • 【数一线性代数】021入门
  • 代码工艺:Spring Boot 防御式编程实践
  • JavaScript Map全解:从基础到高级应用
  • jackson对于对象序列化的时候默认空值和手动传入的null的不同处理
  • 模拟斗地主发扑克的编程
  • Vue.js组件开发教程
  • JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
  • 新手教学系列——curl_cffi异步Session使用注意事项
  • AI生成垃圾内容对互联网的冲击与应对:一场持续扩展的危机
  • 嵌入式面试刷题(day18)
  • 在Ubuntu 16.04上使用LAMP安装WordPress
  • uniapp中uni.request的统一封装 (ts版)
  • PHP:构建高效Web应用的基石与实战案例
  • 【C++】多态(上)
  • 废品回收小程序/环保垃圾回收/收二手垃圾小程序/分类资源回收系统/独立版系统源码
  • 解析TMalign文本文件中的转换矩阵
  • 鸿蒙harmonyos next flutter混合开发之开发package
  • C++队列、双向队列
  • Linux Debian12使用Podman安装bwapp靶场环境
  • [Linux]Shell基本