面向对象编程

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

引言

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

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

    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. Rainmeter 雨滴桌面 主题分享

    说明 先安装主程序 Rainmeter-3.1.exe,然后安装 Techzero_1.0.rmskin,打开主题管理应用主题就可以. 下载 http://pan.baidu.com/s/1i3zI3 ...

  2. 【DataStructure】Description and usage of queue

    [Description] A queue is a collection that implements the first-in-first-out protocal. This means th ...

  3. Vs2012于Linux应用程序开发(2):图案

    1.1     代码提示功能 在vs中开发中,Visual Assist是一个很优秀的插件,我们仍然能够使用它进行代码的分析,但它仅仅能支持vcxprojproject,因而我们选择对vcxproj的 ...

  4. 常用Git命令汇总

    常用Git命令汇总 跟着R哥来到了新公司(一个从硬件向互联网转型中的公司),新公司以前的代码基本是使用SVN做版本控制,甚至有些代码没有做版本控制,所以R哥叫HG做了一次Git分享,准备把公司所有的代 ...

  5. ibatisnet框架使用说明

    ibatis配置文件主要包括三个 sqlmap.config,providers.config,database.config,注意所有文件生成操作都为嵌入的资源.其中database.config主 ...

  6. PHP学习笔记二十四【Get Set】

    <?php Class Person{ private $n1; private $n2; private $n3; //使用__set方法来管理所有的属性 public function __ ...

  7. 前端学习书籍大全 包含PDF地址

    JavaScript类: javascript高级程序设计 pdf下载 ---->教程 javascript权威指南 pdf下载  ---->教程 javascript基础教程 pdf下载 ...

  8. oracle的nvl和sql server的isnull函数

    最近公司在做Oracle数据库相关产品,在这里作以小结: ISNULL()函数 语法     ISNULL ( check_expression , replacement_value)  参数    ...

  9. C语言基本概念

    1. 标准C语言 C语言诞生于20世纪70年代,年龄比我们自己还要大,期间产生了很多标准,但是各种编译器对标准的支持不尽相同. ANSI C是使用的最广泛的一个标准,也是第一个正式标准,被称为“标准C ...

  10. angularJS常用命令

    首先使用命令行进入你要编辑的web项目目录下: (一)编译浏览项目 1:grunt build    对web项目编译: 2:grunt server    装载(在浏览器上查看页面): 3:ctrl ...