#include <iostream> using namespace std; class Test { public: // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的 Test(); //不带参数的构造函数称为默认构造函数 private: int num_; }; Test::Test() { num_ = 0; cout << "Initializing Default" << endl; } int main(void) { Test t; return 0; }
4、回顾
在C++中类型大致可以分为三种
第一种、内置类型
如int, char, float, unsigned等。内置类型是最基本的类型。
第二种、复合类型
复合类型:使用其它类型定义的类型。有三种复合类型:引用,指针,数组。
第三种、类类型
就是类。比如string以及自己定义的类。
5、若使用编译器自动生成的默认构造函数(或自己定义一个未进行任何操作的默认构造函数),则类中的每个成员,使用与初始化变量相同的规则来进行初始化。
——类类型成员:运行该类型的默认构造函数来初始化
——内置或符合类型的成员:依赖于对象的作用域,在局部作用域中这些成员不被初始化,而在全局作用域中他们被初始化为0。
例子:
下面代码中a,b的各个成员变量值时多少?
class Student { public: Student(){} void show(); private: string name; int number; int score; }; Student a; int main() { Student b; }
解答:a与b的name都调用string类的默认构造函数初始化,a是全局对象,故a的number与score初始化为0;而b是局部对象,故b的number与score不被初始化,为垃圾值。
1、构造函数可以被重载
一般而言,不同的构造函数允许用户指定不同的方式来初始化数据成员。构造函数可以有任意类型和任意个数的参数,一个类可以有多个构造函数(重载)。
2、实参决定使用哪个构造函数
Sales_item(); //Sales_item empty; Sales_item(const string &); //Sales_item Primer_3td_Ed("0-201-82470-1"); Sales_item(std::istream &); //Sales_item Primer_4th_Ed(cin);
3、构造函数自动执行
创建类类型的新对象,编译器自动会调用构造函数
//Test.h #ifndef _TEST_H_ #define _TEST_H_ class Test { public: // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的 // 默认的构造函数 Test(); Test(int num); void Display(); ~Test(); private: int num_; }; #endif // _TEST_H_
//Test.cpp #include "Test.h" #include <iostream> using namespace std; // 不带参数的构造函数称为默认构造函数 Test::Test() { num_ = 0; cout<<"Initializing Default"<<endl; } Test::Test(int num) { num_ = num; cout<<"Initializing "<<num_<<endl; } Test::~Test() { cout<<"Destroy "<<num_<<endl; } void Test::Display() { cout<<"num="<<num_<<endl; }
//01.cpp #include "Test.h" #include <iostream> using namespace std; Test t(10); int main(void) { cout<<"Entering main ..."<<endl; cout<<"Exiting main ..."<<endl; return 0; }
Initializing 10
Entering main ...
Exiting main ...
Destroy 10
解释:在return 0 时全局变量的生存期也到了,故也会自动调用析构函数。
//02.cpp #include "Test.h" int main(void) { Test t; t.Display(); Test t2(10); t2.Display(); Test* t3 = new Test(20); // new operator t3->Display(); delete t3; return 0; }
Initializing Default
num=0
Initializing 10
num=10
Initializing 20
num=20
Destroy 20
Destroy 10
Destroy 0
类名::~默认析构函数名( ) { }
(7)何时调用析构函数
撤销类对象时会自动调用析构函数:动态分配的对象只有在指向该对象的指针被删除时才撤销,如果没有删除指向动态对象的指针,则不会运行该对象的析构函数,对象就会一直存在,从而导致内存泄漏,而且,对象内部使用的任何资源也不会释放!
//03.cpp #include "Test.h" int main(void) { Test t[2] = {10, 20}; Test* t2 = new Test(2); delete t2; Test* t3 = new Test[2]; delete[] t3; return 0; }
Initializing 10
Initializing 20
Initializing 2
Destroy 2
Initializing Default
Initializing Default
Destroy 0
Destroy 0
Destroy 20
Destroy 10
解释:注意 Test t[2] = {10, 20}; 中10,20是当作参数传递给每个对象的构造函数的,如果没有对应的构造函数,比如只有2个参数的构造函数,那么编译是失败的。
实际上,构造函数和析构函数都是可以被显式调用的,只是很少这样做。
1、示例(Test.cpp和Test.h 同上)
#include "Test.h" int main(void) { Test t; t.~Test(); // 析构函数可以显式调用,但一般很少用。 return 0; }
十、转换构造函数
4、示例(Test.cpp和Test.h 同上)
//05.cpp #include "Test.h" int main(void) { Test t(10); // 带一个参数的构造函数,充当的是普通构造函数的功能 t = 20; // 将20这个整数赋值给t对象(调用转换构造函数) // 1、调用转换构造函数将20这个整数转换成类类型 (生成一个临时对象) // 2、将临时对象赋值给t对象(调用的是=运算符) // 3、最后将临时对象20释放 Test t2; //为方便观察临时对象20释放的现象 return 0; }
运行结果:
Initializing 10
Initializing 20
Destroy 20
Initializing Default
Destroy 0
Destroy 20
解释:可以看到初始化了一个临时对象,传递参数20,然后调用赋值运算符operator=,接着释放临时对象,最后释放的对象是已经被更改过的t 。赋值运算符的格式为:Test& Test::operator=(const Test& other);事实上如果没有自己实现,编译器也会实现一个默认的赋值运算符。
2、赋值操作(同时会调用转换构造函数)
3、重载“=”运算符
Test& Test::operator=(const Test& other);
4、示例(Test.cpp和Test.h 添加了operator=函数)
//Test.h #ifndef _TEST_H_ #define _TEST_H_ class Test { public: // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的 // 默认的构造函数 Test(); /*explicit */Test(int num); void Display(); Test& operator=(const Test& other); ~Test(); private: int num_; }; #endif // _TEST_H_
//Test.cpp #include "Test.h" #include <iostream> using namespace std; // 不带参数的构造函数称为默认构造函数 Test::Test() { num_ = 0; cout<<"Initializing Default"<<endl; } Test::Test(int num) { num_ = num; cout<<"Initializing "<<num_<<endl; } Test::~Test() { cout<<"Destroy "<<num_<<endl; } void Test::Display() { cout<<"num="<<num_<<endl; } Test& Test::operator=(const Test& other) { cout<<"Test::operator="<<endl; if (this == &other) //当t = t时,就不用再赋值了 return *this; num_ = other.num_; //一般编译器也是类似的生成 return *this; }
//06.cpp #include "Test.h" int main(void) { Test t = 10; // 等价于Test t(10); 这里的=不是运算符,表示初始化。因此也没有调用转换构造函数 t = 20; // 赋值操作,会调用转换构造函数 Test t2; //方便观察 t = t2; // 赋值操作 t.operator=(t2);也没有调用转换构造函数 return 0; }
运行结果:
Initializing 10
Initializing 20
Test::operator=
Destroy 20
Initializing Default
Test::operator=
Destroy 0
Destroy 0
解释:第一条语句是初始化,后面是赋值操作,参照上面临时对象的创建销毁,赋值运算符的调用可以理解输出。
十二、explicit 关键字(抑制由构造函数定义的隐式转换)
1、只提供给类的构造函数使用的关键字,来防止在需要隐式转换的上下文中使用构造函数。
2、编译器不会把声明为explicit的构造函数用于隐式转换,它只能在程序代码中显示创建对象。
3、示例(Test.cpp 同上)
//Test.h #ifndef _TEST_H_ #define _TEST_H_ class Test { public: // 如果类不提供任何一个构造函数,系统将为我们提供一个不带参数的 // 默认的构造函数 Test(); explicit Test(int num); void Display(); Test& operator=(const Test& other); ~Test(); private: int num_; }; #endif // _TEST_H_
//07.cpp #include "Test.h" int main(void) { Test t = 10; Test t2; t2 = 20; return 0; }
运行结果:
编译不通过
由于在构造函数Test(int num); 前面加上explicit 关键字。那么Test t = 10;做初始化的时候是不等价于Test t(10)的;另外的,t2 = 20; 这种语句都是编译不通过的;因为不允许隐式转换。