第七章_类_7.3 类的其他特性

本文探讨了类Screen和Window_mgr的设计,涉及类型成员、内联成员函数、返回*this、类类型、友元关系以及类声明。重点讲解了如何定义类结构、设置成员访问权限和重载,以及友元如何增强类的功能交互。
摘要由CSDN通过智能技术生成

7.3 类的其他特性

7.3.1 类成员再探

定义一对相互关联的类 Screen 和 Window_mgr.

定义一个类型成员

Screen 表示显示器中的一个窗口。每个 Screen 包含一个用于保存 Screen 内容的 string 成员和三个 string::size_type 类型的成员,分别表示光标的位置以及屏幕的高和宽。

class Screen{
public:
    typedef std::string::size_type pos;
private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
};

还可以等价的使用类型别名

class Screen{
public:
    using pos = std::string::size_type;
private:
    pos cursor = 0;
    pos height = 0, width = 0;
    std::string contents;
};

注意,用来定义类型的成员必须先定义后使用。类型成员通常出现在类开始的地方。

Screen 类的成员函数

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

class Screen{
public:
    typedef std::string::size_type pos;
public:
    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;
};

令成员作为内联函数

定义在类的内部的成员函数是自动inline的。

可以在类的外部使用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 {	// 已经在类的内部声明成 inline
    pos row = r * width;				// 计算行的位置
    return contents[row + c];			// 返回给定列的字符
}

最好只在类外部定义的地方说明inline,这样可以使类更容易理解。

在头文件中定义inline函数,inline成员函数应该与相应的类的定义在同一个头文件中

重载成员函数

成员函数可以被重载,只要函数之间在参数的数量和(或)类型上有所区别就行。

成员函数的函数匹配过程与非成员函数非常类似。

可变数据成员

通过在变量的声明中加入mutable关键字来修改类的某个数据成员,即使是在一个const成员函数内.

一个**可变数据成员(mutable data member)**永远不会是const,即使它是const对象的成员。因此一个 const 成员函数可以改变一个可变成员的值。举例如下:

class Screen {
public:
    void some_member() const;
private:
    mutable size_t access_ctr;		// 即使在一个 const 对象内也能被修改
    // 其他成员与之前的版本一致
};

void Screen::some_member() const {  
    ++access_ctr;					// 保存一个计数值,用于记录成员函数被调用的次数
    // 该成员需要完成的其他工作
}

access_ctr是一个可变成员,因此任何成员函数,包括 const 函数在内都能改变它的值。

类数据成员的初始值

定义一个窗口管理类 Window_mgr 表示显示器上的一组 Screen。这个类包含一个 Screen 类型的 vector,每个元素表示一个特定的 Screen。

若希望 Window_mgr 类开始时总是拥有一个默认初始化的 Screen,最好的方式是把这个默认值声明成一个类内初始值。

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

当我们初始化类类型的成员时,需要为构造函数传递一个符合成员类型的实参。上例中,使用了一个单独的元素值对 vector 成员执行了列表初始化。

当我们提供一个类内初始值时,必须以符号=(初始化数据成员)或者花括号(直接初始化类)表示。

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;			// 将 this 对象作为左值返回
}

inline Screen& Screen::set(pos r, pos col, char ch) {
    contents[r * width + col] = ch;	// 设置给定位置的新值
    return *this;					// 将 this 对象作为左值返回
}

set 成员的返回值是调用 set 的对象的引用。返回引用的函数是左值的,意味着这些函数返回的对象本身而非对象的副本。

进行如下操作

// 把光标移动到一个指定的位置,然后设置该位置的字符值
myScreen.move(4, 0).set('#');

这些操作是在同一个对象上执行。上述语句等价于

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

如果move set返回的是Screen而非Screen&那么等价于

Screen temp = myScreen.move(4,0);	// 对返回值进行拷贝
temp.set('#');						// 不会改变 myScreen 的 contents

move的返回值将是*this的副本,因此调用set只能改变临时副本,而不能改变myScreen的值.

*从 const 成员函数返回 this

添加一个 display 的操作,负责打印 Screen 的内容。我们希望这个函数能够和 move 以及 set 出现在同一表达式中,因此 display 操作应该返回执行它的对象的引用。

因为显示一个 Screen 并不需要改变它的内容,因此令 display 为一个 const 成员。由此推断,display 的返回类型应该是 const Sale_data&。然而如果真的令 display 返回一个 const 的引用,则我们不能把 display 嵌入到一组动作的序列中去。

Screen myScreen;
myScreen.display(cout).set('*');	// 错误,无法 set 一个常量对象

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

基于 const 的重载

通过区分成员函数是否是 const 的,我们可以对其进行重载。

非常量版本的函数对于常量是不可用的,所以只能在一个常量对象上调用 const 成员函数。

