a. C++标准中提到“The default constructor, copy constructor and copy assignment operator, and destructor are special member functions.[Note: The implementation will implicitly declare these member functions for some class types when the program does not explicitly declare them. The implementation will implicitly define them if they are used.]”。即缺省构造函数、拷贝构造函数、拷贝赋值操作符和析构函数是特殊成员函数。
b. “Constructors do not have names. A special declarator syntax using an optional sequence of function- specifiers(inline, virtual and explicit) followed by the constructor’s class name followed by a parameter list is used to declare or define the constructor.” 构造函数没有名称。
c. 构造函数不能有返回类型,也不能由virtual, const, static 和 volatile来修饰。但可以由inline来修饰,事实上隐式构造函数就是用inline来修饰的。inline表示编译时展开,通常速度块;virtual表示运行时绑定,通常意味着灵活。
d. 类中存在虚函数或者有虚基类的情况下需要显式声明构造函数。拷贝构造函数也是如此。
#include <iostream> using namespace std; class A{ public: A(){ cout << "A" << endl;} //virtual ~A(){ cout << "~A" << endl;} ~A(){ cout << "~A" << endl;} }; class B : public A{ public: B(){ cout << "B" << endl;} ~B(){ cout << "~B" << endl;} }; int main() { A *a = new B(); delete a; return 0; }
输出:
A
B
~A
请按任意键继续. . .
一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。我想所有的C++程序员都知道这样的危险性。当然,如果在析构函数中做了其他工作的话,那你的所有努力也都是白费力气。
这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。
X::X(X& x)
X::X(const X& x)
X::X(X& x, int a = 0, int b = 1…)
g. 什么时候会调用拷贝构造函数?
以下三种情况出现时,会调用一个类的拷贝构造函数:
1) 用一个已经实例化了的该类对象,去实例化该类的另外一个对象;
2) 用该类的对象传值的方式作为一个函数的参数;
3) 一个函数返回值为该类的一个对象。
#include <iostream> using namespace std; class CA { public: int a; int b; public: inline CA() { a = 1; b = 1; } inline CA(int A, int B) { a = A; b = B; } inline CA(CA& x) { a = x.a; b = x.b; cout << "copy constructor is called." << endl; } void printInfo() { cout << "a = " << a << ", b = " << b << endl; } }; int someFun1(CA x) { return x.a + x.b; } CA someFun2(int a, int b) { CA ca(a, b); return ca; } int main(void) { CA a; // CA b(); // 不能用这种方式声明CA的对象b! CA c(10, 10); CA d(c); // 情况1) -> 调用拷贝构造函数 int anInt = someFun1(c); // 情况2) -> 调用拷贝构造函数 CA e = someFun2(11, 11); // 情况3) -> 调用拷贝构造函数 return 0; }
运行结果:
copy constructor is called.
copy constructor is called.
copy constructor is called.
运行结果表明,上述结论是正确的。
h. 什么时候必须要显式声明拷贝构造函数?
拷贝构造函数的作用就是用一个已经实例化了的该类对象,去实例化该类的另外一个对象。
1) 下面的代码并没有显式声明一个构造函数,编译器会自动为类CExample1生成一个缺省的隐式拷贝构造函数:
#include <iostream> using namespace std; class CExample1 { private: int a; public: CExample1(int b){a = b;} void SetValue(int a){this->a = a;} void Show(){cout << a << endl;} }; int main(void) { CExample1 A(100); CExample1 B = A; // 调用了缺省的隐式拷贝构造函数 CExample1 C(B); // 调用了缺省的隐式拷贝构造函数 B.Show(); // 输出应该是100 B.SetValue(90); B.Show(); // 输出应该是90 A.Show(); // 输出应该是100 C.Show(); // 输出应该是100 return 0; }
输出为:
100
90
100
100
2) 如果有成员变量以指针形式存在,涉及动态内存分配等情况下,一定要显式声明拷贝构造函数。要注意到,如果需要显式定义拷贝构造函数,那么通常都是需要同时定义析构函数(因为通常涉及了动态内存分配),至于是否必须重载操作符“=”,要视情况而定。
#include <iostream> using namespace std; class CSomething { public: int a; int b; public: CSomething(int a, int b) {this->a = a; this->b = b;} }; class CA { private: CSomething* sth; // 以指针形式存在的成员变量 public: CA(CSomething* sth){this->sth = new CSomething(sth->a, sth->b);} ~CA() { cout << "In the destructor of class CA..." << endl; if (NULL != sth) delete sth; } void Show(){cout << "(" << sth->a << ", " << sth->b << ")" << endl;} void setValue(int a, int b){sth->a = a; sth->b = b;} void getSthAddress() { cout << sth << endl; } }; int main(void) { CSomething sth(1, 2); CA ca(&sth); ca.Show(); CA cb(ca); // 调用缺省的隐式拷贝构造函数 cb.Show(); cb.setValue(2, 3); ca.Show(); cb.Show(); ca.getSthAddress(); cb.getSthAddress(); return 0; }
可见,ca和cb中的指针成员变量sth指向的是同一个内存地址(Console输出的第5、6行),这就是为什么在cb.setValue(2, 3)后,ca对应的内容也发生了改变(Console输出的第3、4行),而这不是我们所期望的;其次,我们生成了两个对象ca和cb,因此对两次调用析构函数,第一次调用析构函数的时候没有问题,因为此时sth里面有内容,第二次调用析构函数时,sth里面的内容由于在第一次调用析构函数的时候已经被delete了,所以会出现如上的错误提示。
保持其他代码不变,现在我们增加一个拷贝构造函数如下: