侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集

文章目录

  • Ⅰ C++ part1 面向对象编程
    • 1 头文件与类的声明
      • 1.1 c vs cpp关于数据和函数
      • 1.2 头文件与类
        • 1.2.1 头文件
        • 1.2.2 class的声明
        • 1.2.3 模板初识
    • 2 构造函数
      • 2.1 inline 函数
      • 2.2 访问级别
      • 2.3 ctor 构造函数
        • 2.3.1 ctor 的写法
        • 2.3.2 ctor/函数 重载
        • 2.3.3 ctor 放在 private 区
      • 2.4 const 常量成员函数
    • 3 参数传递与返回值——引用
      • 3.1 参数传递
      • 3.2 返回值传递
    • 4 友元 friend
      • 4.1 友元
      • 4.2 相同 class 的 object 互为 friends
    • 5 操作符重载与临时对象
      • 5.1 操作符重载
        • 5.1.1 成员函数实现 / this
        • 5.1.2 非成员函数实现
        • 5.1.3 output函数 << 的重载
      • 5.2 临时对象
    • 6 带指针的类:三大函数
      • 6.1 ctor 和 dtor (构造和析构函数)
        • 6.1.1 ctor 构造函数
        • 6.1.2 dtor 析构函数
      • 6.2 copy ctor 拷贝构造函数
      • 6.3 copy op= 拷贝赋值函数
    • 7 堆,栈,内存管理
      • 7.1 堆和栈
      • 7.2 object 生命期
      • 7.3 new 和delete
        • 7.3.1 new
        • 7.3.2 delete
      • 7.4 内存动态分配
        • 7.4.1 在VC下内存动态分配
        • 7.4.2 array new/delete
    • 8 静态 模板 namespace
      • 8.1 static
      • 8.2 template
        • 8.2.1 class template 类模板
        • 8.2.2 function template 函数模板
      • 8.3 namespace
    • 9 复合 委托
      • 9.1 Composition 复合
        • 9.1.1 复合下的构造和析构
      • 9.2 Delegation 委托
    • 10 继承与虚函数
      • 10.1 Inheritance 继承
        • 10.1.1 继承下的构造和析构
      • 10.2 虚函数
      • 10.3 继承 with virtual
      • 10.4 缩略图
      • 10.5 继承+复合
      • 10.6 继承+委托
        • 10.6.1 例一 Observer
        • 10.6.2 例二 Composite
        • 10.6.3 例三 Prototype
  • Ⅱ C++ part2 兼谈对象模型
    • 1 转换
      • 1.1 转换函数
      • 1.2 non-explicit-one-argument ctor
      • 1.3 explicit
    • 2 xxx-like classes
      • 2.1 pointer-like classes
        • 2.1.1 智能指针
        • 2.1.2 迭代器
      • 2.2 function-like classes
    • 3 模板
      • 3.1 类模板/函数模板
      • 3.2 成员模板
      • 3.3 模板模板参数
    • 4 specialization 特化
      • 4.1 全特化 full specialization
      • 4.2 偏特化 partial specialization
        • 4.2.1 个数上的偏
        • 4.2.2 范围上的偏
    • 5 三个C++11新特性
      • 5.1 variadic templates
      • 5.2 auto
      • 5.3 ranged-base for
    • 6 多态 虚机制
      • 6.1 虚机制
      • 6.2 动态绑定
    • 7 reference、const、new/delete
      • 7.1 reference
      • 7.2 const
      • 7.3 new delete
        • 7.3.1 全局重载
        • 7.3.2 class中成员重载
        • 7.3.3 placement new delete

Ⅰ C++ part1 面向对象编程

1 头文件与类的声明

1.1 c vs cpp关于数据和函数

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第1张图片

c语言中,data和函数都是分别定义,根据类型创建的。这样创建出的变量,是全局的

cpp中,将数据data和函数都包含在一起(class),创建出一个对象,即为面向对象;数据和函数(类的方法)都是局部的,不是全局的

class的两个经典分类:

  • 无指针成员的类(complex)——复数
  • 有指针成员的类(string)——字符串

1.2 头文件与类

1.2.1 头文件

引用自己写的头文件,用双引号

头文件的标准写法:

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第2张图片

complex.h:

#ifndef _COMPLEX_  // 如果没有被定义过就定义 (防卫式声明)
#define _COMPLEX_

#endif
  • 首先是防卫式声明,如果没定义这个名词,那么就定义一下。ifndef+define。(这样如果程序是第一次引用它,则定义,后续则不需要重复定义,不需要重复进入下面的过程)
  • 1要写的类的声明,2是要写类的具体定义,写1 2的时候发现有一些东西需要提前声明,写在0

1.2.2 class的声明

在C++中 struct和class唯一的区别就在于默认的访问权限不同

  • struct 默认权限为公共
  • class 默认权限为私有
class complex  //class head
{              //class body  /*有些函数直接在此定义,另一些在 body 之外定义*/
public:
 complex (double r = 0, double i = 0)
    : re (r), im (i) 
 { }
 complex& operator += (const complex&);
 double real () const { return re; }
 double imag () const { return im; }
private:
 double re, im;

