C++面向对象高级编程(侯捷)笔记2

侯捷C++面向对象高级编程

本文是学习笔记,仅供个人学习使用,如有侵权,请联系删除。

如果你对C++面向对象的组合、继承和委托不了解,对什么是拷贝构造、什么是拷贝赋值和析构不清楚,对类设计中的Adapter、pImpl、Template method、Observer、Composite、Prototype等设计模式认识不清,想知道在面向对象中如何引入设计模式,那么这门课就是你需要的。

学习地址:

Youtube: C++面向对象高级编程(侯捷)

B站: 侯捷C++之C++面向对象高级编程(上)

文章目录

  • 侯捷C++面向对象高级编程
    • 7 三大函数:拷贝构造、拷贝赋值、析构
    • 8 堆 栈和内存管理
    • 9 复习String类的实现过程
    • 10 扩展补充:类模板,函数模板及其他
    • 11 组合与继承
      • Composition复合,表示has-a
      • Delegation委托. Composition by reference
      • Inheritance(继承),表示is-a
    • 12 虚函数和多态
    • 13 委托相关设计

7 三大函数:拷贝构造、拷贝赋值、析构

Class with pointer member(s)

String class的框架

// string.h
#ifndef __MYSTRING__
#define __MYSTRING__

// 1
class String
{
    ...
};
// 2
String::function(...)...
Global-function(...)...
#endif
    
    
// string-test.cpp
int main()
{
    String s1();
    String s2("hello");
    
    String s3(s1);
    cout << s3 << endl;
    s3 = s2;
    cout << s3 << endl;
}

Big Three 三个特殊函数:拷贝构造,拷贝赋值,析构函数

class String
{
public:
    String(const char* cstr = 0);
    String(const String& str); // 拷贝构造,接收的是自己一样的类型String
    String& operator = (const String& str);  // 拷贝赋值
    ~String();  // 析构函数
    char* get_c_str() const {return m_data;}
private:
    char* m_data;
};

构造函数和析构函数

class里面有指针,多半要动态分配,对象要死亡之前,会调用析构函数,在析构函数里面释放掉动态分配的内存。

inline
String::String(const char* cstr = 0)
{
    if(cstr) {
        m_data = new char[strlen(cstr) + 1];  // +1是因为最后还有一个结束符号\0
        strcpy(m_data, cstr);
    }
    else {  // 未指定初值,设为空字符串
        m_data = new char[1];
        *m_data = '\0';
    }
}

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

// 使用
{
    String s1();
    String s2("hello");
    
    String* p = new String("hello");
    delete p;
}

Class with pointer member(s)必须要拷贝构造和拷贝赋值

String a("hello");
String b("hello");

b = a;

使用默认拷贝构造或者默认拷贝赋值,会造成b的m_data指针指向a的空间,造成b原本指向的内存空间泄露(没有指针指向它),这个叫浅拷贝(只是指针指过去,不创建新的空间)

深拷贝:创建一份新的内存空间并拷贝过去

inline
String::String(const String& str)
{
	m_data = new char[strlen(str.m_data) + 1];
    strcpy(m_data, str.m_data);
}

// 使用
{
    String s1("hello");
    String s2(s1);
    String s2 = s1;
}

拷贝赋值操作 copy assignment operator

inline
String& String::operator=(const String& str)
{
    if(this == &str)  // 检测自我赋值
        return *this;
    delete[] m_data;  // 1. 先释放掉自己的部分的空间
	m_data = new char[strlen(str.m_data) + 1]; // 2.重新分配跟str一样大的空间
    strcpy(m_data, str.m_data); // 3.将str的内容拷贝到新分配的空间
    return *this;
}

// 使用
{
    String s1("hello");
    String s2(s1);
    String s2 = s1;
}

一定要在拷贝赋值操作里面检查自我赋值

如果是 s1 = s1,若没有检查自我赋值的情况下,首先会delete掉自己,然后重新分配右边的(自己)同样大小的空间就无从谈起了,会产生不确定的行为。

