热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C++类:类的其他特性(类成员再探、返回*this的成员函数)

文章目录类的其他特性类成员再探定义一个类型成员Screen类的成员函数令成员作为内联函数重载成员函数可变数据成员类数据成员的初始值返回*this的成员函数从const成员函数返回*


文章目录

      • 类的其他特性
        • 类成员再探
          • 定义一个类型成员
          • Screen 类的成员函数
          • 令成员作为内联函数
          • 重载成员函数
          • 可变数据成员
          • 类数据成员的初始值
        • 返回 *this 的成员函数
          • 从 const 成员函数返回 *this
          • 基于 const 的重载


类的其他特性

​ 这里主要特性包括:类型成员、类的成员的类内初始值、可变数据成员、内联成员函数、从成员函数返回 *this、如何定义并使用类类型以及友元的更多知识。


类成员再探

​ 这里将用一对相互关联的类来讲解。分别是 Screen 和 Window_mgr


定义一个类型成员

这里是成员部分代码:

class Screen {
public:typedef std::string::size_type pos; // 当然,也可以用 using 声明类型别名
private:pos cursor = 0; // 表示光标位置pos height = 0, width = 0; // 高 和 宽std::string contents; // 存放内容
};

在 public 部分定义 pos 使用户能够使用这个名字。其他的放入 private 部分隐藏具体细节。有一点需要注意的,和普通成员不同(普通成员无视定义顺序,可直接使用),类型成员需要先定义后使用。


Screen 类的成员函数

​ 一个类,当然不能够缺少构造函数。这里添加一个构造函数令用户能够定义屏幕尺寸和内容,以及其他两个成员,分别负责移动光标和读取给定位置的字符:

class Screen {
public:typedef std::string::size_type pos; // 当然,也可以用 using 声明类型别名Screen() = default; // 因为有其他的构造函数,此默认构造函数是必须的Screen(pos ht, pos wd, char c):height(ht),width(wd),contents(ht * wd,c) { }char get() const // 读取光标处字符{ return contents[cursor]; } // 隐式内联inline char get(pos ht,pos wd) const; // 显示内联Screen &move(pos r,pos c);
private:pos cursor = 0; // 表示光标位置pos height = 0, width = 0; // 高 和 宽std::string contents; // 存放内容
};

由于提供了其他的构造函数,编译器将不会自动生成默认的构造函数,所以 Scree() = default; 是必须的。

可以看到,提供了三个参数的构造函数中并没有初始化 cursor,所以 cursor 隐式的使用了类内初始值进行初始化。如果 cursor 没有类内初始值,就需要向其他的成员一样显示地初始化 cursor。


令成员作为内联函数

​ 在类中,一般规模较小的函数适合于被声明为内联函数。定义在类内部的成员函数是自动 inline 的。如上 Screen 的构造函数 和 返回光标所指字符的 get 函数默认是内联函数。

​ 可以在类的内部吧 inline 作为声明的一部分来显示声明为内联的成员函数,也可以在类外部用 inline 关键字修饰函数的定义:

inline Screen& Screen::move(pos r,pos c) { // 在定义处指定 inlinepos row = r * width; // 计算行的位置cursor = row + c; // 在行内将光标移动到指定列return *this; // 以左值的形式返回对象
}
char Screen::get(pos r,pos c) const { // 在类内部声明成 inlinepos row = r * width;return contents[row + c];
}
// !! 为什么是 row = r * width,因为存放数据的时候用的一个 字符串存放二维的数组,
// 相当于使用的一维数组存放来二维数据

虽然不需要在声明处和定义处同时说明 inline,但是最好在类外部定义的地方说明 inline,这样使类更容易理解。


重载成员函数

​ 成员函数同样可以重载,同样需要函数之间在参数数量或者类型上有所区别。

​ 如以上类定义中的两个 get 函数,分别返回的是光标当前位置 与 有行号和列号确定的位置的字符。编译器同样根据实参来决定运行哪一个函数。


可变数据成员

​ 有时(但不频繁)会发生这样一种情况,我们希望能修改类的某个数据成员,即使是在一个 const 成员函数内。可以通过在变量声明中假如 mutable 关键字实现。

​ 一个可变数据成员永远不会是 const,即使它是 const 对象的成员。因此一个 const 成员函数可以改变一个可变成员的值。如:在 Screen 中添加一个名为 access_ctr 的可变成员,通过它追踪每个 Screen 成员函数被调用的次数:

class Screes {
public:void some_member() const;
private:mutable size_t access_ctr; // 即使在一个 const 对象内仍能被修改// 其他成员与之前一致
};void Screen::some_member() const {++ access_ctr; //保存一个计数值,用于记录成员函数被调用次数// 其他操作
}

注意 mutable 定义的变量的初始化



类数据成员的初始值

