C++面向对象程序设计(侯捷)笔记

  侯捷老师带我飞,再学习C++系列。

前言

  再复习C++,现整理笔记如下。(以下【注】为我的注释)   写正规大气的C++代码。感谢侯捷老师!

C++编程简介

你应具备的基础

  • 曾学过某种procedural language(C语言) 变量(variables) 类型(types):int, float, char, struct … 作用域(scope) 循环(loops):while, for 流程控制:if-else, switch-case
  • 知道一个程序需要编译、连结才能被执行
  • 知道如何编译和连结(如何建立一个可运行程序)

【注】台湾译法也许有某些不同,如此处“连结”。本笔记不改变作者用词。

我们的目标

  • 培养正规的、大气的编程习惯
  • 以良好的方式编写C++ class(Object Based)
    • class without pointer members – Complex
    • class with pointer members – String
  • 学习Classes之间的关系(Objected Oriented)
    • 继承(inheritance)
    • 复合(composition)
    • 委托(delegation)

你将获得的代码

complex.h complex-test.cpp

string.h string-test.cpp

oop-demo.h oop-test.cpp

C++的历史

  • B语言(1969)
  • C语言(1972)
  • C++语言(1983) (new C -> C with Class ->C++)
  • Java语言
  • C#语言

C++演化

  • C++ 98(1.0)
  • C++ 03(TR1, Technical Report 1)
  • C++ 11(2.0)
  • C++ 14

C++:C++语言、C++标准库

Bibliography(书目志)

  1. C++ Primer
  2. The C++ PROGRAMMING LANGUAGE
  3. Effective C++
  4. THE C++ STANDARD LIBRARY
  5. STL源码剖析

头文件与类的声明

C vs C++,关于数据和函数

C vs C++

C++,关于数据和函数

C++,关于数据和函数

1
2
3
complex c1(2,1);
complex c2;
complex* pc = new complex(0,1);

【注】数据可以有很多份,函数只有一份。

1
2
3
string s1("Hello");
string s2("World");
string* ps = new string;

Object Based(基于对象) vs Object Oriented(面向对象)

Object Based:面向的是单一class的设计 Object Oriented:面对的是多重classes的设计,classes和classes之间的关系。

我们的第一个C++程序

Classes的两个经典分类:

  • Class without pointer member(s) complex
  • Class with pointer member(s) string

C++ programs代码基本形式

代码基本形式

延伸文件名(extension file name)不一定是.h或.cpp,也可能是.hpp或其他或甚至无延伸名。

Output, C++ vs. C

C++

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include <iostream>
using namespace std;

int main()
{
    int i = 7;
    cout<< "i=" << i << endl;

    return 0;
}

C

1
2
3
4
5
6
7
8
9
include <stdio.h>

int main()
{
    int i = 7;
    printf("i=%d \n", i);

    return 0;
}

Header(头文件)中的防卫式声明

guard(防卫式声明) complex.h

1
2
3
4
5
6
#ifndef __COMPLEX__
#define __COMPLEX__

...

#endif

【注】如果xxx已经定义过了,则不进入下方的定义。

complex-test.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include "complex.h"
using namespace std;

int main()
{
    complex c1(2,1);
    complex c2;
    cout << c1 << endl;
    cout << c2 << endl;

    c2 = c1 + 5;
    c2 = 7 + c1;
    c2 = c1 + c2;
    c2 += c1;
    c2 += 3;
    c2 = -c1;

    cout << (c1 == c2) << endl;
    cout << (c1 != c2) << endl;
    cout << conj(c1) << endl;
    return 0;
}

Header(头文件)的布局

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef __COMPLEX__
#define __COMPLEX__

#include<cmath>

//forward declarations(前置声明)
class ostream;
class complex;

complex&
    __doapl (complex* ths, const complex& r);

//class declarations(类-声明)
class complex
{
    ...
};

//class definiton(类-定义)
complex::function ...

#endif

class的声明(declaration)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class complex//class head
{//class body
public:
    complex (double r = 0, double i = 0)
    : re (r), im(i)
    {}
    complex& operator += (const complex&);
    double real () const {return re;}
    double imag () const {reutn im;}//有些函数在此直接定义,另一些在body之外定义
private:
    double re, im;

    friend complex& __doapl (complex*, const complex&);
};

使用:

1
2
3
4
5
{
    complex c1(2,1);
    complex c2;
    ...
}

class template(模板)简介

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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 {reutn im;}//有些函数在此直接定义,另一些在body之外定义
private:
    T re, im;

    friend complex& __doapl (complex*, const complex&);
};

使用:

1
2
3
4
5
{
    complex<double> c1(2.5,1.5);
    complex<int> c2(2,6);
    ...
}

构造函数

inline(内联)函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class complex
{
public:
    complex (double r = 0, double i = 0)
    : re (r), im(i)
    {}//函数若在class body内定义完成,便成为inline候选人
    complex& operator += (const complex&);
    double real () const {return re;}//可以inline
    double imag () const {reutn im;}//可以inline
private:
    double re, im;

    friend complex& __doapl (complex*, const complex&);
};

使用:

1
2
3
4
5
inline double
imag(const complex& x)
{
    return x.imag ();
}

【注】函数太复杂,就不能inline。

access level(访问级别)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class complex
{
public:
    complex (double r = 0, double i = 0)
    : re (r), im(i)
    {}
    complex& operator += (const complex&);
    double real () const {return re;}
    double imag () const {reutn im;}
private:
    double re, im;

    friend complex& __doapl (complex*, const complex&);
};

错误使用:

1
2
3
4
5
{
    complex c1(2,1);
    cout << c1.re;
    cout << c1.im;
}

正确使用:

1
2
3
4
5
{
    complex c1(2,1);
    cout << c1.real();
    cout << c1.imag();
}

constructor(ctor,构造函数)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class complex
{
public:
    complex (double r = 0, double i = 0)//default argument(默认实参)
    : re (r), im(i)//initialization list(初值列,初始列)
    { }
    complex& operator += (const complex&);
    double real () const {return re;}
    double imag () const {reutn im;}
private:
    double re, im;

    friend complex& __doapl (complex*, const complex&);
};

【注】其他函数也可以有默认值。构造函数没有返回值类型。initialization list(初值列,初始列)上下三行等价于

1
2
3
4
complex (double r = 0, double i = 0)
{
    re = r; im = i;
}

但建议用初始列方式写。初始化+赋值。初始列就是初始化的阶段。

使用:

1
2
3
4
5
{
    complex c1(2,1);
    complex c2;//没有指明,用默认值
    complex* p = new complex(4);
}

【注】不带指针的类多半不用写析构函数。

ctor(构造函数)可以有很多个 - overloading(重载)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class complex
{
public:
    complex (double r = 0, double i = 0)
    : re (r), im(i)
    { }
    complex () : re(0), im(0) { }//?!
    complex& operator += (const complex&);
    double real () const {return re;}
    double imag () const {reutn im;}
private:
    double re, im;

    friend complex& __doapl (complex*, const complex&);
};
1
2
3
4
void real(double r)
{
    re = r;
}

real函数编译后的实际名称可能是: ?real@Complex@@QBENXZ ?real@Complex@@QAENABN@Z 取决于编译器。

【注】重载表面名字相同,其实在编译器内名字不同。

构造函数重载:

1
2
3
4
{
    Complex c1;
    Complex c2();//写法不同,意思相同
}

所以?!处的构造函数不能这样重载。

参数传递与返回值

constructor(ctor,构造函数)被放在private区

ctor被放在private区

以下无法使用:

1
2
complex c1(2,1);
complex c2;

那么是不是说ctor不应该放在private区呢?也不是。

