一、什么是构造函数
当一个对象在创建的时候,为了可以同时对该对象的成员变量进行初始化,保证其成员变量值的安全性,C++编译器提供了一个函数,构造函数来解决这个问题。
二、构造函数的特点
1、构造函数也是函数,但函数名必须跟类名相同
2、构造函数无返回值
3、构造函数可以重载
4、在创建类对象时,构造函数会自动隐式调用
5、构造函数初始化列表的执行时间比在构造函数体中进行赋值操作的要早,同时效率更高。
6、系统默认提供了两个默认构造函数,一个是普通的构造函数,另外一个是拷贝构造函数,当用户手动声明时,系统不会提供对应的默认构造函数。
三、普通构造函数
1、使用C++默认提供的构造函数
#include <iostream> using namespace std; class Test { int a; public: void setA(int a) { this->a = a; } int getA() { return a; } }; int main() { Test A; // 使用默认的普通构造函数创建对象 A.setA(10); cout << "a = " << A.getA() << endl; return 0; }
2、使用自己声明的构造函数
定义构造函数时,注意必须把所有非静态成员变量进行初始化,否则无法编译通过。
#include <iostream> using namespace std; class Test { int a; public: Test() // 自己定义了一个构造函数 { cout << "my Test" << endl; } void setA(int a) { this->a = a; } int getA() { return a; } }; int main() { Test A; // 使用自己的普通构造函数创建对象 A.setA(10); cout << "a = " << A.getA() << endl; return 0; }
3、构造函数重载
1、带默认参数声明构造函数时,应该避免声明无参的构造函数
#include <iostream> using namespace std; class Test { int a; public: Test() { cout << "Test()" << endl; } Test(int a = 0) // 这种带默认参数的重载是会模糊的 { cout << "Test(int a = 0)" << endl; } void setA(int a) { this->a = a; } int getA() { return a; } }; int main() { Test A; // 这种声明方式无法确定是调用Test()还是Test(int a = 0) A.setA(10); cout << "A.a = " << A.getA() << endl; // cout << "B.a = " << B.getA() << endl; return 0; }
2、建议的构造函数声明方式
#include <iostream> using namespace std; class Test { int a; public: Test(int a = 0) // 有几个成员变量就参数列表就应该有几个 { cout << "Test(int a = 0)" << endl; this->a = a; } void setA(int a) { this->a = a; } int getA() { return a; } }; int main() { Test A; Test B(10); cout << "A.a = " << A.getA() << endl; cout << "B.a = " << B.getA() << endl; return 0; }
四、拷贝构造函数
1、函数原型
类名 (const 类名 & other)
2、为什么函数原型不能是 类名 (const 类名 other)
如果采用该函数原型 类名 (const 类名 other),在传参时,要将实参传递给形参,相当于创建一个新对象,此时要调用拷贝构造函数,而在调用拷贝构造函数时,又要将本次的形参传递给下一个调用构造函数的形参,无限循环。
函数原型为 类名(const 类名 &other)情况:
#include <iostream> using namespace std; class Test { int a; public: Test() { cout << "Test()" << endl; } Test(const Test & other) // 拷贝构造函数 { cout << "Test(const Test & other)" << endl; a = other.a; } void setA(int a) { this->a = a; } int getA() { return a; } }; int main() { Test A; A.setA(10); Test B = A; // 使用类对象来初始化同类的另一个对象 cout << "A.a = " << A.getA() << endl; cout << "B.a = " << B.getA() << endl; return 0; }
函数原型为 类名(const 类名 other)情况:
#include <iostream> using namespace std; class Test { int a; public: Test() { cout << "Test()" << endl; } Test(const Test other) // 无法编译,错误的函数原型 { cout << "Test(const Test & other)" << endl; a = other.a; } void setA(int a) { this->a = a; } int getA() { return a; } }; int main() { Test A; A.setA(10); Test B = A; // 使用类对象来初始化同类的另一个对象 cout << "A.a = " << A.getA() << endl; cout << "B.a = " << B.getA() << endl; return 0; }
3、使用场景
1)、当用一个类对象去初始化同类的另一个对象。
2)、如果某函数有一个参数是类A的对象,那么该函数被调用时,类A的拷贝构造函数将被调用。
3)、如果函数的返回值是类A的对象时,则函数返回时,A的拷贝构造函数将被调用。(待验证,跟编译器版本有关,现在的大多都是调用构造函数),但是返回是对象的引用时,一定调用拷贝构造函数。
当用一个类对象去初始化同类的另一个对象。案例:
#include <iostream> using namespace std; class Test { int a; public: Test(int a = 0) { cout << "Test(int a = 0)" << endl; this->a = a; } Test(const Test &other) { cout << "Test(const Test& other)" << endl; this->a = other.a; } void setA(int a) { this->a = a; } int getA() { return a; } }; int main() { Test A; A.setA(10); Test B = A; // 使用类对象来初始化同类的另一个对象 cout << "A.a = " << A.getA() << endl; cout << "B.a = " << B.getA() << endl; return 0; }
如果某函数有一个参数是类A的对象,那么该函数被调用时,类A的拷贝构造函数将被调用。案例
#include <iostream> using namespace std; class Test { int a; public: Test(int a = 0) // 普通构造函数 { cout << "Test(int a = 0)" << endl; this->a = a; } Test(const Test &other) // 拷贝构造函数 { cout << "Test(const Test &other)" << endl; this->a = other.a; } void show() { cout << "a = " << a << endl; } }; void func(Test B) { cout << "void func(Test B)" << endl; B.show(); } int main() { Test A(10); A.show(); func(A); return 0; }
如果函数的返回值是类A的对象时,则函数返回时,A的拷贝构造函数将被调用。(待验证,跟编译器版本有关,现在的大多都是调用构造函数)案例:
#include <iostream> #include <cstring> using namespace std; class Test { int a; public: Test(int a = 0) // 普通构造函数 { cout << "Test(int a = 0)" << endl; this->a = a; } Test(const Test &other) // 拷贝构造函数 { cout << "Test(const Test &other)" << endl; this->a = other.a; } void show() { cout << "a = " << a << endl; } }; // 返回对象 Test func1() { cout << "Test func1()" << endl; Test B(10); return B; } // 返回对象的引用 Test& func2() { cout << "Test& func2()" << endl; Test *p = new Test(100); return *p; } int main() { Test B = func1(); B.show(); Test C = func2(); C.show(); return 0; }
五、一些特殊情况下定义构造函数的方式
1、成员变量是const修饰时
const 修饰变量时,保证了该变量在声明时就应该立即初始化。在构造函数中有特殊的声明方式:类名 (参数类型 参数1, 参数类型 参数2,...):成员变量1(参数1),成员变量2(参数2)...
#include <iostream> using namespace std; class Test { int a; const int b; const int c; public: Test(int a = 0, const int _b = 0, const int _c = 0):b(_b), c(_c) // 有几个成员变量就参数列表就应该有几个 { this->a = a; // this->b = _b; // 这行不用写,只用在参数列表冒号后面写就行 // this->c = _c } void show() { cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl; } }; int main() { Test A(10, 100, 1000); A.show(); return 0; }
2、成员变量是指针类型时
不能直接把指针赋值给指针,指针类型的成员变量需要重新申请一个空间,这样在释放对象时,不会对同一个空间重复释放多次。
#include <iostream> #include <cstring> using namespace std; class Test { int a; char *b; public: Test(int a = 0) // 普通构造函数 { cout << "Test(int a = 0)" << endl; this->a = a; this->b = nullptr; // 创建对象时,指针指向空 } Test(const Test &other) // 拷贝构造函数 { cout << "Test(const Test &other)" << endl; this->a = other.a; this->b = new char(strlen(other.b)+1); // 重新申请一份堆空间 strcpy(this->b, other.b); this->b[strlen(other.b)] = '\0'; } void setB(char *str) { this->b = new char(strlen(str)+1); // 重新申请一份堆空间 strcpy(this->b, str); this->b[strlen(str)] = '\0'; } void show() { cout << "a = " << a << endl; cout << "b = " << b << endl; } }; int main() { Test A(10); A.setB((char*)"Hello World"); A.show(); Test B = A; B.show(); return 0; }
3、成员变量的类型是类类型时
#include <iostream> #include <cstring> using namespace std; class Test { int a; public: Test(int a = 0) // 普通构造函数 { cout << "Test(int a = 0)" << endl; this->a = a; } Test(const Test &other) // 拷贝构造函数 { cout << "Test(const Test &other)" << endl; this->a = other.a; } void show() { cout << "a = " << a << endl; } }; class Data { Test test; int c; public: Data(Test _test = Test(40), int _c = 0) // 建议在声明时显示调用构造函数 { cout << "Data(Test _test = Test(), int _c = 0)" << endl; test = _test; c = _c; } void show() { cout << "Data::show()" << endl; test.show(); cout << "c = " << c << endl; } }; int main() { Data B; B.show(); return 0; }
六、总结
以上就是构造函数的介绍,有些结论可能不是一样的,跟编译器版本有关,但是如果是面试的话,建议直接讲结论。 关于更多有关构造函数的,如派生类,继承那些构造函数放在后面讲。