这一章的目的是设计一个class
。一般class
由两部分组成:(1)一组公开的(private)操作函数和运算符;(2)一组私有的(private)实现细节。这些操作函数和运算符称为类的成员函数(member function)。
1、如何实现一个Class
以stack
为例,定义类先从所谓的抽象(abstraction)开始。本例栈中存放string
类型。
Class
的声明,然后也可以用类指针(class pointer)来定义:
class Stack;// 类的声明
Stack *pt = 0; // 定义类指针
void process(const Stack&); // 以Stack作为数据类型
接下来,需要定义Stack
类的骨架本身:
class Stack{
public:
// ...public的接口函数
private:
// ...private的接口函数
};
上面可以看到,Class
的定义是由两部分组成的:(1)类的声明;(2)声明之后的主体。主体里面一般有两部分,其中public member
可以在程序的任何地方访问,private member
只能在类的内部或者是class friend
中访问。下面是类的起始定义:
class Stack{
public:
bool push(const string& );
bool pop(string &elem );
bool peek(string &elem);
bool empty();
bool full();
//size()定义在class本身中,其余成员函数仅仅是声明。
int size(){
return _stack.size();
}
private:
vector<string> _stack;
};
下面说明如何定义并使用类中的成员函数:
void fill_stack(Stack &stack, istream &is = Cin){
string str;
while(is >> str and !stack.full())
stack.push(str);
cout << "Read in " << stack.size() << "elements" << endl;
}
所有的成员函数必须在类内声明但是否在类内定义可以自由决定。如果在类内定义,这个成员函数被视为内敛(inline)函数。如果子啊类的外部定义,必须在前面加上关键字inline:
inline bool Stack::empty(){
return _stack.empty();
}
inline bool Stack::pop(string &elem){
if(empty())
return false;
elem = _stack.back();
_stack.pop_back();
return true;
}
上面的Stack::empty()
告诉编译器,函数是类内部的,并且两个冒号即所谓的类作用于解析(class scope resolution) 运算符。
下面是class Stack
中其余成员函数的定义。
inline bool Stack::full(){
return _stack.size() == _stack.max_size();
}
inline bool Stack::peek(string &elem){
if(empty())
return false;
elem = _stack.back();
return true;
}
inline bool Stack::push(const string &elem){
if(full())
return false;
_stack.push_back(elem);
return true;
}
2、什么是构造函数和析构函数
一种极为特殊的初始化函数和终止函数,被称为构造函数(constructor)和析构函数(destructor)。
构造函数名称必须与类的名称相同,不返回任何类型,不用任何返回值,可以被重载。例如,Triangular
类的三种构造函数:
class Triangular{
public:
Triangular();// 默认构造函数
Triangular(int len);
Triangular(int len, int beg_pos);
};
类的对象被定义出来后,编译器会自动根据获得的参数挑选被调用的构造函数,最简单的构造函数是所谓的默认构造函数,无需任何参数,意味着两点:(1)不接受任何参数:
Triangular::Triangular(){
_length = 1;
_beg_pos = 1;
_next = 0;
}
(2)为每个参数提供了默认值:
class Triangular{
public:
Triangular(int len = 1, int bp = 1);
};
有参构造函数:
Triangular::Triangular(int len, int bp){
_length = ken > 0 ? len : 1;
_beg_pos = bp > 0 ? bp : 1;
_next = _beg_pos - 1;
}
构造函数的第二种初始化方法就是所谓的成员初始化列表(member initialization list):
Triangular::Triangular(const Triangular &rhs) : _length(rhs._length), _beg_pos(rhs._beg_pos), _next(rhs._next)
{} // 这里是空的
析构函数(destructor) 是用户自定义的类成员函数。当类中的对象生命周期结束时,便会自动调用析构函数来释放在构造函数中或对象生命周期中分配的资源。
析构函数名称规定:class
名称前加~
前缀,没有返回值,没有参数,不被重载。
class Matrix{
public:
Matrix(int row, int col) : _row(row), _col(col){
_pmat = new double[row * col];
}
~Matrix(){
delete [] _pmat;
}
private:
int _row, _col;
double *_pmat;
};
//Matrix类的初始化
{
Matrix mat(4, 5);
// 调用构造函数
// 调用析构函数
}
C++编程最困难部分之一,便是了解何时需要定义析构函数而何时不需要。
下列情景是默认的成员逐一初始化:
Triangular tril1(8);
Triangular tril2 = tril1;
则本例中的成员数据便依次的赋值到另一个对象中,即所谓的默认成员逐一初始化。
但是,这并不适应于所有情况,看下面代码:
Matrix mat(4, 4); // 构造函数发挥作用
Matrix mat2 = mat; // 此处进行默认的成员逐一初始化
// 使用完mat2后,mat2的析构函数发挥作用
// 这里使用mat。。。。
// 然后mat的析构函数再这里发挥作用。
上述过程中默认逐一初始化后会将mat2的_pmat设为mat的_pmat值:
这样会使得两个对象的指针都指向同一个堆区。当执行mat2的析构函数时,该空间便会被释放,而mat的指针还指向这块数组,将会出现严重的错误。
为Matrix类提供一个拷贝构造函数(copy constructor),可以改变默认的成员逐一初始化模式。
拷贝构造函数的唯一参数是一个const reference
,指向(代表)一个Matrix对象:
Matrix::Matrix(const Matrix &rhs) : _row(rhs.row), _col(rhs.col)
{
int elem_cnt = _row * _col;
_pmat = new double[elem_cnt];
for(int i = 0; i < elem_cnt; i++)
_pmat[i] = rhs._pmat[i];
}
3、何谓mutable(可变)和const(不变)
看下面这个例子,求Triangular
中的元素和:
int sum(const Triangular &trian){
int beg_pos = trian.beg_pos();
int length = trian.length();
int sum = 0;
for(int i = 0; i < length; i++)
sum += trian.elem(beg_pos + i);
return sum;
}
显然sum()
很容易修改trian
中的元素。只有在class
中的成员函数上标注const
,才能保证成员函数不会更改类中对象的内容:
class Triangular {
public:
int length() const{
return _length;
}
int beg_pos() const{
return _beg_pos;
}
int elem(int pos) const;
// 下述是非const成员函数
bool next(int &val);
void next_reset(){
_next = _beg_pos - 1;
}
private:
int _length;
int _beg_pos;
int _next;
// 静态成员
static vector<int> _elems;
};
在类之外声明的类的成员函数如下:
int Triangular::elem(int pos) const{
return _elems[pos - 1];
}
下面是sum()
函数的另一种写法:
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类型的对象,里面的函数对改变_next
的值。可以将_next
权限改为mutable
,则对_next
的改变不会改变类对象的常量性。
class Triangular {
public:
bool next(int &val) const;
void next_reset() const {
_next = _beg_pos - 1;
}
private:
mutable int _next;
int _beg_pos;
int _length;
};
4、什么是this指针
只有设计copy()
成员函数,才能将一个类对象最为另一个类对象的初值:
Triangular tr1(8);
Triangular tr2(8, 9);
tr1.copy(tr2);//会将tr2的长度和初值赋值给tr1。
我们需要一个指向整个对象的方法,就是所谓的this
。this
指针指向tr1
,编译器自动将this
指针加到每个成员函数的参数列表中。copy()
成员函数的实现:
Triangular& Triangular::copy(const Triangular &rhs){
if(this != &rhs){// 判断两个对象是否相同
_length = rhs._length;
_beg_pos = rhs._beg_pos;
_next = rhs._beg_pos - 1;
}
return *this;
}
5、静态类成员
static
(静态)数据成员用来表示唯一的、可共享的成员。它可以在同一类中的的所有对象中被访问。对类而言,静态数据成员只有唯一一份实体,就像全局对象的定义。如果放在类的外面定义,必须加上类的作用域解析运算符。
vector<int> Triangular::elems;
int Triangular::_initial_size = 8;
静态成员函数:
下面的这个函数判断某值是否在Triangular数列内,返回真假:
bool Triangular::is_elem(int value){
if(!_elem.size() or _elem[_elem.size() - 1] < value)
get_elems_to_value(value);
vector<int>::iterator found_it;
vector<int>::iterator end_it = _elem.end();
found_it = find(_elems.begin(), _elems.end(), value);
return found_it != _elems.end();
}
一般,成员函数必须通过其类的某个对象来调用。这个对象会被绑定至该成员函数的this
指针。用存储于每个对象中的this
指针,成员函数才能访问存储于每个对象中的非静态数据成员。
显然上面这个函数并没有访问任何非静态数据成员。它的工作和任何对象都没有关系,所以静态成员函数便可以在“与任何对象都无瓜葛”的情形下被调用。
6、打造一个Iterator Class
体验如何实现Iterator Class
:
Triangular train(1, 8);
Triangular::iterator it = train.begin(), end_it = train.end();
while(it != end_it){
cout << *it << ' ';
it++;
}
为了让上述代码能够工作,必须为Iterator Class
定义一些!=,*,++
等运算符。运算符函数不用指定函数名,只需在运算符前加上关键字operator
即可:
class Triangular_iterator{
public:
Triangular_iterator(int index) : _index(index - 1)
{}
bool operator==(const Triangular_iterator&) const;
bool operator!=(const Triangular_iterator&) const;
int operator*() const;
Triangular_iterator& operator++(); // 前置版
Triangular_iterator operator++(int); // 后置版
private:
void check_integrity() const;
int _index;
};
如果两个Triangular_iterator
的_index
相等,便说这两个对象相等:
inline bool Triangular_iterator::operator==(const Triangular_iterator &rhs) const{
return _index == rhs._index;
}
定义好之后,便可直接作用于类的对象:
if(trian1 == trian2).........
运算符重载的规则:
-
.,.*,::,?:
这四类无法重载,其余均可以。 - 运算符的操作数的个数无法改变。二元运算符必须两个操作数,一元运算符需要一个操作数。
- 运算符的优先级无法改变。
- 运算符函数的参数列表中,必须至少有一个参数为
class
类型。
下面提供increment(递增)运算符的前置(++trian)和后置(trian++)两个版本。前置版的参数列表是空的:
inline Triangular_iterator& Triangular_iterator::operator++(){
++_index;
check_integrity();
return *this;
}
后置版:
inline Triangular_iterator& Triangular_iterator::operator++(int){
Triangular_iterator tmp = *this;
++_index;
check_integrity();
return tmp;
}
下面为Triangular提供一组begin(),end()
成员函数,先对Triangular进行修正:
#include "Triangular_iterator.h"
class Triangular{
public:
typedef Triangular_iterator iterator;
Triangular_iterator begin() const{
return Triangular_iterator(_beg_pos);
}
Triangular_iterator end() const{
return Triangular_iterator(_beg_pos + _length);
}
private:
int _beg_pos;
int _length;
};
嵌套类型:
使用typedef
可以为某个类型设定不同的名称。令iterator
等同于Triangular_iterator
,以便简化:
Triangular::iterator it = trian.begin();
必须使用类作用于运算符来指引编译器。将iterator
嵌套放在每个类内,就可以提供有着相同名称的多个定义。
下篇继续更新 基于对象的编程风格,to be continued…