【学习笔记】C++面向对象高级开发-侯捷

文章目录

  • 0 书籍推荐
  • 1 面向对象高级开发(上)
    • 1.1 头文件与类的声明
    • 1.2 构造函数
      • 1.2.1 内联函数
      • 1.2.2 访问权限
      • 1.2.3 构造函数
    • 1.3 参数传递与返回值
      • 1.3.1 常成员函数
      • 1.3.2 参数传递:值传递 vs 引用传递
      • 1.3.3 返回值传递:返回值 vs 返回引用
      • 1.3.4 友元friend
    • 1.4 操作符重载与临时对象
      • 1.4.1 操作符重载-成员函数(含this)
      • 1.4.2 操作符重载-非成员函数/全局函数(不含this)
    • 1.5 构造函数和析构函数
      • 1.5.1 具有指针成员的类必须包含拷贝构造函数和operator=函数
      • 1.5.2 一定要在拷贝赋值函数operator=中检查是否自我赋值
    • 1.6 栈stack和堆heap
      • 1.6.1 stack object栈对象的生命周期
      • 1.6.2 static local object静态局部对象的生命周期
      • 1.6.3 global object全局对象的生命周期
      • 1.6.4 heap object堆对象的生命周期
      • 1.6.5 new的作用
      • 1.6.6 delete的作用
      • 1.6.7 malloc动态内存分配
        • 单个对象的动态内存分配
        • 对象数组的动态内存分配
        • array new和array delete搭配使用
    • 1.7 扩展补充:static、类模板、函数模板
      • 1.7.1 static
      • 1.7.2 构造函数私有化-单例模式
      • 1.7.3 类模板(class template)
      • 1.7.4 函数模板(function template)
      • 1.7.5 命名空间namespace
        • using编译指令(using directive)
        • using声明(using declaration)
      • 1.7.6 待深入学习的细节
    • 1.8 组合与继承
      • 1.8.1 复合/组合Composition,表示has-a
      • 1.8.2 委托Delegation,Composition by reference
      • 1.8.3 继承Inheritance,表示is-a
    • 1.9 虚函数与多态
      • 1.9.1 继承与虚函数
      • 1.9.2 委托(Delegation) + 继承(Inheritance)
        • 观察者模式(Observer)
    • 1.10 委托设计案例Delegation
      • 1.10.1 组合模式(Composite)
      • 1.10.2 原型模式(Prototype)
  • 2 面向对象高级开发(下)
    • 2.1 转换函数 Conversion Function
      • 2.1.1 转换函数 Conversion Function
      • 2.1.2 无explicit修饰的单实参构造函数non-explicit-one-argument ctor
      • 2.1.3 conversion function vs. non-explicit-one-argument ctor
      • 2.1.4 explicit-one-argument ctor
      • 2.1.5 转换函数的其它应用场景
    • 2.2 pointer-like classes
      • 2.2.1 智能指针
      • 2.2.2 迭代器
    • 2.3 function-like classes 仿函数
    • 2.4 命名空间 namespace
    • 2.5 模板 template
      • 2.5.1 类模板 class template
      • 2.5.2 函数模板 function template
      • 2.5.3 成员模板 member template
    • 2.6 特化 specialization
      • 2.6.1 类模板的全特化 template specialization
      • 2.6.2 类模板的偏特化/局部特化 partial specialization
        • 个数的偏特化
        • 范围的偏特化
      • 2.6.3 函数模板的全特化


学习笔记源自博览网侯捷大师的C++课程,在原视频及讲义的基础上填充注释。
如有侵权,请联系删除,抱歉。


0 书籍推荐

  • C++ Primer【C++首个编译器的作者】
  • The C++ Programming Language【C++之父】
  • Effective C++
  • Effective Modern C++【C++11/14】
  • The C++ Standard Library
  • STL源码剖析

1 面向对象高级开发(上)

1.1 头文件与类的声明

防卫式声明

complex.h

#ifndef __COMPLEX__
#define __COMPLEX__ 

//0.前置声明 forward declarations
class ostream;
class complex;
complex& __doapl (complex *ths, const complex& r); 
    
//1.类声明 class declarations
class complex{
     
    ...
};

//2.类定义 class definition
complex::function ...
     
#endif

注:等价于#pragma once

示例:复数类-类的声明(类模板、默认参数、初始化列表、常函数、重载运算符、友元函数)

template<typename T>	//类模板
class complex{
     
private:
    T re, im;	//模板
    friend complex& __doapl (complex *ths, const complex& r); 	//友元函数

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; }
};
int main(){
	complex c1(2.5, 1.5);
	complex c2(1, 2);
	...
}

1.2 构造函数

1.2.1 内联函数

注1:函数若在class body内定义完成,则自动成为inline内联函数

注2:内联函数仅是对编译器的建议,是否实际为内联函数,由编译器决定。


1.2.2 访问权限

private:封装数据成员,在类外无法直接访问

public:被外界调用的函数,在类外可直接访问

protected:在子类中可直接访问


1.2.3 构造函数

//默认参数、初始化列表
complex(T r = 0, T i = 0) : re(r), im(i){
     }

//等价写法:赋值(效率相对较差)
complex(T r = 0, T i = 0){
     
    re = r;
    im = i;
}

注1:构造函数可有多个,即构造函数的重载。

注2:若某个构造函数已包含默认参数,则构造函数重载时不能产生冲突。

complex(T r = 0, T i = 0) : re(r), im(i){
     }	//包含默认参数的构造函数
