0%

侯捷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 之间的关系 (Object Oriented)
      • Inheritance 继承
      • Composition 复合
      • Delegation 委托
  • 历史

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

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

    • C++ Primer Fifth Edition
    • The C++ Programming Language Fourth Edition
    • Effective C++ Third Edition
    • The C++ Standard Library Second Edition
    • C++ 源码剖析

头文件与类的声明

  • C vs. C++
    • Data 与 Functions -> Data Members 与 Member Functions
    • 把数据与处理数据的函数包在一起
    • 一份函数,多份数据
1
2
3
4
/* complex 类 */
complex c1(2,1);
complex c2;
complex* pc = new complex(0,1);
1
2
3
4
/* string 类 */
string s1("Hello ");
string s2("World ");
string* ps = new string;

  • C++ programs 代码基本形式
    • .h(header files) Classes Declaration 声明
    • .cpp main()
    • .h(header files) Standard Library 标准库

avatar


  • Output, C++ vs. C
    • #include<iostream>
      • std::cout
    • #include<stdio.h>
      • printf
1
2
3
4
5
6
7
8
9
10
#include <iostream.h>
using namespace std;

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

return 0;
}
1
2
3
4
5
6
7
8
#include <stdio.h>
int main()
{
int i = 7;
printf("i=%d \n", i);

return 0;
}

  • Header(头文件)中的防卫式声明(guard)
    • 避免重复 include
1
2
3
4
5
6
7
// complex.h
#ifndef __COMPLEX__
#define __COMPLEX__

/* ... */

#endif

  • Header(头文件)的布局
    • 防卫式声明
    • forward declarations 前置声明
    • class declarations 类-声明
    • class definition 类-定义
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>

class ostream;
class complex;

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

/* 类-声明 */
class complex
{
...
};

/* 类-定义 */
complex::function ...

#endif

  • class 的声明
    • class head
    • class body
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& operator += (const complex&);
// 有些函数在此直接定义,另外一些在 body 之外定义
double real() const { return re; }
double imag() const { return im; }
private:
double re, im;

friend complex& __doapl(complex*, const complex&);
};
1
2
3
4
5
{
complex c1(2,1);
complex c2;
...
}

  • class template(模板)简介
    • 需求:刚刚上面的 complex 类,实部与虚部的类型不想写死
    • 将来用的时候可以指定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T>
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 { return im; }
private:
T re, im;

friend complex& __doapl(complex*, const complex&);
};
1
2
3
4
5
{
complex<double> c1(2,1);
complex<int> c2;
...
}

  • inline(内联)函数
    • inline 有宏的特性,但没有宏的缺点
    • 函数太复杂,即便声明成 inline,编译器也不把它 inline
    • inline 只是对编译器的建议
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& operator += (const complex&);
// 函数若在 class body 内定义完成,便自动成为 inline 候选人
double real() const { return re; }
double imag() const { return im; }
private:
double re, im;

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

  • access level 访问级别
    • public
    • private
    • protected
1
2
3
4
5
6
7
8
9
10
11
12
13
// 不被允许
{
complex c1(2, 1);
cout << c1.re;
cout << c1.im;
}

// 数据通过函数传递,而不是直接拿
{
complex c1(2, 1);
cout << c1.real();
cout << c1.imag();
}

构造函数

  • constructor(ctor,构造函数)
    • 使用构造函数
    • 构造函数名称与类的名称一定相同
    • 构造函数可以拥有参数,参数可以有默认值(default argument 默认实参非构造函数独有)
    • 没有返回类型
    • 拥有 initialize list(初值列,构造函数独有),注意其与 assignments 赋值的区别
1
2
3
4
5
6
7
8
// 注意初始化与赋值的时点
// 有些变量必须在初始化时有值,那么就必须用初值列表给它赋初值
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }

complex (double r = 0, double i = 0)
{ re = r; im = i; }
1
2
3
4
5
6
{
complex<double> c1(2,1);
complex<int> c2;
// 用动态的方式创建一个复数类的实例,得到的是一个指针
complex* p = new complex(4);
}

  • 构造函数的重载 overloading
    • 重载:相同函数名称却有一个以上