 friend complex& __doapl (complex*, const complex&); 
};
{
 complex c1(2,1);
 complex c2;
 ...
}

1.2.3 模板初识

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第3张图片
{
    complex<double> c1(2.5, 1.5);
    complex<int> c2(2, 6);
    ...
}
  • 因为实部和虚部的类型不确定,可能是 double float int,定义一个模板类型叫做 T
  • T作为一个类型参数来传入,在调用的时候就可以指定类型了
  • 通过在定义类的前面加入一行代码 template 来实现

2 构造函数

2.1 inline 函数

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第4张图片

定义类的时候,可以直接在body中定义函数(inline函数,在body中定义完成),也可以只是在body中声明函数

  • inline内联函数:如果定义的函数是内联函数,那么会运行比较快,尽可能定义为内联函数
  • 在body外,通过inline关键字来指定该函数为inline函数

注意的是,上面所有的inline函数,都只是我们指定的,希望它为inline,具体是不是,要看编译器来决定

2.2 访问级别

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第5张图片
  • 数据应该被定义为private

  • 函数要被外界使用,定义为public;若只是内部处理,定义为private

2.3 ctor 构造函数

2.3.1 ctor 的写法

方式一:(推荐)

complex(T r = 0, T i = 0) //函数名称与class的名称一致
    : re(r), im(i)        //中间这一行就是初始化
{ }

方式二:(不推荐)

complex(double r = 0, double i = 0)  
{
    re = r; im = i;       //用赋值来进行初始化
}

通过构造函数来创建对象。会自动调用构造函数进行创建。

  • 构造函数名称需要与类的名称一样
  • 函数的参数可以有默认参数
  • 构造函数没有返回类型

2.3.2 ctor/函数 重载

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第6张图片

构造函数可以有很多个,可以重载;但是上面的1 2两个构造函数冲突了

complex c2();   // "()" 可以不要,一样的

上面的调用方式对两个构造函数都适用,冲突


double real () const { return re; }
void real (double r) {  re = r;  }  //不能有const
  • 同名的函数可以有多个,编译器会编成不同的名称,实际调用哪个会根据哪个适用

2.3.3 ctor 放在 private 区

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第7张图片
  • 通常构造函数不要放在private中,这样外界没法调用,也就无法创建对象
  • 在设计模式 Singleton(单体)中,将构造函数放在了private中;这个class只有一份,外界想要调用的时候,只能使用定义的 getInstance() 函数来取得这一份;外界无法创建新的对象

2.4 const 常量成员函数

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第8张图片

对于不会改变数据内容的函数,一定要加上const

{
    const complex c1(2, 1);
    cout << c1.real();
    cout << c1.imag();
}

对于上面调用方式,我们创建一个常量复数然后调用函数输出实部虚部,如果上面real和imag函数定义的时候,没有加const,那么这里函数默认的意思是可能会改变数据,与我们的常量复数就矛盾了,编译器会报错;因此,对于不会改变数据内容的函数,一定一定要加const

3 参数传递与返回值——引用

3.1 参数传递

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第9张图片
  • 值传递 pass by value,传递value是把整个参数全传过去,尽量不要直接value传递double r

  • 引用传递 pass by reference,传引用相当于传指针,快,形式也漂亮 例 complex&

  • 如果只是为了提升速度,不向改变数据,那么传const引用;这样传进去的东西,不能被修改

    const complex&

3.2 返回值传递

返回值的传递,尽量返回引用

在函数中创建的变量 (local 变量),要返回——这种情况是不能返回引用的;因为函数结束后函数中创建的变量就消失了,无法引用


侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第10张图片

传递者无需知道接受者是以reference形式接受——所以用reference形式很便捷

4 友元 friend

4.1 友元

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第11张图片

友元:friend,修饰在函数定义之前表示这个函数可以直接拿该类对象的private数据

inline complex&
__doapl(complex* ths, const complex& r)
{
    ths->re += r.re;  //直接拿private的数据,不需要函数
    ths->im += r.im;
    return *ths;
}
  • 如上面所示,声明为friend之后,函数可以直接取到re和im,如果不被声明为friend,只能通过调用real和imag函数来得到,效率较低

4.2 相同 class 的 object 互为 friends

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第12张图片

{
    complex c1(2, 1);
    complex c2;
    c2.func(c1);
}

相同class的不同对象互为友元,即可以直接取另一個 object 的 private data

5 操作符重载与临时对象

5.1 操作符重载

在c++里我们可以定义加法等操作符,比如我们可以定义两个石头的加法

5.1.1 成员函数实现 / this

成员函数: complex :: function .... 前面带有class的名称(在class里先声明了的)

inline complex&
complex::operator += (const complex& r) {
    return __doapl(this, r);   //do assignment plus
}
侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第13张图片

所有的成员函数都带有一个隐藏的参数this是一个指针),this指向调用这个函数的调用者

  • 定义函数的时候,在参数中不能写出来this,直接用即可

  • 函数里可写可不写,但当传入参数成员变量名相同时要写

    public:
    	double real () const { return this->re; }  //这里的this->可省略 
    