//complex() : re(0), im(0){}	//与已有构造函数存在冲突,编译器不知道如何调用

例:
complex c1;
//complex c2();

注3:构造函数可以被private修饰,则不能被外部调用创建对象,如单例模式Singleton

class A{
     
public:
    static A& getInstance();	//对外部提供获取对象的接口
    setup(){
     ...}
    
private:
    A();				//默认无参构造
    A(const A& rhs);	//拷贝构造
};

A& A::getInstance(){
     
    static A a;			//唯一对象
    return a;
}

//对象调用
A::getInstance().setup();

1.3 参数传递与返回值

1.3.1 常成员函数

常成员函数不修改类中数据成员的内容,使用const修饰(中括号()和花括号{}之间)。

template<typename T>	//类模板
class complex{
     
public:
  	//成员函数-常函数
    T real() const {
      return re; }		
    T imag() const {
      return im; }
};

1.3.2 参数传递:值传递 vs 引用传递

值传递:pass by value

引用传递:pass by reference(to const)

C语言中,参数传递可使用指针;C++语言中,参数传递建议使用引用

注1:C++中建议函数参数尽量使用引用传递,但bool/char/short等短数据类型可使用值传递。

注2:传递引用时若不希望参数被修改,函数参数可使用const修饰:void func(const Object& obj){}

引用的本质为指针常量T* const p

指针常量的指针指向不可变;但指针常量指向的值可改变(解引用,重新赋值)。


1.3.3 返回值传递:返回值 vs 返回引用

值传递:return by value

引用传递:return by reference(to const)

注1:C++中建议函数的返回值类型尽量返回引用

注2:不能返回引用的情况:函数作用域内的局部变量/临时对象


1.3.4 友元friend

友元:类中声明的友元,可直接获取类的private数据成员

template<typename T>	//类模板
class complex{
     
private:
    T re, im;	//模板
    
    //友元函数的声明
    friend complex& __doapl (complex *ths, const complex& r); 	//友元函数
	...
};

//友元函数的定义
inline complex& __doapl(complex *ths, const complex& r){
     
    //自由获取friend的private成员
    ths->re += r.re;
    ths->im += r.im;
    return *ths;
}

注:友元破坏了类的封装性。

相同/同一class的各个对象(objects),互为友元(friends)

class complex{
     
private:
    double re, im;
    
public:
    complex(double r = 0, double i = 0) : re(r), im(i){
     }
    
    //当前对象可直接访问另一对象的private私有数据成员
    int func(const complex& param){
     
        return param.re + param.im;
    }
};

//相同/同一class的各个对象(objects),互为友元(friends)
int main(){
     
    complex c1(2, 1);
    complex c2;
    
    //对象c2可直接访问另一对象c1的private私有数据成员
    c2.func(c1);
}

1.4 操作符重载与临时对象

1.4.1 操作符重载-成员函数(含this)

注:任何成员函数,均隐含this指针,指向当前成员函数的调用者。

inline complex& __doapl(complex* ths, const complex& r){
     
    ths->re += r.re;
    ths->im += r.im;
    return *ths;
}

inline complex& complex::operator+=(const complex &r){
     
    return __doapl(this, r);	//this表示当前对象
}

int main(){
     
    complex c1(2, 1);
    complex c2(5);
    
    c2 += c1;
    ...
}

1.4.2 操作符重载-非成员函数/全局函数(不含this)

临时对象/匿名对象(temp object):typename()

/* 操作符重载 */
inline complex operator+(const complex& x, const complex& y){
     
    //临时对象/匿名对象
    return complex(real(x) + real(y), imag(x) + imag(y));
}

inline complex operator+(const complex& x, double y){
     
    return complex(real(x) + y, imag(x));
}

inline complex operator+(double x, const complex& y){
     
    return complex(x + real(y), imag(y));
}

int main(){
     
    //临时对象/匿名对象
    int(7);
    complex();
    complex(4, 5);
    cout << complex(2) << endl;
    
    complex c1(2, 1);
    complex c2;
}
#include 
ostream& operator<<(ostream& os, const complex& x){
     
    return os << "(" << real(x) << "," << imag(x) << ")";
}

int main(){
     
    complex c1(1, 2);
    complex c2(3, 4);
    
    cout << c1 << endl;	//(1, 2)
    cout << c1 << c2 << endl;	//(1, 2)(3, 4)
}

1.5 构造函数和析构函数

class String{
     
public:
    String(const char* cstr = 0);
    String(const String& str);
    String& operator=(const String& str);
    ~String();
    char* get_c_str() const {
     return m_data};

private:
    char* m_data;
};

inline String::String(const char* cstr = 0){
     
    if(cstr){
     
        m_data = new char[strlen(cstr) + 1];
        strcpy(m_data, cstr);
    }else{
     
        m_data = new char[1];
        *m_data = '\0';
    }
}

//拷贝构造函数
/* 同一个类的不同对象之间互为友元,可直接访问private成员 */
inline String::String(const String& str){
     
	m_data = new char[strlen(str.m_data) + 1];
    strcpy(m_data, str.m_data);
}

//拷贝赋值函数
/* 先释放左值;再创建与右值相同大小的空间;最后深拷贝 */
inline String& String::operator=(const String& str){
     
    //检测自我赋值(self assignment)
	if(this == &str){
     
        return *this;
    }
    
    //1.释放原有空间
    delete[] m_data;
    //2.创建新空间
    m_data = new char[strlen(str.m_data) + 1];
	//3.深拷贝
    strcpy(m_data, str.m_data);
    return *this;
}