Singleton(单例)设计模式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class A
{
public:
    static A& getInstance();
    setup() {...}
private:
    A();
    A(const A& rhs);
    ...
};

A& A::getInstance()
{
    static A a;
    return a;
}

使用:

1
A::getInstance().setup();

const member functions(常量成员函数)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class complex
{
public:
    complex (double r = 0, double i = 0)
    : re (r), im(i)
    { }
    complex& operator += (const complex&);
    double real () const {return re;}
    double imag () const {reutn im;}
private:
    double re, im;

    friend complex& __doapl (complex*, const complex&);
};

正确使用:

1
2
3
4
5
{
    complex c1(2,1);
    cout << c1.real();
    cout << c1.imag();
}

【注】不改变数据就加const。

?!

1
2
3
4
5
{
    const complex c1(2,1);//我这个值是不能改变的哦
    cout << c1.real();//万一real函数没写const,就可能改data。就会产生矛盾
    cout << c2.imag();
}

参数传递:pass by value vs. pass by reference(to const)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class complex
{
public:
    complex (double r = 0, double i = 0)
    : re (r), im(i)
    { }
    complex& operator += (const complex&);//传引用速度很快哦,并且不能改我,我加了const哦。如果你改我,编译器就会报错
    double real () const {return re;}
    double imag () const {reutn im;}
private:
    double re, im;

    friend complex& __doapl (complex*, const complex&);
};

没有const:

1
2
3
4
5
6
ostream&
operator << (ostream& os, const complex& x)
{
    return os << '(' << real (x) << ','
              << imag (x) << ')';
}

【注】pass by value压到栈里。大的遵循守则,尽量不要pass by value。在C里用指针。C++ pass by reference。 如果不希望对方改数据,加const。

使用:

1
2
3
4
5
6
7
{
    complex c1(2,1);
    complex c2;

    c2 += c1;
    cout << c2;
}

返回值传递:return by value vs. return by reference(to const)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class complex
{
public:
    complex (double r = 0, double i = 0)
    : re (r), im(i)
    { }
    complex& operator += (const complex&);
    double real () const {return re;}
    double imag () const {reutn im;}
private:
    double re, im;

    friend complex& __doapl (complex*, const complex&);
};

friend(友元)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class complex
{
public:
    complex (double r = 0, double i = 0)
    : re (r), im(i)
    { }
    complex& operator += (const complex&);
    double real () const {return re;}
    double imag () const {reutn im;}
private:
    double re, im;

    friend complex& __doapl (complex*, const complex&);
};

使用:

1
2
3
4
5
6
7
inline complex&
__doapl (complex* ths, const complex& r)
{
    this->re += r.re;
    this->im += r.im;//自由取得friend的private成员
    return *ths;
}

【注】friend打破封装。

相同class的各个objects互为friends(友元)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class complex
{
public:
    complex (double r = 0; double i = 0)
    : re (r), im (i)
    { }

    int func(const complex& param)
    { return param.re + param.im; }//这个怎么可以直接拿咧~

private:
    double re, im;
};

使用:

1
2
3
4
5
6
{
    complex c1(2,1);
    complex c2;

    c2.func(c1);
}

class body外的各种定义(definitions)

什么情况下可以pass by reference 什么情况下可以return by reference

do assignment plus 第一参数将会被改动,第二参数不会被改动

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
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);
}

操作符重载与临时对象

operator overloading(操作符重载-1,成员函数) this

操作符重载之成员函数

return by reference语法分析

传递者无需知道接受者是以reference形式接收。 语法分析

【注】return *ths; 接收端是complex&,不矛盾。

class body外的各种定义(definitions)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
inline double
imag(const complex& x)
{
    return x.imag ();
}

inline double
real(const complex& x)
{
    return x.real ();
}

使用:

1
2
3
4
5
6
{
    complex c1(2,1);

    cout << imag(c1);
    cout << real(c1);
}

operator overloading(操作符重载-2,非成员函数)(无this)