c3 += c2 += c1;    // c2 加了 c1 后如果返回 void 就无法进行 c3 的操作了

将操作符写为void函数也可以,但为了可以兼容c3+=c2+=c1的形式,写成返回引用更好。

5.1.2 非成员函数实现

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第14张图片

非成员函数没有this

应对三种使用方法,写出三种方式

  • 非成员函数是global函数——为了后面两种使用方法

  • 这些函数不能返回引用,必须值传递

    在函数中创建的新变量 (local 变量),要返回

5.1.3 output函数 << 的重载

cout不认识新定义的这种复数,因此也需要对<<进行操作符重载

只能全局函数,不能成员函数——导致使用时方向相反

#include 
ostream&
operator<<(ostream& os, const complex& x)
{
    return os << '(' << real(x) << ',' << imag(x) << ')';  //自定义输出
}
  • ostream&cout 的 classname

参数传递:os 在函数中会变化,所以不能加 const

返回值传递:为了避免 cout << c1 << conj(c1); 连续输出,不用 void

cout << c1 返回值需要与 cout 类型一致

5.2 临时对象

classname () 创建一个classname类型的临时对象——不需要名称,生命只有一行

6 带指针的类:三大函数

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第15张图片
  • 析构函数:~String();

  • 拷贝构造函数 copy ctor : String (const String& str); —— string s3(s1)

  • 拷贝赋值函数 copy op= : String& operator=(const String& str); —— s3=s2

    编译器默认的拷贝构造赋值(一个bit一个bit的复制),编译器默认的只是拷贝了指针(浅拷贝),而不是指针指向的数据

    侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第16张图片

    alias(别名)和 memory leak(内存泄漏)都是十分危险的

    因此,如果类中有指针,一定自己写这两个函数

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

6.1.1 ctor 构造函数

这里的 new 是申请的字符串的空间

inline
String::String(const char* cstr = 0)
{
    if (cstr) {       // 指定了初值—— String s2("hello");
        m_data = new char[strlen(cstr) + 1];  // 字符串长度 + /0
        strcpy(m_data, cstr);
    }
    else {            // 未指定初值—— String s1();
        m_data = new char[1];
        *m_data = '\0';
    }
}

这里的 new 是申请的指针的空间String()里面还有一个 new

String* p = new String("hello");  
delete p;

6.1.2 dtor 析构函数

inline  
String::~String()
{
    delete[] m_data;
}

每个 new 都对应一个 delete —— 一定要释放

类对象死亡的时候(离开作用域),析构函数会被自动调用

例:这里结束会调用三次 dtor

{
    String s1(),
    String s2("hello");
    String* p = new String("hello");
    delete p;
}

6.2 copy ctor 拷贝构造函数

inline
String::String(const String& str)
{
    m_data = new char[strlen(str.m_data) + 1]; // “str.m_data” 兄弟之间互为友元 
    strcpy(m_data, str.m_data); // 深拷贝
}
String s1("hello ");
String s2(s1);

6.3 copy op= 拷贝赋值函数

  1. 先杀死调用者

  2. 重新申请指定大小的空间

  3. 复制字符串内容到调用者

inline
String& String::operator=(const String & str)
{
    if (this == &str)  // 检测自我赋值 self assignment
        return *this;

    delete[] m_data;                               // 第一步
    m_data = new char[strlen(str.m_data) + 1];     // 第二步
    strcpy(m_data, str.m_data);                    // 第三步
    return *this;
}

一定要在开始就检测自我赋值,因为a=a时第一步 delete 了后,会使第三步出现问题

7 堆,栈,内存管理

7.1 堆和栈

Stack ,是存在于某作用域 (scope) 的一块内存空间。

例如当你调用函数,函数本身即会形成一个 stack 用来放置它所接收的参数,以及返回地址;在函数本体 (function body) 内声明的任何变量其所使用的内存块都取自上述 stack

Heap ,或称为 system heap ,是指由操作系统提供的一块 global 内存空间,程序可动态分配 (dynamic allocated) 从中获得若干区块 (blocks)

可以用 new 来动态取得

在 stack 中的是自动生成的空间,作用域结束空间会自动释放

在 heap 中的是自己申请的空间,需要自己释放

{
   complex c1(1,2);              
   /*c1空间来自stack*/
   complex* p = new complex(3);  
   /*complex(3) 是个临时对象
   其所用的空间是以new从heap动态分配而得,并由p指向*/
}

7.2 object 生命期

  • stack objects 的生命期

    c1 便是所谓 stack object,其生命在作用域 (scope) 结束之际结束这种作用域内的 object,又称为 auto object,因为它会被“自动”清理(结束自动调用析构函数)

    {
    	complex c1(1,2);
    }
    
  • static local objects 的生命期

    若在前面加上 static 后,其会存在到整个程序结束

    {
        static complex c2(1,2);
    }
    
  • global objects 的生命期

    写在任何作用域之外的对象,其生命在整个程序结束之后才结束,你也可以把它视为一种 static object,其作用域是整个程序

    ...
    complex c3(1,2);
    
    int main()
    {
        ...
    }
    
  • heap objects 的生命期

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

    {
        complex* p = new complex;
        ...
        delete p;
    }
    
    