//输出函数-重载左移运算符
ostream& operator<<(ostream& os, const String& str){
     
    os << str.get_c_str();
    return os;
}

//析构函数
inline String::~String(){
     
    delete[] m_data;
}

1.5.1 具有指针成员的类必须包含拷贝构造函数和operator=函数

具有指针成员的类,必须自定义拷贝构造函数拷贝赋值函数operator=,否则会导致浅拷贝,并造成内存泄露重复释放

//拷贝构造函数
/* 同一个类的不同对象之间互为友元,可直接访问private成员 */
inline String::String(const String& str){
     
	m_data = new char[strlen(str.m_data) + 1];
    strcpy(m_data, str.m_data);
}

//拷贝赋值函数
/* 先释放左值;再创建与右值相同大小的空间;最后深拷贝 */
inline String& String::operator=(const String& str){
     
    //检测自我赋值(self assignment)
	if(this == &str){
     
        return *this;
    }
    
    //1.释放原有空间
    delete[] m_data;
    //2.创建新空间
    m_data = new char[strlen(str.m_data) + 1];
	//3.深拷贝
    strcpy(m_data, str.m_data);
    return *this;
}

1.5.2 一定要在拷贝赋值函数operator=中检查是否自我赋值

检测自我赋值的作用

  1. 直接返回对象本身,效率高;
  2. 若未判断是否自我赋值,则释放原有空间后,访问右值时会产生不确定行为。
//拷贝赋值函数
/* 先释放左值;再创建与右值相同大小的空间;最后深拷贝 */
inline String& String::operator=(const String& str){
     
    //检测自我赋值(self assignment)
    //1.直接返回对象本身,效率高
    //2.若未判断是否自我赋值,则释放原有空间后,访问右值时会产生不确定行为
	if(this == &str){
     
        return *this;
    }
    
    //1.释放原有空间
    delete[] m_data;
    //2.创建新空间
    m_data = new char[strlen(str.m_data) + 1];
	//3.深拷贝
    strcpy(m_data, str.m_data);
    return *this;
}

1.6 栈stack和堆heap

Stack:存在于某作用域scope的一块内存空间。

  • 当调用函数时,函数本身会形成一个stack用于存储所接受的函数参数及返回地址
  • 函数体内声明的任何变量(含临时对象/匿名对象),所使用的的内存块均取自上述stack。

Heap:即system heap,由操作系统提供的一块global全局内存空间,程序可动态分配(dynamic allocated),从中获取若干区块(blocks)。

通过动态内存分配获取的堆内存,需由程序员主动释放(delete)。


1.6.1 stack object栈对象的生命周期

栈对象的生命周期存在于作用域内,被称为auto object。

析构函数在作用域结束时被自动调用。

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

1.6.2 static local object静态局部对象的生命周期

静态局部对象的生命周期,在作用域结束后仍存在,直到整个程序结束。

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

1.6.3 global object全局对象的生命周期

全局对象的生命周期,在整个程序结束后才结束,作用域是整个程序(全局作用域)。

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

int main()
{
     
    ...
}

1.6.4 heap object堆对象的生命周期

堆对象的生命周期,在其被delete后结束。

class Complex{
     ...};
...
{
     
    Complex *p = new Complex(1, 2);
    //若未delete,会导致内存泄露
    delete p;
}

注:若未delete指向堆对象的指针p,则当作用域结束后,p指向的堆内存未被释放,当指针p本身已被释放,无法再delete,导致内存泄露。


1.6.5 new的作用

new的作用:先分配内存,再调用构造函数。

Complex* pc = new Complex(1, 2);
/* 编译器内部实现 */
Complex* pc;

//1.分配内存
//new操作符内部调用malloc()函数
void* mem = operator new(sizeof(Complex));

//2.静态类型转换
pc = static_cast<Complex*>(mem);

//3.调用构造函数
//pc调用构造函数,则pc即隐藏的this指针
pc->Complex::Complex(1, 2);

构造函数属于类的成员函数,隐含了this指针。

Complex::Complex(pc, 1, 2);

Complex::Complex(this, 1, 2);

【学习笔记】C++面向对象高级开发-侯捷_第1张图片


1.6.6 delete的作用

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

class String{
     
public:
    ~String(){
     
        delete[] m_data;
    }
    ...
private:
    char* m_data;
}
String* ps = new String("Hello");
...
delete ps;

/* 编译器内部实现 */
//1.调用析构函数
String::~String(ps);
//2.释放内存
//delete操作符内部调用free(ps)函数
operator delete(ps);

【学习笔记】C++面向对象高级开发-侯捷_第2张图片


1.6.7 malloc动态内存分配

单个对象的动态内存分配