为了应对client的三种可能用法,这儿对应开发三个函数。 操作符重载之非成员函数

temp object(临时对象) typename();

上图complex这些函数绝不可return by reference,因为它们返回的必定是个local object。

三大函数:拷贝构造,拷贝复制,析构

  • Class without pointer member(s) complex
  • Class with pointer member(s) string

String class

string-test.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
int main()
{
    String s1(),
    String s2("hello");

    String s3(s1);
    cout << s3 << endl;
    s3 = s2;
    cout << s3 << endl;
}

string.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#ifndef __MYSTRING__
define __MYSTRING__

class String
{
    ...
};

String::function(...)   ...
Global-function(...)    ...

#endif

Big Three, 三个特殊函数

Big Three:拷贝构造、拷贝赋值、析构函数 【注】拷贝构造、拷贝赋值,在带有指针的情况下,不能用编译器自带的那一套,需要自己编写。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class String
{
public:
    String(const char* cstr = 0);
    String(const String& str);//拷贝构造
    String& operator=(const String& s);//操作符重载(拷贝赋值)
    ~String();//析构函数
    char* get_c_str() const { return m_data};//inline
private:
    char* m_data;
};

【注】类似于动态分配的方式,用指针指向字符串,而不要用数组。

ctor和dtor(构造函数和析构函数)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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';
    }
}

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

使用:

1
2
3
4
5
6
7
{
    String s1();
    String s2("hello");

    String* p = new String("hello");
    delete p;
}

【注】离开作用域(一对大括号)时,s1,s2自然而然调用析构函数,p手动调用析构函数。

class with pointer members 必须有 copy ctor 和 copy op=

copy ctor(拷贝构造) copy op=(拷贝赋值)

e.g. a有一个data,指向Hello\0 b有一个data,指向World\0

如果使用 default copy ctor 或 default op= 就会形成以下局面 b = a; 导致b的指针也指向Hello\0

而World\0造成memory leak(内存泄漏) 这种叫做浅拷贝

cpoy ctor(拷贝构造函数)

深拷贝

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

使用:

1
2
3
4
5
{
    String s1("hello");
    String s2(s1);
//  String s2 = s1;
}

copy assignment operator(拷贝赋值函数)

【注】类比:原来有一个装水和油的瓶子。现在要赋值,步骤:

  1. 倒掉油(杀掉自己)
  2. 将瓶子改造成水瓶一样大(重新创造自己)
  3. 将水从水瓶倒入新瓶(拷贝过来)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
inline String& String::operator=(const String&)
{
    if(this == &str)//检测自我赋值(self assignment)
        return *this;
    
    delete[] m_data;//杀掉自己
    m_data = new char[strlen(str.m_data) + 1];//重新创造自己
    strcpy(m_data, str.m_data);//拷贝过来
    return *this;  
}

使用:

1
2
3
4
5
{
    String s1("hello");
    String s2(s1);
    s2 = s1;
}
一定要在 operator= 中检查是否 self assignment

【注】这样做不仅是为了提高效率,不做还会影响正确性。

比如, this和rhs的指针指向同一片内存Hello\0 前述operator=的第一件事情就是delete,造成this和rhs指向??? 然后,当企图存取(访问)rhs,产生不确定行为(undefined behavior)

output函数

1
2
3
4
5
6
#include <iostream.h>
ostream& operator<<(ostream& os, const String& str)
{
    os << str.get_c_str();
    return os;
}

使用:

1
2
3
4
{
    String s1("hello");
    cout << s1;
}

堆,栈与内存管理

所谓stack(栈),所谓heap(堆)

Stack,是存在于某作用域(scope)的一块内存空间(memory space)。例如当你调用函数,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址。 在函数本体(function body)内声明的任何变量,其所使用的内存块都取自上述stack。

Heap,或谓system heap,是指由操作系统提供的一块global内存空间,程序可动态分配(dynamic allocated)从某中获得若干区块(blocks)。

