C++ PRIMER 学习笔记 第十五章

Posted on 2022-08-10 10:42  金色的省略号  阅读(40)  评论(0编辑  收藏  举报

  第十五章 面向对象编程

  

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

  继承和动态绑定在两个方面简化了我们的程序:能够容易地定义与其他类相似但又不同的新类,能够更容易地编写忽略这些相似类型之间区别的程序

第一版本:继承

#include <iostream>
using std::cout;
using std::endl;

class Item_base{
    std::string isbn;
protected:
    double price;
public:
    Item_base(const std::string &book = "", double sales_price = 0.0 ):
        isbn(book ),price(sales_price ) { }
    std::string book()const { return isbn; } //基类希望派生类继承的函数不能定义为虚函数  //const
    virtual double net_price(size_t n )const //基类期待派生类重定义的定义为虚函数    //const
        {return price * n; }
    virtual ~Item_base() { } //根类一般都要定义虚析构
    void print_total(std::ostream &os, const Item_base &item, size_t n ); //const Item_base &item // std::string book()const
};

class Bulk_item:public Item_base{
    std::size_t min_qty; //最小数量
    double discount;     //购买超过最小数量时给的折扣
public:
    Bulk_item( const std::string &book="", double sales_price=0.0,std::size_t qty=0, double disc_rate=0.0  ):
        Item_base(book,sales_price),min_qty(qty ), discount( disc_rate ) {  }
    virtual double net_price(size_t )const;
};

void Item_base::print_total(std::ostream &os, const Item_base &item, size_t n ) //std::ostream &os
{
    os << "ISBN:" << item.book() 
        << "\tnumber sold: " << n << "\ttotal price: "
        << item.net_price(n ) << endl;   //通过基类的引用调用虚函数时,发生动态绑定
}

double Bulk_item::net_price(size_t cnt )const
{
    if(cnt>=min_qty)
        return cnt * (1 - discount ) * price;
    else
        return cnt * price;
}


int main()
{
    Bulk_item bitem("iphone",1,5,0.3 );//Bulk_item bitem;    
    Item_base* ib = &bitem;
    cout << ib->Item_base::net_price(5 );
    cout << endl;
    cout << ib->net_price(5 );
    
    return 0; 
}
View Code

第二版本:类的重构

#include <iostream>
using std::cout;
using std::endl;

class Item_base{
    std::string isbn;
protected:
    double price;
public:
    Item_base(const std::string &book = "", double sales_price = 0.0 ):
        isbn(book ),price(sales_price ) {  }
    std::string book()const { return isbn; } //基类希望派生类继承的函数不能定义为虚函数  //const
    virtual double net_price(size_t n )const //基类期待派生类重定义的定义为虚函数    //const
        {return price * n; }
    virtual ~Item_base() { } //根类一般都要定义虚析构
    void print_total(std::ostream &os, const Item_base &item, size_t n ); //const Item_base &item // std::string book()const
};

class Disc_item:public Item_base{  //类的重构
public:
    Disc_item(const std::string &book = "",
                double sales_price = 0.0,
                std::size_t qty = 0,
                double disc_rate = 0.0 ):Item_base(book,sales_price ), //构造父类Item_base
                quantity(qty),discount(disc_rate) {   }
protected:
    std::size_t quantity;   //数量
    double discount;        //购买超过最小数量时给的折扣
};

class Bulk_item:public Disc_item{  //不再直接继承Item_base,继承Item_base的子类Dis_item
public:
    Bulk_item( const std::string &book="", double sales_price=0.0,std::size_t qty=0, double disc_rate=0.0  ):
        Disc_item(book, sales_price, qty, disc_rate ) {   }
    virtual double net_price(size_t )const;
};

void Item_base::print_total(std::ostream &os, const Item_base &item, size_t n ) //std::ostream &os
{
    os << "ISBN:" << item.book() 
        << "\tnumber sold: " << n << "\ttotal price: "
        << item.net_price(n ) << endl;   //通过基类的引用调用虚函数时,发生动态绑定
}

double Bulk_item::net_price(size_t cnt )const //修改//访问Disc_item的protected成员quantity
{
    if(cnt>=quantity)
        return cnt * (1 - discount ) * price; //访问Item_base的protected成员price
    else
        return cnt * price;
}


int main()
{
    Bulk_item bitem("iphone",1,5,0.3 );//Bulk_item bitem;    
    Item_base* ib = &bitem;
    cout << ib->Item_base::net_price(5 );
    cout << endl;
    cout << ib->net_price(5 );
        
    
    return 0; 
}
View Code

第三版:纯虚函数,抽象基类

#include <iostream>
using std::cout;
using std::endl;

class Item_base{
    std::string isbn;
protected:
    double price;
public:
    Item_base(const std::string &book = "", double sales_price = 0.0 ):
        isbn(book ),price(sales_price ) {  }
    std::string book()const { return isbn; } //基类希望派生类继承的函数不能定义为虚函数  //const
    virtual double net_price(size_t n )const //基类期待派生类重定义的定义为虚函数    //const
        {return price * n; }
    virtual ~Item_base() { } //根类一般都要定义虚析构
    void print_total(std::ostream &os, const Item_base &item, size_t n ); //const Item_base &item // std::string book()const
};

class Disc_item:public Item_base{  //类的重构  //抽象基类//纯虚函数
public:
    Disc_item(const std::string &book = "",
                double sales_price = 0.0,
                std::size_t qty = 0,
                double disc_rate = 0.0 ):Item_base(book,sales_price ), //构造父类Item_base
                quantity(qty),discount(disc_rate) {   }
    std::pair<size_t,double> discount_policy()const{  //
        return std::make_pair(quantity, discount );
    }
    virtual double net_price(size_t )const = 0;  //纯虚函数//强制实现这一设计意图并正确指出该net_price版本没有意义
protected:
    std::size_t quantity;   //数量
    double discount;        //购买超过最小数量时给的折扣
};

class Bulk_item:public Disc_item{  //不再直接继承Item_base,继承Item_base的子类Dis_item
public:
    Bulk_item( const std::string &book="", double sales_price=0.0,std::size_t qty=0, double disc_rate=0.0  ):
        Disc_item(book, sales_price, qty, disc_rate ) {   }
    virtual double net_price(size_t )const;
};

void Item_base::print_total(std::ostream &os, const Item_base &item, size_t n ) //std::ostream &os
{
    os << "ISBN:" << item.book() 
        << "\tnumber sold: " << n << "\ttotal price: "
        << item.net_price(n ) << endl;   //通过基类的引用调用虚函数时,发生动态绑定
}

double Bulk_item::net_price(size_t cnt )const //修改//访问Disc_item的protected成员quantity
{
    if(cnt>=quantity)
        return cnt * (1 - discount ) * price; //访问Item_base的protected成员price
    else
        return cnt * price;
}

int main()
{
    
    return 0; 
}
View Code

第四版:未知类型复制、句柄

#include <iostream>
using std::cout;
using std::endl;

class Item_base{
    std::string isbn;
protected:
    double price;
public:
    Item_base(const std::string &book = "", double sales_price = 0.0 ):
        isbn(book ),price(sales_price ) {  }
    std::string book()const { return isbn; } //基类希望派生类继承的函数不能定义为虚函数  //const
    virtual double net_price(size_t n )const //基类期待派生类重定义的定义为虚函数    //const
        {return price * n; }
    virtual ~Item_base() { } //根类一般都要定义虚析构
    void print_total(std::ostream &os, const Item_base &item, size_t n ); //const Item_base &item // std::string book()const
    virtual Item_base* clone()const{   //支持句柄虚操作//复制副本//从基类开始
        return new Item_base(*this );
    }
};

class Disc_item:public Item_base{  //类的重构  //抽象基类//纯虚函数
public:
    Disc_item(const std::string &book = "",
                double sales_price = 0.0,
                std::size_t qty = 0,
                double disc_rate = 0.0 ):Item_base(book,sales_price ), //构造父类Item_base
                quantity(qty),discount(disc_rate) {   }
    std::pair<size_t,double> discount_policy()const{  //
        return std::make_pair(quantity, discount );
    }
    virtual double net_price(size_t )const = 0;  //纯虚函数//强制实现这一设计意图并正确指出该net_price版本没有意义
protected:
    std::size_t quantity;   //数量
    double discount;        //购买超过最小数量时给的折扣
};

class Bulk_item:public Disc_item{  //不再直接继承Item_base,继承Item_base的子类Dis_item
public:
    Bulk_item( const std::string &book="", double sales_price=0.0,std::size_t qty=0, double disc_rate=0.0  ):
        Disc_item(book, sales_price, qty, disc_rate ) {   }
    virtual double net_price(size_t )const;
    virtual Bulk_item* clone()const{   //支持句柄虚操作//复制副本//从基类开始
        return new Bulk_item(*this );
    }
};

class Sales_item{   //绑定Item_base类型的对象并使用* ->操作符执行Item_base的操作//句柄类
    Item_base *p;
    std::size_t *use;
    void decr_use(){
        if(--*use == 0 ) { delete p; delete use; }
    }
public:
    Sales_item():p(0),use(new std::size_t(1) ) { }
    Sales_item(const Item_base& item):p(item.clone()),use(new std::size_t(1) ){ } //绑定一个Item_base对象的副本
    //复制控制三成员函数
    Sales_item(const Sales_item &i):p(i.p ), use(i.use ) { ++*use; } //复制构造
    Sales_item& operator = (const Sales_item& ); //赋值运算符重载
    ~Sales_item() { decr_use(); };  //析构
    //数据成员访问操作
    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");
    }
};