1
2
3
4
5
6
7
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
// 下面的不能与上面的同时存在
// 因为上面的 ctor 参数存在默认值,这样,两个函数就会产生冲突
// 编译器将不知道选择哪一个
complex () : re(0), im(0) { }
1
2
3
4
5
6
7
8
double real() const { return re; }
void real(double r) { re = r }

// 实际上这两个函数的名称也不相同
// real 函数编译后的实际名称可能是:
// ?real@Complex@@QBENXZ
// ?real@Complex@@QAENABN@Z
// 取决于编译器

  • ctor 被放在 private 区里
    • 不允许被外界创建对象
    • 经典:单例模式 Singleton
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Class A {
public:
static A& getInstance();
setup() { ... }
private:
A();
A(const A& rhs);
...
};

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

  • const member function(常量成员函数)
    • 在 ( ) 与 { } 中间用 const 修饰
    • 函数不改变数据内容
    • 设计接口时就想好,这个函数不允许改变数据内容,写函数定义时若进行了数据内容的修改,编译器就会报错
    • 该写时一定要写
1
2
3
4
5
6
7
8
9
double real() const { return re; }
...
{
// 定义对象为常量,那么如果 real() 函数中间没有被 const 修饰
// 下面这个函数调用在编译器就不被允许
const complex c1(2, 1);
cout << c1.real();
cout << c1.imag();
}

参数传递与返回值

  • 参数传递
    • pass by value vs. pass by reference(to const)
    • 参数传递最好传引用
    • 速度上相当于传指针
    • 引用传进来可以被改变,会影响外面,于是用 const 修饰
1
2
3
4
5
6
7
8
9
10
11
// 值传递
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
// 引用传递
complex& operator += (const complex&);
ostream&
operator << (ostream& os, const complex& x)
{
return os << '(' << real(x) << ',' << imag(x) << ')';
}

  • 返回值传递

    • return by value vs. return by reference(to const)
    • 返回尽量传引用,尽量而不是一定
  • friend(友元)

    • 自由取得 friend 的 private 成员
    • 相同 class 的各个 objects 互为 friends(友元)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
private:
double re, im;

friend complex& __doapl(complex*, const complex&);
...
inline complex&
__doapl (complex* ths, const complex& r)
{
// 打破了封装
ths->re += r.re;
ths->im += r.im;
return *ths;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
};

...

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

c2.func(c1);
}

  • class body 外的各种定义(definitions)
    • 什么情况下可以 pass by reference
    • 什么情况下可以 return by reference
      • 引用的本体是局部变量,它的作用域只在函数里,函数结束就无了
      • 这时候在返回时传它的引用,外面的调用者就会看到坏东西
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// do assignment plus
// 第一个参数将会被改动,第二个参数不会被改动
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
    • this 指针
    • 写代码时不要显式的写出来
1
2
3
4
5
inline complex&
complex::operator += (const complex& r)
// 其实实际上是下面这样有两个参数
// 但 this 无需写出(也不能写)
// complex::operator += (this, const complex& r)

  • return by reference 语法分析
    • 传递者无需知道接收者是以 reference 形式接收
1
2
3
4
5
6
7
// 返回类型:complex& 实际返回:*ths
inline complex&
__doapl (complex* ths, const complex& r)
{
...
return *ths;
}

  • operator overloading(操作符重载-2,非成员函数) 无 this
    • 为了对付 client 的三种可能用法,这儿对应开发三个函数
1
2
3
4
5
6
7
8
{
complex c1(2, 1);
complex c2;

c2 = c1 + c2;
c2 = c1 + 5;
c2 = 7 + c1;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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));
}

  • temp object(临时对象) typename();
    • 上面这三个函数绝不可 return by reference
    • 因为它们返回的必定是个 local object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
inline complex
operator + (const complex& x)
{
return x;
}

// nagate 反相(取反)
inline complex
operator - (const complex& x)
{
return complex (-real(x), -imag(x));
}
// 不可 return by reference

