【侯捷】C++面向对象程序设计

  • 0. 课程目标
  • 1. 基于对象
    • 1.1 头文件与类的声明
      • 1.1.1 头文件
      • 1.1.2 类的声明
    • 1.2 构造函数
      • 1.2.1 Big Six
      • 1.2.2 初始化列表
      • 1.2.3 构造函数的overloading(重载)
      • 1.2.4 放在private的构造函数
    • 1.3 成员函数
      • 1.3.1 常量成员函数
      • 1.3.2 传值 vs. 传引用
    • 1.4 友元
    • 1.5 操作符重载
      • 1.5.1 this指针
      • 1.5.2 reference的传递与接受
      • 1.5.3 连续操作
      • 1.5.4 临时对象
      • 1.5.5 成员函数重载vs.全局函数重载
  • 2. 带资源的基于对象
    • 2.1 Big Three
      • 2.1.1 析构函数
      • 2.1.2 拷贝构造
      • 2.1.3 拷贝赋值
    • 2.2 栈stack与堆heap
      • 2.2.1 stack/heap/static/global
      • 2.2.2 new/delete
      • 2.2.3 array new/delete[]
    • 2.3 更多细节
  • 3. 面向对象
    • 3.1 复合、继承、委托
      • 3.1.1 复合
      • 3.1.2 委托
      • 3.1.3 继承
    • 3.2 多态、虚函数
      • 3.2.1 virtual函数
      • 3.2.2 继承+复合
      • 3.2.3 委托+继承

本系列相关链接
【侯捷】C++11

0. 课程目标

  • 培养正规、大器的编程习惯
  • 良好方式编写class,做“有生产力”的开发工程师
  • 学习class之间的关系

c++98 (1.0)
c++11 (2.0) <–目前大多数公司使用,成熟、通用
c++14 c++17 c++20

C++包括C++语言、C++标准库;

C++class分两种,一种带指针(资源)(例如string),一种不带指针(例如complex),这个区别对class的设计影响深远。

侯捷老师的该课程共有13节,本文按课程顺序重新编节。

推荐书籍:
C++ Primer 中英文
Effective C++ 中英文(中文侯捷出品)
Effective Modern C++ 中英文,这个只讲C++2.0
THE C++ STANDARD LIBRARY,STL源码剖析(侯捷出品)

C vs C++ 关于数据与函数
C语言面向过程,数据和函数分开;
C++语言面向对象,数据和处理数据的函数封装成class,class可以实例化多个对象,每个对象有独立的数据部分,而成员函数只有一份,每个对象通过函数指针和this指针共用成员函数。

Object Based vs. Object Oriented
基于对象,面对的是单一class的设计;
面向对象,面对的是多重classes的设计,考量classes之间的关系;

1. 基于对象

1.1 头文件与类的声明

1.1.1 头文件

C++代码基本形式
标准库头文件,#include<> 尖括号;
用户头文件,#include""双引号;
编译器查找顺序,见博文:C++语言;

头文件防卫式声明(declaration)
#ifndef和#pragma,见博文:C++语言;

头文件布局
防卫式声明+标准库头文件+用户头文件+前置声明+类声明+类定义(?)
推荐做法:在头文件声明class,在源文件定义class;–>防止定义多次展开;

1.1.2 类的声明

class template
模板类,用占位符将type的指定延后到实例化对象;

class的声明
有些成员函数在class body中定义;<–自动inline
有些成员函数在class body以外定义,可能是头文件,也可能是源文件;
<–区别?

内联函数
见博文:C++语言;

访问级别
见博文:C++语言;

1.2 构造函数

1.2.1 Big Six

默认构造函数
拷贝构造函数
移动构造函数
拷贝赋值运算符
移动赋值运算符
析构函数

class A { //=delete, =default;
public:A() {...}             //构造A(const A& ) {...}    //拷贝构造A(A && ) {...}        //移动构造A& operator=(const A& a) {...} //拷贝赋值A& operator=(A&& other) {...} //移动赋值~A() {...} //析构
};