void Item_base::print_total(std::ostream &os, const Item_base &item, size_t n ) //std::ostream &os
{
    os << "ISBN:" << item.book() 
        << "\tnumber sold: " << n << "\ttotal price: "
        << item.net_price(n ) << endl;   //通过基类的引用调用虚函数时,发生动态绑定
}

double Bulk_item::net_price(size_t cnt )const //修改//访问Disc_item的protected成员quantity
{
    if(cnt>=quantity)
        return cnt * (1 - discount ) * price; //访问Item_base的protected成员price
    else
        return cnt * price;
}

Sales_item& Sales_item::operator= (const Sales_item& rhs )
{
    ++*rhs.use;        //右操作数(rhs)计数++
    decr_use();     //处理左操作数的数据成员 //可以防止自身赋值
    p = rhs.p;      //从右操作数向左操作数复制指针
    use = rhs.use;  //从右操作数向左操作数复制指针
    return *this;   //返回左操作数的引用
}

int main()
{
    
    return 0; 
}
View Code

第五版:容器、句柄

#include <iostream>
using std::cout;
using std::endl;

#include <set>
//#include <typeinfo>

class Item_base{
    std::string isbn;
protected:
    double price;
public:
    Item_base(const std::string &book = "", double sales_price = 0.0 ):
        isbn(book ),price(sales_price ) {  }
    std::string book()const { return isbn; } //基类希望派生类继承的函数不能定义为虚函数  //const
    virtual double net_price(size_t n )const //基类期待派生类重定义的定义为虚函数    //const
        {return price * n; }
    virtual ~Item_base() { } //根类一般都要定义虚析构
    void print_total(std::ostream &os, const Item_base &item, size_t n ); //const Item_base &item // std::string book()const
    virtual Item_base* clone()const{   //支持句柄虚操作//复制副本//从基类开始
        return new Item_base(*this );
    }
};

class Disc_item:public Item_base{  //类的重构  //抽象基类//纯虚函数
public:
    Disc_item(const std::string &book = "",
                double sales_price = 0.0,
                std::size_t qty = 0,
                double disc_rate = 0.0 ):Item_base(book,sales_price ), //构造父类Item_base
                quantity(qty),discount(disc_rate) {   }
    std::pair<size_t,double> discount_policy()const{  //
        return std::make_pair(quantity, discount );
    }
    virtual double net_price(size_t )const = 0;  //纯虚函数//强制实现这一设计意图并正确指出该net_price版本没有意义
protected:
    std::size_t quantity;   //数量
    double discount;        //购买超过最小数量时给的折扣
};

class Bulk_item:public Disc_item{  //不再直接继承Item_base,继承Item_base的子类Dis_item
public:
    Bulk_item( const std::string &book="", double sales_price=0.0,std::size_t qty=0, double disc_rate=0.0  ):
        Disc_item(book, sales_price, qty, disc_rate ) {   }
    virtual double net_price(size_t )const;
    virtual Bulk_item* clone()const{   //支持句柄虚操作//复制副本//从基类开始
        return new Bulk_item(*this );
    }
};

class Sales_item{   //绑定Item_base类型的对象并使用* ->操作符执行Item_base的操作//句柄类
    Item_base *p;
    std::size_t *use;
    void decr_use(){
        if(--*use == 0 ) { delete p; delete use; }
    }
public:
    Sales_item():p(0),use(new std::size_t(1) ) { }
    Sales_item(const Item_base& item):p(item.clone()),use(new std::size_t(1) ){ } //绑定一个Item_base对象的副本
    //复制控制三成员函数
    Sales_item(const Sales_item &i):p(i.p ), use(i.use ) { ++*use; } //复制构造
    Sales_item& operator = (const Sales_item& ); //赋值运算符重载
    ~Sales_item() { decr_use(); };  //析构
    //数据成员访问操作
    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");
    }
};

inline bool compare(const Sales_item &lhs, const Sales_item &rhs ){ //与数据成员multiset绑定
    return lhs->book() < rhs->book(); //ISBN比较
}

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 size_type;
    typedef set_type::const_iterator const_iter;
    
    Basket():items(compare ) {  } //构造//compare与数据成员multiset绑定
    
    void add_item(const Sales_item &item){ //添加元素
        items.insert(item );
    }
    size_type size(const Sales_item &i)const{
        return items.count(i );
    }
    double total()const;
private:
    std::multiset<Sales_item, Comp > items;
};

void Item_base::print_total(std::ostream &os, const Item_base &item, size_t n ) //std::ostream &os
{
    os << "ISBN:" << item.book() 
        << "\tnumber sold: " << n << "\ttotal price: "
        << item.net_price(n ) << endl;   //通过基类的引用调用虚函数时,发生动态绑定
}

double Bulk_item::net_price(size_t cnt )const //修改//访问Disc_item的protected成员quantity
{
    if(cnt>=quantity)
        return cnt * (1 - discount ) * price; //访问Item_base的protected成员price
    else
        return cnt * price;
}

Sales_item& Sales_item::operator= (const Sales_item& rhs )
{
    ++*rhs.use;        //右操作数(rhs)计数++
    decr_use();     //处理左操作数的数据成员 //可以防止自身赋值
    p = rhs.p;      //从右操作数向左操作数复制指针
    use = rhs.use;  //从右操作数向左操作数复制指针
    return *this;   //返回左操作数的引用
}

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;
}

int main()
{
    Basket t;
    t.add_item(Sales_item(Bulk_item("iphone",1)) );
    t.add_item(Sales_item(Bulk_item("iphone",1)) ); 
    t.add_item(Sales_item(Bulk_item("xphone",2)) );
    cout << t.total();
    
    return 0; 
}
View Code

第六版:综合文本查找,容器,句柄

#include <iostream>
#include <fstream>  //ifstream 
#include <sstream>  //istringstream
#include <vector>
#include <set>
#include <map>
#include <string>
#include <algorithm>

//*****************TextQuery************************//
class TextQuery{
public:
    typedef std::vector<std::string>::size_type line_no;
    void read_file(std::ifstream &is){  //std::ifstream &
        store_file(is);
        build_map();
    }
    std::set<line_no> run_query(const std::string & )const;
    std::string text_line(line_no )const;
    line_no size() const{ return lines_of_text.size(); } //!!!const函数
private:
    void store_file(std::ifstream & );  //std::ifstream & //保存文件
    void build_map();                   //从文件提取单词(去除标点符号)并记录单词所在文件的行号   
    std::vector<std::string> lines_of_text;  
    std::map< std::string, std::set<line_no> > word_map;//
};

void TextQuery::store_file(std::ifstream &is )
{
    std::string textline;
    while(getline(is,textline) ){
        lines_of_text.push_back(textline );
    }
}

void TextQuery::build_map()
{
    for( line_no line_num = 0; 
         line_num != lines_of_text.size();
         ++line_num )
    {
        std::istringstream line(lines_of_text[line_num] );//从std::vector读到 std::istringstream
        std::string word;        
        while(line >> word ){
            word.std::string::erase( std::remove_if(word.begin(),word.end(), static_cast<int(*)(int) >(&ispunct)), word.end() );//!!!去除标点符号
            word_map[word].insert(line_num );
        }
    }    
}

std::set<TextQuery::line_no> TextQuery::run_query(const std::string &query_word )const
{
    std::map<std::string,std::set<line_no> > ::const_iterator loc = word_map.find(query_word ); //std::map里查找行std::set
    if(loc == word_map.end() )
        return std::set<line_no>();  //没找到返回set空对象
    else
        return loc->second;
}

std::string TextQuery::text_line(line_no line ) const
{
    if(line <lines_of_text.size() )
        return lines_of_text[line ];
    throw std::out_of_range("line number out of range");
}

//3个辅助函数声明
std::ifstream& open_file(std::ifstream &in, const std::string &file ); //open_file
void print_results(const std::set<TextQuery::line_no > &locs, 
                   const std::string &sought, 
                   const TextQuery &file  );            //print_results//用到了TextQuery类的成员函数text_line
