侯捷 八部曲 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语言中,data和函数都是分别定义,根据类型创建的。这样创建出的变量,是全局的

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

class的两个经典分类:

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

1.2 头文件与类

1.2.1 头文件

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

头文件的标准写法:

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 模板初识

{complex<double> c1(2.5, 1.5);complex<int> c2(2, 6);...
}
  • 因为实部和虚部的类型不确定,可能是 double float int,定义一个模板类型叫做 T
  • T作为一个类型参数来传入,在调用的时候就可以指定类型了
  • 通过在定义类的前面加入一行代码 template<typename T> 来实现

2 构造函数

2.1 inline 函数

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

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

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

2.2 访问级别

  • 数据应该被定义为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/函数 重载

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

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

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


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

2.3.3 ctor 放在 private 区

  • 通常构造函数不要放在private中,这样外界没法调用,也就无法创建对象
  • 在设计模式 Singleton(单体)中,将构造函数放在了private中;这个class只有一份,外界想要调用的时候,只能使用定义的 getInstance() 函数来取得这一份;外界无法创建新的对象

2.4 const 常量成员函数

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

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

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

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

3.1 参数传递

  • 值传递 pass by value,传递value是把整个参数全传过去,尽量不要直接value传递double r

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

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

    const complex&

3.2 返回值传递

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

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


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

4 友元 friend

4.1 友元

友元: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

{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
}

所有的成员函数都带有一个隐藏的参数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 非成员函数实现

非成员函数没有this

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

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

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

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

5.1.3 output函数 << 的重载

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

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

#include <iostream.h>
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 带指针的类:三大函数

  • 析构函数:~String();

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

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

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

    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];  // 字符串长度 + /0strcpy(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 assignmentreturn *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

image-20230731092332395
  1. 分配内存:先用一个特殊函数,按 class 的定义分配了两个 double 的大小
  2. 转型(忽视)
  3. 调用构造函数,赋值(1,2)

7.3.2 delete

delete:先调用 dtor, 再释放 memory

image-20230731092947259
  1. 调用析构函数——释放的是 m_date 指向的字符串 Hello 的空间(即构造函数中 new 申请的空间)
  2. 释放内存:用一个特殊函数释放了 ps 指向的空间(即String* ps = new String("Hello");new 申请的空间)

7.4 内存动态分配

7.4.1 在VC下内存动态分配

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

image-20230731095853726
  • 调试模式:

    (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

image-20230731101729210

array new 一定要搭配 array delete

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

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

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

8 静态 模板 namespace

8.1 static

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

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

image-20230731154548833

对于静态的函数和数据:

静态函数没有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 nameAccount::set_rate(5.0);//调用静态函数法2——by objectAccount a;a.set_rate(7.0); //静态函数与a无关/无this
}

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

image-20230731163117925
  • 构造函数放在private中,外界无法调用
  • 设计了getInstance静态函数,来生成并返回唯一的一份

8.2 template

8.2.1 class template 类模板

  • T来代替某种类型
  • 使用时classname<type1> xxx,编译器会把T全部替换为type1

8.2.2 function template 函数模板

image-20230731164908165

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

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

8.3 namespace

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

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

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

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

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

9 复合 委托

9.1 Composition 复合

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

image-20230801093748678

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

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

9.1.1 复合下的构造和析构

image-20230801095529359
  • 构造是由内而外

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

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

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

  • 析构是由外而内

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

9.2 Delegation 委托

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

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

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

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

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

    image-20230801101907977

10 继承与虚函数

10.1 Inheritance 继承

语法::public base_class_name

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

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

10.1.1 继承下的构造和析构

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

image-20230801150053963
  • 构造是由内而外

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

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

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

  • 析构是由外而内

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

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

    注意:Base class 的 dtor 必需是 virtual

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

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

10.2 虚函数

image-20230801152023433
  • pure virtual 函数:

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

    语法:virtual xxxxxx =0;

  • virtual 函数:

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

    语法:virtual xxxxxx;

  • non-virtual 函数:

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

10.3 继承 with virtual

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

image-20230801154005427
  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

  • 类中的元素:image-20230802085810812 变量名称 : 变量类型(与代码刚好相反

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

    • 前面加一个 - 表示 private

    • 前面加一个 # 表示 protected

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

10.5 继承+复合

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

  • 第一种:

    image-20230801161457590
    • 构造由内到外 先 Base 再 Component

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

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

    • 析构由外而内 先 Component 再 Base

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

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

  • 第二种:

    image-20230801162202797

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

10.6 继承+委托

10.6.1 例一 Observer

设计模式—— Observer

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

image-20230801163932926

通过 vector<Observer> m_views; 来进行委托

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

10.6.2 例二 Composite

设计模式—— Composite

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

image-20230802082524919

要达成目的,就可以再设计一个父类 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

image-20230802163941216
  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-> 得到的东西会继续用 -> 作用上去,相当于这个->符号用了两次

image-20230807095542200

2.1.2 迭代器

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

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

image-20230807100734372
  • 下图 *ite 的意图是取 data——即一个 Foo 类型的 object
  • 下图 ite->method 的意图是调用 Foo 中的函数 method
image-20230807100804223

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()) {} //ctor1pair(const T1& a, const T2& b) : 	  //ctor2first(a), second(b) {}template <class U1, class U2>		  //ctor3pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {}
};
  • 例一,可以使用 <鲫鱼,麻雀> 对象来构造一个 <鱼类,鸟类> 的pair

    image-20230807152238567
  • 例二,父类指针是可以指向子类的,叫做 up-cast;智能指针也必须可以,所以其构造函数需要为==模板构造函数==

    image-20230807152501305

