C++的构造、析构函数

来源:互联网 发布:加载寄售物品数据库 编辑:程序博客网 时间:2024/06/11 06:39

构造、析构函数的由来

由于C++类是许多成员函数和成员变量的集合,在实例化一个类时肯定需要将其内部变量初始化,而这个工作如果由程序员在创建对象后显示调用初始化函数完成,恐怕难免会有疏漏,所以C++之父发挥程序员“”懒惰“的特性,规定了两个函数分别是构造和析构函数,来完成初始化和清理工作,而这两个函数很重要的一点是,他们是在对象创建和消亡时自动执行的。


特别的是,如果程序员没有定义构造和析构函数,编译器会自动生成,但这只是需有其表而已,有时还有带来错误,例如自动生成的拷贝构造函数,是按位拷贝的,当有指针变量时,就会带来问题,所以建议还是主动定义构成和析构函数,而像拷贝构造函数和赋值函数,不需要使用最好主动禁止它们,方法是在private权限下只声明而不定义它们。


class String{public:        String(const char *str = NULL);        String(const String &other);        ~String();        String& operator=(const String &other);private:        char *m_data;};String::String(const char *str){        if (NULL == str)        {                m_data = new char[1];                *m_data = '\0';        }        else        {                m_data = new char[strlen(str)+1];                strcpy(m_data, str);        }}String::String(const String &other){        m_data = new char[strlen(other.m_data)+1];        strcpy(m_data, other.m_data);}String::~String(){        delete[] m_data;/*        delete m_data;*/}String& String::operator =(const String &other){        String tmp(other);        char *orig = m_data;        m_data = tmp.m_data;        tmp.m_data = orig;        return *this;/*        if (this == other)                return *this;        delete[] m_data;        m_data = new char[strlen(other.m_data)+1];        strcpy(m_data, other.m_data);        return *this;*/}



以上代码是String类的简单实现,从中我们可以总结几点:


1、构造函数和析构函数的函数名必须与类名相同,保证编译器能明确识别。而且都不能有返回值,这也许是由于它们在对象创建和消亡时自动执行,没必要返回任何值,又或许是因为没有返回值显得比较另类,以突出它们的特殊性吧。注意赋值函数不是构造函数,从返回值这一点就能看出。


2、构造函数可以有多个,包含一个拷贝构造函数,以提供灵活的对象初始化方式,而这也是重载存在的必要性。但析构函数不能有多个,不然编译器无法确定使用哪个。


3、拷贝构造函数的参数需要为引用类型,如果是传值,那么在实参拷贝到形参时又会调用拷贝构造函数,也就是发生无限递归,这会导致栈溢出,所以C++编译器会直接禁止参数为传值形式的拷贝构造函数声明。


4、赋值函数(针对上述代码):

1)由于原来对象中的申请的空间不一定能满足拷贝需求,所以需要重新申请,但不能简单的释放资源后再申请资源然后拷贝内容。这样存在两个问题,自我赋值和异常安全性。当赋值的两个对象为同一个时,释放了源对象资源也就是释放了被拷贝对象的资源,之后再去拷贝内容时,拷贝的是一段已释放的空间;还有一个问题是如果new执行失败,而对象里的资源已经被释放,那么该对象中相当于保存了一个野指针。

2)处理这两个问题的关键在于不能提前释放对象里的资源,可以向对象里的指针指向新的地址,然后将指向地址里的空间释放,而上述代码里设计更为巧妙,先创建一个局部对象,将内容拷贝到这个局部对象,再互换原对象和局部对象的空间资源,当函数退出后,局部对象会调用析构函数释放空间,而这段空间恰好是原对象的最初那段空间。这样处理后同样也解决了自我赋值的问题。

3)关于new和delete操作符,当new失败时,它并不会返回NULL(可能老版本为了兼容C是这样处理的),而是会抛出异常,而delete如果new时用的是[],delete也需要用[].


参考文献:《高质量C++编程指南》



6、由于构造、析构函数不能够继承,所以派生类的构造函数应该在初始化列表中调用基类的构造函数;

7、基类和派生类的构造函数应该为虚(virtual);

8、构造和析构的次序

构造函数遵循从内向外构造的次序,即先构造基类,再派生类,然后一层层向外的次序。

析构函数正好想法,即先析构最外层,逐渐向内层析构。


0 0
原创粉丝点击