您的位置:首页 > 编程语言 > C语言/C++

《C++ Primer》第 15 章 面向对象编程

2013-04-30 09:32 183 查看


概述

面向对象编程基于三个基本的概念:数据抽象、继承、动态绑定。在C++中,用类进行数据抽象,用类派生从一个类继承另一个类:派生类继承基类成员。动态绑定使编译器能够在运行时决定使用基类中定义的函数韩式派生类定义的函数。

面向对象编程概述

多态性。在C++中,多态性仅用于通过继承而相关联的类型或是指针。
定义virtual的函数是基类期待派生类期待派生类重新定义的,基类希望派生类定义的函数不能定义为虚函数(析构函数除外)
动态绑定:我们能够边学程序使用继承层次中任意类型的对象,无需关心对象的具体类型。
在C++中,通过基类的的引用(或指针)调用虚函数的时候,发生动态绑定。引用既可以指向基类对象也可以指向派生类对象,这是动态绑定的关键。这在运行的时候才确定被调用函数的实际对象

定义基类和派生类

基类的接口其实就是构造函数,对接口的一点认识
virtual启用动态绑定。成员默认的是非虚函数,对非虚函数的调用时编译时确定。除了构造函数以外(后面解释原因),任何的非static成员函数都可以是虚函数。
protected成员可以被派生类的对象访问(派生类定义的内部成员),但不能被该类型的普通用户访问。
派生类只能通过派生类对象访问其基类的protected成员,派生类对象对其基类类型对象的protected成员(Bulk_item继承自Item_base)
void Bulk_item::memfcn(const Bulk_item &d,const Item_base &b)
{
double ret = price;
ret = d.price;        // ok,use price from Bulk_item object
ret = b.price;        // error,no access to price from Item_base object
}


派生类虚函数的声明必须与基类的定义方式完全匹配,但一个例外:返回对基类型引用的虚函数,可以返回派生类的引用(在clone函数中体现)
一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变其虚函数这一事实
触发动态绑定的两个条件:a指定为虚函数的成员函数 b必须通过基类类型的引用或指针进行函数调用
从派生类到基类的转换
double print _total(const Item_base &,size_t);
Item_base item;
print _total(item,10);
Bulk_item bulk;
print _total(bulk,10)


基类类型的引用和指针的关键点在于静态类型和动态类型可能不同(仅在运行时可知)
可以在运行时确定virtual函数的调用
void print_total(ostream &os,const Item_base &item,size_t n)
{
os << item.net_price(n)<<endl;//net_price是虚函数
}
Item_base item;
Bulk_item bulk;
print_total(cout,item,10);//调用的是基类的net_price
print_total(cout,bulk,10);//调用的是派生类的net_price


引用和指针的静态类型和动态类型可以不同,这是C++多态性的基石。与指针或是引用不同的是,对象时非多态的,对象的动态类型总是与静态类型相同
在编译时确定非virtual调用:根据在编译时根据调用该函数的对象、引用或指针的类型而确定
不常用:覆盖虚函数机制。当派生类调用基类版本时,必须显示使用作用域操作符:
Item_base *baseP = &derived;
double d = baseP->Item_base::net_price(43);


虚函数与默认实参:如果通过基类的引用或指针调用虚函数时,默认实参的值有在基类声明中指定的值,派生类同理。注意:在同一虚函数的派生类与基类采用不同默认实参会引起麻烦。
每个类控制它所定义的成员的访问。派生类可以进一步限制但不能放松对继承的成员的访问。
public继承是接口继承,private和protected继承是实现继承,最常见的形式是public继承(以后体会)
一个小技巧,访问私有继承中的成员
class Derived:private Base{
public:
using Base::size;
protected:
.......
}


默认继承保护级别:class默认是private继承,而struct默认public继承(笔试面试常考)
友元关系不能继承。基类的友元对派生类的成员没有特殊的访问权限。
如果基类定义了static成员,则整个继承层次中只有一个这样的成员。既可以通过基类访问static成员,也可以通过派生类访问

转换和继承

理解基类型与派生类型之间的转换非常关键
有派生类型引用到基类型引用的自动转化,但没有基类型引用到派生类类型的引用的转换
对于对象而言,可以使用派生类对象对基类型对象进行初始化赋值,但没有派生类型对象到基类类型的直接转换。
从基类到派生类的自动转换时不存在的。如果确定基类到派生类的转换时安全的,可以使用static_cast。

构造函数和复制控制

构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员
派生类构造函数首先初始化基类,然后根据声明次序初始化派生类的成员
派生类构造函数只能初始化直接基类
关键概念:重构。重新定义类的层次,将操作或数据从一个类移动到另一个类
尊重基类的接口:一旦类定义了自己的接口,与该类对象的所有交互都应该通过该接口
是否重定义复制控制函数原则:只包含类类型或内置类型数据成员、不含指针的类可以使用系统提供的合成操作。但是具有指针成员的一般需要自己定义这样的操作
如果派生类显示定义自己的复制构造函数或赋值操作符,则该定义完全覆盖默认定义。被继承类的复制构造函数和赋值操作符负责基类成分和自己的成员进行复制和赋值
如果派生类定义了自己的赋值操作符,必须对基类部分进行显示赋值。同样,赋值操作符防止自身赋值
派生类的析构函数:不负责撤销基类对象的成员。对象的撤销与构造顺序相反:首先运行派生类析构函数,然后继层次向上调用各基类析构函数。
虚析构函数:保证可以运行适当的析构函数(显示定义)。无论派生类显示定义析构函数还是使用合成析构函数,派生类析构函数总是虚函数。
基类析构函数的三法则的一个重要的例外:如果基类为了将析构函数设置为虚析构函数,那么并不表示也需要复制构造函数与赋值操作符
在复制控制中,只有析构函数可以定义为虚函数,构造函数不可以定义为虚函数。原因:构造函数是在对象完全构造前运行的,在构造函数运行时,对象的动态类型还不完整
将赋值操作符定义为虚函数没有多大意义

