C++中的类——构造函数

一、什么是构造函数

每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。


二、构造函数的定义

class 类名

{

   访问说明符:

   类名(形参列表):初始值列表{函数体定义}

};

————————————————————————————————————————————————

1、构造函数的名字和类名相同、无返回类型有一个(可能为空的)参数列表和一个(可能为空的)函数体。

2、类可以包含多个构造函数,和其他重载函数差不多,不同的构造函数之间必须在参数数量或参数类型上有所区别。

3、不同于其他成员函数,构造函数不能被声明成const的。当我们创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常亮”属性。因此,构造函数在const对象的构造过程中可以向其写值。

4、与其他成员函数相同的是,构造函数在类外定义时也需要明确指出是哪个类。

5、通常情况下,我们将构造函数声明为public的,可以供外部调用。然而有时候我们会将构造函数声明为private或protected的:(1)如果类的作者不希望用户直接构造一个类对象,着只是希望用户构造这个类的子类,那么就可以将构造函数声明为protected,而将该类的子类声明为public。(2)如果将构造函数声明为private,那只有这个类的成员函数才能构造这个类的对象。

默认构造函数

6、默认构造函数:默认构造函数没有参数,即在实例化对象时不需要给定初始值,这时所调用的构造函数为默认构造函数。默认构造函数的初始化规则:(1)如果存在类内初始值,则用它来初始化成员。(2)默认初始化。

7、合成的默认构造函数:当类没有声明任何构造函数时,编译器便会自动地生成默认构造函数。这个由编译器自动创建的构造函数叫做合成的默认构造函数。

8、如果类包含有内置类型或复合类型的成员,则只有当这些成员全部被赋予了类内的初始值时,这个类才适合于使用合成的默认构造函数。因为如果对象定义在块中,则该对象的成员初始值将是不可预测的。

9、有些时候编译器不能为某些类合成默认的构造函数。例如,如果类中包含一个其他类类型的成员且这个成员的类型没有默认构造函数,那么编译器将无法初始化该成员。对于这样的类来说,我们必须自定义默认构造函数,否则该类将没有可用的默认构造函数。当然还有一些其他的情况也会导致编译器无法生成一个正确的默认构造函数。

10、如果类内已经定义了其他的构造函数,那么强烈建议定义一个默认构造函数。我们定义默认构造函数的目的是因为我们即需要在定义类类型的对象时给予初始值的其他形式的构造函数,也需要在定义类类型的对象时不给予初始化参数而根据类内初始值初始化或默认初始化的默认构造函数。我们希望这个函数的作用完全等同于之前使用的合成默认构造函数。

11、在C++11新标准中,如果我们需要默认的行为,那么可以通过在参数列表后写上“=default”(不含引号)来要求编译器生成构造函数。其中,“=default”即可以和声明一起出现在类的内部,也可以作为定义出现在类的外部。在不支持C++11的情况下,可以用一个空参数列表空初始值列表(见下文)函数体内不对成员进行赋值操作的构造函数来代替(典型的:类名(){})。

构造函数初始值列表

构造函数初始值列表:类名(形参列表):数据成员1(初始值),数据成员2(初始值)...{函数体}

上面蓝色部分即为构造函数初始值列表,他负责为新创建的对象的一个或几个数据成员赋初始值。

————————————————————————————————————————————————

12、构造函数初始值是成员名字的一个列表,每个名字后紧跟括号括起来的(或者在花括号内)成员初始值。不同成员的初始化通过逗号分隔开来。

13、构造函数初始值列表只说明用于初始化成员的值,而不限定初始化的具体执行循序。实质上,成员的初始化顺序与它们在类定义中的出现顺序一致。然而如果一个成员是用另一个成员初始化的,那么这两个成员的初始化顺序就很重要了。有的编译器会当构造函数的初始值列表中的数据成员顺序与这些成员声明的顺序不符时生成一条警告信息。最好令构造函数初始值的顺序与成员声明的顺序保持一致,并且可能的话,最好用构造函数的参数作为成员的初始值,而避免使用同一个对象的其他成员,这样就不必考虑成员的初始化顺序了。

14、如果没有在构造函数的初始值列表中显式的初始化成员,则该成员将在构造函数体之前执行类内初始值初始化或默认初始化。这一点也就解释了在不支持C++11的情况下如何生成一个执行默认操作的默认构造函数(见第11点)。

15、一旦构造函数体开始执行,也就意味着初始化结束了。可以在构造函数体内部重新为成员赋值。有时我们可以忽略数据成员初始化和赋值之间的差异,但并非总能这样。例如,如果成员是const或者引用或属于某种类类型且该类没有定义默认构造函数时,必须将这个成员初始化。

默认实参构造函数

16、构造函数也可以具有默认的实参,如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数。

三、委托构造函数

class 类名

{

public:

   类名(int a,string b,vector<string> c):aa(a),bb(b){ cc=c; }//构造函数

   类名(形参列表):类名(0," "){}//委托构造函数

   类名(形参列表):类名(5,"hellol"){}//委托构造函数

private:

   int aa;

   string bb;