![malloc动态内存分配原理图【学习笔记】C++面向对象高级开发-侯捷_第3张图片

  • 目标对象:Complex类对象,8字节
  • 目标对象上下侧空间(调试模式下):4字节 * 8 + 4字节
  • 上下侧Cookie:4字节 + 4字节,记录区块的大小
  • 填充内存大小:4字节 * 3(pad)

注1:VC环境下,每个内存区块的大小一定为16的倍数,否则需要进行填充。

注2:操作系统需要依赖额外空间包含的信息,对内存区块进行回收。

注:Cookie值:十六进制数0x00000041。

实际为0x40,借用最后1位表示申请(1)或回收(0)内存。

倒数第2位:4*16=64字节;

倒数第1位:1,表示申请内存。

对象数组的动态内存分配

【学习笔记】C++面向对象高级开发-侯捷_第4张图片

注:在堆区动态创建对象数组时,会使用4字节的内存空间记录数组元素的个数。

array new和array delete搭配使用

【学习笔记】C++面向对象高级开发-侯捷_第5张图片

  • 不含指针类型成员时,仅使用delete p时,只会调用1次析构函数,但不会造成数组的内存泄露,操作系统会根据Cookie值释放动态分配的堆区数组内存。
  • 包含指针类型成员时,仅使用delete p时,只会调用1次析构函数,会造成指针类型成员的内存泄露,但不会造成数组的内存泄露,操作系统会根据Cookie值释放动态分配的堆区数组内存。

注:new对象/变量数组,必须delete对象/变量数组。new/delete不对应,可能造成指针类型成员的内存泄露。


1.7 扩展补充:static、类模板、函数模板

1.7.1 static

类的成员函数只有1份,但需处理多个不同对象,成员函数通过this指针获取不同需处理的对象。


静态成员:只存在1份。

静态成员函数:只存在1份,不含this指针只能处理静态成员/数据

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(){
     
    /* 静态成员函数的调用 */
    //1.通过类名调用
    Account::set_rate(5.0);
    
    //2.通过对象调用
    Account a;
    a.set_rate(6.0);
}

1.7.2 构造函数私有化-单例模式

单例模式-饿汉式:类加载时,初始化对象。

优点:获取对象的速度快;

缺点:类加载较慢。

class A{
     
public:
    //通过静态函数,获取唯一的静态对象
    static A& getInstance(){
      return a; }
    void setup(){
     ...}
    
private:
    //构造函数私有化,外部无法通过构造函数创建对象
    A();
    A(const A& rhs);
    //静态对象-唯一
    static A a;
};

int main(){
     
    //通过类名调用静态成员函数(获取静态对象),进而调用非静态成员函数
    A::getInstance().setup();
}

单例模式-懒汉式延迟加载对象;类加载时,不初始化对象,在调用静态函数时才创建对象。

优点:类加载较快;

缺点:获取对象的速度慢。

class A{
     
public:
    //通过静态函数,获取唯一的静态对象
    static A& getInstance(){
      
        //仅当静态函数调用时,才创建唯一的静态对象
        //当函数作用域结束时,唯一的静态对象仍存在
    	static A a;
        return a;
    }
    void setup(){
     ...}
    
private:
    //构造函数私有化,外部无法通过构造函数创建对象
    A();
    A(const A& rhs);
};

int main(){
     
    //通过类名调用静态成员函数(获取静态对象),进而调用非静态成员函数
    A::getInstance().setup();
}

1.7.3 类模板(class template)

类模板使用时,必须显式指定具体的泛型类型。

template<typename T>	//类模板
class complex{
     
private:
    T re, im;	//模板
    friend complex& __doap1 (complex *ths, const complex& r); 	//友元函数

public:
    complex(T r = 0, T i = 0) : re(r), im(i){
     }	//默认参数、初始化列表
    complex& operate+=(const complex&);	//重载运算符
    T real() const {
      return re; }		//成员函数-常函数
    T imag() const {
      return im; }
};
int main(){
	complex c1(2.5, 1.5);
	complex c2(1, 2);
	...
}

1.7.4 函数模板(function template)

注1:关键字class可替换为typename

注2:函数模板使用时,不必显式指定具体的泛型类型。编译器会对函数模板进行实参推导/类型推导(argument deduction)。类模板使用时,需显式指定具体的泛型类型。

//函数模板
template<class T>
inline const T& min(const T& a, 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){
     
        return this->_weight < rhs._weight;
    }
    
private:
    int _w, _h, _weight;
}

int main(){
     
    stone r1(2, 3), r2(3, 3), r3;
    //调用模板函数min,类型推导
    //类型推导T为stone类型,进一步调用stone::operator<
    r3 = min(r1, r2);	
}

1.7.5 命名空间namespace

语法

namespace std
{
     
    ...
}

using编译指令(using directive)

using编译指令使用指定命名空间。使用using编译指令后,可直接访问命名空间的成员,而无需使用::作用域运算符

语法using namespace 命名空间名;

#include 
using namespace std;

int main(){
     
    cin >> ... ;
    cout << ... ;
    
    return 0;
}

using声明(using declaration)

using声明使用指定命名空间的成员。使用using声明后,可直接访问命名空间的成员,而无需使用::作用域运算符
语法using 命名空间名::成员;

#include 
using std::cout;

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

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

1.7.6 待深入学习的细节

【学习笔记】C++面向对象高级开发-侯捷_第6张图片


1.8 组合与继承

类与类之间的关系:

  • 继承Inheritance
  • 复合/组合Composition
  • 委托Delegation

1.8.1 复合/组合Composition,表示has-a

复合/组合:一个类包含另一个类。

具有组合关系的两个类,生命周期相同(同步)

适配器模式Adapter

/* 利用已有的功能强大的容器deque,构造queue,仅开放部分函数接口 */
template <class T, class Sequence = deque<T>>
class queue{
     
    ...
protected:
    //底层容器
    Sequence c;	//等价于 deque 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(x); }
    void pop() {
      c.pop_front(); }
};

组合关系的内存示意图

【学习笔记】C++面向对象高级开发-侯捷_第7张图片


组合关系下的构造函数/析构函数调用顺序

构造函数的调用由内向外

Container的构造函数先调用Component的默认构造函数(由编译器完成),再执行本身。

析构函数的调用由外向内

Container的析构函数先执行本身,再调用Component的析构函数(由编译器完成)。

