(一二四)给类对象赋值、以及类对象的返回值

简介:

于直接给对象赋值:

之前学过,如何给对象在初始化时进行赋值。

对于C++11来说,初始化方式有三种:

① man c = man"cc",1 };

② man d = { "dd",1 };

③ man f{ "ff",1 };

 

假如有一类M,他有两个私有成员abint类型)。

于是新建一对象M q; 对象q使用默认构造函数(假如都赋值为0,这个不重要);

现在,我们想给对象q的第一个私有成员赋值,该怎么办?

这章刚学过运算符重载,难道要写个运算符重载么?隐式调用类对象,参数为赋值符号=后面的值?(我们知道,赋值符号是可以被成员函数重载的)

这显然太麻烦。

 

如果可以这样q=3,于是q的第一个成员被赋值3就十分方便了。

这是可行的,不是通过运算符重载,而是通过构造函数,构造函数设置一个参数时,使用q=3,则默认3为第一个参数,假设构造函数这么写。

M(int c = 1) { a = c; b = 1.1; };

那么使用q=3时,a将等于3b将等于1.1(此刻相当于同时给2个私有成员赋值)。

如果我们只想给a赋值,不给b赋值,那么删掉b=1.1也是可以的(但这是默认构造函数,如果删除的话,创建新对象时,调用这个默认构造函数时则无法给b进行初始化)。

 

那么如果想利用赋值符号,同时给ab赋值呢,且b使用我们给他的值,而不是上面的默认值1.1。办法是,继续使用构造函数,且这个构造函数有两个参数,赋值时使用列表化进行赋值(大括号)。

如:

M(int cdouble d) { a = c;b = d; }

注意:这里不能给默认参数,否则在使用q=3这样时,会有二义性(即能匹配第一个默认构造函数,也能匹配这个构造函数)。

然后使用:q={5,4.4}; 这样的形式,可以同时给ab赋值。

此时调用了有两个参数的构造函数。另外,需要注意的是,不能省略掉大括号,否则,会导致调用了默认构造函数。

 

如代码:

#include<iostream>

class M
{
	int a;
	double b;
public:
	M(int c = 1) { a = c; b = 1.1; }	//带一个默认参数,默认构造函数
	M(int c, double d) { a = c;b = d; }	//两个参数,无默认参数,以避免二义性,构造函数
	void show() { std::cout << a << ", " << b << std::endl; };	//显示a和b的值
};

int main()
{
	using namespace std;
	M q;	//创建对象q(调用默认构造函数)
	q.show();	//输出
	q = 5;	//使用赋值符号,(此刻调用默认构造函数,但使用给的值作为参数)
	q.show();	
	q = { 5,4.4 };	//调用有两个参数的构造函数
	q.show();
	q = 9,3.3;	//虽然给了两个参数,但是只会调用默认构造函数	
	q.show();
	system("Pause");
	return 0;
}

显示:

1, 1.1
5, 1.1
5, 4.4
9, 1.1
请按任意键继续. . .


 

 

以上,将某个值直接赋给类对象,其原理是:使用类的构造函数,创造一个临时的对象,然后,将值赋给临时对象,再采用逐成员赋值的方式,将该临时对象的内容复制到目标对象之中。

这一过程,被称为 隐式转换 ,因为他是自动进行的,而不需要显式强制类型转换。

 

这种转换,称为是 类型转换

 

只有接受一个参数的构造函数,才能作为转换函数。有两个参数的构造函数,是不能用来转换类型的(就像上面说的,需要用大括号把两个参数都括起来)。但如果给第二个参数提供默认值,便又可以用于转换类型了(但前提是不会因此导致 二义性)。

 

将构造函数用于自动类型转换,在某些时候比较方便,但这种特性并非总是合乎需要的(比如说我们某个时候不需要他这么做,暂时举不出例子),因为这可能导致意外的类型转换(比如说只想给一个值赋值,但因为构造函数,给另外一个值赋了默认值)。

因此,C++新增了关键字 explicit,用于关闭这种隐式转换(但仍允许显式转换),即 显式强制类型转换

 

例如,double a=int (4.1);  便是一种 显式强制类型转换。double类型的4.1被强制转换为int类型(此时值为4),又被赋值给double类型,于是a=4

构造函数改为:explicit M(int c = 1) { a = c; b = 1.1; }

此时,M q是可行的,

q=5是不可行的,

但是q = M(5);又是可行的。

另外,需要提一下的是,之前q = 9,3.3;只能导致9被赋值(调用了默认构造函数),但是修改后为 = M(9,3.3); 则调用的是另一个构造函数了。

 

 

假如不使用explicit给构造函数(一个参数的,例如M(int c = 1)),那么,编译器将在什么启动隐式转换(使用构造函数,然后进行赋值)呢?

①声明一个类对象,并进行初始化。例如:M q(1);

 

