文章目录类的其他特性类成员再探定义一个类型成员Screen类的成员函数令成员作为内联函数重载成员函数可变数据成员类数据成员的初始值返回*this的成员函数从const成员函数返回*
文章目录
- 类的其他特性
- 类成员再探
- 定义一个类型成员
- Screen 类的成员函数
- 令成员作为内联函数
- 重载成员函数
- 可变数据成员
- 类数据成员的初始值
- 返回 *this 的成员函数
- 从 const 成员函数返回 *this
- 基于 const 的重载
类的其他特性
这里主要特性包括:类型成员、类的成员的类内初始值、可变数据成员、内联成员函数、从成员函数返回 *this、如何定义并使用类类型以及友元的更多知识。
类成员再探
这里将用一对相互关联的类来讲解。分别是 Screen 和 Window_mgr
定义一个类型成员
这里是成员部分代码:
class Screen {
public:typedef std::string::size_type pos;
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; 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) { pos row = r * width; cursor = row + c; return *this;
}
char Screen::get(pos r,pos c) const { pos row = r * width;return contents[row + c];
}
虽然不需要在声明处和定义处同时说明 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;
};void Screen::some_member() const {++ access_ctr;
}
注意 mutable 定义的变量的初始化
类数据成员的初始值
在定义好 Screen 类后,还将定义一个窗口管理类并用它表示显示器上的一组 Screen。这个类将包含一个 Screen 类型的 vector,每个元素表示一个特定的 Scree。默认情况下,我们希望 Window_mgr 类开始时总是拥有一个默认初始化的 Screen。在 C++11 中,最好方式就是把这个默认值声明成一个类内初始值。
class Window_mgr {
private:std::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;
}
inline Screen& Screen::set(pos r,pos c,char ch) {contents[r * width &#43; c] &#61; ch;return *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;myScreen.display(cout).set(&#39;*&#39;);
即&#xff1a;一个 const 成员函数如果以引用的形式返回 *this&#xff0c;那么他的返回类型将是常量引用
基于 const 的重载
通过区分成员函数是否是 const 的&#xff0c;我们可以对其进行重载&#xff0c;原因与我们之前根据指针参数是否指向 const 而重载函数的原因差不多。具体来说&#xff0c;因为非常量的版本对于常量对象是不可用的&#xff0c;所以只能在一个常量对象时调用 const 成员函数。另一方面&#xff0c;虽然可以在非常量对象上调用常量版本或者非常量版本&#xff0c;但此时显然非常量版本更好。
这里将定义一个名为 do_display 的私有函数&#xff0c;有它负责实际的打印工作。
class Screen {
public: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;所以必须重载。