7.3 new 和delete

7.3.1 new

new:先分配 memory , 再调用 ctor

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第17张图片
  1. 分配内存:先用一个特殊函数,按 class 的定义分配了两个 double 的大小
  2. 转型(忽视)
  3. 调用构造函数,赋值(1,2)

7.3.2 delete

delete:先调用 dtor, 再释放 memory

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第18张图片
  1. 调用析构函数——释放的是 m_date 指向的字符串 Hello 的空间(即构造函数中 new 申请的空间)
  2. 释放内存:用一个特殊函数释放了 ps 指向的空间(即String* ps = new String("Hello");new 申请的空间)

7.4 内存动态分配

7.4.1 在VC下内存动态分配

在VC下(不同编译器的内存动态分配可能不同)

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第19张图片
  • 调试模式:

    (4*3) 是3个指针的大小

    (32+4) 是调试模式所需空间(橘色部分)

    (4*2) 是上下两个 cookie ——表示内存块的开始与结束

    4 是数组才有的长度记录

    由于分配内存块需要是16的倍数,所以需要 pad 来填充到64

  • 执行模式:

    去掉调试模式的空间即可

因为内存块是16的倍数,因此最后四位bit一定都是0,cookie 就借用最后的一位1表示占用内存,0表示释放内存

如上图41h1即表示占用内存

7.4.2 array new/delete

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第20张图片

array new 一定要搭配 array delete

new后有[ ]—> delete后加[ ]

普通的delete只调用一次析构函数——剩下两个指针的指向的空间没有调用析构函数,内存泄漏

这种情况发生在有指针的类,但最好都这样写

8 静态 模板 namespace

8.1 static

对于非静态的函数和数据:

非静态的成员函数通过this指针来处理不同的数据(一份函数—>多个对象)

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第21张图片 侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第22张图片

对于静态的函数和数据:

静态函数没有this,不能处理一般的数据,只能处理静态的数据

例1:

class Account
{
public:
	static double m_rate;  //静态变量的声明
	static void set_rate(const double& x) { m_rate = x; } //静态函数
};
double Account::m_rate = 0; //静态变量的定义 一定要有

int main()
{
    //调用静态函数法1——by class name
	Account::set_rate(5.0);
    //调用静态函数法2——by object
	Account a;
	a.set_rate(7.0); //静态函数与a无关/无this
}

例2:设计模式 Singleton(单体)

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第23张图片
  • 构造函数放在private中,外界无法调用
  • 设计了getInstance静态函数,来生成并返回唯一的一份

8.2 template

8.2.1 class template 类模板

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第24张图片
  • T来代替某种类型
  • 使用时classname xxx,编译器会把T全部替换为type1

8.2.2 function template 函数模板

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第25张图片

比较函数——任何类型都可以进行比较;T来代替某种类型

应用时,不需要写某种类型——编译器自己会推导

8.3 namespace

对东西进行一个包装(不一定要一次性全写在一起,可分开包装在一起)

namespace name
{
...    
}
  1. 用法一:using directive

    #include 
    using namespace std; //直接把包装全打开
    int main()
    {
    	cin << ...;
    	cout << ...;
    	return 0;
    }
    
  2. 用法二:using declaration

    #include 
    using std::cout; //只打开一条
    int main()
    {
    	std::cin << ...; //没打开要写全名
    	cout << ...;
    	return 0;
    }
    
  3. 用法三:都写全名

    #include 
    int main()
    {
    	std::cin << ; 
    	std::cout << ...;
    	return 0;
    }
    

9 复合 委托

9.1 Composition 复合

类似于c中结构里有结构——class里有class

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第26张图片

deque 是一个已经存在的功能很多的类(两头进出的队列);利用deque的功能来实现queue的多种操作

该例只是复合的一种情况——设计模式 Adapter

9.1.1 复合下的构造和析构

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第27张图片
  • 构造是由内而外

    Container 的构造函数,编译器会自动先调用 Component 的 default 构造函数,再执行自己

    注意如果要调用 Component 的其他构造函数需要自己写出来

    Container::Container(…): Component() { … };

  • 析构是由外而内

    Container 的析构函数会先执行自己,之后编译器调用 Component 的析构函数

9.2 Delegation 委托

委托就是 Composition by reference;即通过指针把任务委托给另一个类

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第28张图片

复合中,内部和外部是一起出现的;而委托是不同步的

这是一个著名的设计模式——pimpl (pointer to implementation) 或者叫 “编译防火墙”

  • 右边怎么变动都不会影响左边

  • reference counting 多个指针共享一个 “Hello”;但当a要改变内容时, 系统会单独复制一份出来给a来改,b和c依然在共享

    侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第29张图片

10 继承与虚函数

10.1 Inheritance 继承

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第30张图片

语法::public base_class_name