【学习笔记】C++面向对象高级开发-侯捷_第8张图片


1.8.2 委托Delegation,Composition by reference

委托:两个类之间使用指针相连。

具有委托关系的两个类,生命周期不相同(不同步),委托类的对象先被创建,当需要使用被委托类(指针指向的类)时,才创建被委托类的对象。

委托类对外接口;调用被委托类服务(进行具体实现)。

被委托类具体实现

/* Handle */
// file String.hpp
class StringRep;
class String {
     
public:
    String();
    String(const char* s);
    String(const String& s);
    String& operator=(const String& s);
    ~String();
    ...
        
private:
    /* pImpl——pointer to implementation */
    //Handle / Body
    //该指针可指向不同的实现类
    StringRep* rep;	//指向具体实现的指针
};
/* Body */
// file String.cpp
#include "String.hpp"
namespace {
     
clase StringRep {
     
	friend class String;
    StringRep(const char* s);
    ~StringRep();
    
    int count;
    char* rep;
};
}

String::String(){
     ...}

pImpl(Handle / Body,委托类/被委托类):编译防火墙,可灵活地指向不同的实现类。
委托类的指针成员指向具体的实现类(被委托类)。

【学习笔记】C++面向对象高级开发-侯捷_第9张图片


1.8.3 继承Inheritance,表示is-a

C++的3种继承方式:

  • public
  • private
  • protected
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;
};

父类的数据成员,被子类完整地继承。


继承关系下的构造函数/析构函数调用顺序

构造函数的调用由内向外

子类/派生类的构造函数先调用父类/基类的默认构造函数(由编译器完成),再执行本身。

析构函数的调用由外向内

子类/派生类的的析构函数先执行本身,再调用父类/基类的析构函数(由编译器完成)。

父类/基类的构造函数必须为虚函数,由子类进行重写,否则会导致未定义的行为。

【学习笔记】C++面向对象高级开发-侯捷_第10张图片


1.9 虚函数与多态

1.9.1 继承与虚函数

非虚函数(non-virtual):不希望派生类重新定义(重写,override)。

虚函数(virtual):希望派生类重新定义(重写,override),且已有默认定义。

纯虚函数(pure virtual):希望派生类一定重新定义(重写,override),且无默认定义。

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

1.9.2 委托(Delegation) + 继承(Inheritance)

观察者模式(Observer)

class Subject{
     
	int m_value;
    /* 委托Delegation:使用指针指向另一个类 */
	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_views.size(); i++){
     
            m_views[i]->update(this, m_value);
        }
    }

    //注销
};
class Observer{
     
public:
    /* 继承Inheritance */
    //纯虚函数:子类必须重写
    virtual void update(Subject* sub, int value) = 0;
};

【学习笔记】C++面向对象高级开发-侯捷_第11张图片


1.10 委托设计案例Delegation

1.10.1 组合模式(Composite)

【学习笔记】C++面向对象高级开发-侯捷_第12张图片

  • Primitive类和Composite类抽象出一个共同基类Component类。

  • Composite类中vector容器的元素类型vector,必须使用指向Component类的指针(Component*),而不能使用Component类的子类对象(PrimitiveComposite)。

    注:vector存储的元素类型必须相同

    基类指针类型(Component*)的数据类型大小相同;

    而基类的子类对象(PrimitiveComposite)的数据类型大小可能不同。

    因此,必须使用vector


1.10.2 原型模式(Prototype)

基类创建未来可能扩展的子类(对象)。

注:clone()函数不能是静态成员函数。

静态成员函数的调用方式为类名::静态成员函数(),需要类名;但子类的类名尚未确定。

【学习笔记】C++面向对象高级开发-侯捷_第13张图片


原型模式-基类

【学习笔记】C++面向对象高级开发-侯捷_第14张图片


原型模式-子类

【学习笔记】C++面向对象高级开发-侯捷_第15张图片


原型模式-测试程序
【学习笔记】C++面向对象高级开发-侯捷_第16张图片


2 面向对象高级开发(下)

2.1 转换函数 Conversion Function

2.1.1 转换函数 Conversion Function

需求:将目标类的对象,转换为指定数据类型。

例:包含分子、分母的分数类,转为double类型。

class Fraction
{
     
public:
    Fraction(int num, int den = 1)
      : m_numerator(num), m_denoinator(den) {
     }
    
    //重载的函数名与目标返回值类型相同,可省略返回值类型
    operator double() const {
     
        return (double)(m_numerator / m_denoinator);
    }
    
private:
    int m_numerator;	//分子
    int m_denoinator;	//分母
};

int main(){
     
    Fraction f(3, 5);
    double d = 4 + f;  //调用operator double()函数,将f转为0.6
}

注:可包含多个转换函数,如将整数转换为double/string类型。


2.1.2 无explicit修饰的单实参构造函数non-explicit-one-argument ctor

注:argument表示实参;parameter表示形参。

class Fraction
{
     
public:
    Fraction(int num, int den = 1)
      : m_numerator(num), m_denoinator(den) {
     }

    Fraction operator+(const Fraction& f) {
     
        return Fraction(...);
    }
    
private:
    int m_numerator;	//分子
    int m_denoinator;	//分母
};

int main(){
     
    Fraction f(3, 5);
    //重载运算符operator+()的右操作数为Fraction对象
    //构造函数未使用explicit修饰,操作数4与默认参数可隐式转换为Fraction对象
    Fraction d = f + 4;	/* 编译正常 */
}

2.1.3 conversion function vs. non-explicit-one-argument ctor

