定义一个类型成员
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(); } //正确
};