C/C++程序设计
1
六、数据的类型转换七、自增运算符和自减运算符八、函数调用运算符 ()
C/C++程序设计
2
六、数据的类型转换编译器扫描表达式,如果探查到存在一个不严格的数据匹配,就启动类型转换判断机制,通过类型匹配较检和转换,源操作数的数据类型得以转变为目标操作数的类型。
编译器首先执行常规的算术类型转换,如将整型数转换为浮点型等。
如果常规的类型转换不胜任,则搜寻用户提交的转换函数。
用户提交的转换函数是单参构造函数和运算符类型转换函数。
C/C++程序设计
3
转换函数如果满足目标参量的类型要求系统就自动调用该函数。
单参构造函数的目标方向是其它类型转换当前类的方向,运算符类型转换函数的目标是将当前类转换为另外的类型。
转换函数的隐含调用遵循访问控制属性的控制,且不能存在歧义的理解。
在外部涉及的类型转换要求相应的公共成员函数。
C/C++程序设计
4
1,单参数构造函数的类型转换形如 CType (type)的构造函数是单参数的构造函数,起类型转换作用的构造函数就是单参数的构造函数。 type 是有定义的类型,type通常表示算术类型或指针类型。
当原本需要 CType对象类型的地方出现 type类型的表达式,相应的单参数构造函数可被调用。
例如,双目运算符 x@obj需要 CType对象 obj出现的位置实际是 type型的表达式 v,则导致 CType (type)的隐含调用。
对于函数原型 [void f (CType);],如果虚实结合时调用形式为 f (v)则构造函数 CType (type)被调用。
C/C++程序设计
5
[例 ] 构造函数的类型转换
#include <stdio.h>
#include<string.h>
class CType
{ private,float m_x;
public,CType (float x);
CType (const CType& r) ;
void Show ()const { printf ("m_x=%4.1f\n",m_x); }
};
CType::CType (float x)
{ m_x=x;
printf ("%d,CType (float);",(int)m_x);
}
C/C++程序设计
6
CType::CType (const CType& r)
{ printf ("%d,CType (const CType&); ",(int) r.m_x);
memcpy (this,&r,sizeof (CType));
}
void fc (CType c) { printf (" fc(CType c); "); c.Show(); }
void fr ( CType& r) { r.Show (); }
void main()
{ CType a (1); a.Show (); fc (2);
a=3; fr (a);
fc (a); fr (a);
}
// fc(2)导致构造函数 CType(float)的调用,相当于
//fc(CType(2));
C/C++程序设计
7
a=3编译器隐含处理为 a=CType(3)先调用 CType(float)
产生无名对象,接着调用等号运算符函数。
fc(a)启动拷贝构造函数 CType(const CType&)将数值对象压入堆栈,fr(a)无需如此。
程序运行输出结果如下:
1,CType (float); m_x= 1.0
2,CType (float); fc(CType c); m_x= 2.0
3,CType (float); m_x= 3.0
3,CType (const CType&);
fc(CType c); m_x= 3.0
m_x= 3.0
C/C++程序设计
8
2,explicit关键字抑制自动类型转换关键字 explicit的目的是禁止单参数构造函数充当隐含类型转换的作用,这个 explicit在 iostream类库中其着明显的禁闭作用,从而避免早期的隐含类型转换导致的歧义错误,
该关键字的使用方法是在相关的单参数构造函数声明语句前放置此关键字如下:
class CType
{ explicit CType (char* s); CType(int n); };
CType(char* s) 的隐含转换作用就被关闭,CType(int n)
的隐含类型转换则依然起作用。
Explicit也就是做出请显式调用构造函数的提醒。
C/C++程序设计
9
3,运算符类型转换函数单参构造函数是朝当前类方向转换的类型转换函数。运算符类型转换函数将当前类型的数据转换为另外的类型,即从 CType转换到指定的目标类型 type。
目标类型 type通常是算术类型或指针类型。
其语法格式或使用的三个步骤为:
a,在类中声明为公共访问属性的非静态的成员函数以便外部隐含调用
class CType { public,operator type();,.,};
class 当前类名 { 公共属性,运算符 目标类型 (); };
C/C++程序设计
10
b,在类外定义时,返回一个可转换为 type类型的成员变量。
如,m_expre
CType::operator type()
{ ////// 语句序列 ; //...;
return m_expre;
//或 return (type)m_expre;
}
C/C++程序设计
11
c,隐含调用格式:
对于 [CType obj; type v;],运算符 @作用于包含对象的表达式,类型 type代表运算符表达式的目标方向。 例如,
v@obj // 或单目运算符 @obj
此时系统找到类型转换函数 operator type()将其自动本质的处理为,
v@ (type)m_expre // 或单目运算符 @ (type) m_expre
对于 type 型的函数形参如 void f(type v) 虚实结合时
F (obj)也触发类型转换运算符函数的调用。一般地 obj出现在 type型的目标环境,引起运算符类型转换函数的调用。
C/C++程序设计
12
运算符类型转换函数仅在对象定义好之后才发生作用。
运算符转换函数的特点为,
1) 既不带入口参量也不带返回类型。因为返回类型已经由 type指出。
2) 不能是友元函数可以是虚函数。
[例 ] 多个类型转换函数构成重载函数
#include <stdio.h>
class CType
{ public,explicit CType ( double d)
{ m_d=d ; m_expre= (int)d+1; printf ("CType();"); }
operator double()
{ printf ("double (%d);",(int) m_d); return m_d; }
C/C++程序设计
13
operator int()
{ printf ("int (%d);",m_expre); return m_expre; }
protected,double m_d;
int m_expre ;
};
int f (int n) { return n; }
void main()
{ CType a(1); f(a) ;
a.operator double (); a=CType (2);
printf ("%d,%f\n",(int)a,(double) a);
} //输出:
CType(); int(2); double(1); CType(); double(2); int(3);
3,2.000000
C/C++程序设计
14
说明,
为无歧义地调用类型转换函数,特在 a前加上类型转换
(int),以协助编译器完成选择。
这样 (int)a调用转换函数 operator int(),(double)a调用转换函数 operator double()。
运算符转换函数可以存在多个版本,歧义的乘虚而入就在所难免,定义运算符转换函数时不宜提供与映射本质无关的版本,以免构成混乱。
C/C++程序设计
15
有了 operator type()运算符类型转换函数,原先只可以出现 type型表达式的地方可以出现 CType类型表达式;
有了 CType(type)单参数的构造函数,原来应该出现
CType类型表达式的地方可以出现 type型表达式。
这两个函数完成相反的类型转换或数据映射,编译器在必要的时候自动调用这两种类型转换函数。
C/C++程序设计
16
七、自增运算符和自减运算符后置运算符比前置运算符的优先级高。
后置运算符 rLvalue++要求操作数是一个左值而运算的结果是右值,这个结果是操作数变化前的值,即编译器必事先需将操作数的原始值存放在一个临时单元中。
前置运算符要求操作数是一个左值而运算的结果是变化后的左值,前置运算符直接对内存单元操作无需临时单元,
为了鉴别这两个前后不一的运算符,系统对于后置运算符添补一个虚哑的 int类型参量。
这个类型参量一般不带相关的形式变元,特别地担当名称细分的重任,增加的参量必须是 int 型的,不允许其它虚哑的数据类型。
C/C++程序设计
17
[例 ] 前置后置运算符函数
#include<stdio.h>
class CA
{ long x;
//私有的成员对应的运算符全局函数声明为友元函数
public:CA (long m) { x=m; }
void Show(){ printf ("%d x=%d\n",num,x); }
CA& operator++();
CA operator++(int);
friend CA operator--(CA&,int);
static int num;
};
C/C++程序设计
18
CA& CA::operator++()
{ ++x;
printf ("%d.CA::operator++(); x=%d\n",num++,x);
return *this;
}
CA CA::operator++(int)
{ printf ("%d.CA::operator++(int); x=%d\n",num++,x);
CA temp (*this);
x++;
return temp;
}
C/C++程序设计
19
CA operator--(CA& r,int)
{ printf ("%d.operator--
(CA&,int);x=%d\n",CA::num++,r.x);
CA temp(r);
r.x--; return temp;
}
int CA::num=1;
void main()
{ CA a(1); a.operator++(1); a.operator++();
++a--++; a.Show();
}
C/C++程序设计
20
程序运行输出结果
1.CA::operator++(int);x=1
2.CA::operator++();x=3
3.operator--(CA&,int);x=3
4.CA::operator++(int);x=3
5.CA::operator++();x=4
6.x=2
当编译器遇到 ++a对象表达式时,就寻找
CA::operator++()
形式的成员版本,或 operator++(CA&)形式的全局版本,找到唯一的版本就转换为正确的调用,本例中存在成员版本,
因此转换为调用 a.operator++()。
这和其它单目运算符函数的转换步调是一致的。
C/C++程序设计
21
编译器扫描到后置运算符 a--则搜寻 operator--(CA&,int)
的全局版本,或相应的成员版本 CA::operator--(int)。
本例中存在全局版本,因此表达式 a--系统转换为
operator--(a,0)的调用格式。
实在参数 0匹配原来的函数接口中虚设的不带变元的类型参量 int。
类似地 a++转换为函数调用 a.operator++(0)。
后置运算符的优先级别高于前置运算符,编译器分解系列操作 ++a--++的先后次序为,
++((a--)++)
即先执行后置运算符函数,然后执行前置运算符函数。
C/C++程序设计
22
序列运算符函数调用 ++a--++之结果是有副作用的。
副作用的始作俑者是后置运算符函数,对于孤零零的
a--++运算符函数调用,由于其函数在函数体中进行分离的操作,一方面对象自增一个单位另一方面函数的返回结果是原来位置的临时局部对象。
对于简单变量的后置运算 v++,编译器为了杜绝此种模棱两可的局面,加上一个约束即 v++的结果为右值表达式,
这就限制 v++--表达式的出现。
为模拟简单变量后置运算的结果为右值,可以在后置运算符函数的返回类型前加 const限制。
C/C++程序设计
23
八、函数调用运算符 ()
函数调用运算符函数只能是非静态的成员版本,它的用法是对象名当作函数名一样使用,也就是函数对象名语法。
构造函数的隐含调用就是建立在函数对象名的运转机制上。
作为成员的函数调用运算符函数的声明格式为:
ret_type operator() (type1 v1,type2 v2,...);
返回类型 函数调用运算符函数 (0到多个形参列表 );
C/C++程序设计
24
在类外定义的格式为:
ret_type CType::operator() (type1 v1,type2 v2,...)
{ 隐含 this的语句序列 ; }
对象名 a的调用格式为 a(v1,v2,...),此种格式导致显式调用 a.operator() (v1,v2,...)。
函数调用运算符函数的形参个数可多于两个以上,也可以设置默认值。
下面的例子模拟 FORTRAN语言中维数的说明和使用形式。圆括号指定数组的维数,索引数组的元素也采用圆括号形式。
C/C++程序设计
25
[例 ] 函数对象名语法模拟 FORTRAN数组的定义
class CType
{ double **pp;
int m_nRow;
int m_nColumn;
public,CType (int r,int c=1);
~CType();
double& operator () (int r,int c=0);
};
C/C++程序设计
26
CType::CType(int row,int column)
{ m_nRow=row;
m_nColumn= column;
pp=new double*[row];
for (int j=0; j< row; j++)
pp[j]=new double [column];
}
inline double& CType::operator()(int r,int c)
{ return pp[r][c]; }
CType::~CType()
{ for (int j=0; j< m_nRow; j++)
delete [ ] pp[j];
delete [ ] pp;
}
C/C++程序设计
27
#include<stdio.h>
void main()
{CType a(2,3);
CType b(3);
a(0,0)=1; a(0,1)=2; a(0,2)=3;
a(1,0)=2; a(1,1)=4;
a.operator ()(1,2)=6;
b(0)=2;b(1)=2;b(2)=2;
printf("a(0)*b=%f\t",a(0,0)*b(0)+a(0,1)*b(1)+a(0,2)*b(2));
printf("a(1)*b=%f\n",a(1,0)*b(0)+a(1,1)*b(1)+a(1,2)*b(2));
}
C/C++程序设计
28
//输出,a(0)*b=12.000000 a(1)*b=24.000000
在上面的例题中,函数对象名表达式 a(N,M)将引发构造函数和特殊的 operator ()函数 的调用转换。
编译器根据其周边环境切入正确的版本,在对象的定义处调用构造函数,在其它位置执行 operator ()运算符函数,
如此分流构造函数和 operator ()运算符函数的重载。
C/C++程序设计
29