C++构造函数和拷贝构造函数详解

构造函数、析构函数与赋值函数是每个类最基本的函数。它们太普通以致让人容易麻痹大意,其实这些貌似简单的函数就象没有顶盖的下水道那样危险。

每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。

对于任意一个类A,如果不想编写上述函数,C++编译器将自动为A 产生四个缺省的函数,例如:

A(void); // 缺省的无参数构造函数

A(const A &a); // 缺省的拷贝构造函数

~A(void); // 缺省的析构函数

A & operate =(const A &a); // 缺省的赋值函数

这不禁让人疑惑,既然能自动生成函数,为什么还要程序员编写?原因如下:

<1>如果使用“缺省的无参数构造函数”和“缺省的析构函数”,等于放弃了自主“初始化”和“清除”的机会,C++发明人Stroustrup 的好心好意白费了。

<2>“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。

C++ 默认构造函数 :

1、每个类必须有一个构造函数,否则没法创建对象;
2、若 程序员没有提供任何构造函数,则
C++提供一个默认的构造函数,该默认构造函数是无参构造函数,它仅负责创建对象,不做任何初始化的工作;
3、只要 programer
定义了一个构造函数(不管是无参还是有参构造),C++就不再提供默认的默认构造函数。即如果为类定义了一个带参的构造函数,还想要无参构造函数,就必须自己定义;

4、与变量定义类似,在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的位模式全为
0,否则,对象值是随机的。

C++默认拷贝构造函数:
1、默认的拷贝构造函数执行的顺序与其他用户定义的构造函数相同,执行先父类后子类的构造.
2、拷贝构造函数对类中每一个数据成员执行成员拷贝(memberwise
Copy)的动作.
3、如果数据成员为某一个类的实例,那么调用此类的拷贝构造函数.
4、如果数据成员是一个数组,对数组的每一个执行按位拷贝.

5、如果数据成员是一个数量,如int,double,那么调用系统内建的赋值运算符对其进行赋值.

请看下面代码:

#include <iostream>
#include <string>
using namespace std;

class Student
{
public:
	Student()
	{
		cout << "构造函数1" << endl;
	}
	Student(int k)
	{
		cout << "构造函数2" << endl;
		i = k;
	}
	Student(Student const &m)
	{
		cout << "拷贝构造函数" << endl;
		i = m.i * (-1);
	}

	void p()
	{
		cout << i << endl;
	}
	~Student()
	{
		cout << "析构函数" << endl;
	}
protected:
	int i;
};

int main(int argc, char **argv)
{
	Student s(9818);
	// 调用构造函数2
	s.p();

	Student t(s);
	// 调用拷贝构造函数
	t.p();

	Student k = s;
	// 调用拷贝构造函数
	k.p();

	Student *p = new Student(s);
	// 调用拷贝构造函数
	p->p();

	Student m;
	// 调用构造函数1
	m = s;// 赋值运算
	m.p();

	return 0;
}

  运行结果:

构造函数2
9818
拷贝构造函数
-9818
拷贝构造函数
-9818
拷贝构造函数
-9818
构造函数1
9818
析构函数
析构函数
析构函数
析构函数

下面我们来讨论一下关于浅拷贝和深拷贝的问题。

深拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发生复制过程的时候(复制指针所指向的值),这个过程就可以叫做深拷贝,反之对象存在资源但复制过程并未复制资源(只复制了指针所指的地址)的情况视为浅拷贝。
  很多人会问到,既然系统会自动提供一个默认的拷贝构造函数来处理复制,那么我们没有必要去自定义拷贝构造函数呀,对,就普通情况而言这的确是没有必要的,但在某些状况下,类体内的成员是需要开辟动态堆内存的,如果我们不自定义拷贝构造函数而让系统自己处理,那么就会导致堆内存的所属权产生混乱。试想一下,已经开辟的一端堆地址原来是属于对象a的,由于复制过程发生,b对象取得是a已经开辟的堆地址,一旦程序产生析构,释放堆的时候,计算机不清楚这段地址是真正属于谁的,当连续发生两次析构的时候就出现了运行错误。
  为了更详细的说明问题,请看如下的代码。

