四、基于对象的编程风格

  • 每个class都会提供一组操作函数,让我们作用于其object上。包括具名函数(size()empty())以及重载运算符(===)。
  • 一般而言,class由两部分组成:一组公开的(public)操作函数和运算符以及一组私有的(private)实现细节。
  • 这些操作函数和运算符称为class的成员函数(member function),代表这个class的公开接口,用户只能访问公开接口。
  • class的private实现细节可有member function的定义以及与此class相关的任何数据组成。
  • class的声明以关键字class开始,其后接一个class名称(可任意命名):
class Stack;
  • class 定义由两部分组成:class的声明和紧接其后的主体
  • 主体由一对大括号括住,并以分号结尾。主体内的两个关键字publicprivate,用来标示每个块的member访问权限
  • public member可以在程序的任何地方被访问,private member只能在member function或是class friend内被访问:
class Stack{
public:
	//任何操作函数如果执行成功,就返回true
	//pop和peek(查看)会将字符串内容置于elem内
	bool push( const string& );
	bool pop( string &elem );
	bool peek( string &elem );

	bool empty();
	bool full();

	//size()定义于class本身中,
	//其他member这里只是声明
	int size() {return _stack.size(); }

private:
	vector<string> _stack;
	//习惯上在data member之前加下划线
};
  • 所有member function都必须在class主体内进行声明。如果要在class主体内定义,这个member function会自动被视为inline函数(比如上述的size())。
  • 在主体之外定义member function,必须使用特殊的语法(类名+类作用域解析运算符class scope resolution)告诉编译器该函数究竟属于哪一个class。
  • 如果希望这个函数为inline,应该在最前面指定关键字inline:
//两个冒号即类作用域解析运算符
inline bool Stack::empty()
{
	return _stack.empty();
}
  • class定义及其inline member function通常会放在class同名的头文件中(.h),non-inline member function应该在程序代码文件中定义,该文件通常和class同名(.cpp)。
  • 在member functionfind()中使用同名泛型算法find()时,必须使用全局作用域global scope运算符(::)加以限定,否则会递归调用到自己。
  • 如果我们提供一个或多个特别的函数对data member进行初始化,编译器会在每次class object被定义出来时调用适当的函数加以处理。这些特别的初始化函数称为constructor构造函数)。
  • constructor的函数名称必须与class名称相同,指定返回类型,亦用返回任何值,可以被重载
class Triangular {
	public:
		//一组重载的constructor
		Triangular(); //default constructors
		Triangular(int len);
		Triangular(int len, int beg_pos);
		
		//... 
}; 

Triangular t1;会对t1应用default constructor(无需任何参数的constructor);
Triangular t2(10, 3);会调用带有两个参数的构造函数,括号内的值被视为传给constructor的参数;
Triangular t3 = 8;会调用带有单一参数的constructor,而不是赋值运算( ? 我推测是因为会重载运算符);
Triangular t5();无法成功定义Triangular对象,而是将t5定义为一个函数,参数列表为空,返回Triangular对象——因为C++兼容C,对C而言,t5后带小括号会使其被视为函数。Triangular t5; // ok

  • 最简单的constructor是default constructor。它不需要任何参数

    • 一、它不接受任何参数:
    Triangular::Triangular()
    {
    	//default constructor
    	_length = 1;
    	_beg_pos = 1;
    	_next = 0;
    }
    
    • 二、这种情况更常见,它为每个参数提供默认值:
    class Triangular {
    	public:
    		//也是default constructor
    		Triangular(int len = 1, int bp = 1);
    		//... 
    }; 
    
    Triangular::Triangular(int len, int bp)
    {
    	//_length和_beg_pos都必须>=1
    	//不要相信“用户永远是对的”这句话:) 
    	_length = len > 0 ? len : 1;
    	_beg_pos = bp > 0 ? bp : 1;
    	_next = _beg_pos - 1;
    }
    
  • 当我们给两个整数提供了默认值,所以上述default constructor同时支持原本的三个constructor。

  • 成员初始化列表(member initialization list)是构造函数的第二种初始化语法——member initialization list紧接在参数列表最后的冒号后面,是个以逗号分隔的列表,欲赋值给member的数值被放在member名称后面的小括号中:

