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

C++复习 15 面向对象编程

2007-10-24 23:43 302 查看
声明,所有的朋友,如果要转我的帖子,务必注明"作者:黑啤来源:CSDN博客"和 具体的网络地址http://blog.csdn.net/nx500/archive/2007/10/24/1842542.aspx,并且我的所有 博客内容,禁止任何形式的商业用途,请大家尊重我的劳动.谢谢! 目 录 十五.面向对象编程.三个基本概念:数据抽象/继承和动态绑定. C++中,用类进行数据抽象. 用类派生从一个类继承另一个类,派生类继承基类成员. 动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数. 继承和动态绑定能够容易地定义与其他类相似但又不同的新类,能够容易地编写忽略这些相似类型之间区别的程序. 001 面向对象编程的关键思想是多态(polymorphism).通过继承而相关联的类型称为多态类型,在许多情况下,可以互换地使用派生类型或基类型的"许多形态". 派生类能够继承基类定义的成员,派生类可以无须改变而使用那些与派生类型具体特性不相关的操作,派生类可以重定义那些与派生类型相关的成员函数,派生类还可以定义更多的成员. 在C++中,基类必须指出希望派生类重定义哪些函数,定义为virtual的函数(虚函数)是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数. 动态绑定使用继承层次中任意类型的对象,无须关心对象的动态类型.使用这些类的程序无须区分函数是在基类还是在派生类中定义. 在C++中,通过基类的引用(或指针)调用虚函数时,发生动态绑定.调用的虚函数在运行时确定. 002 一般的,处于继承层次的根类定义"虚"析构函数. 除了构造函数之外,任意非static成员函数都可以是虚函数.保留字virtual只在内部的成员函数声明中出现,不能出现在类外的函数定义上. 类的对象可以访问类的public成员而不能访问private成员,private成员只能由基类的成员和友元访问;派生类可访问public成员而不能访问private成员. 如果基类有些成员希望派生类能访问但又禁止类对象访问的,可以对这些成员应用protect访问标号. 在派生类成员函数中,可以通过派生类的对象访问基类的protected成员. // Bulk_item 是Item_base的派生类,price是Item_base类中的protece成员 void Bulk_item::memfcn(const Bulk_item &d, const Item_base &b){ double ret = price; // ok 访问自己继承的protect成员 ret = d.price; // ok 访问"自己"类的对象的protect成员 ret = b.price; // error 访问基类对象的protect成员是不允许的 } 一旦成员函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实,派生类重定义虚函数时,不是必须使用virtual保留字. 派生类一般会重新定义所继承的虚函数,如果派生类没有重定义某个虚函数,则使用基类中定义版本. 派生类必须对想要重新定义的每个继承成员进行声明. 派生类中虚函数的声明必须与基类中的定义方式完全匹配,但是对于返回基类型的引用或指针的虚函数,派生类中的虚函数可以返回派生类的引用和指针. 派生类对象由派生类本身定义的成员(非static)加上由基类成员(非static)组成的子对象组成. 用作基类的类必须是已经定义了的类,只有声明是不可以的. 派生类也可以作为其他类的基类.声明一个派生类不需要派生列表. 003 要触发动态绑定,必须指定对应的成员函数为虚函数,必须通过基类类型的引用或指针进行函数调用. 因为每个派生类对象都包含基类部分,所以可将基类类型的引用绑定到派生类对象的基类部分,也可以用指向基类的指针指向派生类对象. // 注意期望动态绑定的类对象形参一定要声明为引用或指针. void print_total(ostream& os, const Item_base&, size_t); Item_base item; print_total(cout, item, 10); // ok. Item_base *p = item; // 定义基类指针. Bulk_item bulk; // 定义派生类对象. print_total(cout, bulk, 10); // ok 派生类对象,包含基类部分. p = &bulk; // ok p指向派生类对象的基类部分. 基类类型引用和指针的关键点在于静态类型和动态类型可能不同.将基类类型的引用或指针绑定到派生类对象,对象本身仍为派生类类型. 但通过引用或指针调用虚函数时,编译器生成代码,在运行时确定调用哪个函数,被调用的是与动态类型相对应的函数. void print_total(ostream& os, const Item_base& item, size_n){ os << "ISBN: " << item.book() << "/tnumber sold: " << n << "/ttotal price" // 调用虚函数,具体使用基类成员还是派生类成员在程序运行时确定. << item.net_price(n) << endl; } 如果调用的是非虚函数,则无论实际对象是什么,都执行基类类型所定义的函数.非虚函数总是在编译时根据调用该函数的对象/引用或指针的类型确定. 对象的类型是明确的而且不变的,对象的动态类型总是与静态类型相同. 在某些情况下,希望覆盖虚函数机制并强制函数调用使用虚函数的特定版本,这时可以使用作用域操作符 // 只有成员函数中的代码才应该使用作用域操作符覆盖虚函数机制. Item_base *baseP = &derived; double d = baseP->Item_base::net_price(42); 派生类虚函数调用基类版本时,必须显式使用作用域操作符.如果派生类函数忽略了这样做,则函数调用会在运行时确定并且将是一个自身调用,从而导致无穷递归. 虚函数也可以有默认实参.在同一个虚函数的基类版本和派生类版本中使用不同的默认实参几乎一定会引起麻烦. 004 public private 和 protected 的继承. 派生类所继承的成员的访问控制由基类中的访问级别和派生类派生列表中使用的访问标号共同控制. 这个访问控制是针对派生类对象而言的,派生类可以访问基类中所有的成员. public继承,基类成员保持自己的访问级别. protected继承,基类的public和protected成员在派生类中为protected. privete继承,基类的所有成员在派生类中为private成员. 同时派生访问标号还控制来自非直接派生类的访问. public派生类继承基类的接口,它具有与基类相同的接口.设计良好的类层次中,public派生类的对象可以用在任何需要基类对象的地方. 使用private和protected派生的类不继承基类的接口,这些派生称为实现继承. 可以使用using声明将根据控制级别派生类对象不能访问的成员提升为可以访问级别. class Base{ public: std::size_t size() const{reutrn n;} protected: std::size_t n; }; class Derived : private Base{ public: using Base::size; // 正常的在派生类中为private,通过using提升为pulic. protected: using Base::n; } 使用class保留字定义的派生类默认具有private继承,而用struct保留字定义的类默认具有public继承. 友元关系不能继承.基类的友元对派生类的成员没有特殊访问权限. 如果基类定义了static成员,则整个继承层次中只有一个这样的成员.无论从基类派生出多少个派生类,每个satic成员只有一个实例. 005 每个派生类对象都包含一个基类部分,这意味着可以像使用基类对象一样在派生类对象上执行操作.所以存在从派生类型引用到基类型引用的自动转换. 基类类型对象既可以作为独立对象存在,也可以作为派生类对象的一部分存在.所以没有从基类引用到派生类引用的自动转换. 如果有一个派生类型的对象,则可以使用它的地址对基类类型的指针(或引用)进行赋值或初始化. 将派生类对象传给希望接受基类类型"引用"参数的函数时,该对象仍是派生类对象,为发生任何转化. 将派生类对象传给希望接受基类类型"对象"参数的函数时,该派生类对象的基类部分被复制到形参中. 用派生类对象对基类对象进行初始化或者赋值,首先,派生类对象转为基类的引用,该引用作为实参传给基类的构造函数或者赋值操作符,创建基类对象. 如果派生类是public于基类的,则从派生类到基类的转换是可行的;但如果是private或protected继承,则转化是不可以的. 从基类到派生类的转换是不存在的.需要派生类对象时不能使用基类对象. 当基类指针(或引用)实际绑定到派生类对象时,从基类到派生类的转换也存在限制. Bulk_item bulk; Item_base *itemP = &bulk; // ok 静态类型为基类,动态类型为派生类. Bulk_item *bulkP = itemP; // error itemP为基类指针,不能绑定到派生类指针上,即使itemP实际绑定的是派生类对象. 006 每个派生类对象由派生类中定义的成员加上一个或多个基类子对象构成,当构造/复制/赋值和撤销派生类对象时,也会构造/复制/赋值和撤销基类对象. 作为根的基类,如果需要只有派生类使用的构造函数,则可以把构造函数定义为protected. 每个派生类构造函数除了初始化自己的数据成员之外,还要初始化基类. 派生类自动生成的默认构造函数除了要初始化派生类的成员外,还要调用基类的默认构造函数初始化基类的成员. 向基类构造函数传递实参. 派生类构造函数的初始化列表只能初始化派生类的成员,不能直接初始化继承成员,通过将基类包含在构造函数初始化列表中来间接初始化继承成员. class Item_base{ public: Item_base(const std::string &book = "", double sales_price = 0.0): isbn(book), price(sales_price){} }; class Bulk_item : public Item_base(){ public: // 下面是含有两个默认实参的构造函数,也可以编写全部都含有默认实参的构造函数 Bulk_item(const std::string& book, double sales_price, std::size_t qty = 0, double disc-rate = 0.0): Item_base(book, sales_price), min_qty(qty), discount(disc_rate){} }; 构造函数初始化列表为类的基类和成员提供初始值,它并不指定初始化的执行次序.首先初始化基类,然后根据声明次序初始化派生类的成员. 一个类只能初始化自己的直接基类. 重构:为了适应应用程序的需要而重新设计类,将操作或数据从一个类移到另一个类,重新定义类层次,以便增加新函数或处理其他改变. 虽然重构改变了继承层次,但是对应使用类的代码一般不需要改变. 007 派生类也可以定义自己的复制控制成员或者使用编译器自动生成的复制控制成员. 如果派生类定义了自己的复制构造函数,该复制构造函数一般应该显式使用基类复制构造函数初始化对象的基类部分. class Base{ // ..}; class Derived: public Base{ public: Derived(const Derived& d):Base(d){ // ... } } 如果没有显式的调用B(d),则会调用Base的默认构造函数初始化对象的基类部分,这会导致Derived成员的Base部分保存默认值,而其他成员是另一个对象的副本. 派生类的赋值操作符 Derived &Derived::operator=(const Derived &rhs){ // 赋值操作符必须防止自身赋值 if (this != &rhs){ Base::operator=(rhs); // ...anything else } return *this; } 派生类的析构函数不负责撤销基类对象的成员.每个析构函数只负责清楚自己的成员. 对象的撤销顺序与构造顺序相反,首先运行派生类的析构函数,然后按继承层次依次向上调用各基类析构函数. class Derived : public Base{ public: ~Derived(){} } 008 自动调用基类部分的析构函数对基类的设计有重要影响. 删除指向动态分配对象的指针时,需要在释放对象的内存之前运行析构函数清除对象. 处理继承层次中的对象时,指针的静态类型可能与被删除对象的动态类型不同,可能会删除实际指向派生类对象的基类类型指针. 如果删除基类指针,则需要运行基类析构函数并清楚基类的成员,如果对象实际是派生类型,则没有定义该行为. 要保证运行适当的析构函数,基类中的析构函数必须为虚函数.那么通过指针调用时,运行哪个析构函数将因指针所指对象类型不同而不同. 基类析构函数是三法则的一个重要例外.即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数. 在复制控制成员中,只有析构函数应定义为虚函数,构造函数不能定义为虚函数. 009 每个类都保持着自己的作用域,在该作用域中定义了成员的名字.在继承情况下,派生类的作用域嵌套在基类作用域中. 如果不能在派生类作用域确定名字,就在外围基类作用域中查找该名字的定义.名字查找在编译是发生. 基类指针(引用或对象)只能访问对象的基类部分.即使绑定在基类指针或引用上的是派生类对象,也不能通过这样的指针或引用访问派生类的成员. 与基类成员同名的派生类成员将屏蔽对基类成员的访问.只用使用域操作符才能访问被屏蔽的基类成员. 自基类和派生类中使用同一个名字的成员函数,即使函数原型不同,基类成员函数也会被屏蔽. struct Base{ int memfcn(); }; struct Derived : Base{ int memfcn(int); }; Derived d; Base b; b.memfcn(); // ok d.memfcn(10); // ok d.memfcn(); // error, Base中的memfcn被屏蔽 d.Base::memfcn(); // ok 还记得么?局部作用域中声明的函数不会重载全局作用域中定义的函数,同样派生类中定义的函数也不重载基类中定义的成员. 通过派生类对象调用函数时,实参必须与派生类中定义的版本相匹配,只有在派生类根本没有定义该函数时,才考虑基类函数. 如果派生类重定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员.要派生类不用重定义所继承的每一个基类版本,可以为重载成员提供using声明. 一个using声明只能指定一个名字,不能指定形参表,因此,为基类成员函数名称而作的using声明将该函数的所有重载实例加载到派生类的作用域中. 虚函数和作用域.派生类的虚函数和基类的虚函数必须拥有相同的原型,如果形参不同,就没有办法通过基类类型的引用或指针调用派生类函数. class Base { public: virtual int fcn(); }; class D1 : public Base { public: int fcn(int); // D1中定义了一个新的fcn(int)函数,而不是重载基类的虚函数,同时应为相同的函数名而屏蔽了基类的虚函数. }; class D2 : public D1 { public: int fcn(int); // 新定义了一个fcn(int)函数,屏蔽了D1泪中的fcn(int)函数. int fcn(); // 重定义了Base中的虚函数. }; Base bobj; D1 d1obj; D2 d2obj; Base *bp1 = &bobj, *bp2 = &d1obj, *bp3 = d2obj; bp1->fcn(); // ok 基类的fcn(). bobj.fcn(); // ok 同上. bp2->fcn(); // ok 还是调用基类的obj. d1obj.fcn(); // error 不能通过D1的对象调用基类的fcn(),因为该函数被fcn(int)屏蔽了. d1obj.fcn(1); // ok 调用D1定义的函数. bp3->fcn(); // ok 调用D2重载的虚函数. d2obj.fcn(); // ok 调用D2重载的虚函数. d2obj.fcn(1); // ok 调用D2重定义的fcn(int). 010 纯虚函数.在函数形参表后面写上=0以指定纯虚函数.含有存虚函数的类不能定义对象,称为抽象基类. 011 容器与继承.因为派生类对象在赋值给基类对象时会被"切掉",所以容器与通过继承相关的类型不能很好地融合. 可行的策略是使用容器保存基类对象的指针,代价是用户要面对管理指针的问题,用户必须保证只要容器存在,被指向的对象就存在. 如果对象是动态分配的,用户必须保证在容器消失时适当的释放对象. 012 以上问题更好的解决方案是使用句柄类.句柄类存储和管理基类指针.指针所指对象的类型可以变化,用户通过句柄类访问继承层次的操作. 指针型句柄举例.Sale_item类通过句柄构造函数来保存Item_base类对象或者Bule_item类对象.


