《Essential C++》读书笔记 之 基于对象编程风格
《Essential C++》读书笔记 之 基于对象编程风格
2014-07-13
4.2 什么是Constructors(构造函数)和Destructors(析构函数)
Member Initialization List(成员初值表)
Memberwise initialization(成员逐一初始化)
4.5 Static Class Member(静态的类成员)
4.8 实现一个copy assignment operator
4.11 指针:指向Class Member Functions
4.1 如何实现一个class
class定义由两部分组成:class的声明,以及紧接在声明之后的主体。主体部分由一对大括号括住,并以分号结尾。
主体内的两个关键词public和private。public members可以在程序的任何地方被取用,private members只能在member function或class friend内被取用。
以下是stack class的起始定义:
class Stack {
public:
bool push( const string& );
bool pop( string &elem );
bool peek( string &elem ); bool empty();
bool full(); //size()定义于class本身内,其它members则仅仅只是声明
int size() { return _stack.size(); }
private:
vector<string> _stack;
};
以下是如何定义并使用Stack class object:
void fill_stack( Stack &stack, istream &is = cin )
{
string str;
while ( is >> str && ! stack.full() )
stack.push( str ); cout << "Read in " << stack.size() << " elements\n";
}
所有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不应指定返回型别,亦不需返回值。它可以被重载。看以下代码:
class Triangular
{
public:
//一组重载constructors
Triangular(); //default constructors
Triangular(int lin);
Triangular(int len, int beg_pos);
private:
int _length; //元素数目
int _beg_pos; //起始位置
int _next; //下一个迭代目标
}
如何调用构造函数:
//对t施行default constructor(无参构造函数)
Triangular t;
//调用戴两个参数的constructor
Triangular t2(,);
//是调用constructor or assignment operator呢?答案是constructor,调用戴一个参数的constructor
Triangular t3=; //该语句t5被定义为一个函数,其参数表为空,返回Triangular object。why?
//因为C++必须兼容与C,对C而言,t5之后带有小括号,会被视为函数。
Triangular t5();
对于默认函数,它不需要任何arguments。这意味着以下两种情况:
- 第一,它不接收任何参数:
Triangular::Triangular()
{
int _length=;
int _beg_pos=;
int _next=;
}
- 第二,它为每个参数提供了默认值:
class Triangular
{
public:
//default constructors
Triangular(int len=, int beg_pos=);
Member Initialization List(成员初值表)
Constructor定义式的第二种初始化语法,是Member Initialization list:
Triangular::Triangular(const Triangular &rhs)
: _length(rhs._length), _beg_pos(rhs._beg_pos),_next(rhs._next)
{} //是的,空的
Member Initialization List的主要作用是将参数传递给member class object的constructor。假设重新定义Triangular,令它包含一个string member:
class Triangular
{
public:
//...
private:
string _name;
int _length, _beg_pos, _next;
}
为了将_name的初值传给string constructor,必须以member intialization list完成。代码如下:
Triangular::Triangular(int len, int bp)
: _name("Triangularr")
{
_length=len>?len:;
_beg_pos=bp>?bp:;
_next=_beg_pos-;
}
Destructor
和constructor对立的是destructor。所谓destructor是用户自行定义的一个class member。一旦某个class提供有destructor,当其object结束生命时,便会自动调用destructor处理善后。destructor主要用来释放在constructor中或对象生命周期中配置的资源。
语法规定:Destructors函数名称必须在class名称再加上‘~’前导符号。它绝对不会有返回值,也没有任何参数。正由于起参数表为空,所以也决不可能被重载(overloaded)。
考虑以下的Matrix class。其constructor使用new表达式从heap中配置double数组所需的空间;其destructor则负责释放这些内存:
class Matrix
{
public:
Matrix(int row, int col):_row(row),_col(col)
{
//constructor进行资源配置
_pmat=new double[row*col];
}
~Matrix()
{
//destructor进行资源释放
delete [] _pmat;
}
private:
int _row,_col;
double* _pmat;
}
Memberwise initialization(成员逐一初始化)
默认情况下,当我们以某个class object作为另一个object的初值,class data members会被依次复制。但对Matrix class而言,default memberwise initialization并不恰当。看以下代码:
//此处,constructor发生作用
Matrix mat(,); //此处,进行default memberwise initialization
Matrix mat2=mat;
//...这里使用mat2
//此处,mat2的destructor发生作用 //此处,mat的destructor发生作用
上述代码地5行,default membrewise initialziation会将mat2的_pmat设为_pmat值:
mat2._pmat=mat._pmat;
这会使得两个对象的_pmat都寻址到heap内的统一个数组。当Matrix destructor施行mat2身上是,该数组空间便被释放。不幸的是mat的_pmat仍然指向那个数组,而你知道,对空间以被释放的数组进行操作,是非常严重的错误行为。
如何避免这样的情况呢?本例中必须改变这种“成员逐一初始化”的行为模式。可以通过“为Matrix提供另一个copy constructor”达到目的。见如下代码:
Matrix::Matrix(const Matrix &rhs):_row(rhs._row),_col(rhs._col)
{
//对rhx._pmat所寻址之叔祖产生一份完全副本
int elem_cnt=_row*_col;
_pmat=new double[elem_cnt]; for(int ix=;ix<elem_cnt; ++ix)
_pmat[ix]=rhs._pmat[ix];
}
4.3 何谓mutable(可变)和const(不变)
看下面这个函数sum,它调用对象Triangular的member function:
int sum(const Triangular &trian)
{
int beg_pos=trian.beg_pos();
int length=trian.length();
int sum=; for(int ix=;ix<length;++ix)
sum+=trian.elem(beg_pos+ix);
return sum;
}
trian是个const reference参数,因此,编译器必须保证trian在sum()之中不会被修改。但是,sum()所调用的每一个member funciton都有可能更动trian的值。为了确保trian的值不被更动,编译器必须保证beg_pos(),length(),elem()都不会更动其调用者。编译器如何得知这项信息呢?
class的设计这必须在member function身上标注const,以此告诉编译器:这个membr function不会更动class object的内容:
class Triangular
{
public:
//一组重载constructors
Triangular(); //default constructors
Triangular(int lin);
Triangular(int len, int beg_pos); //以下是const member fuctions
int length() const { return _length; }
int beg_pos() const { return _beg_pos; }
int elem( int pos ) const; //以下是non-const member fuctions
bool next( int &val );
void next_reset() { _next = _beg_pos - ; }
private:
int _length; //元素数目
int _beg_pos; //起始位置
int _next; //下一个迭代目标 //static data members将于4.5节说明
static vector<int> _elems;
};
const修饰词紧接于函数参数表之后。凡在class主题外定义者,如果它是一个const member function,那必须同时在声明式与定时式都指定const。如下代码:
int Triangular::elem(int pos) const
{ return _elems[pos-];}
编译器会检查每个声明为const的member function,看看他们是否真的没有更动class object内容。如下代码:
bool Triangular::next(int &value) const
{
if(_next<_beg_pos+_length-)
{
//错误,更动了_next的值
value=_elems[_next++];
return true;
}
return false;
}
注意:编译器能够区分const版本和non-const版本的函数。如下代码:
class val_class
{
public:
val_class(const BigClass &v):_val(v){}
const BigClass& val() const{return _val;}
BigClass& val() {return _val;}
private:
BigClass _val;
};
class BigClass{};
在调用时,const class object会调用const版的val()(那就不可能改变对象的内容了)。non-const class object会调用non-const版的val()。如下代码:
void exmpale(const val_class *pbc, val_class &rbc)
{
pbc->val(); //这会调用const版本
rbc.val(); //这会调用non-const版本
}
Mutable Data Member(可变的数据成员)
以下是函数sum()的另一种做法,用next()和next_reset()两个member function对trian元素进行迭代:
int sum(const Triangular &trian)
{
if(!trian.length())
return ;
int val,sum=;
//Compile error
trian.next_reset();
//Compile error
while(trian.next(val))
sum+=val;
return sum;
}
上述代码会编译出错。因为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的常数性:
class Triangular
{
public:
//添加const
bool next( int &val ) const;
void next_reset() const { _next = _beg_pos - ; }
//...
private:
//添加mutable
mutable int _next; //下一个迭代目标
int _length;
int _beg_pos;
};
现在,next()和next_reset()既可以修改_next的值,又可以被声明为const member functions。
4.4 什么是this指针
我们得设计一个copy()成员对象,才能够以Triangular class object作为另一个Triangular class object的初值。假设有以下两个对象,将其中一个拷贝给另一个:
Triangular tr1();
Triangular tr1(,); ////将tr2拷贝给tr1
tr1.copy(tr2);
函数copy()的实现:
Triangular& Triangular::copy(const Triangular &rhs)
{
_length=ths._length;
_beg_pos=rhs._beg_pos;
_next=rhs._beg_pos-; retrun *this; //什么是this指针
};
其中rhs(right hand side的缩写)被绑定至tr2。而赋值操作中,_length寻址至tr1内的相应成员。这里出现一个问题:如何寻址至tr1对象本身?this指针就是扮演这样的角色。
本例中,this指向tr1。这是如何作到的?内部的过程是,编译器自动将指针夹道每一个member functions的参数表中,于是copy()被转换为以下形式:
//伪码(pseudo code):member function被转换后的结果
Triangular& Triangular::copy(Triangular *this, const Triangular &rhs)
{
this->_length=ths._length;
this->_beg_pos=rhs._beg_pos;
this->_next=rhs._beg_pos-; retrun *this; //什么是this指针
};
4.5 Static Class Member(静态的类成员)
注意:member functions只有在“不存取任何non-static members”的条件下才能够被声明为static,声明方式是在声明式之前加上关键词static:
class Triangular
{
public:
static bool is_elem(int);
//...
private:
static vector<int> _elems;
//...
};
当我们在class主体外部进行member fuctions的定义时,不许要重复加上关键词static。
4.6 打造一个Iteator Class
定义运算符:运算符函数看起来很像普通函数,唯一的差别是它不需指定名称,只需在运算符符号之前加上关键词operator即可。如class Triangular_iterator:
class Triangular_iterator
{
public:
//为了不要在每次存取元素时都执行-1操作,此处将_index的值设为index-1
Triangular_iterator( int index ) : _index( index- ){} bool operator==( const Triangular_iterator& ) const;
bool operator!=( const Triangular_iterator& ) const;
int operator*() const;
Triangular_iterator& operator++(); //前置(prefix)版
Triangular_iterator operator++( int ); //后置(prefix)版 private:
void check_integrity() const;
int _index;
};
Triangular_iterator维护一个索引值,用以索引Triangular中用来存储数列元素的那个static data member,也就是_elems。为了达到这个目的,Triangular必须赋予Triangular_iterator member functions特殊的存取权限。我们会在4.7节看到如何通过friend机制给予这种特殊权限。
如果两个Triangular_iterator对象的_idex相等,我们边说这两个对象相等:
inline bool Triangular_iterator::
operator==( const Triangular_iterator &rhs ) const
{ return _index == rhs._index; }
运算符的定义方式:
- 可以像member functions一样:
inline int Triangular_iterator::
operator*() const
{
check_integrity();
return Triangular::_elems[ _index ];
}
- 也可以像non_member functions一样:
inline int operator*(const Triangular_iterator &rhs)
{
rhs.check_integrity();
//注意:如果这是个non-member function,就不具有取用non-public members的权利
return Triangular::_elems[ _index ];
}
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?
inline int operator*(const Triangular_iterator &rhs)
{
rhs.check_integrity(); //直接取用private member
return Triangular::_elems[ _index ]; //直接取用private member
}
因为任何class都可以将其它的functions或classes指定为友元(friend)。所谓friend,具备类于class member function相同的存取权限。为了让operator*()通过编译,不论Triangular或Triangular_iterator都必须将operator*()声明为“友元”:
class Triangular
{
friend int operator*(const Triangular_iterator &rhs);
//...
}
class Triangular_iterator
{
friend int operator*(const Triangular_iterator &rhs);
//...
}
只要将某个函数的原型(prototype)之前加上关键词friend,就可以将它声明为某个class的friend。这份声明可以出现在class定义式的任何位置上,不受private和public的影响。
如果你希望将某个重载函数 声明为某个class的friend,你必须明白的为这个函数加上关键词friend。比如:Triangular_iterator内的operator*()需要直接取用Triangular的private members,就需要将它声明为Triangular的friend:
class Triangular
{
friend int Triangular_iterator::operator*();
//...
这样,编译器就知道它是Triangular_iterator的member function。
也可以令class A与class B建立friend关系,让class A的所有member functions都成为class B的friend。如下代码:
注意:友谊关系的建立,通常是为了效率的考虑。如果我们仅仅只是希望进行某个data member的读写,那么,为它提供具有public存取前线的inline函数即可。
class Triangular
{
friend class Trianguar_iterator;
//...
4.8 实现一个copy assignment operator
默认情况下,当我们将某个class object赋值给另一个,像这样:
Triangular tril(), tri2(,);
tri1 = tri2;
class data members会被依次赋值过去。辄被称为default memberwise copy。但对于4.2节的Matrix class,这种default memberwise copy行为便不正确。
Matrix需要一个copy constructor和一个copy assignment operator。以下便是我们为Matrix的copy assignment operator所做的定义:
Matrix& Matrix::
operator=(const Matrix &rhs)
{
if(this!=&rhs)
{
_row=rhs._row; _col=rhs._col;
int elem_cnt=_row*_col;
delete [] _pmat;
_pmat=new double[elem_cnt]; for(int ix=;ix<elem_cnt; ++ix)
_pmat[ix]=rhs._pmat[ix];
}
return *this;
}
4.9 实现一个fuction object
我们已经在3.6节看到了标准程序库定义的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:
class LessThan
{
public:
LessThan(int val):_val(val){}
int comp_val()const {return _val;} //基值的读取
void comp_val(int nval) {_val=nval;} //基值的写入 bool operator()(int _value)const;
private:
int _val;
};
其中的function call运算符实现如下:
inline bool LessThan::operator()(int value)const{return value<_val;}
定义和调用function call运算符:
int count_less_than(const vector<int>&vec,int comp)
{
LessThan lt(comp);
int count=;
for(int ix=;ix<vec.size();++ix)
if(lt(vec[ix]))
++count;
return count;
}
通常我们会把function object当作参数传给泛型算法,例如:
void print_less_than(const vector<int>&vec,int comp, ostream &os=cout)
{
LessThan lt(comp);
vector<int>::const_iterator iter=vec.begin();
vector<int>::const_iterator it_end=vec.end(); os<<"elements less than"<<lt.comp_val()<<endl;
while((iter=find_if(iter,it_end,lt))!=it_end)
{
os<<*iter<<' ';
++iter;
}
}
4.11 指针:指向Class Member Functions
pointer to member function(指向成员函数之指针)机制的运用 ,这种指针看起来和pointer to non-member function(2.8节介绍过)极为相似。两者皆必须制定其返回型别和参数表。不过,pointer to member function还得指定它所指出的究竟是哪一个class。例如:
void (num_sequence::*pm)(int)=;
便是将pm声明为一个指针,指向num_sequence's member function,后者的返回型别必须是void,且只接受单一参数,参数型别为int。pm的初始值为0,表示它当前并不指向任何member function。
如果觉得上述语法过于复杂,我们可以通过typedef加以简化。如下代码:
typedef void(num_sequence::*PtrType)(int);
PtrType pm=;
pointer to member function和pointer to function的一个不同点是:前者必须通过同类对象加以调用,而该对象便是此member function内的this指针所指之物:
num_sequence ns;
typedef void(num_sequence::*PtrType)(int);
PtrType pm=&num_sequence::fibonaaci; //以下写法和ns.fibonaaci(pos)相同
(ns.*pm)(pos)
其中的.*符号是个“pointer to member selection运算符”,系针对class object运行。
至于针对pointer to class object运行的“pointer to member selection运算符”,其符号是->*:
num_sequence *pns=&ns //以下写法和pns->Fibonacci(pos)相同
(pns->*pm)(pos)
《Essential C++》读书笔记 之 基于对象编程风格的更多相关文章
- TJI读书笔记15-持有对象
TJI读书笔记15-持有对象 总览 类型安全和泛型 Collection接口 添加元素 List 迭代器 LinkedList 栈 Set Map Queue Collection和Iterator ...
- Essential C#读书笔记
Essential C#读书笔记 这是一个多变的时代,一次又一次的浪潮将不同的人推上了巅峰.新的人想搭上这一波,同时老的人也不想死在沙滩上.这些年新的浪潮又一次推开,历史不停地重复上演,那便是移动互联 ...
- 《软件测试自动化之道》读书笔记 之 基于反射的UI测试
<软件测试自动化之道>读书笔记 之 基于反射的UI测试 2014-09-24 测试自动化程序的任务待测程序测试程序 启动待测程序 设置窗体的属性 获取窗体的属性 设置控件的属性 ...
- 《软件测试自动化之道》读书笔记 之 基于Windows的UI测试
<软件测试自动化之道>读书笔记 之 基于Windows的UI测试 2014-09-25 测试自动化程序的任务待测程序测试程序 启动待测程序 获得待测程序主窗体的句柄 获得有名字控件的 ...
- 《编写可维护的javascript》读书笔记(中)——编程实践
上篇读书笔记系列之:<编写可维护的javascript>读书笔记(上) 上篇说的是编程风格,记录的都是最重要的点,不讲废话,写的比较简洁,而本篇将加入一些实例,因为那样比较容易说明问题. ...
- JavaScript基于对象编程
js面向对象特征介绍 javascript是一种面向(基于)对象的动态脚本语言,是一种基于对象(Object)和事件驱动(EventDirven)并具有安全性能的脚本语言.它具有面向对象语言所特有的各 ...
- js面向(基于)对象编程—类(原型对象)与对象
JS分三个部分: 1. ECMAScript标准--基础语法 2. DOM Document Object Model 文档对象模型 3. BOM Browser Object Moldel 浏览 ...
- JavaScript学习总结(九)——Javascript面向(基于)对象编程
一.澄清概念 1.JS中"基于对象=面向对象" 2.JS中没有类(Class),但是它取了一个新的名字叫“原型对象”,因此"类=原型对象" 二.类(原型对象)和 ...
- JavaScript学习总结(5)——Javascript面向(基于)对象编程
一.澄清概念 1.JS中"基于对象=面向对象" 2.JS中没有类(Class),但是它取了一个新的名字叫"原型对象",因此"类=原型对象" ...
随机推荐
- 潭州课堂25班:Ph201805201 WEB 之 jQuery 第七课 (课堂笔记)
jq 的导入 <body> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js">< ...
- echarts设置toolTip大小和样式问题
最近研究echarts,发现提示框太大,位置不合适问题, 用jq,css选中div的tooltip设置大小有时候不管用: 查了官网文档 http://echarts.baidu.com/option. ...
- maven的pom.xml配置文件中常用的配置标签解析(2018-03-13)
来自:https://www.cnblogs.com/Nick-Hu/p/7288198.html 拿过来记录下 <project xmlns="http://maven.apache ...
- bzoj 3143 随机游走
题意: 给一个简单无向图,一个人从1号节点开始随机游走(即以相同概率走向与它相邻的点),走到n便停止,问每条边期望走的步数. 首先求出每个点期望走到的次数,每条边自然是从它的两个端点走来. /**** ...
- 向安装包中添加设备 UDID. 蒲公英内测
向安装包中添加设备 UDID 前言 注:本文适用于只有苹果个人开发者账号.公司开发者账号.或教育开发者账号的 iOS 开发者. 对于没有企业开发者账号(299$)的开发者来说,要想使用蒲公英将自己的应 ...
- iOS埋点统计
链接: iOS无埋点数据SDK实践之路 iOS:无埋点数据SDK实践之路,看完能懂就厉害了 iOS埋点业务方案 ios 无码统计埋点 使用AOP实现iOS应用内的埋点计数 Aspects(AOP) A ...
- 虚拟机搭建和安装Hadoop及启动
马士兵hadoop第一课:虚拟机搭建和安装hadoop及启动 马士兵hadoop第二课:hdfs集群集中管理和hadoop文件操作 马士兵hadoop第三课:java开发hdfs 马士兵hadoop第 ...
- 分布式理论——从ACID到CAP再到BASE
在传统的数据中,有ACID四大原则,在分布式中也有对应的CAP理论和BASE理论,这些都是分布式理论的基础. 更多内容参考:大数据学习之路 ACID ACID分别是Atomicity 原子性.Cons ...
- AIX中vi编辑器使用
3.1 vi 简介 vi 是 UNIX 世界里使用非常普遍的全屏幕文本编辑器,几乎任何一种UNIX系统都会提供这套软件.AIX当然也支持这种编辑器.熟悉DOS下的文本处理后,用户在开始接触vi时也许会 ...
- Intellij IDEA 通过数据库表逆向生成带注释的实体类文件超级详细步骤,附详细解决方案
参考:https://blog.csdn.net/qq_34371461/article/details/80571281 https://blog.csdn.net/mqchenrong/arti ...