重温C++ primer 之面向对象概念编程
2012-05-24 21:19
519 查看
派生类中虚函数的声明必须与基类中的定义方式完全匹配,但有一个例外:返回对基类型的引用(指针)的虚函数。派生类中的虚函数可以返回基类函数所返回类型的派生的引用(指针)
如果基类定义了static成员,则整个继承层次中只有这样一个的成员。无论从基类派生出多少个派生类,每个static成员只有一个实例。
覆盖虚函数机制:
友元关系,构造函数,复制控制成员都不能被继承。
构造函数初始化列表使用以下规则:
1.如果类存在继承关系,派生类必须在其初始化表中调用基类的构造函数
2.类的const常量和引用类型的变量只能在初始化表中被初始化,不能在函数体内用赋值的方式初始化
3。类的数据成员的初始化可以采用初始化列表或函数体内赋值二种方式,这二种方式的效率不同。非内部数据类型的成员对象应当采用第一种方式初始化。
上面代码首先使用Item_base的默认构造函数初始化Itme_base部分,那个构造函数将isbn置为空并将price置为0。执行完毕后,再初始化Bulk_item部分的成员,并执行构造函数的函数体(函数体为空)。
如果想向基类构造函数传递实参,代码如下
概念:重构包括重新定义类层次,将操作和数据从一个类移到另一个类。
例如:上述代码重构
观察上述代码发现,Disc_item不对应具体哪个折扣策略,这个类只是为了让其他类继承。我们不想让用户定义Disc_item对象,相反,Disc_item对象只应该作为Disc_item派生类型的对象的一部分而存在。如果定义了这个类的对象,没有办法防止用户利用这个Disc_item对象调用net_price函数,将调用基类Item_base的net_price函数,造成不打折的价格的严重问题。
从上面分析的问题,我们引入了纯虚函数的概念,在Disc_item类中加入double net_price(size_t ) const = 0;
试图创建抽象基类的对象将发生编译时错误。Disc_item discounted;//error
定义派生类复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分
class Base { .........};
class Derived : public Base
{
public:
Derived(const Derived& d) : Base(d) {.......}
}
如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显式赋值。
Derived& Derived::operator= (const Derived &rhs)
{
if(this != &rhs)
{
Base::operator= (rhs);
}
return *this;
}
派生类析构函数不负责撤销基类对象的成员。编译器总是显式调用派生类对象基类部分的析构函数。首先运行派生类析构函数,然后按继承层次依次向上跳跃那个基类析构函数
继承情况下的类作用域
1.名字查找在编译时发生。
2.名字冲突与继承
a.与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。使用作用域操作符访问被屏蔽成员
b.在基类和派生类中使用同一个名字的成员函数,其行为与数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型不同,基类成员也会被屏蔽。
c.虚函数与作用域。通过基类调用被屏蔽的虚函数
class Base
{
public:
virtual int fcn();
};
class D1 : public Base
{
public:
//hides fcn in the base:this fcn is not virtual
int fcn(int);
//D1 inherits definition of Base::fcn();
}
class D2 : public D1
{
public:
int fcn(int);//nonvirtual function hides D1::fcn(int)
int fcn(); //redefines virtual fcn from Base
}
通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类
Base bobj;
D1 d1obj;
D2 d2obj;
Base *bp1 = &bobj,*bp2 = &d1obj,*bp3 = &d2obj;
bp1->fcn();//call Base::fcn
bp2->fcn();//Base::fcn
bp3->fcn();//D2::fcn
如果基类定义了static成员,则整个继承层次中只有这样一个的成员。无论从基类派生出多少个派生类,每个static成员只有一个实例。
覆盖虚函数机制:
#include "stdafx.h" #include <iostream> #include <string> class Item_base { public: Item_base(const std::string &book = "", double sales_price = 0.0) : isbn(book), price(sales_price) {} std::string book() const { return isbn; } virtual double net_price(size_t n) const { return n * price; } virtual ~Item_base() { } private: std::string isbn; protected: double price; }; class Bulk_item : public Item_base { public:
Bulk_item() : min_qty(0), discount(0) {} virtual double net_price(size_t cnt) const; private: size_t min_qty; double discount; }; double Bulk_item::net_price(size_t cnt) const { if (cnt >= min_qty) { return cnt * (1 - discount) * price; } else { return cnt * price; } } int main(int argc, char* argv[]) { Bulk_item derived; Item_base *baseP = &derived; double d = baseP->net_price(42); return 0; } 将double d = baseP->net_price(42);改为double d = baseP->Item_base::net_price(42);
以下修改是错误的,double b = ((Item_base *)basep)->net_price()仍然调用派生类的虚函数
友元关系,构造函数,复制控制成员都不能被继承。
构造函数初始化列表使用以下规则:
1.如果类存在继承关系,派生类必须在其初始化表中调用基类的构造函数
2.类的const常量和引用类型的变量只能在初始化表中被初始化,不能在函数体内用赋值的方式初始化
3。类的数据成员的初始化可以采用初始化列表或函数体内赋值二种方式,这二种方式的效率不同。非内部数据类型的成员对象应当采用第一种方式初始化。
上面代码首先使用Item_base的默认构造函数初始化Itme_base部分,那个构造函数将isbn置为空并将price置为0。执行完毕后,再初始化Bulk_item部分的成员,并执行构造函数的函数体(函数体为空)。
如果想向基类构造函数传递实参,代码如下
class Bulk_item : public Item_base { public: Bulk_item(const std::string &book = "",double sales_price = 0.0,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 cnt) const; private: size_t min_qty; double discount; };
概念:重构包括重新定义类层次,将操作和数据从一个类移到另一个类。
例如:上述代码重构
class Item_base { public: Item_base(const std::string &book = "", double sales_price = 0.0) : isbn(book), price(sales_price) {} std::string book() const { return isbn; } virtual double net_price(size_t n) const { return n * price; } virtual ~Item_base() { } private: std::string isbn; protected: double price; }; //折扣策略类,包含数量和折扣率 class Disc_item : public Item_base { public: Disc_item(const std::string &book = "", double sales_price = 0.0, size_t qty = 0, double disc_rate = 0.0) : Item_base(book,sales_price), quantity(qty),discount(disc_rate) { } virtual ~Disc_item() {} protected: size_t quantity; double discount; }; //Bulk_item类是折扣策略中的一种具体折扣 class Bulk_item : public Disc_item { public: Bulk_item(const std::string &book = "",double sales_price = 0.0,size_t qty = 0,double disc_rate = 0.0) : Disc_item(book,sales_price,qty,disc_rate) {} virtual ~Bulk_item() {} virtual double net_price(size_t cnt) const; }; double Bulk_item::net_price(size_t cnt) const { if (cnt >= quantity) { return cnt * (1 - discount) * price; } else { return cnt * price; } }
观察上述代码发现,Disc_item不对应具体哪个折扣策略,这个类只是为了让其他类继承。我们不想让用户定义Disc_item对象,相反,Disc_item对象只应该作为Disc_item派生类型的对象的一部分而存在。如果定义了这个类的对象,没有办法防止用户利用这个Disc_item对象调用net_price函数,将调用基类Item_base的net_price函数,造成不打折的价格的严重问题。
从上面分析的问题,我们引入了纯虚函数的概念,在Disc_item类中加入double net_price(size_t ) const = 0;
试图创建抽象基类的对象将发生编译时错误。Disc_item discounted;//error
定义派生类复制构造函数,该复制构造函数一般应显式使用基类复制构造函数初始化对象的基类部分
class Base { .........};
class Derived : public Base
{
public:
Derived(const Derived& d) : Base(d) {.......}
}
如果派生类定义了自己的赋值操作符,则该操作符必须对基类部分进行显式赋值。
Derived& Derived::operator= (const Derived &rhs)
{
if(this != &rhs)
{
Base::operator= (rhs);
}
return *this;
}
派生类析构函数不负责撤销基类对象的成员。编译器总是显式调用派生类对象基类部分的析构函数。首先运行派生类析构函数,然后按继承层次依次向上跳跃那个基类析构函数
继承情况下的类作用域
1.名字查找在编译时发生。
2.名字冲突与继承
a.与基类成员同名的派生类成员将屏蔽对基类成员的直接访问。使用作用域操作符访问被屏蔽成员
b.在基类和派生类中使用同一个名字的成员函数,其行为与数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型不同,基类成员也会被屏蔽。
c.虚函数与作用域。通过基类调用被屏蔽的虚函数
class Base
{
public:
virtual int fcn();
};
class D1 : public Base
{
public:
//hides fcn in the base:this fcn is not virtual
int fcn(int);
//D1 inherits definition of Base::fcn();
}
class D2 : public D1
{
public:
int fcn(int);//nonvirtual function hides D1::fcn(int)
int fcn(); //redefines virtual fcn from Base
}
通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类
Base bobj;
D1 d1obj;
D2 d2obj;
Base *bp1 = &bobj,*bp2 = &d1obj,*bp3 = &d2obj;
bp1->fcn();//call Base::fcn
bp2->fcn();//Base::fcn
bp3->fcn();//D2::fcn
相关文章推荐
- 8面向对象高级编程--->高级概念
- 《C++ Primer 4 Answer Book》 整理[14]——第15章 面向对象编程
- 面向对象概念
- 编程思想:面向对象和面向过程
- JavaScript学习总结(九)——Javascript面向(基于)对象编程
- 【Python】学习笔记——-8.3、面向对象高级编程:3.多重继承
- C#.net技术内幕05-面向对象概念
- Android编程思想,面向对象程序设计第一篇——设计模式6个原则
- 面向对象基本概念(一)
- 在VB编程中使用面向对象思想
- 开发中涉及到面向切面的编程的基本概念
- 面向对象开发的几个核心的思想和概念
- 面向对象简单概念
- 面向对象的概念:继承、封装、多态
- 面向对象不是计算机编程的基本原子
- Boolan C++面向对象高级编程(上)第一周作业
- 《每日一课》Python入门之面向对象高级编程
- java se 面向网络的编程(概念)
- Java学习系列(十三)Java面向对象之界面编程
- Java面向对象及编程的理解