// 15012_ItemBase.h

#include <iostream>

#ifndef ITEM_BASE_H

#define ITEM_BASE_H



class Item_base...{

public:

friend std::ostream& operator<<(std::ostream& , const Item_base&);

// 默认构造函数,isbn默认为空字符串,价格默认为0.

Item_base(const std::string& book ="", double sales_price = 0.0):



isbn(book), price(sales_price)...{}

// 根据对象自身创建一个临时对象,声明为虚函数,继承类对象,就可以返回继承类类型的临时对象.

// 用于句柄构造函数.

virtual Item_base* clone() const;

// 查询isbn.



std::string book()const...{

return isbn;

}

// 根据购买书本数目计算总价

virtual double net_price(std::size_t n) const;



virtual void print() const...{

std::cout << *this;

}

private:

std::string isbn;

protected:

double price;

};

#endif


// 15012_ItemBase.cpp

#include <iostream>

#include "15012_ItemBase.h"

using std::ostream;

// 输出操作



inline ostream& operator<<(ostream& out, const Item_base& s)......{

out << s.book() << " " << s.price;

return out;

}



double Item_base::net_price(std::size_t n) const......{

return n * price;

}





Item_base* Item_base::clone() const......{

return new Item_base(*this);

}



// 15012_BulkItem.h

