构造函数的作用是在创建对象时初始化对象,即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。一个类可以有多个构造函数,可根据参数个数不同或类型不同来进行构造函数的重载。
- 默认构造函数
Student(); //没有参数 //如果创建一个类没有写任何构造函数,系统会自动生成默认的无参构造函数 //系统自动生成的默认构造函数为空,什么都不做初始化构造函数 //也可自己写出默认构造函数,在函数体内进行变量的赋值操作 Student() { num = ; age = ; }
- 初始化构造函数
可以看到,采用初始化构造函数时,对于类的成员变量的初始化有两种方式,一种是使用初始化列表,一种是使用函数体内赋值的方式。两种方式的区别是:Student(int num, in age); //初始化构造函数有参数 //初始化构造函数的定义如下 //可以采用这种初始化列表,即:后进行变量初始化 Student(int n, int a):num(n), age(a){} //也可在函数体内进行初始化 Student(int n, int a) { num = n; age = a; }
- 内部数据类型(char, int……指针等)
class Animal { public: Animal(int weight, int height): //A初始化列表 m_weight(weight), m_height(height) { } Animal(int weight, int height) //B函数体内初始化 { m_weight = weight; m_height = height; } private: int m_weight; int m_height; };
对于这些内部类型来说,基本上是没有区别的,效率上也不存在多大差异。
但是A和B方式不能同时存在。
-
无默认构造函数的继承关系中
当使用了初始化构造函数时,如果不特别地写出默认构造函数,系统是不会自动生成默认构造函数的。因此在继承中,会出现以下问题:
class Animal { public: Animal(int weight, int height): //没有提供无参构造函数 m_weight(weight), m_height(height) {} private: int m_weight; int m_height; }; class Dog: public Animal { public: Dog(int weight, int height, int type) { } private: int m_type; };
上面的子类和父类编译会出错。
因为子类Dog初始化之前要进行父类Animal的初始化,但是根据Dog的构造函数,没有给父类传递参数,使用了父类Animal的无参构造函数。而父类Animal提供了有参构造函数,这样编译器就不会给父类Animal提供一个默认的无参数的构造函数了,所以编译时报错,说找不到合适的默认构造函数可用。要么提供一个无参数的构造函数,要么在子类的Dog的初始化列表中给父类Animal传递初始化参数,如下:
class Dog: public Animal { public: Dog(int weight, int height, int type): Animal(weight, height) { ; } private: int m_type; };
- 类中const常量,必须在初始化列表中初始,不能使用赋值的方式初始化
class Dog: public Animal { public: Dog(int weight, int height, int type): Animal(weight, height), LEGS(4) //必须在初始化列表中初始化 { //LEGS=4; //error } private: int m_type; const int LEGS; };
- 包含有自定义数据类型(类)对象的成员初始化
class Food { public: Food(int type = ) { m_type = ; } Food(Food &other) //拷贝构造函数 { m_type = other.m_type; } Food & operator = (Food &other) //重载赋值=函数 { m_type = other.m_type; return *this; } private: int m_type; }; //(1)构造函数赋值方式,初始化成员对象m_food class Dog: public Animal { public: Dog(Food &food) { m_food = food; //初始化成员对象 } private: Food m_food; }; //使用 Food fd; Dog dog(fd); /*Dog dog(fd);结果是,先执行了对象类型构造函数Food(int typt = 10) 然后执行对象类型构造函数Food & operator = (Food &other)*/ //(2)构造函数初始化列表方式 class Dog: public Animal { public: Dog(Food &food) :m_food(food) //初始化 成员对象 { //m_food = food; } private: Food m_food; }; //使用 Food fd; Dog dog(fd); /*Dog dog(fd);结果是执行Food(Food &other)拷贝构造函数完成舒适化*/
初始化构造函数的执行过程:
1)传参 2)给类成员开辟空间 3)执行冒号语法给数据成员初始化 4)执行构造函数体内内容
- 内部数据类型(char, int……指针等)
-
复制(拷贝)构造函数
复制构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中。
若没有显示的写复制构造函数,则系统会默认创建一个,但当类中有指针成员时,由系统默认创建该复制构造函数会存在风险。
- 复制构造函数用于复制本类的对象
Student s2(, ); Student s3(s2); //将对象s2复制给s3。注意复制和赋值的概念不同
- 下面这种情况叫做赋值,不调用复制构造函数
Student s4; s4 = s2;
- 大多数时候,在类中我们没有声明复制构造函数,而系统会自动为我们生成一个
Student(Student &b) { this.x = b.x; this.y = b.y; }
- 复制构造函数用于复制本类的对象
-
转换构造函数
转换构造函数,根据一个指定的类型的对象创建一个本类的对象
例如:下面将根据一个double类型的对象创建一个Complex对象
Complex::Complex(double r) { m_real = r; m_imag = ; } Student(int r) { int num = ; int age = r; }
-
转换构造函数用在哪里?
加入重载了+号运算符,使得两个Student类的对象可以相加,其结果为两个对象的成员变量age之和
Student s1(,); Student s2(,); s1+s2;
-
那么s1+19呢(类对象与int直接相加)?
因为我们定义了转换构造函数,所以s1+19的执行过程:
首先调用+号运算符,发现19不是Student类的对象,而是int类型
然后调用转换构造函数,将19变为Student(19)
现在便可以进行加法运算
-