C++ 面向对象上

语言+标准库

头文件与类的声明

类的经典分类

  • 带指针成员的类
  • 不带指针成员的类

头文件的防范式声明

1
2
3
4
5
#ifndef __xxx__
#define __xxx__ abc
//执行
#endif
//执行

类要作提前引用声明

使用参数初始化列表来规范化类的构造函数

默认构造函数

创建类对象时无需给定参数的构造函数

  • 无参构造函数
  • 全部参数带默认值的构造函数

    不可以有两个默认构造函数

私有构造函数

参数传递

函数传参尽量采用址传递

常量引用代替值传递

函数返回值的传递也尽量址传递

注意:若返回对象是在函数中创建的,不可将其引用传回,因为函数调用结束后,其生命周四结束,内存被释放,传回的引用指向的为垃圾数据

Return by reference 语法

传递着无需无需知道接收者是以reference形式接受的,即接收者以对应引用接收时,传递者可使用相应类型传递值

友元函数

相同class的各个对象互为friends

运算符重载

  • 作成员函数

    任何成员函数会有一个隐藏参数this指针指向(函数调用者)即该运算符的左操作数,不能在参数处显示声明,但是可以在函数体中使用

  • 非成员函数

  • 思考:重载运算符函数已经将运算结果保存到了对应参数中,为什么还要有返回值呢???

    因为运算符有连续操作,void完全ok,但是如果出现连=,+,-等操作呢??所以,还需要有返回值存储当前运算结果

匿名对象

1
Typename()

带指针成员的类

Big Three

带指针成员的类不得不写Big Three,默认的函数无法满足需求且会引发问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class String {
public:
String(const char* el=0){
if(el) {
elem = new char[strlen(el)+1];
strcpy(elem,el);
}else {
elem = new char[1];
*elem = '\0';
}
}
String(const String& s) {
//深拷贝
elem = new char[strlen(s.elem)+1];
strcpy(this.elem,s.elem);
}
String& operator=(const String& s) {
if(this == &s) {
//判断是否为同一对象
return *this;
}
delete []elem;
elem = new char[strlen(s.elem)+1];
strcpy(this.elem,s.elem);
}
~String() {
delete[] elem;
}

private:
char* elem;
}
  • 拷贝构造函数

从零开始创建一个与给定对象相同的对象

浅拷贝

  • 仅仅为成员指针的拷贝,这样导致被创建的对象的指针成员指向的内存泄漏,且一个内存被两个指针指向,操作相互受影响

深拷贝

  • 为指针指向内存的内容的拷贝

以字符串为例

  • 拷贝赋值函数 对=赋值运算符的重载

    将已存在的对象赋值为另一个对象

    以A=B的赋值过程为例:

    1. 将A指针指向的内存清除

    2. A中重新创建一个与B中待拷贝内存大小相同的内存

    3. 逐个将B中内存内容拷贝至A中

    一般拷贝赋值函数考虑自我赋值,不作任何操作,返回本身即可

  • 析构函数

  • 生命周期

    • stack对象

    离开栈的作用域,对象的生命周期就结束,系统自动清理对象内存

    • static对象

    static对象生命在作用域结束之后仍然存在,直到程序结束

    • 全局对象

    直到程序结束

  • 生命周期

    • heap堆对象 指针指向的内存对象

    当delete语句删除内存对象时,该内存对象生命期结束

  • 内存泄漏

    当动态创建对象后不存在delete语句释放动态创建的内存时,一旦超出指针作用域,指针失效后,内存空间便无法使用,却没有删除,造成内存泄漏

堆与栈的区别

  • 栈内存由系统以函数为单位自动分配与释放,局部变量,函数参数均存于栈中

    堆由人为动态创建与释放,c语言使用malloc/free函数,c++使用new/delete运算符

    由于堆空间需要人为管理,容易造成内存泄漏

  • 栈内存的分配释放速度快,效率高,内存连续

    堆内存的分配释放速度效率较低,内存不一定连续

内存管理

  • new运算符

    先分配内存后调用构造函数

    1
    2
    3
    4
    obj *p=new obj(); //实现过程
    void* mem=operator new(sizeof(obj)) //内部调用malloc(n)分配内存
    p=static_cast<obj*>(mem) //指针类型的转型
    p->obj::obj(); //调用obj对象的构造函数初始化
  • delete运算符

    先调用析构函数再释放内存

    1
    2
    3
    delete p; //实现过程
    obj::~obj(p) //先调用obj对象的析构函数
    operator delete(p) //内部调用free(n)释放内存
    • 先调用析构函数原因

    delete删除动态创建的带有指针的类对象时,只会删除为该对象分配的内存空间,即为指针分配的4字节内存空间,但是指针指向的内存空间并没有被删除,但是指针已被删除,这样再次导致内存泄漏问题

内存分配状态 vc为例

  • new Object

    1
    2
    obj *p=new obj();
    delete p;

    debug模式内存分配

    cookie头 记录分配的内存空间长度 4+4

    debug头部 32+4

    对象

    填充字节部分 -vc的内存需要为16的倍数,不满足取与实际内存最接近的适合大小

    -

    非debug模式的内存分配

    cookie头 记录分配的内存空间长度 4+4

    对象

    填充字节部分 -vc的内存需要为16的倍数,不满足取与实际内存最接近的适合大小

  • new Arrays

    1
    2
    obj *p=new obj[size];
    delete[] p; //[]用于指明为arrays内存模式,不会仅调用一次析构函数

    debug模式内存分配

    相较于object下的内存,多分配了4byte的数组长度位

    -

    非debug模式的内存分配

    相较于object下的内存,多分配了4byte的数组长度位

注意:Arrays内存分配下,delete时务必添加[]

原因:要将Arrays下的内存全部清理,需要执行size次类对象的析构函数清理各个对象元素的内存,而缺省[]的情况下,编译器将只执行一次首元素的析构函数,虽然arrays的内存空间被清理,但是其余对象元素的空间未被清理,导致内存泄漏

Static

  • 普通成员变量

    内存的分配跟随对象的创建,从属于某一特定对象,相互独立

  • 普通成员函数

    成员函数的内存单独占一份,有隐藏this指针,不同对象调用时可识别并操作特定对象的数据成员

  • 静态成员变量

    该成员变量的内存单独占一份,且被所有对象所共享,且类被声明即已存在

  • 静态成员函数

    该成员函数仍单独占一份内存,但是特殊在于无隐藏this指针参数,只能对静态数据成员进行操作

  • 静态局部变量

    该局部变量即使跳出局部作用域,其内存生命仍存在,但是局部作用域外无法访问

设计模式的应用

  • 单例模式

    1
    //暂略

名称空间

防止名称冲突

  • using namespace name

    “将封装的命名空间全部打开,直接使用该命名空间中的成员”

  • using name::member

“将封装的命名空间部分打开,可直接使用已打开的命名空间中的成员”

  • 直接使用命名空间全名加::引用命名空间中的成员

OOP AND OOD

Composition 复合

has-a 一个对象成员直接包含另一类型的对象

  • 黑盒复用角度解释

    外部对象可以调用内部对象的方法来实现某些功能

    Adaptor 适配器设计模式

  • 内存角度解释

    外部对象的内存为外部对象额外成员与内部对象成员之和

符号表示

实心菱形+箭头 箭头所指的为被组合的对象

组合中的构造与析构

  • 构造函数

    由内向外逐步构造,先调用内部对象的默认构造函数

  • 析构函数

    由外向内逐步析构,先调用外部对象的析构函数

Delegation 委托

Composition by reference

一个对象含有一个指向另一类型对象的指针成员

Handle/Body(pImpl)

符号表示

空心菱形+箭头 箭头所指的为被组合的对象

特点

在使用时动态创建复用对象,无需定义外层对象时便创建

继承

is-a 继承父类的部分数据成员与成员函数的调用权

符号表示

空心三角+直线 空心三角所指的为被继承的对象

继承中的构造与析构

  • 构造函数

    由内向外逐步构造,先调用父类的默认构造函数

  • 析构函数

    由外向内逐步析构,先调用子类的析构函数

继承与虚函数

  • 纯虚函数

    1
    virtual void function()=0;

    无函数体,函数必须被子类override重写

  • 虚函数

    1
    virtual void function() {}```

    有函数体可为空,即存在默认函数体,子类可重写也可不重写

    Template method 模板方法设计模式

    父类方法中延时调用子类重写的虚函数

    继承+复用

    继承+委托

    Observer观察者设计模式

设计模式

  1. 应用场景
  2. 继承,组合,委托
  3. 类图

单例

adaptor适配器

Handle/Body(pImpl)

observer

Composite

文件系统

Prptotype

暂略