#include <iostream>
#include <string>
using namespace std;

class aa
{
public:
    aa()
    {
        cout << "调用构造函数" << endl;
        f = new char[10];
    }
    ~aa()
    {
        cout << "调用析构函数" << endl;
        delete[] f;
    }
    char * f;
};

int main(int argc, char **argv)
{
    aa p;
    printf("p.f=%p\n",p.f);
    strcpy(p.f, "Computer");
    cout << p.f << endl;
    aa q(p);// 调用默认的拷贝构造函数
    printf("q.f=%p\n",q.f);
    cout << q.f << endl;

    strcpy(p.f, "Software");
    cout << p.f << endl;
    cout << q.f << endl;

    strcpy(q.f, "Software");
    cout << p.f << endl;
    cout << q.f << endl;

    return 0;
}

运行结果:

调用构造函数
p.f=003F1048
Computer
q.f=003F1048
Computer
Software
Software
Software
Software
调用析构函数
调用析构函数

通过上面的例子,我们能清楚的看到,第二个对象调用拷贝构造函数,q.f获得的地址值与p.f相同,即指向了同一片内存区域。程序结束时,会两次调用析构函数,即同一个指针执行两遍delete操作,会发生什么呢?这可能是一场灾难,可能会破坏该堆及自由内存表。

那我们该如何避免呢?这里我们就需要使用深拷贝。

#include <iostream>
#include <string>
using namespace std;

class aa
{
public:
    aa()
    {
        cout << "调用构造函数" << endl;
        f = new char[10];
    }
    aa(aa const & s)
    {
        cout << "调用拷贝构造函数" << endl;
        f = new char[10];
        strcpy(f, s.f);
    }
    ~aa()
    {
        cout << "调用析构函数" << endl;
        delete[] f;
    }
    char * f;
};

int main(int argc, char **argv)
{
    aa p;
    printf("p.f=%p\n", p.f);
    strcpy(p.f, "Computer");
    cout << p.f << endl;
    aa q(p);// 调用用戶的拷贝构造函数
    printf("q.f=%p\n", q.f);
    cout << q.f << endl;

    strcpy(p.f, "Software");
    cout << p.f << endl;
    cout << q.f << endl;

    strcpy(q.f, "Software");
    cout << p.f << endl;
    cout << q.f << endl;

    return 0;
}

运行结果:

调用构造函数
p.f=00351048
Computer
调用拷贝构造函数
q.f=00351060
Computer
Software
Computer
Software
Software
调用析构函数
调用析构函数

现在我们可以看到,p.f和q.f分别指向不同的内存区域。

来自:http://blog.csdn.net/sg131971/article/details/7045278

时间: 2024-08-08 01:58:35

C++构造函数和拷贝构造函数详解的相关文章

【转】 c++拷贝构造函数(深拷贝,浅拷贝)详解

c++拷贝构造函数(深拷贝,浅拷贝)详解 2013-11-05 20:30:29 分类: C/C++ 原文地址:http://blog.chinaunix.net/uid-28977986-id-3977861.html 一.什么是拷贝构造函数      首先对于普通类型的对象来说,它们之间的复制是很简单的,例如: int a=100; int b=a; 而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量.  下面看一个类对象拷贝的简单例子. #include<iostream

c++ 构造函数,拷贝构造函数,析构函数与赋值操作符

