面向对象编程

--继承情况下的类作用域

引言

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

正是这样的类作用域的层次嵌套使我们能够直接訪问基类的成员,就好像这些成员是派生类成员一样:

    Bulk_item bulk;

    cout << bulk.book() << endl;

名字book的使用将这样确定[先派生->后基类]:

1)bulk是Bulk_item类对象,在Bulk_item类中查找,找不到名字book。

2)由于从Item_base派生Bulk_item,所以接着在Item_base类中查找,找到名字book,则引用成功的确定了。

一、名字查找在编译时发生

对象、引用或指针的静态类型决定了对象能够完毕的行为。甚至当静态类型和动态类型可能不同的时候,就像使用基类类型的引用或指针时可能会发生的,静态类型仍然决定着能够使用什么成员:

class Disc_item : public Item_base
{
public:
std::pair<size_t,double> discount_policy() const
{
return std::make_pair(quantity,discount);
} //other member as before...
};

仅仅能通过Disc_item类型或Disc_item派生类型的对象、指针或引用訪问discount_policy():

    Bulk_item bulk;
Bulk_item *bulkP = &bulk;
Item_base *itemP = &bulk; bulkP -> discount_policy(); //OK
itemP -> discount_policy(); //Error

通过itemP訪问是错误的,由于基类类型的指针(引用或对象)仅仅能訪问对象的基类部分,而不能訪问派生类部分,而在基类中又未定义discount_policy()成员。

//P498 习题15.21/22
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(std::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,
std::size_t qty = 0,
double disc_rate = 0.0):
Item_base(book,sales_price),quantity(qty),discount(disc_rate) {} //将函数设置为纯虚函数,以防止用户创建Disc_item对象
double net_price(size_t) const = 0; std::pair<size_t,double> discount_policy() const
{
return std::make_pair(quantity,discount);
} protected:
std::size_t quantity; //可实行折扣的数量
double discount; //折扣率
}; //批量购买折扣类
class Bulk_item : public Disc_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) {} double net_price(std::size_t cnt) const
{
if (cnt >= quantity)
{
return cnt * (1 - discount) * price;
}
else
{
return cnt * price;
}
}
}; //有限折扣类
class Lds_item : public Disc_item
{
public:
Lds_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) {} double net_price(std::size_t cnt) const
{
if (cnt <= quantity)
{
return cnt * (1 - discount) * price;
}
else
{
return price * (cnt - quantity * discount);
}
}
};

二、名字冲突与继承

与基类成员同名的派生类成员将屏蔽对基类成员的直接訪问:

class Base
{
public:
Base():mem(0){} protected:
int mem;
}; class Derived : public Base
{
public:
Derived(int i):mem(i){}
int get_mem() const
{
return mem; //Derived::mem
} private:
int mem; //将会屏蔽Base::mem
};

get_mem中对mem的引用被确定为Derive中的名字:

    Derived d(43);
cout << d.get_mem() << endl; //output 43

能够使用作用域操作符訪问被屏蔽的成员:

class Derived : public Base
{
public:
int get_mem() const
{
return Base::mem; //Derived::mem
}
//As before
}; //測试
Derived d(43);
cout << d.get_mem() << endl; //output 0

作用域操作符指示编译器在Base中查找mem成员。

【最佳实践】

设计派生类时,仅仅要可能,最好避免与基类成员的名字冲突

//P499 习题15.23
class Base
{
public:
void foo(int); protected:
int bar;
double foo_bar;
}; class Derived : public Base
{
public:
void foo(string);
bool bar(Base *pb);
void foobar(); protected:
string bar;
}; void Derived::foobar()
{
bar = "1024";
} bool Derived::bar(Derived *pb)
{
return foo_bar == pb -> foo_bar;
} int main()
{
Derived d;
d.foo("1024");
}
/*说明:可能是g++编译器对类型检查比較严格,这个程序在g++编译器上死活编译只是,
*由于在Derivd中的string bar处编译器提示说:与前面的声明冲突了!
*的确,在Derivd中,bar既有数据成员又有成员函数!!!
*/

三、作用域与成员函数

在基类和派生类中使用同一名字的成员函数,其行为与数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员。即使函数原型不同,基类成员也会被屏蔽:

struct Base
{
int memfuc();
}; struct Derived : Base
{
int memfuc(int);
}; int main()
{
Derived d;
Base b; b.memfuc(); //调用Base::memfuc()
d.memfuc(10); //调用Derived::memfuc()
d.memfuc(); //Error
d.Base::memfuc();//调用Base::memfuc()
}

Derived中的memfuc声明隐藏了Base中的声明。在确定以下一条语句时:

    d.memfuc();	

编译器查找名字memfuc,并在Derived类中找到。一旦找到了名字,编译器要就不再继续查找了。

【小心地雷】