output函数

#include<iostream.h>
ostream& operator <<(ostream& os, const String& str)
{
    os << str.get_c_str();
    return os;
}

// 使用
{
    String s1("hello");
    cout << s1;
}

8 堆 栈和内存管理

所谓stack,所谓heap

Stack:是存在于某作用域的一块内存空间。例如当调用函数时,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址。

在函数体内声明的任何变量,其所使用的内存块都取自stack。

Heap:是指由操作系统提供的一块global内存空间,程序可变动分配从其中获得若干区块。

class Complex {...};
...
{
    Complex c1(1, 2);  // c1占用的空间来自stack
    Complex* p = new Complex(3); // 从堆动态分配而来
}

stack objects的生命期

c1就是所谓的stack object,其生命在作用域结束之际结束。这种作用域内的object,又被称为auto object,因为它会被自动清理。

static local objects的生命期

class Complex {...};
...
{
    static Complex c2(1, 2); 
}

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

global objects的生命期

class Complex {...};
...
Complex c3(1, 2); 

int main()
{
    ...
}

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

heap objects的生命期

class Complex {...};
...
{
    Complex* p = new Complex(3); 
    delete p; // delete指针
}

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

class Complex {...};
...
{
    Complex* p = new Complex(3); 
}

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

new:先分配memory,再调用构造函数

Complex* pc = new Complex(1, 2);

// new被编译器转化为:
Complex *pc;
void* mem = operator new(sizeof(Complex)); // 1. 分配内存, operator new是一个函数名,内部调用malloc函数
pc = static_cast<Complex*>(mem); // 2. 转型
pc->Complex::Complex(1, 2); // 3. 构造函数

// 上面构造函数隐藏的this指针:
// 这里构造函数是成员函数,所以会有一个隐藏的this指针,谁调用这个函数,谁就是this
Complex::Complex(pc, 1, 2);// 这里pc就是this,只不过它是隐藏的

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

Complex* pc = new Complex(1, 2);
delete pc;

// delete被编译器转化为:
Complex::~Complex(pc); // 1. 析构函数
operator delete(pc);// 2. 释放内存, delete内部调用free(pc),operator delete是一个函数名

array new 一定要搭配array delete

String* p = new String[3];
delete[] p; // 唤起3次析构函数,正确的用法

//错误的用法,不加[]
String* p = new String[3];
delete p; // 唤起1次析构函数,错误的用法

在这里插入图片描述

9 复习String类的实现过程

String类的具体类,构造函数,成员函数等如上面的笔记所示。

10 扩展补充:类模板,函数模板及其他

进一步补充:static

静态成员:当类的成员被定义为静态的时候,无论创建多少个类的对象,静态成员的副本只有一个。静态成员在类的所有对象中是共享的。

静态成员函数:可以把函数和类的对象独立开来,静态成员函数可以在类的对象不存在的情况下被调用。静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。

static function静态函数没有this指针

静态变量的初始化不能放在类的定义中,在类的外部来重新声明来进行初始化。

C++ 类的静态成员

银行的例子:利率为静态变量

class Account {
public:
    static double m_rate;
    static void set_rate(const double& x) { m_rate = x;}
};
double Account::m_rate = 8.0;

int main(){
    Account::set_rate(5.0);
    Account a;
    a.set_rate(7.0);
}

调用static函数的方式有:

(1)通过object调用

(2)通过class name调用

构造函数可以被放在private区,在设计模式中,单例模式(singleton)便是这样做的

class A 
{
public:
    static A& getInstance();
    setup() { ... }
private:
    A();
    A(const A& rhs);
    ...
}

A& A::getInstance() 
{
    static A a;  //只有调用的时候,A才会创建,离开这个函数之后,静态变量A依然存在
    return a;
}

外界调用时

A::getInstance().setup();

没有人使用单例的话,这个单例就不存在;一旦有人用了一次,单例才出现,并且只有一份。