#ifndef BULKITEM_H

#define BULKITEM_H

#include <iostream>

#include <string>

#include "15012_ItemBase.h"



class Bulk_item : public Item_base......{

public:

friend std::ostream& operator<<(std::ostream&, const Bulk_item&);

Bulk_item(const std::string& book ="", double sales_price = 0.0,

std::size_t mq = 2, double disc = 0.2):



Item_base(book, sales_price), min_qty(mq), discount(disc)......{}

// 针对书购买的数量确定是否打折,并返回最后的计算价格.

double net_price(std::size_t) const;

// 返回一个Bulk_item类型的对象,用于句柄构造函数.



Bulk_item * clone() const......{

return new Bulk_item(*this);

}



void print() const......{

std::cout << *this;

}

private:

std::size_t min_qty; // 最低起折扣的数量.

double discount; // 折扣 默认20%off(8折).

};

#endif



// 15012_BulkItem.cpp

#include <iostream>

#include "15012_BulkItem.h"

using std::ostream;





inline ostream& operator<<(ostream& out, const Bulk_item& s)......{

out << s.book() << " " << s.price << "*";

return out;

}





double Bulk_item::net_price(std::size_t cnt) const......{

if (cnt >= min_qty)

return cnt * (1 - discount) * price;

else

return cnt * price;

}