1
2
3
4
5
6
class Complex{...};
...
{
    Complex c1(1, 2);
    Complex* p = new Complex(3);
}

c1所占用的空间来自stack Complex(3)是个临时对象,其所占用的空间乃是以new自heap动态分配而得,并由p指向。

stack objects 的生命期

1
2
3
4
5
class Complex{...};
...
{
    Complex c1(1, 2);
}

c1便是所谓stack object,其生命在作用域(scope)结束之后结束。 这种作用域内的object,又称为auto object,因为它会被“自动”清理。

stack local objects 的生命期

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

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

global objects 的生命期

1
2
3
4
5
6
7
8
class Complex{...};
...
Complex c3(1, 2);

int main()
{
    ...
}

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

heap objects 的生命期

1
2
3
4
5
6
7
class Complex{...};
...
{
    Complex* p = new Complex;
    ...
    delete p;
}

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

1
2
3
4
5
class Complex{...};
...
{
    Complex* p = new Complex;
}

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

new:先分配memory,再调用ctor

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

编译器转化为

1
2
3
4
5
Complex *pc;

void* mem = operator new(sizeof(Complex));//分配内存
pc = static_cast<Complex*>(mem);//转型
pc->Complex::Complex(1, 2);//构造函数

operator new是一个函数,其内部调用malloc(n) 构造函数的全名是

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

pc即隐藏的参数this。

delete: 先调用dtor,再释放memory

1
2
3
Complex* ps = new Complex(1, 2);
...
delete ps;

编译器转化为

1
2
Complex::~Complex(ps);//析构函数
operator delete(ps);//释放内存

delete函数内部调用free(ps)

析构函数先删除内容,delete删除指针

动态分配所得的内存块(memory block),in VC

Complex大小为8b,调试模式下会增加灰色的内存块(32+4),并且收尾各有一个cookie(4*2)(用于回收) vc每一块都是16的倍数,所以需要一些填补物(pad)

release下没有灰色部分

String大小为4b 动态分配所得的内存块

动态分配所得的array

vc用一个整数记录数组个数,所以+4 动态分配所得的array

array new一定要搭配array delete

array new一定要搭配array delete 【注】看清内存泄漏的地方。

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

static

静态函数和一般成员函数的区别:静态函数没有this pointer 静态函数只能处理静态数据

如设计银行户头的类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
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调用

把ctor放在private区

Singleton

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class A
{
public:
    static A& getInstance{return a;};//取得唯一的自己
    setup(){...}
private:
    A();//任何人不能创建它
    A(const A& rhs);
    static A a;//已经创建了一份
    ...
};

使用:

1
A::getInstance().setup();

如果不用a,但a仍然存在,为避免资源浪费,更好的写法是: Meyers Singleton

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class A
{
public:
    static A& getInstance();
    setup() {...}
private:
    A();
    A(const A& rhs);
    ...
};

A& A::getInstance()
{
    static A a;
    return a;
}

使用:

1
A::getInstance().setup();

cout

1
2
3
4
class _IO_ostream_withassign:public ostream{
    ...
};
extern _IO_ostream_withassign cout;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
class ostream:virtual public ios
{
public:
    ostream& operator<<(char c);
    ostream& operator<<(unsigned char c){return (*this)<<(char)c;}
    ostream& operator<<(signed char c){return (*this)<<(char)c;}
    ostream& operator<<(const char *s);
    ostream& operator<<(const unsigned char *s)
    {return (*this) << (const char*)s;}
    ostream& operator<<(const signed char *s)
    {reutrn (*this) << (const char*)s;}
    ostream& operator<<(const void *p);
    ostream& operator<<(int n);
    ostream& operator<<(unsigned int n);
    ostream& operator<<(long n);
    ostream& operator<<(unsigned long n);
    ...
};

class template,类模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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&);
};

使用:

