博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
(一二四)给类对象赋值、以及类对象的返回值
阅读量:5889 次
发布时间:2019-06-19

本文共 5874 字,大约阅读时间需要 19 分钟。

于直接给对象赋值:

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

对于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
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.15, 1.15, 4.49, 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
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.1a=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
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.47, 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
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)。

 

转载地址:http://plwsx.baihongyu.com/

你可能感兴趣的文章
Hibernate基础-HelloWord
查看>>
Android Studio系列教程四--Gradle基础
查看>>
添加cordova-plugin-file-opener2后,打包出错
查看>>
python 重载方法有哪些特点 - 老王python - 博客园
查看>>
在Fedora8上安装MySQL5.0.45的过程
查看>>
TCP长连接与短连接的区别
查看>>
设计模式之命令模式
查看>>
android 测试 mondey
查看>>
Spring AOP项目应用——方法入参校验 & 日志横切
查看>>
TestNG 六 测试结果
查看>>
用Fiddler或Charles进行mock数据搭建测试环境
查看>>
使用REST-Assured对API接口进行自动化测试
查看>>
GitHub发布史上最大更新,年度报告出炉!
查看>>
王潮歌跨界指导HUAWEI P20系列发布会 颠覆传统 眼界大开!
查看>>
王高飞:微博已收购一直播 明年一季度重点是功能与流量打通
查看>>
趣头条发行区间7至9美元 预计9月14日美国上市
查看>>
新北市长侯友宜:两岸交流应从隔壁最亲近的人开始
查看>>
全面屏的Nokia X即将上线,不到2000元的信仰你要充值吗?
查看>>
利用clear清除浮动的一些问题
查看>>
区块链杂谈-测链
查看>>