std::string make_plural(std::size_t ctr, 
                            const std::string &word,
                            const std::string &ending );//make_plural //单词复数形式
                            
//******************Query***********************//
class Query_base{
    friend class Query; //句柄Query
protected://派生类访问
    typedef TextQuery::line_no line_no;
    virtual ~Query_base() {  }
private:   //两个接口,用户和派生类只通过句柄Query使用Query_base类
    virtual std::set<line_no> eval(const TextQuery&) const = 0;
    virtual std::ostream& display(std::ostream& = std::cout) const = 0;
};

class Query{
    friend Query operator~(const Query &);
    friend Query operator|(const Query &, const Query &);
    friend Query operator&(const Query &, const Query &);
public:
    Query(const std::string &); //创建WordQuery对象
    Query(const Query &c):q(c.q),use(c.use) { ++*use; }
    ~Query() { decr_use(); }
    Query& operator=(const Query&);
    std::set<TextQuery::line_no> eval(const TextQuery &t)const {return q->eval(t); }
    std::ostream &display(std::ostream &os = std::cout) const {return q->display(os); }
private:
    Query(Query_base *query):q(query), use(new std::size_t(1) ) {  } //操作符创建Query对象调用
    Query_base *q;
    std::size_t *use;
    void decr_use(){  if(--*use == 0){delete q;delete use;}  }
};

inline std::ostream& operator << (std::ostream &os, const Query &q){
    return q.display(os ); //参数q是const,调用的display函数也必须是const
}

class WordQuery: public Query_base{
    friend class Query;//句柄Query友元
    WordQuery(const std::string &s ):query_word(s ) {  }
    std::set<line_no > eval(const TextQuery &t)const{  //实现基类纯虚函数eval
        return t.run_query(query_word ); //用到了TextQuery类的成员函数run_query
    }
    std::ostream& display(std::ostream &os)const{      //实现基类纯虚函数display
        return os << query_word;
    }
    std::string query_word;  //数据成员query_word
};

Query::Query(const std::string &s ):q(new WordQuery(s ) ),use(new std::size_t(1)) {   }    ; //创建WordQuery对象

class NotQuery: public Query_base{
    friend Query operator~(const Query & );
    NotQuery(Query q ):query(q ) {  } //构造
    std::set<line_no > eval(const TextQuery & )const;
    std::ostream& display(std::ostream &os )const{
        return os << "~(" << query << ")"; //输出操作符的使用最终是对Query_base对象的虚函数的调用
    }
    const Query query;  //数据成员Query句柄对象
};

class BinaryQuery: public Query_base{
protected:
    BinaryQuery(Query left, Query right, std::string op ):lhs(left),rhs(right),oper(op) {  }
    std::ostream& display(std::ostream &os)const{
        return os << "(" << lhs << " " << oper << " "
                        << rhs << ")"; //输出操作符的使用最终是对Query_base对象的虚函数的调用
    }
    const Query lhs, rhs;
    const std::string oper;
};

class AndQuery: public BinaryQuery{
    friend Query operator&(const Query &, const Query & );
    AndQuery(Query left, Query right ):BinaryQuery(left, right, "&" ) {  }
    std::set<line_no > eval(const TextQuery & )const;
};

class OrQuery: public BinaryQuery{
    friend Query operator|(const Query &, const Query & );
    OrQuery(Query left, Query right ):BinaryQuery(left, right, "|" ) {  }
    std::set<line_no > eval(const TextQuery & )const;
};

std::set<TextQuery::line_no > OrQuery::eval(const TextQuery &file) const{
    std::set<line_no > right = rhs.eval(file ),
                    ret_lines = lhs.eval(file );    //返回的set对象ret_lines初始化为lhs的结果
    ret_lines.insert(right.begin(), right.end() );
    return ret_lines;
}

std::set<TextQuery::line_no > AndQuery::eval(const TextQuery &file) const{
    std::set<line_no > left = lhs.eval(file ),
                    right = rhs.eval(file );
    std::set<line_no > ret_lines;
    set_intersection(left.begin(), left.end(),
                    right.begin(),right.end(),
                    inserter(ret_lines, ret_lines.begin()) );
    return ret_lines;
}

std::set<TextQuery::line_no > NotQuery::eval(const TextQuery &file) const{
    std::set<TextQuery::line_no > has_val = query.eval(file );
    std::set<line_no > ret_lines;
    for(TextQuery::line_no n = 0; n != file.size(); ++n ){    
        if(has_val.find(n) == has_val.end() ) //没找到,就插入
            ret_lines.insert(n);
    }
    return ret_lines;
}    

//return语句隐式使用接受Query_base指针的Query构造函数
inline Query operator&(const Query &lhs, const Query &rhs){
    return new AndQuery(lhs, rhs );
}
inline Query operator|(const Query &lhs, const Query &rhs){
    return new OrQuery(lhs, rhs );
}
inline Query operator~(const Query &oper){
    return new NotQuery(oper );
}                

int main(int argc, char**argv )
{
    std::ifstream infile;
    if(argc<2 ||!open_file(infile,argv[1] ) ) {
        std::cerr << "No input file!" << std::endl;
        return EXIT_FAILURE;
    }
    TextQuery tq;
    tq.read_file(infile );
    
    Query q = Query("fiery") & Query("bird") | Query("wind");
    std::set<TextQuery::line_no> qset = q.eval(tq );
    q.display(std::cout ); // ((fiery & bird) | wind)
    print_results(qset,"",tq );
          
    return 0;
}

std::ifstream& open_file(std::ifstream &in, const std::string &file )
{
    in.close(); //如果已经打开
    in.clear();
    in.open(file.c_str() ); //打开文件
    return in;
}

void print_results(const std::set<TextQuery::line_no > &locs, 
                   const std::string &sought,
                   const TextQuery &file  )
{
    typedef std::set<TextQuery::line_no > line_nums;
    line_nums::size_type size = locs.size();
    std::cout << "\n" << sought << " occurs "
            << size << " "
            << make_plural(size, "time", "s" ) << std::endl;
    line_nums::const_iterator it = locs.begin();
    for( ; it != locs.end(); ++it ){
        std::cout << "\t(line "
            << (*it) + 1 << ")"
            << file.text_line(*it ) << std::endl;
    }
}

std::string make_plural(std::size_t ctr, 
                            const std::string &word,
                            const std::string &ending )    //make_plural //单词复数形式
{
    return (ctr == 1 ) ? word : word + ending;
}

/* 
Alice Emma has long flowing red hair. 
Her Daddy says when the wind blows 
through her hair, it looks almost alive, 
like a fiery bird in flight. 
A beautiful fiery bird, he tells her, 
magical but untamed. 
"Daddy, shush, there is no such thing," 
she tells him, at the same time wanting 
him to tell her more.
Shyly, she asks, "I mean, Daddy, is there?"    
 */
View Code

第七版:句柄模板

  使用句柄模板修改第五版

#include <iostream>
using std::cout;
using std::endl;

#include <set>
//#include <typeinfo>

class Item_base{
    std::string isbn;
protected:
    double price;
public:
    Item_base(const std::string &book = "", double sales_price = 0.0 ):
        isbn(book ),price(sales_price ) {  }
    std::string book()const { return isbn; } //基类希望派生类继承的函数不能定义为虚函数  //const
    virtual double net_price(size_t n )const //基类期待派生类重定义的定义为虚函数    //const
        {return price * n; }
    virtual ~Item_base() { } //根类一般都要定义虚析构
    void print_total(std::ostream &os, const Item_base &item, size_t n ); //const Item_base &item // std::string book()const
    virtual Item_base* clone()const{   //支持句柄虚操作//复制副本//从基类开始
        return new Item_base(*this );
    }
};

class Disc_item:public Item_base{  //类的重构  //抽象基类//纯虚函数
public:
    Disc_item(const std::string &book = "",
                double sales_price = 0.0,
                std::size_t qty = 0,
                double disc_rate = 0.0 ):Item_base(book,sales_price ), //构造父类Item_base
                quantity(qty),discount(disc_rate) {   }
    std::pair<size_t,double> discount_policy()const{  //
        return std::make_pair(quantity, discount );
    }
    virtual double net_price(size_t )const = 0;  //纯虚函数//强制实现这一设计意图并正确指出该net_price版本没有意义
protected:
    std::size_t quantity;   //数量
    double discount;        //购买超过最小数量时给的折扣
};

class Bulk_item:public Disc_item{  //不再直接继承Item_base,继承Item_base的子类Dis_item
public:
    Bulk_item( const std::string &book="", double sales_price=0.0,std::size_t qty=0, double disc_rate=0.0  ):
        Disc_item(book, sales_price, qty, disc_rate ) {   }
    virtual double net_price(size_t )const;
    virtual Bulk_item* clone()const{   //支持句柄虚操作//复制副本//从基类开始
        return new Bulk_item(*this );
    }
};

