一、类的const成员函数
被const修饰的类成员函数称为const成员函数。const修饰类成员函数,实际是修饰该成员函数隐含的this指针,使在该成员函数中不能对类的任何成员进行修改。
- 关键字 const 被放在成员函数的参数表和函数体之间,对于在类体之外定义的 const 成员函数,我们必须在定义和声明中同时指定关键字 const。
class Test {
public:
char get () const;
// ...
private:
char a;
// ...
};
char get () const
{
return a;
}
- 把一个修改类数据成员的函数声明为 const 是非法的。
注意:const 成员函数不能保证在调用成员函数期间类对象引用的所有东西都保持不变。程序员自己要保持警惕。 - 只有 const 成员函数才能被一个 const 类对象调用。构造函数和析构函数是两个例外,即使构造函数和析构函数不是 const 成员函数,const 类对象也可以调用它们。一个 const 类对象从构造完成时刻到析构开始时刻,这段时间内被认为是 const。
- const 成员函数可以被相同参数表的非 const 成员函数重载,这时类对象的常量性决定了调用哪个函数。
class Test {
public:
char get();
char get() const;
// ...
};
int main() {
const Test cs1;
Test cs2;
char ch = cs1.get(); // 调用 const 成员
ch = cs2.get(); // 调用非 const 成员
}
二、静态类成员
声明为static的类成员称为静态类成员。
(1)静态数据成员
- 静态数据成员被当作该类类型的全局对象。对于非静态数据成员,每个类对象都有自己的拷贝;而静态数据成员只有一份,由该类类型的所有对象共享访问。同全局对象相比,使用静态数据成员有两个优势:
- 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其他全局名字冲突的可能性。
- 可以实现信息隐藏 静态成员可以是 private 成员,而全局对象不能。
- 在类体中的数据成员声明前加上关键字 static,就使该数据成员成为静态的。一般的,静态数据成员必须在类外定义及初始化,定义时不添加static关键字且静态成员的名字必须被其类名限定修饰。
class Test {
//...
privte:
static double interestRate;
//...
};
double Test::interestRate = 0.09;
注意:静态数据成员的初始化不应该被放在头文件中,而应该放在含有类的非 inline 函数定义的文件中。
- 作为特例,有序型的 const 静态数据成员可以在类体中用一常量值初始化。
class Account {
// ...
private:
static const int nameSize = 16;
static const char name[nameSize];
};
const int Account::nameSize; // 必需的成员定义
const char Account::name[nameSize] = "Savings Account";
用常量值作初始化的有序类型的 const 静态数据成员是一个常量表达式,在类体内初始化一个const 静态数据成员时,该成员必须仍然要被定义在类定义之外。
- 在类的成员函数中可以直接访问该类的静态数据成员,而不必使用成员访问操作符。在非成员函数中,我们必须以两种方式之一访问静态数据成员:
- 使用成员访问操作符
- 用被类名限定修饰的名字直接访问。因为静态成员不是全局对象 所以我们不能在全局域中找到它
- 静态数据成员的惟一性本质(独立于类的任何对象而存在的惟一实例)使它能够以独特的方式被使用,而这些方式对于非 static 数据成员来说是非法的:
- 静态数据成员的类型可以是其所属类 而非 static 数据成员只能被声明为该类的对象的指针或引用。
class Test {
public:
// ...
private:
static Test mem1; // ok
Test *mem2; // ok
Test mem3; // 错误
};
- 静态数据成员可以被作为类成员函数的缺省实参 而非 static 成员不能。
extern int var;
class Test {
private:
int var;
static int num;
public:
// 错误: 被解析为非 static 的 Test::var
// 没有相关的类对象
int mem1( int = var );
// ok: 解析为 static 的 Test::num
// 无需相关的类对象
int mem2( int = num );
// ok: int var 的全局实例
int mem3( int = ::var );
};
(2)静态成员函数
- 静态成员函数的声明除了在类体中的函数声明前加上关键字 static 以及不能声明为 const 或 volatile 之外,与非静态成员函数相同,出现在类体外的函数定义不能指定关键字 static。
class Account {
public:
static void raiseInterest( double incr );
private:
static double _interestRate;
};
inline void Account::raiseInterest( double incr )
{
_interestRate += incr;
}
-
静态成员函数没有隐藏的this指针,不能访问任何非静态成员,在静态成员函数中隐式或显式地引用 this 指针都将导致编译时刻错误。
-
可以用成员访问操作符点 . 和箭头 -> 为一个类对象或指向类对象的指针调用静态成员函数,也可以用限定修饰名直接访问或调用静态成员函数,而无需声明类对象。
-
静态成员函数不可以调用非静态成员函数,非静态成员函数可以调用类的静态成员函数。
三、嵌套类
-
一个类可以在另一个类中定义,这样的类被称为嵌套类。嵌套类是其外围类的一个成员,嵌套类的定义可以出现在其外围类的公有,私有或保护区中。
-
嵌套类的名字在其外围类域中是可见的,但是在其他类域或名字空间中是不可见的。
class Node { /* ... */ };
class Tree {
public:
// Node 被封装在 Tree 的域中
// 在类域中 Tree::Node 隐藏了 ::Node
class Node {...};
// ok: 被解析为嵌套类: Tree::Node
Node *tree;
};
// Tree::Node 在全局域中不可见
// Node 被解析为全局的 Node 声明
Node *pnode;
-
除非外围类被声明为嵌套类的友元,否则它没有权利访问嵌套类的私有成员;嵌套类也没有任何特权访问其外围类的私有成员。
-
当我们没有在嵌套类体内以 inline 形式定义嵌套类的成员函数时,我们就必须在最外围的类之外定义这些成员函数。同样的,如果嵌套类声明了一个静态成员,那么它的定义也要放在全局域中。
class Tree {
public:
// ...
private:
class Node {
public:
static int num;
Node( int val = 0 );
// ...
};
};
// 错误: Node 不在全局域中
Node::Node( int val ) { ... } // error
// 用外围类名限定修饰嵌套类名
Tree::Node::Node( int val ) { ... }
int Tree::Node::num = 1024;
- 嵌套类也可以被定义在其外围类之外。在全局定义中,嵌套类的名字必须由其外围类的名字限定修饰,且在外围类体内的嵌套类的声明不能省略。
class Tree {
public:
// ...
private:
// 这个声明是必需的
class Node;
};
// 用外围类名限定修饰嵌套类名
class Tree::Node {
public:
Node( int val = 0 );
Node *next;
int value;
};
注意:在嵌套类的定义被看到之前 我们只能声明嵌套类的指针和引用
- 嵌套类可以直接访问外围类的静态成员,枚举成员(假定这些成员是公有的)。但嵌套类不能直接访问其外围类的非静态成员,即使这些成员是公有的,任何对外围类的非静态成员的访问都要求通过外围类的指针,引用或对象来完成。
class Tree {
public:
int init( int );
private:
class Node {
public:
Node( int val = 0 );
void mf( const Tree & );
int value;
int memb;
};
};
Tree::Node::Node( int val )
{
// Tree::init() 是类 Tree 的非静态成员
// 必须通过 Tree 类型的对象或指针来使用
value = init( val ); // error: 非法使用 init
}
//成员 init() 是针对函数实参指定的对象而被调用的
void Tree::Node::mf( const Tree &il ) {
memb = il.init(); // ok: 通过引用调用 init()
}
-
在嵌套类域中的名字解析
(1)被用在嵌套类的定义中的名字(除了 inline 成员函数定义中的名字和缺省实参的名字之外)其解析过程如下:
1 考虑出现在名字使用点之前的嵌套类的成员声明。
2 如果第 1 步没有成功 则考虑出现在名字使用点之前的外围类的成员声明。
3 如果第 2 步没有成功 则考虑出现在嵌套类定义之前的名字空间域中的声明。
如果在全局域中,在外围域之外定义嵌套类,则外围类的所有成员都已经被声明完毕,因而编译器将考虑其所有声明。
(2)被用在嵌套类的成员函数定义中的名字,其解析过程如下:
1 首先考虑在成员函数局部域中的声明。
2 如果第 1 步没有成功 则考虑所有嵌套类成员的声明。
3 如果第 2 步没有成功 则考虑所有外围类成员的声明。
4 如果第 3 步没有成功 则考虑在成员函数定义之前的名字空间域中出现的声明。
注意:只有在名字解析成功之后 编译器才会检查访问许可和类型兼容性。 -
sizeof(外部类)=外部类,和内部类没有任何关系。