极客班C++面向对象高级编程(上)第二周笔记

2016-08-01  本文已影响0人  Wancho

Classes的两个经典分类

Class without pointer member(s)

complex

Class with pointer member(s)

string

带指针的class

String class

它的Header的结构

它将实现的功能:

三种构造方式:

无初值的、有初值的 和 拷贝构造。

两种操作符重载:

“<<”(输出到“cout”) & “=”(拷贝赋值)

Big Three 三个特殊函数

四个主要函数:

1.默认用指针构造;

2.拷贝构造,接受的是自身的类型的对象;

3.拷贝赋值,只有类带有指针,一定要包含有这个函数;

4.析构函数,当以这个类创建的对象,即将死亡的时候,它就会被调用。

其中2,3,4 被称为 Big Three .

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

构造函数 ctor :

字符串会以头指针的形式传入,并以‘\0’为结尾;因此构造函数要适应不同长度的,所以用‘new’分配一块大小适合内存。

析构函数 dtor:

用于清理,清理动态分配的内存,不然就是内存泄漏了,用 delete[] 关键字释放动态分配的内存;

在调用的示例中:

指针p指向动态分配的内存,所以用 delete 释放内存;

当离开作用域的时候,会调用3次析构函数,包括以new动态分配的String对象的析构函数。

class with pointer members (带有指针的类)必须有 copy ctor (拷贝构造函数) 和 copy operator= (拷贝赋值)

如果不写拷贝赋值函数,而是直接使用原来‘=’把a的地址复制到b里去,b只会指向a所指的内存,原来所指的内存也将会丢失(造成内存泄漏),这叫浅拷贝,不是我们想要的结果,我们要的是把内容拷贝过来的深拷贝。

在设计中应该避免有别名出现,即多个指针指向同一块内存。

copy ctor (拷贝构造函数)

copy assignment operator(拷贝赋值函数)

经典的写法

① 释放原来的内存

② 重新申请一个足够大的内存

③ 将内容拷贝进来

然后,在最前面检测自我赋值的情况;如果出现自我赋值,而又没有检测自我赋值,这样会因为内存被释放而丢失原来的内容。

(这里的this是指向调用者的,而且这个函数它还是成员函数,所以可以直接更改)

output函数

operator<< 函数一定是全局函数,如果它是成员函数的话cout就要在右边,因为成员函数的左边一定是自身类的指针 *this

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

Stack,是存在于作用域(scope)的一块内存空间(memory space)。例如当你调用函数,函数本身即会形成一个stack用来旋转它所接收的参数,以及返回地址。

在函数本体(function body)内声明的任何变量,其所使用的内存块都取自上述stack。

Heap,或调system heap,是指由操作系统提供的一块global(全局的)内存空间,程序可动态分配,从中获得若干块。

c1所占用的空间来自Stack;当离开作用域时,它的生命自然结束;

Complex(3)是个临时创建的对象,占用的空间是以new动态分配非得,并由heap提供;它必须需要手动delete掉。

关于生命期

stack object:

c1便是,其生命在作用域结束之际结束。又称为auto object,因为它会被[自动]清理。

static local object(静态对象):

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

global object(全局对象):

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

heap objects:

p所指的便是heap object,其生命在它被deleted之际结束。

在创建时,我们得到的是一个指针p,所以我们应该delete指针p,同时会调用被删除对象内的析构函数。

如果没把heap对象 delete掉,就会出现内存泄漏(memory leak),即是程序失去对内存块的控制(内存块再也找不回来了)

以上便会出现内存泄漏,因为当作用域结束,p所指的heap object仍然存在,但指针p的生命却结束了,作用域之外再也看不到p(也就没机会delete p)

动态分配所得的内存块(关于new和delete)

new其实也是一个函数

new调用时,先分配memory,再调用ctor

以上便是编译器将new转化的三个动作,实际上调了malloc()函数分配内存。

delete:先调用dtor,再释放memory

编译器转化的两个动作,它里面也调用了free()函数。

动态分配数组对象

以上的用法叫做array new和array delete;

array new一定要搭配array delete,因为delete只是会删除指针所指的内存,数组内的其它内存就会因此而丢失(内存泄漏)。

(注:动态分配的内存区块中,头尾也会有一个称为cookie标识的内存块,标识中用16进位表示状态,结尾为1表示“分配”,并且动态分配的内存区块一定是16的倍数)

扩展补充

关于static

一般的成员函数都会有this point

以上是从编译器角度看到的成员函数的调用。

如果一个数据在每个同类的对象都是相同的,这时候它就是应该变成static数据,在它的声明前加上static便是;

static数据要在class body外定义,上面黄色部分便是。

