7.4 Class Scope
----------------------------------------------------------------
void Window_mgr::clear(ScreenIndex i)
{
Screen &s = screens[i];
s.contents = string(s.height * s.width, ' ');
}
此函数中的Window_mgr,说明这个函数都在它的scope里。
如果函数的返回值要使用Window_mgr中的类型的话,必须加上Window_mgr的specify。
比如:
Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s);
编译器只有在找到所以类的成员声明之后,才会去处理成员函数的定义。
编译器会现在class里面找,然后在enclosing scope里找,再找不到就会报错。
在类的内部和外部,变量可以重名,但是typedef的定义不能够重名,否则就会报错。
编译器如何处理成员函数内部的名称?
• 在成员函数内部寻找,且仅仅在使用这个名称之前的代码。
• 在类里面找。
• 在这个类前面的scope中找。
如果我们想使用类成员的名称,而不是成员函数里的本地名称,可以通过this或者类名访问:
this->height;
Screen::height;
如果我们想使用类外面的全局名称,就使用"::"这个符号,它意味着全局名称:
::height
7.5 Constructors Revisited
----------------------------------------------------------------
如果在定义一个变量时,是先定义再赋值,那么这个变量会先被赋为默认值,
因此不如在初始化的时候顺便把值给初始化了:
string foo = "Hello World!";
构造函数也是如此,与其在内部赋值,不如在它的参数列表里面指定其成员的初始值。
const类型的变量/引用/指针,以及没有默认构造函数的类,必须在参数列表里被初始化。
在构造函数的参数列表里,成员初始化的顺便并不是列表的顺序,而是它们在类中的存储顺序。
因此,最好按照类中的存储顺序来初始化成员变量,并且不要使用成员变量来初始化另一个成员变量。
Sales_data(std::string s = ""): bookNo(s) { }
Sales_data(std::string s, unsigned cnt, double rev):
bookNo(s), units_sold(cnt), revenue(rev*cnt) { }
Sales_data(std::istream &is) { read(is, *this); }
第一个函数是默认的构造函数,因为它提供的参数列表和默认的参数列表并没有区别。
我们也可以使用空列表来调用它。
----------------
Delegating Constructors
使用其他构造函数来形成自己的构造函数:
Sales_data(std::string s, unsigned cnt, double price):
bookNo(s), units_sold(cnt), revenue(cnt*price) {}
Sales_data(): Sales_data("", 0, 0) {}
Sales_data(std::string s): Sales_data(s, 0,0) {}
Sales_data(std::istream &is): Sales_data() {read(is, *this);}
此时,被delegating的构造函数的参数列表及函数体都会被执行,它们执行完了之后,
delegating函数的函数体会接着执行。
----------------
The Role of the Default Constructor
什么时候会用到默认的构造函数?
• 在block scope中定义nonstatic变量,且没有初始化它。
• 类包含有synthesize default constructor的成员类。
• 当构造函数没有显式的初始化每一个成员变量时。
• 当没有提供完整的数组初始化个数时。
• 当定义一个本地的static对象且没有初始化时。
• 当初始化类似vector的变量时,T()使用了类作为其成员。
----------------
Implicit Class-Type Conversions
不仅仅是build-in类型可以进行转换,class类型也可以。
单参数的构造函数,就代表了一个类型转换,因此这类函数也称为converting constructors。
但要注意的是,转换函数只能接受一次转换,如果构造函数接收string作为参数,那就不能使用:
combine("9-999-99999");
如果在构造函数前加上explicit关键字,意味着我们不能使用它来行类型的隐式转换。
explicit只需要用在单参数的converting constructors上;
explicit只能用在类的内部,不能在类的外部使用;
explicit只能用在直接的initialization上:
Sales_data item1(null_book); // OK
Sales_data item1 = null_book; // ERROR
----------------
Aggregate Classes
我们可以直接访问它的成员:
struct Data {
int ival;
string s;
};
Data val1 = {0, "Anna"};
这种直接访问类成员的方式看其来很简单,实际上有很多缺点:
• 所有的成员变量必须是public。
• 类的使用者必须负责进行冗长且乏味的赋值操作。
• 一旦类修改,所有的赋值操作都要修改。
----------------
Literal Classes
一个aggregate class,如果它的成员变量都是literal类型的,就可以称它literal class。
一个非aggregate class,满足如下要求的也是literal class:
• 所有的成员都是literal类型;
• 至少有一个constexpr构造函数;
• in-class的初始化必须是constant表达式,或者成员类有constexpr构造函数;
• 必须使用默认的析构函数。
7.6 Class Members
----------------------------------------------------------------
static关键字,使得类的成员不再属于某个单独的类对象,而是属于整个类。
被static修饰的成员,可以是const/引用/数组/clas或其他。
被static修饰的成员,可以是private或者public。
class Account {
public:
void calculate() { amount += amount * interestRate; }
static double rate() { return interestRate; }
static void rate(double);
private:
std::string owner;
double amount;
static double interestRate;
static double initRate();
};
每个Account个体仅仅包含owner和amount两个成员,其他被static修饰的都是共用的。
被static修饰的函数,不会有this指针。
被static修饰的函数,也不会是const的。
我们可以通过类的类型名直接访问static变量或函数:
double r;
r = Accout::rate();
当然我们仍然可以通过具体的对象来访问它们。
成员函数可以直接访问被static修饰的成员变量。
在类的外部定义static函数时,不需要使用static修饰,在类的内部做就可以了。
类的构造函数不会去处理static成员变量,同理我们不能在类的内部去处理static变量。
static变量必须在类的外部进行定义和处理,就像是全局变量一样。
为了保证static成员变量只被定义一次,我们通常将它们放置在对应的源文件中。
一般来说,static成员变量不能在类的内部进行初始化,但也有例外的情况:
如果static成员是const integral类型,就可以在内部进行。
如果static成员只用在了编译器可直接使用它的值的地方,就无需在外面再定义,否则就需要。
如果static成员进行了类内部的初始化,外部的定义就不能再赋值了。
static成员定义在了类外面,因此它的数据类型可以是这个类本身。
static成员可以作为构造函数的参数。