进一步补充 cout

在这里插入图片描述

cout是一种ostream,这里对<<进行了各种各样的重载,所以cout可以打印输出各种各样的数据。

进一步补充:class template 类模板

template<typename T>
class complex
{
 public:
    complex(T r = 0, T i = 0)
        : re(r), im(i)  // 构造函数的初始化列
    {}
    complex& operator += (const complex&);
    T real() const { return re;}
    T imag() const { return im;}
 private:
    T re, im;
    friend complex& __doapl(complex*, const complex&);
};

// 使用模板的例子,指定T的具体类型
{
    complex<double> c1(2.5, 1.5);
    complex<int> c2(2, 6);
    ...
}

进一步补充:function template 函数模板

在这里插入图片描述

stone r1(2, 3), r2(3, 3), r3;
r3 = min(r1, r2); // 编译器会对function template 进行引数推导

template<class T>  // 引数推导的结果,T为stone,于是调用stone::operator<
inline
const T& min(const T&, const T&, b)
{
    return b < a ? b : a;
}

class stone
{
public:
    stone(int w, int h, int we)
        : _w(w), _h(h), _weight(we) {}
    bool operator<(const stone& rhs) const {return _weight < rhs._weight;}
 
private:
    int _w, _h, _weight;
};

进一步补充:namespace

命名空间定义了上下文,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。命名空间

namespace name
{
	...
}

11 组合与继承

Composition复合,表示has-a

template<class T, class Sequence = deque<T> >
class queue {
...
protected:
    Sequence c; // 底层容器
public:
    // 以下完全利用c的操作函数完成
    bool empty() const {return c.empty();}
    size_type size() const {return c.size();}
    reference front() {return c.front();}
    reference back() {return c.back();}
    // deque是两端可进出,queue是末端进前端出(先进先出)
    void push(const value_type& x) {c.push_back(x);}
    void pop() {c.pop_front();}
};

把上面的class Sequence = deque替换进来

class queue {
...
protected:
    deque<T> c; // 底层容器
public:
    // 以下完全利用c的操作函数完成
    bool empty() const {return c.empty();}
    size_type size() const {return c.size();}
    reference front() {return c.front();}
    reference back() {return c.back();}
    // deque是两端可进出,queue是末端进前端出(先进先出)
    void push(const value_type& x) {c.push_back(x);}
    void pop() {c.pop_front();}
};

其中 queue里面has a deque,就是复合关系

这里还有设计模式中的Adapter模式,queue使用deque开放的6个函数,改头换面改装适配一下。这里谁是adapter呢?queue。

UML类图中使用黑色菱形代表复合。

在这里插入图片描述

从内存的角度看一下composition

如下图,Itr里面有4个指针,大小共为16B;deque里面有2个Itr、1个指针、1个unsigned int,每个Itr的大小为16B,deque大小共为40B;queue里面有deque,故为40B。

在这里插入图片描述

Composition复合关系下的构造和析构

在这里插入图片描述

构造由内而外

Container的构造函数首先调用Component的默认构造函数,然后才执行自己。这是编译器帮我们做的。

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

析构由外而内

Container的析构函数首先执行自己,然后才调用Component的析构函数。

Container::~Container(...) {... ~Component() };

Delegation委托. Composition by reference

还是Container“有“ component,只是有一个指针指向另一个component,比如下面的String类中rep指针,指向StringRep类。

UML类图中用白色的菱形表示委托。

// file String.hpp
class StringRep;
class String {
public:
    String();
    String(const char* s);
    String(const String& s);
    String& operator=(const String& s);
    ~String();
private:
    StringRep* rep; // pimpl
};
// file String.cpp
#include"String.hpp"
namespace {
class StringRep{
friend class String;
    StringRep(const char* s);
    ~StringRep();
    int count;
    char* rep;
};
}

String::String() {...}
...

在这里插入图片描述

pimpl:pointer to Implementation,另一个名字叫做handle/body

