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

news2024/12/23 10:29:21

文章目录

  • Ⅰ 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];  // 字符串长度 + /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

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 name
	Account::set_rate(5.0);
    //调用静态函数法2——by object
	Account 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()) {} //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

    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.coloradmin.cn/o/891496.html

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

相关文章

计算机竞赛 python图像检索系统设计与实现

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; python图像检索系统设计与实现 &#x1f947;学长这里给一个题目综合评分(每项满分5分) 难度系数&#xff1a;3分工作量&#xff1a;3分创新点&#xff1a;4分 该项目较为新颖&#xff0c…

Hands on RL 之 Off-policy Maximum Entropy Actor-Critic (SAC)

Hands on RL 之 Off-policy Maximum Entropy Actor-Critic (SAC) 文章目录 Hands on RL 之 Off-policy Maximum Entropy Actor-Critic (SAC)1. 理论基础1.1 Maximum Entropy Reinforcement Learning, MERL1.2 Soft Policy Evaluation and Soft Policy Improvement in SAC1.3 Tw…

【Java高级开发高频面试题】面试者角度的口述版

文章目录 1.具备扎实的Java基础集合HashMap底层工作原理HashMap版本问题HashMap并发修改异常HashMap影响HashMap性能的因素HashMap使用优化 SynchronizedThreadLocalAQS线程池JVM内存模型类加载机制与双亲委派垃圾回收算法、垃圾回收器、空间分配担保策略引用计数器算法、可达性…

小白到运维工程师自学之路 第七十七集 (基于Prometheus监控Kubernetes集群)

一、Prometheus简介 Prometheus是由SoundCloud开发的开源监控报警系统和时序列数据库(TSDB)&#xff1b;Prometheus使用Go语言开发&#xff0c;是Google BorgMon监控系统的开源版本&#xff1b;2016年由Google发起Linux基金会旗下的原生云基金会(Cloud Native Computing Found…

Next.js - Route Groups(路由组)

路由组的作用 在应用程序目录中&#xff0c;嵌套文件夹通常会映射到 URL 路径。不过&#xff0c;您可以将文件夹标记为路由组&#xff0c;以防止该文件夹包含在路由的 URL 路径中。 这样就可以在不影响 URL 路径结构的情况下&#xff0c;将路由段和项目文件组织到逻辑组中。 …

Vue 根据Upload组件的before-upload方法,限制用户上传文件的类型及大小

文章目录 一、前端 Vue Upload组件的before-upload方法二&#xff0c;使用方法 一、前端 Vue Upload组件的before-upload方法 判断用户上传的文件是否符合要求&#xff0c;可以根据文件类型或者大小做出限制。 文件类型值docapplication/msworddocxapplication/vnd.openxmlform…

Docker基础入门:常规软件安装与镜像加载原理

Docker基础入门&#xff1a;常规软件安装与镜像加载原理 一、Docker常规软件安装1.1、部署nginx1.2、部署tomcat1.3、部署elasticsearch1.4、如何部署kibana-->连接elasticsearch1.5、部署可视化工具 二、 镜像加载原理2.1、镜像是什么2.2、Docker镜像加速原理2.3、分层理解…

【Python程序设计】基于Python Flask 机器学习的全国+上海气象数据采集预测可视化系统-附下载链接以及详细论文报告,原创项目其他均为抄袭

基于Python Flask 机器学习的全国上海气象数据采集预测可视化系统 一、项目简介二、开发环境三、项目技术四、功能结构五、运行截图六、功能实现七、数据库设计八、源码下载地址 一、项目简介 在信息科技蓬勃发展的当代&#xff0c;我们推出了一款基于Python Flask的全国上海气…

什么是合成数据(Synthetic Data)?

关于合成数据您需要知道的一切 推出人工智能&#xff08;AI&#xff09;的企业在为其模型采集足够的数据方面会遇到一个主要障碍。对于许多用例来说&#xff0c;正确的数据根本不可用&#xff0c;或者获取数据非常困难且成本高昂。在创建AI模型时&#xff0c;数据缺失或不完整…

哨兵1号处理流程

分步步骤 1.打开.mainsafe文件 2.轨道校正&#xff1a;Radar—>Apply Orbit File&#xff0c;勾上Do not fail 3.热噪声去除&#xff1a;Radar—>Radiometric—>S-1 Thermal Noise 4.辐射定标&#xff1a;Radar—>Radiometric—>Calibrate &#xff08;只选outp…

