构造函数
创建一个类类型的对象时,编译器会自动使用一个构造函数(第 2.3.3 节)来初始化该对象。构造函数是一个特殊的、与类同名的成员函数,用于给每个数据成员设置适当的初始值。
构造函数一般就使用一个构造函数初始化列表(第 7.7.3 节),来初始化对象的数据成员:
Sales_item(): units_sold(0), revenue(0.0) { }
成员函数
在类内部,声明成员函数是必需的,而定义成员函数则是可选的。在类内部定义的函数默认为 inline。
const 成员不能改变其所操作的对象的数据成员。const 必须同时出现在声明和定义中,若只出现在其中一处,就会出现一个编译时错误。
封装和抽象
类背后蕴涵的基本思想是数据抽象和封装。
大多数标准库类型,如vector,既是封装的,又是抽象的。而数组在概念上类似于 vector,但既不是抽象的,也不是封装的。
并非所有类型都必须是抽象的。标准库中的 pair 类就是一个具体类而不是抽象类。具体类会暴露而非隐藏其实现细节
类定义
1除了定义数据和函数成员之外,类还可以定义自己的局部类型名字。如果为 std::string::size_type 提供一个类型别名,那么 Screen 类将是一个更好的抽象:
class Screen {
public:
// interface member functions
typedef std::string::size_type index;
private:
std::string contents;
index cursor;
index height, width;
};
2可以显式地将成员函数声明为 inline:在声明和定义处指定 inline 都是合法的。如下
class Screen {
public:
typedef std::string::size_type index;
char get() const { return contents[cursor]; }
inline char get(index ht, index wd) const;//声明时指定inline
index get_cursor() const;
};
char Screen::get(index r, index c) const
{
index row = r * width;
return contents[row + c];
}
inline Screen::index Screen::get_cursor() const//定义时指定inline
{
return cursor;
}
类对象的定义
定义对象时,将为其分配存储空间,但定义类型时不进行存储分配。
为什么类的定义分号结束?
类的定义分号结束,因为在类定义之后可以接一个对象定义列表。定义必须以分号结束:
class Sales_item { /* ... */ };
class Sales_item { /* ... */ } accum, trans;
隐含的形参 this
1成员函数具有一个附加的隐含形参,即指向该类对象的一个指针。
2在普通的非 const 成员函数中,this 的类型是一个指向类类型的 const 指针(第 4.2.5 节)。可以改变 this 所指向的值,但不能改变this 所保存的地址。在 const 成员函数中,this 的类型是一个指向 const 类类型对象的 const 指针。既不能改变 this 所指向的对象,也不能改变 this 所保存的地址。
3不能从 const 成员函数返回指向类对象的普通引用。const 成员函数只能返回 *this 作为一个 const 引用
基于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 嵌入到一个长表达式中时,将调用非 const 版本。当我们 display 一个 const 对象时,就调用 const 版本:
Screen myScreen(5,3);
const Screen blank(5, 3);
myScreen.set('#').display(cout); // calls nonconst version
blank.display(cout);
类的作用域
形参表和函数体处于类作用域中。
函数返回类型不一定在类的作用域中,因此需要完全限定名。
class Screen {
public:
typedef std::string::size_type index;
index get_cursor() const;
};
inline Screen::index Screen::get_cursor() const
{
return cursor;
}
构造函数初始化式
1类类型的数据成员总是在初始化阶段初始化,无论它是否在构造函数的初始化列表中显式初始化。初始化发生在计算阶段开始之前。
2也就是说,构造函数分两个阶段执行:(1)初始化阶段;(2)普通的计算阶段。计算阶段由构造函数函数体中的所有语句组成。
3有些成员必须在构造函数初始化列表中进行初始化: 没有默认构造函数的类类型的成员, const 或引用类型的成员。
4成员被初始化的次序与初始化列表中的次序无关,而是取决于定义成员的次序 。
5类类型的数据成员的初始化
初始化类类型的成员时,要指定实参并传递给成员类型的某个构造函数。例如,Sales_item 类可以使用任意一个 string 构造函数来初始化isbn
Sales_item(): isbn(10, '9'), units_sold(0), revenue(0.0) {}
默认构造函数(合成的默认构造函数)
1只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。
2合成的默认构造函数使用与变量初始化相同的规则来初始化成员。具有类类型的成员通过运行各自的默认构造函数来进行初始化。内置和复合类型的成员,如指针和数组,只对定义在全局作用域中的对象才初始化。当对象定义在局部作用域中时,内置或复合类型的成员不进行初始化。
Explicit关键字
当构造函数被声明 explicit 时,编译器将不使用它作为转换操作符
友元
友元可以是普通的非成员函数,或前面定义的其他类的成员函数,或整个类。将一个类设为友元,友元类的所有成员函数都可以访问授予友元关系的那个类的非公有成员。
static类成员
1非 static 数据成员存在于类类型的每个对象中。而static 数据成员独立于该类的任意对象而存在;每个 static 数据成员是与类关联的对象,并不与该类的对象相关联。
2类也可以定义 static 成员函数。static 成员函数没有 this 形参,它可以直接访问所属类的 static 成员,但不能直接使用非 static 成员。