Triangular::Triangular(const Triangular &rhs)
	: _length(rhs._length), _beg_pos(rhs._beg_pos),
	  _next(rhs.beg_pos - 1)
{ } //是的,空的 
  • member initialization list主要用于将参数传给member class object的constructor。
  • 与之对应的destructor(析构函数)是用户自定义的一个class member。当类对象结束生命时,便会自动调用destructor,主要用来释放在constructor中或对象生命周期中分配的资源
  • 析构函数的名称有严格规定:class名称前加上~前缀。绝对不会有返回值,也没有任何参数,其参数列表是空的,因而也不能被重载:
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;
};
//通过Matrix本身的constructor和destructor,
//完成了heap内存的自动管理
  • destructor并非绝对必要。例如Triangular的data member皆以储值方式存放,在对象被定义后存在,又在对象结束生命时被释放。
  • 默认情形下,当我们以某个类对象作为另一个对象的初值时(如Triangular tril(8);)class data member会被依次复制,即默认的成员逐一初始化操作(default memberwise initialization)。
  • 在上述的Matrix类中,默认的成员逐一初始化并不适当,Matrix mat2 = mat;会将mat2_pmat设为mat的_pmat值。使得两个对象的_pmat指向heap内的同一个数组。若mat2在析构函数的作用下被释放,mat_pmat则指向一个空间已被释放的数组!!
  • 可以通过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]; 
}
  • class设计者必须在member function身上标注const(不变),以此告诉编译器——这个member function不会更改class object的内容(const修饰符紧跟在函数参数列表之后):
class Triangular {
	public:
		//以下是const member function 
		int length() const {return _length;}
		int beg_pos() const {return _beg_pos;}
		int elem(int pos) const;
		//主体外定义时,
		//必须同时在声明和定义中指定const 
		
		//以下是non-const member function
		bool next(int &val);
		void next_reset() { _next = _beg_pos - 1; }
		
		//... 
	private:
		int _length; //元素个数 
		int _beg_pos; //起始位置 
		int _next; //下一个迭代目标 
		
		static vector<int> _elems;
}; 

//主体外,定义也要包含const
int Triangular::elem(int pos) const
{
	return _elems[pos - 1];
}
  • 编译器会检查每个声明为const的member function,看其是否真的没有更改调用它的class object内容。
  • member function可以根据const与否而重载
class val_class {
public:
	//提供两份定义,一个const,一个non-const 
	const BigClass& val() const { return _val; }
	BigClass& val() { return _val; }
	//...
}; 

void example(const BigClass *pbc, BigClass &rbc)
{
	pbc->val(); //这会调用const版本,不改变对象内容
	rbc.val(); //这会调用non-const版本 
}
  • 设计class时,鉴定其const member function是一件很重要的事情。因为const reference class参数是不能调用公开接口中的non-const成分的(但目前很多编译器对此情况都只给警告)。
  • 关键字mutable(可变)可以宣称对变量所做的改变不会破坏类对象的常量性(constness)。
  • 如下所示,next()next_reset()既可以修改_next的值,又可以被声明为const member function。解决了const对象无法调用non-const成员函数next()next_reset()的问题(当_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;
}; 
  • this指针在member function内指向其调用者。内部工作过程是编译器自动将this指针加到每个member function的参数列表中,如:
Triangular& Triangular:: 
copy( Triangular *this, const Triangular &rhs )
{
	this->_length = rhs._length;
	this->_beg_pos = rhs._beg_pos;
	this->_next = rhs._beg_pos - 1;
	return *this; //返回this指针指向的对象(即tr1
} 

tr1.copy( tr2 );
//内部程序转换,tr1变为由this指针所指的对象
copy( &tr1, tr2 ); 
  • 欲以一个对象复制出另一个对象,先确定两个对象是否相同是一个好习惯(if(this != &rhs)),这必然运用this指针。
  • 类中,static(静态) data member用来表示唯一的、可共享的member,它可以在同一类的所有对象中被访问。对class而言,其只有唯一的一份实体。(这种定义看起来很像全局对象,唯一的差别是在程序代码文件中其名称必须附上class scope运算符)。
  • 如果要在class member function内访问static data member,其方式有如访问一般non-static数据成员。
  • 像buf_size这类const static int data member,可以在声明时为其明确初值。
class intBuffer{
public:
	//...
	
private:
	static const int _buf_size = 1024; 
	int _buffer[_buf_size];
};
  • 一般情形下,member function必须通过其类的某个对象来调用。这个对象会被绑定至该member function的this指针,通过储存于每个对象中的this指针,member function才能访问储存于每个对象中的non-static data member。

  • member function在访问任何non-static data member的条件下被声明为static(即在声明前加上关键字static),可以很方便地以一般non-member function的方式调用,但要加上class scope运算符:

    //不必通过对象调用
    if(Triangular::is_elem(8)) ...
    
  • 当我们在class主体外部进行member function的定义时,无须重复加上关键字static(这个同样适用于static data member)。

  • 我们可以像定义member function那样定义运算符,运算符函数看起来和普通函数一样,唯一差别在于它不用指定名称,只需在运算符前加上关键字operator即可:

class Triangular_iterator {
public:
	//避免每次访问元素时执行-1操作 
	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维护一个索引值,
//用以索引Triangular中用来储存数列元素的那个static data member

//如果两个Triangular_iterator对象的_index相等,
//我们便说这两个对象相等
inline bool Triangular_iterator::
operator==(const Triangular_iterator &rhs) const
{
	return _index == rhs._index;
}
  • 运算符重载的规则如下:

