文章目录

  • 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 函数模板的全特化
    • 2.7 模板模板参数 template template parameter
      • 2.7.1 模板模板参数的场景
      • 2.7.2 非模板模板参数的场景
    • 2.8 c++标准库STL(Standard Library)
    • 2.9 不定长的模板参数/可变模板参数 variadic templates
    • 2.10 auto关键字
    • 2.11 基于范围的for循环 ranged-base for
    • 2.12 引用 reference
      • 2.12.1 引用的概念
      • 2.12.2 引用的常见用途
    • 2.13 继承与组合关系下的构造和析构
      • 2.13.1 继承关系下的构造函数/析构函数
      • 2.13.2 组合关系下的构造函数/析构函数
      • 2.13.3 继承和组合关系下的构造函数/析构函数
    • 2.14 对象模型(Object Model):虚指针vptr和虚表vtbl
    • 2.15 对象模型(Object Model):this指针
    • 2.16 对象模型(Object Model):动态绑定(Dynamic Binding)
    • 2.17 const关键字
    • 2.18 new和delete的补充
      • 2.18.1 new表达式和delete表达式
      • 2.18.2 重载全局函数::operator new、::operator delete、::operator new[]、::operator delete[]
      • 2.18.3 重载成员函数operator new/delete、operator new[]/delete[]
        • 重载成员函数operator new/delete
        • 重载成员函数operator new[]/delete[]
      • 2.18.4 示例
        • 示例1:调用全局函数及成员函数的operator new/delete
        • 示例2:调用重载的成员函数operator new[]/delete[]
        • 示例3:调用重载的全局函数::operator new[]/delete[]
      • 2.18.5 重载placement new / placement delete
        • 重载placement new,即new()
        • 重载placement delete,即delete()
      • 2.18.6 重载placement new/delete的STL案例

学习笔记源自博览网侯捷大师的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<double> c1(2.5, 1.5);complex<int> 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 <iostream>
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);


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);


1.6.7 malloc动态内存分配

单个对象的动态内存分配

![malloc动态内存分配原理图

  • 目标对象: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,表示申请内存。

对象数组的动态内存分配

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

array new和array delete搭配使用

  • 不含指针类型成员时,仅使用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<double> c1(2.5, 1.5);complex<int> 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 <iostream>
using namespace std;int main(){cin >> ... ;cout << ... ;return 0;
}

using声明(using declaration)

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

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

1.7.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<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(x); }void pop() { c.pop_front(); }
};

组合关系的内存示意图


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

构造函数的调用:由内向外

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

析构函数的调用:由外向内

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


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,委托类/被委托类):编译防火墙,可灵活地指向不同的实现类。
委托类的指针成员指向具体的实现类(被委托类)。


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

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


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

构造函数的调用:由内向外

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

析构函数的调用:由外向内

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

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


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


1.10 委托设计案例Delegation

1.10.1 组合模式(Composite)

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

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

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

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

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

    因此,必须使用vector<Component*>


1.10.2 原型模式(Prototype)

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

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

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


原型模式-基类


原型模式-子类


原型模式-测试程序


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 转换函数的其它应用场景


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表示实际指针,指向双向链表的节点;迭代器是包装实际指针的智能指针


2.3 function-like classes 仿函数

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

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

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

select1st类仿函数的调用:select1st<Pair>()(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;}
};

仿函数继承的基类


2.4 命名空间 namespace

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

using namespace std;//---------------------------
#include <iostream>
#include <memory> //shared_ptrnamespace ns01
{void test_member_template(){...}
}   //namespace ns01
//---------------------------#include <iostream>
#include <list>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 <iostream>
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 <iostream>
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;
}

2.7 模板模板参数 template template parameter

模板模板参数模板参数列表中的模板参数(泛型类型)仍是模板。

注:只有在模板参数列表的尖括号<>内,关键字classtypename才等价。

2.7.1 模板模板参数的场景

