构造函数
构造函数:类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。其任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。
一、先看下构造函数的特点:
构造函数没有返回类型,可能有一个参数列表(可能为空)和一个函数体(可能为空)。构造函数不能被声明成const,因为当我们创建一个const对象时,直到构造函数完成初始化过程,对象才真正能取得“const”属性。
合成的默认构造函数将会按照如下规则初始化数据成员:1.如果存在类内初始值,则用它来初始化成员。2.否则默认初始化该成员。
二、有些类不能依赖于合成默认构造函数
- 只有当类没有声明任何构造函数时,编译器才会自动地生成默认构造函数。一旦我们定义了其他构造函数(不是默认构造函数),除非我们再定义一个默认构造函数,否则类将没有默认构造函数。(和发救济粮一样,能吃上白面时,*不会给你发窝窝头了,想吃窝窝头就得自己做)。
- 合成的默认构造函数可能引发错误,如果类包含有内置类型或者符合类型的成员,则只有当这些成员全部被赋予了类内的初始值时,这个类才适合使用合成的默认构造函数。
- 有时编译器不能为某些类合成默认构造函数。比如如果类中包含一个其他类类型的成员并且这个成员的类型没有默认构造函数
1 1 struct sales_data{ 2 2 3 3 //默认构造函数 在参数列表后面加上=default来要求编译器生成构造函数。如果=default出现在类内部,则默认构造函数是内联的,否则不是。该构造函数之所以有效,是因为我们为内置数据成员提供了初始值。 4 4 sales_data() = default; 5 5 sales_data(const std::string &s):bokno(s) {} 6 6 //冒号及冒号之间的部分称为构造函数初始值列表,括号内称为参数列表,花括号内称为函数体。 7 7 sales_data(const std::string &s,unsigned n,double p ):bookno(s),units_sold(n),revenue(p*n){} 8 8 sales_data(std::istream &); 9 9 10 10 //string是一个类,它决定自己的对象的初始值是什么,如果没有指定初始值,则生成一个空串。 11 11 std::string bookno; 12 12 //在内部的内置类型应该指定类内初始值 13 13 unsigned units_sold = 0; 14 14 //且构造函数不应该轻易覆盖掉类内初始值,除非新赋的值与原值不同。 15 15 double revenue = 0.0; 16 16 17 17 }; 18 18 //我们定义sales_data类的成员,它的名字是sales_dtata.这个构造函数初始值列表是空的,但是由于执行了构造函数体,对象的成员仍然能够被初始化。通过相应的类内初始值初始化。 19 19 sales_data::sales_data(std::istream &is){ 20 20 read(is,*this); 21 21
三、看下在构造函数内部成员是如何初始化的:
如果没有在构造函数的初始值列表中显式的初始化成员,则该成员将在构造函数体之前执行默认初始化。
1 //这个是对数据成员进行了赋值操作,而不是初始化。=号是赋值操作。初始化的含义是创建变量时赋予其一个初始值,而赋予的含义是把对象的当前值擦除,而以一个新值来替代。 2 sales_data::sales_data(const string &s,unsigend cnt,double price){ 3 bookno = s; 4 units_sold = cnt; 5 revenue = cnt *price; 6 7 }
如果成员是const是const或者是引用的话,必须将其初始化。随着构造函数体一开始执行,初始化就完成了。我们初始化const或者引用类型的唯一机会就是通过构造函数初始值。
1 constref::constref(int ii):i(ii),ci(ii),ri(i){}
四、再来看下什么是委托构造函数(就是把活儿都给人家干,嫁接在别的构造函数上):
一个委托构造函数可以使用它所属类的其他构造函数来执行它自己的初始化过程。委托构造函数也有一个成员初始值列表和一个函数体。成员初始值列表只有一个唯一入口,就是类名本身。
1 class sales_data{ 2 public: 3 //非委托构造函数使用对应的实参初始化成员 4 sales_data(std::string s,unsigend cnt ,double price):bookno(s),units_sold(cnt),revenue(cnt*price){} 5 //其余构造函数全部委托给另一个构造函数 6 sales_data():sales_data("",0,0){} 7 sales_dta(std::string s):sales_data(s,0,0){} 8 //先委托给默认构造函数,然后默认构造函数再委托给三实参函数。 9 sales_data(std::istream &is):sales_data(){read(is ,*this);} 10 };
五、来关注下默认构造函数的作用:
默认初始化发生在以下情况:
- 当我们在块作用域内不使用任何初始值定义一个非静态变量。
- 当一个类本身还有类类型成员并且使用合成的默认构造函数时。
- 当类类型成员没有再构造函数初始值列表中显示初始化时。
值初始化发生在以下情况:
- 在数组初始化过程中,如果我们提供的初始值数量少于数组大小时。
- 当我们不使用初始值定义一个局部静态变量时。
- 通过书写形如t()的表达式显示请求值初始化时
六、来关注下隐式的类类型转换(只适用于提供一个实参的构造函数,生成一个临时类对象,传入到类的成员函数中)
转换构造函数:如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制。
1 //在我们的sales_data类中,接受string或者istream的构造函数分别定义了从这两种类型向sales_data的隐式转换准则。也就是说在需要使用sales_dta的地方可以使用这两种类型替代。但是只允许一步隐式转换。 2 string null_book = "9-9999-9999"; 3 //构造一个临时sales_data对象,该对象的bookno=null_book,其他参数由类内初始值指定。因为combine的参数是常量引用,所以我们可以传递给它一个临时量。 4 item.combine(null_book); 5 6 //下面的函数就是错误的,因为隐式类型转换只允许一步转换。 7 item.combine("9-9999-9999") 8 //不过我们可以显示的将字符串转换为sales_data,这个sales_data对象就是个临时量。 9 item.combine(sales_data(9-9999-9999));
抑制隐式转换:
1 class sales_data{ 2 public: 3 sales_data() = default; 4 //需要多个实参的构造函数不能用于执行隐式转换,所以无需将其指定为explicit 5 sales_data(const std:string &s,unsigend n,double p): 6 bookno(s),units_sold(n),revenue(p*n){} 7 //虽然不能隐式转换了,但是可以显式转换 8 explicit sales_data(const std::string &s) :bookno(s) {} 9 explicit sales_data(std::istream&) ; 10 11 } 12 13 item.combine(null_book); //错误:string构造函数是explicit 14 sales_data iterm1(null_book); //正确:直接初始化 可以使用explicit类型的构造函数 15 sales_data iterm2 =null_book; //错误:不能将explicit构造函数用于拷贝形式的初始化 16 //虽然执行了explicit构造函数,但是仍然可以执行显示地强制进行转换 17 //static_cast可以使用explicit的构造函数 18 item。combine(static<sales_data>(cin))
上一篇: 从无建立一个vue项目
下一篇: 这味道