    • 不可以引入新的运算符。(除了..*::?其他运算符皆可被重载;
    • 运算符的操作数个数不可改变。(例如equality运算符只能接受两个操作数);
    • 运算符的优先级不可改变;
    • 运算符函数的参数列表中,必须至少有一个参数为class类型。(无法为non-class类型,重新定义其原已存在的运算符或引进新运算符)。
  • 运算符的定义既可以像member function也可以像non-member function,non-member的参数列表中一定会比相应的member运算符多一个参数,也就是this指针。对member运算符而言,这个this指针隐式代表左操作数:

//member function
inline int Triangular_iterator:: operator*() const
{
	check_integrity();
	return Triangular::_elems[_index];
}

//non-member function
inline int operator*(const Triangular_iterator &rhs)
{
	rhs.check_integrity();
	return Triangular::_elems[_index];
}
  • 递增(increment)运算符的前置版参数列表是空的,因重载规则要求参数列表必须独一无二,C++要求后置版的参数列表中有一个int参数,编译器会自动为后置版产生一个int参数(值为0)。
  • typedef为某个类型设定另一个不同的名称:typedef existing_type new_name;,其中的existing_type 可以是任何内置类型、复合类型或class类型。
  • 任何class都可以将其他function或class指定为friend,使其具备与class member function相同的访问权限,可以访问class的private member。
  • 只要在某个函数的原型前加上关键字friend,就可以将它声明为某个class的friend。这份声明可以出现在class定义的任意位置(不受private或public的影响)。
  • 如果希望将数个重载函数都声明为某个class的friend,必须明确的为每个函数都加上关键字friend。
  • function object是“提供有function call运算符的class”的实例对象。如下对function call运算符进行重载,测试传入值是否小于某指定值:
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;
};

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

  • 通常我们会把function object当作参数传给泛型算法:
LessThan lt10(10);
iter = find_if(vec.begin(), vvec.end(), lt10);
  • 重载output运算符对class object进行读取:
ostream& operator<<(ostream &os, const Triangular &rhs)
{
	os << "(" << rhs.beg_pos() << ", "
	   << rhs.length() << ")";
	
	rhs.display(rhs.length(), rhs.beg_pos(), os);
	return os;	
}

Triangular tri(6,3);
cout << tri << '\n';
//(3, 6) 6 10 15 21 28 36
  • 传入函数的ostream对象又被原封不动的返回,得以串接多个output运算符。
  • ? 如果output运算符被设计为tri的class member function,其左操作数必须隶属于同一个class对象,那么tri对象就必须被放在output运算符的左侧:tri << cout << '\n';
  • 指向成员函数的指针(pointer to member function)机制与第二章的pointer to non-member function相似,皆需指定其返回类型和参数列表。不过,指向成员函数的指针还要指定它所指的究竟是哪一个class:
void (num_sequence::*pm)(int) = 0;
//指向num_sequence的成员函数,
//该成员函数的返回类型必须为void,并只接受单一参数int
//pm初始值为0,表示目前不指向任何成员函数

//用typedef简化语法
typedef void (num_sequence::*PtrType)(int);
PtrType pm = 0;
  • 对函数名称应用取址运算符获得member function的地址。注意:函数名称前必须以class scope运算符加以限定,返回类型和参数列表皆不必指明。如:PtrType pm = &num_sequence::fibonacci;定义一个指向成员函数fibonacci()的指针。
  • static vector<vector<int>> seq;无法编译成功。基于maximal munch编译原则,即要求每个符号序列总是以合法符号序列中最长的那个解释。因为>>是合法运算符序列,所以会被合在一起看待,应写成static vector<vector<int> > seq;
  • pointer to member function必须通过同一类的对象加以调用,而该对象便是此member function内的this指针所指之物:(有点没看懂其实)
//假设有如下定义 
num_sequence ns;
num_sequence *pns = &ns;
PtrType pm = &num_sequence::fibonacci;

//以下写法和 ns.fibonacci(pos)相同
(ns.*pm)(pos)

//以下写法和pns->fibonacci(pos)相同
(pns->*pm)(pos) 
  • .*符号是一个pointer to member selection运算符,针对class object工作,要在外围加上小括号。->*符号是pointer to member selection运算符,针对pointer to class object工作。
  • 借由class封装数据,可以避免外界直接访问;只开放一小组函数作为对外接口;更进一步,还可以把这些对象名称隐藏于类范围内,不和其他全局实体名称发生冲突。
  • _itoa()是C标准库提供的函数,会将整数转换为对应的ASCII字符串形式
char*_itoa(int value,char* string,int radix);
/*
value-----要转换的整形值
string-----转换后的字符串
radix------表示(2, 8, 10, 16)等进制基数
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值