C++类中函数(构造函数、析构函数、拷贝构造函数、赋值构造函数)

 

【1】为什么空类可以创建对象呢?

示例代码如下:

#include <iostream>
using namespace std;

class Empty
{
};

void main()
{
    Empty obj1;
    cout << sizeof(Empty) << endl;  // 1
}

让我们先看看这个例子。既然都没有构造函数,怎么实现对象obj1的构建呢?

哦,经过大脑的回旋式搜索,忆得有一本书上说过,当用户定义一个空类(如上)时,编译器就会为这个类默认生成六个方法。

既然是编译器默认完成的工作,那我们只要知道具体是那些方法,其余就学习编译器的原理了。

那么,编译器生成了那六个方法:示例代码如下:

class Empty
{
public:
    Empty();                      // 默认构造方法
    Empty(const Empty &);         // 拷贝构造函数
    ~Empty();                     // 析构函数
    Empty &operator=(const Empty &);   // 赋值构造函数
    Empty *operator &();               // 取地址
    const Empty * operator&() const;   // 常对象取地址
};

OK,这就是默认生成的那六个方法。其中前四个是经常谈及到的,也是平常定义一个类时,尤其要慎重考虑的。

到这里,也许有人还看出了点问题。那就是为什么空类的大小是1呢??呵呵~~编译器搞的鬼?其实也不是随随便便搞的鬼,我们说:存在即是合理的!这样做肯定有它的道理。

大家试想一个极端问题,仍然是上面这个空类,我们定义一个语句:Empty ar[10]; // 一个包含10个Empty对象的数组。

好啦,如果sizeof(Empty) == 0,那么我们如何区分数组中的十个元素呢??你想想这是不是狭隘的表现吗?

所以说,为了更完善,更健壮,更伟大。编译器插入这个字节,是为了在使用这个类定义多个对象的时候能保证每个对象有自己的地址。

另外,大家看看这个例子:

Empty *pa1, *pa2;
    pa1 = new Empty();
    pa2 = new Empty();
    // ..
    if (pa1 == pa2)    // 如果不分配内存,这个比较就会失去意义
    {
    }

如果不分配内存,如上的代码会不成立!

【2】什么是构造函数?使用构造函数有哪些注意事项?

(1)构造函数

构造函数是一种特殊的方法,主要作用在创建对象时初始化对象,即为对象的属性(数据成员)赋初始值,一般与 new 运算符一起使用在创建对象的语句中。

在C++重载机制下,一个类可以有多个构造函数,可根据其参数个数的不同、或参数类型的不同、或参数顺序的不同来区分它们,即构造函数的重载。

(2)使用构造函数注意事项:

<1> 构造函数的函数名必须与类名相同,而且没有返回值,更不能用void来修饰。

<2> 当一个类没有定义构造函数时,编译器会为每个类添加一个默认的构造函数。默认构造函数访问权限是public的,且为inline函数(常识)。

<3> 构造函数允许重载。(各个不同的构造函数创建的对象不同,即所谓的个体存在先天性差异)

<4> 构造函数不能被直接调用,必须通过new运算符在创建对象时才会自动调用。

<5> 在一个类中,只要有用户自定义的一个构造函数,那么这个类的默认构造函数就不再存在。

那么,也就意味着,想再实现像默认构造函数那样创建对象的形式,必须另外添加一个默认构造函数,或用户定义一个复合默认构造函数。

为了准确理解,请看以下示例代码:

第一、默认构造函数创建对象

/*
 * 默认构造函数创建对象
 */
class Test
{
private:
    int a;
};

void main()
{
    Test t1;  // 默认构造函数创建对象
}

第二、自定义构造函数创建对象

/*
 * 自定义构造函数
 */
class Test
{
private:
    int a;

public:
    Test(int x) : a(x)
    {}
};

void main()
{
//  Test t1;       // error! 编译器提示:no appropriate default constructor available
    Test t2(100);  // OK 因为有相应类型的构造函数(自定义构造函数)
}

第三、复合默认构造函数创建对象

/*
 * 复合默认构造函数
 */
class  Test
{
private:
    int a;

public:
    // Test(){};      // error!!  编译时出错,因为下面的这个是复合默认构造函数,二者冲突
    Test(int x = 10) : a(x) 
    {}
};

