深入讲解C++中的构造函数
C++构造函数
当创建一个对象时,往往需要做一些初始化工作,例如对数据成员赋值等。为了解决这个问题,C++提供了构造函数。
构造函数(Constructor)是一种特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户调用(用户也不能调用),而是在创建对象时自动执行。构造函数的作用是在创建对象时进行初始化工作,最常见的就是对成员变量赋值。
一个构造函数的例子:
#include<iostream> usingnamespacestd; classStudent{ private: char*name; intage; floatscore; public: //声明构造函数 Student(char*,int,float); //声明普通成员函数 voidsay(); }; //定义构造函数 Student::Student(char*name1,intage1,floatscore1){ name=name1; age=age1; score=score1; } //定义普通成员函数 voidStudent::say(){ cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl; } intmain(){ //根据构造函数创建对象 Studentstu("小明",15,90.5f);//传参形式类似于函数调用 stu.say(); return0; }
运行结果:
小明的年龄是15,成绩是90.5
在类中我们定义了一个构造函数Student(),它的作用是给3个private属性的成员变量赋值。在main函数中,我们根据构造函数创建了一个对象stu;因为构造函数有参数,所以创建对象时要相应地传入实参,形式类似于函数调用。
读者要注意:一旦在类中定义了构造函数,那么创建对象时一定会被执行;如果构造函数有参数,创建对象时就要传参。
另外,构造函数主要用来进行初始化,没有返回值(有返回值没有任何意义),这就意味着:
不管是声明还是定义,函数名前面都不能出现返回值类型,即使是void也不允许;
函数体中不能有return语句。
默认构造函数
如果用户自己没有定义构造函数,那么编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空的,也没有参数,不执行任何操作。比如上面的Student类,默认生成的构造函数如下:
Student(){}
一个类,必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管它是public属性的,还是private、protected属性的,编译器都不再自动生成。上面的Student类,只有一个构造函数,就是我们自己定义的。
实际上,编译器只有在必要的时候才会生成默认构造函数,而且它的函数体一般不为空。默认构造函数的目的是帮助编译器做初始化工作,而不是帮助程序员。这是C++的内部实现机制,这里不再深究,初学者可以按照上面说的“一定有一个空函数体的默认构造函数”来理解。
构造函数的重载
和普通成员函数一样,构造函数是允许重载的。一个类可以提供多个构造函数,让用户在创建对象时进行选择,编译器会根据创建对象时传递的参数来确定调用哪一个构造函数。也就是说:
只有一个构造函数会被执行;
创建对象时提供的参数必须和其中的一个构造函数匹配,否则编译错误。
一个构造函数重载的例子:
#include<iostream> usingnamespacestd; classStudent{ private: char*name; intage; floatscore; public: //声明构造函数 Student(); Student(char*,int,float); //声明普通成员函数 voidsetname(char*); voidsetage(int); voidsetscore(float); voidsay(); }; //定义构造函数 Student::Student(){} Student::Student(char*name1,intage1,floatscore1){ name=name1; age=age1; score=score1; } //定义普通成员函数 voidStudent::setname(char*name1){ name=name1; } voidStudent::setage(intage1){ age=age1; } voidStudent::setscore(floatscore1){ score=score1; } voidStudent::say(){ cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl; } intmain(){ //创建对象时初始化成员变量 Studentstu1("小明",15,90.5f); stu1.say(); //调用成员函数来初始化成员变量的值 Studentstu2; stu2.setname("李磊"); stu2.setage(16); stu2.setscore(95); stu2.say(); return0; }
运行结果:
小明的年龄是15,成绩是90.5 李磊的年龄是16,成绩是95
类中定义了两个构造函数,一个带参数一个不带参数,它们是重载关系。当根据不带参数的构造函数创建对象时,不需要传参,成员变量不会被初始化,所以要调用成员函数来设置它们的值。
C++带参数的构造函数
不带参数的构造函数使该类的每一个对象都得到相同的初始值。
如果希望对不同的对象赋予不同的初始值,则需要使用带参数的构造函数,在调用不同对象的构造函数时,将不同的数据传给构造函数,以实现不同的初始化。
构造函数首部的一般格式为:
构造函数名(类型1形参1,类型2形参2,…)
由于用户是不能调用构造函数的,因此无法采用常规的调用函数的方法给出实参。实参是在创建对象时给出的。创建对象的一般格式为:
类名对象名(实参1,实参2,…);
【例】有两个长方柱,其长、宽、高分别为12,20,25和10,14,20,求它们的体积。编写一个基于对象的程序,在类中用带参数的构造函数。
#include<iostream> usingnamespacestd; classBox { public: Box(int,int,int); intvolume(); private: intheight; intwidth; intlength; }; //声明带参数的构造函数//声明计算体积的函数 Box::Box(inth,intw,intlen)//在类外定义带参数的构造函数 { height=h; width=w; length=len; } intBox::volume()//定义计算体积的函数 { return(height*width*length); } intmain() { Boxbox1(12,25,30);//建立对象box1,并指定box1长、宽、高的值 cout<<"Thevolumeofbox1is"<<box1.volume()<<endl; Boxbox2(15,30,21);//建立对象box2,并指定box2长、宽、高的值 cout<<"Thevolumeofbox2is"<<box2.volume()<<endl; return0; }
程序运行结果如下:
Thevolumeofbox1is9000 Thevolumeofbox2is9450
可以知道:
带参数的构造函数中的形参,其对应的实参在定义对象时给定。
用这种方法可以方便地实现对不同的对象进行不同的初始化。
用参数初始化表对数据成员初始化
上面介绍的是在构造函数的函数体内通过赋值语句对数据成员实现初始化。C++还提供另一种初始化数据成员的方法——参数初始化表来实现对数据成员的初始化。这种方法不在函数体内对数据成员初始化,而是在函数首部实现。
例中定义构造函数可以改用以下形式:
Box::Box(inth,intw,intlen):height(h),width(w),length(len){}
这种写法方便、简练,尤其当需要初始化的数据成员较多时更显其优越性。甚至可以直接在类体中(而不是在类外)定义构造函数。