c++ 成员函数与非成员函数的抉择

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。避免隐式类型转换。

(0)

相关推荐

  • C++指向类成员函数的指针详细解析

    首先 函数指针是指向一组同类型的函数的指针:而类成员函数我们也可以相似的认为,它是指向同类中同一组类型的成员函数的指针,当然这里的成员函数更准确的讲应该是指非静态的成员函数.前者是直接指向函数地址的,而后者我们从字面上也可以知道 它肯定是跟类和对象有着关系的. 函数指针实例: 复制代码 代码如下: typedef int (*p)(int,int);//定义一个接受两个int型且返回int型变量的函数指针类型int func(int x,int y){ printf("func:x=%d,y=%

  • c++ 成员函数与非成员函数的抉择

    1.尽量用类的非成员函数以及友元函数替换类的成员函数 例如一个类来模拟人People 复制代码 代码如下: 1 class People{ 2 public: 3 ... 4 void Getup( ); 5 void Washing( ); 6 void eating( ); 7 ... 8 } 其实上面三个动作是早上"起床"."洗簌"."吃饭"三个常见的动作,如果现在用一个函数来表示使用成员函数即为 复制代码 代码如下: 1 class Pe

  • C++中成员函数和友元函数的使用及区别详解

    为什么使用成员函数和友元函数 这个问题至关重要,直接影响着后面的理解: 程序数据: 数据是程序的信息,会受到程序函数的影响.封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全. 数据封装引申出了另一个重要的 OOP 概念,即 数据隐藏 .数据封装 是一种把数据和操作数据的函数捆绑在一起的机制, 数据抽象 是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制.C++ 通过创建类来支持封装和数据隐藏(public.protected.p

  • C++之普通成员函数、虚函数以及纯虚函数的区别与用法要点

    普通成员函数是静态编译的,没有运行时多态,只会根据指针或引用的"字面值"类对象,调用自己的普通函数:虚函数为了重载和多态的需要,在基类中定义的,即便定义为空:纯虚函数是在基类中声明的虚函数,它可以再基类中有定义,且派生类必须定义自己的实现方法. 假设我们有三个类Person.Teacher.Student它们之间的关系如下: 类的关系图 普通成员函数 [Demo1] 根据这个类图,我们有下面的代码实现 #ifndef __OBJEDT_H__ #define __OBJEDT_H__

  • 怎么实现类的成员函数作为回调函数

    如果试图直接使用C++的成员函数作为回调函数将发生错误,甚至编译就不能通过.其错误是普通的C++成员函数都隐含了一个传递函数作为参数,亦即"this"指针,C++通过传递this指针给其成员函数从而实现程序函数可以访问C++的数据成员.这也可以理解为什么C++类的多个实例可以共享成员函数却-有不同的数据成员.由于this指针的作用,使得将一个CALL-BACK型的成员函数作为回调函数安装时就会因为隐含的this指针使得函数参数个数不匹配,从而导致回调函数安装失败.要解决这一问题的关键就

  • PHP回调函数与匿名函数实例详解

    本文实例讲述了PHP回调函数与匿名函数.分享给大家供大家参考,具体如下: 回调函数和匿名函数 回调函数.闭包在JS中并不陌生,JS使用它可以完成事件机制,进行许多复杂的操作.PHP中却不常使用,今天来说一说PHP中中的回调函数和匿名函数. 回调函数 回调函数:Callback (即call then back 被主函数调用运算后会返回主函数),是指通过函数参数传递到其它代码的,某一块可执行代码的引用. 通俗的解释就是把函数作为参数传入进另一个函数中使用:PHP中有许多 "需求参数为函数"

  • 深入探讨:宏、内联函数与普通函数的区别

    内联函数的执行过程与带参数宏定义很相似,但参数的处理不同.带参数的宏定义并不对参数进行运算,而是直接替换:内联函数首先是函数,这就意味着函数的很多性质都适用于内联函数,即内联函数先把参数表达式进行运算求值,然后把表达式的值传递给形式参数.    内联函数与带参数宏定义的另一个区别是,内联函数的参数类型和返回值类型在声明中都有明确的指定:而带参数宏定义的参数没有类型的概念,只有在宏展开以后,才由编译器检查语法,这就存在很多的安全隐患.    使用内联函数时,应注意以下问题:    1)内联函数的定

  • C++的get()函数与getline()函数使用详解

    C++ get()函数读入一个字符 get()函数是cin输入流对象的成员函数,它有3种形式:无参数的,有一个参数的,有3个参数的. 1) 不带参数的get函数 其调用形式为 cin.get() 用来从指定的输入流中提取一个字符(包括空白字符),函数的返回值就是读入的字符. 若遇到输入流中的文件结束符,则函数值返回文件结束标志EOF(End Of File),一般以-1代表EOF,用-1而不用0或正值,是考虑到不与字符的ASCII代码混淆,但不同的C ++系统所用的EOF值有可能不同. [例]

  • PHP回调函数及匿名函数概念与用法详解

    本文实例讲述了PHP回调函数及匿名函数概念与用法.分享给大家供大家参考,具体如下: 1.回调函数 PHP的回调函数其实和C.Java等语言的回调函数的作用是一模一样的,都是在主线程执行的过程中,突然跳去执行设置的回调函数: 回调函数执行完毕之后,再回到主线程处理接下来的流程 而在php调用回调函数,不想c以及java那样直接使用函数名作为函数参数,而是在php中使用函数对应的字符串名称执行 1.1.无参数回调 <?php //无参数回调 function callback(){ echo 'ex

  • c++11 符号修饰与函数签名、函数指针、匿名函数、仿函数、std::function与std::bind

    一.符号修饰与函数签名 1.符号修饰 编译器将c++源代码编译成目标文件时,用函数签名的信息对函数名进行改编,形成修饰名.GCC的C++符号修饰方法如下: 1)所有符号都以_z开头 2)名字空间的名字 名字空间(或类)的名字前加上N 名字前还有一个数字,是名字的字符数.比如1C,1是C的长度. 3)函数名 与名字空间一样,函数名前也有数字,比如4func,4是func的字符数. 4)参数 参数以E开头 例子 N::C::func(int) 的函数签名经过修饰为_ZN1N1C4funcEi 2.函

  • C++知识点之inline函数、回调函数和普通函数

    目录 一.inline内联函数# 1.1 使用# 1.2 编译器对 inline 函数处理步骤# 1.3 优缺点# 1.3.1 优点# 1.3.2 慎用内联# 1.3.3 不宜使用内联# 1.4 虚函数(virtual)可以是内联函数(inline)吗?# 二.回调函数和普通函数# 2.1 什么是回调函数?# 2.2 为什么要使用回调函数?# 回调函数和普通函数有什么区别? 总结 一.inline内联函数# 特征 相当于把内联函数里面的内容写在调用内联函数处: 相当于不用执行进入函数的步骤,直接

随机推荐