C++ Primer笔记14_面向对象程序设计

OOP概述

面向对象程序设计(object-oriented programming)的核心思想是数据抽象继承动态绑定

1.继承

类的一种层次关系,通常在层次关系的根部有一个基类,其他类则直接或间接的继承基类而来。这些继承而来的类称为派生类

基类希望它的派生类自定义适合自身的版本的函数,基类就将函数声明为虚函数,加上virtual关键字。

2.动态绑定

通过动态绑定,可以使用同一段代码处理基类和子类对象。

在C++中,当我们使用基类的引用或指针调用一个虚函数时会发生动态绑定。有虚函数(virtual)才会发生动态绑定。

在C++中,基类必须将它的两种成员函数区分开,一种是希望派生类进行覆盖的函数;一种是希望派生类直接继承而不覆盖的函数。

当且仅当通过指针或引用对虚函数调用时会在运行时被解析。

3.派生类构造函数

派生类中含有从基类继承而来的成员,派生类必须使用基类的构造函数来初始化它的基类部分。

派生类可以访问基类的公有(public)成员和受保护(protected)成员。

4.纯虚函数

在函数体声明最后写=0,即可将一个函数声明为纯虚函数。

含有纯虚函数的类是抽象基类。抽象基类只负责定义接口,后续的其他类可以覆盖该接口。

不能直接创建一个抽象基类的对象(含有纯虚函数的类不能直接实例化)。

派生类如果没有定义继承而来的纯虚函数,则派生类也是抽象类,不能实例化。

5.类的作用域

每个类有自己的作用域,在这个作用域内我们定义类的成员。

当存在继承关系时,派生类作用域嵌套在基类作用域内,如果一个名字在派生类的作用域内无法解析,则编译器将继续在外层的基类中寻找该名字的定义。

派生类的成员将隐藏同名的基类成员。

6.隐藏、覆盖。重载的区别:

(覆盖即派生类自己实现了基类中同名的函数(虚函数),
函数覆盖发生在父类与子类之间,其函数名、参数类型、返回值类型必须同父类中的相对应被覆盖的函数严格一致,覆盖函数和被覆盖函数只有函数体不同)

只要基类在定义成员函数时已经声明了virtual关键字,在派生类实现的时候覆盖该函数时,virtual关键字可加可不加,不影响多态的实现。

容易与隐藏混淆:

隐藏是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

1) 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。

2) 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

比如,在下面的程序中:

#include <iostream.h>
class Base
{
public:
virtual void f(float x){ cout << "Base::f(float) " << x << endl; }
void g(float x){ cout << "Base::g(float) " << x << endl; }
void h(float x){ cout << "Base::h(float) " << x << endl; }
}; 

class Derived : public Base
{
public:
virtual void f(float x){ cout << "Derived::f(float) " << x << endl; }
void g(int x){ cout << "Derived::g(int) " << x << endl; }
void h(float x){ cout << "Derived::h(float) " << x << endl; }
}; 

通过分析可得:

1) 函数Derived::f(float)覆盖了Base::f(float)。

2) 函数Derived::g(int)隐藏了Base::g(float),注意,不是重载。

3) 函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。

7.例子

test.h

#ifndef _TEST_H
#define _TEST_H

using namespace std;
#include <string>

class Animal
{
public:
	Animal();
	Animal(int a);
	virtual ~Animal();

	virtual void shout();
	virtual void fight() = 0;
	void eat();
	void sleep();
protected:
	int age;
};

class Person : public Animal
{
public:
	Person();
	Person(int a, string n);
	~Person();

	virtual void shout();//Cover!
	//virtual void shout()const;//Hide!
	virtual void fight();//Cover
	void eat(string &n);//Hide  not override!
	void sleep();//Hide not Cover!
	void show();
private:
	//int age;//Hide!
	string name;
};

#endif

test.cpp

#include <iostream>
#include "test.h"

