200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > 【侯捷】C++面向对象高级编程(上)

【侯捷】C++面向对象高级编程(上)

时间:2024-06-07 01:37:24

相关推荐

【侯捷】C++面向对象高级编程(上)

一、C++编程简介

1.1 目标

培养正规的、大气的编程习惯以良好的方式编写C++ class 【Object Based(基于对象)】 class without pointer members Complex class with pointer members String 学习Classes 之间的关系 【Object Oriented(面向对象)】 继承(inheritance)复合(composition)委托(delegation)

1.2 C++ 的历史

B语言(1969)

C语言(1972)

C++ 语言 (1983)

(new C -> C with Class -> C++)

Java 语言

C# 语言

1.3 C++ 演化

C++ 98 (1.0)C++ 03 (TR1, Technical Report 1)C++ 11 (2.0)C++ 14

C++ = C++语言 + C++标准库

推荐书籍:

《C++ Primer》《THE C++ PROGRAMMING LANGUAGE》《Effective C++》《The C++ Standard Library》《STL 源码剖析》

二、头文件与类声明

2.1 C vs. C++, 关于数据和函数

C语言:语言没有关键,数据必须是全局的 C++:增强版struct;数据和函数包在一起;数据之间不会混杂

2.2 C++,关于数据和函数

2.3 Object Bases(基于对象) vs. Object Oriented(面向对象)

Object Based:面向的是单一 class 的设计Object Oriented:面对的是多重 classes 的设计,classes 和 classes 之间的关系

Classes 的两个经典分类:

Class without pointer member(s):ComplexClass with pointer member(s):String

2.4 C++ programs 代码基本形式

延伸文件名(extension file name)即文件扩展名不一定是.h.cpp,也可能是.hpp或其它或甚至无延伸名。

2.5 Header(头文件)的布局

2.6 class的声明(declaration)

2.7 class template(模板)简介

实部和虚部的类型在使用的时候才确定。

2.8 inline(内联)函数

即使写了inline关键字,但是函数最终是否能成为inline,由编译器决定。

2.9 access level(访问级别)

所有的数据都放在private,如果函数是要被外界调用的就放在public,若不打算被外界调用则放在private

class body内的publicprivate可以交错地出现,并不是只能分为两段,比如:

class complex {private:...public:...private:...public:...};

三、构造函数

3.1 constructor(ctor,构造函数)

构造函数没有返回值类型;构造函数的函数名必须和类名一致;构造函数的参数可以有默认值;只有构造函数才有初始列表,使用初始列表进行数据的初始化比使用赋值进行值的设定的效率更高;本类中没有指针变量,一般这种没有指针的类多半不需要写析构函数。

3.2 构造函数可以重载(overloading)

同名函数编译后的实际名称是不同的,取决于编译器;上图中的②的存在时不行的,会与 ① 的构造函数发生冲突,右侧构造对象的是时候不知道调用哪个

3.3 constructor(ctors,构造函数)被放在 private 区

构造函数放在private区后,是不能被外界直接调用的。单例模式:有将ctors放在private区的需求

3.4 const member functions(常量成员函数)

const修饰成员函数,要在如上图的位置;不会改变数据内容的函数加上constconst对象一定要调用const方法。

四、参数传递与返回值

4.1 参数传递:pass by valuevs.pass by reference(to const)

数据所占内存很大(32位机器上大于4字节)的时候,传地址(32位机器上占4字节)的速度更快;而引用在底层就是指针,传引用就相当于传指针;参数传递的时候尽量传递引用;如果传递的参数不希望被函数修改,那么就传const引用

4.2 返回值传递:return by value vs. return by reference (to const)

4.3 friend(友元)

4.4 相同 class 的各个 objects 互为 friends(友元)

4.5 class body 外的各种定义(definitions)

什么情况下可以 pass by reference什么情况下可以 return by reference

运算结果放到一个不存在的空间(变量)中,则不能返回引用,除此之外,都可以返回引用。

五、操作符重载与临时对象

5.1 operator overloading (操作符重载-1,成员函数)this

所有的成员函数都有一个隐藏的参数this,指向调用者。_doapl,do assignment plus,标准库的代码

5.2 return by reference语法分析

传递者无需知道接受者是以reference形式接收

_doapl函数返回的是object(指针指向的内容),是个值,但是接收使用的是complex&引用;c2 += c1,其中c1就是传递者。

5.3 class body 之外的重定义

5.4 operator overloading(操作符重载-2, 非成员函数)无this