示例1:模板参数包含容器类型(容器的类模板通常包含多个模板参数

/* 模板模板参数 */
template<typename T,template<typename T>class Container    // 第2个模板参数(泛型类型)仍是模板>
class XCLs
{private:Container<T> c;       // 第2个模板参数Container以第1个模板参数T作为元素类型
public:...
};
/* 期望的使用方式 */
int main(){// 容器包含多个模板参数,实际不止template<typename T>,其它模板参数通常具有默认值//XCLs<string, list> mylist1;  // 编译失败
}
/* 正确的使用方式 */
// 容器包含多个模板参数,如链表list的类模板:list<T, allocator<T>>
template<typename T>
using lst = list<T, allocator<T>>; // C++ 2.0语法int main(){XCLs<string, lst> mylist2;   // 编译成功/* 等价写法 */// 第2个模板参数的泛型类型未被完全绑定XCLs<string, list<T, allocator<T>> mylist3;    // 编译成功
}

示例2:模板参数包含智能指针类型(智能指针的类模板通常只包含1个模板参数

/* 模板模板参数 */
template<typename T,template<typename T>class SmartPtr // 第2个模板参数(泛型类型)仍是模板>
class XCLs
{private:SmartPtr<T> sp;
public:XCLs() : sp(new T) {}    // 构造函数 + 初始化列表
};
/* 使用传入的智能指针SmartPtr作为第2个模板参数的类型,并使用第1个模板参数T作为智能指针的类型 */
int main(){XCLs<string, shared_ptr> p1;XCLs<long, auto_ptr> p2;//XCLs<double, unique_ptr> p3; // unique_ptr特性不支持//XCLs<int, weak__ptr> p4;      // weak_ptr特性不支持
}

2.7.2 非模板模板参数的场景

template<class T,class Sequence = deque<T> // 默认值为deque<T>,而不是类模板>
class stack {friend bool operator== <> (const stack&, const stack&);friend bool operator< <> (const stack&, const stack&);protected:Sequence c;    // 底层容器...
};
/* 使用方式 */
int main(){stack<int> s1;             // 第2个模板参数为默认值deque<int>stack<int, list<int>> s2; // 第2个模板参数为指定值list<int>,泛型类型已完全绑定,不再是模板
}

2.8 c++标准库STL(Standard Library)

  • 容器 Containers
  • 算法 Algorithms
  • 迭代器 Iterators
  • 仿函数 Functors

2.9 不定长的模板参数/可变模板参数 variadic templates

语法

  • 模板参数列表:template<typename T, typename... Types>
  • 函数形参列表或类的泛型列表:Types... argsTypes&... args
  • 调用点:func(args...)
// 形参列表为空的print函数
void print(){}// 形参列表为 单参 + 可变模板参数 的print函数
/* 可变模板参数 */
template <typename T, typename... Types>
// 将调用者传入的模板参数分为1个和1小包
void print(const T& firstArg, const Types&... args)
{cout << firstArg << endl;/* 递归调用print()函数 */// 递归终止条件:可变模板参数均已访问完毕,最后调用形参列表为空的print函数print(args...);
}int main()
{// 依次传入:// (1)7.5 和 {"hello", bitset<16>(377), 42}// (2)"hello" 和 {bitset<16>(377), 42}// (3)bitset<16>(377) 和 42// (4)42 和 空参// (5)空参print(7.5, "hello", bitset<16>(377), 42); // bitset类包含重载的<<运算符// 7.5// hello// 0000000101111001// 42
}

注1:空参版本和单参 + 可变模板参数版本的print函数必须同时存在,否则当可变模板参数均已访问完毕后,调用出错。

注2:在包含不定长模板参数的模板中,可使用sizeof...(args)操作符,获取不定长模板参数的个数


2.10 auto关键字

使用auto关键字声明变量或对象时,编译器自动对其类型进行推导。【语法糖】

/* 正常写法 */
list<string> c;
...
list<string>::iterator ite;
ite = find(c.begin(), c.end(), targetStr);/* 使用auto关键字 */
list<string> c;
...
auto ite = find(c.begin(), c.end(), targetStr);

注:使用auto关键字声明变量或对象的同时,必须进行定义(显式赋值操作)。

/* 错误写法 */
list<string> c;
...
// auto ite;
// ite = find(c.begin(), c.end(), targetStr);

2.11 基于范围的for循环 ranged-base for

语法

for( decl : coll ) {statement
}for( 声明 : 容器 ) {语句
}

注:该写法比迭代器、for_each()简单。【语法糖】

示例1

for(int i : {1, 3, 5, 7, 9}) {cout << i << endl;
}

示例2

vector<int> vec
.../* 传值:pass by value */
// 拷贝容器中的元素并赋值给局部变量elem——不影响容器中的原有元素
for(auto elem : vec) {cout << elem << endl;
}/* 传引用:pass by reference */
// 对容器中的元素进行操作——影响容器中的原有元素
for(auto& elem : vec) {elem *= 3;
}

2.12 引用 reference

2.12.1 引用的概念

引用:变量或对象的别名。

注1:声明引用时必须显式初始化赋值,且设定初值后不可以再改变引用所代表的变量或对象,但可以修改其值。

注2:引用与指针不等价,但大多数编译器底层使用指针实现引用

注3:变量或对象和其引用的内存大小相同地址相同。(均为假象

int& r = x;

sizeof(r) == sizeof(x); // 内存大小相同

&r = &x; // 地址相同

注4:Java中,所有变量均为引用(reference)。

int x = 0;      // 4字节
// 指针类型:指向变量的地址
int* p = &x;   // 32位系统下指针类型为4字节
// 引用类型:变量的别名,代表该变量
int& r = x;        // 4字节

错误示例

/* 1.声明引用时,必须显式初始化赋值 */
// int& r1;     // 错误
// r1 = x;/* 2.引用设定初值后,不可再改变 */
int a = 1;
int b = 5;
int& r2 = a;
// 引用声明后,不能再重新代表其它变量或对象
r2 = b;        // r2表示a,将r2和a的值由1修改为5(表示的变量不变,但重新赋值)

示例:变量或对象和其引用的内存大小相同地址相同。(均为假象

typedef struct Stag {int a, b, c, d} S;int main(){double x = 0;double* p = &x; // p指向x,p的值是x的地址double& r = x;  // r代表x,r、x的值均为0cout << sizeof(x) << endl; // 8cout << sizeof(p) << endl; // 4(32位系统下)cout << sizeof(r) << endl; // 8(假象:引用底层实现为指针,应为4,但表现为8)cout << p << endl; // 0065FDFCcout << &x << endl;  // 0065FDFCcout << &r << endl;  // 0065FDFC(假象:引用实际存在自己的内存地址,但表现为与变量相同)cout << *p << endl;  // 0cout << x << endl;  // 0cout << r << endl;  // 0S s;S& rs = s;cout << sizeof(s) << endl;   // 16cout << sizeof(rs) << endl;    // 16(假象)cout << &s << endl;      // 0065FDE8cout << &rs << endl; // 0065FDE8(假象)
}

2.12.2 引用的常见用途

引用通常较少用于直接声明变量或对象,常用于参数传递(函数参数类型和返回类型的表示)。

/* 非同名函数的调用 */
// 指针传递:pass by pointer
void func1(Cls* pobj) { pobj->xxx(); }
// 值传递:pass by value
void func2(Cls obj) { obj.xxx(); }
// 引用传递:pass by reference
void func3(Cls& obj) { obj.xxx(); }Cls obj;
func1(&obj);
func2(obj); // 实参传递时会拷贝对象,效率较差
func3(obj);

注:同名函数的签名,形参列表的非引用版本和引用版本不可同时存在,不构成函数重载。否则存在歧义,编译器不清楚应该调用哪个函数。

/* 同名函数的签名,非引用版本和引用版本,不可同时存在,不构成函数重载 */
double func(const double& d) {...}
//double func(const double d) {...}     // Ambiguity(二义性)

注: const是函数签名的一部分,未使用const的版本和使用const的版本可同时存在,构成函数重载

/* 同名函数的签名,未使用const和使用const,可同时存在,构成函数重载 */
string function(const string& str) const {...}
string function(const string& str) {...}

2.13 继承与组合关系下的构造和析构

2.13.1 继承关系下的构造函数/析构函数

继承关系:从内存/数据的角度而言,子类对象包含父类的成分。

构造函数的调用:由内向外

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

Derived::Devired(...) : Base() { ... } // 编译器自动调用父类默认构造


析构函数的调用:由外向内

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

Derived::~Devired(...) { ... ~Base() } // 执行完子类析构后,再执行父类析构

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


2.13.2 组合关系下的构造函数/析构函数

组合/复合关系:从内存/数据的角度而言,Container对象包含Component类的成分。

构造函数的调用:由内向外

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

Container::Container(...) : Component() { ... } // 编译器自动调用Component类默认构造


析构函数的调用:由外向内

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

Container::~Container(...) { ... ~Component() } // 执行完Container类析构后,再执行Component类析构


2.13.3 继承和组合关系下的构造函数/析构函数

从内存/数据的角度而言,Devired对象包含父类和Component类的成分。

构造函数的调用:由内向外

Devired的构造函数先调用Base的默认构造函数,再调用Component的默认构造函数,最后执行本身。

Devired::Devired(...) : Base(), Component() { ... }


析构函数的调用:由外向内

Devired的析构函数先执行本身,再调用Component的析构函数,最后调用Base的析构函数。

Devired::~Devired(...) { ... ~Component(), ~Base() }


2.14 对象模型(Object Model):虚指针vptr和虚表vtbl

虚指针(virtual pointer):当类包含虚函数时,其对象包含指向虚函数表的虚指针。

虚函数表/虚表(virtual function table):存储的元素均为函数指针,指向虚函数的内存地址

注:继承虚函数,是继承函数的调用权,而不是函数所占的内存大小。

静态绑定(C语言) vs. 动态绑定(C++语言)

静态绑定(C语言):编译器针对调用的动作,编译为CALL_XXX(地址),当调用某个函数时,编译器进行解析并跳转至对应地址,随后return返回。

动态绑定(C++语言):通过指向对象的指针(可向上转型,即多态),依次查找虚指针、虚函数表及指向的虚函数地址。指向当前对象的指针即this指针

虚机制(virtual mechanism):满足动态绑定的3个条件时,编译器会以动态绑定的形式进行编译。

  1. 通过指向对象的指针调用;
  2. 指向对象的指针向上转型(up-cast),即多态(polymorphism);
  3. 调用虚函数。

使用C语言的语法,实现的动态绑定:

(*(p->vptr)[n])(p);(* p->vptr[n])(p);

注:具体调用哪个类的虚函数,由传入对象的具体类型决定。

注:多态、虚函数、动态绑定(虚指针与虚函数表)为同一概念。


示例:存储不同形状的容器

  • 不同形状对应的类,其对象所占用的内存大小可能不同。
  • C++容器只能存储相同类型的元素。

为保证容器可以存储不同形状的对象,需将容器的元素类型定义为指向形状基类的指针类型

例如:list<Shape*> shapeList;


2.15 对象模型(Object Model):this指针

this指针:当前调用成员变量或成员函数的对象的地址

注1:C++中所有成员函数均包含隐藏的this指针,表示当前调用该成员函数的对象。
注2:this指针即指向当前对象的指针,可通过动态绑定,依次查找虚指针、虚函数表及指向的虚函数地址

虚函数应用的场景:

  • 多态(Polymorphism)
  • 模板方法(Template Method)

模板方法:在抽象基类中定义整个算法流程,而将若干具体实现步骤(通过使用虚函数)延迟子类中。
模板方法使得子类可以不改变抽象基类的算法流程,即可重定义该算法的某些特定步骤(重写虚函数)。
不同的子类能够以不同的逻辑实现特定步骤,而不影响抽象基类的算法流程。


2.16 对象模型(Object Model):动态绑定(Dynamic Binding)



2.17 const关键字

常成员函数:const修饰的成员函数只访问且不修改类的数据成员。
语法T func() const {}


(常)对象调用(常)成员函数的规则
规则一

对象调用成员函数的关系 常对象
const object
(不可修改数据成员)
非常对象
non-const object
(可修改数据成员)
常成员函数
const member functions
(保证不修改数据成员)
非常成员函数
non-const member functions
(不保证不修改数据成员)
×

规则二

当成员函数的const版本和non-const版本同时存在时(即函数重载):

  • 常对象(const object)只会(只能)调用成员函数的const版本;
  • 非常对象(non-const object)只会(只能)调用成员函数的non-const版本。

注:非常成员函数(non-const)可调用常成员函数(const),反之不行。

否则报错:connot convert 'this' pointer from 'const class X' to 'class X &'. Conversion loses qualifiers.(转换丢失限定符)

示例
template class std::basic_string<..> 包括2个同名成员函数。
STL字符串类通过引用计数实现,对象拷贝后共享同一份数据内容。
当涉及共享操作时,需考虑数据修改的问题。

/* const版本 */
// 常量字符串调用const版本,无需考虑COW
charT operator[](size_type pos) const
{ /* 不必考虑COW(Copy On Write) */
}/* 非const版本 */
// 非常量字符串调用非const版本,需考虑COW
reference operator[](size_type pos)
{ /* 必须考虑COW(Copy On Write) */
}

注:const属于函数签名的一部分,使用和未使用const的同名函数构成函数重载


2.18 new和delete的补充

2.18.1 new表达式和delete表达式

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);

delete表达式:先调用析构函数,再释放内存。

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

注:new表达式的步骤(分配内存、静态类型转换、调用构造函数)和delete表达式的步骤(调用析构函数、释放内存)不可改变;但operator new()operator delete()函数可重载。


2.18.2 重载全局函数::operator new、::operator delete、::operator new[]、::operator delete[]

注:::operator new::operator delete::operator new[]::operator delete[]均为全局函数。重载后的全局函数由编译器调用。

void* myAlloc(size_t size)
{return malloc(size);
}void myFree(void* ptr)
{free(ptr);
}/* 重载全局函数::operator new */
inline void* operator new(size_t size)
{cout << "self global new()" << endl;return myAlloc(size);
}/* 重载全局函数::operator new[] */
inline void* operator new[](size_t size)
{cout << "self global new[]()" << endl;return myAlloc(size);
}/* 重载全局函数::operator delete */
inline void operator delete(void* ptr)
{cout << "self global delete()" << endl;myFree(ptr);
}/* 重载全局函数::operator delete[] */
inline void operator delete[](void* ptr)
{cout << "self global delete[]()" << endl;myFree(ptr);
}

2.18.3 重载成员函数operator new/delete、operator new[]/delete[]

注:重载成员函数operator new/deleteoperator new[]/delete[],可改变调用者的行为。

重载成员函数operator new/delete

class Foo {public:/* per-class allocator */void* operator new(size_t);void operator delete(void*, size_t /* optional */);
};
/* 调用成员函数operator new */
Foo* p = new Foo;/* 调用成员函数operator new的底层步骤 */
try{// 1.调用重载后的成员函数operator newvoid* mem = operator new(sizeof(Foo));// 2.静态类型转换:void* → Foo*p = static_cast<Foo*>(mem);// 3.调用构造函数p->Foo:Foo();
}
/* 调用成员函数operator delete */
delete p;/* 调用成员函数operator delete的底层步骤 */
// 1.调用析构函数
p->Foo:~Foo();
// 2.调用重载后的成员函数operator delete
operator delete(p);


重载成员函数operator new[]/delete[]

class Foo {public:/* per-class allocator */void* operator new[](size_t);void operator delete[](void*, size_t /* optional */);
};
/* 调用成员函数operator new[] */
Foo* p = new Foo[N];/* 调用成员函数operator new[]的底层步骤 */
try{// 1.调用重载后的成员函数operator new[]void* mem = operator new(sizeof(Foo) * N + 4);// 2.静态类型转换:void* → Foo*p = static_cast<Foo*>(mem);// 3.调用构造函数p->Foo:Foo();   /* 调用N次构造函数 */
}
/* 调用成员函数operator delete[] */
delete[] p;/* 调用成员函数operator delete[]的底层步骤 */
// 1.调用析构函数
p->Foo:~Foo();   /* 调用N次析构函数 */
// 2.调用重载后的成员函数operator delete
operator delete(p);


2.18.4 示例

示例1:调用全局函数及成员函数的operator new/delete

注:若调用者使用全局函数::operator new/delete,则编译器底层调用全局的void* ::operator new(size_t)void operator delete(void*)


示例2:调用重载的成员函数operator new[]/delete[]


示例3:调用重载的全局函数::operator new[]/delete[]

当调用者使用全局函数::operator new[]/delete[]时,编译器底层会强制使用全局函数版本。


2.18.5 重载placement new / placement delete

重载placement new,即new()

类成员函数operator new()可进行函数重载,需满足如下条件:

  • 每个重载版本的函数声明,必须有唯一的参数列表(不同重载版本的参数列表不能相同);
  • 参数列表的第1个参数,必须是size_t类型;
  • 参数列表的其余参数,以new所指定的placement argument为初值,即出现在new(...)小括号中的参数。

例:Foo* pf = new(300, 'c') Foo; // 调用无参构造函数,300和’c’均为placement argument


示例:重载new()

/* 构造不同重载版本的operator new() */
class Foo {public:// 无参构造Foo() {cout << "Foo:Foo()" << endl;}// 有参构造Foo(int) {cout << "Foo:Foo(int)" << endl;// 特意抛出异常,用于测试placement operator deletethrow Bad();    // 异常类:class Bad {};}/* operator new()的不同重载版本 */// (1) 一般重载版本:不包含placement argumentvoid* operator new(size_t size) {return malloc(size);}// (2) STL提供的placement new()的重载形式void* operator new(size_t size, void* start) {return start;}// (3) 自定义的placement new版本void* operator new(size_t size, long extra) {return malloc(size + extra);}// (4) 自定义的placement new版本void* operator new(size_t size, long extra, char init) {return malloc(size + extra);}/*// (5) 特意遗漏第1参数的placement new版本:第1参数必须是size_t,因此构造抛出异常的场景//错误信息://[Error]'operator new' takes type 'size_t'('unsigned int') as first parametervoid* operator new(long extra, char init) {return malloc(extra);}*/
};

重载placement delete,即delete()

类成员函数operator delete()可进行函数重载(非强制),但重载版本一定不会被delete所调用。

仅当new所调用的构造函数抛出异常Exception),才会调用重载版本的operator delete()

注:operator delete()仅能在该条件下呗调用,主要作用是释放未完全创建成功的对象所占用的内存。


示例:重载delete()

注:operator delete()即使未与operator new()一一对应,也不会出现任何报错,编译器会视为:放弃处理构造函数抛出的异常。

VC6 warning C4291: ‘void’ *__cdecl Foo::operator new(~~~) no matching operator delete found; memory will not be freed if initialization throws an exception.

/* 针对不同重载版本的operator new()构造的operator delete() */
// 仅当构造函数抛出异常时,与operator new()对应版本的operator delete()才会被调用
// 作用:释放对应operator new()分配的内存/* operator delete()的不同重载版本 */
// (1) 一般重载版本:不包含placement argument
void* operator delete(void*, size_t /* 可选 */) {cout << "operator delete(void*, size_t)" << endl;
}// (2) STL提供的placement delete()的重载形式
void* operator delete(void*, void*) {cout << "operator delete(void*, void*)" << endl;
}// (3) 自定义的placement delete版本
void* operator delete(void*, long) {cout << "operator delete(void*, long)" << endl;
}// (4) 自定义的placement delete版本
void* operator delete(void*, long, char) {cout << "operator delete(void*, long, char)" << endl;
}
/* 测试程序 */
Foo start;
// 调用无参构造函数
Foo* p1 = new Foo;
Foo* p2 = new(&start) Foo;
Foo* p3 = new(100) Foo;
Foo* p4 = new(100, 'a') Foo;// 调用有参构造函数
Foo* p5 = new Foo(1);  // 调用时已抛出异常,后续代码不会继续执行
// Foo* p6 = new(&start) Foo(1);
// Foo* p7 = new(100) Foo(1);
// Foo* p8 = new(100, 'a') Foo(1);

2.18.6 重载placement new/delete的STL案例

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

  1. 【学习笔记】C++内存管理-侯捷

    文章目录 C++内存管理:从平地到万丈高楼 一.primitives-基础工具 1.内存分配的四个层面 2.new/delete表达式 2.1 new表达式 2.2 delete表达式 学习笔记源自博 ...

  2. Python学习笔记:面向对象高级编程(完)

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  3. Python学习笔记:面向对象高级编程(中下)

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  4. Python学习笔记:面向对象高级编程(中上)

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  5. Python学习笔记:面向对象高级编程(上)

    前言 最近在学习深度学习,已经跑出了几个模型,但Pyhton的基础不够扎实,因此,开始补习Python了,大家都推荐廖雪峰的课程,因此,开始了学习,但光学有没有用,还要和大家讨论一下,因此,写下这些帖 ...

  6. 【廖雪峰Python学习笔记】面向对象高级编程

    文章目录 为实例和类绑定属性和方法 \_\_slots__限制当前class实例,对子类不管用 @property装饰器,将`getter`方法变成属性 多重继承 -- 一个子类可同时获得多个父类的所 ...

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

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

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

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

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

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

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

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

最新文章

  1. 实战:车牌识别之车牌定位
  2. Python 进阶 — 面向对象设计原则
  3. 霍布森选择效应(Hobson choice Effect)
  4. cmake学习(五) 系统默认变量和内置变量
  5. java mysql nclob_java语言操作Oracle数据库中的CLOB数据类型 (转)
  6. 本月初 本月末 java_本月内容作家(2018年8月)
  7. Pycharm2018的激活方法或破解方法
  8. python 把txt变成字符串_如何通过 Python 如何写文件 ?
  9. virtualenv: 未找到命令
  10. linux不允许将硬链接指向目录,为什么 UNIX/Linux 不允许目录硬链 【翻译】
  11. 关于修改域用户密码的WebPart的问题的问题.
  12. 上位机plc编程入门_图解PLC编程入门
  13. 下载XAMPP并安装和使用(Mac环境)
  14. 写在1024,致程序员致程序员节致自己
  15. 苹果cms安装mysql检测失败_苹果cmsV10安装过程中的常见问题处理办法
  16. javascript高级一
  17. 关于“Error: Net gdfx_temp0, which fans out to ***:inst4|BIN, cannot be assigned more than”错误
  18. 怎么把一张普通照片变成一寸照?这个小技巧了解一下
  19. Linux文件及日志内容
  20. 一篇文章搞懂设计模式

热门文章

  1. 定制化件T恤其实很简单,您需要了解的有以下几点
  2. linux系统可以安装浩辰CAD,浩辰CAD Linux下载
  3. css实现超过两行用...表示
  4. 【WEb数据采集之js埋码】
  5. 滕振宇谈如何进行单元测试
  6. 代码评审的价值和规范
  7. php 新历转农历,PHP实现阳历转阴历的方法
  8. JAVAWEB-NOTE01
  9. 广告roi怎么计算公式_【图】- 什么是ROI?ROI计算公式?ROI的影响因素 - 杭州江干九堡广告媒体 - 杭州百姓网...
  10. Java文字转语音功能实现