题目: 为下面的Rectangle类实现构造函数,拷贝构造函数,赋值操作符,析构函数. class Shape { int no; }; class Point { int x; int y; }; class Rectangle: public Shape { int width; int height; Point * leftUp; public: Rectangle(int width, int height, int x, int y); Rectangle(const Rectang

C++中构造函数,拷贝构造函数,析构函数

C++中默认构造函数就是没有形参的构造函数.准确的说法,按照<C++ Primer>中定义:只要定义一个对象时没有提供初始化式,就是用默认构造函数.为所有 的形参提供默认实参的构造函数也定义了默认构造函数. 合成的默认构造函数,即编译器自动生成的默认构造函数.<C++ Primer>中的说明:一个类哪怕只定义了一个构造函数,编译器也不会再生成默认构造函数.这条规则的根据是,如果一个类再某种情况下需要控制对象初始化,则该类很可能在所有情况下都需要控制.只有当一个类没有定义构造函数时,

构造函数、拷贝构造函数和析构函数的的调用时刻及调用顺序

构造函数.拷贝构造函数和析构函数的的调用时刻及调用顺序 对象是由“底层向上”开始构造的,当建立一个对象时,首先调用基类的构造函数,然后调用下一个派生类的构造函数,依次类推,直至到达派生类次数最多的派生次数最多的类的构造函数为止.因为,构造函数一开始构造时,总是要调用它的基类的构造函数,然后才开始执行其构造函数体,调用直接基类构造函数时,如果无专门说明0,就调用直接基类的默认构造函数.在对象析构时,其顺序正好相反.   下面简单介绍下这三个函数. 构造函数       1.构造函数不能有返回值  

【编程题】编写String类的构造函数、拷贝构造函数、析构函数和赋值函数

[编程题]编写String类的构造函数.拷贝构造函数.析构函数和赋值函数 [题目]:请编写如下4个函数 1 class String 2 { 3 public: 4 String(const char *str = NULL);// 普通构造函数 5 String(const String &other); // 拷贝构造函数 6 ~ String(void); // 析构函数 7 String & operate =(const String &other);// 赋值函数 8

构造函数、拷贝构造函数、析构函数

构造函数:在对象被创建时利用特定的初始值构造对象,即初始化对象. 拷贝构造函数:用一个已经存在的对象去初始化另一个对象,这两个对象的类型应该是一样的. 格式: class 类名 { public: 类名(形参);                 //构造函数 类名(类名 &对象名);    //拷贝构造函数 ... }; 类名::类名(形参)            //构造函数的实现 { 函数体 } 类名::类(类名 &对象名)      //拷贝构造函数的实现 { 函数体 } 构造函数在

C++ 构造函数_拷贝构造函数

拷贝构造函数 系统自动生成的函数: 普通构造函数 拷贝构造函数 如果自己定义了普通构造函数,系统不会再自动生成普通构造函数: 如果自己定义了拷贝构造函数,系统不会再自动生成拷贝构造函数. ***如果没有自定义的拷贝构造函数则系统自动生成一个默认的拷贝构造函数. ***当采用直接初始化或者复制初始化实例对象时,系统自动调用拷贝构造函数. 拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象.拷贝构造函数通常用于: 通过使用另一个同类型的对象来初始化新创

CPP_类默认函数:构造函数,拷贝构造函数,赋值函数和析构函数

类默认函数:构造函数,拷贝构造函数,赋值函数和析构函数 class Person{ public : Person():age(10), name(NULL); Person(int myage, char *myname); Person(const Person &a); ~Person(void); void set_age(int myage); void get_age(void); void set_other_age(Person &a, int b); private: i

C++空类编译器自动生成的6个成员函数、关于构造函数、拷贝构造函数的解释

对于空类,编译器不会生成任何的成员函数,只会生成1个字节的占位符. 有时可能会以为编译器会为空类生成默认构造函数等,事实上是不会的,编译器只会在需要的时候生成6个成员函数:默认构造函数.默认拷贝构造函数.默认析构函数.默认赋值运算符 这四个是我们通常大都知道的.但是除了这四个,还有两个,那就是取址运算符和 取址运算符 const. class Empty { public: Empty(); //缺省构造函数 Empty(const Empty &rhs); //拷贝构造函数 ~Empty();