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

类的其他特性

​ 这里主要特性包括:类型成员、类的成员的类内初始值、可变数据成员、内联成员函数、从成员函数返回 *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) {		// 在定义处指定 inline
    pos row = r * width;						// 计算行的位置
    cursor = row + c;							// 在行内将光标移动到指定列
    return *this;								// 以左值的形式返回对象
}
char Screen::get(pos r,pos c) const {			// 在类内部声明成 inline
    pos 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
    // 默认情况下,一个 Window_mgr 包含一个标准尺寸的空白 Screen
    std::vector<Screen> screens { Screen(24,80,' ') };
};

当提供一个类内初始值时,必须以符号 = 或者花括号表示.>

返回 *this 的成员函数

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

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

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

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

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

那么以上语句就等价于:

	Screen tmp = myScreen.move(4,0);
	tmp.set('#');

即相当于改变的是 临时对象 tmp,而不是 原对象 myScreen

从 const 成员函数返回 *this

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

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

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

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

基于 const 的重载

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

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

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; }
    // 其他成员和之前一致
};

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

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

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

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

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

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

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值