CH15 面向对象程序设计
面向对象程序设计是基于三个基本概念的:数据抽象、继承和多态。
第7章介绍了数据抽象的知识,简单来说,C++通过定义自己的数据类型来实现数据抽象。
数据抽象是一种依赖于接口和实现分离的编程技术:类的设计者必须关心类是如何实现的,但使用该类的程序员不必了解这些细节。
封装是一项将低层次的元素组合起来形成新的、高层次实体的技术!函数和类都是封装的具体形式。其中类代表若干成员的聚集。大多数(设计良好的)类类型隐藏了实现该类型的成员。
一个简单的PersonInfo类,包含类成员、成员函数、构造函数
#include <string> using namespace std;
class PersonInfo {
public:
PersonInfo();
PersonInfo(string& name):strname(name),age(){}
string getName() { return strname; }
int getAge() { return age; } private:
string strname;
int age;
};
2.访问控制符
程序所有部分均可访问public成员
只有在类内部才能访问类的private成员,即类的private成员对外是不可见的。
3.成员函数可以被重载,上面的PersonInfo类就定义了两个版本的构造函数,被重载的成员函数之间的参数数量和类型不能完全相同,因为重载的成员函数被调用时会根据参数数量和类型进行匹配。重载构造函数的的原理和调用和普通成员函数的重载是一样的。
4.类的声明和定义
一个源文件中一个类只能定义一次。
可以只声明一个类,而先不定义它,一般,这种情况是有关联的类
class StrBlobPtr;
class StrBlob {
friend class StrBlobPtr;
public:
StrBlob();
StrBlob(initializer_list<string>il){}
//其它成员函数
StrBlobPtr begin();
StrBlobPtr end();
private:
//私有成员
};
但是只声明没有定义的类,不能使用,即:不能定义该类的对象,因为生命之后,定义之前,StrBlobPtr是不完全的类,并不知道这个类有哪些成员。不完全类型只能用于定义指向该类型的指针及引用,或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数。,如上面的begin()和end()函数。
5.类对象
定义一个类后,就可以定义该类的对象,可以认为对象是类的实体,类通过对象实现一系列操作。定义对象时,会为其分配内存空间,定义类型时并没有进行存储分配。
//两种定义对象的方式都可以,一般是第一种
PersonInfo a;
class PersonInfo b;
6.this指针
成员函数有一个隐形形参,指向该类对象的一个隐形形参,即this指针,this的形参是由编译器定义的,与成员的当前对象绑定在一起
class Screen {
public:
typedef string::size_type pos;//此处类为自己定义了一个局部类型,使用typedef为string::size_tyoe 定义了个别名
//为类做了一个更好的抽象,
Screen() = default;
Screen(pos ht, pos wd,char c):height(ht),width(wd),contents(ht*wd,c){}//define constructor and initizalize
char get()const { return contents[cursor]; }
inline char get(pos ht, pos wd)const;//declare operator member function
Screen &move(pos r, pos c);//Note:此处返回的是引用,该引用指向执行操作的那个对象
//add some function to set the char in the position pointed by the cursor
//和move()一样,set成员的返回值是调用set对象的引用,返回引用的函数是左值的
Screen &set(char);
Screen &set(pos, pos, char);
private:
pos cursor = ;
pos height = , width = ;
string contents;
}; //define move()
//勒种一些规模较小是函数适合被声明为内联函数,类内部的默认自动是inlien 的。
inline Screen &Screen::move(pos r,pos c)
{//函数返回调用自己的对象,使用this 来访问该对象
pos row = r*width;//cacculate the position of the row
cursor = row + c;//move the cursou to the pointed column in the row
return *this;//return the object } char Screen::get(pos r, pos c)const
{
pos row = r*width;
return contents[row + c];
}
inline Screen& Screen::set(char c)
{
contents[cursor] = c;
return *this;//返回的是this指向的对象,将this对象作为左值返回
}
//重载的set()函数
inline Screen& Screen::set(pos r, pos col, char ch)
{
contents[r*width + col] = ch;
return *this;
}
int main()
{ Screen myScreen; myScreen.move(, ).set('*');//这句相当于下面两句,
myScreen.move(, );
myScreen.set('*');//,
system("pause");
return ;
}
Note: set()成员不能定义为const 的,因为set()返回的是调用set()对象的引用,返回的是左值,而左值必须是可以修改值的,不能是const 的
下面我们定义一个函数从const 成员返回*this
在普通的非const成员函数中,this的类型是一个指向类类型的const指针。可以改变this所指向的值,但不能改变this所保存的地址。在const成员函数中,this的类型是一个指向const类类型对象的const指针。既不能改变this所指向的对象,也不能改变this所保存的地址。
不能从const成员函数返回指向类对象的普通引用。const成 员函数只能返回*this作为一个 const引用。
定义一个display 成员打印出Screen的内容,只是显示Screen的内容不需要改变,因此将const定义为一个const成员,此时this 是一个指向const的指针,*this 就是const对象 ,所以display的返回类型应该是const Screen&,但是,如 如果令display返回一个const引用,则我们不能把display嵌入到一组动作的序列中
此时若用display返回的对象调用set()成员,就会发生错误,,上面说了set成员为什么不能设为常量、
所以下面调用是错误的
myScreen.dipaly(cout).set('#');
Note:一个const成员函数如果以引用方式返回*this,那么它的返回类型将是常量引用。所以定义一个非const的display成员
此处注意,定义了一个小的私有成员,do_play(),看似没简化操作,实则,这样的小程序段是非常有用的,关于这样公共代码使用私有功能函数的建议,课本P248有详细说明
class Screen {
public:
//其它以定义的public成员
const Screen &dipaly(ostream& os)const { do_display(os); return *this; }
Screen& dispaly(ostream&os) { do_display(os); return *this; }
private:
//其它以定义的prvate成员
void do_display (ostream &os)const { os << contents; }
};
7.类作用域
名字查找与类的作用域
类的定义分两步
1.编译成员的声明
2.直到类全部可见后才编译函数体
编译器处理完类中的全部声明后才会处理成员函数的定义
OOP概述
1.OOP核心思想是数据抽象,继承和动态绑定,使用数据抽象可以将类的接口与实现分离,使用继承可以定义相似的类型并对其相似关系建模;使用动态绑定,可以在一定程度上忽略相似类型的区别,以统一方式使用它们的对象
2.定义基类和派生类
其中Quote类是基类,Bulk_quote类是派生类。派生类的构造函数必须重新写过,不能继承。(因为毕竟两个类的类名都不一样,不可能构造函数继承)只继承其他的成员函数和成员变量。
派生类可以覆盖基类的虚函数,但是也可以选择不覆盖(即直接使用父类的函数版本)
每个类控制它自己的成员的初始化过程,派生类初始化成员时,对于从父类继承来的成员必须使用父类的构造函数进行初始化,对于自己新增加的成员,调用自己的构造函数初始化
Note:基类通常都应该定义一个虚析构函数,即使该函数不执行任何实际操作也是如此
#include <string>
//#include <cstddef>
#include <iostream>
using namespace std;
//基类
class Quote {
public:
//将默认构造函数声明为default,则要求编译器为嗯合成默认构造函数
Quote() = default;
//重载constructor
Quote(const string &book,double sales_price):bookNo(book),price(sales_price){}
string isbn()const { return bookNo; }
//返回某种书的销售总额
//派生类会根据不同情形重写该函数,所以定义为虚函数
virtual double net_price(size_t n)const { return n*price; }
virtual ~Quote() = default;
private:
string bookNo;
protected:
double price = 0.0;
};
//派生类,从基类Quote那里继承了isbn()和bookNo和price,重新定义了net_price()函数
//新增加了min_qty 和discount 成员
class Bulk_quote:public Quote {
public:
Bulk_quote() = default;
Bulk_quote(const string&, double, size_t, double);
double net_price(size_t)const override;
private:
size_t min_qty = ;//可以使用指定折扣的最低数量
double discount = 0.0;//折扣
};
Bulk_quote::Bulk_quote(const string& book, double p, size_t qty,double disc):
Quote(book,p),min_qty(qty),discount(disc)
{
//注意初始化列表中对成员bookNo 和price的初始化,不是像之前那样bookNo(book),price(p)
//进行初始化,而是调用基类的构造函数进行初始化
}
double Bulk_quote::net_price(size_t cnt)const
{
if (cnt >= min_qty)
return cnt*price*( - discount);
else
return cnt*price;
}
//dynamic binding
double print_total(ostream &os, const Quote &item, size_t n)
{
//根据传入的item形参的对象类型调用net_price()
double ret = item.net_price(n);
os << "ISBN: " << item.isbn() <<'\t'<< "#sold: " << n << " total due: "
<< ret << endl;
return ret;
}
int main()
{
Quote item("978-7-121-31092-8", );//调用基类构造函数
Bulk_quote bulk("978-7-121-31092-8", , , 0.2);//call the constructor of sub class
//bookA.net_price(20);
//Quote item;//基类对象
//Bulk_quote bulk;//派生类对象 Quote *p =&item;
p->net_price();//dynamic binding
// Quote base;
// Bulk_quote subcls;
print_total(cout, item, );
print_total(cout, bulk, );
system("pause");
return ;
}
继承与静态成员
如果基类有一个静态成员,那么基类和所有派生类(包括派生类的派生类)都共同拥有这仅有的一个静态成员。并且,对该成员的访问控制与非静态成员的访问控制方式一样,即,如果是private的,则派生类无权访问,,如果是可访问的,则既可以通过基类使用它,也可通过派生类使用它。
class Base {
public:
static void statmem();//static member
};
class Derived :public Base {
void f(const Derived&);
}; void Derived::f(const Derived& derived_obj)
{
Base::statmem();//correct:difine in Base
Derived::statmem();//correct: Derived derived it from Base
derived_obj.statmem();//correct:visit it throgh the object of Derived
statmem();//correct: visit it through object of pointed by this
}
类型转换与继承
可以将基类的指针或引用绑定到派生类对象上(上面Quote例子中的63,64行):当使用基类引用(或指针)时,实际上我们并不清楚该引用(指针)所绑定的真实类型,该对象可能是基类的对象,也可能是派生类对象。
静态类型在编译时总是已知的,而动态类型的对象直到运行时才可知。
3.虚函数
我们必须为每一个虚函数都提供定义,不管它是否被用到了,因为编译器无法确定到底会使用哪个虚函数。
OOP的核心思想就是多态(polymorphism):具有继承关系的多个类型称为多态类型。
派生类中的虚函数,当在派生类中覆盖了基类的某个虚函数,可以使用virtual关键字指明,即使不这么做,C++也是默认virtual的。
一个派生类的函数如果覆盖了某个继承而来的虚函数,则它的形参类型必须与被它覆盖的基类函数完全一致。
同样,派生类中虚函数的返回类型也必须与基类函数匹配,当类的虚函数返回类型是类本身的指针或引用时例外。
只有虚函数才能被覆盖
基类的虚函数默认实参最好与派生类的虚函数默认实参一致。
因为如果通过基类的引用或指针调用函数,则将使用基类的默认实参版本,但是该函数的实现是动态绑定的,即可能是基类的函数也可能是派生类的函数。
struct B {
virtual void f1(int)const;
virtual void f2();
void f3();
};
struct D :B {
void f1(int)const;//correct:f1matched with f1 in class B
void f2(int)override;//incorrect:theris no f2(int) in class B
void f3()override;//incorrect:f3 is not a virtual function
void f4()override;//incorrect:there is no function named f4 in class B
};
4.抽象基类
纯虚函数:纯虚函数不需要定义,在声明时写上=0即可,智能出现在虚函数声明处的语句。
含有(或未经覆盖而直接继承)纯虚函数的类为抽象类,抽象基类负责定义借口,而后续其它类可以覆盖接口。Note:不能创建抽象基类的对象
如将之前的net_price()定义为虚函数
class Dis_quote :public Quote{
public:
Dis_quote() = default;
Dis_quote(const string& book, double p, size_t qty, double disc) :
Quote(book, p), quanty(qty), discount(disc)
{ } double net_price(size_t)const = ;//pure virtual
protected:
size_t quanty = ;
double discount = 0.0;
};
Disc_quote discounted;//incorrect :can not define the object for abstract base class
Disc_quote 的派生类必须定义自己的net_price(),否则仍是抽象基类
现在可以重新定义Bulk_quote,让它继承Disc_quote而不是直接继承Quote
class Bulk_quote :public Disc_quote {
Bulk_quote() = default;
Bulk_quote(const string& book,double p,size_t qty,double disc):
Disc_quote(book,p,qty,disc){}
//overeide net_price() in class base
double net_price(size_t)const override;
};
访问控制与继承
对于访问控制,记住基类的私有成员,不管派生类的继承方式是什么,都是不可见的。派生类可以改变基类中的成员的访问权限,也只限于可访问的成员。
6. 继承中的类作用域
当存在继承关系时,派生类的作用域嵌套在其基类中的作用域内,如果一个名字在派生类的作用域内是无法解析的,则编译器将继续在外层的基类作用域寻找该名字,并且是在编译时进行名字查找;当派生类
的成员名字与基类成员名字冲时,此时定义在内层作用域(派生类)的名字将隐藏定义在外层作用域(基类)的名字
#include <iostream> struct Base {
Base():mem(){}
protected:
int mem; };
struct Derived :Base {
Derived(int i) :mem(i) {}//用i初始化mem,
//Base::mem 进行默认初始化
int get_mem() { return mem;}//返回Derived::mem
protected:
int mem;//与基类成员名形同,会隐藏基类中的mem
};
int main()
{
Derived d();
std::cout << d.get_mem() << std::endl;//打印结果42,说明调用get_mem()
//对mem的解析结果是定义在Derived中的
system("pause");
return ; }
CH15 面向对象程序设计的更多相关文章
- [.net 面向对象程序设计深入](0) 开篇
[.net 面向对象程序设计深入](0)开篇 [.net 面向对象编程基础]和 [.net 面向对象程序设计进阶]在15年底写完了,群里也加进来不少热爱学习的小伙伴.让我深切感受到在这个 ...
- [.net 面向对象程序设计进阶] (1) 开篇
[.net 面向对象程序设计进阶] (1) 开篇 上一系列文章<.net 面向对象编程基础>写完后,很多小伙伴们希望我有时间再写一点进阶的文章,于是有了这个系列文章.这一系列的文章中, 对 ...
- [.net 面向对象程序设计深入](6).NET MVC 6 —— 模型、视图、控制器、路由等的基本操作
[.net 面向对象程序设计深入](6).NET MVC 6 —— 模型.视图.控制器.路由等的基本操作 1. 使用Visual Studio 2015创建Web App (1)文件>新建> ...
- [.net 面向对象程序设计深入](5)MVC 6 —— 构建跨平台.NET开发环境(Windows/Mac OS X/Linux)
[.net 面向对象程序设计深入](5)MVC 6 —— 构建跨平台.NET开发环境(Windows/Mac OS X/Linux) 1.关于跨平台 上篇中介绍了MVC的发展历程,说到ASP.NET ...
- [.net 面向对象程序设计深入](4)MVC 6 —— 谈谈MVC的版本变迁及新版本6.0发展方向
[.net 面向对象程序设计深入](4)MVC 6 ——谈谈MVC的版本变迁及新版本6.0发展方向 1.关于MVC 在本篇中不再详细介绍MVC的基础概念,这些东西百度要比我写的全面多了,MVC从1.0 ...
- [.net 面向对象程序设计深入](3)UML——在Visual Studio 2013/2015中设计UML活动图
[.net 面向对象程序设计深入](3)UML——在Visual Studio 2013/2015中设计UML活动图 1.活动图简介 定义:是阐明了业务用例实现的工作流程. 业务工作流程说明了业务为向 ...
- [.net 面向对象程序设计深入](2)UML——在Visual Studio 2013/2015中设计UML用例图
[.net 面向对象程序设计深入](2)UML——在Visual Studio 2013/2015中设计UML用例图 1.用例图简介 定义:用例图主要用来描述“用户.需求.系统功能单元”之间的关系. ...
- [.net 面向对象程序设计深入](1)UML——在Visual Studio 2013/2015中设计UML类图
[.net 面向对象程序设计深入](1)UML——在Visual Studio 2013/2015中设计UML类图 1.UML简介 Unified Modeling Language (UML)又称统 ...
- [.net 面向对象程序设计进阶] (28) 结束语——告别2015
[.net 面向对象程序设计进阶] (28) 结束语——告别2015 <.net面向对象程序设计进阶>这一系列文章写了太长的时间了,大概有半年没写,在年底又一口气写了好几篇.在整个过程中目 ...
随机推荐
- hadoop StandAlone环境搭建
1.准备一台服务器 192.168.100.100 2.提前安装jdk 3.hadoop运行服务 NameNode 192.168.100.100 SecondaryNameNo ...
- web.xml中的welcome-file-list标签作用
welcome-file-list是一个配置在web.xml中的一个欢迎页,用于当用户在url中输入项目名称或者输入web容器url(如http://localhost:8080/)时直接跳转的页面. ...
- 获取class对象的三种方法以及通过Class对象获取某个类中变量,方法,访问成员
public class ReflexAndClass { public static void main(String[] args) throws Exception { /** * 获取Clas ...
- eclipse 热部署
参考: http://blog.sina.com.cn/s/blog_be8b002e0101koql.html
- Android Studio如何更新support repository
转自: http://blog.csdn.net/sinat_29696083/article/details/70256377 刚进新公司,熟悉新业务中.老大叫我看看关于ConstraintLayo ...
- QRious入门
qrious是一款基于HTML5 Canvas的纯JS二维码生成插件.通过qrious.js可以快速生成各种二维码,你可以控制二维码的尺寸颜色,还可以将生成的二维码进行Base64编码. qrious ...
- js中的关键字与保留字
关键字就是指:js中用到的单词,比如var : function: 保留字是指:js以后可能会发展成为关键字的,先保留起来不让你用.比如class,要是给一个对象添加class:obj.classNa ...
- iOS项目开发日常之创建文件(协议、类、分类、扩展)
iOS项目开发过程中,是以不断创建文件的形式进行着的. 创建得比较频繁的文件类型是: 这两个类型中创建的文件有:子类.分类.扩展.协议四种文件,如下: 这四类文件是频繁创建的,我们来看一下各自分 ...
- 投资人分享答疑----HHR计划----以太直播课第三课
分享大纲:(祥峰投资) 一,投资人会看什么: 1,赛道定位:“生意”还是“独角兽-to be”? 2,如何退出?上市还是收购? 3, 团队能力,愿景力 4,壁垒:数据和价值 5,价格 二,融资需要准 ...
- 【原】linux设置网络延迟/丢包操作
1.tc方式 * 清除设备策略:tc qdisc del root dev eth2 2>/dev/null* 设置设备策略:tc qdisc add dev eth0 root netem l ...