成员函数与非成员函数的抉择
1.尽量用类的非成员函数以及友元函数替换类的成员函数
例如一个类来模拟人People
1 class People{ 2 public: 3 ... 4 void Getup( ); 5 void Washing( ); 6 void eating( ); 7 ... 8 }
其实上面三个动作是早上“起床”、“洗簌”、“吃饭”三个常见的动作,如果现在用一个函数来表示使用成员函数即为
1 class People 2 { 3 ... 4 void morningAction( ) 5 { 6 Getup( ); 7 Washing( ); 8 eating( ); 9 } 10 }
如果写一个非成员函数即为
1 void moringAction(People& p) 2 { 3 p.Getup( ); 4 p.Washing( ); 5 p.eating( ); 6 }
那么是选择类的成员函数还是类的非成员函数呢?
面向对象则要求是,将操作数据的函数与数据放在一起。但这不意味着要选择成员函数。从封装的角度看,成员函数的moringAction封装性比非成员函数要低。如果某些东西被封装,它就不再可见。越多东西被封装,越少人可以看到它。所以使用非成员函数的类,封装性较低。而越少人看到它,我们就有越大弹性去变化它,因为我们的改变仅仅直接影响看到改变的那些人事物。因此,越多东西被封装,改变哪些东西能力越大。
在考虑对象内的数据。越少的代码可以看到数据(访问它),越多的数据可以被封装,而我们也就越能自由改变对象数据。现在如果一个成员函数、非成员函数都能提供相同的机能,我们选择非成员函数。
在说下面内容之前我们先谈谈C++中的类型转换分显示类型转换和隐式类型转换。
2.显示类型转换
C++有显示类型转换操作符分别为:static_cast,const_cast,dynamic_cast和reinterpret_cast
2.1static_cast
转换功能与C风格类型转换一样,含义也一样。通过使用static_cast可以使没有继承关系的类型进行转换。但是要注意不能将内置类型转化为自定义类型,或者将自定义类型转化为内置类型,并且不能将cosnt类型去掉,但能将non-cosnt类型转换为const类型。例如:
char a='a';
int b=static_cast<int>(a);
两个类型之间的转换,其实也是要自己实现支持,原理和内置类型之间转换相似。
1 #include <iostream> 2 class Car; 3 4 class People 5 { 6 public: 7 People(int a,int h) 8 :age(a),height(h) 9 {} 10 11 inline int getAge() const 12 { 13 return age; 14 } 15 16 inline int getHeight() const 17 { 18 return height; 19 } 20 21 People & operator=(const Car& c); 22 private: 23 int age; 24 int height; 25 }; 26 27 class Car 28 { 29 public: 30 Car(double c, double w) 31 :cost(c),weight(w) 32 {} 33 34 inline double getCost() const 35 { 36 return cost; 37 } 38 39 inline double getWeight() const 40 { 41 return weight; 42 } 43 44 Car & operator=(const People& c); 45 private: 46 double cost; 47 double weight; 48 }; 49 50 People & People::operator=(const Car& c) 51 { 52 age = static_cast<int>(c.getCost()); 53 height = static_cast<int>(c.getWeight()); 54 return *this; 55 } 56 57 Car & Car::operator=(const People& c) 58 { 59 cost = static_cast<double>(c.getAge()); 60 weight = static_cast<double>(c.getHeight()); 61 return *this; 62 } 63 64 int main(int argc,char * argv[]) 65 { 66 Car c(1000.87,287.65); 67 People p(20,66); 68 People p2(0,0); 69 Car c2(0.00,0.00); 70 p2=c; 71 c2=p; 72 std::cout<< "car'info: cost is " << c2.getCost() << ". weight is " << c2.getWeight() <<std::endl; 73 std::cout<< "people'info: age is " << p2.getAge() <<". height is " << p2.getHeight() <<std::endl; 74 return 0; 75 }
运行结果为
car'info: cost is 20. weight is 66 people'info: age is 1000. height is 287
2.2const_cast
主要用来去掉const和volatileness属性,大多数情况下是用来去掉const限制。
2.3dynamic_cast
它用于安全地沿着继承关系向下进行类型转换。一般使用dynamic_cast把指向基类指针或者引用转换成其派生类的指针或者引用,并且当转换失败时候,会返回空指针。
2.4reinterprit_cast
该转换最普通用途就是在函数指针类型之间进行转换。
1 typedef void (*fun) ( ) //一个指向空函数的指针 2 fun funArray[10]; //含有10个函数指针的数据。 3 int function( ); //一个返回值为int类型函数 4 funArray[0] = &function( ) //错误!类型不匹配 5 funArray[0] = reinterpret_cast<fun>(&function); //ok
3.使用非成员函数可以发生隐式转换
C++是支持隐式类型转换的,例如在做运算的时候,或者传递参数给函数的时候常常会发生隐式类型转换。
假设你设计一个class用来表现有理数。其实令类支持隐式类型转换是一个槽糕的决定。当然在建立数值类型时就是例外。下面定义一个有理数类型:
1 class Rational { 2 public: 3 Rational( int numerator = 0,int denominator =1 ); 4 int numerator( ) const; 5 int denominator ( ) const ; 6 private: 7 ... 8 }
有理数类型想当然支持算数运算,但是不确定是否声明为成员函数或非成员函数或者是友元函数来实现它们。
首先是考虑成员函数写法:
1 class Rational { 2 public: 3 ... 4 const Rational operator* (const Rational& rhs) const; 5 ... 6 } 7 Rational testOne(1,4); 8 Rational testTwo(1,1); 9 //做算术运算 10 Rational result = testOne * testTwo; 11 //与常量做运算 12 result = testOne * 2; //ok 13 //乘法满足交换定律 14 result = 2 * testOne //error!!
那么为什么将常量提前就错误了呢?这里我们换一种写法
1 result = testOne.operator*(2); //ok 2 result =2.operator*(oneHalf); //error
这里发生了什么?其实在第一行式子里发生了所谓隐式类型转换。哪里发生了隐式类型转换呢?看上面的代码operator*函数参数是const Rational类型,而2是一个cosnt int类型,。编译器发现有non-explicit型的单参数类为int类型的构造函数,可以造出Rational类型。所以编译器那样做了,发生了隐式类型转换。所以第一个式子可以正常运行,但是第二个是没有将Rational类型转换为int类型的。
设计出上面两个式子正常运行才算合理的运行。
1 class Rational{ 2 ... 3 }; 4 const Rational operator*(const Rational & lhs, const Rational & rhs) 5 { 6 return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator() ); 7 } 8 Rational testOne(1, 4); 9 Rational result; 10 result = oneFourth *2; 11 result = 2 * oneFourth; 通过 12 }
按上面代码设计成非成员函数,那么在调用int类型整数的时候会发生隐式类型转换。
operaotr* 是否应该称为Rational class的一个友元函数呢?对本例子而言,完全没有必要。
如果你需要为某个函数所有参数进行类型转换,那么这个函数必须是个非成员函数。
4.谨慎使用隐式类型转换
我们对一些类型隐式转换无能为力,因为它们是语言本身的特性。不过当编写自定义类时,我们可以选择是否提供函数让编译器进行隐式类型转换。
有两种函数允许编译器进行隐式类型转换:单参数构造函数与隐式类型转换运算符。
1 public Name{ 2 public: 3 Name(const string& s); //转换string到Name 4 5 ... 6 }; 7 8 class Rational { //有理数类 9 public: 10 //转换从int到有理数类 11 Rational(int numerator=0,int denominatior =1); 12 ... 13 }
C++是支持隐式类型转换的,例如在做运算的时候,或者传递参数给函数的时候常常会发生隐式类型转换。有两种函数允许编译器进行隐式转换。单参数构造函数和隐式类型转换运算符。
也许前面说道了一些隐式类型转换带来的方便之处,下面说说一些麻烦之处。
1 template<class T> 2 class Array{ 3 Array(int lowBound,int highBound); 4 Array(int size); 5 T& operator[](int index); 6 ... 7 }; 8 bool oerpator==(const Array<int>& lhs,const Array<int>& rhs); 9 Array<int> a(10); 10 Array<int> b(10); 11 ... 12 for(int i=0;i < 10; ++i) 13 if(a == b[i]) { 14 ... } 15 else 16 { 17 ... 18 }
如果这里不小心将数组a的下标忘记写了,这里编译器应该报出警告信息,但是其实是没有的。因为编译器将a看成Array<int>类型,b为 int,根据上面的经验,编译器看到一个non-explicit单参数构造函数其参数类型为int,而且operator需要一个Array<int>类型,那么编译器就这样做了,发生了隐式类型转换。相信如果真的发生这样,会很麻烦。
怎么样避免呢?将构造函数声明为explicit。避免隐式类型转换。