Animal::Animal():age(0)
{
	cout << "Animal 1" << endl;
}

Animal::Animal(int a):age(a)
{
	cout << "Animal 2" << endl;
}

Animal::~Animal()
{
	cout << "~ Animal " << endl;
}

void Animal::shout()
{
	cout << "Animal Shout!" << endl;
}

void Animal::eat()
{
	cout << "Animal eat!" << endl;
}

void Animal::sleep()
{
	cout << "Animal sleep!" << endl;
}

//----------------------------------------------------------------

Person::Person()
{
	cout << "Person 1" << endl;
}

Person::Person(int a, string n):Animal(a), name(n)//call Base class Counstruction Fun
{
	cout << "Person 2" << endl;
}

Person::~Person()
{
	cout << "~ Person " << endl;
}

void Person::shout()
{
	cout << "Person Shout!" << endl;
}

void Person::fight()
{
	cout << "Person fight!" << endl;
}

/*
void Person::shout()const
{
	cout << "const Person Shout!" << endl;
}
*/

void Person::show()
{
	cout << "I'm Person, Age: " << age << " Name: " << name << endl;
}

void Person::eat(string &n)
{
	cout << "Person: " << name <<  " eat!" << endl;
}

void Person::sleep()
{
	cout << "Person sleep!" << endl;
}

main.cpp

#include <iostream>
#include "test.h"

int main()
{
	Animal *p = new Person(20, "July");
	p->shout();//run time bind!
	p->fight();
	p->eat();
	p->sleep();
	delete p;//~ Animal
		 //when add virtual before ~Animal() will output : ~ Person ~ Animal
	//Animal *p = new Animal(20, "July"); the class has pure virtual functions cannot init!

//if shout() is declared as : void shout(); p->shout() output :Animal shout  not run time bind!

/*
	Person p(20, "Mike");
	Animal &refP = p;
	refP.shout();
*/

//	p->show();  Error Animal has no number of show

	return 0;
}

运行结果:

Animal 2

Person 2

Person Shout!

Person fight!

Animal eat!

Animal sleep!

~ Person

~ Animal

可以得出这样一个结论,被隐藏的函数是不能实现多态的。只有覆盖virtual函数才能。

另外,基类析构函数需要加virtual,以便于正确的调用基类与派生类的析构函数,如果不加,delete p只会输出:

~Animal,而不会调用派生类的析构函数。

C++ Primer笔记14_面向对象程序设计

时间: 2024-10-12 07:32:14

C++ Primer笔记14_面向对象程序设计的相关文章

JavaScript高级程序设计6学习笔记之面向对象程序设计

