C++类的初始化
在C++语言中,可以构造函数对类进行初始化,但构造函数形式多样其中还包括参数列表,本文对C++构造函数进行一次梳理与总结。
-
构造函数的定义
构造函数是与类同名的特殊成员函数,主要作用是为类内成员变量赋初值(初始化对象的数据成员)。只要有类的对象被创建,就必须执行构造函数完成对象的初始化。
-
构造函数的特点:
- 构造函数与类同名且没有返回类型;
- 构造函数可以被重载;
- 构造函数由系统自动调用,不可用户显式调用;
- 构造函数不可被声明为 const 函数
-
构造函数的使用
-
合成的默认构造函数
在C++中,每个类必须有构造函数,在用户没显式声明构造函数时,系统会自动为其声明一个默认的构造函数,称为合成的默认构造函数。
合成的默认构造函数是一个无参数的构造函数,负责对象的初始化。如果创建的对象是全局或静态对象,则它将对象的位模式全部设置为0;如果是局部对象,则不对其对象的数据成员进行初始化。
但在有些情况下,编译器无法为类创建合成的默认构造函数。
比如 : 类A的某些数据成员由类B创建,但是类B有其他构造函数,没有合成的默认构造函数。这时,类A必须定义构造函数,并负责为对象成员提供构造函数初值。
-
默认参数构造函数
扫描二维码关注公众号,回复: 12408486 查看本文章构造函数可以将自己的参数定义为默认参数,为参数提供默认信息
class Point { private: int x; int y; public: Point(int xx = 0, int yy = 0){ // 默认参数构造函数 x = xx; y = yy; } // Point(){ // 无参构造函数 // x = 0; // y = 0; // } int get_x() { return x;} int get_y() { return y;} void set_point(int xx, int yy) { x = xx; y = yy; } };
应注意的是:如果定义了无参构造函数,又定义了全部参数都有默认值的构造函数,就会在定义对象时产生二义性。因此,上述代码中的构造函数定义方式只能二选一。
-
重载构造函数
与普通函数一样,重载的构造函数必须具有不同的参数列表
class Point { private: int x; int y; public: Point(int xx = 0, int yy = 0){ // 默认参数构造函数 x = xx; y = yy; } Point(int point){ // 重载构造函数 x = point; y = point; }intitn int get_x() { return x;} int get_y() { return y;} void set_point(int xx, int yy) { x = xx; y = yy; } };
-
拷贝构造函数
-
再用已经存在的对象初始化新建对象时,会调用拷贝构造函数完成对象的复制,在设计类时,必须考虑拷贝构造函数的设计问题
class Point{ ...}; Point point1; // 调用普通构造函数 Point point2 = point1; // 调用拷贝构造函数 Point point3(point1); // 调用拷贝构造函数 Point poi(Point point) // 调用拷贝构造函数 { Point poi; ...; return poi; } Point points[4] = { point1, point2, point3}; // points[0]、points[1]、points[2]调用拷贝构造函数,points[3]调用默认构造函数
-
每个类都有拷贝构造函数,如果没有定义类的拷贝构造函数时,在需要时,编译器会为其创建一个具有最小功能的默认构造函数。成为合成的拷贝构造函数。
-
合成拷贝构造函数形式 :第一个参数必须是自身类的类型的引用,其余参数必须有默认值。
Point :: Point(Point &poi, ...)
-
指针悬挂问题
如果类数据成员中含有指针类型,合成构造函数通常会产生指针空挂问题。此时,必须显式定义拷贝构造函数。
#include <iostream> #include <string.h> using namespace std; class Student { private: char* name; int* id; int age; public: Student(char* Name, int* Id , int Age){ name = new char[strlen(Name) + 1]; strcpy(name, Name); id = new int[5]; for(int i = 0; i < 5; ++i) id[i] = Id[i]; age = Age; }; ~Student(){ delete name; delete id; } char* get_name(){ return name;} int* get_id(){ return id;} int get_age(){ return age;} }; int main(){ // stu1初始化 char* name_X = "Xiao Ming"; int id_X[5] = { 1, 2, 3, 4, 5}; int age_X = 18; Student stu1(name_X, id_X, age_X); cout << stu1.get_name() << endl; int *stu1_id = stu1.get_id(); for(int i = 0; i < 5; ++i){ cout << stu1_id[i]; } cout << endl; cout << stu1.get_age() <<endl; // 调用合成构造函数,此时造成指针空挂 Student stu2 = stu1; cout << stu2.get_name() << endl; int *stu2_id = stu2.get_id(); for(int i = 0; i < 5; ++i){ cout << stu2_id[i]; } cout << endl; cout << stu2.get_age() <<endl; return 0; }
关于指针悬挂问题的具体分析,请参考我的这篇文章:指针悬挂(深拷贝与浅拷贝)
-
拷贝构造函数应用说明:
-
拷贝构造函数与一般构造函数相同,与类同名,可以重载,无返回类型。
-
拷贝构造函数的参数往往是 const 类型的本类对象的引用。
-
拷贝构造函数与赋值运算符成员运算函数调用时机不同。
class Point{ ...}; Point point1; Point point2; point1 = point2; // 两个类皆以声明,故此时是赋值运算符 Point point3 = point1; // 新建point3并用point1初始化,此时调用拷贝构造函数。
-
-
-
-
委托构造函数
一个构造函数使用它所在类的其他构造函数执行自己的初始化功能,或者说一个构造函数把它自己的一些(或全部)职责委托给其他构造函数,就称为委托构造函数。
注意:
委托构造函数只能够在初始化列表中调用它要委托的构造函数,而且初始化列表中不允许再有其他成员初始化列表,但委托构造函数体内可以有程序代码。
class A { private: int a; int b; int c; public: A(); A(int); A(int, int); A(int , int , int); } A::A():A(1, 2, 3){ } // 委托构造函数 A::A(int x, int y):A(x, y, 3){ } // 委托构造函数 A::A(int x, int y, int z):A(x, y), c(z){ } // 错误使用委托函数 A::A(int x, int y, int z){ // 构造函数 a = x; b = y; c = z; }
-
初始化参数列表:
除了在函数体中通过复制语句为数据成员赋初值外,构造函数含可以采用成员初始化列表的方式对数据成员进行初始化。而且在某些特殊条件下,必须采用初始化列表的方式才能完成成员的初始化。
class Point { private: int x; int y; public: Point(int, int); int get_x(); int get_y(); void set_point(int ,int ); }; // 初始化参数列表 // 注意:初始化参数列表可以与构造函数体混用 Point::Point(int xx, int yy):x(xx), y(yy) { } int Point::get_x() { return x;} int Point::get_y() { return y;} void Point::set_point(int xx, int yy) { x = xx; y = yy; }
注意:
-
构造函数初始化列表中的成员初始化次序与它在类中的声明次序相同,与初始列表中的次序无关。
Point::Point(int xx, int yy):x(xx), y(yy) { } Point::Point(int xx, int yy):y(yy), x(xx) { }
两者初始化次序相同,都是x->y
-
构造函数初始化列表的执行时间。如果数据成员有类内初始值,则执行次序为:
类内初始值->构造函数初始化列表->构造函数体
-
常量成员、引用成员、类对象成员以及派生类构造函数对基类构造函数的调用等必须采用类内初始化值或构造函数初始化列表进行初始化。但类内初始化值是从C++11后才被允许的。
-
-