继承情况下的类作用域

如果不能再派生类作用域中确定名字,就在外围基类作用域中找该名字的定义。
名字查找在编译时发生。
名字冲突域继承:与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。即使函数原型不同,也会被屏蔽。
设计派生类时,最好避免与基类成员名字的冲突。
如果派生类想通过自身类型使用所有的重载版本,必须重载所有的重载版本,要么一个也不重定义。也可以使用using声明。
C++继承层次调用函数。确定调用函数的步骤:a确定函数调用的对象、引用、指针的静态类型 b.在该类中查找该类,查找不到,在直接基类查找。循着继承链向上查找。如果查找不到,则调用时错误的 c一旦找到名字,就进行常规检查,确定该函数调用是否合法 d.一旦确定函数合法,编译器就生成代码,如果函数是虚函数且通过引用或指针调用,则编译器生成码以确定对象的动态类型运行哪个函数版本,否则,编译器生成代码直接调用函数。

纯虚函数

纯虚函数:只是为了类,用户不能创建该类的对象
double net_price(std::size_t) const = 0;
纯虚函数说明该函数的后代类型提供了可以覆盖的接口,但是这个类的版本坚决不会使用。
含有(或继承)一个会多个纯虚函数的类是抽象基类

容器与继承

我们有时需要用容器保存因继承而相关联的对象,但是,对象不是多态的
因为派生类对象在赋值给基类对象时会被“切掉”,所以容器与通过继承相关的类型不能很好的融合
唯一可行的选择可能使用容器保存对象的指针。代价是需要用户管理面对对象和指针的问题,容器保证存在,被指向的对象存在。如果对象时动态分配的,用户必须保证在容器消失时适当释放对象

句柄类和继承

C++具有讽刺意味的是,不能使用对象进行面向对象编程,只能用指针或是引用
void get_prices(Item_base object,const Item_base *pointer,const Item_base &reference)
{
//which version of net_price is called is determined at run time
cout << pointer->net_price(1) << endl;
cout << reference.net_price(1) << endl;

//always invokes Item_base::net_price
cout << object.net_price(1) << endl;
}


句柄类存储和管理基类指针。指针所指向对象的类型可以变换,它既可以指向基类类型对象又可以指向派生类对象。用户可以通过句柄类访问层次的操作。因为句柄类使用指针进行操作,虚成员的行为将在运行时根据句柄类的实际对象的类型而变化
包装了继承层次的句柄有两个重要的设计考虑因素:a像对任何保存指针的类一样,确定复制控制做些什么。包装了继承层次的句柄通常像一个智能指针。 b.句柄类决定接口屏蔽还是不屏蔽继承层次,如果不屏蔽继承层次,用户必须了解和使用基类层次中的对象。(待体会)

指针型句柄

定义句柄:Sales_Item有三个构造函数,包括默认、复制、接受Item_base对象的构造函数。Sales_Item有两个数据,都是指针,一个指向Item_base对象,另一个指向使用计数。Item_base指针既可以指向基类对象也可以指向派生类对象
class Sales_item{
public:
//默认构造函数
Sales_item():p(0),use(new std::size_t(1)){}
//构造函数将复制Item_base对象
Sales_item(const Item_base&);
//复制构造函数
Sales_item(const Sales_item &i);
//赋值构造函数
Sales_item & operator=(const Sales_item&);
//析构函数
~Sales_item(decr_use());
//还要定义解引用操作符和箭头操作符
const Item_base *operator->() const
{
if(p) return p;else throw .....
}
const Item_base &operator*() const
{
if(p) return *p;else throw .....
}
private:
Item_base *p;
std::size_t *use;
void decr_use()
{
if(--(*use == 0))
{
delete p;delete use;
}
}
};
Sales_item & Sales_item::operator=(const Sales_item& rhs)
{
if(rhs != this)
{
++*use;
decr_use();
p = rhs.p;
use = rhs.use;
}
return *this;
}


复制未知类型:句柄类需要在不知对象的确切类型时分配分配已知对象的新副本
//构造函数将复制Item_base对象
Sales_item(const Item_base&);
解决这个问题是定义虚操作进行复制,名为clone
class Item_base{
public:
virtual Item_base * clone() const
{
return new Item_base(*this);
}
}
class BulkItem:public Item_base{
public:
virtual BulkItem * clone() const
{
return new BulkItem(*this);
}
}

Sales_item::Sales_item(const Item_base&item):p(item.clone()),use(new std::size_t(1)){}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: