【C++基础】类的默认成员函数的几种调用方式

先写一个日期类,把构造函数,拷贝构造函数,析构函数,赋值运算符的重载都写上,代码如下:

class Date{
public:
	//构造函数
	Date() {
		cout << "Date()" << endl;
	}
	//拷贝构造函数
	Date (const Date & d) {
		cout << "Date(cont Date& d)" << endl;
	}
	//析构函数
	~Date() {
		cout << "~Date" << endl;
	}
	//赋值运算符重载
	Date& operator=(const Date& d) {
		cout << "operator=" << endl;
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

注意,函数里面都只是输出了一个字符串,用来显示调用情况

构造函数是创建一个对象的时候调用的,如果没有写,编译器会自动生成一个默认的构造函数,例:

Date d;

创建一个对象,它会调用一次构造函数,如图:

拷贝构造函数也是构造函数,它是在一个对象被创建的时候调用,有以下两种使用方式:

Date d1;
Date d2 = d1;
Date d3(d1);

接下来看一种特殊的情况:

Date();
这种情况,是产生了一个 匿名的对象,会调用一次构造函数和析构函数,它的生命周期只有这一行,如图:

它被构造之后立即就被销毁了

接下来看这种情况:

Date d = Date();

有了先前的分析,应该是一次构造函数,一次拷贝构造函数,是真正 的结果如图:

只有一次构造函数,并没有拷贝构造函数

注意,这里是编译器优化的效果,首先这里直接创建了一个Date(),这是一个匿名的对象,然后要进行拷贝构造的时候编译器进行了优化

为了测试正确与否,我们先改一下构造函数和拷贝构造函数,代码如下:

	//构造函数
	Date(int year = 1800, int month = 1, int day = 1) {
        cout << "Date()" << endl;
        _year = year;
        _month = month;
        _day = day;
    }
	//拷贝构造函数
	Date (const Date & d) {
		cout << "Date(cont Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
 }

再更改一下测试用例:

Date d3 = Date(1,1,1);



如图我们发现,这个表达式调用 了Date(1,1,1),然后并没有再调用拷贝构造函数,但是,d3还是和这个匿名的对象值一样, 所以,编译器的优化是这样的:直接让d3成为这个匿名的对象。

什么意思呢?相当于给匿名对象起了个名字叫d3

这就是编译器的一种优化

注意,我们说过拷贝构造函数调用有两种方式:

Date d1;
Date d2 = d1;
Date d3(d1);

但是在这里,虽然Date d = Date();编译器是优化了的,但是另一种方式是行不通的!

像这样:

Date d3(Date());

为什么呢?我也不知道~反正vs2013的编译器是不认的,构造函数拷贝构造函数都没有调用

【C++基础】类的默认成员函数的几种调用方式_第1张图片

d3直接就没有创建成功

好了~不如我们把这些归咎于编译器吧~反正也不知道为啥

但是,这样是可以创建成功的:


为什么?别问我,我不知道,谁要是知道了可以评论下

接下来我们定义两个函数,进一步去测试

void fun1(Date d)
{}

Date func2()
{
    Date ret;
    return ret;
}
场景一:
Date d1;
fun1(d1);
创建d1的时候调用一次构造,然后fun1函数传参的时候调用一次拷贝构造,然后fun1函数推出的时候调用一次析构函数


场景二:

Date d2 = func2();

函数在返回值的时候,会有一个临时变量,如图:

【C++基础】类的默认成员函数的几种调用方式_第2张图片

像这样,所以应该是ret调用一次构造函数,临时变量调用一次拷贝构造,然后ret再调用一次析构函数,然后临时变量赋值给d2的时候再调用一次拷贝构造函数

但是,结果却不是这样的,因为编译器优化了,如图:

编译器优化后,没有临时变量的拷贝构造了,如图:

【C++基础】类的默认成员函数的几种调用方式_第3张图片

好了,接下来看场景三:

Date d3;
d3 = func2();

【C++基础】类的默认成员函数的几种调用方式_第4张图片

细细一看,这个好像并没有优化,为什么呢?

因为d3已经被创建好了,无法进行优化

我们改一下fun2函数,代码如下:

Date func2()
{
	return Date();
}

Date()这个是不是似曾相识~

好了我们来测试一下,场景一:

Date d2 = func2();

结果如下:

【C++基础】类的默认成员函数的几种调用方式_第5张图片

只有一次构造函数???

如果认真看了上面的例子,这个例子肯定很简单了,编译器会优化,没有临时变量的拷贝,如图:

【C++基础】类的默认成员函数的几种调用方式_第6张图片

这个不就是Date d2 = Date();

场景二:

Date d3;

d3 = func2();

【C++基础】类的默认成员函数的几种调用方式_第7张图片

如图,d3一次构造函数,Date()一次构造函数,因为Date()是匿名的对象而且刚创建就要返回,所以这里就直接优化了

调用方式基本就讲完了,附上一个题:

class AA
{
public:
	AA()
	{
	}
	AA(const AA& a)
	{
		cout<<"AA(const AA& a)"<

代码如上,问:所有的Test函数总共调用了几次构造函数,几次拷贝构造函数,几次析构函数

首先来看Test1();

【C++基础】类的默认成员函数的几种调用方式_第8张图片

一次构造函数,两次拷贝构造,一次赋值运算符的重载,过程如下图:

【C++基础】类的默认成员函数的几种调用方式_第9张图片

接下来看Test2();

一次构造函数,一次拷贝构造函数,零次赋值运算符的重载

【C++基础】类的默认成员函数的几种调用方式_第10张图片

因为这里对象a2是新创建的,所以编译器会进行优化,如下图:

【C++基础】类的默认成员函数的几种调用方式_第11张图片

接下来看最难的Test3();代码如下:

void Test3 ()
{
	AA a1 ;
	AA a2 = f(f(a1));
}

可以看到这里有一个嵌套,我们先一步一步分析

显示a1拷贝给函数f的形参a,然后返回a,返回a的时候有个临时变量tmp(假设),然后a拷贝给tmp,tmp再拷贝给外面的函数的形参a,这个a和上面做同样的动作,思路分析如图:

【C++基础】类的默认成员函数的几种调用方式_第12张图片

运行一下看结果图:

【C++基础】类的默认成员函数的几种调用方式_第13张图片

结果是只有三次拷贝构造函数,和上面一样,编译器肯定进行了优化

优化的情况下肯定不需要临时变量了,思路如图:

【C++基础】类的默认成员函数的几种调用方式_第14张图片

完!

你可能感兴趣的:(C++基础)