void main()
{
    Test  t1;       // OK 因为构造函数有默认值
    Test  t2(100);  // OK 因为有相应类型的构造函数 
}

<6> 构造函数一般不建议定义为私有的,因为在外部无法实现对象的创建。但是,特殊需要的情况下也是可以的这样实现的。

<7> 构造函数有两种方式实现对数据成员的初始化。

1:使用初始化列表进行初始化(以上<5>条第二示例代码可见)

2:在函数体中进行初始化

<8> 在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的位模式全为 0,否则,对象值是随机的。

<9> 基类构造函数负责构建基类的对象,派生类的构造函数负责构建派生类的对象。派生类对象创建是由基类开始进行的。

<10> 构造函数不可以声明为const,也是完全没有必要的。

<11> 注意:分清楚对象与函数声明问题。示例代码如下:

class  Test
{
private:
    int a;
public:
    Test(){};    
};
void main()
{
    Test  t1;     //对象?
    Test  t2();   //对象?函数?
}

注意:第十一行:编译器是以函数声明进行编译的,不是一个对象!

【3】什么是析构函数?使用析构函数有哪些注意事项?

(1)析构函数

我们已经了解了C++的编程哲学观是面向对象的,那么程序在运行时,创建了很多的对象,而当初系统在创建这些对象时,是为各自都分配有一定的内存空间的。

现在,程序要结束了!我们就要释放建立这些对象所占用的内存资源。而析构函数就是完成这些善后工作的。

(2)析构函数注意事项:

<1> 构造函数的函数名必须与类名相同,与构造相反再加一个非(~)。而且无参数没有返回值,更不能用void来修饰。

<2> 当一个类没有定义析构函数时,编译器会为每个类添加一个默认的析构函数。默认访问权限是public的,并且inline函数(常识)。

<3> 析构函数不可以重载,一个类中只允许有一个析构函数。

<4> 建立对象时若用new开辟了一片内存空间,应在退出前在析构函数中用delete全部释放。

<5> 析构函数仅仅只是一个普通成员函数。

<6> new 与 delete操作符详解

请看下面的示例代码以及说明:

new操作符使用示例:

Test* ptr = new Test();

这一条语句,new操作符完成了两件事。

1:分配足够的内存以便容纳所需类型的对象。

2:调用构造函数初始化内存中的对象。

而且,你始终记住,new操作符的使命,或者说赋予它的能力就是这么大,只完成这两件事情,不能以任何方式改变它的行为。

delete操作符使用示例:

delete ptr;

这一条语句,delete操作符完成了两件事。

1:调用对象的析构函数,释放对象申请的堆内存。

2:释放对象本身的内存,彻底释放本次申请的资源。

另外,请看如下两段代码:

代码1:

#include <iostream>
using namespace std;

class Test
{
public:
    Test()
    {
        p = new char[1024];
    }
    ~Test()
    {
        cout << "deconstructor" << endl;
        delete []p;
        p = NULL;
    }

private:
    char *p;
};

void main()
{
    Test* pa = new Test();
    pa->~Test(); // 调用析构函数
}

代码2:

#include<iostream>
using namespace  std;

class Test
{
public:
    Test()
    {
        p = new char[1024];
    }
    ~Test()
    {
        cout << "deconstructor" << endl; 
        delete []p; 
        p = NULL;
    }
private:
    char *p;
};

void main()
{
    Test  *pa = new Test();
    delete pa; // 既调用析构函数,又释放申请的内存资源
}

分析:注意每个示例的注释行:

第一个:仅仅调用了析构函数,析构函数释放了构造函数中申请的堆内存。

第二个:delete不仅隐式的调用了析构函数,并且释放了pa所指向的对象本身内存资源。最后main函数退出前,摧毁掉了临时变量pa指针。

其实,这个道理很简单的。举个现实的例子:见过盖房子和拆房子吧?申请内存就像索取宅基地,构建对象就类似盖房子,析构就像拆房子,而delete不仅拆房子还归还了宅基地。

所以说这也就是问题的关键,我们要归还的不仅仅是你房子占的那么大面积,而是要全部释放掉你当初申请的那么大块的宅基地面积。

