类型成员
class Screen
{
public:
typedef std::string::size_type pos;
private:
pos cursor = 0;
pos height = 0, width = 0;
std::string contents;
};
- 类型成员同样存在访问限制,可以是public或private的
- 用来定义类型的成员必须先定义后使用。因此,类型成员通常出现在类开始的地方
- 增加类型成员有两个好处,隐藏细节、一改全改
- 也可以使用等价的using声明
构造函数
Screen类用来表示我们熟悉的命令行窗口,想一想命令行窗口有哪些属性
- 窗口的大小(宽、高)
- 光标的位置
- 窗口中需要显示的内容
把Screen类的数据成员和命令行窗口的属性一一对应后,会更好的理解Screen类
class Screen
{
public:
Screen() = default;
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(ht * wd, c) {}
};
为了方便用户在创建时指明窗口的属性值,我们为Screen类添加了一个构造函数,它接受三个参数,分别表示窗口的宽、高和默认显示的内容
注意到构造函数中并没有为cursor指定初始值,它将使用类内初始值
内联函数
定义在类内部的成员函数是自动inline的
class Screen
{
public:
char get() const { return contents[cursor]; }
inline char get(pos ht, pos wd) const;
Screen &move(pos r, pos c);
};
- 无参的get函数是隐式内联的
- 带参的get函数是显式内联的
- move函数仍有机会在定义时添加inline关键字修饰
不需要在函数声明和定义的地方同时说明inline
inline成员函数也应该与相应的类定义在同一个头文件中
可变数据成员
我们知道在const成员函数内,无法修改数据成员的值,但有的时候确实需要。
可以用可变数据成员来解决在const成员函数中无法修改数据成员的问题
在数据成员的声明中加入mutable关键字即可将其声明为可变数据成员
class Screen
{
public:
void some_member() const;
private:
mutable size_t access_ctr;
};
一个可变数据成员永远不会是const,即使它是const对象的成员。
void Screen::some_member() const
{
++access_ctr;
}
类内初始值
当我们为类的数据成员提供类内初始值时,必须以=或者花括号表示。
不能用()表示,因为可能会出现这样的情况:
class Widget
{
private:
typedef int x;
int z(x);
};
这样的话,就会变为函数声明。
有关初始化的内容,如{} () = 等可以参考这篇文章:https://zhuanlan.zhihu.com/p/21102748
返回*this的成员函数
class Screen
{
public:
Screen &set(char);
Screen &move(pos r, pos c);
};
inline Screen &Screen::set(char c)
{
contents[cursor] = c;
return *this;
}
需要注意的是,set和move的返回值类型都是Screen对象的引用,意味着函数返回的是对象本身而非对象的副本
myScreen.move(4,0).set('#');
这样才能保证类似的连续操作是在同一个对象上执行的。
如果set和move返回Screen而非Screen&,则返回值将是*this的副本。上面的连续调用等价于
Screen temp = myScreen.move(4,0);
temp.set('#');
从const成员函数返回*this
如果成员函数是const的,那么它的this指针将是一个指向const的指针
而*this是const对象
如果成员函数返回*this,根据赋值规则,返回值类型应该是常量引用,即const Sales_data&
基于const的重载
通过区分成员函数是否是const的,我们可以对其进行重载
Screen &display(std::ostream &os){}
const Screen &display(std::ostream &os) const {}
两个display成员函数构成重载,这和this指针有关
类类型
注意区分类和对象
- 类用来定义一类事物
- 对象用来表示该类事物的一个具体实例
比如定义了Person类,可以抽象出人类共有的特征作为Person类的成员,比如眼睛、手、脚作为数据成员,行走、吃饭、睡觉作为函数成员等,那么具体到某一个人就是类的对象了
Sales_data item1;
class Sales_data item1;
以上两种方式都可以用来创建对象(第2种方式是由c语言继承而来)
类的声明
和函数类似,我们也可以仅声明类而暂时不定义它,这种声明称作前向声明
class Screen;
这个时候,我们只知道Screen是一个类类型,但是不知道它有哪些成员,因此使用的场景是有限制的。只能在类的级别使用,不能涉及成员操作。
一旦一个类的名字出现后,它就被认为是声明过了(但尚未定义),因此允许包含指向它自身类型的引用或指针
class Link_screen
{
Screen window;
Link_screen *next;
Link_screen *prev;
};
友元再探
类除了可以把普通函数作为友元外,还可以
- 把其他的类定义成友元
- 把其他类的成员函数定义成友元
令类作为友元
class Screen
{
friend class Window_mgr;
};
作出声明后,Window_mgr类的成员函数将可以访问Screen类的所有成员。
void Window_mgr::clear(ScreenIndex i)
{
Screen &s = screens[i];
s.contents = string(s.height * s.width, ' ');
}
令成员函数作为友元
class Screen
{
friend void Window_mgr::clear(ScreenIndex);
};
声明本身很简单,但是必须组织好程序的结构以满足依赖关系
- 首先定义Window_mgr类,其中声明clear函数,但是不能定义它。在clear使用Screen的成员之前必须先声明Screen
- 接下来定义Screen,包括对于clear的友元声明
- 最后定义clear,此时它才可以使用Screen的成员
参考P252。