关于构造函数,咱们耳熟能详,彷佛都没有必要成为一个知识点,或者说是重要的知识点拿出来特殊说明,毕竟C++的编译器都能帮咱们完成这个工做,只是,事情真的如想象的那么简单么;ios
可能不是。c++
本文试图挖掘关于构造函数,可能不是那么简单的一面,固然也不会很全面,权当一块儿学习了。ide
构造函数的概念:提供类的对象的初始化的方式,类经过一个或几个特殊的成员函数来控制对象的初始化过程。函数
有这个概念出发,咱们能够知道,全部的构造函数都是在类的对象初始化时由系统调用的,具体调用哪一个是按重载函数的调用规则来的。工具
备注:构造函数不能被声明为const。能够想一想为什么?学习
构造函数也不能是虚函数,这个应该好解释。测试
这个最简单,在面向对象的世界里,万物皆是对象,由于万物皆须要构造函数,若是咱们没有定义一个构造函数,那么就由C++的编译器帮咱们完成,在《c++ primer》里叫作合成的默认构造函数。this
下面开始咱们的编码求学之旅:编码
首先,定义一个类设计者工具类:spa
#include <iostream> using namespace std; class ClassDesignTool { public: void printSp(){ cout << sp_ << "\n"; } private: string *sp_; };
在这样一个什么没有写构造函数的类里,默认构造函数依然会在编译阶段生成,测试代码以下:
ClassDesignTool tool; tool.printSp();
在VS2010的编译环境下的结果是CCCCCCCC
,看到这个你应该很熟悉,这是Windows环境下对全部未显式赋值变量的默认赋值,这也就能证实,Windows系统在编译后使用默认合成构造函数,将成员变量sp_赋值为CCCCCCCC
了。
若是你不放心,能够把默认构造函数加上去,
ClassDesignTool(){};
测试的结果是同样的。
这说明,若是你不许备在类的对象初始化时作点什么,彻底能够把这件事交给编译器。反之,咱们须要作点别的工做了。
可能,你认为默认的合成构造函数什么事也没作,对它心有怨恨,因此你决定出马把它改写(覆盖之)。
ClassDesignTool():sp_(new string("lcksfa")){ cout << "use override default constructor " << "\n"; } //打印函数同时修改 void printSp(){ cout << "sp_ is " << sp_->c_str() << "\n"; }
测试结果:
use override default constructor sp_ is lcksfa
如今,咱们覆盖(override)了默认构造函数,合成的默认构造函数不会被调用,而调用咱们本身的构造函数。
函数重载(overload)的概念,我相信你们都不会陌生,对于构造函数,一样的也能将其重载。和调用普通的重载函数同样,系统会在初始化对象时,根据不一样的参数类型去调用不一样的重载构造函数:
在上面的代码里添加以下代码:
//overload constructor ClassDesignTool(const string& str) :sp_(new string(str)){ std::cout << "use overload constructor " << "\n"; }
以上,咱们重载了一个构造函数,其参数为一个const string&类型。
ClassDesignTool tool4(string("4")); tool4.printSp();
测试结果以下:
use overload constructor sp_ is 4
这说明,当咱们添加了构造函数的重载函数后,使用string("4")参数构造对象时,调用了咱们的string参数的构造函数。
上面的东西都很简单,下面,咱们说下稍微复杂的。
从函数重载层面,拷贝构造函数也是构造函数的重载,只是其参数为本类的const引用,以下:
//copy constructor ClassDesignTool(const ClassDesignTool&);
ClassDesignTool::ClassDesignTool(const ClassDesignTool& rhs) { std::cout << "use copy constructor from " << rhs.sp_->c_str() << "\n"; sp_ = new string(*(rhs.sp_)); }
何时调用?
ClassDesignTool tool("lcksfa"); ClassDesignTool tool2(tool); tool2.printSp();
测试输出:
use overload constructor use copy constructor from lcksfa sp_ is lcksfa
以上代码说明,tool是使用的构造函数初始化,其参数为"lcksfa",而tool2是使用拷贝构造函数初始化,其参数为tool。
说完构造函数,说下析构函数。咱们知道对象在建立时调用了构造函数,而在销毁时则会调用析构函数。
//destructor ~ClassDesignTool(){ std::cout <<"use destructor "<<sp_->c_str()<<"\n"; delete sp_; }
以上是析构函数,事实上,我已经把默认的析构函数给覆盖了,缘由在于sp_的内存释放,若是使用合成的默认析构函数,系统将不会释放sp__的内存,从而致使内存泄漏。
和构造函数不一样,析构函数没有重载函数。这一点和人生很像啊。
每个构造函数都是 由两部分组成的,一个是初始化部分,另外一个才是函数体,成员的初始化是在函数体执行以前完成的,因此你的代码里也须要作这两个部分的区分,不要把成员的初始化和函数体混为一体,由于,可能会影响析构函数的执行(只是,没有你想的那么严重)。由于一个析构函数,其也是由函数体和其析构部分组成的,析构时,先执行函数体,再执行销毁操做,成员按构造的初始化列表的逆序销毁。
若是你须要覆盖重写析构函数体,那么几乎能够确定你还须要拷贝构造函数和拷贝赋值运算符。
举例子,我在上面的程序中重写了析构函数,由于我须要显示释放sp_的内存,按上面的程序看,还可能出现什么问题呢?毕竟我没有拷贝赋值运算符函数。在测试函数中添加如下代码:
ClassDesignTool tool ; { ClassDesignTool tool2("not me"); tool2 = tool; // tool.printSp(); tool2.printSp(); }
测试输出:
use override default constructor use overload constructor sp_ is lcksfa use destructor lcksfa use destructor ///奔溃了!!!
使用大括号{}将tool2的赋值部分封起来,确保tool2先析构。
程序输出后,到tool析构处就奔溃了!
缘由何在?
由于这里的系统默认的赋值运算是直接将sp_ 的值进行赋值,而没有去拷贝sp_ 指向的内存,tool2离开做用域时调用析构将sp_ delete掉了,等到tool离开做用域时,尝试delete的仍是同一块内存,因而就出现了double delete的问题!
这种状况的解决方案之一就是咱们本身定义一个赋值操做运算符:
ClassDesignTool& ClassDesignTool::operator=(const ClassDesignTool& rhs) { std::cout << "use copy-assignment operaotr"<<"\n"; auto spNew = new string(*(rhs.sp_)); delete sp_; sp_ = spNew; return *this; }
本函数的写法颇为模式化:
总结起来就是 综合了析构和构造函数的操做。销毁了左值运算对象的资源,而从右值运算对象中拷贝资源。
小结:本文初略的说明了构造函数、析构函数和拷贝赋值运算符的重载,能够做为入门者的参考。