博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《Essential C++》读书笔记 之 基于对象编程风格
阅读量:6555 次
发布时间:2019-06-24

本文共 14899 字,大约阅读时间需要 49 分钟。

《Essential C++》读书笔记 之 基于对象编程风格

2014-07-13

 

   

  

   

  

 

  )

4.1 如何实现一个class


 

class定义由两部分组成:class的声明,以及紧接在声明之后的主体。主体部分由一对大括号括住,并以分号结尾。

主体内的两个关键词public和private。public members可以在程序的任何地方被取用,private members只能在member function或class friend内被取用。

以下是stack class的起始定义:

1 class Stack { 2 public:      3     bool   push( const string& ); 4     bool   pop(  string &elem ); 5     bool   peek( string &elem ); 6  7     bool   empty(); 8     bool   full(); 9 10        //size()定义于class本身内,其它members则仅仅只是声明11     int    size() { return _stack.size(); }12 private:13     vector
_stack;14 };
View Code

以下是如何定义并使用Stack class object:

1 void fill_stack( Stack &stack, istream &is = cin )2 {3     string str;4     while ( is >> str && ! stack.full() )5             stack.push( str );6 7     cout << "Read in " << stack.size() << " elements\n";8 }
View Code

所有member function都必须在class主体内进行声明。至于是否要同时进行定义,可自由决定义。

  • 如果要在class主体内定义,这个member fuction会自动被视为inline函数。例如size()函数
  • 如果要在class主体外定义,必须使用特殊语法,来分辨该函数究竟属于哪一个class。如果希望该函数为inline,应该在最前面指定关键字inline:
inline bool Stack::empty(){    return _stack.empty();}

上述的: Stack::empty()

告诉编译器说,empty()是Stack class的一个member。class名称之后的两个冒号(Stack::)是所谓的class scope resolution(类范围决议)运算符。

  • 对inline函数而言,定义于class主体内或主体外,并没有分别。class定义式及其inline member function通常会放在与class同名得头文件中。例如Stack class的定义和其empty()函数都放在Stack.h头文件中。
  • non-inline member functions应该在程序代码中定义,该文件通常和class同名,其后接着扩展名.c,.cc,.cpp。

4.2 什么是Constructors(构造函数)和Destructors(析构函数)


 

Constructor

Constructor(构造函数)的作用是对data member进行初始化。

语法规定: costructors函数名称必须和class名称相同。constructor不应指定返回型别,亦不需返回值。它可以被重载。看以下代码:

