侯捷老师带我飞,再学习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(书目志)
- C++ Primer
- The C++ PROGRAMMING LANGUAGE
- Effective C++
- THE C++ STANDARD LIBRARY
- STL源码剖析
头文件与类的声明
C vs C++,关于数据和函数
C++,关于数据和函数
1 | complex c1(2,1); |
【注】数据可以有很多份,函数只有一份。
1 | string s1("Hello"); |
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
using namespace std;
int main()
{
int i = 7;
cout<< "i=" << i << endl;
return 0;
}
C1
2
3
4
5
6
7
8
9include <stdio.h>
int main()
{
int i = 7;
printf("i=%d \n", i);
return 0;
}
Header(头文件)中的防卫式声明
guard(防卫式声明)
complex.h1
2
3
4
5
6
...
【注】如果xxx已经定义过了,则不进入下方的定义。
complex-test.h
1 |
|
Header(头文件)的布局
1 |
|
class的声明(declaration)
1 | class complex//class head |
使用:1
2
3
4
5{
complex c1(2,1);
complex c2;
...
}
class template(模板)简介
1 | template<typename T> |
使用:1
2
3
4
5{
complex<double> c1(2.5,1.5);
complex<int> c2(2,6);
...
}
构造函数
inline(内联)函数
1 | class complex |
使用:1
2
3
4
5inline double
imag(const complex& x)
{
return x.imag ();
}
【注】函数太复杂,就不能inline。
access level(访问级别)
1 | class 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 | class complex |
【注】其他函数也可以有默认值。构造函数没有返回值类型。initialization list(初值列,初始列)上下三行等价于1
2
3
4complex (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 | class complex |
1 | void real(double r) |
real函数编译后的实际名称可能是:
?real@Complex@@QBENXZ
?real@Complex@@QAENABN@Z
取决于编译器。
【注】重载表面名字相同,其实在编译器内名字不同。
构造函数重载:1
2
3
4{
Complex c1;
Complex c2();//写法不同,意思相同
}
所以?!处的构造函数不能这样重载。
参数传递与返回值
constructor(ctor,构造函数)被放在private区
以下无法使用:1
2complex c1(2,1);
complex c2;
那么是不是说ctor不应该放在private区呢?也不是。
Singleton(单例)设计模式:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class 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 | class 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 | class complex |
没有const:1
2
3
4
5
6ostream&
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 | class complex |
friend(友元)
1 | class complex |
使用:1
2
3
4
5
6
7inline complex&
__doapl (complex* ths, const complex& r)
{
this->re += r.re;
this->im += r.im;//自由取得friend的private成员
return *ths;
}
【注】friend打破封装。
相同class的各个objects互为friends(友元)
1 | class complex |
使用: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
13inline 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 | inline double |
使用: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.cpp1
2
3
4
5
6
7
8
9
10int main()
{
String s1(),
String s2("hello");
String s3(s1);
cout << s3 << endl;
s3 = s2;
cout << s3 << endl;
}
string.h1
2
3
4
5
6
7
8
9
10
11
12
define __MYSTRING__
class String
{
...
};
String::function(...) ...
Global-function(...) ...
Big Three, 三个特殊函数
Big Three:拷贝构造、拷贝赋值、析构函数
【注】拷贝构造、拷贝赋值,在带有指针的情况下,不能用编译器自带的那一套,需要自己编写。
1 | class String |
【注】类似于动态分配的方式,用指针指向字符串,而不要用数组。
ctor和dtor(构造函数和析构函数)
1 | inline String::String(const char* cstr = 0) |
使用: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
5inline 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 | inline String& String::operator=(const String&) |
使用: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
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
6class Complex{...};
...
{
Complex c1(1, 2);
Complex* p = new Complex(3);
}
c1所占用的空间来自stack
Complex(3)是个临时对象,其所占用的空间乃是以new自heap动态分配而得,并由p指向。
stack objects 的生命期
1 | class Complex{...}; |
c1便是所谓stack object,其生命在作用域(scope)结束之后结束。
这种作用域内的object,又称为auto object,因为它会被“自动”清理。
stack local objects 的生命期
1 | class Complex{...}; |
c2便是所谓的static object,其生命在作用域(scope)结束之后仍然存在,直到整个程序结束。
global objects 的生命期
1 | class Complex{...}; |
c3便是所谓global object,其生命在整个程序结束之后才结束。也可以把它视为一种static object,其作用域是“整个程序”。
heap objects 的生命期
1 | class Complex{...}; |
p所指的便是heap object,其生命在它被deleted之后结束。
1 | class Complex{...}; |
以上为内存泄漏(memory leak),因为当作用域结束,p所指的heap object仍然存在,但指针p的生命却结束了,作用域之外再也看不到p(也就没机会delete p)。
new:先分配memory,再调用ctor
1 | Complex* pc = new Complex(1, 2); |
编译器转化为1
2
3
4
5Complex *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 | Complex* ps = new Complex(1, 2); |
编译器转化为1
2Complex::~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 new一定要搭配array delete
【注】看清内存泄漏的地方。
扩展补充:类模板,函数模板及其他
static
静态函数和一般成员函数的区别:静态函数没有this pointer
静态函数只能处理静态数据
如设计银行户头的类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class 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区
Singleton1
2
3
4
5
6
7
8
9
10
11class A
{
public:
static A& getInstance{return a;};//取得唯一的自己
setup(){...}
private:
A();//任何人不能创建它
A(const A& rhs);
static A a;//已经创建了一份
...
};
使用:1
A::getInstance().setup();
如果不用a,但a仍然存在,为避免资源浪费,更好的写法是:
Meyers Singleton1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16class 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 | class _IO_ostream_withassign:public ostream{ |
1 | class ostream:virtual public ios |
class template,类模板
1 | template<typename T> |
使用:1
2
3
4
5{
complex<double> c1(2.5, 1.5);
complex<int> c2(2, 6);
...
}
function template, 函数模板
1 | stone r1(2,3),r(3,3),r3; |
编译器会对function template进行引数推导(argument deduction)1
2
3
4
5template<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
10class 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 | namespace std |
using directive1
2
3
4
5
6
7
8
9
10
using namespace std;
int main()
{
cin << ...;
cout << ...;
return 0;
}
using declaration1
2
3
4
5
6
7
8
9
10
using std::cout;
int main()
{
std::cin<<...;
cout<<...;
return 0;
}
1 |
|
更多细节与深入
- 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
Adapter1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16template <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
7template <class T>
class queue
{
protected:
deque<T> c;
...
};
Sizeof: 40
1 | template <class T> |
Sizeof: 16 * 2 + 4 + 4
1 | template <class T> |
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
namespace
{
class StringRep
{
friend class String;
StringRep(const char* s);
~StringRep();
int count;
char* rep;
};
}
String::String(){...}
...
这种手法可称为编译防火墙
n=3
共享同一个Hello,节省内存。
Inheritance(继承), 表示is-a
1 | struct _List_node_base |
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 | class Shape |
Template Method
" class="lazyload" data-srcset="" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" class="lazyload" data-srcset="" srcset="data:image/gif;base64,R0lGODlhAQABAIAAAP///////yH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
1 |
|
1 | class CMyDoc : public CDocument |
1 | int main() |
Inheritance + Composition关系下的构造和析构
第一个问号:
第二个问号:构造函数调用顺序:Component, Base , Derived
析构函数则相反。
Delegation(委托) + Inheritance(继承)
Observer1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class 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 | class Observer |
Composite
1 | class Primitive:public Component |
1 | class Component |
1 | class Composite:public Component |
1 | //Client calls this public static member function when it needs an instance |
子类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
39class 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;