class Fraction
{
     
public:
    Fraction(int num, int den = 1)
      : m_numerator(num), m_denoinator(den) {
     }
    
    //重载的函数名与目标返回值类型相同,可省略返回值类型
    operator double() const {
     
        return (double)(m_numerator / m_denoinator);
    }

    Fraction operator+(const Fraction& f) {
     
        return Fraction(...);
    }
    
private:
    int m_numerator;	//分子
    int m_denoinator;	//分母
};

int main(){
     
    Fraction f(3, 5);
    //路径1:调用operator double()函数,将f转换为double类型;3/5即0.6与操作数4相加得到4.6,隐式转换为Fraction类型
    //路径2:构造函数未使用explicit修饰,操作数4可隐式转换为Fraction对象;再调用operator+()函数
    Fraction d2 = f + 4;	/* 编译报错:存在歧义ambiguous */
}

2.1.4 explicit-one-argument ctor

class Fraction
{
     
public:
    explicit Fraction(int num, int den = 1)
      : m_numerator(num), m_denoinator(den) {
     }
    
    //重载的函数名与目标返回值类型相同,可省略返回值类型
    operator double() const {
     
        return (double)(m_numerator / m_denoinator);
    }

    Fraction operator+(const Fraction& f) {
     
        return Fraction(...);
    }
    
private:
    int m_numerator;	//分子
    int m_denoinator;	//分母
};

int main(){
     
    Fraction f(3, 5);
    
    /* 单参构造函数使用explicit修饰 */
    //1.右操作数4不会被隐式转换为Fraction对象,继而无法调用operator+()函数
    //2.调用operator double()将f转换为double,求和后,无法将double转换为Fraction类型
    Fraction d3 = f + 4;	/* Error:无法将double转换为Fraction类型 */
}

2.1.5 转换函数的其它应用场景

【学习笔记】C++面向对象高级开发-侯捷_第17张图片


2.2 pointer-like classes

2.2.1 智能指针

智能指针需要具备指针允许的所有操作。智能指针类的成员一定包含一般指针。

智能指针通用写法:

template<class T>
class shared_ptr
{
     
public:
    //重载运算符 *
    T& operator*() const{
     
        return *px;
    }
    //重载运算符 ->
    T* operator->() const{
     
        return px;
    }
    //智能指针的有参构造函数,使用初始化列表对原指针(成员)进行赋值
    shared_ptr(T* p) : px(p) {
     }
    
private:
    T* px;		//被智能指针包装的指针
    long* pn;
    ...
};

使用场景:

struct Foo
{
     
    ...
    void method(void) {
      ... }
};

//使用构造函数初始化,初始化列表使用new FOO对类成员px赋值
shared_ptr<Foo> sp(new Foo);

//使用场景1:原始类Foo的拷贝构造函数
Foo f(*sp);

//使用场景2:调用函数
sp->method();	
//等价于px->method();

注:sp->相当于px

特殊:C++中重载运算符->后需要继续使用->作用下去,即sp->实际应该为sp->->px->


2.2.2 迭代器

迭代器表示容器中当前指向的元素的位置。

注1:迭代器相当于智能指针,存在运算符重载

注2:link_type node表示实际指针,指向双向链表的节点;迭代器是包装实际指针的智能指针

【学习笔记】C++面向对象高级开发-侯捷_第18张图片

【学习笔记】C++面向对象高级开发-侯捷_第19张图片


2.3 function-like classes 仿函数

仿函数:类中重载函数调用运算符(),即重载operator()。该类创建的对象为函数对象

():函数调用运算符(function call operator)

注:仿函数类的对象,称为函数对象

【学习笔记】C++面向对象高级开发-侯捷_第20张图片

select1st类仿函数的调用:select1st()(Pair pair)

第1个()表示创建匿名对象

第2个()表示调用重载的函数调用运算符(),即operator()(…)

标准库中的仿函数

/* 继承一元操作符类unary_function */
template<class T>
struct identity : public unary_function<T, T> {
     
	const T& operator()(const T& x) const {
     
        return x;
    }  
};

template<class Pair>
struct select1st : public unary_function<Pair, typename Pair::first_type> {
     
	const typename Pair::first_type& operator()(const Pair& x) const {
     
        return x.first;
    }  
};

template<class Pair>
struct select2nd : public unary_function<Pair, typename Pair::second_type> {
     
	const typename Pair::second_type& operator()(const Pair& x) const {
     
        return x.second;
    }  
};


/* 继承二元操作符类binary_function */
template<class T>
struct plus : public binary_function<T, T, T> {
     
	T operator()(const T& x, const T& y) const {
     
        return x + y;	// x + y为新创建的局部变量,返回值需要使用T接收,而不能使用引用T&
    }  
};

template<class T>
struct minus : public binary_function<T, T, T> {
     
	T operator()(const T& x, const T& y) const {
     
        return x - y;	// x - y为新创建的局部变量,返回值需要使用T接收,而不能使用引用T&
    }  
};

template<class T>
struct equal_to : public binary_function<T, T, T> {
     
	bool operator()(const T& x, const T& y) const {
     
        return x == y;
    }  
};

template<class T>
struct less : public binary_function<T, T, T> {
     
	bool operator()(const T& x, const T& y) const {
     
        return x < y;
    }  
};

仿函数继承的基类
【学习笔记】C++面向对象高级开发-侯捷_第21张图片


2.4 命名空间 namespace

namespace中可定义变量、函数、结构体和类等。

using namespace std;

//---------------------------
#include 
#include  //shared_ptr

namespace ns01
{
     
    void test_member_template(){
     
        ...
    }
}	//namespace ns01
//---------------------------

#include 
#include 

namespace ns02
{
     
    template<typename T>
    using Lst = list<T, allocator<T>>;
    void test_template_template_param(){
     
        ...
    }
}	//namespace ns02
//---------------------------

/* 测试程序 */
int main(int argc, char** argv)
{
     
    // 多个命名空间之间无任何关联
    ns01::test_member_template();
    ns02::test_template_template_param();
}

2.5 模板 template

2.5.1 类模板 class template

调用者在使用时再指定泛型的具体类型

template<typename T>	//类模板
class complex{
     
private:
    T re, im;	//模板
    friend complex& __doap1 (complex *ths, const complex& r); 	//友元函数

public:
    complex(T r = 0, T i = 0) : re(r), im(i){
     }	//默认参数、初始化列表
    complex& operate+=(const complex&);	//重载运算符
    T real() const {
      return re; }		//成员函数-常函数
    T imag() const {
      return im; }
};

2.5.2 函数模板 function template

注1:关键字class可替换为typename

注2:函数模板使用时,不必显式指定具体的泛型类型。编译器会对函数模板进行实参推导/类型推导(argument deduction)。类模板使用时,需显式指定具体的泛型类型。

//函数模板
template<class T>
inline const T& min(const T& a, 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){
     
        return this->_weight < rhs._weight;
    }
    
private:
    int _w, _h, _weight;
}

int main(){
     
    stone r1(2, 3), r2(3, 3), r3;
    //调用模板函数min,类型推导
    //类型推导T为stone类型,进一步调用stone::operator<
    r3 = min(r1, r2);	
}

2.5.3 成员模板 member template

成员模板:类模板中的成员(函数/属性)也为模板。

template <class T1, class T2>
struct pair {
     
    typedef T1 first_type;
    typedef T2 second_type;
    
    // 成员变量
    T1 first;
    T2 second;
    
    // 构造函数
    pair() : first(T1()) , second(T2()) {
     }	// 匿名对象T1()、T2()
    pair(const T1& a, const T2& b) : first(a), second(b) {
     }	// 有参构造
    
    /* 成员模板 */
    template <class U1, class U2>
    pair(const pair<U1, U2>& p) : first(p.first), second(p.second) {
     }	// 拷贝构造
};

应用场景:STL中的**拷贝构造函数//有参构造函数**常设计为成员模板。

成员模板的泛型类型类模板的泛型类型子类

注:成员模板的泛型类型U1/U2,继承自类模板的泛型类型T1/T2。反之不成立

class Base1 {
     };
class Derived1 : public Base1 {
     };

class Base2 {
     };
class Derived2 : public Base2 {
     };
pair</* U1 */Devired1, /* U2 */Devired2> p;
pair<Base1, Base2> p2(p);

/* 等价写法 */
// 使用成员模板创建匿名对象,用于类的拷贝构造函数
pair</*T1*/Base1, /*T2*/Base2> p2(pair</*U1*/Devired1, /*U2*/Devired2>());

示例:智能指针类成员模板的应用

智能指针子类shared_ptr有参构造函数的参数(被包装的指针),是智能指针父类__shared_ptr有参函数构造函数的参数(被包装的指针)的子类

template<typename _Tp>
class shared_ptr : public __shared_ptr<_Tp>
{
     
    ...
    /* 成员模板 */
    template<typename _Tp1>
    // 构造函数
    // 子类构造函数shared_ptr的参数__p,对父类构造函数__shared_ptr<_Tp>赋值
    explicit shared_ptr(/*Devired*/_Tp1* __p) : __shared_ptr</*Base*/_Tp>(__p) {
     }
    ...
};
/* 指针类的多态 */
Base1 *ptr = new Devired1;	// 向上转型up-cast
/* 智能指针类的多态 */
// 使用子类对象
shared_ptr<Base1*> sptr(new Devired1);	// 模拟向上转型up-cast

2.6 特化 specialization

2.6.1 类模板的全特化 template specialization

  • 泛化(generalization):使用时再指定模板参数的具体类型。
  • 特化(specialization):将模板泛化的部分进行局部的特征化。显式指定部分或全部模板参数的具体类型。
    • 全特化(full specialization):显式指定全部模板参数的具体类型。
    • 偏特化/局部特化(partial specialization):指定部分模板参数的具体类型。

注1:特化的优先级高于泛化。当存在合适的特化版本时,编译器优先调用相应的特化版本。

注2:全特化的所有模板参数均被指定,故模板参数列表为空,即template <>

示例:类模板的泛化和特化

/* 一般的泛化 */
template <class Key>
struct hash {
     };
/* 不同版本的特化 */
// 本案例只包含1个模板参数,特化后即全特化
template<>	// 全特化的所有参数均被指定,故模板参数列表为空。
struct hash<char> {
     
    size_t operator()(char x) const {
     
        return x;
    }
};

template<>
struct hash<int> {
     
    size_t operator()(int x) const {
     
        return x;
    }
};

template<>
struct hash<long> {
     
    size_t operator()(long x) const {
     
        return x;
    }
};
/* 测试案例 */
// 1.当未指定类型时,则使用泛化的类模板
// 2.当指定特定类型时,则使用相应特化版本的类模板
cout << hash<long>()(1000);	// 第1个()表示匿名对象;第2个()表示调用函数调用运算符

2.6.2 类模板的偏特化/局部特化 partial specialization

偏特化/局部特化(partial specialization):指定部分模板参数的具体类型。

