类成员再探

定义一个类型成员

class Screen
{
public:
	typedef std::string::size_type pos;
	// using pos = std::string::size_type; //等价声明
private:
	pos cursor = 0;
	pos height = 0, width = 0;
	std::string contents;
};

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

成员作为内联函数

在类中,常有一些规模比较小的函数适合被声明成内联函数。定义在类内部的成员函数 自动 inline

可以在内部把 inline 作为声明的一部分显示的声明成员函数,同样也可以在类的外部用inline关键字修饰函数的定义。

虽然无须在声明和定义的地方同时说明 inline,但这么做是合法的,不过,最好只在类外部定义的地方说明 inline,这样可以使类更容易理解。

重载成员函数

和非成员函数一样,成员函数也可以被重载。成员函数的匹配过程与非成员函数一样。

可变数据成员

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

一个可变数据成员永远不会是const,即使它是const对象的成员。因此,一个const成员函数可以修改一个可变成员的值。

class Screen
{
public:
	void some_member() const;

private:
	mutable size_t access_ctr;
};

void Screen::some_member() const
{
	++access_ctr;  //尽管some_member是一个const成员函数,仍然能够修改可变数据成员
}

类数据成员的初始值

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

返回 *this 的成员函数

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

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

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;}
}

display 调用函数 do_display 时,它的 this 指针将隐式地传递给 do_display 。而当 display的非常量版本调用 do_display 时,它的 this指针将隐式的转换成指向常量的指针。

do_display 完成后,display 函数各自返回解引用所得的对象,在非常量版本中,this 指向一个非常量对象,因此 display 返回一个普通的引用,而 const 成员则返回一个常量引用。

当我们在某个对象上调用 display 时,该对象是否是 const 决定了应该调用哪个版本的 display 函数:

Screen screen1(5,3);
const Screen screen2(5,3);
screen1.display();	//调用非常量版本
screen2.display();	//调用常量版本

建议:对于公共代码使用私有的功能函数

上面的 display函数为什么要费力单独定义呢:

  • 一个愿望是避免在多处使用相同的代码。
  • display 函数的功能随着发展变得更加复杂,此时把相应的操作写在一处便于统一修改。
  • 在开发过程中有可能给 display 函数添加一些打印信息,而这些信息在最终版本是要删除的,将其在一处添加和删除更容易。
  • 这个额外的函数定义在类内部,默认内联,所以不会增加开销。
类类型

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

struct First
{
	int memi;
	int getMem();
}

struct second
{
	int memi;
	int getMem();
}

First obj1;
Second obj2 = obj1; //错误,obj1和obj2的类型不同

可以把类名作为类型名称直接使用,也可以把类名跟在关键字 class 或 struct 的后面:

Sales_data item;
class Sales_data item; //等价声明

类的声明

可以声明一个类而暂时不定义类,这种声明称为前向声明,它向程序中引入了这个类。

class Screen;

对于 Screen 来说,在它声明之后定义之前是一个不完全类型,也就是说我们已知 Screen 是一个类类型但是不清楚它包含了哪些成员。

不完全类型的使用情景:

  • 可以定义指向这种类类型的指针和引用。
  • 也可以声明(不能定义)以不完全类型作为参数或返回类型的函数。

对于一个类来说,在创建对象之前该类必须是被定义过的,而不能仅仅只是声明,否则编译器无法了解这样的对象需要占用多少存储空间。

一个类的成员类型不能是其本身,但是可以包含指向其自身类型的引用或指针。

class Link_screen{
	screen window;
	Link_screen *next;
	Link_screen *prev;
};
友元再探

类除了可以将非成员函数定义成友元函数之外,还可以将其他类定义成友元,也可以把其他类的函数定义成友元。

友元函数能在定义在类的内部,这样的函数隐式内联。

类之间的友元关系

class Screen{
	friend class Window_mgr;
};

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

但是需要注意的是,友元关系不具有传递性,也就是说如果 Window_mgr 有自己的友元,则这些友元并不具有访问 Screen 的特权。

令成员函数作为友元

class Screen{
	friend void Window_mgr::clear(ScreenIndex);
};
  • 必须指明友元函数所属的类。
  • Window_mgr::clear 函数必须在类 Screen 之前被声明。

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

  • 首先定义 Window_mgr 类,其中声明 clear 函数,但是不定义它,因为 clear 函数将用到 Screen 的成员,在此之前必须先定义 Screen
  • 定义 Screen,包括对 clear 友元的声明。
  • 最后定义 clear,此时可以使用 Screen 的成员。

函数重载与友元

尽管重载函数的名字不同,但是他们仍然是不同的函数,因此如果想把一个类的一组重载函数声明为它的友元,需要对这组重载函数逐一声明。

友元声明和作用域

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

甚至就算在类的内部定义该函数,我们也必须在类外部提供相应的声明从而使该函数可见。

struct X {
	friend void f() {/*友元函数可以在类的内部定义*/}
	X() { f(); }	//错误,f没有被声明
};
void f(); // 声明那个定义在X中的函数

struct X {
	friend void f() {/*友元函数可以在类的内部定义*/}
	X() { f(); }	//正确
};