始化列表中没有显式提及的每个成员,使用与初始化变量相同的规则来进行初始化。运行该类型的默认构造函数,来初始化类类型的数据成员。内置或复合类型的成员的初始值依赖于对象的作用域:在局部作用域中这些成员不被初始化,而在全局作用域中它们被初始化为 0。
?
ppps:有时需要构造函数初始化列表。如果没有为类成员提供初始化式,则编译器会隐式地使用成员类型的默认构造函数。如果那个类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。在这种情况下,为了初始化数据成员,必须提供初始化式。有些成员必须在构造函数初始化列表中进行初始化。对于这样的成员,在构造函数函数体中对它们赋值不起作用。没有默认构造函数的类类型的成员,以及 const 或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化。
如:
classConstRef {
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
};
ConstRef::ConstRef(intii)
{ i =ii; // ok
ci = ii;// error: cannot assign to a const,只能初始化
ri = i; // assigns to ri which was not bound to an object,errpr
}
改为:ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii) { }
?
ps:成员初始化的顺序
每个成员在构造函数初始化列表中只能指定一次,这不会令人惊讶。毕竟,给一个成员两个初始值意味着什么?也许更令人惊讶的是,构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的次序。成员被初始化的次序就是定义成员的次序。 第一个成员首先被初始化,然后是第二个, 依次类推。初始化的次序常常无关紧要。然而,如果一个成员是根据其他成员而初始化,则成员初始化的次序是至关重要的。
如:
class X{
int i;
int j;
public:
// run-time error: i is initialized before j
X(int val): j(val), i(j) { }
};
在这种情况下,构造函数初始化列表看起来似乎是用 val 初始化 j,然后再用 j 来初始化 i。然而,i 首先被初始化。但这个初始化列表的实际效果是用尚未初始化的 j 值来初始化 i!
如果数据成员在构造函数初始化列表中的列出次序与成员被声明的次序不同,那么有的编译器非常友好,会给出一个警告。按照与成员声明一致的次序编写构造函数初始化列表是个好主意。此外,尽可能避免使用成员来初始化其他成员。
写成X(int val): i(val), j(val) { }可能更好!
l 初始化式可以是任意表达式,Sales_item(conststd::string &book, int cnt, double price) : isbn(book), units_sold(cnt),revenue(cnt * price) { }
l 类类型的数据成员的初始化式,可以使用该类型的任意构造函数。例如,Sales_item 类可以使用任意一个 string构造函数来初始化 isbn。也可以用 ISBN 取值的极限值来表示isbn 的默认值,而不是用空字符串。即可以将 isbn 初始化为由 10 个 9 构成的串:
Sales_item(): isbn(10, '9'),units_sold(0), revenue(0.0) {}
l 构造函数也可以有默认参数
l 默认构造函数
只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。哪怕只定义了一个构造函数,编译器也不会再自动生成默认构造函数了。合成的默认构造函数使用与变量初始化相同的规则来初始化成员。即类成员使用默认构造函数,内置变量不初始化(与java不同,java中都是值初始化,即类成员为null,int为0,double为0.0)。
?
ps:通常应为类定义一个默认构造函数,如果没有,则会有以下问题:
1、定义对象时必须传参
2、该类不能用作动态分配数组的元素类型
3、如果容器保存该类对象,如vector,则不能使用只接受容器大小的构造函数
pps:使用默认构造函数
Sales_item myobj; //正确!
或者 Sales_item myobj=Sales_item(); //先创建一个无名对象,再用它复制给myobj
Sales_item myobj(); //当成了一个函数声明!!
?
? 隐式类类型转换
classSales_item {
public:
Sales_item(const std::string &book = ):isbn(book),units_sold(0), revenue(0.0) { }
Sales_item(std::istream &is);
};
当string null_book = 9-999-99999-9;
item.same_isbn(null_book);
和item.same_isbn(cin); 都会隐式执行构造函数创建一个临时对象,再调用函数same_isbn。
但是如下:
classSales_item {
public:
explicit Sales_item(const std::string&book = ):isbn(book), units_sold(0), revenue(0.0) { }
explicit Sales_item(std::istream&is);
};
explicit 关键字只能用于类内部的构造函数声明上。在类的定义体外部所做的定义上不可再写它!!
item.same_isbn(null_book); // error: string constructor is explicit
item.same_isbn(cin); // error:istream constructor is explicit
只能item.same_isbn(Sales_item(null_book));
ps:通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数(转换构造函数??)应该为 explicit。将构造函数设置为explicit可以避免错误,并且当转换有用时,用户可以显式地构造对象。
?
? 当类的全体数据成员都是 public时,可以Data val2 = { 1024, Anna Livia Plurabelle };
这样初始化,且列表顺序为成员变量定义的顺序。
?
? 友元
友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类。友元的声明以关键字friend 开始。它只能出现在类定义的内部。友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以它们不受声明出现部分的访问控制影响。通常, 将友元声明成组地放在类定义的开始或结尾是个好主意。
ps:在类内部public下的typedef 的类型名,在类外访问要用类名::类型名,如vector
::iterator
?
例子:
classScreen {
friend class Window_Mgr; //友元类,则Window_Mgr 的所有成员函数可以直接引用 Screen 的私有成员。
};
ps:友元可以是普通的非成员函数,或前面定义的其他类的成员函数,或整个类。将一个类设为友元