C++ 面向對象程序設計--侯捷
C++ 面向對象程序設計-part11.Header(头文件)的防卫式声明2.Header(头文件)的布局2.inline(内联)函数3.access level(访问级别)4.constructo(ctor,构造函数)5.const member functions(常量成员函数)6.参数传递:pass by value VS pass by reference7.返回值传递:return by value VS return by reference8.friend(友元)9.操作符重载10.三大函数11.heap(堆)与stack(栈)12.object的生命周期13.new和delete14.在OOP的三种关系C++ 面向對象程序設計-part21.conversion function,转换函数2.non-explicit-one-argument ctor,非显式单参数构造函数3.conversion function VS non-explicit-one-argument ctor4.explicit-one-argument ctor,显式的单参数构造函数5.pointer-like-classes,像指针的类,如智能指针,迭代器6.function-like classes,仿函数7.namespace经验谈8.class template,类模板或模板类9.function template,函数模板10.member template,成员模板11.specialization,模板特化12.partial specialization,模板偏特化--个数的偏13.partial specialization,模板偏特化--范围的偏14.template template parameter,模板模板参数15.variadic template(since C++11)16.reference,引用17.Composition(复合)关系下的构造和析构18.Inheritance(继承)关系下的构造和析构19.Composition+Inheritance(继承)关系下的构造和析构20.对象模型:关于vptr和vtbl21.对象模型:关于this指针21.对象模型:关于Dynamic Binding(动态绑定)22.关于const23.关于new和delete(未完成)
1.Header(头文件)的防卫式声明
当我们在使用自己定义的头文件时,可能会多次包含头文件,这样会使得一段代码会重复出现。加上上图的防卫式声明,可以避免头文件被重复包含。
2.Header(头文件)的布局
2.inline(内联)函数
在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为
3.access level(访问级别)
-
public
-
protected
-
private
4.constructo(ctor,构造函数)
当我们new一个对象时,需要调用该对象的构造函数。
5.const member functions(常量成员函数)
我们应该养成一种习惯:将所有不会修改对象状态的成员函数都声明为const。
6.参数传递:pass by value VS pass by reference
C++中传递参数有三种方式:
pass by value
pass by reference(to const)
pass by pointer
(传引用其实就是相当于传指针。)当参数的大小较大时,传引用会比传值会快。我们尽量选择pass by reference。
7.返回值传递:return by value VS return by reference
C++中返回值有三种方式:
return by value
return by reference(to const)
return by pointer
我们尽量选择return by reference。
情形:
当类似于+=操作符,应该选择return by reference。(需要将结果放到已经存在的对象中)
当类似于+操作符,应该选择return by value。(不需要将结果放到已经存在的对象中,只需返回一个值)
8.friend(友元)
将某个函数或者成员变量声明为friend,这些称为友元,可以直接获取对应class的private数据。
注意:同一个class的各个objects互为友元。
9.操作符重载
操作符重载-成员函数
特点:
-
应该return by reference
-
函数的一个隐藏参数:this指针
适用操作符:例如+=,++
-
绝大部分的单目运算符
-
部分双目运算符:需要(可以)连串赋值。如:c1 += c2 += c3。
-
操作符的左侧为object
操作符重载-非成员函数
特点:
-
绝对是return by value。返回的是临时对象,return typename();
-
操作符左侧的操作数可能是非对象
适用操作符:例如+,==,!=,(输入输出:>>和<<)
-
个别单目运算符:+和-(正负)。
-
部分双目运算符
10.三大函数
-
copy ctor(拷贝构造函数)
-
copy assignment operator(拷贝赋值函数)
-
析构函数
11.heap(堆)与stack(栈)
-
stack
Stack,是存在於某作用域 (scope) 的一塊內存空間 (memory space)。例如當你調用函數,函數本身即 會形成一個 stack 用來放置它所接收的參數,以及返 回地址。 在函數本體 (function body) 內聲明的任何變量, 其所使用的內存塊都取自上述 stack。
-
heap
Heap,或謂 system heap,是指由操作系統提供的 一塊 global 內存空間,程序可動態分配 (dynamic allocated) 從某中獲得若干區塊 (blocks)。
12.object的生命周期
-
stack objects的生命周期
其生命在作用域 (scope) 結束之際結束。 這種作用域內的 object,又稱為 auto object,因為它會被「自動」清理。
-
static local objects的生命周期
其生命在作用域 (scope) 結束之後仍然存在,直到整個程序結束。
-
global objects的生命周期
其生命在整個程序結束之後 才結束。你也可以把它視為一種 static object,其作用域 是「整個程序」。
-
heap objects的生命周期
其生命 在它被 deleted 之際結束。
13.new和delete
-
new的操作:先分配memory,再调用ctor
-
delete的操作:先调用dtor,再释放memory
注意:array new一定要搭配array delete。
14.在OOP的三种关系
-
Composition(复合),表示has-a
-
Delegation(委托).Composition by reference
-
Inheritance(继承),表示is-a
C++ 面向對象程序設計-part2
1.conversion function,转换函数
转换函数:一个class类里的成员函数,但是其函数名为某个类型的名称typename,且为const函数。例如:在一个分数类(Fraction)中有一个转换函数double,将分数转换为double。代码如下:
class Fraction
{
public:
Fraction(int num, int den = 1) :
m_numerator(num), m_denominator(den) {}
// 转换函数
operator double() const {
return (double)(m_numerator / m_denominator);
}
private:
int m_numerator; // 分子
int m_denominator; // 分母
};
2.non-explicit-one-argument ctor,非显式单参数构造函数
Fraction类的构造函数就是这个类型的:只需要一个参数即可构造对象。
class Fraction
{
public:
Fraction(int num, int den = 1) :
m_numerator(num), m_denominator(den) {}
Fraction operator+(const Fraction& f) const {
// 随便写的
return Fraction(123, 12312);
}
private:
int m_numerator; // 分子
int m_denominator; // 分母
};
Fraction f(3, 5);
Fraction d2=f+4;
当一个Fraction与int相加,编译器会调用non-explicit ctor将4转换为Fraction,然后调用operator +。
3.conversion function VS non-explicit-one-argument ctor
当Fraction类同时拥有conversion function 和 non-explicit-one-argument ctor,执行如下代码会报错:
test.cpp:6:18: error: ambiguous overload for ‘operator+’ (operand types are ‘Fraction’ and ‘int’) Fraction d2=f+4; ^ test.cpp:6:18: note: candidates are: test.cpp:6:18: note: operator+(double, int) <built-in> In file included from test.cpp:1:0: Fraction.h:12:18: note: Fraction Fraction::operator+(const Fraction&) const Fraction operator+(const Fraction& f) const {
Fraction f(3, 5);
Fraction d2=f+4;
编译器报错:出现二义性ambiguous。有两种解释途径:
将4通过调用non-explicit-one-argument ctor转换为Fraction,然后调用operator +。
将f通过调用conversion function转换为double,然后与4相加还是doule,这个double传入non-explicit-one-argument ctor转化为Fraction。
4.explicit-one-argument ctor,显式的单参数构造函数
当我们在构造函数前加上explicit关键字后,编译程序报错:
test.cpp: In function ‘int main()’: test.cpp:6:18: error: conversion from ‘double’ to non-scalar type ‘Fraction’ requested Fraction d2=f+4;
要使得程序正确编译,需要将
Fraction d2=f+4;
改为:
Fraction d2=f+(Fraction)4;//显式调用
5.pointer-like-classes,像指针的类,如智能指针,迭代器
像智能指针这样的类指针类,一般有一个真正的指针,其中要关注pointer-like-classes的operator * 和 operator ->的实现,下面是一个例子:
template<class T>
class shared_ptr
{
public:
T& operator*() const {
return *px;
}
T* operator->() const {
}
shared_ptr(T* p) : px(p) {}
private:
T* px;
long* pn;
};
6.function-like classes,仿函数
顾名思义,就是一些关于作用类似于函数的类。这个类主要是重载operator ()。例如:
template<class T>
struct plus {
T operator() (const T& x, const T& y) const {
return x + y;
}
};
7.namespace经验谈
namespace,称为命名空间。其作用就是为了防止重名的一个。在写大型程序时,一定要使用namespace隔离。例子:
template<class T>
namespace jj01
{
void test();
}
namespace jj02
{
void test();
}
8.class template,类模板或模板类
通过在class的定义上方加上template,传入若干个模板参数,在类中引用这些模板参数。减少类的重复定义。下面是一个简单的例子:
template<class T>
class complex
{
public:
complex(T r = 0, T i = 0) :
re(r), im(i) {}
T real() const {return re;}
T imag() const {return im;}
private:
T re, im;
}
9.function template,函数模板
在函数定义的上方加入template<class T, ...>。使得函数的参数类型可以灵活变动,减少函数由于参数类型不同二重复定义大量作用类似的函数。
template<class T>
inline const T&(T& a, T& b) {
return a > b ? b : a;
}
10.member template,成员模板
在类或模板类中,其某些成员也是带有模板的,这些成员称为成员模板或模板成员。
11.specialization,模板特化
这里模板特化指的是,将template的模板参数具体为某种类型。例如:
template<class T>
struct hash {};
template<>
struct hash<char> {
size_t operator() (char x) const {return x;}
};
template<>
struct hash<int> {
size_t operator() (int x) const {return x;}
};
调用例子:
cout << hash<int>()(1000) << endl;
12.partial specialization,模板偏特化--个数的偏
当一个模板类有多个模板参数,我们可以选择将位于前面的参数先指定特定类型,再将后面的模板参数继续传下去。例如:
template<class T, class Alloc=...>
class vector
{
...
};
// 注意:template的参数
template<class Alloc=...>
class vector<bool, Alloc>
{
...
};
13.partial specialization,模板偏特化--范围的偏
当一个模板类的模板参数是一个class T,范围很大,传入任何参数都可以。此时可以将T的范围缩小,这就是模板特化--范围的偏。例如:
template<class T>
class C
{
...
};
// 这里的T可以换成其他的符号,无关紧要
template<class T>
class C<T*>
{
...
};
14.template template parameter,模板模板参数
模板参数中有参数又是模板,称之为模板模板参数。例如:
template<class T, template<class T> class Container>
class XCls
{
private:
Container<T> c;
public:
...
};
15.variadic template(since C++11)
标题是数量不定的模板参数。例子:下面的第一个print函数一定要写,否则会报错,因为传到最后会没有参数传入。
void print() {
}
template<typename T, typename... Types>
void print(const T& firstArg, const Types&... args) {
cout << firstArg << endl;
print(args...);
}
print(7.5, "hello", 42);
16.reference,引用
其实,reference就是通过指针实现的。只不过,我们需要当reference当作别名使用。两个需要注意的点:
引用的大小与被引用的对象的大小一致
引用的地址与被引用的对象的地址一致
例子:
double x = 0;
double* p = &x;
double& r = x;
cout << sizeof(x) << endl;//8
cout << sizeof(p) << endl;//8
cout << sizeof(r) << endl;//8
cout << p << endl;//0x7ffc3890e088
cout << *p << endl;//0
cout << r << endl;//0
cout << &x << endl;//0x7ffc3890e088
cout<< &r << endl;//0x7ffc3890e088
17.Composition(复合)关系下的构造和析构
首先解释一下复合关系:当类A有一个成员的类型是类B,此时称类A和类B是复合关系。
调用构造函数的次序:当构造一个类A的对象时,首先会调用类B的构造函数,再执行类A的构造函数。
而调用析构函数的次序则是相反的:当销毁一个类A的对象时,会先调用类A的析构函数,再调用类B的析构函数。
记忆技巧:
构造就是穿衣服,需要从里到外穿;
而析构就是脱衣服,需要从外到里脱。
18.Inheritance(继承)关系下的构造和析构
调用构造函数的次序:在构造一个派生类对象时会先调用基类的构造函数,然后再调用派生类的构造函数。
调用析构函数的次序:在销毁一个派生类对象时,会先调用派生类的析构函数,然后再调用基类的析构函数。
19.Composition+Inheritance(继承)关系下的构造和析构
以下称基类为Base,派生类为Dervied,派生类所含的类成员为Component。
调用构造函数的次序:Derived的构造函数会先调用Base的构造函数,然后再调用Component的构造函数。
调用析构函数的次序:Dervied的析构函数会先调用自己的析构函数,再调用Component的析构函数,最后调用Base的析构函数。
20.对象模型:关于vptr和vtbl
vtbl:虚表。用于存放一个class的virtual function的函数指针
vptr:指向虚表的指针。所有拥有virtual function的class都有一个vptr。
关于对象模型,当一个class同时拥有static function、non-static function、virtual function时,前两者不是直接与class关联,而是放在class之外的。而后者是通过class的vptr指向vtbl找到的。
当派生类Derived继承基类Base时,派生类会继承基类Base的数据成员。(以public继承,public数据成员为例)
21.对象模型:关于this指针
当一个class拥有member function时,这些member function在代码层面是属于这个class的,但是在编译器层面,会将这些member function转换为global function,并增加一个指向该class的指针。例如:
class A
{
public:
void print() {
cout << data << endl;
}
private:
int data;
}
以上的成员函数print函数会转换为全局函数,如下(大致如下):
void print_A_nsf(A* this) {
cout << this->data << endl;
}
注意:任何带有static关键字的函数都不会有this指针。
21.对象模型:关于Dynamic Binding(动态绑定)
绑定什么?绑定虚函数。
而绑定又分为动态和静态:
静态绑定:当使用对象变量(非指针、非引用)调用某个虚函数时,此时调用的虚函数版本必定是该变量类型的虚函数。
动态绑定:只会出现在使用某个对象的指针或者引用调用虚函数才会出现。此时调用的虚函数版本有两种情况,如下:
22.关于const
const member functions(常量成员函数):指函数具有const属性,不会改变对象的状态。
如一个class 有一个成员函数定义如下:
double real() const {return re;}上面这个函数就是const member functions(常量成员函数)。
-
关于const是否属于函数签名的一部分?属于。
const function与const object:
CONST OBJECT NON-CONST OBJECT const member function 可以调用 可以调用 non-const member function 不可以调用 可以调用 注意:当一个member function的const版本和non-const版本同时存在,C++标准有一条规则:const object只会(只能)调用const 版本,non-const object只会(只能)调用non-const版本。

浙公网安备 33010602011771号