3.3 模板模板参数

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

image-20230807161321152
  • XCLs<string, list> mylist 中即表示:容器 liststring 类型的—— 创建一个 string 的链表;Container<T> c; 即表示 list<srting> c;

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

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

image-20230807162712548

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

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

4 specialization 特化

4.1 全特化 full specialization

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

  • 其语法为template<> struct xxx<type>
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<int>() 是创建临时变量
cout << hash<int>()(1000)

4.2 偏特化 partial specialization

4.2.1 个数上的偏

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

image-20230807155256372

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

4.2.2 范围上的偏

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

image-20230807160122944
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

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

image-20230808080207006

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

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)

image-20230808095746683

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

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

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

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

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

list<Shape*> Mylist

image-20230808104025485

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

6.2 动态绑定

image-20230808111646258

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

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

image-20230808112307107

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 通常不用于声明变量,用于参数类型和返回类型的描述

image-20230808091745371

以下 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 objectnon-const object
const member function(保证不改变 data members)✔️✔️
non-const member function(不保证 data members 不变)✔️

COWCopy On Write

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

image-20230801101907977

在常量成员函数中,数据不能被改变所以不需要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 占用的内存

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/42035.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

生活随笔,记录我的日常点点滴滴.

前言 &#x1f618;个人主页&#xff1a;曲终酣兴晚^R的小书屋&#x1f971; &#x1f615;作者介绍&#xff1a;一个莽莽撞撞的&#x1f43b; &#x1f496;专栏介绍&#xff1a;日常生活&往事回忆 &#x1f636;‍&#x1f32b;️每日金句&#xff1a;被人暖一下就高热&…

【sgDragSize】自定义拖拽修改DIV尺寸组件,适用于窗体大小调整

核心原理就是在四条边、四个顶点加上透明的div&#xff0c;给不同方向提供按下移动鼠标监听 &#xff0c;对应计算宽度高度、坐标变化 特性&#xff1a; 支持设置拖拽的最小宽度、最小高度、最大宽度、最大高度可以双击某一条边&#xff0c;最大化对应方向的尺寸&#xff1b;再…

一次Linux中的木马病毒解决经历(6379端口---newinit.sh)

病毒入侵解决方案 情景 最近几天一直CPU100%,也没有注意看到了以为正常的服务调用,直到腾讯给发了邮件警告说我的服务器正在入侵其他服务器的6379端口,我就是正常的使用不可能去入侵别人的系统的,这是违法的. 排查 既然入侵6379端口,就怀疑是通过我的Redis服务进入的我的系统…

JDBC封装与设计模式

什么是 DAO &#xff1f; Data Access Object(数据存取对象) 位于业务逻辑和持久化数据之间实现对持久化数据的访问 DAO起着转换器的作用&#xff0c;将数据在实体类和数据库记录之间进行转换。 ----------------------------------------------------- DAO模式的组成部分 …

数据结构--拓扑排序