【4】拷贝构造函数

(1)为什么需要拷贝构造函数?

因为我们想实现用一个已知对象去创建一个与它完全相同的新对象。

(2)拷贝构造函数有什么特点?

<1> 函数名与类名相同,并且没有返回值类型。

<2> 有且仅有一个参数,并且是同类已知对象的常引用。

<3> 每个类必有一个拷贝构造函数,如果程序员没有定义,系统会添加默认拷贝构造函数。

(3)拷贝构造的参数为什么是对象引用?

如果不是的话,将导致无限递归…..

(4)拷贝构造函数一般在什么时候使用?

<1> 声明一个新的对象时,用已知对象初始化新对象。

示例代码如下:

#include<iostream>
using namespace std;

class  Test
{
private:
    int a;

public:
    Test(int x = 0) : a(x)  
    {
        cout << "construction" << endl;
    } 
    Test(const Test & t)
    {
        cout << "Copy  construction" << endl;
        a = t.a;
    }
};

void main()
{
    Test  t1;      // 调用构造函数
    Test  t2(t1);  // 调用拷贝构造函数
    Test  t3 = t2;   // 调用拷贝构造函数
    system("pause");
} 
/*
construction
Copy  construction
Copy  construction
*/

<2> 当一个已知对象作为一个实参传递给一个函数的形参时,在用实参对象初始化形参对象时,需要调用拷贝构造函数。

<3> 当对象作为一个函数的返回值时,要创建临时对象,临时对象的初始化是调用拷贝构造函数进行的。

关于<2>, <3>点示例代码如下:

#include<iostream>
using namespace std;

class  Test
{
private:
    int a;

public:
    Test(int x = 0) : a(x)  
    {
        cout << "construction" << endl;
    } 
    Test(const Test & obj)
    {
        cout << "Copy  construction" << endl;
        a = obj.a;
    }
};

Test Fun(Test t)
{
    return t;
}

void main()
{
    Test  t1;      // 调用构造函数
    Test  t2(t1);  // 调用拷贝构造函数
    Fun(t2);       // 两次调用拷贝构造函数
    system("pause");
} 

//the  result  of  this:
/*
construction
Copy  construction
Copy  construction
Copy  construction
*/

<4> 在以下两种方式下也是应用拷贝构造函数的

1: 在初始化顺序容器中的元素时。

2: 按照元素初始化列表初始化数组元素时。

示例代码如下:

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

class Test
{
private:
    int a;

public:
    Test(int x = 0) : a(x)  
    {
        cout << "construction" << endl;
    }

    Test(const Test & obj)
    {
        cout << "Copy  construction" << endl;
        a = obj.a;
    }
};

void main()
{
    Test temp;
    vector<Test> vTObjs;
    cout << "初始化顺序容器中的元素:" << endl;
    vTObjs.assign(5, temp);

    cout << "根据元素初始化列表初始化数组元素:" << endl;
    Test Array[5] = {temp, temp, temp, temp, temp};

    system("pause");
}

// run out:
/*
construction
初始化顺序容器中的元素:
Copy  construction
Copy  construction
Copy  construction
Copy  construction
Copy  construction
Copy  construction
根据元素初始化列表初始化数组元素:
Copy  construction
Copy  construction
Copy  construction
Copy  construction
Copy  construction
请按任意键继续. . .
*/

(5)什么是浅拷贝?什么是深拷贝?

其实,浅拷贝深拷贝是很简单的问题。这样说话有点欠拍砖!因为会的人什么都觉得简单。呵呵!请看下面的分析:

想象一下:假如一个类中的数据成员中有一个是指针类型的,那么,当用户创建一个对象,系统就要为这个对象的指针成员对应的分配一块内存。

而如果你还想利用这个创建的对象通过拷贝构造函数去创建一个与其一样的新对象,问题就出现了?什么问题呢?就是你问的问题(嘿嘿~~)。

如果你把新对象的指针成员直接赋值为已知对象的指针成员值,就意味着两个对象指向了同一块内存。后果很严重:

程序结束,在析构对象时,因为有一个先后顺序之分,就势必会对同一块内存释放两次,这将会导致系统崩溃!这就是所谓的浅拷贝隐患。