public 只是一种继承的方式,还有protectprivate

子类会拥有自己的以及父类的数据

  • public 继承(public inheritance): 在公有继承中,基类的 public 和 protected 成员的访问属性在子类中保持不变,子类可以访问基类的 public 成员和受保护成员,但不能直接访问基类的私有成员

    通过子类的对象只能访问基类的 public 成员

    通常用于"is-a"关系,表示派生类是基类的一种类型,派生类的对象可以替代基类的对象

  • protected 继承(protected inheritance): 在受保护继承中,基类的 publicprotected 成员都protected 身份出现在子类中,基类的private 成员仍然是私有;子类成员函数可以直接访问基类中的 public 和 protected 成员,但不能直接访问基类的 private 成员

    通过子类的对象不能直接访问基类中的任何成员

    通常用于实现继承的实现细节,不表示"is-a"关系,而是表示派生类需要基类的实现,但不希望公开基类的接口

  • private 继承(private inheritance): 在私有继承中,基类的 publicprotected 成员都以 private 身份出现在子类中,基类的私有成员仍然是私有;子类成员函数可以直接访问基类中的 public 和 protected 成员,但不能直接访问基类的 private 成员

    通过子类的对象不能直接访问基类中的任何成员

基类 public 成员 基类 protected 成员 基类 private 成员
public 继承 public 成员 protected 成员 不能访问
protected 继承 protected 成员 protected 成员 不能访问
private 继承 private 成员 private 成员 不能访问

10.1.1 继承下的构造和析构

与复合下的构造和析构相似

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第31张图片
  • 构造是由内而外

    Derived 的构造函数,编译器会自动先调用 Base 的 default 构造函数,再执行自己

    注意如果要调用 Base 的其他构造函数需要自己写出来

    Derived::Derived(…): Base() { … };

  • 析构是由外而内

    Derived 的析构函数会先执行自己,之后编译器调用 Base 的析构函数

    Derived::~Derived(…){ … /* ~Base() */ };

    注意:Base class 的 dtor 必需是 virtual

    否则下例会导致结束时只会调用 Base 的 dtor

    int main() {
     Base* ptr = new Derived();
     delete ptr; // 只会调用 Base 类的析构函数
     return 0;
    }
    

10.2 虚函数

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第32张图片
  • pure virtual 函数:

    derived class 一定要重新定义 (override 覆写) 它;其没有定义只有声明

    语法:virtual xxxxxx =0;

  • virtual 函数:

    derived class 可以重新定义 (override, 覆写) 它,且它已有默认定义

    语法:virtual xxxxxx;

  • non-virtual 函数:

    不希望 derived class 重新定义 (override, 覆写) 它

10.3 继承 with virtual

例子:在 Windows 平台下用某个软件打开文件——分为好几步,但基本所有软件大多数操作都是一致的,只有一个操作如读取方式是不一样的

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第33张图片
  1. 现有一个框架 Application framework 其写好了所有必要的函数,其中 Serialize() 就是一个 pure virtual 函数
  2. 使用这个框架写自己软件的打开文件,就继承这个框架,其中就需要自己 override 覆写 Serialize() 这个函数
  3. 在执行中,执行 myDoc.OnFileOpen(); 中到 Serialize() 时,是通过 this 来指引到自己写的 Serialize() 中去的

把关键动作延缓到子类再做,这是一个经典的设计模式——Template Method