数据结构–拓扑排序 AOV⽹ A O V ⽹ \color{red}AOV⽹ AOV⽹(Activity On Vertex NetWork&#xff0c;⽤顶点表示活动的⽹)&#xff1a; ⽤ D A G 图 \color{red}DAG图 DAG图&#xff08;有向⽆环图&#xff09;表示⼀个⼯程。顶点表示活动&#xff0c;有向边 < V i , V j …

算法与数据结构(二十四)最优子结构原理和 dp 数组遍历方向

注&#xff1a;此文只在个人总结 labuladong 动态规划框架&#xff0c;仅限于学习交流&#xff0c;版权归原作者所有&#xff1b; 本文是两年前发的 动态规划答疑篇open in new window 的修订版&#xff0c;根据我的不断学习总结以及读者的评论反馈&#xff0c;我给扩展了更多…

【STM32】高效开发工具CubeMonitor快速上手

工欲善其事必先利其器。拥有一个辅助测试工具&#xff0c;能极大提高开发项目的效率。STM32CubeMonitor系列工具能够实时读取和呈现其变量&#xff0c;从而在运行时帮助微调和诊断STM32应用&#xff0c;类似于一个简单的示波器。它是一款基于流程的图形化编程工具&#xff0c;类…

链表之第二回

欢迎来到我的&#xff1a;世界 该文章收入栏目&#xff1a;链表 希望作者的文章对你有所帮助&#xff0c;有不足的地方还请指正&#xff0c;大家一起学习交流 ! 目录 前言第一题&#xff1a;反转一个链表第二题&#xff1a;链表内指定区间反转第三题&#xff1a;判断一个链表…

opencv+ffmpeg+QOpenGLWidget开发的音视频播放器demo

前言 本篇文档的demo包含了 1.使用OpenCV对图像进行处理&#xff0c;对图像进行置灰&#xff0c;旋转&#xff0c;抠图&#xff0c;高斯模糊&#xff0c;中值滤波&#xff0c;部分区域清除置黑&#xff0c;背景移除&#xff0c;边缘检测等操作&#xff1b;2.单纯使用opencv播放…

一个案例:Vue2组件化开发组件从入门到入土

1. 环境搭建 1.1. 创建项目 npm install -g vue/clivue create vue_study_todolist1.2. 清空项目代码 清楚HelloWorld.Vue代码中的内容。 1.3. 启动空项目 1.4 项目目标 项目组件实现以下效果 2. 组件拆分代码 Vue是一个基于组件的框架&#xff0c;允许您将界面拆分成小的…

Golang使用MinIO

最近在使用Golang做了一个网盘项目&#xff08;学习&#xff09;&#xff0c;文件存储一直保存在本地&#xff08;各厂商提供的oss贵&#xff09;&#xff0c;所以就在思考怎么来处理这些文件&#xff0c;类似的方案很对hdfs、fastdfs&#xff0c;但这其中MinIO是最近几年比较火…

生信豆芽菜-差异基因富集分析的圈图

网址&#xff1a;http://www.sxdyc.com/visualsEnrichCirplot 1、数据准备 准备一个基因集的文件 2、选择富集分析的数据库&#xff0c;同时输入展示top几的条目&#xff0c;选择颜色&#xff0c;如果是GO的话选择三个颜色&#xff0c;如果是KEGG选择一个&#xff0c;如果是G…

神经网络论文研读-多模态方向-综述研读(上)

翻译以机翻为主 原文目录 前言 图1&#xff1a;LMU印章&#xff08;左&#xff09;风格转移到梵高的向日葵绘画&#xff08;中&#xff09;并与提示混合 - 梵高&#xff0c;向日葵 -通过CLIPVGAN&#xff08;右&#xff09;。在过去的几年中&#xff0c;自然语言处理&#xff…

无涯教程-Perl - tell函数

描述 此函数返回指定FILEHANDLE中读取指针的当前位置(以字节为单位)。如果省略FILEHANDLE,则它将返回上次访问的文件中的位置。 语法 以下是此函数的简单语法- tell FILEHANDLEtell返回值 此函数以字节为单位返回当前文件位置。 例 以下是显示其基本用法的示例代码,要检…

leetcode473. 火柴拼正方形(回溯算法-java)

火柴拼正方形 leetcode473 火柴拼正方形题目描述回溯算法 上期经典算法 leetcode473 火柴拼正方形 难度 - 中等 原题链接 - leetcode473 火柴拼正方形 题目描述 你将得到一个整数数组 matchsticks &#xff0c;其中 matchsticks[i] 是第 i 个火柴棒的长度。你要用 所有的火柴棍…

微服务—Eureka注册中心

eureka相当于是一个公司的管理人事HR,各部门之间如果有合作时&#xff0c;由HR进行人员的分配以及调度&#xff0c;具体选哪个人&#xff0c;全凭HR的心情&#xff0c;如果你这个部门存在没有意义&#xff0c;直接把你这个部门撤销&#xff0c;全体人员裁掉&#xff0c;所以不想…

Android Studio瀑布流实现

效果&#xff1a; ImageDetail class package com.example.waterfallflow; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.widget.ImageView;public class ImageDetail extends Activity{Overrideprotected void …

DNNGP、DeepGS 和 DLGWAS模型构成对比

一、DNNGP DNNGP 是基于深度卷积神经网络&#xff0c;这个结构包括一个输入层&#xff0c;三个卷积层&#xff0c;一个批标准化层&#xff0c;两个dropout层&#xff0c;一个平坦化层&#xff0c;一个 dense层。 dropout层&#xff1a;在神经网络中,dropout层是一个非常有效的正…

信息与通信工程面试准备——数学知识|正态分布|中心极限定理

目录 正态分布 正态分布的参数 正态分布的第一个参数是均值 正态分布的第二个参数是标准差SD 所有正态分布的共同特征 标准正态分布&#xff1a;正态分布的特例 中心极限定理 理解定义 示例# 1 示例# 2 知道样本均值总是正态分布的实际含义是什么&#xff1f; 正态分…

Scala 如何调试隐式转换--隐式转换代码的显示展示

方法1 在需要隐式转换的地方&#xff0c;把需要的参数显示的写出。 略方法2&#xff0c;查看编译代码 在terminal中 利用 scalac -Xprint:typer xxx.scala方法打印添加了隐式值的代码示例。 对于复杂的工程来说&#xff0c;直接跑到terminal执行 scalac -Xprint:typer xxx.…