// 15012_SalesItem.h

#ifndef SALESITEM_H

#define SALESITEM_H

#include <iostream>

#include <string>

#include <stdexcept>

#include "15012_ItemBase.h"

#include "15012_BulkItem.h"



class Sales_item ......{

public:

// 默认的构造函数,p为空,销售数量为0



Sales_item(): p(0), use(new std::size_t(0)) ......{ }

// 定义句柄构造函数,关键技术技术在item.clone()函数的返回.

Sales_item(const Item_base &item);

// 复制构造函数



Sales_item(const Sales_item &i): p(i.p), use(i.use) ......{

++*use;

}

// 析构函数



~Sales_item()......{

decr_use();

}



Sales_item& operator=(const Sales_item&);





const Item_base *operator->() const......{

if(p)

return p;

else

throw std::logic_error("unbound Sales_item");

}



const Item_base& operator*() const......{

if(p)

return *p;

else

throw std::logic_error("unbound Sales_item");

}

private:

Item_base *p; // 图书信息指针,静态类型是Item_base,但是动态类型可能是Bulk_item.

std::size_t *use; // 书本的销售数量计数器.

// 由于复制构造函数和析构函数都要用到这段代码,所以提炼为一个privae的内联函数.



void decr_use()......{



if(--*use == 0)......{

delete p;

delete use;

}

}

};