1.2.2 初始化列表

构造函数独有;
初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。

主要是性能问题,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表,为什么呢?使用初始化列表少了一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。

1.2.3 构造函数的overloading(重载)

函数名相同,但函数签名不完全相同,则可以构成重载,这些函数同时存在:

  1. 函数名相同
  2. 返回值类型不看
  3. 形参列表个数、顺序、类型有不同
  4. const
    -const在函数尾部,属于函数签名,加const指明不修改入参;
    -const在返回值,不看;
    -const在传值形参,不看,因为改不改传值没意义;
    -const在传引用形参,属于函数签名;

注意与override(覆盖)区分,override是在面向对象角度的类多态中提出的,派生类通过对基类中虚函数覆盖来达到动态绑定的效果。要构成覆盖的条件是:
派生类的函数原型必须完全覆盖基类中的声明,包括:形参列表、返回类型、常量性(cv限定符)。

1.2.4 放在private的构造函数

单例模式,private配合static实现:

class A {public:static A& getInstance();setup() {...}
private:A();             //放在private的构造函数A(const A& rhs); //拷贝构造...
};A& A::getInstance()
{static A a;      //被调用时才唯一一次创建return a;
}

1.3 成员函数

1.3.1 常量成员函数

函数末尾加const,指明该成员函数不修改成员数据;
如果该加不加,但是用户在实例化时指定了对象是const,则会编译报错:

class complex
{public:double real() const { return re;}
private:double re;
}
///
const complex c1(2,1);
cout << c1.real();  //如果上面没定义为常量成员函数,则此处报错;

1.3.2 传值 vs. 传引用

数据占内存很大的时候,传值代价很大,在C语言中可以传指针,在C++语言中推荐使用引用;
引用与指针的区别见:博文:C++语言;
引用的传递也要注意指定是否const,是否修改引用的内容;

形参如此,返回值也如此!
如果函数返回值是在函数内部创建出来的,则不能传引用出去,因为出了函数这个东西就死掉了;

1.4 友元

友元打破c++的封装性,但取数据比函数方式更快;

class complex
{private:double re;friend complex& __doapl(complex*, const complex&);
}
///
inline complex& __doapl(complex* a, const complex& b) {a->re += b.re; //哪怕是私有成员变量,友元函数也可以直接取数据return *a;
}

相同class的各个对象互为friends;

class complex
{public:int funcA(const complex& param) {return param.re + param.im;}
private:double re, im;
}
///
complex c1(2,1);
complex c2;
c2.func(c1); //此处,c2和c1互为友元,所以c2能直接取c1的私有数据;

1.5 操作符重载

c++中操作符就是一种函数,因此操作符也可以重载overloading;

1.5.1 this指针

所有的成员函数都带有一个隐藏的this指针,可以直接使用;

class complex
{public:inline complex& // 有返回值,注意1.5.3operator += (const complex& r) // 接收者1{return __doapl(this, r);}inline complex& // 接收者2__doapl(complex* ths, const complex& r) {...; return *ths;} //传递者2
}
///
c2 += c1; // 传递者1

1.5.2 reference的传递与接受

传递者无需知道接收者是否以reference形式接收;

1.5.3 连续操作

用户可能连续操作,因此操作符重载函数的返回值不应该写成void;

c3 += c2 += c1;

1.5.4 临时对象

typename(),没有名称,它的生命在下一行就消失;
例如:

complex c1(2,1);
complex(3,4); // 临时对象,标准库中常用于return语句;

1.5.5 成员函数重载vs.全局函数重载

complex c1(3,1);
cout << conj(c1);
cout << c1 << conj(c1);

<<作用于cout,因此,<<的重载必须是全局函数形式;

ostream& // 为了能连续操作,返回值类型不能是void,同+=的重载
operator << (ostream& os, const complex& x)
{return os << '(' << x.real() << ',' << x.img() << ')';
}