{
complex c1(2, 1);
complex c2;
cout << -c1;
cout << +c2;
}
  • operator overloading(操作符重载),非成员函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
inline bool
operator == (const complex& x, const complex& y)
{
return real(x) == real(y)
&& imag(x) == imag(y);
}

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

inline bool
operator == (double x, const complex& y)
{
return x == real(y) && imag(y) == 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 共轭复数
inline complex
conj (const complex& x)
{
return complex(real(x), -imag(x));
}

// 操作符重载,必须是非成员函数(全局函数)
#include <iostream>
ostream&
operator << (ostream& os, const complex& x)
{
return os << '(' << real(x) << ','
<< imag(x) << ')';
}

  • class without pointer members
    —— Complex
  • class with pointer members
    —— String
  • 上面的例子完了,看下面这个

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

  • 考虑实现以下功能
    • 字符串构造
    • 字符串拷贝
    • 字符串赋值
    • 用 << 向 cout 输出字符串对象
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;
}

  • Big Three,三个特殊函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;
// 让字符串里有这么一个指针
// 字符串里的数据有大有小,有时候是空字符串
// 不要在字符串里头放一个 char 数组,这个数组要设定多大呢?
// 所以我们要用一种动态分配的方式
};

  • 构造函数(ctor)与析构函数(dtor)
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
inline 
String::String(const char* cstr = 0)
{
if (cstr) {
m_data = new char[strlen(cstr) + 1]; // array new
strcpy(m_data, cstr);
}
else { // 未指定初值
m_data = new char[1];
*m_data = '\0';
}
}

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

...

{
String s1(),
String s2("hello");

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

  • class with pointer members 必须有 copy ctor 和 copy op=
    • a 赋值给 b,用 default copy ctor 或 default op= 并不会如我们所愿的有两个相同内容的对象
    • 而是看起来两个有相同内容,实际上是两个指针指向同一个地址(alias),是一种浅拷贝
    • 若修改 a,则 b 也会跟着受影响
    • 并且原来指向的 “world” 不再有任何一个指针能够访问到,成为了一个孤儿,memory leak 内存泄漏
    • 这就是为什么一定要写自己的一个版本

avatar


  • copy ctor(拷贝构造函数)
    • 那么深拷贝是什么样的呢?
    • 先分配空间,然后把内容拷贝过去,这就是深拷贝
    • 编译器的 default copy ctor 只拷贝指针,是浅拷贝
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
inline
String::String(const String& str)
{
// 直接取另一个 object 的 private data(兄弟之间互为 friend)
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}

...

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

  • copy assignment operator(拷贝赋值函数)
    • 拷贝赋值的步骤:清空、开足空间、复制内容
    • 一定要在 operator= 中检测是否 self assignment
      • 如果一开始左与右的 pointer 就是指的同一个 memory block
      • 前述 operator= 的第一件事情就是 delete,那么两个其实都没有了
      • 当企图存取(访问)右边 pointer 指向的对象时,将产生不确定行为(undefined behavior)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
inline
String& String::operator=(const String& str)
{
if (this == &str) // 检测自我赋值,看两个指针指的是不是同一块东西(地址)
return *this; // (self assignment)

// 三步走
// 1. 把左边清空
// 2. 在左边的对象中开足够的空间
// 3. 把右边的内容复制到左边去

delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcopy(m_data, str.m_data);
return *this;
}

  • 符号重载 <<
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}

...

