C++ Primer 学习笔记_51_类与数据抽象 --构造函数【上】
类
--构造函数【上】
引言:
构造函数确保每个对象在创建时自动调用,以确保每个对象的数据成员都有合适的初始值。
class Sales_item { public: //其中isbn由string的默认构造函数提供初始化 Sales_item():units_sold(0),revenue(0){} private: std::string isbn; unsigned units_sold; double revenue; };
构造函数的几大特征:
1、构造函数可以被重载
一般而言,不同的构造函数允许用户指定不同的方式来初始化数据成员。
class Sales_item { //other members as before public: Sales_item(); Sales_item(const string &); Sales_item(std::istream &); };
2、实参决定使用哪个构造函数
上面我们定义了三个构造函数,在定义新对象时,可以使用这些构造函数中的任意一个:
Sales_item empty; Sales_item Primer_3td_Ed("0-201-82470-1"); Sales_item Primer_4th_Ed(cin);
3、构造函数自动执行
只要创建该类型的一个对象,编译器就自动运行一个构造函数:
//调用含有一个string形参的构造函数 Sales_item Primer_2cn_Ed("0-201-54848-8"); //调用默认构造函数初始化该对象 Sales_item *p = new Sales_item();
4、用于const对象的构造函数
class Sales_item { public: //构造函数不能是const Sales_item() const; //Error };
创建类类型的const对象时,运行一个普通构造函数就可以初始化该const对象。构造函数的工作就是初始化对象,不管对象是否为const,都用一个构造函数来初始化对象(瞬间对构造函数有种敬仰之情...).
//P387 习题12.19 class NoName { public: NoName():pstring(0),ival(0),dval(0){} NoName(std::string *Pstr,int Ival,double Dval):pstring(Pstr),ival(Ival),dval(Dval){} private: std::string *pstring; int ival; double dval; };
一、构造函数初始化式
构造函数的初始化列表以一个冒号可是,接着是一个以逗号分割的数据成员列表,每个数据成员跟一个放在圆括号中的初始化式。
构造函数可以定义在类的内部或外部,但是构造函数的初始化式只在构造函数的定义中而不是在声明中指定。
【小心O(∩_∩)O~】
构造函数初始化列表是许多相当有经验的C++程序员都没有掌握的一个特性。
Sales_item(const std::string &book) { //其实在执行这一条语句之前,isbn已经有值了 //这个构造函数隐式使用string构造函数初始化isbn isbn = book; units_sold = 0; revenue = 0; }
从概念上将,可以认为构造函数分为两个阶段执行:
1)初始化阶段;
2)普通的计算阶段。计算阶段由构造函数函数体中的所有语句组成。
不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化。初始化发生在计算阶段开始之前。
在构造函数初始化列表中没有显式提及的每个成员,使用与初始化变量相同的规则来进行初始化。运行该类型的默认构造函数,来初始化类类型的数据成员。内置或复合类型的成员的初始值依赖于对象的作用域:在局部作用域中这些成员不被初始化,而在全局作用域中它们被初始化为0。
使用构造函数初始化列表的版本初始化【重点是“初始化”】数据成员,没有定义初始化列表的构造函数版本在构造函数函数体中对数据成员赋值【亮点在”赋值“】。这个区别的重要性取决于数据成员的类型。
1、有时需要构造函数初始化列表
有些成员必须在构造函数初始化列表中进行初始化。对于这样的成员,在构造函数函数体中对它们赋值不起作用。没有默认构造函数的类类型的成员,以及const或引用类型的成员,不管是哪种类型,都必须在构造函数初始化列表中进行初始化。
class ConstRef { public: ConstRef(int ii); private: int i; const int ci; int &ri; }; ConstRef::ConstRef(int ii) { i = ii; //OK ci = ii; //Error ri = ii; //没有编译错误,但是ri事实上根本没有绑定任何对象 }
谨记:可以初始化const对象或引用类型的对象,初始化const或引用类型数据成员的唯一机会是构造函数初始化列表中。编写该构造函数的正确方式为:
class ConstRef { public: ConstRef(int ii); private: int i; const int ci; int &ri; }; ConstRef::ConstRef(int ii):i(ii),ci(ii),ri(ii){}
【建议:使用构造函数初始化列表,P389】
在许多类中,初始化和赋值严格来讲都是低效率的:数据成员可能已经被直接初始化了,还要对它进行初始化和赋值。比较率问题更重要的是,某些数据成员必须要初始化,这是一个事实。
因此,必须对任何const或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。
当类成员需要使用初始化列表时,通过常规地使用构造函数初始化列表,就可以避免发生编译时错误。
2、成员初始化的次序
每个成员在构造函数初始化列表中只能指定一次;
构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的次序。成员被初始化的次序就是定义成员的次序!
初始化的次序常常无关紧要。然而,如果一个成员是根据其他成员而初始化,则成员初始化的次序是至关重要的。
class X { int i; int j; public: X(int val):j(val),i(j){} //在GCC编译器上会给出警告 };
按照与成员声明一致的次序编写构造函数初始化列表是个好主意。此外,尽可能避免使用成员来初始化其他成员。因此,一般情况下,通过(重复)使用构造函数的形参而不是使用对象的数据成员!
X(int val):j(val),i(val){}
3、初始化式可以是任意复杂的表达式
Sales_item(const std::string &book,int cnt,double price): isbn(book),units_sold(cnt),revenue(cnt * price) {}
4、类类型的数据成员的初始化式
初始化类类型的成员时,要指定实参并传递给成员类型的一个构造函数。可以使用该类型的任意构造函数。
Sales_item():isbn(10,‘a‘),units_sold(0),revenue(0){}
//P390 习题12.21 class DemoClass { public: DemoClass():str("DemoClass"),ival(0),pdou(0),in(inFile){} private: const string str; int ival; double *pdou; ifstream ∈ };
//习题12.23 class NoDefault { public: NoDefault(int); }; class C { public: C(int ival):no(ival){} private: NoDefault no; };