②将值赋给对象时,例如:q=5;

 

③将值传递给接受类对象参数的函数时。例如:void abc(M t = 3); 这样。3相当于是函数的默认参数。

 

④返回值是类对象时,函数试图返回值时。就比如:

M abc()

{

return 3;

}

这个时候,相当于返回一个临时类对象,然后这个对象被初始化赋值为3,就像M q(3); 然后return q一样。只不过这里不是返回q,是返回一个临时的类对象

 

⑤在上述任意一种情况下,使用可转换为 可直接赋值给类对象的值的类型的 内置类型。

假如构造函数M(int c = 1) ,这个时候,需要用int类型的值给其赋值,例如q=3; 而我们知道,double类型可以转换为int类型,因此,我们也可以输入q=3.3; 这时,3.3被转换为int类型的3,然后int类型的3被隐式转换,赋值给类对象。这种情况也是允许的。

 

 

另外,以上隐式转换发生的前提是,函数不存在二义性,否则会提示错误。

 

 

 

转换函数:

隐式转换和显示转换,可以把一个比如说int类型的值,赋给类对象。

那么类对象是否能赋值给一个int类型的变量呢,也是可以的。

要进行这种转换,必须使用特殊的C++运算符函数—— 转换函数

转换函数是 用户定义(不定义是不能用的)的强制类型转换,可以像使用强制类型那样使用他们。

 

 

转换函数的创建方法:

格式:operator 类型名();

 

关键点:

①转换函数必须是类方法(成员函数);

②转换函数不能指定返回类型(靠类型名);

③转换函数不能有参数(靠私有成员的值)。

 

例如,转换为int类型,其函数原型应该这么写:operator int();

函数定义如下写:

operator int()
{
return a;

}

这样,将返回私有成员a的值。

注意,假如有operator double(); 那么返回int值时调用第一个,返回double值时调用第二个。返回其他类型,会提示冲突。

 

如代码:

#include<iostream>

class M
{
	int a;
	double b;
public:
	M(int c = 1) { a = c; b = 4.1; }	//带一个默认参数,默认构造函数
	void show() { std::cout << a << ", " << b << std::endl; };	//显示a和b的值
	operator int() { return a; }	//转换函数返回int值时调用这个
	operator double() { return b; }	//转换函数返回double值时调用这个
};

int main()
{
	using namespace std;
	M q;	//创建对象q(调用默认构造函数)
	q.show();	//输出
	int a = q;	//返回int值
	double b = q;	//返回double值
	double c = int(q);	//强制类型转换为int,因此返回int值
	cout << "a=" << a << ",b=" << b << ",c=" << c << endl;
	system("Pause");
	return 0;
}

显示:


1, 4.1
a=1,b=4.1,c=1
请按任意键继续. . .

自动应用类型转换:

假如,一个类只有一个转换函数,例如强制转换为int

那么调用这个对象时,显示的便是其转换函数返回的值。

例如假如上面那个类方法,只有operator int()这个转换函数。那么当我们使用cout<<q<<endl; 时,

 

这个时候,程序便自动应用了类型转换,显示的是“1”。

 

但假如同时存在两个转换函数,再这样做的话,会提示二义性,因为程序并不知道你想要显示的是int类型的值还是double类型的值,除非你使用了强制类型转换。

 

 

注意:应谨慎的使用隐式转换函数。通常,最好选择仅在被显式的调用时才会被执行的函数。

 

例如:用一个返回值是你想要转换函数返回的值的函数,例如,使用operator()返回私有成员a,那么,用int M_to_int(){ return a;} 这样的函数,来返回私有成员a,调用时,则使用q.M_to_int();

 

 

总之:C++为类提供了下面的类型转换:

①利用构造函数,把基本类型转换为类 类型。

②利用转换函数,把类 类型转换为基本类型。

 

 

 

友元函数(运算符重载)和转换函数:

有类M,有类对象成员a,有int变量b

有赋值,M c=a+b;

 

1)假如有友元函数(或者运算符重载)的情况下:a+b将调用运算符重载函数,将函数返回值赋给类对象c

 

2)假如有转换函数,且类M的构造函数接受一个int参数。那么a将被转化为int值(根据转换函数),a+b的和(int值)作为参数,赋给类对象c

假如有转换函数,但类M的构造函数不接受一个int参数(例如有两个参数且无默认参数),那么则提示出错,无法编译。

 

3)假如既有运算符重载(“+”运算符),又有转换函数。

那么,根据实际测试来看,优先调用运算符重载函数或友元函数(假如他能完全匹配的话);

如果不能完全匹配,那么就可能导致二义性错误。

如代码:

#include<iostream>
 