外界看到的是左边的接口,而右边的实现通过一个指针指向,外界看不到。

Inheritance(继承),表示is-a

子类继承父类的数据,成员函数

struct _List_node_base
{
	_List_node_base* _M_next;
    _List_node_base* _M_prev;
};

template<typename _Tp>
struct _List_node
    : public _List_node_base  // 继承的语法
{
 	_Tp _M_data;       
};

UML类图中使用空心的三角形表示继承,从子类指向父类

在这里插入图片描述

继承关系下的构造和析构

父类是Base,Derived是派生类,派生类的对象里面有父类的成分在里面。

构造由内而外

Derived的构造函数首先调用Base的默认构造函数,然后才执行自己。

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

析构由外而内

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

Derived::~Derived(...) {... ~Base()};

在这里插入图片描述

base class的析构函数必须是virtual,否则会出现undefined behavior。

12 虚函数和多态

Inheritance with virtual functions 虚函数

non-virtual函数:不希望derived class重新定义

virtual函数:希望derived class重新定义(override),且它已有默认定义。

pure virtual函数:希望derived class一定要重新定义,对它没有默认定义

class Shape{
public:
    virtual void draw() const = 0;  // 纯虚函数, 被子类重新定义
    virtual void error(const std::string& msg);  // 虚函数, 父类有默认的定义
    int objectID() const;
};
class Rectangle:public Shape{...};
class Ellipse:public Shape{...};

打开文件的例子

在这里插入图片描述

这种写法是一个大名鼎鼎的设计模式:Template method

函数的一个动作延缓到子类去实现。应用框架先把固定功能的函数写好,遇到无法决定的函数把它写成虚函数,让应用这个框架的子类去定义它。

在这里插入图片描述

13 委托相关设计

委托和继承

Observer设计模式:定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新。

Subject:比如excel中的数据

Observer:使用者可以开多个窗口来观察Subject,比如使用柱状图、折线图等不同的窗口来观察excel中的数据。这就是通过派生子类来实现的。

Subject和Observer是指针指向(委托)的关系

class Subject
{
    int m_value;
    vector<Observer*> m_views; // 委托,使用指针指向另一个类
 public:
    void attach(Observer* obs)
    {
		m_views.push_back(obs);
    }
    
    void set_val(int value)
    {
        m_value = value;
        notify();
    }
    
    void notify()
    {
        for (int i = 0; i < m_view.size(); ++i)
            m_views[i]->update(this, m_value);
    }
};

class Observer
{
public:
    virtual void update(Subject* sub, int value) = 0;
};

在这里插入图片描述

Composite设计模式

组合模式(Composite Pattern),又叫部分整体模式,是用于把一组相似的对象当作一个单一的对象。

这里是用Primitive,Composite和Component三个类合作完成的。Composite类想要放不同类的对象,这样让不同的类继承自同一个父类,这样就可以把父类的指针存在Composite里,从而实现把一组相似的对象都存进来。

在这里插入图片描述

Prototype设计模式

原型模式

原型模式(Prototype Pattern)是用于创建重复的对象,同时又能保证性能。这种模式是实现了一个原型接口,该接口用于创建当前对象的克隆。

遇到问题:如下图所示,波浪线上面是提前写好的类,但是它要用到未来才出现的类,该怎么办呢?

波浪线下面的类继承上面的类,然后调用父类的addPrototype将自己添加到父类的空间,让父类看到子类。子类调用clone函数生成自己的拷贝。

在这里插入图片描述

父类如下:

在这里插入图片描述

子类如下:下图中的ctor指的是构造函数

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用定时器setInterval,在Moment.js 时间格式化插件基础上完成当前时间持续动态变化

1、引入插件 npm install moment --save 2、js配置&#xff1a;当前需要使用的文件中直接引入 import moment from moment; 3、代码实现&#xff1a;定义一个变量进行回显 3.1、dom部分 <span> {{ timeData }} </span> 3.2、js代码 <script> import mo…