那深拷贝呢?也就是解决了如上的麻烦。

其解决方案就是重新为新对象的指针成员开辟空间,然后把已知对象的指针所指内容拷贝过去,实现内容的完全一致。并且保证各自独立。

浅拷贝示例代码如下:

#include <iostream>
#include <assert.h>
using namespace std;

char * strcpy2(char *strDest, const char *strSrc)
{
    assert((strDest != NULL) && (strSrc != NULL));
    if (strDest == strSrc)
    {
        return strDest;
    }
    char *address = strDest;
    while (*strDest++ = *strSrc++);
    return address;
}

class String
{
public:
    String(const char *str = NULL);
    String(const String &other);
    ~String();

private:
    char  *m_data;
};

String::String(const char *str)
{
    if (NULL == str)
    {
        m_data = new char[1];
        assert(m_data != NULL);
        *m_data = '\0';
    }
    else
    {
        int length = strlen(str);
        m_data = new char[length + 1];
        assert(m_data != NULL);
        strcpy2(m_data, str);
    }
}

String::String(const String &other)
{
    m_data = other.m_data;
}

String::~String()
{
    delete []m_data;
}

void main()
{
    String  str1("xian");
    String  str2(str1);
}

注意,浅拷贝也是系统默认拷贝构造函数的实现方式,因为存在这种缺陷,所以才有了深拷贝的进一步完善。

深拷贝示例代码如下:

String::String(const String &other)
{
    int length = strlen(other.m_data);
    m_data = new char[length + 1];
    assert(m_data != NULL);
    strcpy(m_data, other.m_data);
}

(6)拷贝构造函数一般为共有的。

【5】赋值构造函数

(1)什么是赋值构造函数?

一个类中赋值运算符的重载方法即就是赋值构造函数。

(2)为什么需要赋值构造函数?

当用户使用内置数据类型时,可以进行顺利的赋值运算操作。

示例代码如下:

1 void main()
2 {
3     int a = 10, b = 20, sum;
4     sum = a + b;
5     cout << sum << endl;
6 }

而如果Programer自己定义新的类型时,由于系统没有实现赋值运算符重载函数,所以是无法进行相关的操作。

示例代码如下:

#include <iostream>
using namespace std;

class Complex  // 复数类
{
public:
    double real; // 实数
    double imag; // 虚数

    Complex (double real = 0, double imag = 0)
    {
        this->real = real;
        this->imag = imag;
    }
};

void main()
{
    Complex com1(10, 20), com2(1, 2), sum;
    sum = com1 + com2;             // error!!编译错误
}

那么,为了处理这种问题,C++可以使用运算符重载机制,实现运算符重载函数。即就是赋值构造函数的由来。

(3)使用运算符重载函数注意事项?

<1> 运算符重载函数其函数名字规定为operator后紧跟重载运算符。比如:operator+(), operator*()等。

现在我们给上述程序声明一个加法运算符的重载函数用于完成复数的加法运算:

示例代码如下:

#include <iostream>
using namespace std;

class Complex  // 复数类
{
public:
    double real; // 实数
    double imag; // 虚数

    Complex (double real = 0, double imag = 0)
    {
        this->real = real;
        this->imag = imag;
    }
};

Complex operator+(Complex com1, Complex com2)   // 实现运算符重载函数
{
    return Complex(com1.real + com2.real, com1.imag + com2.imag);
}

void main()
{
    Complex com1(10, 10), com2(20, 20), sum;
    sum = com1 + com2;    // 或sum = operator+(com1, com2)

    cout << "sum的实数部分为" << sum.real << endl;
    cout << "sum的虚数部分为" << sum.imag << "i" << endl;
}

/*
 *sum的实数部分为30
 *sum的虚数部分为30i
 */

如果仔细观察的话,上面的示例是有很多问题的。

因为这个运算符重载函数是全局的,那也就意味着,如果类中的数据成员不是public,这个方法就无能为力!

为了解决这个问题:“解铃仍需系铃人”C++不是有友元函数吗?OK,看下面的处理方案。

友元函数重载双目运算符。示例代码如下:

#include <iostream>
using namespace std;