1 class Triangular 2 { 3 public: 4     //一组重载constructors 5     Triangular(); //default constructors 6     Triangular(int lin); 7     Triangular(int len, int beg_pos); 8 private: 9     int _length;  //元素数目10     int _beg_pos; //起始位置11     int _next;    //下一个迭代目标12 }
View Code

如何调用构造函数:

1     //对t施行default constructor(无参构造函数) 2     Triangular t; 3     //调用戴两个参数的constructor 4     Triangular t2(10,3); 5     //是调用constructor or assignment operator呢?答案是constructor,调用戴一个参数的constructor 6     Triangular t3=8; 7  8     //该语句t5被定义为一个函数,其参数表为空,返回Triangular object。why? 9     //因为C++必须兼容与C,对C而言,t5之后带有小括号,会被视为函数。10     Triangular t5();
View Code

对于默认函数,它不需要任何arguments。这意味着以下两种情况:

  • 第一,它不接收任何参数:
1 Triangular::Triangular()2 {3     int _length=1;4     int _beg_pos=1;5     int _next=0;6 }
View Code
  • 第二,它为每个参数提供了默认值:
1 class Triangular2 {3 public:4     //default constructors5     Triangular(int len=1, int beg_pos=1);
View Code
Member Initialization List(成员初值表)

Constructor定义式的第二种初始化语法,是Member Initialization list:

1 Triangular::Triangular(const Triangular &rhs)2     : _length(rhs._length), _beg_pos(rhs._beg_pos),_next(rhs._next)3 {}  //是的,空的

Member Initialization List的主要作用是将参数传递给member class object的constructor。假设重新定义Triangular,令它包含一个string member:

1 class Triangular2 {3 public:4     //...5 private:6     string _name;7     int _length, _beg_pos, _next;8 }
View Code

为了将_name的初值传给string constructor,必须以member intialization list完成。代码如下:

1 Triangular::Triangular(int len, int bp)2     : _name("Triangularr")3 {4     _length=len>0?len:1;5     _beg_pos=bp>0?bp:1;6     _next=_beg_pos-1;7 }
View Code

Destructor

和constructor对立的是destructor。所谓destructor是用户自行定义的一个class member。一旦某个class提供有destructor,当其object结束生命时,便会自动调用destructor处理善后。destructor主要用来释放在constructor中或对象生命周期中配置的资源。

语法规定:Destructors函数名称必须在class名称再加上‘~’前导符号。它绝对不会有返回值,也没有任何参数。正由于起参数表为空,所以也决不可能被重载(overloaded)。

考虑以下的Matrix class。其constructor使用new表达式从heap中配置double数组所需的空间;其destructor则负责释放这些内存:

1 class Matrix 2 { 3 public: 4     Matrix(int row, int col):_row(row),_col(col) 5     { 6         //constructor进行资源配置 7         _pmat=new double[row*col]; 8     } 9     ~Matrix()10     {11         //destructor进行资源释放12         delete [] _pmat;13     }14 private:15     int _row,_col;16     double* _pmat;17 }
View Code
Memberwise initialization(成员逐一初始化)

默认情况下,当我们以某个class object作为另一个object的初值,class data members会被依次复制。但对Matrix class而言,default memberwise initialization并不恰当。看以下代码:

1     //此处,constructor发生作用2     Matrix mat(4,4);  3 4     //此处,进行default memberwise initialization5     Matrix mat2=mat;6     //...这里使用mat27     //此处,mat2的destructor发生作用8 9     //此处,mat的destructor发生作用

上述代码地5行,default membrewise initialziation会将mat2的_pmat设为_pmat值:

mat2._pmat=mat._pmat;

这会使得两个对象的_pmat都寻址到heap内的统一个数组。当Matrix destructor施行mat2身上是,该数组空间便被释放。不幸的是mat的_pmat仍然指向那个数组,而你知道,对空间以被释放的数组进行操作,是非常严重的错误行为。

如何避免这样的情况呢?本例中必须改变这种“成员逐一初始化”的行为模式。可以通过“为Matrix提供另一个copy constructor”达到目的。见如下代码:

1 Matrix::Matrix(const Matrix &rhs):_row(rhs._row),_col(rhs._col)2 {3     //对rhx._pmat所寻址之叔祖产生一份完全副本4     int elem_cnt=_row*_col;5     _pmat=new double[elem_cnt];6 7     for(int ix=0;ix
View Code

4.3 何谓mutable(可变)和const(不变)


  

看下面这个函数sum,它调用对象Triangular的member function:

1 int sum(const Triangular &trian) 2 { 3     int beg_pos=trian.beg_pos(); 4     int length=trian.length(); 5     int sum=0; 6  7     for(int ix=0;ix
View Code

trian是个const reference参数,因此,编译器必须保证trian在sum()之中不会被修改。但是,sum()所调用的每一个member funciton都有可能更动trian的值。为了确保trian的值不被更动,编译器必须保证beg_pos(),length(),elem()都不会更动其调用者。编译器如何得知这项信息呢?

class的设计这必须在member function身上标注const,以此告诉编译器:这个membr function不会更动class object的内容:

1 class Triangular 2 { 3 public: 4     //一组重载constructors 5     Triangular(); //default constructors 6     Triangular(int lin); 7     Triangular(int len, int beg_pos); 8  9     //以下是const member fuctions10     int length()  const { return _length;  }11     int beg_pos() const { return _beg_pos; }12     int elem( int pos ) const;13 14     //以下是non-const member fuctions15     bool next( int &val ); 16     void next_reset() { _next = _beg_pos - 1; }17 private:18     int _length;  //元素数目19     int _beg_pos; //起始位置20     int _next;    //下一个迭代目标21 22     //static data members将于4.5节说明23     static vector
_elems;24 };
View Code

const修饰词紧接于函数参数表之后。凡在class主题外定义者,如果它是一个const member function,那必须同时在声明式与定时式都指定const。如下代码:

1 int Triangular::elem(int pos) const2 {    return _elems[pos-1];}
View Code

 编译器会检查每个声明为const的member function,看看他们是否真的没有更动class object内容。如下代码:

1 bool Triangular::next(int &value) const 2 { 3     if(_next<_beg_pos+_length-1) 4     { 5         //错误,更动了_next的值 6         value=_elems[_next++]; 7         return true; 8     } 9     return false;10 }
View Code

注意:编译器能够区分const版本和non-const版本的函数。如下代码:

1 class val_class 2 { 3 public: 4     val_class(const BigClass &v):_val(v){} 5     const BigClass& val() const{
return _val;} 6 BigClass& val() {
return _val;} 7 private: 8 BigClass _val; 9 };10 class BigClass{};
View Code

在调用时,const class object会调用const版的val()(那就不可能改变对象的内容了)。non-const class object会调用non-const版的val()。如下代码:

1 void exmpale(const val_class *pbc, val_class &rbc)2 {3     pbc->val(); //这会调用const版本4     rbc.val();  //这会调用non-const版本5 }
View Code

Mutable Data Member(可变的数据成员)

以下是函数sum()的另一种做法,用next()和next_reset()两个member function对trian元素进行迭代:

1 int sum(const Triangular &trian) 2 { 3     if(!trian.length()) 4         return 0; 5     int val,sum=0; 6     //Compile error 7     trian.next_reset(); 8     //Compile error 9     while(trian.next(val))10         sum+=val;11     return sum;12 }
View Code

上述代码会编译出错。因为trian是个const object,而next()和next_reset()都会更懂_next的值,它们都不是const member function。但他们被train调用,于是造成错误。

于是我们想是否把next()和next_reset()改为const。但它们确实是改变了_next的值呀!

这里,我们要重新认识一下,在class的data member中,哪些应限定为常数性(constness),哪些不是:

  • _length和_beg_pos提供了数列的抽象属性。如果我们改变了它们,形同改变了其性质,和未改变的状态不再相同。所以它们应限定为常数性;
  • _next只是用来让我们得以实现除iterator机制,它本身不属于数列抽象概念的一环。改变_next的值,从意义上来说,不能视为改变class object的状态,或者说不算破坏了对象的常数性。 

只要将_next标识为mutable,我们就可以宣传:对_next所作的改变并不会破坏class object的常数性: 

1 class Triangular 2 { 3 public: 4     //添加const 5     bool next( int &val ) const; 6     void next_reset() const { _next = _beg_pos - 1; } 7     //... 8 private: 9     //添加mutable10     mutable int _next;    //下一个迭代目标11     int _length;  12     int _beg_pos;13 };
View Code

现在,next()和next_reset()既可以修改_next的值,又可以被声明为const member functions。

4.4 什么是this指针


  

我们得设计一个copy()成员对象,才能够以Triangular class object作为另一个Triangular class object的初值。假设有以下两个对象,将其中一个拷贝给另一个:

1     Triangular tr1(8);2     Triangular tr1(8,9);3 4     ////将tr2拷贝给tr15     tr1.copy(tr2);
View Code

函数copy()的实现:

1 Triangular& Triangular::copy(const Triangular &rhs)2 {3     _length=ths._length;4     _beg_pos=rhs._beg_pos;5     _next=rhs._beg_pos-1;6 7     retrun *this;  //什么是this指针8 };
View Code

其中rhs(right hand side的缩写)被绑定至tr2。而赋值操作中,_length寻址至tr1内的相应成员。这里出现一个问题:如何寻址至tr1对象本身?this指针就是扮演这样的角色。

本例中,this指向tr1。这是如何作到的?内部的过程是,编译器自动将指针夹道每一个member functions的参数表中,于是copy()被转换为以下形式:

1 //伪码(pseudo code):member function被转换后的结果2 Triangular& Triangular::copy(Triangular *this, const Triangular &rhs)3 {4     this->_length=ths._length;5     this->_beg_pos=rhs._beg_pos;6     this->_next=rhs._beg_pos-1;7 8     retrun *this;  //什么是this指针9 };
View Code

 4.5 Static Class Member(静态的类成员) 


  

注意:member functions只有在“不存取任何non-static members”的条件下才能够被声明为static,声明方式是在声明式之前加上关键词static:

1 class Triangular2 {3 public:4     static bool is_elem(int);5     //...6 private:    7     static vector
_elems;8 //...9 };
View Code

当我们在class主体外部进行member fuctions的定义时,不许要重复加上关键词static。

4.6 打造一个Iteator Class


  

定义运算符:运算符函数看起来很像普通函数,唯一的差别是它不需指定名称,只需在运算符符号之前加上关键词operator即可。如class Triangular_iterator:

1 class Triangular_iterator 2 { 3 public: 4     //为了不要在每次存取元素时都执行-1操作,此处将_index的值设为index-1 5     Triangular_iterator( int index ) : _index( index-1 ){}  6  7     bool operator==( const Triangular_iterator& ) const; 8     bool operator!=( const Triangular_iterator& ) const; 9     int  operator*() const;10     Triangular_iterator& operator++();       //前置(prefix)版11     Triangular_iterator  operator++( int );  //后置(prefix)版12 13 private:14     void check_integrity() const;  15     int _index;16 };
View Code

Triangular_iterator维护一个索引值,用以索引Triangular中用来存储数列元素的那个static data member,也就是_elems。为了达到这个目的,Triangular必须赋予Triangular_iterator member functions特殊的存取权限。我们会在4.7节看到如何通过friend机制给予这种特殊权限。

如果两个Triangular_iterator对象的_idex相等,我们边说这两个对象相等:

1 inline bool Triangular_iterator::2 operator==( const Triangular_iterator &rhs ) const3           { return _index == rhs._index; }
View Code

运算符的定义方式:

  • 可以像member functions一样:
1 inline int Triangular_iterator::2 operator*() const3 {4     check_integrity();5     return Triangular::_elems[ _index ]; 6 }
View Code
  • 也可以像non_member functions一样:
1 inline int operator*(const Triangular_iterator &rhs) 2 {3     rhs.check_integrity();4     //注意:如果这是个non-member function,就不具有取用non-public members的权利5     return Triangular::_elems[ _index ]; 6 }
View Code

non-member运算符的参数列中,一定会比相应的member运算符多一个参数,也就是this指针。

嵌套型别(Nested Types)

typedef可以为某个型别设定另一个不同的名称。其通用形式为:

typedef existing_type new_name

其中existing_type可以是人翮一个内建型别、复合型别,或class型别。

下面一个例子,另iterator等同于Triangular_iterator ,以简化其使用形式:

Triangular::iterator it = trian.begin();

上述代码的Triangular::告诉编译器,在Triangular内部检视iterator。

可以将iterator嵌套置于每个“提供iterator抽象观念”的class内。

4.7 合作关系必须建立在友谊的基础上 


  

以下代码的non-member operator*()会直接取用Triangular的private member:why? 

1 inline int operator*(const Triangular_iterator &rhs) 2 {3     rhs.check_integrity();   //直接取用private member4     return Triangular::_elems[ _index ];  //直接取用private member5 }
View Code

因为任何class都可以将其它的functions或classes指定为友元(friend)。所谓friend,具备类于class member function相同的存取权限。为了让operator*()通过编译,不论Triangular或Triangular_iterator都必须将operator*()声明为“友元”:

1 class Triangular 2 { 3     friend int operator*(const Triangular_iterator &rhs); 4     //... 5 } 6 class Triangular_iterator 7 { 8     friend int operator*(const Triangular_iterator &rhs); 9     //...10 }
View Code

只要将某个函数的原型(prototype)之前加上关键词friend,就可以将它声明为某个class的friend。这份声明可以出现在class定义式的任何位置上,不受private和public的影响。

如果你希望将某个重载函数 声明为某个class的friend,你必须明白的为这个函数加上关键词friend。比如:Triangular_iterator内的operator*()需要直接取用Triangular的private members,就需要将它声明为Triangular的friend:

1 class Triangular2 {3     friend int Triangular_iterator::operator*();4     //...
View Code

这样,编译器就知道它是Triangular_iterator的member function。

也可以令class A与class B建立friend关系,让class A的所有member functions都成为class B的friend。如下代码:

注意:友谊关系的建立,通常是为了效率的考虑。如果我们仅仅只是希望进行某个data member的读写,那么,为它提供具有public存取前线的inline函数即可。

1 class Triangular2 {3     friend class Trianguar_iterator;4     //...
View Code

 4.8 实现一个copy assignment operator


  

默认情况下,当我们将某个class object赋值给另一个,像这样:

Triangular tril(8), tri2(8,9);tri1 = tri2;

class data members会被依次赋值过去。辄被称为default memberwise copy。但对于节的Matrix class,这种default memberwise copy行为便不正确。

Matrix需要一个copy constructor和一个copy assignment operator。以下便是我们为Matrix的copy assignment operator所做的定义:

1 Matrix& Matrix:: 2     operator=(const Matrix &rhs) 3 { 4     if(this!=&rhs) 5     { 6         _row=rhs._row; _col=rhs._col; 7         int elem_cnt=_row*_col; 8         delete [] _pmat; 9         _pmat=new double[elem_cnt];10             11         for(int ix=0;ix
View Code

4.9 实现一个fuction object


  

我们已经在节看到了标准程序库定义的function objects。本节教你如何实现自己的function object。

所谓functon object乃是一种“提供有function call运算符”的class。

当编译器在编译过程中遇到函数调用,例如:

lt(ival);

时,lt可能是函数名称,可能是函数指针,也可能是提供了function call运算符的function object。如果是function object,编译器会在内部将此语句转换为:

lt.operator(ival);

现在我们实现一个function call运算符,测试传入值是否小于某个指定指。我们将此class命名为LessThan:

1 class LessThan 2 { 3 public: 4     LessThan(int val):_val(val){} 5     int comp_val()const {
return _val;} //基值的读取 6 void comp_val(int nval) {_val=nval;} //基值的写入 7 8 bool operator()(int _value)const; 9 private:10 int _val;11 };
View Code

其中的function call运算符实现如下:

inline bool LessThan::operator()(int value)const{
return value<_val;}

定义和调用function call运算符:

1 int count_less_than(const vector
&vec,int comp)2 {3 LessThan lt(comp);4 int count=0;5 for(int ix=0;ix
View Code

通常我们会把function object当作参数传给泛型算法,例如:

1 void print_less_than(const vector
&vec,int comp, ostream &os=cout) 2 { 3 LessThan lt(comp); 4 vector
::const_iterator iter=vec.begin(); 5 vector
::const_iterator it_end=vec.end(); 6 7 os<<"elements less than"<
<
View Code

4.11 指针:指向Class Member Functions 


  

pointer to member function(指向成员函数之指针)机制的运用 ,这种指针看起来和pointer to non-member function(介绍过)极为相似。两者皆必须制定其返回型别和参数表。不过,pointer to member function还得指定它所指出的究竟是哪一个class。例如:

void (num_sequence::*pm)(int)=0;

便是将pm声明为一个指针,指向num_sequence's member function,后者的返回型别必须是void,且只接受单一参数,参数型别为int。pm的初始值为0,表示它当前并不指向任何member function。

如果觉得上述语法过于复杂,我们可以通过typedef加以简化。如下代码:

typedef void(num_sequence::*PtrType)(int);PtrType pm=0;

pointer to member function和pointer to function的一个不同点是:前者必须通过同类对象加以调用,而该对象便是此member function内的this指针所指之物:

1 num_sequence ns;2 typedef void(num_sequence::*PtrType)(int);3 PtrType pm=&num_sequence::fibonaaci;4 5 //以下写法和ns.fibonaaci(pos)相同6 (ns.*pm)(pos)
View Code

其中的.*符号是个“pointer to member selection运算符”,系针对class object运行。

至于针对pointer to class object运行的“pointer to member selection运算符”,其符号是->*:

1 num_sequence *pns=&ns2 3 //以下写法和pns->Fibonacci(pos)相同4 (pns->*pm)(pos)
View Code

 

 

 

 

 

 

 

 

 

 

 

 

 

 

转载地址:http://gitco.baihongyu.com/

你可能感兴趣的文章
Android开发者指南(7) —— App Install Location
查看>>
Trafficserver Cluster模式
查看>>
亚马逊推出 Blox,用于 EC2 容器服务的开源工具集合
查看>>
Linux:在中国没有真正的新闻
查看>>
iOS推送功能极光推送的介绍与实现
查看>>
单用户模式与grub加密
查看>>
Chromium Graphics: 3D上下文及其虚拟化 - Part I
查看>>
jquery javascript获得网页的高度和宽度
查看>>
2019 -2-15 复习
查看>>
vim锁定屏幕
查看>>
实用的 JavaScript 调试小技巧
查看>>
027移除元素
查看>>
Linux下清理内存和Cache方法
查看>>
CodeVS 1018 单词接龙(DFS)
查看>>
我的博客园的CSS和html设置
查看>>
android launchmode(四种启动模式)应用场景及实例
查看>>
工作中简单的kettle使用
查看>>
spark shuffle:分区原理及相关的疑问
查看>>
C#匿名委托
查看>>
Laravel5.5 使用第三方Vendor添加注册验证码
查看>>