首先 JavaScript是没有类这个概念的 ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值,对象或者函数” 创建对象的方法: var person = new Object(); person.name = "niko"; person.age = 20; person.say = function(){alert(this.name);}; 上面的例子用对象字面量语法可以写为: var Person = { name : "niko"; a

C++ Primer 学习笔记_73_面向对象编程 --再谈文本查询示例

面向对象编程 --再谈文本查询示例 引言: 扩展第10.6节的文本查询应用程序,使我们的系统可以支持更复杂的查询. 为了说明问题,将用下面的简单小说来运行查询: 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

C++ Primer 学习笔记_74_面向对象编程 --再谈文本查询示例[续/习题]

面向对象编程 --再谈文本查询示例[续/习题] //P522 习题15.41 //1 in TextQuery.h #ifndef TEXTQUERY_H_INCLUDED #define TEXTQUERY_H_INCLUDED #include <iostream> #include <fstream> #include <sstream> #include <vector> #include <set> #include <map&g

C++ Primer 学习笔记_66_面向对象编程 --定义基类和派生类[续]

算法旨在用尽可能简单的思路解决问题,理解算法也应该是一个越看越简单的过程,当你看到算法里的一串概念,或者一大坨代码,第一感觉是复杂,此时不妨从例子入手,通过一个简单的例子,并编程实现,这个过程其实就可以理解清楚算法里的最重要的思想,之后扩展,对算法的引理或者更复杂的情况,对算法进行改进.最后,再考虑时间和空间复杂度的问题. 了解这个算法是源于在Network Alignment问题中,图论算法用得比较多,而对于alignment,特别是pairwise alignment, 又经常遇到maxim

初探C++Primer(15.面向对象程序设计)

最近在恶补OOP相关知识,很遗憾学校的课没选上,于是只能上网购进C++Primer一本,开始重学C++之旅... (壮哉我大ZJU,网购半天到货XDD) 学习路线 7.类->13.类设计者的工具->15.面向对象程序设计 总的来说,C++Primer的章节编排顺序是很合理的.有些教材习惯上来就讲虚函数,继承,恰恰缺乏对有关问题的引导,造成学完后半懂不懂的情况. 7.类 类的特性,成员函数,友元函数,构造函数简介 13.类设计者的工具 拷贝构造函数,拷贝赋值运算符,析构函数,内存管理类,合成拷贝

C++ Primer 学习笔记_34_面向对象编程(5)--虚函数与多态(二):纯虚函数、抽象类、虚析构函数、动态创建对象

C++ Primer 学习笔记_34_面向对象编程(5)--虚函数与多态(二):纯虚函数.抽象类.虚析构函数.动态创建对象 一.纯虚函数 1.虚函数是实现多态性的前提 需要在基类中定义共同的接口 接口要定义为虚函数 2.如果基类的接口没办法实现怎么办? 如形状类Shape 解决方法 将这些接口定义为纯虚函数 3.在基类中不能给出有意义的虚函数定义,这时可以把它声明成纯虚函数,把它的定义留给派生类来做 4.定义纯虚函数: class <类名> { virtual <类型> <函

C++ Primer 学习笔记_65_面向对象编程 --概述、定义基类和派生类

面向对象编程 --概述.定义基类和派生类 引言: 面向对象编程基于的三个基本概念:数据抽象.继承和动态绑定. 在C++中,用类进行数据抽象,用类派生从一个类继承另一个:派生类继承基类的成员.动态绑定使编译器能够在运行时决定是使用基类中定义的函数还是派生类中定义的函数. 继承和动态绑定在两个方面简化了我们的程序:[继承]能够容易地定义与其他类相似但又不相同的新类,[派生]能够更容易地编写忽略这些相似类型之间区别的程序. 面向对象编程:概述 面向对象编程的关键思想是多态性(polymorphism)

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

面向对象编程 --句柄类与继承[续] 三.句柄的使用 使用Sales_item对象能够更easy地编写书店应用程序.代码将不必管理Item_base对象的指针,但仍然能够获得通过Sales_item对象进行的调用的虚行为. 1.比較两个Sales_item对象 在编写函数计算销售总数之前,须要定义比較Sales_item对象的方法.要用Sales_item作为关联容器的keyword,必须能够比較它们.关联容器默认使用keyword类型的小于操作符,可是假设给Sales_item定义小于操作符,

C++ Primer 学习笔记33_面向对象编程(4)--虚函数与多态(一):多态、派生类重定义、虚函数的访问、 . 和-&gt;的区别、虚析构函数、object slicing与虚函数

C++ Primer学习笔记33_面向对象编程(4)--虚函数与多态(一):多态.派生类重定义.虚函数的访问. . 和->的区别.虚析构函数.object slicing与虚函数 一.多态 多态可以简单地概括为"一个接口,多种方法",前面讲过的重载就是一种简单的多态,一个函数名(调用接口)对应着几个不同的函数原型(方法). 更通俗的说,多态行是指同一个操作作用于不同的对象就会产生不同的响应.或者说,多态性是指发出同样的消息被不同类型的对象接收时有可能导致完全不同的行为. 多态行分