10.4 缩略图

  • 复合:image-20230802084858622

  • 委托:image-20230802085101744

  • 继承:image-20230802085210589

  • 类中的元素:侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第34张图片 变量名称 : 变量类型(与代码刚好相反

    • 变量下面加下划线 表示 static

    • 前面加一个 - 表示 private

    • 前面加一个 # 表示 protected

    • 前面加一个 + 表示 public(一般可以省略)

10.5 继承+复合

这种关系下的构造和析构与之前的类似

  • 第一种:

    侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第35张图片
    • 构造由内到外 先 Base 再 Component

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

      Derived::Derived(…): Base(),Component() { … };

    • 析构由外而内 先 Component 再 Base

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

      Derived::~Derived(…){… /*~Component() ~Base()*/};

  • 第二种:

    侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第36张图片

    同理构造由内到外,析构由外而内

10.6 继承+委托

10.6.1 例一 Observer

设计模式—— Observer

例如一串数据,可以用饼图来观察,也可以用条形图来观察,这种种的观察方式都是继承于 Observer

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第37张图片

通过 vector m_views; 来进行委托

当数据改变的时候,Observer 也需要更新,即 notify 函数,来将目前所有的观察者更新

10.6.2 例二 Composite

设计模式—— Composite

例如文件系统,文件夹里可以有文件夹(与自己相同的类),也可以有文件,其中文件就是最基本的 Primitive,而文件夹就是复合物 Composite

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第38张图片

要达成目的,就可以再设计一个父类 Component ,文件和文件夹就继承于同一父类;

其中 Composite 要用委托到父类的方式 Component* 设计容器和操作——使其 PrimitiveComposite 都可以适用

//父类 Component
class Component
{
private:
    int value;
public:
    Component(int val)	{value = val;}  
    virtual void add( Component* ) {} //虚函数
};

//复合物 Composite
class Composite 
    : public Component
{
	vector <Component*> c;  
public:
	Composite(int val) : Component(val) {}

	void add(Component* elem)
	{
		c.push_back(elem);
	}}

//基本类 Primitive
class Primitive
    : public Component
{
public:
	Primitive(int val): Component(val) {}
};

component中add是虚函数(且是空函数),不能是纯虚函数——Primitive 不会 override add函数(最基本的单位,不能 add 了),而 Composite 需要 override add函数

10.6.3 例三 Prototype

设计模式—— Prototype

框架(父类)要创建未来才会出现的子类——要求子类要创建一个自己当作原型 Prototype 让框架(父类)来找到并创建 FindAndClone

补充:当一个子类继承自父类时,它可以被视为是父类的一种类型,因此可以使用父类的指针或引用来引用子类的对象;

这种用父类的指针或引用来处理子类对象的方式称为——**向上转型 ** Upcasting

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第39张图片
  1. 父类中,有一个存放原型的数组,有纯虚函数 Image *clone(),还有两个静态函数 Image FindAndClone(imageType); void addPrototype(Image *image){...}

  2. 子类中,创建一个静态的自己 _LAST ,把它放到父类的一个空间中,这样父类就可以找到新创建的子类

    private 的构造函数 LandSatImage() 中是 addPrototype(this); //这里的 this 就是 _LAST 将自己的原型放到了父类中去

  3. 子类中,准备一个 clone()函数,父类通过调用找到的相应类型的 clone 函数来创建子类的副本

    这里的 clone 函数就不能用之前的那个构造函数来创建副本了——其会放到父类中去,所以创建一个新的构造函数 LandSatImage(int) 用传进一个无用参数(随便传个int型数据就好)来进行区分

Ⅱ C++ part2 兼谈对象模型

1 转换

1.1 转换函数

将当前对象的类型转换成其他类型

  • operator 开头,函数名称为需要转成的类型,无参数
  • 前面不需要写返回类型,编译器会自动根据函数名称进行补充
  • 转换函数中,分子分母都没改变,所以通常加 const
// class Fraction里的一个成员函数
operator double() const
{
    return (double) (m_numerator / m_denominator);
}
Fraction f(3,5);
double d = 4 + f; //编译器自动调用转换函数将f转换为0.6

1.2 non-explicit-one-argument ctor

将其他类型的对象转换为当前类型

one-argument 表示只要一个实参就够了

// non-explicit-one-argument ctor
Fraction(int num, int den = 1) 
    : m_numerator(num), m_denominator(den) {}
Fraction f(3,5);
Fraction d = f + 4; //编译器调用ctor将4转化为Fraction

1.3 explicit

当上面两个都有转换功能的函数在一起,编译器调用时都可以用,报错

class Fraction
{
public:
	Fraction(int num, int den = 1) 
		: m_numerator(num), m_denominator(den) {}
	operator double() const
	{
		return (double)m_numerator / m_denominator;
	}
	Fraction operator+(const Fraction& f) const
	{
		return Fraction(...);
	}
private:
	int m_numerator; // 分子
	int m_denominator; // 分母
};
...
    
Fraction f(3,5);
Fraction d = f + 4; // [Error] ambiguous

one-argument ctor 加上 explicit,表示这个 ctor 只能在构造的时候使用,编译器不能拿来进行类型转换了

...
explicit Fraction(int num, int den = 1) 
    : m_numerator(num), m_denominator(den) {}
...
    
Fraction f(3,5);
Fraction d = f + 4; // [Error] 4不能从‘double’转化为‘Fraction’

关键字 explicit 主要就在这里运用

2 xxx-like classes

2.1 pointer-like classes

2.1.1 智能指针

  • 设计得像指针class,能有更多的功能,包着一个普通指针
  • 指针允许的动作,这个类也要有,其中 *-> 一般都要重载
template <typename T>
class shared_ptr
{
public:
	T& operator*() const { return *px; }
	T* operator->() const { return px; }
	shared_ptr(T* p) : ptr(p) {}
private:
	T* px;
	long* pn;
};

在使用时,*shared_ptr1 就返回 *px

但是 shared_ptr1-> 得到的东西会继续用 -> 作用上去,相当于这个->符号用了两次

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第40张图片

2.1.2 迭代器

以标准库中的链表迭代器为例,这种智能指针还需要处理 ++ -- 等符号

node 是迭代器包着的一个真正的指针,其指向 _list_node

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第41张图片
  • 下图 *ite 的意图是取 data——即一个 Foo 类型的 object
  • 下图 ite->method 的意图是调用 Foo 中的函数 method
侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第42张图片

2.2 function-like classes

设计一个class,行为像一个函数

函数行为即 —— xxx() 有一个小括号,所以函数中要有() 进行重载

template <class pair>
struct select1st ... // 这里是继承奇特的Base classes,先不管
{
	const typename pair::first_type& // 返回值类型,先不管
	operator()(const pair& x) const
	{
		return x.first;
	}
};

...
//像一个函数一样在用这个类
select1st<my_pair> selector;
first_type first_element = selector(example_pair);

//还可以这样写,第一个()在创建临时对象
first_type first_element = select1st<my_pair>()(example_pair);

...

3 模板

3.1 类模板/函数模板

补充:只有模板的尖括号中<>,关键字 typenameclass 是一样的

3.2 成员模板

它即是模板的一部分,自己又是模板,则称为成员模板

其经常用于构造函数

  1. ctor1 这是默认构造函数的实现;它初始化 firstsecond 分别为 T1T2 类型的默认构造函数生成的默认值
  2. ctor2 这是带参数的构造函数的实现;它接受两个参数 ab,并将它们分别用来初始化 firstsecond 成员变量
  3. ctor3 这是一个==模板构造函数==,接受一个不同类型的 pair 对象作为参数;它允许从一个不同类型的 pair 对象构造当前类型的 pair 对象,在构造过程中,它将源 pair 对象的 firstsecond 成员变量分别赋值给当前对象的成员变量,使其具有一定的灵活性和通用性
template <class T1, class T2>
struct pair
{
	T1 first;
	T2 second;
	pair() : first(T1()), second(T2()) {} //ctor1
	pair(const T1& a, const T2& b) : 	  //ctor2
		first(a), second(b) {}

	template <class U1, class U2>		  //ctor3
	pair(const pair<U1, U2>& p) : 
		first(p.first), second(p.second) {}
};
  • 例一,可以使用 <鲫鱼,麻雀> 对象来构造一个 <鱼类,鸟类> 的pair

    侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第43张图片
  • 例二,父类指针是可以指向子类的,叫做 up-cast;智能指针也必须可以,所以其构造函数需要为==模板构造函数==

    侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第44张图片

3.3 模板模板参数

即模板中的一个模板参数也为模板,下图黄色高亮部分

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第45张图片
  • XCLs mylist 中即表示:容器 liststring 类型的—— 创建一个 string 的链表;Container c; 即表示 list c;

  • 但是这样 Container c; 语法过不了,容器 list 后面还有参数,需要用中间框和下面框下一行的代码 —— c++11的内容

注:下面不是模板模板参数

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第46张图片

class Sequence = deque 是有一个初始值,当没指定时就初始为 deque

在要指定时,如最后一行中的 list 是确切的,不是模板

4 specialization 特化

4.1 全特化 full specialization

模板是泛化,特化是泛化的反面,可以针对不同的类型,来设计不同的东西

  • 其语法为template<> struct xxx
template<>
struct hash<char>
{
...
    size_t operator()(char& x) const {return x;}
};

template<>
struct hash<int>
{
...
	size_t operator()(int& x) const { return x; }
};
  • 这里编译器就会用 int 的那段代码;注意:hash() 是创建临时变量
cout << hash<int>()(1000)

4.2 偏特化 partial specialization

4.2.1 个数上的偏

例如:第一个模板参数我想针对 bool 特别设计

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第47张图片

注意绑定模板参数不能跳着绑定,需要从左到右

4.2.2 范围上的偏

例如:想要当模板参数是指针时特别设计

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第48张图片
C<string> obj1; //编译器会调用上面的
C<string*> obj2; //编译器会调用下面的

5 三个C++11新特性

5.1 variadic templates

模板参数可变化,其语法为 ... (加在哪看情况)

// 当参数pack里没有东西了就调用这个基本函数结束输出
void print() {
}

// 用于打印多个参数的可变参数模板函数
template <typename T, typename... Args>
void print(const T& first, const Args&... args) {
    std::cout << first << " ";
    print(args...);  // 使用剩余参数进行递归调用
}

int main() {
    print(1, "Hello", 3.14, "World");
    return 0;
}

还可以使用 sizeof...(args) 来得到参数pack里的数量

5.2 auto

编译器通过赋值的返回值类型,自动匹配返回类型

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第49张图片

注:下面这样是不行的,第一行编译器找不到返回值类型

auto ite; // error
ite = find(c.begin(), c.end(), target);

5.3 ranged-base for

for 循环的新语法,for(声明变量 : 容器),编译器会从容器中依次拿出数据赋值给声明变量中

for (decl : coll)
{
    statement
}

//例
for (int i : {1, 3, 4, 6, 8}) // {xx,xx,xx} 也是c++11的新特性
{
    cout << i << endl;
}

注意:改变原容器中的值需要 pass by reference

vector<double> vec;
...

for (auto elem : vec) //值传递
{
    cout << elem << endl;
}
for (auto& elem : vec) //引用传递
{
    elem *= 3;
}

6 多态 虚机制

6.1 虚机制

当类中有虚函数时(无论多少个),其就会多一个指针—— vptr 虚指针,其会指向一个 vtbl 虚函数表,而 vtbl 中有指针一一对应指向所有的虚函数

有三个类依次继承,其中A有两个虚函数 vfunc1() vfunc2(),B改写了A的 vfunc1(),C又改写了B的 vfunc1(),子类在继承中对于虚函数会通过指针的方式进行——因为可能其会被改写

继承中,子类要继承父类所有的数据和其函数调用权,但虚函数可能会被改写,所以调用虚函数是==动态绑定==的,通过指针 p 找到 vptr,找到vtbl,再找到调用的第n个虚函数函数——( *(p->vptr[n]) )(p)

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第50张图片

编译器在满足以下三个条件时就会做==动态绑定==:

  1. 通过指针调用
  2. 指针是向上转型 up-cast ——Base* basePtr = new Derived;
  3. 调用的是虚函数

编译器就会编译成 ( *(p->vptr[n]) )(p) 这样来调用

例如:用一个 Shape(父类)的指针,调用 Circle(子类)的 draw 函数(每个形状的 draw 都不一样,继承自 Shape)

多态:同样是 Shape 的指针,在链表中却指向了不同的类型

list Mylist

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第51张图片

多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护

6.2 动态绑定

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第52张图片

a.vfunc1() 是通过对象来调用,是 static binding 静态绑定

在汇编代码中,是通过 call 函数的固定地址来进行调用的

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第53张图片

pa 是指针,是向上转型,是用其调用虚函数—— dynamic binding 动态绑定

在汇编代码中,调用函数的时候,蓝框的操作用 c语言 的形式即是 —— ( *(p->vptr[n]) )(p)

下面同理

7 reference、const、new/delete

7.1 reference

x 是整数,占4字节;p 是指针占4字节(32位);r 代表x,那么r也是整数,占4字节

int x = 0;
int* p = &x; // 地址和指针是互通的
int& r = x; // 引用是代表x

引用与指针不同,只能代表一个变量,不能改变

引用底部的实现也是指针,但是注意 object 和它的 reference 的大小是相同的,地址也是相同的(是编译器制造的假象)

sizeof(r) == sizeof(x)
&x == &r

reference 通常不用于声明变量,用于参数类型和返回类型的描述

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第54张图片

以下 imag(const double& im)imag(const double im) 的签名signature 在C++中是视为相同的——二者不能同时存在

double imag(const double& im) /*const*/ {....}
double imag(const double im){....} //Ambiguity

注意:const 是函数签名的一部分,所以加上后是可以共存的

7.2 const

const 加在函数后面 —— 常量成员函数(成员函数才有):表示这个成员函数保证不改变 class 的 data

const object non-const object
const member function(保证不改变 data members) ✔️ ✔️
non-const member function(不保证 data members 不变) ✔️

COWCopy On Write

多个指针共享一个 “Hello”;但当a要改变内容时, 系统会单独复制一份出来给a来改,即 COW

侯捷 八部曲 C++面向对象高级开发(上)+(下)【C++学习笔记】 超详细 万字笔记总结 笔记合集_第55张图片

在常量成员函数中,数据不能被改变所以不需要COW;而非常量成员函数中数据就有可能被改变,需要COW

charT
operator[] (size_type pos)const
{
	.... /* 不必考虑COW */   
}

reference
operator[] (size_type pos)
{
    .... /* 必须考虑COW */
}

函数签名不包括返回类型但包括const,所以上面两个函数是共存的

当两个版本同时存在时,const object 只能调用 const 版本,non-const object 只能调用 non-const 版本

7.3 new delete

7.3.1 全局重载

  • 可以全局重载 operator newoperator deleteoperator new[]operator delete[]
  • 这几个函数是在 new 的时候,编译器的分解步骤中的函数,是给编译器调用的

注意这个影响非常大!

inline void* operator new(size_t size){....}
inline void* operator new[](size_t size){....}
inline void operator delete(void* ptr){....}
inline void operator delete[](void* ptr){....}

7.3.2 class中成员重载

  • 可以重载 class 中成员函数 operator newoperator deleteoperator new[]operator delete[]
  • 重载之后,new 这个类时,编译器会使用重载之后的
class Foo
{
publicvoid* operator new(size_t size){....}
    void operator delete(void* ptr, size_t size){....} // size_t可有可无
    
    void* operator new[](size_t size){....}
    void operator delete[](void* ptr, size_t size){....} // size_t可有可无
    ....
}
// 这里优先调用 members,若无就调用 globals
Foo* pf = new Foo;
delete pf;

// 这里强制调用 globals
Foo* pf = ::new Foo;
::delete pf;

7.3.3 placement new delete

可以重载 class 成员函数 placement new operator new(),可以写出多个版本,前提是每一个版本的声明有独特的传入参数列,且其中第一个参数必须是 size_t,其余参数出现于 new(.....) 小括号内(即 placement arguments

Foo* pf = new(300, 'c') Foo; // 其中第一个参数size_t不用写
// 对应的operator new
void* operator new (size_t size, long extra, char init){....}

我们也可以重载对应的 class 成员函数 operator delete(),但其不会被delete调用,只当 new 调用的构造函数抛出异常 exception 的时候,才会调用来归还未能完全创建成功的 object 占用的内存

你可能感兴趣的:(C++,c++,学习,笔记)