{
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
7
8
9
class Complex { ... };
...
{
// c1 所占用的空间来自 stack
Complex c1(1, 2);
// Complex(3) 是个临时对象,其所占用的空间乃是以 new 自 heap 动态分配而得
// 并由 p 指向
Complex* p = new Complex(3);
}

  • stack objects 的生命期

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

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

    • c3 便是所谓 global object,其生命在整个程序结束之后才结束。其作用域是整个程序
1
2
3
4
5
6
7
8
9
10
11
class Complex { ... };
...
Complex c3(1, 2);

{
Complex c1(1, 2);
static Complex c2(1, 2);
}

int main()
{ ... }

  • heap objects 的生命期
    • p 所指的便是 heap object,其生命在它被 deleted 之际结束
    • 若没有 delete p,将会出现内存泄漏(memory leak)
      • 因为当作用域结束,p 所指的 heap object 仍然存在,但指针 p 的生命值却结束了
        作用域之外再也看不到 p(也就没机会 delete p)
1
2
3
4
5
6
7
class Complex { ... };
...
{
Complex* p = new Complex;
...
delete p;
}

  • new:先分配 memory,再调用 ctor
  • delete:先调用 dtor,再释放 memory
  • 注意正好相反
1
2
3
4
5
6
7
8
9
10
11
12
13
Complex* pc = new Complex(1, 2);
...
delete pc;

// 第一句被编译器转化为:
Complex *pc;
void* mem = operator new( sizeof(Complex) ); // 分配内存
pc = static_cast<Complex*>(mem); // 转型
pc->Complex::Complex(1,2); // 构造函数

// 最后一句被编译器转化为:
Complex::~Complex(pc); // 析构函数
operator delete(pc); // 释放内存

avatar

avatar


  • 注意,这里的析构函数并没有做什么事情,因为我们这个复数对象里面就两个 double 的变量,析构完了就要把它删掉了,也就不需要 dtor 做什么事情
  • 但是在 String 里,情况就发生了变化
    • 调用 dtor 要把 m_data 指向的它动态分配的内存里的东西删掉
    • 至于 String 对象本身,它里面只是一个指针而已,在 delete 这个对象的时候,只是删掉了这个指针

  • 上面介绍的分配内存和释放内存,实际上使用的是 C 语言的 malloc 和 free
    • 那么具体分配的情况是怎样?
    • 动态分配所得的内存块(memory block),in VC

avatar


  • 这是调试模式下(Debug)与构建模式下(Release)的区别(1 格为 4 个 Byte)
    注意:这里的 double 为 4 字节,pointer 为 4 字节,而在现在普遍的机器(x86_64)上两者皆为 8 字节
    • 调试模式
      • 在 Debug 模式下 new 一个 Complex
      • 光一个 Complex,里面只有两个 double,共 8 字节
      • 在调试模式下,会多给你灰色的区域,我们看到,上面 32 字节,下面 4 字节
      • 最上面和最下面红色区域的叫做 cookie,大小为 4 * 2 字节
      • 这就总共有了 52 字节,而在 VC 中,分配的内存块一定是 16 的倍数
      • 所以还会有 12 个字节的填充(绿色区域)
      • 虽然我们只要存两个 double 共 8 个字节,但是在调试模式下,一共分配了 64 个字节给我们,需要知道的是,这是一种必要的浪费
    • 构建模式
      • 在 Release 模式下 new 一个 Complex
      • 将灰色的区域先去掉(不需要 debugger header),重新来算
      • 总共 8 + 4 * 2 = 16 个字节,不需要进行填充
    • cookie 的作用
      • 记录分配的整块空间的大小
        在调试模式下,分配 64 字节,即 0x40,在构建模式下,分配 16 字节,即 0x10
        但与图中实际情况不符,图中为 0x41 与 0x10
      • 借最后一位 bit 来表示这块空间是分配出去了(1)还是已经收回来(0)
        • 为什么能借位,因为分配的空间一定是 16 的倍数,这意味着后 4 位都没有意义(正常都是 0)

  • 如果分配的是数组,那又是何种情况呢?
    • 动态分配的所得的 array

avatar

  • 可以看到,在数组这边有一点不同
    • 在 Debugger Header 与 data 中间,有一个空间用来记录数组的大小

  • array new 一定要搭配 array delete
    • 用 delete 代替 delete[],为对象分配的空间会正常回收
    • 但 delete 只调用 1 次析构函数,而原本的 delete[] 会调用多次析构函数
    • 每一次调用析构函数都应该会将数组其中一个元素它动态申请的内存空间回收
    • 用 delete 便只回收一次(回收了数组首个元素动态申请的内存空间)
    • 这样就造成了内存泄漏
    • 那么如果是 Complex 数组是不是就用 delete 也不会出错?
    • 是的,但不推荐
1
2
3
4
String* p = new String[3];
...
delete[] p; // 唤起 3 次 dtor
// delete p; // 唤起 1 次 dtor

String 类的实现过程

  • String 类的实现过程
    • 设计一个 class,先思考需要什么样的数据
      • 字符串会放很多的字符,一种想法是,用数组存储,但无法确定大小
      • 放一个指针,动态分配内存
      • 在 32 位的机器中,pointer 为 4 Bytes,在 64 位机器中为 8 Bytes
    • 接下来思考,要准备哪些函数,开放给外界调用
      • 首先就是(同名的)构造函数
      • 其次就是 Big Three:拷贝构造、拷贝复制、析构
      • 设计一个辅助函数 get_c_str(),用于将字符串送入 std::cout
      • 注意函数中的 const
      • 在实现时尽量作 inline 的建议
1
2
3
4
5
6
7
8
9
10
11
12
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;
};

  • 接下来看 ctor 与 dtor 如何进行设计
    • ctor
      • 需要足够的空间来放初值
      • 若未指定初值,应进行处理
      • 注意用 strlen 与 strcpy 需引入 cstring 库
    • dtor
      • 用 array delete
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;
}

  • 再看拷贝构造函数 copy ctor
    • 从来源端(参数)拷贝到目的端(该对象)
  • 拷贝赋值函数 copy assignment operator
    • 判断是否自我赋值(来源端与目的端相等)
    • 三步走:清空、开足空间、复制内容
    • 返回 this 指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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)
{
if (this == &str)
return *this;

delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}