class Complex  // 复数类
{
private: // 私有
    double real; // 实数
    double imag; // 虚数

public:
    Complex(double real = 0, double imag = 0)
    {
        this->real = real;
        this->imag = imag;
    }

    friend Complex operator+(Complex com1, Complex com2);      // 友元函数重载双目运算符+
    void showSum();
};

Complex operator+(Complex com1, Complex com2)        // 友元运算符重载函数
{
    return Complex(com1.real + com2.real, com1.imag + com2.imag);
}

void Complex::showSum()
{
    cout << real;
    if (imag > 0)
        cout << "+";
    if (imag != 0)
        cout << imag << "i" << endl;
}

void main()
{
    Complex com1(10, 10), com2(20, -20), sum;
    sum = com1 + com2;    // 或sum = operator+(com1, com2)
    sum.showSum();        // 输出复数相加结果
}

/*
 *30-10i
 */

友元函数重载单目运算符。示例代码如下:

#include <iostream>
using namespace std;

class Point    // 坐标类
{
private:
    int x;
    int y;
public:
    Point(int x, int y)
    {
        this->x = x;
        this->y = y;
    }

    friend void operator++(Point& point);// 友元函数重载单目运算符++
    void showPoint();
};

void operator++(Point& point)          // 友元运算符重载函数
{
    ++point.x;
    ++point.y;
}

void Point::showPoint()
{
    cout << "(" << x << "," << y << ")" << endl;
}

void main()
{
    Point point(10, 10);
    ++point;           // 或operator++(point)
    point.showPoint(); // 输出坐标值
}
/*
 *<11,11>
 */

注意:像赋值运算符=、下标运算符[]、函数调用运算符()等是不能被定义为友元运算符重载函数。

<2> 运算符重载函数可以返回任何类型,甚至是void,但通常返回类型都与它所操作的类类型一 样,这样可以使运算符使用在复杂的表达式中。

比如把上述双目运算符重载函数示例代码中main()主函数里的com1 + com2 改为 com1 + com2 + com2,那么结果又会不一样了。

<3> 对于成员函数重载运算符而言,双目运算符的参数表中仅有一个参数,而单目则无参数。

示例代码如下:

#include <iostream>
using namespace std;

class Complex  // 复数类
{
private: // 私有
    double real; // 实数
    double imag; // 虚数

public:
    Complex(double real = 0, double imag = 0)
    {
        this->real = real;
        this->imag = imag;
    }

    Complex operator+(Complex com1);// 成员函数重载双目运算符+
    void showSum();
};

Complex Complex::operator+(Complex com1)
{
    return Complex(real+com1.real, imag+com1.imag);
}

void Complex::showSum()
{
    cout << real;
    if (imag > 0)
        cout << "+";
    if (imag != 0)
        cout << imag << "i" << endl;
}

void main()
{
    Complex com1(10, 10), com2(20, -20), sum;
    sum = com1 + com2;    // 或sum = com1.operator+(com2)
    sum.showSum();     // 输出复数相加结果
}
/*
 *30-10i
 */

同样是重载,为什么与友元函数在参数的个数上会有所区别呢?原因在于友元函数没有this指针。

<4> C++中只能对已有的C++运算符进行重载,不允许用户自己定义新的运算符。

<5> C++中绝大部分的运算符可重载,除了成员访问运算符.,成员指针访问运算符.*,作用域运算符::,长度运算符sizeof以及条件运算符?:。

<6> 重载后不能改变运算符的操作对象(操作数)的个数。如:”+”是实现两个操作数的运算符,重载后仍然为双目运算符。

<7> 重载不能改变运算符原有的优先级。

<8> 重载不能改变运算符原有结合的特性。比如:z = x / y * a,执行时是先做左结合的运算x/y,重载后也是如此,不会变成先做右结合y*a。

<9> 运算符重载不能全部是C++中预定义的基本数据,这样做的目的是为了防止用户修改用于基本类型数据的运算符性质。

<10> 从上述的示例中可以看到双目运算符可以被重载为友元函数也可以重载为成员函数。

但有一种情况,只能使用友元函数,是什么情况呢?我举个例子,示例代码分析如下:

#include <iostream>
using namespace std;