直播获奖(live)CSP2020

描述 NOI2130 即将举行。 为了增加观赏性&#xff0c;CCF 决定逐一评出每个选手的成绩&#xff0c;并直播即时的获奖分数线。 本次竞赛的获奖率为w&#xff0c;即当前排名前w 的选手的最低成绩就是即时的分数线。 更具体地&#xff0c;若当前已评出了 p 个选手的成绩&#…

GROUP_CONCAT报错解决

有如下表 其中awardee和awardee_unit都是保存的json类型的字符串, awardee是多个人员id, awardee_unit是部门的全路径 查询时要注意转换 需要将name拼接起来合并成一行,直接 GROUP_CONCAT 会报错 百度的大部分答案是修改数据库配置去掉严格模式,如果不方便修改数据库可以这样…

力扣刷题-二叉树-最大二叉树

654.最大二叉树 给定一个不含重复元素的整数数组。一个以此数组构建的最大二叉树定义如下&#xff1a; 二叉树的根是数组中的最大元素。 左子树是通过数组中最大值左边部分构造出的最大二叉树。 右子树是通过数组中最大值右边部分构造出的最大二叉树。 通过给定的数组构建最大…

Javascript学习之路:事件在页面加载和关闭时的执行顺序

&#x1f449;前言 在实际项目中&#xff0c;如果我想在打开浏览器页面加载时执行某些任务&#xff0c;比如获取当前页面的全部或部分数据&#xff0c;优先想到的事件是onload&#xff0c;当关闭页面时&#xff0c;我们也常常会使用onunload事件。但是这两个事件却有一定的局限…

图解算法数据结构-LeetBook-回溯01_机械累加器

请设计一个机械累加器&#xff0c;计算从 1、2… 一直累加到目标数值 target 的总和。注意这是一个只能进行加法操作的程序&#xff0c;不具备乘除、if-else、switch-case、for 循环、while 循环&#xff0c;及条件判断语句等高级功能。 注意&#xff1a;不能用等差数列求和公式…

多级缓存、OpenResty、Redis分布式缓存、进程缓存

目录标题 一、预期表现二、环境配置1、nginx环境2、OpenResty环境3、redis环境4、进程缓存环境 三 、主要编码工作3.1、OpenResty编码openresty/nginx/conf/nginx.conflualib/redis_common.lualualib/common.luanginx/lua/item.lua 3.2、进程缓存编码 四、结果分析4.1 初始无缓…

【KingbaseES】实现MySql函数len

原理&#xff1a;调用length函数返回到结果中 CREATE OR REPLACE FUNCTION len(input_text text) RETURNS integer AS $$ BEGIN RETURN length(input_text); END; $$ LANGUAGE plpgsql;结果如下&#xff1a;

Docker介绍、常用命令、项目部署

什么是Docker 简单说&#xff1a;Docker就是一个虚拟机&#xff0c;专业说&#xff1a;它是一个开源的容器平台。它和我们常用的VMware有很多相似的地方。 名词解释 镜像/images 由本体打包出来的文件。并不是文件本身&#xff0c;但是具有该文件的功能。举个不太贴切的例子&…

毛虫目标检测数据集VOC格式550张

毛虫&#xff0c;一种令人惊叹的生物&#xff0c;以其独特的外貌和习性&#xff0c;成为了自然界中的一道亮丽风景。 毛虫的外观非常特别&#xff0c;身体呈圆柱形&#xff0c;表面覆盖着许多细小的毛发&#xff0c;这使得它们在叶子上伪装得非常好。它们的头部有一对坚硬的颚…

【JavaSE】P1~P32 进制,字节,Java代码规范,常量,变量,运算符

目录 IDEA常用快键键和快捷用法一、需要复习的重难点概念1 进制、字节、DOS操作命令语句进制及转化字节DOS系统命令提示符 2 Java 常用代码规范3 常量及常量的打印4 变量数据类型及取值范围变量的概念和使用数据类型转换ASCII码表&#xff08;128种&#xff09;基本数据类型极其…

