7.3 类的其他特性
7.3.1 类成员再探
class Screen {
public:
typedef string::size_type pos;
// using pos = string::size_type
private:
pos cursor = 0;
pos height = 0, width = 0;
string contents;
};
这里定义了一个screen
类,pos
是一个别名,类似于unsigned
Screen类的成员函数
class Screen {
public:
typedef string::size_type pos;
Screen() = default;
Screen(pos h, pos w, char c)
: height(h), width(w), contents(h * w, c) {}
char get() const { return contents[cursor]; } // 隐式内联
inline char get(pos, pos) const; // 显式内联
Screen& move(pos, pos); // 能在之后被设为内联
};
因为已经提供了一个构造函数了,编译器就不会生成默认构造函数,如果还是需要默认构造函数,就需要显式地声明出来
令成员作为内联函数
定义在类内部的成员函数是自动
inline
的
char Screen::get(pos r, pos c) const {
pos row = r * width;
return contents[row + c]; // 获取指定位置的字符
}
inline // 可以在函数的定义处指定inline
Screen& Screen::move(pos r, pos c) {
pos row = r * width;
cursor = row + c; // 移动光标到指定位置
return *this;
}
重载成员函数
只要成员函数在参数的数量和/或类型上有所区别,就可以实现重载
Screen myscreen;
char ch = myscreen.get(); // 调用 Screen::get()
ch = myscreen.get(0, 0); // 调用 Screen::get(pos, pos)
可变数据成员
有时会希望修改某个数据成员,即使是在一个const
成员函数里面,可以通过在变量声明中加入mutable
做到
class Screen {
public:
void some_member() const;
private:
mutable size_t access_ctr; // 即使在一个const对象内也能修改
};
void Screen::some_member() const {
++access_ctr;
}
类数据成员的初始值
定义一个窗口管理类,包含一个Screen
的vector
,并增加一个默认初始化元素
class Window_mgr {
private:
vector<Screen> screens{Screen(24, 80, '*')};
};
当我们提供一个类内初始值时,必须以
=
或者{}
表示
7.3.2 返回*this的成员函数
增加修改当前位置和指定位置字符的成员函数
class Screen {
public:
Screen& set(char);
Screen& set(pos, pos, char);
};
inline Screen& Screen::set(char c) {
contents[cursor] = c; // 修改当前位置的字符
return *this;
}
inline Screen& Screen::set(pos r, pos c, char ch) {
contents[r * width + c] = ch; // 修改指定位置的字符
return *this;
}
因为是返回对象本身,所以可以连续调用函数
myscreen.move(4, 0).set('#');
从const成员函数返回*this
可以定义一个display
函数输出,可以指定为const
,那么返回值就应该是const Screen&
,因此不能再去调用非const
的函数
Screen myScreen;
myScreen.display(cout).set('*'); // Err
尽管myScreen
是个非常量对象,但display
返回的是常量引用,所以就不能调用set()
对非共有变量进行修改
const
成员函数如果以引用的形式返回*this
,那么它的返回类型将是常量引用基于const的重载
class Screen {
public:
Screen& display(ostream& os) {
do_display(os);
return *this;
}
const Screen& display(ostream& os) const {
do_display(os);
return *this;
}
private:
void do_display(ostream& os) const { os << contents; }
};
在某个对象上调用display
时,该对象是否是const
决定了应该调用display
的哪个版本
Screen myScreen(5, 3, '$');
const Screen blank(5, 3, '&');
myScreen.set('%').display(cout); // 调用非常量版本
blank.display(cout); // 调用常量版本
7.3.3 类类型
每个类定义了唯一的类型,对于两个类来说,即使他们的成员完全一样,也是不同的类型
类的声明
可以仅仅声明类而暂时不定义
class Screen; // 类的声明
在声明之后定义之前是不完全类型
可以定义指向这种类型的指针或引用,也可以声明(不能定义)以不完全类型作为参数或者返回类型的函数
一旦一个类的名字出现后,就被认为是声明过了,因此类允许包含指向它本身的引用或指针
class Link_screen {
Screen window;
Link_screen *next;
Link_screen *prev;
};
7.3.4 友元再探
如果Window_mgr
类需要改变Screen
对象的值,需要在Screen
内将其设为友元
class Screen {
friend class Window_mgr;
};
这样Window_mgr
的成员就能访问Screen
类的私有部分
class Window_mgr {
public:
using ScreenIndex = vector<Screen>::size_type;
void clear(ScreenIndex);
private:
vector<Screen> screens{Screen(24, 80, '*')};
};
void Window_mgr::clear(ScreenIndex i) { // 要清空的Screen的下标
Screen& s = screens[i]; // 指向要清空的那个Screen
s.contents = string(s.height * s.width, ' ');
}
友元关系不存在传递性,如果
Window_mgr
有自己的友元,那他们并不能访问Screen
的成员
令成员函数作为友元
可以只让clear
函数作为Screen
的友元函数,注意clear
必须在Screen
类之前被声明,顺序是
- 首先声明
Screen
类,不用定义- 然后定义
Window_mgr
类,声明clear
函数- 再定义
Screen
,包括对于clear
友元声明- 最后定义
clear
// 1. 先声明Screen,因为Window_mgr中用到了
class Screen;
// 2. 定义Window_mgr类,声明clear函数
class Window_mgr {
public:
using ScreenIndex = vector<Screen>::size_type;
void clear(ScreenIndex);
private:
vector<Screen> screens; // 这里就不能调用Screen的构造函数了
};
// 3. 定义Screen,包括对于clear友元声明
class Screen
{
friend void Window_mgr::clear(ScreenIndex);
};
// 4. 最后定义clear
void Window_mgr::clear(ScreenIndex i) {
Screen& s = screens[i]; // 指向要清空的那个Screen
s.contents = string(s.height * s.width, ' ');
}
函数重载和友元
要把重载的函数声明为友元,需要逐个声明
友元声明和作用域
可以在类的内部定义友元函数,但也需要在外部声明使得友元函数可见