1
2
3
4
5
{
    complex<double> c1(2.5, 1.5);
    complex<int> c2(2, 6);
    ...
}

function template, 函数模板

1
2
stone r1(2,3),r(3,3),r3;
r3 = min(r1, r2);

编译器会对function template进行引数推导(argument deduction)

1
2
3
4
5
template<class T>
inline const T& min(const T& a, const T& b)
{
    return b < a ? b : a;
}

引数推导的结果,T为stone,于是调用stone::operator<

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class stone
{
public:
    stone(int w, int h, int we):_w(w), _h(h), _weight(we)
    {}
    bool operator< (const strone& rhs) const
    {return _weight < rhs._weight;}
private:
    int _w, _h, _weight;
};

namespace

1
2
3
4
namespace std
{
    ...
}

using directive

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include<iostream.h>
using namespace std;

int main()
{
    cin << ...;
    cout << ...;

    return 0;
}

using declaration

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#include<iostream.h>
using std::cout;

int main()
{
    std::cin<<...;
    cout<<...;

    return 0;
}
1
2
3
4
5
6
7
8
9
#include<iostream.h>

int main()
{
    std::cin<<...;
    std::cout<<...;

    return 0;
}

更多细节与深入

  • operator type() const;
  • *explicit complex(…):initialization list{}
  • pointer-like object
  • funtion-like object
  • Namespace
  • template specialization
  • Standard Library
  • variadic template(since C++11)
  • move ctor(since C++11)
  • Rvalue reference(since C++11)
  • auto(since C++11)
  • lambda(since C++11)
  • range-base for loop(since C++11)
  • unordered containers(since C++ 11)

组合与继承

Object Oriented Programming, Object Oriented Design OOP, OOD

  • Inheritance(继承)
  • Composition(复合)
  • Delegation(委托)

Compostion(复合),表示has-a

Adapter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
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();}
};

从内存角度看

1
2
3
4
5
6
7
template <class T>
class queue
{
protected:
    deque<T> c;
...
};

Sizeof: 40

1
2
3
4
5
6
7
8
9
template <class T>
class deque
{
protected:
    Itr<T> start;
    Itr<T> finish;
    T** map;
    unsigned int map_size;
};

Sizeof: 16 * 2 + 4 + 4

1
2
3
4
5
6
7
8
9
template <class T>
struct Itr
{
    T* cur;
    T* first;
    T* last;
    T** node;
...
};

Sizeof: 4*4

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

复合关系下的构造和析构 构造由内而外 Container的构造函数首先调用Component的default构造函数,然后才执行自己。

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

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

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

Delegation(委托). Composition by reference.

Handle/Body(pImpl)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
//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
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//file String.cpp
#include "String.hpp"
namespace
{
class StringRep
{
friend class String;
    StringRep(const char* s);
    ~StringRep();
    int count;
    char* rep;
};
}

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

这种手法可称为编译防火墙 引用计数 n=3 共享同一个Hello,节省内存。 委托 图

Inheritance(继承), 表示is-a

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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;
};

继承 图

Inheritance(继承)关系下的构造和析构

内存上看

base class的dtor必须是virtual,否则会出现undefined behavior

构造由内而外 Derived的构造函数首先调用Base的default构造函数,然后才执行自己。

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

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

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

Inheritance(继承) with virtual functions(虚函数)

non-virtual函数:不希望derived class重新定义(override,复写)它。 virtual函数:希望derived class重新定义(override,复写)它,它已有默认定义。 pure virtual函数:希望derived class一定要重新定义(override)它,对它没有默认定义。

【注】:纯虚函数其实可以有定义,只是本文不提及。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Shape
{
public:
    virtual void draw() const = 0;//pure virtual
    virtual void error(const std::string& msg);//impure virtual
    int objectID() const;//non-virtual
    ...
};

class Rectangle:public Shape {...};
class Ellipse:public Shape {...};

Template Method 继承 虚函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