   vector<string> cc;

};

————————————————————————————————————————————————

17、C++11新标准扩展了构造函数初始值的功能,我们可以定义委托构造函数。一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把它自己的一些(或者全部)职责委托给了其他构造函数。

18、委托构造函数也有一个成员初始值列表(相当于对构造函数的调用)和一个函数体。在委托构造函数内,成员初始值列表只有一个唯一的入口,就是类名本身。和其他成员初始值一样,类名后面紧跟圆括号括起来的参数列表,参数列表必须与另一个构造函数匹配。

19、当一个构造函数委托给另一个构造函数时,受委托的构造函数的初始值列表和函数体被依次执行,然后控制权才会交给委托者的函数体。

20、委托可以多级传递,即A可以委托给B,B又可以委托给C。

四、隐式的类类型转换

21、如果构造函数只接受一个实参,则它便具有了转换为此类类型的隐式转换机制。把这种构造函数称为转换构造函数。具体表现在:在成员函数的参数为类类型时,可以为其传一个数据成员值,该值会自动转换成一个类类型的临时对象作为实参。

22、需要注意以下几点;(1)转换前传递的实参必须具有对应的转换构造函数。(2)编译器只会自动的执行一步类型转换。(例如,需要一个string类型作为形参的转换构造函数,我们不能用一个字符串字面量作为实参去掉用需要该类类型作为形参的成员函数,因为这时需要两部转换)(3)临时的中间变量都是const,所有没有const的引用会失败。所以如果成员函数的形参为该类类型的引用,则必须将其定义为const类型。

#include <iostream>
using namespace std;

class part
{
public:
	part(string s):ss(s),ii(10){}
	part(int i,string s):ii(i),ss(s){}
	void foo(const part& partn){ss=partn.ss;}
	void print()
	{
		cout<<ii<<endl<<ss<<endl;
	}
private:
	int ii;
	string ss;
}part1(100,"world");

int main()
{
	part1.print();
	string s="hello";
	part1.foo(s);
	part1.print();
}

23、可以用explicit修饰构造函数从而达到如下效果(explicit只允许出现在类的内部):

   23.1、如果构造函数是转换构造函数,此时将无法进行隐式转换。

   23.2、发生隐式转换的另一种情况是,当我们执行拷贝形式的初始化时(=)。此时,只能使用直接初始化,而不能将explicit构造函数用于拷贝形式的初始化。

24、尽管编译器不会将explicit的构造函数用于隐式转换过程,但是可以显式的强制进行转换。

part1.foo(part("hellio"));

part1.foo(static_cast<part>"hello");

25、标准库中的构造函数

   25.1、接受一个单参数的const char*的string构造函数不是explicit的。

string s="hello";

string s("hello");

   25.2、接收一个容量参数的vector构造函数是explicit的。

vector<T> v(n);

五、聚合类

26、聚合类与C语言中的结构体很类似,它使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。

27、当一个类满足如下条件时,我们说它是聚合类:

   27.1、所有成员都是public的

   27.2、没有定义任何构造函数

   27.3、没有类内初始值

   27.4、没有基类,也没有virtual函数。

28、可以用一个花括号括起来的成员初始值列表初始化聚合类的数据成员,初始值的顺序必须与声明的顺序一致。

29、如果初始值列表中的元素个数少于类的成员数量,则靠后的成员被值初始化。初始值列表的元素个数绝对不能超过类的成员数量。

六、字面值常量类

此部分可参考——C++11中的constexpr

30、数据成员都是字面值类型的聚合类是字面值常量类。

31、如果一个类不是聚合列,但它符合下述要求,则它也是一个字面值常量类。

   31.1、数据成员都必须是字面值类型

   31.2、类必须至少含有一个constexpr构造函数(尽管构造函数不能是const的)

   31.3、如果一个数据成员含有类内初始值,则内置类型成员的初始值必须是一条常量表达式;或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数

   31.4、类必须使用析构函数的默认定义,该成员负责销毁类的对象。

32、constexpr构造函数可以声明成=default的形式(或者是删除函数的形式)。否则,constexpr构造函数就必须既符合构造函数的要求(不能包含返回语句),又符合constexpr函数的要求(意味着它能拥有的唯一可执行语句就是返回语句)。综合这两点,constexpr构造函数体应该是空的。通过前置关键字constexpr就可以声明一个constexpr构造函数了。

33、constexpr构造函数必须初始化所以数据成员,初始值要么使用constexpr构造函数,要么是一条常量表达式。

34、constexpr构造函数用于生成constexpr对象以及constexpr函数的参数或返回类型。

#include <iostream>
using namespace std;
#include <string>

class part
{
public:
	constexpr part(int i, int s) :ii(i), ss(s) {}
	void print()
	{
		cout << ii << endl << ss << endl;
	}
	constexpr int poo()
	{
		return ss;
	}
private:
	int ii;
	int ss;
	//只能使用字面值类型
};

int main()
{
	constexpr part p1(10, 20);
	int data[p1.poo()];
	cout << sizeof(data) << endl;
}

 

  • 14
    点赞
  • 102
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值