//****************Handle句柄模板**********************begin//
template<class T> class Handle{
private:
    T* ptr;          //指向基础对象的指针
    size_t *use;     //指针计数 //即多少个指针指向同一个基础对象
    void rem_ref(){  //删除基础对象(根据计数判断是否删除)
        if(--*use == 0){
            delete ptr; //删除基础对象
            delete use; //删除计数
        }
    }
public:
    Handle(T *p = 0 ):ptr(p ),use(new size_t(1) ){ } //指针ptr指向动态分配的基础对象的地址
    //复制控制
    Handle(const Handle& h):ptr(h.ptr ), use(h.use) { ++*use; } //复制,++*use
    Handle& operator=(const Handle& rhs );
    ~Handle() { rem_ref(); }   //Handle对象析构 // 删除基础对象(根据计数判断是否删除)
    //用于访问基础对象
    const T& operator*()const; //解引用操作符
    const T* operator->()const; //成员操作符            
};

template<class T>
inline Handle<T>& Handle<T>:: operator=(const Handle& rhs)
{
    ++*rhs.use;     //protect against self-assignment //防止自我复制
    rem_ref();    //decrement use count and delete pointers if needed
    ptr = rhs.ptr;
    use = rhs.use;
    
    return *this;
}

template<class T>//const Handle对象可以调用,返回类型是const T&不可以修改基础对象
inline const T& Handle<T>::operator*()const   
{
    if(ptr)
        return *ptr;
    throw std::runtime_error("dereference of unbound Handle");
}

template<class T>
inline  const T* Handle<T>::operator->()const
{
    if(ptr)
        return ptr;
    throw std::runtime_error("dereference of unbound Handle");
}
//****************Handle句柄模板**********************end//

//****************使用句柄模板的Sales_item类*************begin//
class Sales_item{   
    Handle<Item_base > h; //传递给构造函数的Item_base对象的副本上的Handle对象
public:
    Sales_item():h() { }
    Sales_item(const Item_base& item):h(item.clone() ){  } 
    //数据成员没有指针,使用合成复制控制成员函数//
    //数据成员访问操作
    const Item_base* operator->() const { h.operator->(); } //返回基础对象指针
    const Item_base& operator* () const { *h; } //返回基础对象
};
//****************使用句柄模板的Sales_item类*************end//

inline bool compare(const Sales_item &lhs, const Sales_item &rhs ){ //与数据成员multiset绑定
    return lhs->book() < rhs->book(); //ISBN比较
}

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 size_type;
    typedef set_type::const_iterator const_iter;
    
    Basket():items(compare ) {  } //构造//compare与数据成员multiset绑定
    
    void add_item(const Sales_item &item){ //添加元素
        items.insert(item );
    }
    size_type size(const Sales_item &i)const{
        return items.count(i );
    }
    double total()const;
private:
    std::multiset<Sales_item, Comp > items;
};

void Item_base::print_total(std::ostream &os, const Item_base &item, size_t n ) //std::ostream &os
{
    os << "ISBN:" << item.book() 
        << "\tnumber sold: " << n << "\ttotal price: "
        << item.net_price(n ) << endl;   //通过基类的引用调用虚函数时,发生动态绑定
}

double Bulk_item::net_price(size_t cnt )const //修改//访问Disc_item的protected成员quantity
{
    if(cnt>=quantity)
        return cnt * (1 - discount ) * price; //访问Item_base的protected成员price
    else
        return cnt * price;
}

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;
}

int main()
{
    Basket t;
    t.add_item(Sales_item(Bulk_item("iphone",1)) );
    t.add_item(Sales_item(Bulk_item("iphone",1)) ); 
    t.add_item(Sales_item(Bulk_item("xphone",2)) );
    cout << t.total();
    
    return 0; 
}
View Code

  使用句柄模板修改第六版

#include <iostream>
#include <fstream>  //ifstream 
#include <sstream>  //istringstream
#include <vector>
#include <set>
#include <map>
#include <string>
#include <algorithm>

//*****************TextQuery************************//
class TextQuery{
public:
    typedef std::vector<std::string>::size_type line_no;
    void read_file(std::ifstream &is){  //std::ifstream &
        store_file(is);
        build_map();
    }
    std::set<line_no> run_query(const std::string & )const;
    std::string text_line(line_no )const;
    line_no size() const{ return lines_of_text.size(); } //!!!const函数
private:
    void store_file(std::ifstream & );  //std::ifstream & //保存文件
    void build_map();                   //从文件提取单词(去除标点符号)并记录单词所在文件的行号   
    std::vector<std::string> lines_of_text;  
    std::map< std::string, std::set<line_no> > word_map;//
};

void TextQuery::store_file(std::ifstream &is )
{
    std::string textline;
    while(getline(is,textline) ){
        lines_of_text.push_back(textline );
    }
}

void TextQuery::build_map()
{
    for( line_no line_num = 0; 
         line_num != lines_of_text.size();
         ++line_num )
    {
        std::istringstream line(lines_of_text[line_num] );//从std::vector读到 std::istringstream
        std::string word;        
        while(line >> word ){
            word.std::string::erase( std::remove_if(word.begin(),word.end(), static_cast<int(*)(int) >(&ispunct)), word.end() );//!!!去除标点符号
            word_map[word].insert(line_num );
        }
    }    
}

std::set<TextQuery::line_no> TextQuery::run_query(const std::string &query_word )const
{
    std::map<std::string,std::set<line_no> > ::const_iterator loc = word_map.find(query_word ); //std::map里查找行std::set
    if(loc == word_map.end() )
        return std::set<line_no>();  //没找到返回set空对象
    else
        return loc->second;
}

std::string TextQuery::text_line(line_no line ) const
{
    if(line <lines_of_text.size() )
        return lines_of_text[line ];
    throw std::out_of_range("line number out of range");
}

//3个辅助函数声明
std::ifstream& open_file(std::ifstream &in, const std::string &file ); //open_file
void print_results(const std::set<TextQuery::line_no > &locs, 
                   const std::string &sought, 
                   const TextQuery &file  );            //print_results//用到了TextQuery类的成员函数text_line
std::string make_plural(std::size_t ctr, 
                            const std::string &word,
                            const std::string &ending );//make_plural //单词复数形式
//***************句柄模板************************// 
template<class T> class Handle{
private:
    T* ptr;          //指向基础对象的指针
    size_t *use;     //指针计数 //即多少个指针指向同一个基础对象
    void rem_ref(){  //删除基础对象(根据计数判断是否删除)
        if(--*use == 0){
            delete ptr; //删除基础对象
            delete use; //删除计数
        }
    }
public:
    Handle(T *p = 0 ):ptr(p ),use(new size_t(1) ){ } //指针ptr指向动态分配的基础对象的地址
    //复制控制
    Handle(const Handle& h):ptr(h.ptr ), use(h.use) { ++*use; } //复制,++*use
    Handle& operator=(const Handle& rhs );
    ~Handle() { rem_ref(); }   //Handle对象析构 // 删除基础对象(根据计数判断是否删除)
    //用于访问基础对象
    const T& operator*()const; //解引用操作符
    const T* operator->()const; //成员操作符            
};

template<class T>
inline Handle<T>& Handle<T>:: operator=(const Handle& rhs)
{
    ++*rhs.use;     //protect against self-assignment //防止自我复制
    rem_ref();    //decrement use count and delete pointers if needed
    ptr = rhs.ptr;
    use = rhs.use;
    
    return *this;
}

template<class T>//const Handle对象可以调用,返回类型是const T&不可以修改基础对象
inline const T& Handle<T>::operator*()const   
{
    if(ptr)
        return *ptr;
    throw std::runtime_error("dereference of unbound Handle");
}

template<class T>
inline  const T* Handle<T>::operator->()const
{
    if(ptr)
        return ptr;
    throw std::runtime_error("dereference of unbound Handle");
} 
//******************Query***********************//
class Query_base{
    friend class Query; //句柄Query
    friend class Handle<Query_base >;
protected://派生类访问
    typedef TextQuery::line_no line_no;
    virtual ~Query_base() {  }
private:   //两个接口,用户和派生类只通过句柄Query使用Query_base类
    virtual std::set<line_no> eval(const TextQuery&) const = 0;
    virtual std::ostream& display(std::ostream& = std::cout) const = 0;
};