class CDocument
{
public:
    void OnFileOpen()
    {
        //这是个算法,每个cout输出代表一个实际动作
        cout << "dialog..." << endl;
        cout << "check file status..." << endl;
        cout << "open file..." << endl;
        Serialize();
        cout << "close file..." << endl;
        cout << "update all views..." << endl;
    }

    virtual void Serialize()  {};
};
1
2
3
4
5
6
7
8
9
class CMyDoc : public CDocument
{
public:
    virtual void Serialize()
    {
        //只有应用程序本身才知道如何读取自己的文件(格式)
        cout << "CMyDoc::Serialize()" << endl;
    }
};
1
2
3
4
5
int main()
{
    CMyDoc myDoc;//假设对应[File/open]
    myDoc.OnFileOpen();
}

Inheritance + Composition关系下的构造和析构

继承+复合

第一个问号:

第二个问号:构造函数调用顺序:Component, Base , Derived 析构函数则相反。

Delegation(委托) + Inheritance(继承)

Observer

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
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_views.size(); ++i)
            m_views[i]->update(this, m_value);
    }
};
1
2
3
4
5
class Observer
{
public:
    virtual void update(Subject* sub, int value) = 0;
};

委托+继承

Composite Composite

1
2
3
4
5
class Primitive:public Component
{
public:
    Primitive(int val):Component(val) {}
};
1
2
3
4
5
6
7
class Component
{
    int value;
public:
    Component(int val) { value = val; }
    virtual void add(Component*) {}
};
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Composite:public Component
{
    vector<Component*>c;
public:
    Composite(int val):Component(val) {}

    void add(Component* elem)
    {
        c.push_back(elem);
    }
...
};

**Prototype**
![Prototype](https://github.com/hubojing/BlogImages/blob/master/C++面向对象程序设计(侯捷)笔记——Prototype.png?raw=true)
出自Design Patterns Explained Simply
```cpp
#include<iostream.h>
enum imageType
{
    LSAT, SPOT
};
class Image
{
public:
    virtual void draw() = 0;
    static Image *findAndClone(imageType);
protected:
    virtual imageType returnType() = 0;
    virtual Image* clone() = 0;
    //As each subclass of Image is declared, it registers its prototype
    static void addPrototype(Image *image)
    {
        _prototypes[_nextSlot++] = image;
    }
private:
    //addPrototype() saves each registered prototype here
    static Image* _prototypes[10];
    static int _nextSlot;
};
Image *Image::prototypes[];//定义
int Image::_nextSlot;//定义
1
2
3
4
5
6
7
8
9
//Client calls this public static member function when it needs an instance 
Image *Image::findAndClone(imageType type)
{
    for(int i = 0; i < _nextSlot; i++)
    {
        if(_prototypes[i]->returnType())
        return _prototypes[i]->clone();
    }
}

子类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class LandSatImage:public Image
{
public:
    imageType returnType()
    {
        return LSAT;
    }
    void draw()
    {
        cout << "LandSatImage::draw" << _id << endl;
    }
    //When clone() is called, call the one-argument with a dummy arg
    Image *clone()
    {
        return new LandSatImage(1); 
    }
protected:
    //This is only called from clone()
    LandSatImage(int dummy)
    {
        _id = _count++;
    }
private:
    //Mechanism for initializing an Image subclass - this causes
    the default ctor to be called, which registers the subclass's prototype
    static LandSatImage _landSatImage;
    //This is only called when the private static data member is inited
    LandSatImage()
    {
        addPrototype(this);
    }
    //Nominal "state" per instance mechanism
    int _id;
    static int _count;
};
//Register the subclass's prototype
LandSatImage LandSatImage::_landSatImage;
//Initialize the "state" per instance mechanism
int LandSatImage::_count = 1;
Licensed under CC BY-NC-SA 4.0
最后更新于 0001-01-01 00:00 UTC
使用 Hugo 构建
主题 StackJimmy 设计