day20 飞机大战射击游戏

有飞行物类 飞行 爆炸 的连环画&#xff0c; 飞行的背景图 &#xff0c; 子弹图&#xff0c; 还有游戏开始 暂停 结束 的画面图。 设计一个飞机大战的小游戏&#xff0c; 玩家用鼠标操作hero飞行机&#xff0c; 射出子弹杀死敌机&#xff0c;小蜜蜂。 敌机可以获得分数&…

Vue2集成Echarts实现可视化图表

一、依赖配置 1、引入echarts相关依赖 也可以卸载原有的&#xff0c;重新安装 卸载&#xff1a;npm uninstall echarts --save 安装&#xff1a;npm install echarts4.8.0 --save 引入水球图形依赖 npm install echarts-liquidfill2.0.2 --save 水球图可参考文档&#xff1…

【Python】使用python解析someip报文,以someip格式打印报文

文章目录 1.安装scapy库2.示例 1.安装scapy库 使用 pip 安装 scapy 第三方库&#xff0c;打开 cmd&#xff0c;输入以下命令&#xff1a; pip install scapy出现如图所示&#xff0c;表示安装成功&#xff1a; 2.示例 要解析someip格式报文&#xff0c;需要导入someip模块&a…

rabbitmq的死信队列

目录 成为死信的条件 消息TTL过期 队列达到最大长度 消息被拒 延迟队列 延迟队列使用场景 消息设置 TTL 队列设置 TTL 两者区别 producer 将消息投递到 broker 或者直接到 queue 里了&#xff0c; consumer 从 queue 取出消息 进行消费&#xff0c;但某些时候由…

汇聚产学合力二十载,2023英特尔学术大会在南京开幕

8月16日&#xff0c;以“合聚创 共IN智能时代”为主题的“2023英特尔&#xff08;中国&#xff09;学术大会”在南京开幕&#xff0c;邀请专家学者共话科技界前沿趋势&#xff0c;展示科研成果和技术解决方案。本次大会延续了英特尔“为智能而聚能”&#xff0c;推动中国产业界…

ShowMeBug CEO李亚飞受邀出席ArchSummit 全球架构师峰会

2023年7月21-22日&#xff0c;极客邦科技旗下InfoQ中国举办的ArchSummit 全球架构师峰会&#xff08;深圳站&#xff09;2023 在深圳顺利召开。本次会议&#xff0c;聚集了国内外数百位架构师专家来分享技术内容&#xff0c;像MySQL之父、科大讯飞涵盖语言大模型、AIGC、可观测…

214、仿真-基于51单片机温度甲醛一氧化碳(co)电机净化报警Proteus仿真设计(程序+Proteus仿真+配套资料等)

毕设帮助、开题指导、技术解答(有偿)见文未 目录 一、硬件设计 二、设计功能 三、Proteus仿真图 四、程序源码 资料包括&#xff1a; 需要完整的资料可以点击下面的名片加下我&#xff0c;找我要资源压缩包的百度网盘下载地址及提取码。 方案选择 单片机的选择 方案一&a…

Spring系列七:声明式事务

&#x1f418;声明式事务 和AOP有密切的联系, 是AOP的一个实际的应用. &#x1f432;事务分类简述 ●分类 1.编程式事务: 示意代码, 传统方式 Connection connection JdbcUtils.getConnection(); try { //1.先设置事务不要自动提交 connection.setAutoCommit(false…

【数理知识】向量与基的内积,Matlab 代码验证

序号内容1【数理知识】向量的坐标基表示法&#xff0c;Matlab 代码验证2【数理知识】向量与基的内积&#xff0c;Matlab 代码验证 文章目录 1. 向量与基的内积2. 二维平面向量举例3. 代码验证Ref 1. 向量与基的内积 假设存在一个二维平面内的向量 a ⃗ \vec{a} a &#xff0c…

复旦微FR0触摸原理(1)

传统的家电产品通常使用物理按键来进行操作&#xff0c;但随着科技的不断进步&#xff0c;越来越多的家电产品开始采用触摸屏幕和触摸按键来提供更加智能化和便捷的操作方式。 本篇介绍复旦微FM33FR026的触摸检测原理 TSI 模块使用自电容的 方法来检测触摸行为。 自电容检测的原…