class Query{  //使用Query友元的类,增加句柄模板实例为友元
    friend Query operator~(const Query &);
    friend Query operator|(const Query &, const Query &);
    friend Query operator&(const Query &, const Query &);
public:
    Query(const std::string &); //!!!  
    std::set<TextQuery::line_no> eval(const TextQuery &t)const {return h->eval(t); } //!!!
    std::ostream &display(std::ostream &os = std::cout) const {return h->display(os); }//!!!
private:
    Query(Query_base *query):h(query) {  } //!!!操作符创建Query对象调用
    Handle<Query_base > h; //!!!
};

inline std::ostream& operator << (std::ostream &os, const Query &q){
    return q.display(os ); //参数q是const,调用的display函数也必须是const
}

class WordQuery: public Query_base{
    friend class Query;//句柄Query友元
    friend class Handle<Query_base >;
    WordQuery(const std::string &s ):query_word(s ) {  }
    std::set<line_no > eval(const TextQuery &t)const{  //实现基类纯虚函数eval
        return t.run_query(query_word ); //用到了TextQuery类的成员函数run_query
    }
    std::ostream& display(std::ostream &os)const{      //实现基类纯虚函数display
        return os << query_word;
    }
    std::string query_word;  //数据成员query_word
};

Query::Query(const std::string &s ):h(new WordQuery(s ) ) {   }    ; //!!!创建WordQuery对象

class NotQuery: public Query_base{
    friend Query operator~(const Query & );
    NotQuery(Query q ):query(q ) {  } //构造
    std::set<line_no > eval(const TextQuery & )const;
    std::ostream& display(std::ostream &os )const{
        return os << "~(" << query << ")"; //输出操作符的使用最终是对Query_base对象的虚函数的调用
    }
    const Query query;  //数据成员Query句柄对象
};

class BinaryQuery: public Query_base{
protected:
    BinaryQuery(Query left, Query right, std::string op ):lhs(left),rhs(right),oper(op) {  }
    std::ostream& display(std::ostream &os)const{
        return os << "(" << lhs << " " << oper << " "
                        << rhs << ")"; //输出操作符的使用最终是对Query_base对象的虚函数的调用
    }
    const Query lhs, rhs;
    const std::string oper;
};

class AndQuery: public BinaryQuery{
    friend Query operator&(const Query &, const Query & );
    AndQuery(Query left, Query right ):BinaryQuery(left, right, "&" ) {  }
    std::set<line_no > eval(const TextQuery & )const;
};

class OrQuery: public BinaryQuery{
    friend Query operator|(const Query &, const Query & );
    OrQuery(Query left, Query right ):BinaryQuery(left, right, "|" ) {  }
    std::set<line_no > eval(const TextQuery & )const;
};

std::set<TextQuery::line_no > OrQuery::eval(const TextQuery &file) const{
    std::set<line_no > right = rhs.eval(file ),
                    ret_lines = lhs.eval(file );    //返回的set对象ret_lines初始化为lhs的结果
    ret_lines.insert(right.begin(), right.end() );
    return ret_lines;
}

std::set<TextQuery::line_no > AndQuery::eval(const TextQuery &file) const{
    std::set<line_no > left = lhs.eval(file ),
                    right = rhs.eval(file );
    std::set<line_no > ret_lines;
    set_intersection(left.begin(), left.end(),
                    right.begin(),right.end(),
                    inserter(ret_lines, ret_lines.begin()) );
    return ret_lines;
}

std::set<TextQuery::line_no > NotQuery::eval(const TextQuery &file) const{
    std::set<TextQuery::line_no > has_val = query.eval(file );
    std::set<line_no > ret_lines;
    for(TextQuery::line_no n = 0; n != file.size(); ++n ){    
        if(has_val.find(n) == has_val.end() ) //没找到,就插入
            ret_lines.insert(n);
    }
    return ret_lines;
}    

//return语句隐式使用接受Query_base指针的Query构造函数
inline Query operator&(const Query &lhs, const Query &rhs){
    return new AndQuery(lhs, rhs );
}
inline Query operator|(const Query &lhs, const Query &rhs){
    return new OrQuery(lhs, rhs );
}
inline Query operator~(const Query &oper){
    return new NotQuery(oper );
}                

int main(int argc, char**argv )
{
    std::ifstream infile;
    if(argc<2 ||!open_file(infile,argv[1] ) ) {
        std::cerr << "No input file!" << std::endl;
        return EXIT_FAILURE;
    }
    TextQuery tq;
    tq.read_file(infile );
    
    Query q = Query("fiery") & Query("bird") | Query("wind");
    std::set<TextQuery::line_no> qset = q.eval(tq );
    q.display(std::cout ); // ((fiery & bird) | wind)
    print_results(qset,"",tq );
          
    return 0;
}

std::ifstream& open_file(std::ifstream &in, const std::string &file )
{
    in.close(); //如果已经打开
    in.clear();
    in.open(file.c_str() ); //打开文件
    return in;
}

void print_results(const std::set<TextQuery::line_no > &locs, 
                   const std::string &sought,
                   const TextQuery &file  )
{
    typedef std::set<TextQuery::line_no > line_nums;
    line_nums::size_type size = locs.size();
    std::cout << "\n" << sought << " occurs "
            << size << " "
            << make_plural(size, "time", "s" ) << std::endl;
    line_nums::const_iterator it = locs.begin();
    for( ; it != locs.end(); ++it ){
        std::cout << "\t(line "
            << (*it) + 1 << ")"
            << file.text_line(*it ) << std::endl;
    }
}

std::string make_plural(std::size_t ctr, 
                            const std::string &word,
                            const std::string &ending )    //make_plural //单词复数形式
{
    return (ctr == 1 ) ? word : word + ending;
}

/* 
Alice Emma has long flowing red hair. 
Her Daddy says when the wind blows 
through her hair, it looks almost alive, 
like a fiery bird in flight. 
A beautiful fiery bird, he tells her, 
magical but untamed. 
"Daddy, shush, there is no such thing," 
she tells him, at the same time wanting 
him to tell her more.
Shyly, she asks, "I mean, Daddy, is there?"    
 */
View Code

  15.1 概述

  面向对象编程的关键思想是多态性在C++中,多态性仅用于通过继承而相关的类型的引用或指针,通过继承,对类型之间的关系建模,共享公共的东西,仅仅特化本质上不同的东西,定义virtual的函数是基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数在C++中,通过基类的引用或指针调用虚函数,发生动态绑定,引用或指针既可以指向基类对象也可以指向派生类对象

  15.2 定义基类和派生类

    15.2.1 定义基类

  基类也有定义其接口和实现的数据和函数成员,继承层次的根类 一般都要定义虚析构函数,除了构造函数之外,任意非static成员函数都可以是虚函数,基类通常应将派生类需要重定义的任意函数定义为虚函数

    15.2.2 protected成员

  基类的数据成员,它希望允许派生类访问但仍禁止其他用户访问,应使用受保护的访问标号(protected access label),派生类对象 只能 通过派生类成员函数 或 基类的接口 访问其 基类的 portected 成员

  派生类 (类内)只能通过 派生类对象 访问其基类的protected成员派生类对其 基类类型对象 的protected成员没有特殊访问权

#include <iostream>
using namespace std;

class Base{
protected:
    int num;
public:
    Base():num(100) { }
};

class Derived: public Base{
public:
    int getNum(){
        return num;
    }
    void f(){ //派生类的函数
        //Base b;
        //cout << b.num;     //派生类对基类类型对象的protected成员没有特殊访问权
        cout << this->num;   //派生类通过派生类对象访问其基类的protected成员                                
    }
};

int main()
{
    Derived d;
    cout << d.getNum(); //派生类对象,通过派生类的成员函数访问基类的protected成员
    //cout << d.num;    //不可以直接访问    
    d.f( );
    
    return 0;
}
View Code

  类有三种用户:类本身的成员  和 该类的用户从派生类定义新类的程序员,被继承的类必须决定实现的哪些部分声明为protected而哪些部分声明为private,希望禁止派生类访问的成员应该设为private,提供派生类实现所需操作或数据成员设为protected,换句话说 提供给派生类型的接口 是 protected成员 和 public 成员 的组合

#include <iostream>
using std::cout;
using std::endl;

class Base
{
protected:
    int x = 10;
public:
    void f()
    {
        cout << x << endl;
    }
};

class Derived:public Base
{
public:
    void set()
    {
        x = 119; //成员函数 基类 protected 成员 x 
    }
    Derived()
    {
        x = 120; //构造函数
    }
};

template <typename T>
void print(T& t)  //形参是T引用
{
    t.f();
}