2. 带资源的基于对象

class的成员变量包含指针、资源,那么就要额外管理这份资源;

2.1 Big Three

书籍中提到的“Big Three"指:拷贝构造(创建新对象)、拷贝赋值、析构;

String s1("hello");
String s2(s1);
String s2 = s1; //等同于上一行,s2是新创建的对象,因此此处调用拷贝构造函数,而不是拷贝赋值函数
String s2;
s2 = s1; //调用拷贝赋值函数

编译器提供的默认拷贝构造、拷贝赋值,只是忠实的浅拷贝。

2.1.1 析构函数

inline String::String(const char* cstr = nullptr)
{if(cstr == nullptr) {m_data = new char[1];*m_data = '\0';} else {m_data = new char[strlen(cstr)+1];strcpy(m_data, cstr); //深拷贝}
}
inline String::~String() { delete[] m_data;} //注意delete[]释放数组

2.1.2 拷贝构造

只要做一次深拷贝就可以了;

2.1.3 拷贝赋值

另外,拷贝赋值要注意:自拷贝、原数据释放、新数据深拷贝。

inline String& String::operator=(const String& str)
{if(this == &str) { return *this;} //比如指针换名字了,子类父类传了多次等delete[] m_data;m_data = new char[strlen(str.m_data) + 1]; //没做自拷贝检测的话,这里已经跑飞了,errorstrcpy(m_data, str.m_data);return *this;
}

2.2 栈stack与堆heap

2.2.1 stack/heap/static/global

stack,是存在于某scope的一块内存空间。例如调用函数,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址。
在函数体内声明的任何变量,其所使用的内存块都取自上述stack。
因此,超出这个scope后,其生命自动结束。

heap,是指由操作系统提供的一块global的内存空间,程序可动态分配从其中获得若干区块(block)。
因此,手动申请后,还需要主动释放。

global全局变量,其生命在作用域结束后仍然存在,直到整个程序结束,且可以用extern跨文件;
详见:博文:C++语言;

static 局部变量,其生命在作用域结束后仍然存在,直到整个程序结束,但不能跨文件;
静态成员变量只有一份,所有实例、类本身都访问同一个;
静态成员变量的定义必须在class外部;
静态成员函数没有this指针,只用于存取静态数据,可以通过对象访问,也可以通过class name访问;

class Account{public:static double m_rate;static void set_rate(const double& x);
};
double Account::m_rate = 8.0;
//
Account::set_rate(5.0); //注意不是.,是::
Account a;
a.set_rate(3.0);

2.2.2 new/delete

new三步:

  1. 调malloc申请内存;
  2. static_cast数据类型转换;
  3. 调用构造函数;

delete两步:

  1. 调用析构函数;
  2. 调free释放内存;

调试模式下,new得到多少内存?假设一个复数本身占用8字节,则new时:
首尾各4字节cookie,32字节调试信息,8字节数据,4字节no man land,12字节填充 = 64字节;
release模式下,首尾各4字节cookie,8字节数据,16字节对齐 = 64字节;

cookie用于释放内存时计算起止地址;
64字节用十六进制表示就是0x40,最后一个bit的1表示这块内存被分配出去了正在使用。如果内存被还给操作系统,则最后一个bit就会变成0;

2.2.3 array new/delete[]


array new如果不搭配delete[]而是delete,则整块内存仍然被释放,但是第一个资源后面的所有资源都没有被析构,内存泄漏!

2.3 更多细节

以下细节也很重要,详见【侯捷】c++11。

3. 面向对象

3.1 复合、继承、委托

3.1.1 复合

复合,就是A中有B,构造由内而外;composition, has a
复合,构造由内而外,析构由外而内,这些代码是编译器为你自动添加的、调用默认函数,如果不是你想要的,就得自己指定。

下例中,已有一个强大的class A,现在把它做一定限制,形成一个限制版本的class B,这种设计模式称为”装饰器“。

template <typename T>
class queue {protected:deque<T> c;
}

3.1.2 委托

委托,就是A中有指向B的指针;delegation, composition by reference
这种设计模式称为"pimpl",或者Handle and Body,“编译防火墙”;

一个非常常见的用法,是智能指针的引用计数。
共享很好,但是如果想修改指向的值,需要单独拷贝一份出来给它改,这种设计模式称为copy-on-write,或者“写时拷贝”,COW。

3.1.3 继承

继承,A is a B;空心三角箭头,由子类指向父类;
继承,构造先调用父类的构造函数,再构造自己;析构先执行自己,再调用父类的析构函数:

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


public/private/protect继承方式见:博文:C++语言;

  • 公开继承:该访问说明符之后列出的基类的公开和受保护成员在派生类中保持其访问级别,而基类的私有成员对派生类不可访问
  • 受保护继承:该访问说明符之后列出的基类的公开和受保护成员在派生类中是受保护成员,而基类的私有成员对派生类不可访问
  • 私有继承:该访问说明符之后列出的基类的公开和受保护成员在派生类中是私有成员,而基类的私有成员对派生类不可访问

3.2 多态、虚函数

3.2.1 virtual函数

  • 非虚函数:你不希望子类重新定义(覆盖,复写,override)它;
  • 虚函数:你希望子类重新定义它,但也可以用父类已有的默认定义;
    父类的析构函数必须是虚函数;
  • 纯虚函数:你希望子类一定要定义它;

继承和虚函数搭配,非常强大的多态;
先写大框架,而把具体的关键步骤延缓到子类的实现,这种设计模式称为“Template Method",大名鼎鼎;一般性的框架不是核心竞争力,而在于你的领域知识(domain knowledge)。

3.2.2 继承+复合


情况1.
构造:Base, Component, Derived;
析构:Derived, Component, Base;

情况2.
构造:Component, Base, Derived;
析构:Derived, Base, Component;

3.2.3 委托+继承

Composite
这是最经典、超强大的设计模式”Composite";

Prototype
为了创建未来的子类的对象,设计模式称为“Prototype”,出处《Design Patterns Explained Simply》。
下图中static用下划线表示;-表示私有,#表示保护,+表示公有;

静态变量:数据类型
函数名():返回值类型

【侯捷】C++面向对象程序设计相关推荐

  1. 侯捷-C++面向对象高级开发(操作符重载与临时对象)

    侯捷-C++面向对象高级开发(操作符重载与临时对象) 1.操作符重载与临时对象 任何成员函数有一个隐藏的this pointer指向,指向调用者. 传递者无需知道接收者是以什么形式接收 就比如下面方框 ...

  2. 侯捷-C++面向对象高级开发(头文件与类的声明,构造函数,参数传递与返回值)

    侯捷-C++面向对象高级开发 1.头文件与类的声明 Object Based:面对的是单一的class的设计 Object Oriented:面对的是多重classes的设计,classes和clas ...

  3. 侯捷-C++面向对象高级开发(三大函数:拷贝构造,拷贝赋值,析构)

    侯捷-C++面向对象高级开发(三大函数:拷贝构造,拷贝赋值,析构) 三大函数:拷贝构造,拷贝赋值,析构 第一个是拷贝构造,第二个是拷贝赋值 编译器有一套默认的东西实现这俩东西,可用到complex的实 ...

  4. 【C++】侯捷C++面向对象高级编程(上)

    C++面向对象高级编程 前言 C++ Programs代码基本形式 文件类型 头文件写法 头文件布局 class1--complex 类的声明 inline--内联函数 class访问级别(acces ...

  5. 【C++】侯捷C++面向对象高级编程(下)

    转换函数(conversion function) 可以把"这种"东西,转化为"别种"东西. 即Fraction --> double class Fra ...

  6. 侯捷 C++面向对象高级开发(下)笔记整理

    C++面向对象高级开发(下) 一.导读 (1)泛型编程和面向对象编程分属不同的思维, (2)由继承关系所形成的对象模型,包含this指针,vptr指针,vtbl虚表,虚机制,以及虚函数造成的多态. 二 ...

  7. 侯捷C++视频资源全集 | 百度网盘下载

    之前给群里的小伙伴推荐了侯捷老师, 结果他学着学着发现b站侯捷老师的视频都被下掉了. 让我想起了我当年学c++的时候在b站看候捷老师视频的那些日子,每多看一点,就会多一点恍然大悟的感觉哈哈. 辛亏我的 ...

  8. 侯捷 C++系列课程视频 | 侯捷 C++ STL 视频

    侯捷C++课程视频课程一直都被看过的同学广为推荐,今天晚上发现 B 站关于侯捷老师的 C++ 课程视频几乎全部被下架了. 所以在网上找了下资源,找到了一套还算比较齐全的. 包含了 : 侯捷C++ 标准 ...

  9. 【C++面向对象程序设计——侯捷大师】心得摘要

    侯捷大师讲的真好,很多模糊的知识点,都得到了解决.感觉像是相见恨晚!非常感谢侯捷老师的启迪!谢谢啦!非常推荐萌新去学习学习! 下面是侯捷大师的<面向对象程序设计>课程的一些我觉得重要的摘要 ...

  10. 侯捷C++课程笔记01: 面向对象高级编程(上)

    本笔记根据侯捷老师的课程整理而来:C++面向对象高级编程(上) pdf版本笔记的下载地址: 笔记01_面向对象高级编程(上),排版更美观一点(访问密码:3834) 侯捷C++课程笔记01: 面向对象高 ...

最新文章

  1. Linux下Tomcat的安装配置
  2. 深入理解WMS(二):Dialog与Toast源码解析
  3. linux 制作deb包的三种方法
  4. java byte 梳理
  5. 细胞(信息学奥赛一本通-T1329)
  6. Appium python自动化测试系列之Capability介绍(五)
  7. 计算机内存体系与Java 内存模型
  8. Ubuntu 18.04.04 安装显卡驱动 nvidia安装
  9. 地图比例尺与空间分辨率之间的关系_地图比例尺与遥感影像分辨率的关系探讨...
  10. 【论文学习】10、物联网安全WiFi设备的监控与识别
  11. 【启动】Windows上启动图形化软件,报错: 无法启动此程序,因为计算机中丢失api-ms-win-crt-runtime-1-1-0.dll...
  12. 本科论文发表的难度大吗
  13. 【C语言】扫雷游戏(包含递归展开、手自动标记功能)
  14. 【微信小程序】模板消息推送(测试成功)。
  15. 【java】springboot项目启动数据加载内存中的三种方法
  16. 基于逆变器的有源滤波器控制,光伏Mppt采用粒子群算法,ip iq谐波检测,电压电流双闭环控制,电流环采样pi控制和重复控制进行对比,谐波含量低
  17. R语言melt、table、cut函数应用及解释
  18. 【IEEE/ACM专区】一篇高质量的IEEE/ACM Transaction论文是如何顺利发表的?
  19. 史上最全的HTML、CSS知识点总结,浅显易懂。适合入门新手
  20. 11.(地图数据篇)OSM数据如何下载使用

热门文章

  1. vue3 + ts + EsLint + Prettier 规范代码
  2. 蓝凌ekp开发_蓝凌EKP在eclipse中启动报错
  3. 西门子opc服务器注册,西门子OPC服务器怎么做
  4. c语言中断函数作用,进一步理解中断函数
  5. 电磁循迹小车赛后总结
  6. 喵哈哈村的魔法考试 Round #6 (Div.3) 题解
  7. python 公开课_python公开课|可以用Python做的十件事,刚开始学python,你一定要知道...
  8. 扫码点餐小程序源码_做个扫码点餐的小程序贵吗?
  9. 谷歌浏览器翻译插件 划词翻译
  10. 统一修改PCB板上器件标识、阻值,后期方便手工焊接样板。(现以AD10为例)