P1019 [NOIP2000 提高组] 单词接龙

网址如下&#xff1a;P1019 [NOIP2000 提高组] 单词接龙 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 很怪&#xff0c;不知道该说什么 我试了题目给的第一个测试点的输入&#xff0c;发现输出和测试点的一样&#xff0c;但是还是WA 不是很懂为什么 有没有大佬帮我看一下…

用 Python 抓取 bilibili 弹幕并分析!

01 实现思路 首先&#xff0c;利用哔哩哔哩的弹幕接口&#xff0c;把数据保存到本地。接着&#xff0c;对数据进行分词。最后&#xff0c;做了评论的可视化。 02 弹幕数据 平常我们在看视频时&#xff0c;弹幕是出现在视频上的。实际上在网页中&#xff0c;弹幕是被隐藏在源代码…

14.两数之和

题目 class Solution {public int[] twoSum(int[] nums, int target) {int[] ret {-1,-1};for(int i0;i<nums.length;i) {for(int ji1;j<nums.length;j) {if(nums[i] nums[j] target) {ret[0] i;ret[1] j;}}}return ret;} }

顶顶通呼叫中心中间件配置背景音乐(mod_cti基于FreeSWITCH)

介绍 配置外呼任务拨打时的背景音乐&#xff0c;一步步配置 一、配置队列外呼模板 打开ccadmin->队列呼叫模板->添加一条变量根据下方图片配置 二、上传音乐文件 后缀格式为.wav格式的声音文件&#xff0c;声音文件需要上传在这个目录&#xff1a;/ddt/fs/sounds/ct…

【编译原理】期末预习PPT前四章笔记II

看了看学校的ppt&#xff0c;记的比较随意O.o 因为我的考试范围里边没有简答所以概念什么的没怎么记 没有简答只有选择真是太好了嘿嘿嘿 目录 I. 概述&#xff08;好多字。。&#xff09; 一、高级语言的分类 1、体裁 2、执行方式 二、各种语言的执行方式 三、编译程序…

基于ssm的医院交互系统+vue论文

医院交互系统的设计与实现 摘要 当下&#xff0c;正处于信息化的时代&#xff0c;许多行业顺应时代的变化&#xff0c;结合使用计算机技术向数字化、信息化建设迈进。传统的医院交互信息管理模式&#xff0c;采用人工登记的方式保存相关数据&#xff0c;这种以人力为主的管理模…

CCNP课程实验-07-OSPF-Trouble-Shooting

目录 实验条件网络拓朴 环境配置开始排错错点1&#xff1a;R1-R2之间认证不匹配错误2&#xff1a;hello包的时间配置不匹配错误3&#xff1a;R2的e0/1接口区域配置不正确错误4&#xff1a;R4的e0/1接口没有配置进OSPF错误5&#xff1a;R2的区域1没有配置成特殊区域错误6&#x…

RK3568 学习笔记 : ubuntu 20.04 下 Linux-SDK 镜像烧写

前言 开发板&#xff1a;【正点原子】ATK-DLRK3568 开发板&#xff0c;编译完 Linux-SDK 后&#xff0c;生成了相关的镜像文件&#xff0c;本篇记录一下 镜像烧写&#xff0c;当前编译环境在 VMware 虚拟机中&#xff0c;虚拟机系统是 ubuntu 20.04 此次烧写还算顺利&#xff…

2024腾讯云轻量应用服务器详细介绍_轻量全解析

腾讯云轻量应用服务器开箱即用、运维简单的轻量级云服务器&#xff0c;CPU内存带宽配置高并且价格特别便宜&#xff0c;大带宽&#xff0c;但是限制月流量。轻量2核2G3M带宽62元一年、2核2G4M优惠价118元一年&#xff0c;540元三年、2核4G5M带宽218元一年&#xff0c;756元3年、…