int main()
{
    Base b;
    Derived d;
    print<Base >( b ); //实参基类对象
    print<Base >( d ); //实参派生类对象
    
    return 0;
}
View Code 
    15.2.3 派生类

  为了定义派生类,使用类派生列表指定基类,类派生列表可以指定多个基类,如果想继承基类的接口,则应该进行public 派生

  每个派生类对象包含两个部分:从基类继承的成员和自己定义的成员(C++语言不要求编译器将对象的基类部分和派生类部分连续排列,类可以访问其基类的public 和 protected成员,就好像那些成员是派生类自己的成员一样),最底层的派生类对象包含其每个直接基类和间接基类的子对象

  派生类 只定义或重定义 那些与基类不同或扩展基类行为的方面

  派生类的虚函数的声明必须与基类中定义方式完全匹配,但有一个例外:返回对基类型的引用或指针的虚函数派生类中虚函数 可以返回基类所返回类型的派生类的引用或指针

#include <iostream>
using std::cout;
using std::endl;

class Base
{
public:
    virtual Base* func () {   //返回类型是 Base* 
        cout << "Base*" << endl;
    }
};

class Derived:public Base
{
public: // 虚函数完全匹配的例外:
    virtual Derived* func () { //返回类型是 Derived* 
        cout << "Derived*" << endl;
    }
};


int main()
{
    Base b;
    Derived d;
    // 多态
    b.func(); 
    d.func();
    
    return 0;
}
View Code

  已定义的类才可以用作基类,如果已经声明了基类,但没有定义它,则不能用作基类,声明一个派生类,则 声明包含类名 但 不包含派生列表

    15.2.4 virtual与其他成员函数

  要触发动态绑定,必须满足两个条件:第一,只有指定虚函数的成员函数才能进行动态绑定,第二,必须通过基类类型的引用或指针进行函数调用

  将派生类对象当作基类对象时安全的,如果调用非虚函数,则无论实际对象是什么类型,都执行基类类型所定义的函数,非虚函数总是在编译时根据调用该函数的对象、引用或指针的类型而确定

  希望 覆盖虚函数机制 并强制函数调用使用虚函数的特定版本可以使用作用域操作符只有 成员函数中的代码才应该使用作用域操作符 覆盖虚函数机制派生类虚函数调用基类版本时,必须显示使用作用域操作符

#include <iostream>
using std::cout;
using std::endl;

class Base
{
public:
    virtual Base* func () {   //返回类型是 Base* 
        cout << "Base*" << endl;
    }
};

class Derived:public Base
{
public: // 虚函数完全匹配的例外:
    virtual Derived* func () { //返回类型是 Derived* 
        //Base::func();  //派生类虚函数调用基类版本时,必须显示使用作用域操作符
        cout << "Derived*" << endl;
        
    }
    void test(){
        Base::func(); //覆盖虚函数机制//只有成员函数中的代码才应该使用作用域操作符覆盖函数机制
        //func ();
    }
};


int main()
{
    Base b;
    Derived d;
    b.func(); 
    d.func();
    // 多态
    Base* bp = &d;    //派生类对象
    bp->func();       //虚函数
    //bp->Base::func(); //覆盖虚函数,调用基类函数(Base是Derived的基类)
    //只有成员函数中的代码才应该使用作用域操作符覆盖函数机制

    d.test();         //派生类test函数,非虚函数  
    
    return 0;
}
View Code

  在给定调用中的 虚函数默认实参值,将在编译时确定通过基类的引用或指针调用函数时,默认实参为在基类虚函数声明中指定的值,如果派生类的指针或引用调用虚函数,则默认实参是在派生类的版本中声明的值

#include <iostream>
using std::cout;
using std::endl;

class Base
{
public:
    virtual void func (int x=100) { //基类默认实参 100
        cout << "Base*" << endl;
        cout << x << endl;
    }
};

class Derived:public Base
{
public: 
    virtual void func (int x=300) { //派生类默认实参 300
        cout << "Derived*" << endl;
        cout << x << endl;       
    }
};

int main()
{
    Derived d;  
    Base* bp = &d;    //派生类对象
    bp->func();       //基类指针调用虚函数,默认实参是基类虚函数中指定的值 100
    d.func();         //300
    return 0;
}
View Code
    15.2.5 共有、私有和受保护的继承

  公有继承,基类的public成员为派生类的public成员,基类的protected成员为派生类的protected成员;受保护继承,基类的public和protected成员在派生类中为protected成员;私有继承,基类的所有成员在派生类中为private成员

  public派生类继承基类的接口,派生类对象可以用在任何需要基类对象的地方;使用private或protected派生的类,不继承基类的接口,称为实现继承

  使用class保留字定义的派生类默认具有private继承,用struct保留字定义的类默认具有public继承

  私有继承使用using恢复对继承成员的访问级别:比如,size是Base的公有成员函数在Derived的public部分增加一个using声明using Base::size; Derived对象及其子类对象都可以访问基类的size

#include <iostream>
using namespace std;

class Base{
protected:
    int num;
public:
    Base():num(100) { }
};

class Derived: private Base{//私有继承
public:
    using Base::num;        //基类的 public 或 protected成员
};

int main()
{
    Derived d;
    cout << d.num; //可以访问 num
    
    return 0;
}
View Code
    15.2.6 友元关系与继承

  友元关系不能继承,基类的友元对派生类的成员没有特殊权限

    15.2.7 继承与静态成员

  如果基类定义了static成员,则整个继承层次中只有一个这样的成员,无论从基类派生出多少个派生类,假定可以访问成员,则既可以通过基类访问static成员,也可以通过派生类访问static成员

  15.3 转换与继承

  存在从派生类型引用到基类类型引用的自动转换没有从基类引用(或基类指针)到派生类引用(或派生类指针)的自动转换可以使用派生类的对象对基类类型的对象进行初始化或赋值,但是,没有从派生类对象到基类类型对象的直接转换

    15.3.1 派生类到基类的转换

  将派生类型的对象传给希望接受基类引用的函数时,引用直接绑定到该对象,虽然看起来在传递对象,实际上是该对象的引用,对象未被复制;将派生类对象传给希望接受基类类型对象(而不是引用)的函数时,形参的类型是固定的--在编译时和运行时形参都是基类类型对象,如果派生类类型对象调用这样的函数,则派生类对象的基类部分被复制到形参,派生类对象对基类对象进行赋值或初始化初始化调用构造函数,赋值时调用赋值操作符 

  从派生类到基类的转换可能是也可能不是可访问的,转换是否可访问取决于在派生类的派生列表中指定的访问标号:如果类是使用private或protected继承派生的,则用户代码不能将派生类型对象转换为基类对象,如果是private继承,则从private 继承类 派生的类 不能 转换为基类,如果是protected继承,则 后续派生类 的成员 可以 转换为基类类型

#include <iostream>
using std::cout;
using std::endl;

class Base
{
public:
    virtual void func ( ) { 
        cout << "Base" << endl;
    }    
};

class Derived:public Base
{
public: 
    void func ( ) { 
        cout << "Derived" << endl;
    }    
};

void func1 ( Base& b ) {  //形参是基类对象的引用 
    b.func();    
}   

int main()
{
    Base b;
    Derived d;
    
    func1(b);
    func1(d);
    
    return 0;
}
View Code
    15.3.2 基类到派生类的转换

  从基类到派生类的自动转换时不存在的,甚至当基类指针或引用实际绑定到派生类对象时,从基类到派生类的转换也存在限制,编译器确定转换是否合法,只看指针或引用的静态类型,如果知道从基类到派生类的转换是安全的,就可以使用static_cast强制编译器进行转换,或者,用dynamic_cast申请运行时进行检查

/* #include <iostream>
using std::cout;
using std::endl; */

class Base{ 
};

class Derived:public Base{
};
 