进一步补充:static

  • static
    • static data members
      不管有几个对象,都只有一份数据
      一定在类外面进行定义
    • static member functions
      没有 this pointer,只能处理 static data members

avatar


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

  • 在单例模式中,很好的使用了 static 的特性
    • 用 static 在 private data members 中创建一个对象
      并且将类的构造函数放在 private 区,于是保证了始终只有一个对象
    • 再设计一个静态函数 getInstance,作为外界获得这个对象的唯一接口
1
2
3
4
5
6
7
8
9
10
11
12
class A {
public:
static A& getInstance() { return a; }
setup() { ... }
private:
A();
A(const A& rhs);
static A a;
...
};

A::getInstance().setup();

  • 进一步的,我们有 Meyers Singleton
    • 把 static 对象的创建放到 getInstance 函数中去
      只有使用时才会创建,不使用时不占用空间
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A {
public:
static A& getInstance();
setup() { ... }
private:
A();
A(const A& rhs);
...
};

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

A::getInstance().setup();

进一步补充:cout

  • cout 能够接收多种数据,并打印到终端
    • 继承自 ostream
    • 作了很多的操作符重载
1
2
3
4
5
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)
{ return (*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);
...
}

进一步补充:template

  • class template
    • 类型不能提前确定,那就不把它写死
    • T 仅仅是一个符号
    • 编译器会将 T 替换为使用者指定的类型,得到一份新的代码
    • 模板会造成代码的膨胀,但是必要的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T>
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 { 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
    • 编译器会对 function template 进行引数推导(实参推导)
    • 算法写成 function template 的形式
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
stone r1(2, 3, 4), r2(3, 3, 6), r3;
r3 = min(r1, r2);
// 需要设计一个求两者较小值的函数

template<class T>
inline
const T& min(const T& a, const T& b)
{
return b < a ? b : a;
}
// 不是说只有 stone 类的值调用的 min 函数这么写,
// 而是所有类型的 min 函数都是这个原理

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

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

进一步补充:namespace

  • namespace
    • 标准库的命名空间 std
    • using directive
    • using declaration
1
2
3
4
namespace std
{
...
}

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std; // using std::cout; // not say anything

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

return 0;
}

面向对象编程

  • Object Oriented Programming, Object Oriented Design (OOP, OOD)
    • Inheritance (继承)
    • Composition (复合)
    • Delegation (委托)