个数的偏特化

若干数量的模板参数进行特化(显式指定类型)

注1:偏特化的模板参数,必须从左到右连续指定(如偏特化第1/2/3个),而不能穿插指定(如偏特化第1/3/5个),类似于默认参数必须从参数列表的最右侧往左连续。

注2:偏特化仅指定部分模板参数,剩余未被指定的模板参数需存在于模板参数列表,否则报错。

/* 泛化的类模板 */
template<typename T, typename Alloc = .../*默认参数*/>	// 模板参数
class vector
{
     
	... 
};
/* 偏特化的类模板-个数的偏特化 */
// 通过偏特化/局部特化,模板参数的2个泛型类型变为1个
// 偏特化仅指定部分模板参数,剩余未被指定的模板参数需存在于模板参数列表
template<typename Alloc = .../*默认参数*/>
class vector<bool, Alloc>	// 偏特化/局部特化:使用指定的bool类型,绑定泛型类型T
{
     
	... 
};

范围的偏特化

将模板参数类型的表示范围缩小

  • 将泛型类型T缩小为对应的指针类型T*
  • 将泛型类型T缩小为const限定符修饰T const

示例:类模板的泛化和偏特化

/* 泛化的类模板 */
template<typename T>	// 模板参数
class C
{
     
	... 
};
/* 偏特化的类模板-范围的偏特化:指针类型偏特化 */
template<typename T>	// 模板参数
class C<T*>
{
     
	... 
};

// 等价写法
template<typename U>	// 模板参数
class C<U*>
{
     
	... 
};

/* 偏特化的类模板-范围的偏特化:const偏特化 */
template<typename T>	// 模板参数
class C<T const>
{
     
	... 
};
/* 测试案例 */
/* 使用泛化版本的类模板 */
C<string> obj1;

/* 使用偏特化版本的类模板-范围的偏:指针类型偏特化 */
C<string*> obj2;	// 任意类型T → 任意指针类型T*

/* 使用偏特化版本的类模板-范围的偏:const偏特化 */
C<string const> obj3;	// 任意类型T → const类型T const

示例:类模板的泛化、全特化及偏特化的调用顺序

#include 
using namespace std;

/* 泛化的类模板 */
template<typename T1, typename T2>
class Test {
     
public:
	Test(T1 a, T2 b) : _a(a), _b(b) {
     
		cout << "泛化的类模板" << endl;
	}
private:
	T1 _a;
	T2 _b;
};

/* 全特化的类模板 */
template<>  // 全特化的所有参数均被指定,故模板参数列表为空
class Test<int, double> {
     
public:
	Test(int a, double b) : _a(a), _b(b) {
     
		cout << "全特化的类模板" << endl;
	}
private:
	int _a;
	double _b;
};

/* 偏特化的类模板-个数的偏特化 */
template<typename T2>   // 偏特化仅指定部分模板参数,剩余未被指定的模板参数需存在于模板参数列表
class Test<int, T2> {
     
public:
	Test(int a, T2 b) : _a(a), _b(b) {
     
		cout << "偏特化的类模板-个数的偏特化" << endl;
	}
private:
	int _a;
	T2 _b;
};

/* 偏特化的类模板-范围的偏特化:指针类型偏特化 */
template<typename T1, typename T2>
class Test<T1*, T2*> {
       // 将泛型类型 T 缩小为对应的指针类型 T*
public:
	Test(T1* a, T2* b) : _a(a), _b(b) {
     
		cout << "偏特化的类模板-范围的偏特化:指针类型偏特化" << endl;
	}
private:
	T1* _a;
	T2* _b;
};

/* 偏特化的类模板-范围的偏特化:const偏特化 */
template<typename T1, typename T2>
class Test<T1 const, T2 const> {
         // 将泛型类型 T 缩小为const限定符修饰 T const
public:
	Test(T1 a, T2 b) : _a(a), _b(b) {
     
		cout << "偏特化的类模板-范围的偏特化:const偏特化" << endl;
	}
private:
	T1 _a;
	T2 _b;
};

int main()
{
     
	Test<string, double> test1("hello", 1.1);   // 泛化的类模板
	Test<int, double> test2(1, 2.2);            // 全特化的类模板
	Test<int, string> test3(2, "world");        // 偏特化的类模板-个数的偏特化
	Test<int*, int*> test4(nullptr, nullptr);   // 偏特化的类模板-范围的偏特化:指针类型偏特化
	Test<const int, const int> test5(1, 2);     // 偏特化的类模板-范围的偏特化:const偏特化
	return 0;
}

2.6.3 函数模板的全特化

函数模板只有全特化没有偏特化

注:C++存在函数重载:形参列表不同(即参数个数、类型或顺序不同)的同名函数,编译器根据函数类型判断调用的重载函数。若函数模板存在偏特化(如个数的偏特化),会与函数重载产生冲突。

示例:函数模板的泛化及全特化

#include 
using namespace std;

// 函数模板只有全特化,没有偏特化
/* 泛化的函数模板 */
template<typename T1, typename T2>
void func(T1 a, T2 b) {
     
	cout << "泛化的函数模板" << endl;
}

/* 全特化的函数模板 */
template<>
void func(int a, double b) {
     
	cout << "全特化的函数模板" << endl;
}

int main()
{
     
	int a = 1;
	char ch = 'x';
	double b = 2.0;
	func(a, ch);	// 泛化的函数模板
	func(a, b);		// 全特化的函数模板
	return 0;
}

你可能感兴趣的:(C++侯捷大师,c++,面向对象编程)