int main()
{
    Derived d;
    Base *pb = &d;      //基类指针指向派生类对象  //派生类对象的地址对基类类型的指针进行初始化
    //Derived *pd = pb; //基类指针不能直接转换到派生类指针
    Derived *pd = static_cast<Derived*>(pb ); //可以使用static_cast强转(基类指针指向的是派生类对象)
    
    return 0;
}
View Code

  15.4 构造函数和复制控制

  构造函数和复制控制成员不能继承,每个类定义自己的构造函数和复制控制成员,如果不定义自己的版本,将使用合成版本

    15.4.1 基类构造函数和复制控制

  本身不是派生类的基类,其构造函数和复制控制基本上不受影响,某些类需要只希望派生类使用的特殊构造函数,应定义为protected

    15.4.2 派生类构造函数

  每个派生类构造函数 除了初始化自己的数据成员,还要初始化基类的数据成员

  构造函数初始化列表 为类的基类和成员提供初始值,它并不指定初始化的执行次序,首先初始化基类(尊重基类接口,使用基类的构造函数),然后根据声明次序初始化派生类的成员,一个类只能初始化自己的直接基类

  类的重构包括重新定义类层次,将操作和/或数据从一个类移到另一个类,对类进行重构,必须重新编译

    15.4.3 复制控制与继承

  具有指针成员的类一般需要定义自己的复制控制来管理这些成员

  如果派生类显式定义自己的复制构造函数或赋值操作符,则该定义将完全覆盖默认定义:复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分(派生类对象转换为基类部分的引用),如果省略基类初始化函数,则运行基类的默认构造函数初始化对象的基类部分; 赋值操作符必须对基类部分进行显式赋值,赋值操作符必须防止自身赋值

  派生类析构函数不负责撤销基类对象的成员,每个析构函数只负责清除自己的成员对象的撤销顺序与构造顺序相反:首先运行派生类的析构函数,然后按继承层次向上调用基类析构函数

    15.4.4 虚析构函数

  如果删除基类指针,则需要运行基类析构函数并清除基类的成员,如果对象实际是派生类的,则没有定义该行为,要保证运行适当的析构函数,基类中的析构函数必须为虚函数,如果层次中根类的析构函数为虚函数,则派生类析构函数也将是虚函数,在复制控制成员中,只有析构函数应定义为虚函数 

  基类析构函数是三法则的一个重要例外三法则指出,如果类需要析构函数,则类几乎也确实需要其他复制控制成员,基类几乎总是需要析构函数,从而可以将析构函数设为虚函数,如果基类为了将析构函数设为虚函数而具有空析构函数,那么,类具有析构函数并不表示也需要赋值操作符或复制构造函数

    15.4.5 构造函数和析构函数中的虚函数

  如果在构造函数或析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本

  15.5 继承情况下的类作用域

  在继承情况下,派生类的作用域嵌套在基类作用域中,如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义

    15.5.1 名字查找在编译时发生

  对象、引用或指针的静态类型决定了对象能够完成的行为

    15.5.2 名字冲突与继承

  与基类成员同名的派生类成员,将屏蔽对基类成员的直接访问,使用作用域操作符访问被屏蔽成员,设计派生类时,最好避免与基类成员的名字冲突

    15.5.3 作用域与成员函数

  在基类和派生类中使用同一名字的成员函数,在派生类作用域中派生类成员将屏蔽基类成员,即使函数原型不同 (在派生类作用域中找到名字,编译器就不再继续查找,同理:局部作用域中声明的函数不会重载全局作用域中定义的函数),通过派生类对象调用函数时,实参必须与派生类中定义的版本相匹配,只有在派生类根本没有定义该函数时,才考虑基类函数

  成员函数无论虚还是非虚可以重载,派生类可以重定义所继承的0个或多个版本,如果派生类重定义了重载成员,则通过派生类型只能访问派生类中重定义的那些成员为基类成员函数名称而做的using声明,将该函数的所有重载实例加到派生类的作用域

#include <iostream>
using namespace std;

struct Base{
    int memfcn(){ cout << "base" << endl; };
};
struct Derived:Base{
    using Base::memfcn; //解决与基类重名问题
    int memfcn(int){ cout << "derived" << endl; };
};

int main()
{
    Base b;
    Derived d;
    
    d.memfcn();
    d.memfcn(2);
    
    return 0;
}
View Code
    15.5.4 虚函数与作用域  

  通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类,检查实参是否与形参匹配,因此,虚函数在基类和派生类中必须拥有同一原型,是虚函数,编译器会生成代码,在运行时基于引用或指针所绑定的对象的实际类型进行调用

#include <iostream>
using namespace std;

struct Base{
    virtual int fcn(){ cout << "base" << endl; };
};
struct Derived:Base{
    int fcn(int){ cout << "derived" << endl; }; //与基类同名,但原型不同一,屏蔽基类的fcn
    //int fcn() { cout << "虚函数,与基类原型同一" << endl; }  //重定义Base中定义的fcn,原型同一
};

int main()
{
    Base b;
    Derived d;
    Base *pb = &d;
    
    pb->fcn(); //调用Base的 fcn()
    
    return 0;
}
View Code

  15.6 纯虚函数

  将函数定义为纯虚函数能够说明,该函数为后代类型提供了可以覆盖的接口,但是这个类中的版本决不会调用重要的是用户将不能创建该类型的对象,含有或继承一个或多个纯虚函数的类是抽象类,除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象

  15.7 容器与继承

  将对象放进容器时,复制元素,将派生类对象复制到基类对象时,派生类对象将被切掉,希望使用容器或内置数组保存因继承而相关联的对象,唯一可行的选择是 使用容器保存对象的指针,但是,用户必须保证只要容器存在,被指向的对象就存在,如果对象是动态分配的,用户必须保证在容器消失时适当地释放对象

  句柄存储管理基类指针(指针指向有继承层次的动态对象),句柄放入容器时会复制句柄(继承层次对象计数),比如购物篮(包装容器),辣椒句柄(蔬菜)放入篮中,茄子句柄(蔬菜)放入篮中......

  15.8 句柄类与继承  

  使用指针或引用会加重类用户的负担,通用的技术是定义包装类或句柄类,句柄类存储和管理基类指针

  包装了继承层次的句柄 ,有两个重要的设计考虑因素:像对任何保存指针的类一样,必须确定对复制控制做些什么句柄类决定句柄接口屏蔽还是不屏蔽继承层次,如果不屏蔽继承层次,用户必须了解和使用基本层次中的对象

    15.8.1 指针型句柄

   句柄的数据成员是有继承层次的基类指针和另一个计数指针,句柄的函数成员管理指针(对数据成员的操作),包括接受基类对象引用的构造函数、默认构造函数、复制构造函数、赋值运算符函数、解引用操作符函数和箭头操作符函数、析构函数

  接受基类对象引用的构造函数复制或基类对象或派生类对象,基类指针指向复制的对象,计数指针计数1,计数指针记录句柄对象个数

  复制构造函数、赋值运算符函数负责句柄对象的复制和赋值,当复制句柄对象或给句柄对象赋值时,将复制指针(句柄的数据成员),使用计数来管理句柄副本

  解引用操作符函数将返回句柄对象包装的基类指针,箭头操作符将返回句柄对象包装的基类指针指向的对象,析构函数会在句柄对象个数为0时析构,句柄对象的计数,由计数指针记录

#include <iostream>
using std::cout;
using std::endl;

class Base{        //基类
    int x = 111;
public:
    void printBaseX()const{
        cout << x << endl;
    }
    virtual Base* clone()const{
        return new Base( *this ); //调用Base的合成复制构造函数 
    }
    virtual ~ Base(){ cout << "Base虚析构" << endl; }  //虚析构
};

class Derived:public Base{//派生类
public:
    Derived* clone()const{
        return new Derived( *this ); //调用Derived的合成复制构造函数 
    }
    ~Derived() { cout << "Derived析构" << endl; }
};

class Handle{     //句柄类
private:
    Base* bp;         //指向基类或派生类对象的副本
    std::size_t* use; //句柄对象计数
public:
    Handle( const Base& b):bp( b.clone() ),use( new size_t() ){ *use = 1; }//bp指向对象是基类还是派生类的副本不确定//虚函数动态复制
    Handle():bp(NULL), use( new size_t() ){ *use = 1; }           //句柄对象默认构造
    Handle( const Handle& h):bp( h.bp ), use( h.use ) { ++*use; } //复制构造,只复制指针,句柄对象个数++
    
    Handle& operator=( const Handle& h ){                         //赋值运算符函数
        ++*h.use;             //被复制句柄个数++
        if( --*use == 0 ){  //被赋值对象数据成员
            delete bp;
            delete use;
        }        
        bp  = h.bp;         //复制指针
        use = h.use;        
        return *this;
    }
    ~Handle(){
        cout << "Handle析构" << endl;
        if( --*use == 0 ){  //被赋值对象数据成员清空
            delete bp;
            delete use;
        }    
    }
    
    const Base* operator->(){ //箭头运算符函数, 返回Base指针(const版本)
        if(bp) return bp;
        else throw std::logic_error( "unbound Handle" );
    }
    const Base& operator*(){  //解引用运算符函数,返回Base引用(const版本)
        if(bp) return *bp;
        else throw std::logic_error( "unbound Handle" );
    }    
    
    void printUseCounts(){  //打印句柄个数 查看还有多少个句柄对象,其包装的基类指针,指向同一个或基类对象或派生类对象
        cout << *use << endl;
    }
};

