c++ 成员函数与非成员函数的抉择_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。避免隐式类型转换。

时间: 2024-10-04 00:02:59

c++ 成员函数与非成员函数的抉择_C 语言的相关文章

深入C++ 函数映射的使用详解_C 语言

想想我们在遇到多语句分支时是不是首先想到的是 switc case 和 if else if ...这2种方式在编码方面确实简单少,但是当分支达到一定数量后,特别是分支内部有嵌套大段代码或者再嵌套分支,代码会显得异常臃肿,十分难以维护,对于if else if 语句过多的分支带来过多的判定句,势必会影响效率. 3种替代方法简述:1.使用map,需要构建树和节点,比数组的方式消耗更多的内存,查询时间复杂度为Log(N),但扩展起来方便. 2.使用数组,查询直接索引定位, 一般来讲我们是连续的初始化

详解C++编程中向函数传递引用参数的用法_C 语言

引用类型的函数参数向函数传递引用而非大型对象的效率通常更高. 这使编译器能够在保持已用于访问对象的语法的同时传递对象的地址. 请考虑以下使用了 Date 结构的示例: // reference_type_function_arguments.cpp struct Date { short DayOfWeek; short Month; short Day; short Year; }; // Create a Julian date of the form DDDYYYY // from a G

C++友元函数与拷贝构造函数详解_C 语言

一.友元函数 1.友元函数概述: (1)友元函数是定义在一个类外的普通函数. 友元函数和普通函数的定义一样;在类内必须将该普通函数声明为友元. (2)友元函数不是成员函数. 不能通过对象来调用,而是直接调用;友元函数可以访问类的公有.受保护以及私有成员,但是必须通过对象.对象指针或者对象引用来访问. 2.友元函数的声明: friend 返回值类型 函数名(参数表); 在类中只需要将这个声明放置在公有部分即可. class Point { double x, y; public: Point(){

C/C++杂记 虚函数的实现的基本原理(图文)_C 语言

1. 概述 简单地说,每一个含有虚函数(无论是其本身的,还是继承而来的)的类都至少有一个与之对应的虚函数表,其中存放着该类所有的虚函数对应的函数指针.例: 其中: B的虚函数表中存放着B::foo和B::bar两个函数指针. D的虚函数表中存放的既有继承自B的虚函数B::foo,又有重写(override)了基类虚函数B::bar的D::bar,还有新增的虚函数D::quz. 提示:为了描述方便,本文在探讨对象内存布局时,将忽略内存对齐对布局的影响. 2. 虚函数表构造过程 从编译器的角度来说,

C++中函数模板的用法详细解析_C 语言

定义 我们知道函数的重载可以实现一个函数名多用,将功能相同或者类似函数用同一个名来定义.这样可以简化函数的调用形式,但是程序中,仍然需要分别定义每一个函数. C++提供的函数模板可以更加简化这个过程. 所谓函数模板实际上是建立一个通用函数,其涵涵素类型额形参类型不具体指定,用一个虚拟的类型来代表,这个通用函数就称为函数模板. 凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需要在模板中定义一次即可.在调用函数时,系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能

深入解析C语言中函数指针的定义与使用_C 语言

1.函数指针的定义    函数是由执行语句组成的指令序列或者代码,这些代码的有序集合根据其大小被分配到一定的内存空间中,这一片内存空间的起始地址就成为函数的地址,不同的函数有不同的函数地址,编译器通过函数名来索引函数的入口地址,为了方便操作类型属性相同的函数,c/c++引入了函数指针,函数指针就是指向代码入口地址的指针,是指向函数的指针变量. 因而"函数指针"本身首先应该是指针变量,只不过该指针变量指向函数.这正如用指针变量可指向整形变量.字符型.数组一样,这里是指向函数.C在编译时,

C语言 函数指针(指向函数的指针)详解_C 语言

一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似.我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数.这种指针就是函数指针. 函数指针的定义形式为: returnType (*pointerName)(param list); returnType 为函数返回值类型,pointerNmae 为指针名称,param list 为函数参数列表.参数列表中

C语言中函数与指针的应用总结_C 语言

1. 首先,在C语言中函数是一种function-to-pointer的方式,即对于一个函数,会将其自动转换成指针的类型. 复制代码 代码如下: #include<stdio.h> void fun(){} int main(void){   printf("%p %p %p\n", &fun, fun, *fun);   return 0;} -------------------------------------------------------------

指向类成员函数的指针其实并非指针_C 语言

1.与常规指针不同,一个指向成员的指针并不指向一个具体的内存位置,它指向的是一个类的特定成员,而不是指向特定对象里的特定成员.通常最清晰的做法,是将指向数据成员的指针看作为一个偏移量. 这个偏移量告诉你,一个特定成员的位置距离对象的起点有多少个字节. 2.给定一个成员在类内的偏移量,为了访问位于那个偏移量的数据成员,我们需要该类的一个对象的地址.这时候就需要 .*和->*的操作.pC->*pimC,请求将pC内的地址加上pimC内的偏移量,为的是访问pC所指向的C对象中适当的数据成员.aC.*