class Complex  // 复数类
{
private: // 私有
    double real; // 实数
    double imag; // 虚数

public:
    Complex(double real = 0, double imag = 0)
    {
        this->real = real;
        this->imag = imag;
    }
    Complex operator+(int x);
};

Complex Complex::operator+(int x)
{
    return   Complex(real + x, imag);
}

void main()
{
    Complex com1(5, 10), total;
    total = com1 + 5;   // OK  
//  total = 5 + com1;   // 编译error!!!  注意:com1+5 与 5+com1是两个不同的概念
}

因为左操作数5不是该复数类的对象,不能调用相应的成员函数Complex operator+(int x),所以编译错误。

但如果我们定义一下两个友元函数就能解决上述的问题:

friend Complex operator+(Complex com1, int x);
friend Complex operator+(int x, Complex com1);

示例代码如下:

#include <iostream>
using namespace std;

class Complex  // 复数类
{
private: // 私有
    double real; // 实数
    double imag; // 虚数

public:
    Complex(double real = 0, double imag = 0)
    {
        this->real = real;
        this->imag = imag;
    }
    Complex operator+(Complex com1); // 成员函数重载双目运算符+
    // 或friend Complex operator+(Complex com1, Complex com2);  // 友元函数重载双目运算符+
    friend Complex operator+(Complex com1,int x);             // 友元函数重载双目运算符+
    // 或Complex operator+(int x);
    friend Complex operator+(int x,Complex com1);           // 友元函数重载双目运算符+
    void showSum();
};

Complex Complex::operator+(Complex com1)
{
    return Complex(real+com1.real, imag+com1.imag);
}

Complex operator+(Complex com1, int x)        // 左操作数类型为复数,右操作数的类型为整数
{
    return Complex(com1.real + x, com1.imag);
}

Complex operator+(int x, Complex com1)           // 左操作数类型为整数,右操作数的类型为复数
{
    return Complex(x + com1.real, com1.imag);
}

void Complex::showSum()
{
    cout << real;
    if (imag > 0)
        cout << "+";
    if (imag != 0)
        cout << imag << "i" << endl;
}

class Point // 坐标类
{
private:
    int x;
    int y;

public:
    Point(int x, int y)
    {
        this->x = x;
        this->y = y;
    }
    friend void  operator++(Point & point);    // 友元函数重载单目运算符++
    Point operator++();    // 成员函数重载双目运算符++
    void showPoint();
};

void operator++(Point& point) // 友元运算符重载函数
{
    ++point.x;
    ++point.y;
}

Point Point::operator++()
{
    ++x;
    ++y;
    return *this; // 返回当前对象
}

void Point::showPoint()
{
    cout << "(" << x << "," << y << ")" << endl;
}

int main()
{
    // 两个复数相加
    cout << "两个复数相加:" << std::endl;

    Complex com1(10, 10), com2(20, -20), sum;
    sum = com1 + com2;//或sum = com1.operator+(com2)
    cout << "(10 + 10i) + (20 - 20i) = ";
    sum.showSum();// 输出复数相加结果

    // 三个复数相加
    cout << "三个复数相加:" << endl;

    sum = com1 + com2 + com2;
    cout << "(10 + 10i) + (20 - 20i) + (20 - 20i) = ";
    sum.showSum();

    //整数和复数相加
    cout << "整数和复数相加:" << endl;

    Complex com3(5, 10), total;
    total = com3 + 5; // 或total = operator+(com1, 5);
    cout << "(5 + 10i) + 5 = ";
    total.showSum();

    total=5+com3; // 或total = operator+(5, com1);
    // 只能用友元函数来重载运算符
    cout << " 5 + (5 + 10i) = ";
    total.showSum();

    // 单目运算符++重载
    cout << "单目运算符++重载:" << std::endl;

    // 注意:下述实现部分不能只用一个++point会造成二义性
    Point point(10, 10);
    // 调用友元函数
    operator++(point);// 或++point
    cout << "调用友元函数:++(10, 10) = ";
    point.showPoint(); // 输出坐标值

    // 调用成员函数
    point = point.operator++();// 或++point;
    cout << "调用成员函数:++(10, 10) = ";
    point.showPoint();

    system("pause");
}