int main()
{
    Handle h1;
    {
        Derived d;
        h1 = d; //d转换为Handle临时对象(调用Handle的基类对象引用的构造函数,复制d,计数1),然后调用Handle的赋值运算符函数赋值给Handle对象h1(计数2)
        //Handle临时对象析构(--计数,计数1)
        (*h1).printBaseX();  //句柄解引用运算符        
        Handle h2(h1);       // Handle的复制构造 
        h2  ->printBaseX();  //句柄箭头运算符
        h2.printUseCounts(); //查看句柄个数
    }
    //h2对象析构,调用Handle析构函数
    //d对象析构,调用Derived析构函数,调用Base析构函数
    cout << "-----------------------" << endl;
    
    return 0; //Handle对象h1析构,--计数,Handle对象的计数0,析构d对象副本
}
View Code
    15.8.2 复制未知类型

  要实现接受基类对象引用的构造函数,但是我们 不知道给予构造函数的对象的实际类型 -- 或是基类对象或是派生类对象,在不知道对象的确切类型时分配已知对象的新副本,通常方法是定义 虚操作 进行复制,可以命名为 clone,从基类开始(基类必须将该函数定义为虚函数),在继承层次的每个类型中增加clone,返回基类指针(this指针动态生成对象)

    15.8.3 句柄的使用

  关联容器能够指定一个函数用作比较函数,要有效地工作,关联容器需要对每个操作使用同一比较函数,因此,容器记住比较函数是有意义的通过将比较器存储在容器对象中可以保证比较元素的每个操作将一致地进行

typedef bool(*Comp)(const Sales_item&, const Sales_item& );
std::multiset<Sales_item, Comp> items(compare);  //Sales_item是元素类型(句柄对象类型),Comp是比较器类型(比较函数类型),compare是比较函数

  15.9 再谈文本查询示例

   单词查询,非、或、与查询

    15.9.1 面向对象的解决方案

  不继承TextQuery,创建查询类,建立表达式,表达式最终对TextQuery对象中的文件运行查询,定义一个抽象基类Query_base表示由查询执行的操作,主要是为了表示查询类型,不做实际工作:eval操作,返回匹配行编号的集合,display操作,接受ostream引用并打印给定对象在该ostream上执行的查询,这些操作定义为纯虚函数,或(OrQuery)、与(AndQuery)查询类都有两个操作数,增加一个抽象类BinaryQuery,表示两个操作数的查询,和 WordQuery、NotQuery查询类直接继承基类Query_base

    15.9.2 值型句柄

  创建Query句柄类,隐藏继承层次用户代码根据句柄间接操纵Query_base对象

  Query构造函数将动态分配新的Query_base对象,定义Query对象的三个重载操作符(& | ~),每个操作符将生成的对象绑定到Query句柄,给Query定义一个参数为string对象的构造函数,该构造函数将生成新的WordQuery

  Query类将提供与Query_base类同样的操作:eval对相关查询进行计算,display打印查询

Query q = Query("fiery") & Query("bird") | Query("wind");//生成10个对象:5个Query_base对象及其相关的句柄,5个Query_base对象分别是3个WordQuery对象,一个OrQuery对象和一个AndQuery对象
     15.9.3 Query_base类
class Query_base{
    friend class Query; //句柄Query
protected://派生类访问
    typedef TextQuery::line_no line_no;
    virtual ~Query_base() {  }
private:   //两个接口,用户和派生类只通过句柄Query使用Query_base类
    virtual std::set<line_no> eval(const TextQuery&) const = 0;
    virtual std::ostream& display(std::ostream& = std::cout) const = 0;
};
    15.9.4 Query 句柄类

   Query类只为Query_base继承层次提供接口:eval、display,使用句柄Query类的 数据成员即Query_base指针 调用相应Query_base操作(操作是虚函数,会调用实际版本),只通过句柄操作(友元函数)创建Query_base的派生类对象,Query类的 接受string对象的构造函数 创建WordQuery对象,另外,Query类定义三个复制控制成员函数,Query类实现private部分的接受Query_base指针的构造函数,创建Query对象的操作符(& | ~)需要这个函数(初始化Query数据成员,动态分配Query_base派生类型的新对象 

class Query{
    friend Query operator~(const Query &);
    friend Query operator|(const Query &, const Query &);
    friend Query operator&(const Query &, const Query &);
public:
    Query(const std::string &); //创建WordQuery对象
    Query(const Query &c):q(c.q),use(c.use) { ++*use; }
    ~Query() { decr_use(); }
    Query& operator=(const Query&);
    std::set<TextQuery::line_no> eval(const TextQuery &t)const { return q->eval(t); }
    std::ostream &display(std::ostream &os = std::cout) const {return q->display(os); }
private:
    Query(Query_base *query):q(query), use(new std::size_t(1) ) {  } //操作符创建Query对象调用
    Query_base *q;
    std::size_t *use;
    void decr_use(){  if(--*use == 0){delete q;delete use;}  }
};

 Query重载操作符

//return语句隐式使用接受Query_base指针的Query构造函数
inline Query operator&(const Query &lhs, const Query &rhs){
    return new AndQuery(lhs, rhs );
}
inline Query operator|(const Query &lhs, const Query &rhs){
    return new OrQuery(lhs, rhs );
}
inline Query operator~(const Query &oper){
    return new NotQuery(oper );
}

输出操作符,不能是Query_base类的成员,要获得必要的虚函数行为,Query_base类定义了一个虚函数成员display,Query的display成员函数要用到它,Query输出操作符函数,调用Query的display成员函数,Query的display成员函数调用Query_base类的虚函数成员display

inline std::ostream& operator << (std::ostream &os, const Query &q){
    return q.display(os ); //参数q是const,调用的display函数也必须是const
}
    15.9.5 派生类

  WordQuery没有public成员,将句柄Query设为友元,句柄操作创建WordQuery对象

class WordQuery: public Query_base{
    friend class Query;//句柄Query友元
    WordQuery(const std::string &s ):query_word(s ) {  }
    std::set<line_no > eval(const TextQuery &t)const{  //实现基类纯虚函数eval
        return t.run_query(query_word );
    }
    std::ostream& display(std::ostream &os)const{      //实现基类纯虚函数display
        return os << query_word;
    }
    std::string query_word;  //数据成员query_word
};

  NotQuery对象保存一个const Query对象,对它求反

class NotQuery: public Query_base{
    friend Query operator~(const Query & );
    NotQuery(Query q ):query(q ) {  } //构造
    std::set<line_no > eval(const TextQuery & )const;
    std::ostream& display(std::ostream &os )const{
        return os << "~(" << query << ")"; //输出操作符的使用最终是对Query_base对象的虚函数的调用
    }
    const Query query;  //数据成员Query句柄对象
};

  BinaryQuery类是一个抽象类,保存AndQuery 和 OrQuery两个查询类型所需的数据,AndQuery和OrQuery有两个操作数

class BinaryQuery: public Query_base{
protected:
    BinaryQuery(Query left, Query right, std::string op ):lhs(left),rhs(right),oper(op) {  }
    std::ostream& display(std::ostream &os)const{
        return os << "(" << lhs << " " << oper << " "
                        << rhs << ")"; //输出操作符的使用最终是对Query_base对象的虚函数的调用
    }
    const Query lhs, rhs;
    const std::string oper;
};

   AndQuery类和OrQuery类

class AndQuery: public BinaryQuery{
    friend Query operator&(const Query &, const Query & );
    AndQuery(Query left, Query right ):BinaryQuery(left, right, "&" ) {  }
    std::set<line_no > eval(const TextQuery & )const;
};
class OrQuery: public BinaryQuery{
    friend Query operator|(const Query &, const Query & );
    OrQuery(Query left, Query right ):BinaryQuery(left, right, "|" ) {  }
    std::set<line_no > eval(const TextQuery & )const;
};
    15.9.6 eval函数

  OrQuery对象合并由它的两个操作数返回的行编号集合,其结果是它的两个操作数的结果的并集 

std::set<TextQuery::line_no > OrQuery::eval(const TextQuery &file) const{
    std::set<line_no > right = rhs.eval(file ),
                    ret_lines = lhs.eval(file );    //返回的set对象ret_lines初始化为lhs的结果
    ret_lines.insert(right.begin(), right.end() );
    return ret_lines;
}

  AndQuery的eval版本使用了完成集合式操作的一个标准库算法set_intersection

std::set<TextQuery::line_no > AndQuery::eval(const TextQuery &file) const{
    std::set<line_no > left = lhs.eval(file ),
                    right = rhs.eval(file );
    std::set<line_no > ret_lines;
    set_intersection(left.begin(), left.end(),
                    right.begin(),right.end(),
                    inserter(ret_lines, ret_lines.begin()) );
    return ret_lines;
}

   NotQuery查找未出现操作数的每个文本行

std::set<TextQuery::line_no > NotQuery::eval(const TextQuery &file) const{
    std::set<TextQuery::line_no > has_val = query.eval(file );
    std::set<line_no > ret_lines;
    for(TextQuery::line_no n = 0; n != file.size(); ++n ){
        if(has_val.find(n) == has_val.end() ) //没找到,就插入
            ret_lines.insert(n);
    }
    return ret_lines;
} 
  Query q = Query("fiery") & Query("bird") | Query("wind");//表达式所执行的构造函数