​ 在定义好 Screen 类后,还将定义一个窗口管理类并用它表示显示器上的一组 Screen。这个类将包含一个 Screen 类型的 vector,每个元素表示一个特定的 Scree。默认情况下,我们希望 Window_mgr 类开始时总是拥有一个默认初始化的 Screen。在 C++11 中,最好方式就是把这个默认值声明成一个类内初始值。

class Window_mgr {
private:// 这个 Window_mgr 追踪的 Screen// 默认情况下&#xff0c;一个 Window_mgr 包含一个标准尺寸的空白 Screenstd::vector<Screen> screens { Screen(24,80,&#39; &#39;) };
};

当提供一个类内初始值时&#xff0c;必须以符号 &#61; 或者花括号表示.>



返回 *this 的成员函数

​ 现在将继续添加一些函数&#xff0c;负责设置光标所在位置或者其他任一给定位置的字符。

class Screen {
public:Screen &set(char);Screen &set(pos, pos, char);// 其他的和之前一致
};
inline Screen& Screen::set(char ch) {contents[cursor] &#61; ch;return *this; // 将 this 对象作为左值返回
}
inline Screen& Screen::set(pos r,pos c,char ch) {contents[r * width &#43; c] &#61; ch;return *this; // 将 this 对象作为左值返回
}

和 move 函数相同&#xff0c;set 成员函数的返回值是调用 set 对象的引用。返回引用的函数是左值的&#xff0c;意味着函数返回的是对象本身而非对象的副本。所以可以吧一系列操作连接在一条表达式中。

myScreen.move(4,0).set(&#39;#&#39;);
// 等价于myScreen.move(4,0);myScreen.set(&#39;#&#39;);

如果我们 move 函数返回的是 Screen 而不是 Screen&

那么以上语句就等价于:

Screen tmp &#61; myScreen.move(4,0);tmp.set(&#39;#&#39;);

即相当于改变的是 临时对象 tmp&#xff0c;而不是 原对象 myScreen


从 const 成员函数返回 *this

​ 紧接着&#xff0c;我们将添加一个名为 display 的操作&#xff0c;负责打印 Screen 的内容。同样&#xff0c;我们希望这个函数能够与 move 和 set 出现在同一序列中&#xff0c;所以函数的返回值仍然是 Screen&

​ 逻辑上&#xff0c;我们是显示 Screen 的内容&#xff0c;并没有改变&#xff0c;因此我们令 display 为一个 const 成员函数。但是此时&#xff0c;this 指针将是指向 const 的指针而 *this 是 const 对象。所以可知 display 的返回类型便成了 const Screen&。如果 display 返回一个 const 引用&#xff0c;就不能把 display 嵌入到一组动作中去&#xff1a;

Screen myScreen;// display 如果返回常量引用&#xff0c;则调用 set 将发生错误myScreen.display(cout).set(&#39;*&#39;);// 即使 myScreen 是一个非常量对象&#xff0c;对 set 调用仍将无法通过编译

即&#xff1a;一个 const 成员函数如果以引用的形式返回 *this&#xff0c;那么他的返回类型将是常量引用



基于 const 的重载

​ 通过区分成员函数是否是 const 的&#xff0c;我们可以对其进行重载&#xff0c;原因与我们之前根据指针参数是否指向 const 而重载函数的原因差不多。具体来说&#xff0c;因为非常量的版本对于常量对象是不可用的&#xff0c;所以只能在一个常量对象时调用 const 成员函数。另一方面&#xff0c;虽然可以在非常量对象上调用常量版本或者非常量版本&#xff0c;但此时显然非常量版本更好。

​ 这里将定义一个名为 do_display 的私有函数&#xff0c;有它负责实际的打印工作。

class Screen {
public:// 根据对象是否是 const 重载了 display函数Screen &display(std::ostream &os){do_display(os);return *this;}const Screen &display(std::ostream &os) const {do_display(os);return *this;}
private:void do_display(std::ostream &os) const { os << contents; }// 其他成员和之前一致
};

这样&#xff0c;我们就有了 常量版本与非常量版本的 display 函数&#xff0c;如此 我们便可以解决 myScreen.display(cout).set(’*’) 的问题&#xff0c;专门对 非常量对象进行调用。

Screen myScreen(5,3);const Screen blank(5,3);myScreen.set(&#39;#&#39;).dispaly(cout); // 调用非常量版本blank.display(cout); // 调用常量版本

-----3月20日更-----(大家可以当我傻 X 了&#xff0c;突然写了这个问题&#xff0c;实际可能大家都知道)
今天回顾了一下这篇博客&#xff0c;看到 display 重载了 const 版本&#xff0c;脑中突然引发了一个问题&#xff1a;

为什么如同 get 函数&#xff0c;我们不需要重载 const 版本&#xff0c;而是直接 char get() const { return contents[cursor]; } (假设叫函数①)&#xff0c;
而没有 char get() { return contents[cursor]; } 然后重载&#xff1a;const char get() const { return contents[cursor]; } &#xff1f;

解决&#xff1a;(当然了&#xff0c;这是我的想法&#xff0c;不知道对不对)&#xff0c;虽然函数①中的 this 指针的类型是 const Screen* const&#xff0c;所以 contents[cursor] 也应当是 const 的&#xff0c;然是我们返回的并不是引用&#xff0c;我们返回的是该对象的拷贝&#xff0c;所以根本无须关心这里的 this 指针类型到底是不是 const 的。所以无论对象是否是 const&#xff0c;我们都能够使用这个函数。

​ 而 display 函数&#xff0c;我们返回的是当前对象的引用&#xff0c;如果当前对象是 const 的&#xff0c;那么函数返回值就必须是 const 的&#xff0c;所以必须重载。


推荐阅读
  • 在高并发需求的C++项目中,我们最初选择了JsonCpp进行JSON解析和序列化。然而,在处理大数据量时,JsonCpp频繁抛出异常,尤其是在多线程环境下问题更为突出。通过分析发现,旧版本的JsonCpp存在多线程安全性和性能瓶颈。经过评估,我们最终选择了RapidJSON作为替代方案,并实现了显著的性能提升。 ... [详细]
  • JSOI2010 蔬菜庆典:树结构中的无限大权值问题
    本文探讨了 JSOI2010 的蔬菜庆典问题,主要关注如何处理非根非叶子节点的无限大权值情况。通过分析根节点及其子树的特性,提出了有效的解决方案,并详细解释了算法的实现过程。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 本题来自WC2014,题目编号为BZOJ3435、洛谷P3920和UOJ55。该问题描述了一棵不断生长的带权树及其节点上小精灵之间的友谊关系,要求实时计算每次新增节点后树上所有可能的朋友对数。 ... [详细]
  • 本文介绍如何从字符串中移除大写、小写、特殊、数字和非数字字符,并提供了多种编程语言的实现示例。 ... [详细]
  • 在尝试使用C# Windows Forms客户端通过SignalR连接到ASP.NET服务器时,遇到了内部服务器错误(500)。本文将详细探讨问题的原因及解决方案。 ... [详细]
  • 本文介绍了如何使用JavaScript的Fetch API与Express服务器进行交互,涵盖了GET、POST、PUT和DELETE请求的实现,并展示了如何处理JSON响应。 ... [详细]
  • 本文介绍了如何在 Node.js 中使用 `setDefaultEncoding` 方法为可写流设置默认编码,并提供了详细的语法说明和示例代码。 ... [详细]
  • 深入解析Java虚拟机(JVM)架构与原理
    本文旨在为读者提供对Java虚拟机(JVM)的全面理解,涵盖其主要组成部分、工作原理及其在不同平台上的实现。通过详细探讨JVM的结构和内部机制,帮助开发者更好地掌握Java编程的核心技术。 ... [详细]
  • 在进行QT交叉编译时,可能会遇到与目标架构不匹配的宏定义问题。例如,当为ARM或MIPS架构编译时,需要确保使用正确的宏(如QT_ARCH_ARM或QT_ARCH_MIPS),而不是默认的QT_ARCH_I386。本文将详细介绍如何正确配置编译环境以避免此类错误。 ... [详细]
  • 本文介绍如何在Spring Boot项目中集成Redis,并通过具体案例展示其配置和使用方法。包括添加依赖、配置连接信息、自定义序列化方式以及实现仓储接口。 ... [详细]
  • 本文详细介绍了优化DB2数据库性能的多种方法,涵盖统计信息更新、缓冲池调整、日志缓冲区配置、应用程序堆大小设置、排序堆参数调整、代理程序管理、锁机制优化、活动应用程序限制、页清除程序配置、I/O服务器数量设定以及编入组提交数调整等方面。通过这些技术手段,可以显著提升数据库的运行效率和响应速度。 ... [详细]
  • 本文介绍如何使用MFC和ADO技术调用SQL Server中的存储过程,以查询指定小区在特定时间段内的通话统计数据。通过用户界面选择小区ID、开始时间和结束时间,系统将计算并展示小时级的通话量、拥塞率及半速率通话比例。 ... [详细]
  • 深入解析SpringMVC核心组件:DispatcherServlet的工作原理
    本文详细探讨了SpringMVC的核心组件——DispatcherServlet的运作机制,旨在帮助有一定Java和Spring基础的开发人员理解HTTP请求是如何被映射到Controller并执行的。文章将解答以下问题:1. HTTP请求如何映射到Controller;2. Controller是如何被执行的。 ... [详细]
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
author-avatar
780527a
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有