局部作用域中声明的函数不会重载全局作用域中定义的函数,相同,派生类中定义的函数也不会重载基类中定义的成员。通过派生类对象调用函数时,实參必须与派生类中定义的版本号相匹配,仅仅有在派生类中根本未定义该函数时,才考虑基类函数。如:

struct Base
{
int memfuc();
}; struct Derived : Base
{
int memfuc(int);
}; Derived d;
d.memfuc(); //Error

假设将Derived中的intmemfuc(int)凝视掉,则:

d.memfuc();	//OK

重载函数

像其它随意函数一样,成员函数(不管虚还是非虚)也能够重载。派生类能够重定义所继承的0个或多个版本号。

[注意] 假设派生类重定义了重载成员,则通过派生类型仅仅能訪问派生类中重定义的那些成员

struct Derived : Base
{
int memfuc();
int memfuc(int);
double memfuc(double);
}; int main()
{
Derived d;
d.memfuc(); //Derived::memfuc()
d.memfuc(10); //Derived::memfuc(int)
}

假设派生类想通过自身类型使用全部的重载版本号,则派生类必须要么重定义全部重载版本号,要么一个也不重定义。

有时类须要仅仅重定义一个重载版本号,而且想要继承其它版本号的含义,在这样的情况下,派生类不用重定义所继承的每个基类版本号,它能够为重载成员提供using声明。一个using声明仅仅能指定一个名字,不能指定形參表,因此:using声明会将该函数的全部重载实例加到派生类的作用域。将全部名字增加作用域之后,派生类仅仅须要重定义本类型确实必须定义的那些函数,对其它版本号能够使用继承的定义。

struct Base
{
int memfuc();
int memfuc(int);
int memfuc(double);
}; struct Derived : Base
{
using Base::memfuc;
int memfuc(); //重定义
}; int main()
{
Derived d;
d.memfuc(); //Derived::memfuc()
d.memfuc(10); //Base::memfuc(int)
}

四、虚函数与作用域

虚函数:假设基类成员与派生类成员接受的实參不同,就没有办法通过基类类型的引用或指针调用派生类函数:

class Base
{
public:
virtual int fcn();
}; class D1 : public Base
{
public:
//该fcn屏蔽了Base类中的虚函数fun
int fcn(int);
/**此时有两个名为 fcn 的函数:
*类从 Base 继承的一个名为 fcn 的虚函数
*类定义的名为 fcn 的非虚成员函数,该函数接受一个 int 形參
*/
}; class D2 : public D1
{
public:
/**重定义了它继承的两个函数:
*1.重定义了 Base 中定义的 fcn 的原始版本号
*2.重定义了 D1 中定义的非虚版本号。
*/
int fcn();
int fcn(int);
};

通过基类调用被屏蔽的虚函数

通过基类类型的引用或指针调用函数时,编译器将在基类中查找该函数而忽略派生类:

    Base bobj;
D1 d1obj;
D2 d2obj; Base *bp1 = &bobj,*bp2 = &d1obj,*bp3 = &d2obj; bp1 -> fcn(); //调用Base::fcn()
bp2 -> fcn(); //调用Base::fcn()
bp3 -> fcn(); //调用D2::fcn()

【关键概念:名字查找与继承】

理解 C++中继承层次的关键在于理解怎样确定函数调用。确定函数调用遵循以下四个步骤:

1)首先确定进行函数调用的对象、引用或指针的静态类型

2)在该类中查找函数,假设找不到,就在直接基类中查找,如此循着类的继承链往上找,直到找到该函数或者查找完最后一个类。假设不能在类或其相关基类中找到该名字,则调用是错误的。

3)一旦找到了该名字,就进行常规类型检查,查看假设给定找到的定义,该函数调用是否合法。

4)假定函数调用合法,编译器就生成代码。假设函数是虚函数且通过引用或指针调用,则编译器生成代码以确定依据对象的动态类型执行哪个函数版本号,否则,编译器生成代码直接调用函数

//P502 习题15.24
Bulk_item bulk;
Item_base item(bulk);
Item_base *p = &bulk; /**由于net_price为虚函数
*对虚函数而言,仅仅能通过指针或引用进行动态绑定
*而通过对象调用虚函数,所调用到的总是该对象所属类型中定义的函数
*/
p -> net_price(10); //调用Bulk_item版本号的net_price
item.net_price(10); //调用Item_base版本号的net_price

C++ Primer 学习笔记_69_面向对象编程 --继承情况下的类作用域的更多相关文章

  1. C++学习笔记----4.4 继承情况下的类作用域嵌套

    引言: 在继承情况下,派生类的作用域嵌套在基类作用域中:如果不能在派生类作用域中确定名字,就在外围基类作用域中查找该名字的定义. 正是这种类作用域的层次嵌套使我们能够直接访问基类的成员,就好像这些成员 ...

  2. C++ Primer 学习笔记_67_面向对象编程 --转换与继承、复制控制与继承

    面向对象编程 --转换与继承.复制控制与继承 I.转换与继承 引言: 由于每一个派生类对象都包括一个基类部分,因此能够像使用基类对象一样在派生类对象上执行操作. 对于指针/引用,能够将派生类对象的指针 ...

  3. C++ Primer 学习笔记_72_面向对象编程 --句柄类与继承[续]

    面向对象编程 --句柄类与继承[续] 三.句柄的使用 使用Sales_item对象能够更easy地编写书店应用程序.代码将不必管理Item_base对象的指针,但仍然能够获得通过Sales_item对 ...

  4. javascript 学习笔记之面向对象编程(一):类的实现

    ~~想是一回事,做是一回事,写出来又是一回事~~一直以来,从事C++更多的是VC++多一些,从面向过程到面向对象的转变,让我对OO的编程思想有些偏爱,将一个客观存在的规律抽象出来总是让人比较兴奋,通过 ...

  5. javascript 学习笔记之面向对象编程(二):继承&多态

    ~~接上篇~~上一篇实现了类的实现以及类成员变量和方法的定义,下面我们来了解下面向对象中两个最重要的特性:继承和多态. 继承 js中同样可以实现类的继承这一面向对象特性,继承父类中的所有成员(变量和属 ...

  6. python自动化测试学习笔记-7面向对象编程,类,继承,实例变量,邮件

    面向对象编程(OOP)术语: class TestClass(object):   val1 = 100       def __init__(self):     self.val2 = 200   ...

  7. JavaSE学习笔记05面向对象编程01

    面向对象编程01 java的核心思想就是OOP 面向过程&面向对象 面向过程思想: 步骤清晰简单,第一步做什么,第二步做什么...... 面向过程适合处理一些较为简单的问题 面向对象思想: 物 ...

  8. python 学习笔记7 面向对象编程

    一.概述 面向过程:根据业务逻辑从上到下写垒代码 函数式:将某功能代码封装到函数中,日后便无需重复编写,仅调用函数即可 面向对象:对函数进行分类和封装,让开发"更快更好更强..." ...

  9. Spark学习笔记11面向对象编程

    面向对象编程   11.1 object类 11.1.1定义一个简单的类   11.1.2 field的getter与setter 定义类包含,定义类的field及方法.其格式如下 class Cla ...

随机推荐

  1. ubuntu apache2配置详解(含虚拟主机配置方法)

    ubuntu apache2配置详解(含虚拟主机配置方法) 在Windows下,Apache的配置文件通常只有一个,就是httpd.conf.但我在Ubuntu Linux上用apt-get inst ...

  2. [Python][自己写的杀病毒脚本]

    电脑里的HTML都插入了一段VB病毒代码..只能自己手动清除了..发现Python确实好用 import os import re; Root = ["H:"]; for root ...

  3. Entity Framework中datetime2 to datetime转换错误

    datetime2 to datetime 报错. 因为EF中,DATETIME类型默认是datetime2,数据库默认是datetime. 解决方案: 1.改数据库字段类型为datetime2 2. ...

  4. Android的ADB工具使用

    在SDK的Tools文件夹下包含着Android模拟器操作的重要命令ADB,ADB的全称为Android Debug Bridge,就是调试桥的作用,借助这个工具,我们可以管理设备或手机模拟器的状态  ...

  5. React-Native OpenGL体验一

    昨天初体验了一把SVG一个并不是多么复杂的动画,我在iOS模拟器上体验的是流畅的,但是在Android真机上体验,还是比较卡的. 下面来介绍一个OpenGL的第三方库: 下面是我运行的里面Demo的效 ...

  6. 关于用Java写的贪吃蛇游戏的一些感想

    学习Java有那么一个月了,兴趣还是挺高的.然而最近老师布置的一个迷宫问题,着实让我头疼了一两个礼拜,以至于身心疲惫,困扰不安.无奈,暂且先放下这个迷宫问题,写个简单点的贪吃蛇程序,以此来提高低落的情 ...

  7. 原创+部分引用啦:C# Winform界面中的分隔线问题

    C sharp 中Winform中的控件很多,一个小小的问题居然会绕上一个小弯子,做界面的时候, 你需要在界面上弄一条分隔线,把相关的功能分隔开来,结果原来在其它 IDE编辑器里很容易实现的这个功能, ...

  8. java web移植 遇到Project facet Java version 1.7 is not supported

    在移植eclipse项目时,如果遇到 "Project facet Java version 1.7 is not supported." 项目中的jdk1.7不支持.说明项目是其 ...

  9. (转)set集合的应用

    set集合的应用 python的set和其他语言类似, 是一个无序不重复元素集, 基本功能包括关系测试和消除重复元素. 集合对象还支持union(联合), intersection(交), diffe ...

  10. [原]C++程序示例:涉及到抽象类、继承…

    C++复习题. 有助于理解: .h与.cpp分离 基类.派生类.抽象类 多态.纯虚函数 对象指针.引用 派生类使用基类的构造函数 ... =============================== ...