虽然可以在非常量对象上调用常量版本或非常量版本,对于 display 来说显然非常量版本是一个更好的匹配。

下例中,定义一个名为 do_display 的私有成员,由它负责打印 Screen 的实际工作,所有的 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:
    // 负责显示 Screen 的内容
    void do_display(std::ostream &os) const { os << contents; }
};

当一个成员调用另外一个成员时,this 指针在其中隐式地传递。

当某个对象调用 display 时,对象是否是 const 决定了调用版本

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

7.3.3 类类型

每个类定义了唯一的类型。对于两个类来说,即使他们的成员完全一样,这两个类也是两个不同的类型。

即使两个类的成员列表完全一致,他们也是不同的类型。对于一个类来说,它的成员和其他任何类(或者其他任何作用域)的成员都不是一回事。

可以把类名作为类型的名字使用,从而直接指向类类型。或者也可以把类名跟在关键字 class 或 struct 后面

Sales_data item1;			// 默认初始化 Sales_data 类型的对象
class Sales_data item1;		// 一条等价的声明

类的声明

可以仅声明类而暂时不定义它:class Screen

这种声明有时被称作前向声明(forward declaration),它向程序中引入了名字 Screen 并且指明 Screen 是一种类类型。

在类声明之后定义之前是一个不完全类型(incomplete type)。

不完全类型只能在非常有限的情景下使用:可以定义指向这种类型的指针或引用,也可是声明(不能定义)以不完全类型作为参数或者返回类型的函数。

对于一个类来说,在创建它的对象之前该类必须被定义过,而不能仅仅被声明。

只有当类全部完成后类才算被定义,所以一个类的成员类型不能是该类自己。然而,一旦一个类的名字出现后,它就是被声明过了,因此类允许包含指向它自身类型的引用或指针:

class Link_screen {
    Screen window;
    Link_screen *next;
    Link_screen *prev;
};

7.3. 4 友元再探

类可以把普通的非成员函数定义成友元,可以把其他的类定义成友元,也可以把其他类的成员函数定义成友元。此外,友元函数能定义在类的内部,这样的函数是隐式内联的。

类之间的友元关系

假设 Window_mgr 类添加一个名为 clear 的成员,负责把一个指定的 Screen 的内容都设为空白。那么 clear 需要访问 Screen 的私有成员,那么 Screen 需要把 Window_mgr 指定成它的友元。

class Screen {
    // Window_mgr 的成员可以访问 Screen 类的私有部分
    friend class Window_mgr;
};

如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员。

class Window_mgr {
public:
    // 窗口中每个屏幕的编号
    using ScreenIndex = std::vector<Screen>::size_type;
    // 按照编号将指定的 Screen 重置为空白
    void clear(ScreenIndex);
private:
	std::vector<Screen> screens{Screen(24, 80, ' ')};  
};

void Window_mgr::clear(ScreenIndex i) {
    // s 是一个 Screen 的引用,指向我们想清空的那个屏幕
    Screen &s = screens[i];
    // 将那个选定的 Screen 重置为空白
    s.contents = string(s.height * s.width, ' ');
}

每个类负责控制自己的友元类或友元函数。友元不存在传递性。

令成员函数作为友元

还可以只为 clear 提供访问权限。当把一个成员函数声明成友元时,必须明确指出该成员函数属于哪个类。

class Screen {
    // Window_mgr::clear 必须在 Screen 类之前被声明
    friend void Window_mgr::clear(ScreenIndex);
  	// Screen 类的剩余部分  
};

要想令某个成员函数作为友元,必须仔细组织程序的结构以满足声明和定义的彼此依赖关系。

函数重载和友元

如果一个类想把一组重载函数声明成它的友元,必须对这组函数中每一个分别声明。

// 重载的 storeOn 函数
extern std::ostream& storeOn(std::ostream &, Screen &);
extern BitMap& storeOn(BitMap &, Screen &);
class Screen {
    // storeOn 的 ostream 版本能访问 Screen 对象的私有部分
   friend std::ostream& storeOn(std::ostream &, Screen &); 
}

友元声明和作用域

类和非成员函数的声明不是必须在它们的友元声明之前。当一个名字第一次出现在一个友元声明中时,我们隐式地假定该名字在当前作用域中可见的。友元本身不一定真的声明在当前作用域中。

即使仅仅是用声明友元的成员调用该友元函数,它也必须是被声明过的。

struct X{
    friend void f() { /* 友元函数可以定义在类的内部*/}
    X() { f(); } 				// 错误:f 还没有被声明
    void g();
    void h();
}
void X::g() { return f(); } 	// 错误,f 还没有被声明
void f(); 						// 声明那个定义在 X 中的函数
void X::h() { return f(); } 	// 正确:现在 f 的声明在作用域中了

友元声明的作用是影响访问权限,它本身并非普通意义上的声明。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值