static函数:它只能处理static的数据,也没this point;它可以通过object调用(通过已经创建的对象调用),也可以通过class name调用(直接修改类的static数据,作用于所有通过这个类创建的对象)

关于把ctors放在private区

之前说过这种把构造函数放在private中的类,叫做Singleton(单体)

上面的便所谓的Singleton,因为它不能被外境构造,所以它须要在自身内被构造,并且一定是static的;

因为static函数可以通过class name调用(上面的小框便是),通过getInstance()调用‘a’,这样就解决了不能调用的问题。

但就是因为static的数据一被定义了就会直到整个程序结束,它的生命才会结束;

用上面的写法可以解决static object不被使用也一直存在的问题。

关于cout

cout属于右上角的class,而这个类型继承自ostream,所以它也是属于ostream。

从ostream里可以发现,它做了好多种operator<<的重载,这正是cout可以接受多种类型数据的原因。

class template(类模板)

如果在设计没有确定类里面的参数在使用时的类型,可以通过类模板在使用时再确定它们的数据类型。

写法如上:在类的前面声明T是一个关键字;用T替换参数类型的位置。

图中左下角便是它的调用方法。

function template(函数模板)

用法:正如上面的比大小函数,为了适用于所有不同类型的对象,所以它采用了函数模板,代替未知的对象类型;它的声明和类模板相似,要在函数之前用加上黄色的语句。

(使用这种方式时,不要忘了还要对相应的操作符进行重载)

namespace

它的作用是将它里面的东西包装起来,防止和其它人重名。(std是指标准库,标准库的所有东西都被包在std里)

用法:

最简单的用法就是using directive(全开)(写法如上),这样在下面的所以标准库的函数都不用写命名了(cin和cout的全名是std::cin和std::cout)。

using declaration(逐个打开‘声明’)

正如上面的,它只声明了cout,所以就是只有cout不用写全名。

更多拓展,此课程不再详述

Composition(复合),表示has-a

表示queue(队列,先进先出)包含deque(两端都可以进出的队列);

queue拥有deque的所有功能,这样便是所谓Composition,而queue自身没有实现功能只是改变功能的名字,这类情况叫做Adapter(改装);

Adapter:适用于已经存在一个类能实现所需的功能时,只是有一些情况不同(可能是接口不一样-函数名不同),它是复合中一种特殊的情况

Composition类的大小要加上它所包含的所有类的大小

Composition关系下的构造和析构

当Container包含Component时:

编译器会在Container的构造函数名之后,自动加上Component的默认构造(‘ : Component() ’),这便使它先要执行所含类的构造函数(如果需要调用所含类的其它构造函数,需要自己写上)然后才执行自己;

编译器也会在Container的析构函数中加上所含有类的析构函数,且是先执行自己。

Delegation (委托) . Composition by reference

这里是通过指针,指向功能实现的类,而自身却只是一个对外的接口,所有功能的实现都是通过指针委托‘功能实现类’完成的,这样它便有了高度的弹性(可以改变它功能,而不影响整体,也可以方便地增加它的功能)

这样的写法称为pimpl(private implementation),左边的是Handle,右边的是Body,其主要作用是解开类的使用接口和实现的耦合

(这里用到是指针,但为什么也叫by reference而不是by point?因为业界中没有by point这个说法,by point也叫by reference)

Inheritance(继承),表示is-a

(在C++中,struct其实是一种class)

语法:就是加黄色的一行;上面的是父类,下面的是子类(子类继承父类)。

C++的继承拥有三种方式,分别是public / private / protected,其中最重要的public。(而Java中只有public)

子class拥有自己的part(成份)同时,还涵盖了父class的prat。

图示,用空心三角形表示继承。

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

与Composition的关系相似,但前提是父类中的构造函数一定要是虚函数(后面会解释虚函数),否则不会出图中下面是两个动作

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

父类的成员函数分三种类型:非虚函数、虚函数、纯虚函数。

虚函数的语法:在非虚函数之前加virtual。

非虚函数,子类不能重新定义,在父类中定义好,供子类使用。

虚函数,在父类中定义,子类也可以重新定义它。

纯虚函数,在父类中声明,在子类中定义。

Template Method(模板方法):二十三种设计模式之一。

示例中,通过子类的对象调用父类的函数,当调用虚函数时,编译器会检查子类是否有重新定义。

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

在第一种情况中,Base part和Component part它两的构造的先后是顺序,而析构则是逆序的。

在第二种情况中,与之前的相类似,不多说。

Delegation(委托) + Inheritance(继承)

左边委托右边,右边作为父类,可以被继承。

这样的写法可以使左边被创建,内容可以被多个继承右边的子类观察。(就像一个文件在同一个软件里被打开,但有不同的查看窗口)

上一篇下一篇

猜你喜欢

热点阅读