基于对象的编程风格
4.1类的定义
类的定义由两部分组成:类的声明及紧接在声明之后的主体.主体部分由大括号括住并且以分员结束.
类定义的框架:
class 类名{
public://这的访问权限书写形式与java不同
.............//对外提供的公开接口
private:
...............//内部细节
};//记得加分号结束
一个例子:
class Sales_item{
public:
//对象上的操作
double avg_price() const;
bool same_isbn(const Sales_item &rhs) const
{
return(isbn==rhs.isbn);
}
//构造函数
Sales_item:units_sold(0), revenue(0.0) {}
private:
std::string isbn;
unsigned units_sold;
double revenue;
};
//avg_price()定义
double Sales_item::avg_price() const
{//不但在声明中,在定义中也必须加上const,const加在形参之后,函数体之前
//这个函数被定义为const后,不能修改类的成员变量,同时也不能调用类的非const成员方法,只能调用const成员方法
if(units_sold)
return revenue/units_sold;
else
return 0;
}
注意:
4.1.1 C++中类的成员函数必须全部在类内部声明,但函数定义则不硬性要求一定得在类内部定义,这点上和JAVA不同.
例如上面例子中的avg_price()函数在类内部只有声明没有定义,而same_isbn()函数则在类内部定义了,对于在类内部定义的函数默认为inline函数.
对于在类外面定义函数,必须在函数名前返回值类型后加上 类名:: 以表示所属的类.例如double Sales_item::avg_price() const
在类外面定义的函数不会默认为inline函数,如要使它成为inline函数,必须在最前面加上inline关键字,如:
inline double Sales_item::avg_price() const
{
if(units_sold)
return revenue/units_sold;
else
return 0;
}
4.1.2 在形参列表后加上const表const成员函数,详见:const关键字.
4.2构造函数和析构函数
4.2.1 构造函数是用于定义一个类对象并且对类的数据成员进行初始化的特殊函数。构造函数的名字要与类名相同,并且不
需要指定返回类型,也不需要返回值,它是可以被重载的,一个类中可以有多个构造函数,至少都会有一个默认的不带参
数的构造函数。
如:
class Triangular{
public:
//一组重载的构造函数
Triangular();//默认的构造函数
Triangular(int len);
Triangular(int len , int beg_pos);
//............................
};
当定义一个类的对象时,编译器会自动根据参数来调合适的构造函数。如:
Triangular t;//声明一个Triangular对象t,会自动调用无参的构造函数
Triangular t2(10,3);//调动两个参数的构造函数
Triangular t3=8;//这个要小心,这个不是赋值运算,因为两边的类型根本不一致,其实这个是调动单个参数的构造函数
Triangular t4();//这个写法是不对的,会被编译器看作是一个函数t4(),它的返回值是Triangular类型
默认的构造函数是不需要参数的,并且它会将每个数据成员初始化为默认值。
4.2.2构造函数的第二种初始化方式-------成员初值表
上面中介绍了成员初始化的第一种方式即在函数体内初始化,下面将介绍另一种形式,即用成员初值表在函数体外初始化。
如:
Triangular::Triangular(const Triangular &rhs)
:_len(rhs._len), _beg_pos(rhs._beg_pos) {}//以空函数体结束
成员初值表以构造函数形参表后加冒号开始,以空大括号结束,注意不加分号,每个成员初值由其后的括号内给出,
成员之间以逗号隔开。
成员初始表中,每个成员初始化形式是:成员名称(初始值)如_len(rhs._len)相当于调用成员的构造函数,因为上面的两个成员
_len,_beg_pos都是int型,内置类型,所以用两种初始化方式都可以,没什么差别。
但是,当类有类类型的成员时,比如string _name这个成员,声明一个Triangular 对象t ,在初始化时,需先初始化里面的类类型成员
那么,如何才能做到呢,答案就是在函数体外采用初始化表。如:
Triangular ::Triangular(int len,int bp)
:_name("Triangular")//表先于Triangular的初始化
{
_len=len>0?len:1;
_beg_pos=bp>0?bp:1;
}
这个例子中函数体内之所以不为空,是因为还有两个成员要在函数体内初始化,而上面没引进_name成员之前,之所以函数体为空,是因为_len,_beg_pos已经在成员初始表中初始化了,函数体内无事可做,所以为空。因此,可以看到,采用成员初始表时
函数体不一定要为空,并且成员初始化的两种方式可以混合用,当对某些成员需要先初始化时可以放在外面采用成员初值表的方式,有些仍然可以放在函数体内初始化。
4.2.3析构函数
析构函数主要用于释放构造函数和对象生命周期中所配置的资源,它也是类的一个成员函数,由用户自行定义,但是它的调
用是自动的,当一个对象生命结束时,编译器便会自动调用析构函数。
析构函数的名称也要求与类名相同,但还必须在前面加上~号,并且它没有返回值,也没有参数,参数表为空的,因为参数表
只有一种形式,那就是为空,所以不能重载。
如:
class Matrix{
public:
Matrix(int row,int col)
:_row(row),_col(col)
{
//构造函数进行资源配置
//用new在堆中申请内存
_pmat= new double(row*col);
}
//析构函数
~Matrix()
{
//释放资源
delete [] _pmat;//_pmat是一个指针,指向堆中分配的row*col个double元素的数组。
}
private:
int _row,_col;
double *_pmat;
};//别忘了分号
注意,析构函数并非绝对必要
4.2.4 copy constructor(用于复制对象的构造函数)
Triangular t1(8);
Triangular t2=t1;//将t1复制给t2
Triangular t3(t1);//还是复制
复制对象时,成员也会一一复制
这里就会有一个潜在的问题,比如上面的Matrix类
Matrix m1(4,4);
Matrix m2(m1);
复制完后,就会有m2._pmat与m1._pmat指向堆中同一块内存,问题来了,当我们使用完m2后,会自动调用析构
函数释放堆中内存,这样就会使得m1._pmat指向一个空数组,对m1进行操作是相当危险的。这时就有必要为这个类再加上
一个构造函数,专门用于复制。这个构造函数要复制堆内存,即在堆中再new一块内存,然后将这块内存的值初始化为传进来对象对
应成员的值。如,可以这样实现:
Matrix :: Matrix(const Matrix &rhs)
:_row(rhs._row),_col(rhs._col)
{
int elem_count=row*col;
_pmat=new double(elem_count);
for(int i=0;i<elem_count;i++)
_pmat[i]=rhs._pmat[i];
}
注意,当我们如果认为有必要加上copy constructor时,也要记得重写赋值运算符,因为,复制对象通常有两种写法,一是Matrix m2(m1); 二是,m2=m1;
4.3 mutable(可变)和 const(不可变)
const成员函数,注意,函数可以根据const与否进行重载。
我们知道const成员函数是不允许改变成员数据的值的,那是不是真的就没有办法了呢,是有办法的,但要具体分析,
例如,前面的Triangular类再加上_next成员,现在它有三个成员,分别是_len,_beg_pos,_next,前两个成员提供了数组的
抽象属性,就是说这两个值一旦变了,这个数组就变成另一个不同的数组了,而_next只是为了方便我们实现迭代机制,
它的改变并不会改变数组的性质。因此,我们就可以认为对它的改变不会破坏对象的常数性。我们只需要在类中定义
这个成员变量时,在前面加上mutable即可实现。
4.4静态类成员(静态类成员数据和静态成员函数)
class Triangular{
public:
static bool is_elem(int);//静态成员函数
private:
static vector<int> _elems;//静态数据成员的定义形式
};
4.4.1 在JAVA中,调用一个类的静态成员函数时,可以不需要定义一个对象而是可以直接通过类名.静态成员函数名调用。
在C++中也可以,但是书写方式有点不一样,要用到作用域运算符,如:Triangular :: is_elem(8);
4.4.2在类定义中加了static关键字声明为静态成员后,对于这些成员,无论是静态成员数据还是静态成员函数,在类外面使用还有
定义赋值等一律不用再加static.
4.5运算符重载
形式和函数一样,只是将函数名改为operator 运算符
如:bool operator ==(const Triangular &t) const;
运算符重载规则:
不能引入新的运算符,除类属关系运算符.,作用域运算符::,条件运算符?:,->几个运算符外,其他都可以重载。
运算符优先级别不能改变。
运算符的操作数目不能改变。
参数列表中必须至少有一个为class型别,不能全是指针等非class型别
4.6 typedef关键词
我们可以用typedef来给已知型别起别名。
形式:
typedef 已存在的旧型别名 新型别名;//记得加分号结束
4.7友元
C++中,一个类可以将其他的函数或者类声明为自已的友元(friend),从而使这些被声明为友元的函数或类可以访问这个类的成员。
4.7.1 将一个函数声明为友元
如果一个类A要将某个函数B声明为其友元,只需要在类A内部加上一句以friend开头的声明即可。这个声明构成形式是在函数B原型的前面加上friend关键词。
如:
class Triangular{
friend int Triangular_iterator :: operator *();//friend 加函数原型即可。
friend void Triangular_iterator :: check_integrity();
//.........................
};
注意:友元声明可以放在类定义的任意位置上,并不受public或private影响,并且假如一个类要将多个重载函数声明为自己的友元,它必须一个个声明。
上面例子中的operator*()和check_integrity()都是Triangular_iterator内的成员,因为友元声明中含有函数原型,所以必须在友元声明之前提供Triangular_iterator定义给
Triangular类知道,这样才能使得编译器能检查函数原型有没有错,函数是不是Triangular_iterator的成员。
4.7.2将一个类声明为友元
如:
class Triangular{
friend class Trianglar_iterator;//这种方式并不需要在友元声明前给出Triangular_iterator的定义
//................
};
这样就将类Triangular_iterator声明为类Triangular的友元,使得Triangular_iterator的所有函数成员都成为Triangular的友元。
注意:一个类将其他一些函数或类声明为友元,是希望这些友元能访问类的私有成员,如果要访问公有成员则不需声明友元都可以。
4.8指向成员函数的指针
class Sequence{
public:
typedef void (Sequence :: *PtrType)(int);
//成员函数
void fabonacci(int);
void pell(int);
void lucas(int);
void triangular(int);
void sequare(int);
void pantagonal(int);
//................
private:
PtrType _pmf;
};
4.8.1指向成员函数指针的定义
定义式中须指出函数的返回类型及参数,还必须指出所属的类
如:void (Sequence :: *pm)(int);这样就定义了一个指针pm,注意定义前要加星号*表指针,用的时候就不必加了,void是这个指针可指向的成员函数的返回值类型,
(int)表指针可指向的成员函数的参数表,中间部分(Sequence::*pm)中的Sequence ::表所属的类,pm是指针变量名。
pm=0;//初值为0,不指向任何成员函数
这样的定义有些复杂,可以用typedef关键词简化。
void (Sequence :: *PtrType)(int);
PtrType pmf1=0;
PtrType pmf2=0;
4.8.让成员函数指针指向某个成员函数
实现方式:将要绑定的成员函数的入口地址赋给指针变量即可。
成员函数地址的取址:& Sequence::fabonacci //取址时不需要指明函数的返回类型及参数表,但必须要加上所属类如Sequence::
pmf=& Sequence::fabonacci;//指向fabonacci函数