class M
{
int a;
double b;
public:
M(int c=3) { a = c; b = 4.4; }	//带一个默认参数,默认构造函数
void show() { std::cout << a << ", " << b << std::endl; };	//显示a和b的值
operator int() { return a; }	//转换函数返回int值时调用这个
friend int operator+(int c, M & q);
int operator+(int c) { return b + c; }
};
 
 
int main()
{
using namespace std;
M q;	//创建对象q(调用默认构造函数)
M c = 3 + q;
M d = q + 3;
c.show();
d.show();
system("Pause");
return 0;
}
int operator+(int c, M & q) 
{
return c + q.b; 
}


显示:

7, 4.4
7, 4.4
请按任意键继续. . .

之所以结果为7,是因为优先使用了运算符重载函数(友元函数和成员函数),其用成员函数b和参数c相加,结果为7(也就是第一个数字)。

而若使用转换函数的话,其结果应为6,因为转换函数的返回值是33+3,其第一个数字应为6——但具体为什么,推测是函数列表匹配度问题

 

①假如把3+q或者q+3,改为3.3+q和q+3.3,那么函数则会提示错误。原因在于,友元函数和成员函数不能完全匹配了,在匹配列表里优先级降低,成为了标准匹配(第三优先级)。而转换函数的返回值也是int类型,需要进行转换,因此也成为了标准转换(优先级也是第三级),因为匹配优先级相同,因此提示错误。

 

②又假如把q+3改为int(q)+3,那么首先对对象q执行强制类型转换,其为int3(因为转换函数返回值为3),因此3+3=6,启用构造函数,于是第一个值便变成了6.

 

 

由于匹配优先级问题,如果想让q+3结果变为6,那么则在运算符重载函数修改为:int operator+(M&c) { return b + c.b; }  这时,转换函数的优先级则更高。

假如有:

#include<iostream>
class M
{
	int a;
	double b;
public:
	M(int c = 2) { a = c; b = 4.4; }	//带一个默认参数,默认构造函数
	void show() { std::cout << a << ", " << b << std::endl; };	//显示a和b的值
	operator int() { return a; }	//转换函数返回int
	friend M operator+(M&a,M&c) { return a.b + c.b; }
};

int main()
{
	using namespace std;
	M q;	//创建对象q(调用默认构造函数)
	M d = q + 3;
	d.show();
	system("Pause");
	return 0;
}

d的输出结果为5,而不是8。使用的是转换函数,而不是运算符重载函数。原因我推测为:q+3,通过调用转换函数,被转换为2+3,然后将5赋值给了对象d

而若使用运算符重载函数,则可能成为了用户定义的类型转换了。因此优先级不如转换函数。即使把q+3改为q+M(3)这种,将3强制转换为类M的临时对象,其优先级也没有转换函数高。

除非改为:M q;M w=3; M d=q+w; 这种形式,才会使用运算符重载函数。

 

 

总之,这种容易引起意料之外结果的,我觉得还是转换函数和运算符重载函数,二者取其一吧。或者放弃转换函数,而是改使用显式的转换函数(加关键字explicit)。

 


相关实践学习
消息队列RocketMQ版:基础消息收发功能体验
本实验场景介绍消息队列RocketMQ版的基础消息收发功能,涵盖实例创建、Topic、Group资源创建以及消息收发体验等基础功能模块。
消息队列 MNS 入门课程
1、消息队列MNS简介 本节课介绍消息队列的MNS的基础概念 2、消息队列MNS特性 本节课介绍消息队列的MNS的主要特性 3、MNS的最佳实践及场景应用 本节课介绍消息队列的MNS的最佳实践及场景应用案例 4、手把手系列:消息队列MNS实操讲 本节课介绍消息队列的MNS的实际操作演示 5、动手实验:基于MNS,0基础轻松构建 Web Client 本节课带您一起基于MNS,0基础轻松构建 Web Client
目录
相关文章
|
存储 Cloud Native 编译器
C++ 对象生成:构造函数
C++ 对象生成:构造函数
|
2月前
|
编译器 C++
C++ 类构造函数初始化列表
构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。
75 30
|
1月前
|
Java 程序员 C#
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
【类的应用】C#应用之派生类构造方法给基类构造方法传参赋值
13 0
|
1月前
|
C++
C++构造函数初始化类对象
C++构造函数初始化类对象
21 0
|
6月前
|
编译器 C++
30利用构造函数对类对象进行初始化
30利用构造函数对类对象进行初始化
48 0
|
6月前
引用构造器练习
引用构造器练习
51 0
方法引用符、引用类方法、引用对象的实例方法、引用类的实例方法及引用构造器
方法引用符、引用类方法、引用对象的实例方法、引用类的实例方法及引用构造器
91 0
为什么子类会调用父类无参的构造函数
为什么子类会调用父类无参的构造函数
|
编译器 C++
<c++> 类的构造函数与类的析构函数
<c++> 类的构造函数与类的析构函数
94 0
|
Java C++
C++类中在构造器中调用本类的另外构造器
C++类中在构造器中调用本类的另外构造器
98 0