第四章 基于对象的编程风格
前言
class 的相关事务
1、要包含头文件让程序知道它
2、class名称被视为一个类型名称,初始化可以有很多种
3、class一般由两个部分组成:一组公开的(public)操作函数和运算符,以及一组私有的实现细节。这些操作函数和运算符成为class的member function,并代表这个class的公开接口。
4、身为class的的用户,只能访问其公开接口。
5、class 的private实现细节可由member function的定义以及与此class 相关的任何数据组成
4.1 如何实现一个class
//empty() 询问是否为空
class 的声明以关键字class开始,其后接一个class名称(可任意命名): class Stack; //前置声明
前置声明后才使我们得以进行类指针的定义,或以此 class 作为数据类型。、
定义class本身:
class Stack{
public:
// ...public 接口
private:
// ...private接口
};
Class定义由两个部分组成:class 的声明,以及紧接在声明之后的主体。主体部分由一对大括号括住,并以分号结尾。主体内的两个关键字public 和 private, 标示每个块的访问权限。
关于class定义
1、在class主体内定义,会自动视为inline函数,
2、在class主体之外定义member function,必须使用特殊的语法,目的是分辨该函数究竟属于哪一个class。
也要放在头文件里。
4.2 构造函数和析构函数
//构造函数
class Triangular{
public:
// 构造函数
Triangular();
Triangular(int len);
Triangular(int len,int beg_pos);
private:
//成员
int _length;
int _beg_pos;
int _next;
}
由于编辑器不会自动为我们初始化,需要自己提供一个或者多个特别的初始化函数,编辑器就会在每次class object被定义出来时,调用适当的函数加以处理。 这就是构造函数。
构造函数的函数名称与class名称相同。//不应指定返回值类型,亦不用返回任何值。
可以被重载。
注:在调用无需任何参数的构造函数时,Trianular t5(); 会出错,后面加小括号会使t5被视为函数。 Trianular t5;正确
默认构造函数
1、不接受任何参数;
2、或者为每个参数提供默认值
成员初始化列表
Triangular::Triangular(const Triangular &rhs)
:_length(rhs._length),_beg_pos(rhs._beg_pos),_next(rhs._beg_pos-1) { }
紧跟在参数列表最后的冒号后面,是以逗号分隔的列表。其中,欲赋值给member的数值被放在member名称后面的小括号中。
//析构函数 destructor
主要用来释放在constructor中或对象生命周期中分配的资源。
class 名称再加上’~’前缀,它绝对不会有返回值,也没有任何参数。由于其参数列表是空的,所以也不会被重载
//成员逐一初始化
Triangular tri1(8);
Triangular tri2 = tri1;
会将tri1里面的参数依次复制到tri2.
{
Matrix mat(4,4);
//构造函数发生作用
{
Matrix mat2 = mat;
//进行默认成员初始化
//使用mat2
//mat2的析构函数发生作用
}
//使用mat
//mat的析构函数发生作用
}
这样就导致mat2 和 mat 都指向heap内的同一个数组,mat2使用完析构,mat仍 那个数组
怎么解决?
可以通过"为Matrix提供另一个copy constructor"达到目的。
Matrix::Matrix(const Matrix &rhs)
:_row(rhs._row),_col(rhs.col)
{ //对rhs.pmat所指的数组产生一份完全副本
int elem_cnt = row * _col;
_pmat = new double[elem_cnt];
for (int ix = 0;ix < elem_cnt; ++ix)
_pmat[ix] = rhs._pmat[ix];
}
4.3 mutable(可变) 和 const (不变)
int sum(const Triangular &trian)
{
int beg_pos = trian.beg_pos();
int length = trian.length();
int sum = 0;
for (int ix = 0;ix < length;++ix)
sum += trian.elem(beg_pos + ix);
return sum;
}
可以看出trian是个const reference参数,要保证trian在sum()之中不被修改。所以要确保beg_pos(),length(),elem()不会改变其调用者。
class Triangular{
public:
// const member function
int length() const {return _length;}
int beg_pos() const {return _beg_pos;}
int elem(int pos) const;
//non-const member function
bool next(int &val);
void next_reset(){_next = _beg_pos - 1;}
private:
//成员
int _length;
int _beg_pos;
int _next;
}
const 修饰符紧接于函数参数列表后。凡是在class主体之外定义的,如果是一个const member function,那就必须同时在声明与定义中指定const。
int Triangular::elem(int pos) const
{return _elem[pos-1];}
//可变的数据成员
class Triangular{
public:
// const member function
int length() const {return _length;}
int beg_pos() const {return _beg_pos;}
int elem(int pos) const;
//non-const member function
bool next(int &val);
void next_reset() {_next = _beg_pos - 1;}
private:
//成员
int _length;
int _beg_pos;
int _next;
}
int sum(const Triangular &trian)
{
if(!trian.length() )
return 0;
int val,sum = 0;
trian.next_reset();
while(trian.next(val))
sum += val;
return sum;
}
会运行错误! trian是个const object,而next()、next_reset()都会改变_next的值,他们都不是const object。但它们都被trian调用,就会造成错误。
想要实现sum(),next()、next_reset()就得是const object。
于是:
将_next声明为mutable,next()、next_reset()声明为const object就可以了
class Triangular{
public:
// const member function
int length() const {return _length;}
int beg_pos() const {return _beg_pos;}
int elem(int pos) const;
//non-const member function
bool next(int &val) const;
void next_reset() const {_next = _beg_pos - 1;}
private:
//成员
int _length;
int _beg_pos;
mutable int _next;
}
int sum(const Triangular &trian)
{
if(!trian.length() )
return 0;
int val,sum = 0;
trian.next_reset();
while(trian.next(val))
sum += val;
return sum;
}
4.4 什么是this指针
this 指针是在member function内用来指向其调用者(一个对象)。
在member function内,this指针可以让我们访问其调用者的一切,如果想在copy()中返回tr1,只要简单提领this指针就可以。
return *this;
欲以一个对象复制另一个对象,先确定两个对象是否相同:
Triangular & Triangular ::
copy(const Triangular &rhs)
{
if(this != &rhs)
{
_length = rhs._length;
_beg_pos = rhs._beg_pos;
_next = _rhs._beg_pos - 1;
}
return *this;
}
4.5 静态成员
static data member用来表示唯一的、可共享的member,,它可以在同一类的所有对象中被访问。
class Triangular{
public
//...
private:
static vector<int> _elems;
}
对于class而言,static data member只有唯一的一份实体。在程序代码定义:
vector<int> Triangular::_elems;
也可以为它指定初值:
int Triangular::_initial_size = 8;
4.6 Iterator Class 迭代器
怎么定义运算符?
和普通函数一样,唯一的差别是它不用指定名称,只需在运算符前加上关键字operator即可。
运算符重载的规则:
1、不可以引入新的运算符。
2、运算符的操作数个数不可改变。
3、运算符的优先级不可改变。
4、运算符的参数 中,必须至少有一个参数为class类型。
运算符的定义方式:
inline int Triangular_iterator::
operator*() const
{
check_interity();
return Triangular::_elem[_index];
}
//嵌套类型
typedef 可以为某个类型设定另一个不同的名称。同样形式为: typedef existing_type new_name; 创建易于记忆的类型名,用它来归档程序员的意图。
其中existing_type可以是任何一个内置类型、复合类型或class类型。
4.7 合作关系必须建立在友谊的基础上
要想访问class的private member,必须将函数声明为“friend”
只要在某个函数的原型前加上关键字friend,就可以将它声明为某个class的friend。这份声明可以出现在class定义的任意位置,不受private或public的影响。
怎么令class A与class B建立friend关系,借此让class A的所有member function都成为class B的
friend
class A{
friend class B;
}
4.8 实现一个copy assignment operator 复制赋值运算符
对于Matrix,同样需要一个copy constructor 和一个copy assignment operator。
Matrix::Matrix(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 = 0;ix < elem_cnt; ++ix)
_pmat[ix] = rhs._pmat[ix];
}
return *this;
}
4.9 实现一个function object
所谓function object,乃是一种“提供有function call 运算符”的class。
4.10 重载iostream 运算符
想实现 cout << trian << endl;
需要重载output 运算符
ostream& operator << (ostream &os,const Triangular &rhs)
{
os << "(" << rhs.beg_pos() << "," << rhs.length() << ",";
rhs.display(rhs.lenght(),rhs.beg_pos(),os);
return os;
}
传入函数的ostream对象又被原封不动的返回。
4.11 指针,指向Class Memeber Function
实现一个通用的数列类 num_sequence,使其对象可同时支持前面的六种数列。
typedef void (num_sequence::*PtrType)(int); 将PtrType声明为一个指针,指向num_sequence的member function,后者返回类型是void,只接受一个参数。
PtrType pm = 0;
将PtrType声明为一个指针,指向num_sequence的member function,后者的返回类型是void,只接受一个int参数。
class num_sequence{
public:
typedef void (num_sequence::*PtrType)(int);
//_pmf可指向下列任何一个函数
void fi(int);
void pell(int);
void lucas(int);
void tri(int);
void sequ(int);
void pent(int);
private:
PtrType _pmf;
}
为了取得某个member function的地址,我们对函数名称应用 取址 运算符。
PtrType pm = &num_sequence::fi;