C++——使用类
一、运算符重载
1、运算符重载
C++允许将运算符重载扩展到用户定义的类型。
要重载运算符,需使用被称为运算符函数的特殊函数形式。运算符函数的格式如下:
operatorop(argument list);
例如,operator+()重载+运算符,operator*()重载*运算符。op必须是一个有效的C++运算符,不能虚构一个新的符号。
例如,将Person对象的体重相加,如果per1、per2和perSum都是Person对象,便可编写这样的等式:
perSum = per1 + per2;
编译器发现,操作数是Person对象,因此使用相应的运算符函数替换上述运算符:
perSum = per1.operator+(per2);
然后该函数将隐式地使用per1对象(因为它调用了方法),显式地使用per2对象(因为他被作为参数传递),来计算总和,并返回这个值。
Time.h
#include <stdio.h>
class Time{
private:
int _hour;
int _min;
int _sec;
public:
Time(int hour = , int min = , int sec = );
void setHour(int hour);
void setMin(int min);
void setSec(int sec);
int getHour() const;
int getMin() const;
int getSec() const;
void showTime() const;
Time operator+(const Time &time) const;
};
Time.cpp
#include "Time.h"
#include <iostream>
Time::Time(int hour, int min, int sec){
_hour = hour;
_min = min;
_sec = sec;
}
void Time::setHour(int hour){
_hour = hour;
}
void Time::setMin(int min){
_min = min;
}
void Time::setSec(int sec){
_sec = sec;
}
int Time:: getHour() const{
return _hour;
}
int Time:: getMin() const{
return _min;
}
int Time:: getSec() const{
return _sec;
}
Time Time::operator+(const Time &time) const{
int hour, min, sec, min_, hour_;
sec = (_sec + time.getSec())%;
min_ = (_sec + time.getSec())/; min = (_min + time.getMin() + min_)%;
hour_ = (_min + time.getMin() + min_)/; hour = (_hour + time.getHour() + hour_)%;
return Time{hour,min,sec};
}
void Time:: showTime()const{
printf("时间:%02d:%02d:%02d\n",_hour,_min,_sec);
}
main.app
#include <iostream>
#include "Time.h" int main(int argc, const char * argv[]) {
Time t1{,,};
Time t2{,,};
Time t3 = t1 + t2;
t3.showTime();
t3 = t3 + t1 + t2;
t1.showTime();
t2.showTime();
t3.showTime(); return ;
}
输出结果:
时间:::
时间:::
时间:::
时间:::
注意:在运算符表示法中,运算符左侧的对象是调用对象,运算符右侧的对象是作为参数被传递的对象。
2、运算符重载限制
重载的运算符(有些情况例外)不必是成员函数,但至少有一个操作数是用户定义的类型。对于非成员运算符重载函数来说,运算符表达式左边的操作数对应于运算符函数的第一个参数,运算符表达式右边的操作数对应于运算符函数的第二个参数。
#include <iostream>
#include "Time.h"
Time operator-(const Time &,const Time &);//非成员运算符重载函数
void operator-=(Time &time1,const Time & time);
int main(int argc, const char * argv[]) {
Time t1{,,};
Time t2{,,};
Time t3 = t1 + t2;
t3.showTime();
t3-= t2;
t3.showTime();
t3 -=t1;
t3.showTime(); return ;
}
Time operator-( const Time & time1,const Time& time2){
int hour = time1.getHour();
int min = time1.getMin();
int sec = time1.getSec();
if (sec < time2.getSec()) {
min--;
sec = sec + - time2.getSec();
}
else
sec -= time2.getSec();
if (min < time2.getMin()) {
hour--;
min = min + - time2.getMin();
}
else
min -= time2.getMin();
if (hour < time2.getHour()) {
hour = hour - time2.getHour() + ;
}
else
hour -= time2.getHour(); return Time{hour,min,sec};
}
void operator-=( Time &time1,const Time & time){
time1 = time1 - time;
}
下面详细介绍C++对用户定义的运算符重载的限制:
(1)重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符。
(2)使用运算符时不能违反运算符原来的句法规则。例如,不能将%重载成使用一个操作数。同样,不能修改运算符的优先级。
(3)不能创建新的运算符。
(4)不能重载下面的运算符:
* sizeof:szieof运算符;
* . :成员运算符(.);
* .* : 成员指针运算符;
* :: :作用域运算符;
* ?: :条件运算符;
* typeid:一个RTTI运算符;
* const_cast :强制类型转换运算符;
* dynamic_cast :强制类型转换运算符;
* reinterpret_cast:强制类型转换运算符;
* static_cast : 强制类型转换运算符;
(5)下面标中的运算符都可以被重载
可重载的运算符
+ | - | * | / | % | ^ |
& | | | ~= | ! | = | < |
> | += | -= | *= | /= | %= |
^= | &= | |= | << | >> | >>= |
<<= | == | != | <= | >= | && |
|| | ++ | -- | , | ->* | -> |
() | [] | new | delete | new[] | delete[] |
但是下面的运算符只能通过成员函数进行重载:赋值运算符(=)、函数调用运算符(())、下标运算符([])、通过指针访问类成员的运算符(->)。
二、友元
友元有三种:友元函数、友元类、友元成员函数。
1、友元函数
通过让函数成为类的友元,可以赋予该函数与类的成员函数相同访问权限。
由于成员运算符函数重载运算符后,该运算符左边的操作数只能是该对象;然而,当其他类型的操作数与对象运算时,如果其他类型操作数在运算符的左边,例如2*time(其中time为类对象),将会出现错误,因为其他类型的操作数无法调用对象的成员运算符重载函数。这时,可以声明定义非成员运算符重载函数,把函数的第一个参数设置为其他类型的参数,这样就可以与对象的成员运算符重载函数相配合,实现与2*time相似的表达式。
Time类中*的成员运算符重载函数的定义:
Time Time::operator*(unsigned int num)const{
int hour,min,sec;
sec = _sec * num;
min = _min * num + sec/;
hour = _hour * num + min /;
min %= ;
sec %= ;
return Time{hour,min,sec};
}
main.cpp:
#include <iostream>
#include "Time.h"
Time operator*(unsigned int , const Time &);
int main(int argc, const char * argv[]) {
Time t1{,,};
Time t2 = t1 * ;//将会调用成员运算符重载函数
Time t3 = * t1;//将会调用非成员运算符重载函数
using std::cout;
cout << "t1";
t1.showTime();
cout << "t2";
t2.showTime();
cout << "t3";
t3.showTime(); return ;
}
Time operator*(unsigned int num, const Time & time){
int hour, min,sec;
sec = time.getSec() * num;
min = time.getMin() * num + sec /;
hour = time.getHour() * num + min/;
sec %= ;
min %= ;
return Time{hour,min,sec};
} 输出结果:
t1时间:02小时25分30秒
t2时间:04小时51分00秒
t3时间:07小时16分30秒
使用非成员函数可以按所需要的顺序获得操作数,但引发了一个新问题:常规非成员函数不能访问类的私有数据。C++提供了友元函数,一种特殊的非成员函数,可以访问类的私有数据。
(1)创建友元
第一步,将原型放在类声明中,并在原型声明前加上关键字friend。这样声明的函数原型,意味着一下两点:
*虽然该函数原型是在类声明中声明的,但是他不是类的成员函数,因此不能使用成员运算符来调用;
*虽然该函数不是类的成员函数,但它与成员函数的访问权相同。
第二部,编写函数定义。因为友元函数不是成员函数,因此不能使用类作用域限定符。另外,也不要在定义中使用关键字friend。
现在,对上面的Time对象乘以整数的例子进行改进:
Time.h文件:
#include <stdio.h>
class Time{
private:
int _hour;
int _min;
int _sec;
public:
Time(int hour = , int min = , int sec = );
int getHour() const;
int getMin() const;
int getSec() const;
void showTime() const;
Time operator*(unsigned int num) const;//声明成员运算符重载函数
friend Time operator*(unsigned int, const Time &);//声明友元运算符重载函数
};
Time.cpp源文件:
#include "Time.h"
#include <iostream>
Time::Time(int hour, int min, int sec){
_hour = hour;
_min = min;
_sec = sec;
}
int Time:: getHour() const{
return _hour;
}
int Time:: getMin() const{
return _min;
}
int Time:: getSec() const{
return _sec;
}
void Time:: showTime()const{
printf("时间:%02d小时%02d分%02d秒\n",_hour,_min,_sec);
}
Time Time::operator*(unsigned int num)const{//成员运算符重载函数的第一个操作数必须是该类对象
int hour,min,sec;
sec = _sec * num;
min = _min * num + sec/;
hour = _hour * num + min /;
min %= ;
sec %= ;
return Time{hour,min,sec};
}
Time operator*(unsigned int num, const Time &time){//通过友元函数进行反转,定义友元函数的时候不能包含类作用域限定符,也不要包含friend关键
return time * num;
}
main.cpp:
#include <iostream>
#include "Time.h" int main(int argc, const char * argv[]) {
Time t1{,,};
Time t2 = t1 * ;//将会调用成员运算符重载函数
Time t3 = * t1;//将会调用友元运算符重载函数
using std::cout;
cout << "t1";
t1.showTime();
cout << "t2";
t2.showTime();
cout << "t3";
t3.showTime(); return ;
} 输出结果:
t1时间:02小时25分30秒
t2时间:04小时51分00秒
t3时间:07小时16分30秒
提示:如果要为类重载运算符,并将非类的项作为第一个操作数,则可以使用友元函数来反转操作数的顺序。
(2)常用的友元:重载<<运算符
一个很有用的特性是,可以对<<运算符进行重载,使之能与cout一起来显示对象的内容。
要使类知道使用cout,必须使用友元函数。因为像下面这样的语句使用两个对象,其中第一个是ostream类对象(cout):
cout << trip;
如果使用类成员函数来重载<<,类对象将是第一个操作数。就意味着应该这样使用<<:
trip << cout;
这样会使人迷惑。但是,通过友元函数,就可以反转操作数顺序。下面是在Time类中,实现友元<<运算符重载函数:
Time.h文件
#include <ostream> class Time{
private:
int _hour;
int _min;
int _sec;
public:
Time(int hour = , int min = , int sec = );
int getHour() const;
int getMin() const;
int getSec() const;
Time operator*(unsigned int num) const;
friend Time operator*(unsigned int, const Time &);//声明友元函数
friend std::ostream& operator<<(std::ostream &, const Time &);//声明友元<<运算符重载函数 };
Time.cpp文件:
#include "Time.h"
#include <iostream>
Time::Time(int hour, int min, int sec){
_hour = hour;
_min = min;
_sec = sec;
}
int Time:: getHour() const{
return _hour;
}
int Time:: getMin() const{
return _min;
}
int Time:: getSec() const{
return _sec;
} Time Time::operator*(unsigned int num)const{//成员运算符重载函数的第一个操作数必须是该类对象
int hour,min,sec;
sec = _sec * num;
min = _min * num + sec/;
hour = _hour * num + min /;
min %= ;
sec %= ;
return Time{hour,min,sec};
}
Time operator*(unsigned int num, const Time &time){//通过友元函数进行反转,定义友元函数的时候不能包含类作用域限定符,也不要包含friend关键
return time * num;
}
std::ostream& operator<<(std::ostream &os, const Time &time){//定义友元<<运算符重载函数
os << time.getHour() << "小时" << time.getMin() << "分" << time.getSec() << "秒";
return os;
}
main.cpp文件:
#include <iostream>
#include "Time.h" int main(int argc, const char * argv[]) {
Time t1{,,};
Time t2 = t1 * ;//将会调用成员*运算符重载函数
Time t3 = * t1;//将会调用友元*运算符重载函数
std::cout << "t1:" << t1 << "\nt2:" << t2 << "\nt3:" << t3; return ;
} 输出结果:
t1:2小时25分30秒
t2:4小时51分0秒
t3:7小时16分30秒
提示:一般来说,要重载<<运算符来显示c_name对象,可使用一个友元函数,其定义如下:
ostream & oprator<<(ostream & os, const c_name & obj){
os <<...;//显示对象的内容
return os;
}
三、重载运算符:作为成员函数还是非成员函数
对于很多运算符来说,可以选择成员函数或非成员函数来实现运算符重载。一般来说,非成员函数应是友元函数,这样他才能访问类的私有数据。
非成员版本的重载运算符函数所需的参数数目与运算符使用的操作数数目相同,所有的操作数作为函数参数显式地传递给非成员运算符重载函数;而成员版本所需的参数数目少一个,因为其中的一个参数是被隐式地传递的调用对象。
在定义作用于类对象的运算符重载函数时,究竟是使用成员函数还是非成员函数还是两者都适用的选择依据有一下两点:
(1)如果运算符的操作数类型一致,选择友元运算符重载函数或成员运算符重载函数中的一种,而不能同时选择这两种;否则将会报错。
(2)如果运算符的操作数类型不一致,例如一个是内置类型,另一个是类对象,那么用成员运算符重载函数实现类对象在运算符左边的重载功能,然后用友元运算符重载函数反转操作数顺序。
四、再谈重载:一个矢量类
五、类的自动转换和强制类型转换
将一个标准类型变量的值赋给另一种标准类型的变量时,如果这两种类型兼容,则C++自动将这个值转换为接收变量的类型。例如:
long count = 5;
double time = 11;
int side = 3.33;
上面的语句都是可行的。因为在C++看来,各种数值类型都表示相同的东西——一个数字,同时C++包含用于进行转换的内置规则。
但是,C++不自动转换不兼容的类型。例如,C++不会自动把int类型转换为指针,虽然他们都是整数,但是整数和指针完全不同:int *ptr = 13;//这样语句是非法的
然而,在无法自动转换时,可以使用强制类型转换。例如,int *ptr = (int *)12;//将整数强制转换为指针后,再赋给指针是可以的
可以将类定义成与基本类型或另一个类相关,使得从一种类型转换成另一种类型是有意义的。在这种情况下,程序员可以指示C++如何自动进行转换,或通过强制类型转换来完成。
下面设计了两个类,类KG以“千克”为单位来表示质量,类Jin以“斤”为单位来表示质量,并且设计了他们之间的相会转换
KG.h
#include <iostream> class KG{
private:
float weight_;
public:
KG();
KG(float weight);//定义了float到KG类型的转换。
float getWeight()const;
friend std::ostream& operator<<(std::ostream& os,const KG &);
};
KG.cpp
#include "KG.h"
#include <ostream>
KG::KG(){
weight_ = ;
}
KG::KG(float weight){
weight_ = weight;
}
/*KG::KG(Jin jin){
weight_ = jin.getWeight()/2;
}*/
float KG::getWeight()const{
return weight_;
}
std::ostream& operator<<(std::ostream& os, const KG &kg){
os << kg.getWeight() << "千克";
return os;
}
Jin.h
#include <iostream>
#include "KG.h" class Jin{
private:
float weight_;
public:
Jin();
Jin(float weight);
Jin(KG kg);
float getWeight() const;
friend std::ostream& operator<<(std::ostream & os, const Jin & jin);
operator KG()const;//转换函数
};
Jin.cpp
#include "Jin.h"
Jin::Jin(){
weight_ = ;
}
Jin::Jin(float weight){
weight_ = weight;
}
Jin::Jin(KG kg){
weight_ = kg.getWeight() * ;
}
float Jin::getWeight()const{
return weight_;
}
std::ostream& operator<<(std::ostream & os, const Jin & jin){
os << jin.getWeight() << "斤";
return os;
}
Jin::operator KG()const{
return weight_/;
}
main.cpp
#include <iostream>
#include "Jin.h" int main(int argc, const char * argv[]) {
KG kg1 = 22.5,kg2;
Jin jin1 = , jin2;//这里,先将int类型的90转换为float,然后使用Jin(float)构造函数
kg2 = jin1;
jin2 = kg1;//程序用Jin的构造函数Jin(KG)来创建一个临时的Jin对象,并将kg1作为初始化值。然后采用逐成员赋值方式将该临时对象的内容复制到jin2中,这一过程称为隐式转换,因为它是自动进行的
std::cout <<" kg1:" << kg1 << "\n kg2:"<< kg2 << "\njin1:" << jin1 << "\njin2:" << jin2 << std::endl; return ;
} 输出结果:
kg1:.5千克
kg2:45千克
jin1:90斤
jin2:45斤
说明:
(1)在C++中,接受一个参数的构造函数为将类型与该参数相同的值转换为类提供了蓝图。例如,Jin类的构造函数Jin(KG kg)为将KG类型的变量转换为Jin类提供了可能。
(2)只有接受一个参数的构造函数才能作为转换函数。
(3)将构造函数作为自动类型转换函数有时可能会导致意外的类型转换,C++关键字explicit用于关闭这种自动特性。也就是说这样声明构造函数:
explicit Jin(KG);
将关闭上面例子中介绍的隐式转换,但仍然允许显示转换,即显式强制类型转换:
KG kg0 = 40;
Jin ji = (Jin)kg0;
Jin ji2 = Jin(kg0);
注意:只接受一个参数的构造函数定义了从参数类型到类类型的转换。如果使用关键字explicit限定了这种构造函数,则它只能用于显式转换,否则也可以用于隐式转换。
(4)函数原型化提供的参数匹配过程,允许使用Jin(float)、 KG(float)构造函数来转换其他数值类型。例如,Jin ji = 12;先将int类型的12转换为float,然后使用Jin(float)构造函数。然而,当且仅当不存在二义性时,才会进行这种二步转换。即如果类Jin还定义了构造函数Jin(long),则编译器将拒绝这些语句,可能指出:int可被转换为long或float,因此存在二义性。
1、转换函数
构造函数只用于将某种类型到类类型的转换,要进行反向的转换,必须使用C++的运算符函数——转换函数。
转换函数是用户定义的强制类型转换,可以像使用强制类型转换那样使用它们。
要将类类型转换为typeName类型,需要使用这种形式的转换函数:
oprator typeName();
注意以下几点:
1)转换函数必须是类方法;
2)转换函数不能指定返回类型;
3)转换函数不能有参数。
typeName指出了要转换成的类型,因此不需要指定返回类型。转换函数是类方法意味着:它需要通过类对象来调用,从而告知函数要转换的值。因此,函数不需要参数。
当类定义了两种或更多种的转换时,可能会出现二义性,例如,类Jin有operator int()和operator float()着两种转换函数,编译器认为下面的语句有二义性而拒绝他:
Jin ji = 12.4;
double a = ji;
因为int和float类型都可以赋值给double变量,所以编译器使用任何一种转换都是合法的,而编译器不承担选择函数的责任,这样就出现了二义性。但是,仍然可以使用显式强制类型转换来指出要使用哪个转换函数。
和转换构造函数一样,转换函数也有其优缺点。提供执行自动、隐式转换的函数所存在的问题是:在用户不希望进行转换时,转换函数也可能进行转换。原则上,应尽量避免隐式转换,使用显式转换。在C++11中,与关闭转换构造函数的隐式转换功能一样,声明转换函数时在其前面加上explicit关键字来关闭转换函数的隐式转换功能。
总结,C++为类提供了下面的转换函数:
*只有一个参数的构造函数用于将类型与该参数类型相同的值转换为类类型。然而,在构造函数声明中使用explicit关键字可以防止隐式转换,而只允许显式转换。
*被称为转换函数的特殊类成员运算符函数,用于将类对象转换为其他类型。转换函数是类成员、没有返回值、没有参数、名为operator typeName(),其中,typeName是类将被转换成的类型。将类对象赋给typeName变量或将其强制转换为typeName类型时,该转换函数将自动被调用。
C++——使用类的更多相关文章
- Java类的继承与多态特性-入门笔记
相信对于继承和多态的概念性我就不在怎么解释啦!不管你是.Net还是Java面向对象编程都是比不缺少一堂课~~Net如此Java亦也有同样的思想成分包含其中. 继承,多态,封装是Java面向对象的3大特 ...
- C++ 可配置的类工厂
项目中常用到工厂模式,工厂模式可以把创建对象的具体细节封装到Create函数中,减少重复代码,增强可读和可维护性.传统的工厂实现如下: class Widget { public: virtual i ...
- Android请求网络共通类——Hi_博客 Android App 开发笔记
今天 ,来分享一下 ,一个博客App的开发过程,以前也没开发过这种类型App 的经验,求大神们轻点喷. 首先我们要创建一个Andriod 项目 因为要从网络请求数据所以我们先来一个请求网络的共通类. ...
- ASP.NET MVC with Entity Framework and CSS一书翻译系列文章之第二章:利用模型类创建视图、控制器和数据库
在这一章中,我们将直接进入项目,并且为产品和分类添加一些基本的模型类.我们将在Entity Framework的代码优先模式下,利用这些模型类创建一个数据库.我们还将学习如何在代码中创建数据库上下文类 ...
- ASP.NET Core 折腾笔记二:自己写个完整的Cache缓存类来支持.NET Core
背景: 1:.NET Core 已经没System.Web,也木有了HttpRuntime.Cache,因此,该空间下Cache也木有了. 2:.NET Core 有新的Memory Cache提供, ...
- .NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类
.NET Core中间件的注册和管道的构建(2)---- 用UseMiddleware扩展方法注册中间件类 0x00 为什么要引入扩展方法 有的中间件功能比较简单,有的则比较复杂,并且依赖其它组件.除 ...
- Java基础Map接口+Collections工具类
1.Map中我们主要讲两个接口 HashMap 与 LinkedHashMap (1)其中LinkedHashMap是有序的 怎么存怎么取出来 我们讲一下Map的增删改查功能: /* * Ma ...
- PHP-解析验证码类--学习笔记
1.开始 在 网上看到使用PHP写的ValidateCode生成验证码码类,感觉不错,特拿来分析学习一下. 2.类图 3.验证码类部分代码 3.1 定义变量 //随机因子 private $char ...
- C# 多种方式发送邮件(附帮助类)
因项目业务需要,需要做一个发送邮件功能,查了下资料,整了整,汇总如下,亲测可用- QQ邮箱发送邮件 #region 发送邮箱 try { MailMessage mail = new MailMess ...
- .NET平台开源项目速览(18)C#平台JSON实体类生成器JSON C# Class Generator
去年,我在一篇文章用原始方法解析复杂字符串,json一定要用JsonMapper么?中介绍了简单的JSON解析的问题,那种方法在当时的环境是非常方便的,因为不需要生成实体类,结构很容易解析.但随着业务 ...
随机推荐
- 【转】成功在AMD主机上用虚拟机安装原版雪豹
转载地址:http://www.jzh.me/archives/205.html/comment-page-1 一直都很想安装苹果的系统,当雪豹出来的时候就更加想了,但是自己的机器是AMD的,而且还是 ...
- Oracle找出非数字
可以这样判断: select translate('99999999999999', '\1234567890', '\') from dual; 返回的是空 select translate(' ...
- PHP获取手机相关信息
该PHP操作类实现获取手机号手机头信息,取UA,取得手机类型,判断是否是opera,判断是否是m3gate,取得HA,取得手机IP 代码如下: <?php /** * @desc 手机操作类 获 ...
- C# 单例模式Lazy<T>实现版本
非Lazy版本的普通单例实现: public sealed class SingletonClass : ISingleton { private SingletonClass () { // the ...
- Python3发送post请求,自动记住cookie
转载自:http://www.cnblogs.com/meitian/p/4607737.html 在做登录的post请求时,需要记住cookie,否则不能访问登录后的页面. 下面是登录的代码: #c ...
- firefox 最新版地址栏后没有生成二维码的工具
下载火狐离线安装包50.0.2版本,安装后找不到这个图标了,搜索下载附加组件管理器cpmanager-1.2.13.xpi,提示没有签名无法安装,最后下载了一个较高版本的cpmanager-1.5.5 ...
- ocruntime
原作: http://www.jianshu.com/p/25a319aee33d 三种方法的选择 Runtime提供三种方式来将原来的方法实现代替掉,那该怎样选择它们呢? Method Resolu ...
- MySQL操作数据库和表的常用命令新手教程
1.查看数据库 获取服务器上的数据库列表通常很有用.执行show databases;命令就可以搞定. mysql> show databases; 2.创建数据库 mysql> crea ...
- Debug不崩溃Release版本崩溃的一种原因
今天有一个工程Debug是正常,Release崩溃,郁闷至极. 研究了一下下午,原因是一个类成员变量没有构造函数中初始化.而Debug版本正好没有问题. 所以定义类成员,一定不能忘记初始化!!
- include_path详细解析
include_path详细解析 原文地址:http://www.laruence.com/2010/05/04/1450.html 1.php默认的包含路径为 .;C:\php\pear 即 ...