#endif



// 15012_SalesItem.cpp

#include <iostream>

#include "15012_SalesItem.h"



Sales_item::Sales_item(const Item_base &item):



p(item.clone()), use(new std::size_t(1))......{};





Sales_item& Sales_item::operator=(const Sales_item& rhs)......{

++*rhs.use;

decr_use();

p = rhs.p;

use = rhs.use;

return *this;

}



// 15012_Basket.h

#ifndef BASKET_H

#define BASKET_H

#include <iostream>

#include <set>

#include "15012_SalesItem.h"



inline bool compare(const Sales_item &lhs, const Sales_item &rhs)......{

return lhs->book() < rhs->book();

}





class Basket......{

typedef bool(*Comp)(const Sales_item&, const Sales_item&);

public:

typedef std::multiset<Sales_item, Comp> set_type;

typedef set_type::size_type sz_type;

typedef set_type::const_iterator const_iter;

// 默认构造函数,使用自定义的compare函数作为multiset容器的比较谓词.



Basket():items(compare)......{}

// 向购物篮添加书本,这里调用句柄构造函数,放进的可能是Item_base对象,也可能是Bulk_item对象.



void add_item(const Sales_item &item)......{

items.insert(item);

}

// 统计容器中于i相同的元素的个数.



sz_type size(const Sales_item &i) const ......{

return items.count(i);

}

// 计算购物栏中全部书的总价格.

double total() const;

// 显式购物篮中书本的价格.

void lstBook() const;

private:

std::multiset<Sales_item, Comp> items;

};

