12.1.4
因为只有当类定义体完成后才能定义类,因此类不能具有自身类型的数据成员。然而,只要类名一出现就可以认为该类已声明。因此,类的数据成员可以是指向自身类型的指针或引用:
class LinkScreen {
Screen window;
LinkScreen *next;
LinkScreen *prev;
};
12.2
基于成员函数是否为 const, 可以重载一个成员函数; 同样地,基于一个指针形参是否指向 const(第 7.8.4 节),可以重载一个函数。
可变数据成员
我们希望类的数据成员(甚至在 const 成员函数内)可以修改。这可以通过将它们声明为 mutable 来实现。
class Screen {
public:
// interface member functions
private:
mutable size_t access_ctr; // may change in a const members
// other data members as before
};
void Screen::do_display(std::ostream& os) const
{
++access_ctr; // keep count of calls to any member function
os << contents;
}
尽管 do_display 是 const,它也可以增加 access_ctr。该成员是可变成员,所以,任意成员函数,包括 const 函数,都可以改变 access_ctr 的值。
12.3
如果成员是在类的定义体之外定义的,但成员定义就好像它们是在类的作用域中一样。 在定义于类外部的成员函数中,形参表和成员函数体都出现在成员名之后。这些都是在类作用域中定义,所以可以不用限定而引用其他成员。与形参类型相比,返回类型出现在成员名字前面。如果函数在类定义体之外定义,则用于返回类型的名字在类作用域之外。如果返回类型使用由类定义的类型,则必须使用完全限定名。
class Screen {
public:
typedef std::string::size_type index;
index get_cursor() const;
};
inline Screen::index Screen::get_cursor() const
{
return cursor;
}
12.3.1. 类作用域中的名字查找
迄今为止,在我们所编写的程序中, 名字查找(寻找与给定的名字使用相匹配的声明的过程)是相对直接的。
1. 首先,在使用该名字的块中查找名字的声明。只考虑在该项使用之前声明的名字。2. 如果找不到该名字,则在包围的作用域中查找。
类成员声明的名字查找
1. 检查出现在名字使用之前的类成员的声明。2. 如果第 1 步查找不成功,则检查包含类定义的作用域中出现的声明以及出现在类定义之前的声明。
类成员定义中的名字查找
1. 首先检查成员函数局部作用域中的声明。(包括函数形参列表)
2. 如果在成员函数中找不到该名字的声明,则检查对所有类成员的声明。(对于类内部数据成员,即使先使用,然后才有声明,也是正确的)
3. 如果在类中找不到该名字的声明, 则检查在此成员函数定义之前的作用域中出现的声明。
如果想使用全局作用域中被遮掩的变量,使用作用域名::,::height(使用全局height)
当成员定义在类定义的外部时,名字查找的第 3 步不仅要考虑在 Screen类定义之前的全局作用域中的声明, 而且要考虑在成员函数定义之前出现的全局作用域声明:
class Screen {
public:
// ...
void setHeight(index);
private:
index height;
};
Screen::index verify(Screen::index);
void Screen::setHeight(index var) {
// var: refers to the parameter
// height: refers to the class member
// verify: refers to the global function
height = verify(var);
}
全局函数 verify 的声明在 Screen 类定义之前是不可见的。然而,名字查找的第 3 步要考虑那些出现在成员定义之前的外围作用域声明,并找到全局函数 verify 的声明。
typedef也有类似于变量定义覆盖的情况
12.4
构造函数不能声明为 const
12.4.1
初始化 const 或引用类型数据成员的唯一机会是构造函数初始化列表中。
12.4.4
explicit 关键字只能用于类内部的构造函数声明上。在类的定义体外部所做的定义上不再重复它:
class Sales_item {
public:
// default argument for book is the empty string explicit Sales_item(const std::string &book = ""):
isbn(book), units_sold(0), revenue(0.0) { }
explicit Sales_item(std::istream &is);
// as before
};
// error: explicit allowed only on constructor declaration in class header
explicit Sales_item::Sales_item(istream& is)
{
is >> *this; // uses Sales_iteminput operator to read the members
}
类成员的显式初始化
对于没有定义构造函数并且其全体数据成员均为 public 的类,可以采用与初始化数组元素相同的方式初始化其成员:
struct Data {
int ival;
char *ptr;
};
// val1.ival = 0; val1.ptr = 0
Data val1 = { 0, 0 };
// val2.ival = 1024;
// val2.ptr = "Anna Livia Plurabelle"
Data val2 = { 1024, "Anna Livia Plurabelle" };
根据数据成员的声明次序来使用初始化式。例如,因为 ival 在 ptr 之前声明,所以下面的用法是错误的:
// error: can't use "Anna Livia Plurabelle" to initialize the int ival
Data val2 = { "Anna Livia Plurabelle" , 1024 };
显式初始化类类型对象的成员有三个重大的缺点 :
1. 要求类的全体数据成员都是 public。
2. 将初始化每个对象的每个成员的负担放在程序员身上。这样的初始化是乏味且易于出错的,因为容易遗忘初始化式或提供不适当的初始化式。
3. 如果增加或删除一个成员,必须找到所有的初始化并正确更新。
12.6
非 static 数据成员存在于类类型的每个对象中。不像普通的数据成员,static 数据成员独立于该类的任意对象而存在;每个 static 数据成员是与类关联的对象,并不与该类的对象相关联
因此static数据成员的类型可以是该成员所属的类类型。非 static 成员被限定声明为其自身类对象的指针或引用:
class Bar {
public:
// ...
private:
static Bar mem1; // ok
Bar *mem2; // ok
Bar mem3; // error
};
static数据成员在类定义体外部定义,且只定义一次,一般将它放在类的实现文件(源文件)中。在类的外部定义 static 成员时,无须重复指定 static 保留字,该保留字只出现在类定义体内部的声明处。不像普通数据成员,static 成员不是通过类构造函数进行初始化,而是应该在定义时进行初始化。
class Account {
public:
// interface functions here
void applyint() { amount += amount * interestRate; }
static double rate() { return interestRate; }
static void rate(double); // sets a new rate
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
};
double Account::interestRate = initRate();
像其他成员定义一样,一旦成员名出现,static 成员的就是在类作用域中。因此, 我们可以没有限定地直接使用名为 initRate 的 static成员函数,作为 interestRate 初始化式。注意,尽管 initRate 是私有的,我们仍然可以使用该函数来初始化 interestRate。像任意的其他成员定义一样,interestRate 的定义是在类的作用域中,因此可以访问该类的私有成员。
static数据成员可用作默认实参:
class Screen {
public:
// bkground refers to the static member
// declared later in the class definition
Screen& clear(char = bkground);
private:
static const char bkground = '#';
};
然而对于static const只要初始化式是一个常量表达式,整型static const数据成员就可以在类的定义体中进行初始化:
class Account {
public:
static double rate() { return interestRate; }
static void rate(double); // sets a new rate
private:
static const int period = 30; // interest posted every 30 days
double daily_tbl[period]; // ok: period is constant expression
};
const static 数据成员在类的定义体中初始化时,该数据成员仍必须在类的定义体之外进行定义(视编译器而定)。
非 static 数据成员不能用作默认实参,因为它的值不能独立于所属的对象而使用。
static 成员是类的组成部分但不是任何对象的组成部分,因此,static 成员函数没有 this 指针。 static 成员函数不能被
声明为 const。毕竟,将成员函数声明为 const 就是承诺不会修改该函数所属的对象。static 成员函数也不能被声明为虚函数
static成员函数可以直接访问所属类的 static 成员,但不能直接使用非 static 成员
使用 static 成员而不是全局对象有三个优点:
1. static 成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突2. 可以实施封装。static 成员可以是私有成员,而全局对象不可以
3. 通过阅读程序容易看出 static 成员是与特定类关联的。这种可见性可清晰地显示程序员的意图