三个全域函数对应的是三种不同的调用情况:复数 + 复数,复数 + 实数,实数 + 复数。 操作符重载,有两种选择:成员函数或者全局函数,但是<<只能写成全局函数;操作符重载是作用在左边的参数上,成员函数默认有个this参数,如果写成成员函数,使用的时候就变成了c1 << cout,不符合一般的使用习惯,因此<<写成全局函数;ostream& operator<<(ostream& os, const complex& x)函数中的第一个参数os不能用const,因为数据往os里丢数据的时候一直都在改变os;ostream& operator<<(ostream& os, const complex& x)函数的返回类型不能是void,因为可能以这种方式使用:cout << c1 << conj(c1),如果返回的是void,则不能连用<<

5.5 temp object临时对象

这三个函数得到的结果都是目前不存在的,需要新创建,而且返回后可能被外界使用,而这个新创建的对象返回后需要销毁,所以不能返回引用,而一定要返回值。typename();用于创建临时对象,比如complex(x + real(y), imag(y));

{int(7);complex c1(2, 1);complex c2;complex(); //临时对象complex(4, 5); //临时对象//到这里的时候,临时对象的生命到此结束cout << complex(2);}

5.6 小结

设计一个class要注意的事项:

构造函数使用初始列表考虑函数是否要加const参数的传递尽量使用引用,考虑是否加const考虑清楚返回值的类型是值还是引用数据放在private函数绝大部分放在public

六、复习Complex类的实现过程

下面的程序是来自标准库的函数,去除了某些部分。

/*************************************************************************> File Name: complex.h> Author: Maureen > Mail: Maureen@ > Created Time: 二 11/ 9 17:58:17 ************************************************************************/#ifndef _COMPLEX_H#define _COMPLEX_Hclass complex {public:complex(double r = 0, double i = 0) : re(r), im(i) {}complex& operator+=(const complex&);//函数内部没有修改成员数据,所以加上constdouble real() const {return re; }double imag() const {return im; }private:double re, im;friend complex& __doapl(complex*, const complex&);};inline complex& __doapl(complex* ths, const complex& r) {ths->re += r.re;ths->im += r.im;return *ths;}inline complex& complex::operator+=(const complex& r) {return __doapl(this, r);}//不将operator+设计为类的成员函数,就是因为加法的左右值类型是多样变化的inline complex operator+(const complex& x, const complex& y) {//复数 + 复数return complex(real(x) + real(y), imag(x) + imag(y)); //返回的是local object,所以返回值类型必须是值}inline complex operator+(const complex& x, double y) {//复数+实数return complex(real(x) + y, imag(x));}inline complex operator+(double x, const complex& y) {//实数+复数return complex(x + real(y), imag(y));}#include <iostream.h>ostream& operator<<(ostream& os, const complex& x) {//返回值为引用考虑了“连用”return os << '(' << real(x) << ',' << imag(x) << ')';}#endif

七、三大函数:拷贝构造,拷贝赋值,析构

7.1 class的两个经典分类

若类中有指针,如果只是简单的拷贝,那就是指针拷贝,两个指针指向的是同一个空间,所以不能直接拷贝。如果类中有指针,一定要自己写拷贝构造

7.2 String class

7.3 Big Three, 三个特殊函数

此处使用char* m_data,就是在使用的时候才指向字符串,就是动态分配的方式;get_c_str()函数没有修改指针,所以加上const

7.3.1 ctor和dtor(构造函数和析构函数)

class中有指针,多半就要进行动态分配;做了动态分配,在的对象死亡后,就要释放掉分配的空间;s1、s2、p这三个字符串都会调用String(const char* str = 0)这个构造函数,离开作用域之后都会调用析构函数。

7.3.2 class with pointer members 必须有 copy ctor和 copy op=

使用default copy ctordefault op=浅拷贝,是需要避免的。

7.3.3 copy ctor(拷贝构造函数)

此处的

String s2(s1);String s2 = s1;

意思是一样的,都会调用拷贝构造函数。拷贝构造函数里是深拷贝

7.3.4 copy assignment operator(拷贝赋值函数)

①②③的流程:

一定要在operator=中检查是否 selft assignment

如果不做 self assignment,就会造成如下问题:

检查自我赋值(self assignment)不只是为了效率,还是为了正确性。

7.3.5 output函数

operator<<不能写成成员函数,否则cout和要输出的内容就会相反。

八、堆、栈与内存管理

8.1 所谓 stack(栈),所谓 heap(堆)

8.2 stack objects的生命期

「自动」清理即析构函数会被自动调用。

8.3 static local objects的生命期

class Complex {... };...{static Complex c2(1,2);}

c2便是所谓static object,其生命在作用域(scope)结束之后仍然存在,直到整个程序结束。

8.4 global objects的生命期

class Complex {... };...Complex c3(1,2);int main() {...}

c3便是所谓global object,其生命在整个程序结束之后才结束。你也可以把它视为一种static object,其作用域是「整个程序」。

8.5 heap objects的生命周期

8.6 new:先分配memory,再调用ctor

pc是分配的内存的起始位置,也就是this

8.7 delete:先调用dtor,再释放memory

8.8 new:先分配memory,再调用ctor

8.9 delete:先调用dtor,再释放memory

①将字符串里面动态分配的内存销毁;至于字符串本身,只是一个指针;②销毁字符串本身,本身是个指针。

8.10 动态分配所得的内存块(memory block),in VC

图中为内存结构,在VC中,分配的内存空间大小都是16的倍数,第一个图中是调试模式下, 每个格表示4Bytes,Complex对象占8个字节,计算得到的结果是52Bytes,不是16的倍数,所以补充12个Bytes的填补字节,凑到64。开头和结尾的00000041,其中的4是 64的16进制,1表示这块空间已经给出去了,0表示这块空间空闲。第二个图为非调试模式,对象所占的内存空间。

8.11 动态分配的array

8.12 array new 一定要搭配 array delete

错误用法delete p;导致出现的内存泄漏是String类中的指针指向的空间,而非指针本身。

如果是Complex数组对象,因为其中没有指针,所以就算是使用delete p;也是没有问题的。

但是为了万无一失,如果使用了array new一定要搭配array delete

九、复习String类的实现过程

/*************************************************************************> File Name: String.h> Author: Maureen> Mail: Maureen@> Created Time: 四 11/11 16:34:55 ************************************************************************/#ifndef _STRING_H#define _STRING_Hclass String {public:String(const char* cstr = 0);String(const String& str);String& operator=(const String& str);~String();//函数没有更改m_data,所以加上constchar* get_c_str() const {return m_data; }private://动态分配的方式保存字符串char* m_data;};#include <string.h>//建议编译器将函数作为内联函数inlineString::String(const char* cstr = 0) {if (cstr) {m_data = new char[strlen(cstr) + 1];strcpy(m_data, cstr);} else {//未指定初值m_data = new char[1];*m_data = '\0';}}inlineString::~String() {//array new搭配array deletedelete[] m_data;}//拷贝构造inlineString::String(const String& str) {m_data = new char[strlen(str.m_data) + 1];strcpy(m_data, str.m_data);}//拷贝赋值函数inlineString& String::operator=(const String& str) {//Type&,此处的&表示引用//考虑自我赋值,必须做if (this == &str) //&obj,此处的&表示取地址,得到obj的指针return *this;//先将本身的东西清除,再分配足够多的空间容纳新的字符串delete[] m_data;m_data = new char[strlen(str.m_data) + 1];strcpy(m_data, str.m_data);return *this; //考虑到连用,所以要返回引用}#endif

十、扩展补充:类模板、函数模板及其它

10.1 进一步补充:static

static函数通过传入的this指针找到要处理的对象;static数据只有一份;static函数没有this指针;只能处理静态数据。 静态数据要在类外进行定义。

10.2 进一步补充:把ctors放在private区

创建出来的类只能有一个对象 。

第一种写法:

缺点:如果不需要A的实例,但是也创建了。

第二种写法:

函数中的static对象,只有当调用了该函数的时候,该对象才会创建;该函数执行后,这个static对象仍然存在。这种写法更好。

10.3 进一步补充:cout

10.4 进一步补充:class template,类模板

templatetypename都是关键字,template<typename T>告诉编译器当前类型还没有绑定;用法:complex<double>,编译器会将代码中的T替换为double;不同的绑定类型会生成不同的代码,模板会造成代码的膨胀,但是这并不是缺点,而是必要的。

10.5 进一步补充:function template,函数模板

此处的template <class T>中的classtypename是一样的;使用模板函数的时候不用指出类型,因为编译器会进行引数推导

10.6 进一步补充:namespace

10.7 更多细节与深入

十一、组合与继承

Object Oriented Programming,Object Oriented Design

OOP,OOD

Inheritance(继承)Composition(复合)Delegation(委托)

11.1 Composition(复合),表示has-a

queue中有一个deque<T>类型的cqueue是容器,它容纳了dequedeque中可能有很多接口,但是queue只开放这几个,queue的所有功能都是通过deque来完成的,这种模式叫做Adapter模式;该例子是个特例,并不是所有的Composition都是这样;有了Container就有Componenet,生命是一起的;复合就是一种结构中包含其他类型/结构。

内存角度解释Composition:

queue中的c所占的空间为 40Bytes;后面的图为deque内部以及Itr类内部的数据,明白地显示了为什么c的大小为 40Bytes。

11.2 Composition(复合)关系下的构造与析构

左边拥有右边,左边叫做Container,右边叫做Component;红色的部分是编译器自动加上的,并非程序员写的;可能内部的Component有多个构造函数,但是编译器不知道需要哪个,所以就调用一个默认的构造函数,如果这并不是你期望的,那就需要自己在Comtainer的构造函数的初始列位置写上要调用的构造函数。

11.3 Delegation(委托). Composition by reference.

有一个指针指向右边,需要右边的时候才创建右边;Delegation又叫Composition by reference,因为也是拥有,只是拥有的是个指针;学术界不说by pointer,即使用的是指针传也叫by reference;两个类之间用指针相连,就叫做Delegation;因为需要右边的时候才创建右边,所以左右两边的生命不同步;String类中的所有操作都是通过rep指针来实现的,这种方式叫做pointer to implementation, 指针指向为String实现所有功能的类;这种设计的好处是,右边无论怎么变化都不影响左边,这种手法又叫做“编译防火墙”;另一个名称叫做Handle/Body,左边是Handle,右边是Body;这种做法是为了做reference counting,三个String对象abc,都在用字符串"Hello",三个对象共享 “Hello”,现在n是 3。内容一样才能共享。如果a想修改字符串的内容,但是不影响bc,那就拷贝一份让a去更改,然后就只剩下bc共享字符串。这是copy-on-write,就是写的时候copy一份副本让你去写。

11.4 Inheritance(继承),表示is-a

关系图中的T表示这是个模板类;当前的例子中子类_List_node中的数据成员是_M_data,但是它还涵盖了_M_next_M_prev成员

11.5 Inheritance(继承)关系下的构造与析构

子类的对象中有父类的成分;父类的析构函数必须是virtual的,否则会出现undefined behavior;红色部分是编译器添加的;谁先谁后已经是编译器完成了,程序员不用操心。

11.6 虚函数与多态

11.6.1 Inheritance(继承)with virtual functions(虚函数)

虚函数被重新定义才能叫做override

11.6.2 Inheritance(继承)with virtual

输入文件名称后,按下开始键后,程序会收到一个文件名;程序要检测file name是否正确,是否有不合法字符;到硬盘中查找file是否存在;如果存在,则将该文件打开,并读出来。

上面的这个流程所有的软件都是一样的,除了最后将文件读出来有所区别。那么,将这个框架写成模板,即CDocument类:

CMyDoc myDoc;myDoc.OnFileOpen(); //通过子类调用父类的函数

子类中没有函数OnFileOpen()myDoc.OnFileOpen()的全名是CDocument::OnFileOpen(&myDoc),所以才能找到CDocument类中的OnFileOpen函数。

CDocument::OnFileOpen(&myDoc);中传入了myDoc的地址,调用Serialize的时候,编译器通过this进行调用,即this->Serialize(),而this就是myDoc, 也就调用了CMyDoc类的Serialized()函数。

myDoc.OnFileOpen()的执行流程:

调用CDocument的OnFileOpen函数;在OnFileOpen函数里调用了CMyDoc的Serialize()函数;执行完了Seriazlize()函数后又回到OnFileOpen()函数中继续执行;OnFileOpen函数执行完毕后,回到main函数中。

过程如图中的灰色线所示。

将关键动作Serialize延缓到了子类中执行。将OnFileOpen()这个函数的这种做法叫做Template Method模式。

模拟该过程的代码:

11.7 Inheritance+Composition 关系下的构造和析构

/*************************************************************************> File Name: Derived.cpp> Author: Maureen> Mail: Maureen@> Created Time: 三 11/17 20:10:15 ************************************************************************///自己编写#include <iostream>using namespace std;class Component {public:Component() {cout << "Component constructor" << endl;}bool test() {return true;}~Component() {cout << "Component destructor" << endl;}};class Base {public:Base() {cout << "Base constructor" << endl;}~Base() {cout << "Base destructor" << endl;}};class Derived : public Base {protected:Component component;public:Derived() {cout << "Derived constructor" << endl;}bool test() {return component.test(); }~Derived() {cout << "Derived destructor" << endl;}};int main() {Derived obj;return 0;}

运行结果:

Base constructorComponent constructorDerived constructorDerived destructorComponent destructorBase destructor

Derived的构造函数先调用Component的 default 构造函数,然后调用Base的 default 构造函数,然后才执行自己。

Derived::Derived(...): Component(), Base() {...}

Derived的析构函数首先执行自己,然后调用Base的析构函数,然后调用Component的析构函数。

Derived::~Derived(...) {... ~Base(), ~Component() }

示例程序:

/*************************************************************************> File Name: test2.cpp> Author: Maureen> Mail: Maureen@> Created Time: 三 11/17 20:21:15 ************************************************************************///自己编写#include <iostream>using namespace std;class Component {public:Component() {cout << "Component constructor" << endl;}void test() {cout << "Component test function" << endl;}~Component() {cout << "Component destructor" << endl;}};class Base {public:Component comp;Base() {cout << "Base constructor" << endl;}void test() {comp.test(); }~Base() {cout << "Base destructor" << endl;}};class Derived : public Base {public:Derived() {cout << "Derived constructor" << endl;}~Derived() {cout << "Derived destructor" << endl;}};int main() {Derived obj;return 0;}

运行结果:

maureen@localhost 03.inheritance_and_composition % ./a.outComponent constructorBase constructorDerived constructorDerived destructorBase destructorComponent destructor

11.8 Delegation(委托) + Inheritance(继承)

需要解决的问题:

四个窗口在看同一份文件/或者同一份数据,三种不同的view查看;当某个窗口的数据发生变化时,其它的也要跟着改变,因为只有一份数据

设计方案:

11.9 委托相关设计

如何设计一个FileSystem或者WindowSystem?

通常使用Composite这种设计模式:

创建未来可能出现的类的对象

通常使用Prototype模式,子类创建自己类型的原型(对象),并将该对象放到父类中,使得父类能够看见;且子类中都要写函数clone,用于new自己。

源码:

相关设计模式来源《Design Patterns Explained Simply》

源码:/design_patterns/prototype/cpp/3

#include <iostream>enum imageType{LSAT, SPOT};class Image{public:virtual void draw() = 0;static Image *findAndClone(imageType);protected:virtual imageType returnType() = 0;virtual Image *clone() = 0;// As each subclass of Image is declared, it registers its prototypestatic void addPrototype(Image *image){_prototypes[_nextSlot++] = image;}private:// addPrototype() saves each registered prototype herestatic Image *_prototypes[10];static int _nextSlot;};Image *Image::_prototypes[];int Image::_nextSlot;// Client calls this public static member function when it needs an instance// of an Image subclassImage *Image::findAndClone(imageType type){for (int i = 0; i < _nextSlot; i++)if (_prototypes[i]->returnType() == type)return _prototypes[i]->clone();return NULL;}class LandSatImage: public Image{public:imageType returnType(){return LSAT;}void draw(){std::cout << "LandSatImage::draw " << _id << std::endl;}// When clone() is called, call the one-argument ctor with a dummy argImage *clone(){return new LandSatImage(1);}protected:// This is only called from clone()LandSatImage(int dummy){_id = _count++;}private:// Mechanism for initializing an Image subclass - this causes the// default ctor to be called, which registers the subclass's prototypestatic LandSatImage _landSatImage;// This is only called when the private static data member is initiatedLandSatImage(){addPrototype(this);}// Nominal "state" per instance mechanismint _id;static int _count;};// Register the subclass's prototypeLandSatImage LandSatImage::_landSatImage;// Initialize the "state" per instance mechanismint LandSatImage::_count = 1;class SpotImage: public Image{public:imageType returnType(){return SPOT;}void draw(){std::cout << "SpotImage::draw " << _id << std::endl;}Image *clone(){return new SpotImage(1);}protected:SpotImage(int dummy){_id = _count++;}private:SpotImage(){addPrototype(this);}static SpotImage _spotImage;int _id;static int _count;};SpotImage SpotImage::_spotImage;int SpotImage::_count = 1;// Simulated stream of creation requestsconst int NUM_IMAGES = 8;imageType input[NUM_IMAGES] ={LSAT, LSAT, LSAT, SPOT, LSAT, SPOT, SPOT, LSAT};int main(){Image *images[NUM_IMAGES];// Given an image type, find the right prototype, and return a clonefor (int i = 0; i < NUM_IMAGES; i++)images[i] = Image::findAndClone(input[i]);// Demonstrate that correct image objects have been clonedfor (int i = 0; i < NUM_IMAGES; i++)images[i]->draw();// Free the dynamic memoryfor (int i = 0; i < NUM_IMAGES; i++)delete images[i];}

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。