#endif







// 15012_Basket.cpp

#include <iostream>

#include "15012_Basket.h"

using std::cout;

using std::endl;

using std::ostream;



// 计算购物栏中全部书的总价格.



double Basket::total() const.........{

double sum = 0.0;

for (const_iter iter = items.begin(); iter != items.end();



iter = items.upper_bound(*iter)).........{

sum += (*iter)->net_price(items.count(*iter));

}

return sum;

}



// 显式购物篮中书本的价格.



void Basket::lstBook() const.........{



for (const_iter iter = items.begin(); iter != items.end(); ++iter).........{

(*iter)->print();

cout << endl;

}

}


// 15012.cpp main 文件.

#include <iostream>

#include <string>

#include "15012_ItemBase.h"

#include "15012_BulkItem.h"

#include "15012_SalesItem.h"

#include "15012_Basket.h"

using std::string;

using std::cout;

using std::endl;

using std::ostream;







int main()......{

Basket bkt;

Item_base ib1(string("123-456"), 20);

Item_base ib2(string("123-456"), 20);

Item_base ib3(string("122-456"), 13);

Item_base ib4(string("122-456"), 13);

Bulk_item bi1(string("133-456"), 20, 2);

Bulk_item bi2(string("133-456"), 20, 2);

Bulk_item bi3(string("143-456"), 25, 2);



bkt.add_item(ib1);

bkt.lstBook();

cout << "Total price:" << bkt.total() << endl << endl;



bkt.add_item(ib2);

bkt.lstBook();

cout << "Total price:" << bkt.total() << endl << endl;



bkt.add_item(ib3);

bkt.lstBook();

cout << "Total price:" << bkt.total() << endl << endl;



bkt.add_item(ib4);

bkt.lstBook();

cout << "Total price:" << bkt.total() << endl << endl;



bkt.add_item(bi1);

bkt.lstBook();

cout << "Total price:" << bkt.total() << endl << endl;



bkt.add_item(bi2);

bkt.lstBook();

cout << "Total price:" << bkt.total() << endl << endl;



bkt.add_item(bi3);

bkt.lstBook();

cout << "Total price:" << bkt.total() << endl << endl;

return 0;

}


编译及运行结果. heipi@Linux:~/Documents/CPP> g++ -o o 15012_ItemBase.cpp 15012_BulkItem.cpp 15012_SalesItem.cpp 15012_Basket.cpp 15012.cpp heipi@Linux:~/Documents/CPP> ./o 123-456 20 Total price:20 123-456 20 123-456 20 Total price:40 122-456 13 123-456 20 123-456 20 Total price:53 122-456 13 122-456 13 123-456 20 123-456 20 Total price:66 122-456 13 122-456 13 123-456 20 123-456 20 133-456 20* Total price:86 122-456 13 122-456 13 123-456 20 123-456 20 133-456 20* 143-456 25* Total price:111 122-456 13 122-456 13 123-456 20 123-456 20 133-456 20* 143-456 25* 143-456 25* Total price:126 heipi@Linux:~/Documents/CPP>
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: