我的个人网站:
http://riun.xyz
问题是:在C++中定义一个类A,A a定义一个对象,然后可以通过 a = 5这样的代码进行赋值。搞不明白为什么,于是做了几个实验去窥探其中的原理。
一、实验
【实验1】
#include<iostream>
using namespace std;
class A
{
public:
int val;
int t;
A(int n = 0)
{
cout << "构造函数int n" << endl;
val = n;
t = 0;
cout << "构造函数调用完毕" << endl;
}
A(int n, int t)
{
cout << "构造函数int n, int t" << endl;
(*this).val = n;
(*this).t = t;
}
A& GetObj()
{
return *this;
}
};
int main(void)
{
A a;//调用构造函数
cout << a.val << endl << endl;
a .GetObj() = 5;//赋值 再次调用构造函数
cout << endl;
a = 3;//赋值 再次调用构造函数
cout << endl;
return 0;
}
上述代码中,除了在创建一个对象,进行初始化操作时调用了构造函数,在对对象进行赋值时也调用了构造函数。至于为什么能直接给对象赋值,现在我们还未理解。但是我们发现每次赋值都调用了构造函数,是这样吗?我们再试试:
【实验2.1】
int main(void)
{
A a;//调用构造函数
cout << a.val << endl << endl;
A b(1);//调用构造函数
return 0;
}
调用了两次构造函数,因为有两次创建A对象。接下来我们再将b赋值给a:
【实验2.2】
int main(void)
{
A a;//调用构造函数
cout << a.val << endl << endl;
A b(1);//调用构造函数
a = b;
return 0;
}
咦,居然没有再次调用构造函数,但是在【实验1】中每次赋值都调用了构造函数了呀,这是为什么呢。
回想一下,相同类型的对象之间赋值是不需要调用构造函数的,构造函数只有在创建对象的时候才调用。【实验1】中将int类型的值赋给a对象时调用了构造函数,是否说明这里赋值时创建了一个对象呢?
我猜想是的,接着我就给A类写了一个析构函数:
【实验3】
#include<iostream>
using namespace std;
class A
{
public:
int val;
int t;
A(int n = 0)
{
cout << "构造函数int n" << endl;
val = n;
t = 0;
cout << "构造函数调用完毕" << endl;
}
A(int n, int t)
{
cout << "构造函数int n, int t" << endl;
(*this).val = n;
(*this).t = t;
}
A& GetObj()
{
return *this;
}
//新增手写的析构函数
~A()
{
cout << "析构函数调用" << endl;
}
};
int main(void)
{
A a;//调用构造函数
cout << a.val << endl << endl;
a .GetObj() = 5;//再次调用构造函数
cout << endl;
a = 3;//再次调用构造函数
cout << endl;
return 0;
}
我们可以看到,红框中的是a对象的生命周期,篮框和黄框内就是在赋值过程中生成A类型的临时对象,在赋值时生成,赋值结束后就被释放了。它们的作用只是将值赋给a对象。
而C++为了能在代码层面完成可以直接将不同类型的值赋给对象这样的操作,在赋值时临时生成了一个对象,然后就可以像【实验2.2】那里一样作为同类型对象间的赋值。
所以,像 a = 5 这样的代码只是生成了一个临时对象,用临时对象给a赋值。而生成临时对象需要调用对应的构造函数,如果你没有符合的构造函数,就不会生成对象,就会报错。我们将单参的构造函数屏蔽起来试试:
【实验4】
#include<iostream>
using namespace std;
class A
{
public:
int val;
int t;
/*A(int n = 0)
{
cout << "构造函数int n" << endl;
val = n;
t = 0;
cout << "构造函数调用完毕" << endl;
}*/
A(int n, int t)
{
cout << "构造函数int n, int t" << endl;
(*this).val = n;
(*this).t = t;
}
A& GetObj()
{
return *this;
}
};
int main(void)
{
A a(0,0);//调用构造函数
cout << a.val << endl << endl;
a .GetObj() = 5;//再次调用构造函数
cout << endl;
a = 3;//再次调用构造函数
cout << endl;
return 0;
}
报错原因是无法将int类型赋给对象,因为没有对应的运算符类型。
二、结论
结论:在C++中,可以将普通类型的变量和对象间进行赋值操作,赋值时会利用对应的构造函数生成临时对象,然后进行同类型对象间的赋值。这相当于帮我们做了隐式转换,将不同类型转换为相同类型,再进行赋值。
这给我们赋值提供了一种新的解决方案。
- 1、原有的解决方案是:重载赋值运算符,将赋值符号右边的变量作为重载函数的形参传递,然后在函数内部进行赋值。
A& operator=(int i)
{
this->val = i;
return *this;
}
这样就可以进行 a = 5
这样的运算了。
- 2、新的解决方案是:编写对应的构造函数,在赋值时C++会自动生成临时对象,然后进行同类型对象赋值。
三、扩展
既然知道了这些,那么疑问又来了:如果同时出现构造函数和重载运算符,那要执行哪个呢?
【实验5】
#include<iostream>
using namespace std;
class A
{
public:
int val;
int t;
A(int n = 0)
{
cout << "构造函数int n" << endl;
val = n;
t = 0;
cout << "构造函数调用完毕" << endl;
}
A(int n, int t)
{
cout << "构造函数int n, int t" << endl;
(*this).val = n;
(*this).t = t;
}
A& GetObj()
{
return *this;
}
//不需要手动重载,但是如果你显示的重载了赋值运算符,那么就会调用你手动写的,就不会再使用默认的重载函数了
A& operator=(A anthor)
{
cout << "手写重载运算符 A anthor" << endl;
this->val = 100;
return *this;
}
A& operator=(int i)
{
cout << "手写重载运算符 int i" << endl;
this->val = 50;
return *this;
}
};
int main(void)
{
A a;
cout << a.val << endl << endl;
a.GetObj() = 5; //(1)
cout << a.val << endl << endl;
A a2(2);
a = a2; //(2)
cout << a.val << endl << endl;
A a3(1);
a.GetObj() = a3; //(3)
cout << a.val << endl << endl;
a = 5; //(4)
cout << a.val << endl << endl;
return 0;
}
可以看到,红色框内是对a对象的初始化;蓝色框内因为两边都是A类型,所以调用的是A anthor的操作符重载(这里输出的构造函数是创建a2, a3对象时调用的,不要错认为是赋值时调用的哦);黄色框和最底下的青色框是一样的情况,都是将5赋值给a对象,赋值时执行了int i 的操作符重载,而没有将int类型的数字再转化为A类型的对象,由此可见操作符重载的优先级较高,有对应的操作符重载函数,赋值时就不会去生成临时对象了。