// run out:
/*
两个复数相加:
(10 + 10i) + (20 - 20i) = 30-10i
三个复数相加:
(10 + 10i) + (20 - 20i) + (20 - 20i) = 50-30i
整数和复数相加:
(5 + 10i) + 5 = 10+10i
 5 + (5 + 10i) = 10+10i
单目运算符++重载:
调用友元函数:++(10, 10) = (11,11)
调用成员函数:++(10, 10) = (12,12)
请按任意键继续. . .
*/

【6】以下关于上述函数调用细节分析

关于调用细节。示例代码如下:

#include<iostream>
using namespace std;

class B
{
public:
    B ()
    {
        cout << "Default  Construct" << "  " << this << endl;
    }
    B (int i) : data(i)
    {
        cout << "Construct  By  :" << this << "  " << data << endl;
    }
    B (const B &b)
    {
        cout << "Copy  Construct" << "  " << this << "   " << &b << endl;
        data = b.data;
    }

    B &operator=(const B &obj)
    {
        cout << "operator= " << "   " << this << "   " << &obj << endl;
        if (this != &obj)
        {
            data = obj.data;
        }
        return *this;
    }

    ~B()
    {
        cout << "Destructed" << "   " << this << "  " << data << endl;
    }
private:
    int data;
};

B Func(B b)
{
    B t(8);
    return t;
}

void main()
{
    {
        B t1(1);    
        B t2(5);  
        B t3 = t1;  
        B t4(t3); 
        B t5;
        t5 = t2;    
        B t6 = Func(t2);
        B t7; 
        t7 = Func(t1);
    }
    system("pause");
}

// 运行结果如下:
/*
Construct  By  :003BFA80  1
Construct  By  :003BFA74  5
Copy  Construct  003BFA68   003BFA80
Copy  Construct  003BFA5C   003BFA68
Default  Construct  003BFA50
operator=    003BFA50   003BFA74
Copy  Construct  003BF930   003BFA74
Construct  By  :003BF910  8
Copy  Construct  003BFA44   003BF910
Destructed   003BF910  8
Destructed   003BF930  5
Default  Construct  003BFA38
Copy  Construct  003BF930   003BFA80
Construct  By  :003BF910  8
Copy  Construct  003BF96C   003BF910
Destructed   003BF910  8
Destructed   003BF930  1
operator=    003BFA38   003BF96C
Destructed   003BF96C  8
Destructed   003BFA38  8
Destructed   003BFA44  8
Destructed   003BFA50  5
Destructed   003BFA5C  1
Destructed   003BFA68  1
Destructed   003BFA74  5
Destructed   003BFA80  1
请按任意键继续. . .
*/

分析过程:

(1)构建对象t1。调用自定义带参构造函数。

(2)构建对象t2。调用自定义带参构造函数。

(3)构建对象t3,尤其注意这里调用的是拷贝构造函数。

由于t3还不存在!这一句t3 = t1; 相当于声明兼定义,本质类似于t3(t1)。

(4)构建对象t4,与(3)原理一样,调用拷贝构造函数。

(5)构建对象t5,声明兼定义t5,调用默认构造函数。

(6)为对象t5赋值,这里才调用赋值构造函数。

(7)依据打印输出结果逐步展开过程:

1、传参。Func函数传参的过程相当于B b = t2;这么一句代码,效果类似于(3),此很好理解调用的拷贝构造函数。

2、构建局部对象t。调用默认构造函数构建局部对象t。

3、返回局部对象t。相当于B t6 = t; 这么一句代码,效果类似于(3),因此仍然调用拷贝构造函数。

4、释放局部对象。先压栈的是b,所以先析构b;再压栈是t,所以再析构5。

(8)构建对象t7。调用默认构造函数。

(9)依据打印输出结果逐步展开过程:

1、传参(同上)。

2、构建局部对象(同上)。

3、返回局部对象。注意:这时创建了一个临时对象,其地址为003BF96C。

4、释放局部对象(同上)。

5、用临时对象为t7赋值。调用赋值构造函数。

6、析构掉临时对象。Destructed 003BF96C 8

(10)主程序结束,析构掉所有对象。

posted @ 2018-09-02 00:22  jadeshu  阅读(1091)  评论(0编辑  收藏  举报