《C++面向对象高级开发》下篇

1.导读

万丈高楼平地起,勿在浮沙筑高台

我们的目标

  • 在先前基础课程所培养的正规、大器的编程素养上,继续探讨更多技术。
  • 泛型编程和面向对象编程虽然分为不同思维,但他们正是C++的技术主流,所以本课程也讨论template(模板)。
  • 深入探索面向对象之后继承关系所形成的对象模型,包括隐藏在底层的this指针,vptr(虚指针),vtbl(虚表),virtual mechanism(虚机制),以及虚函数(virtual functions)造成的polymorphism(多态)效果。

2.conversion function,转换函数

注意:转换类型不一定是需要基本类型,只要是定义过或者出现过的类型就可以转换。

class Fraction
{
public:
	Fraction(int num,int den=1)
	: m_numerator(num),m_denominator(den){ }

	operator double() const {//将Fraction转换为double,转换没有参数(返回类型转换函数可以不写返回类型)
		return (double) (m_numerator/m_denominator);//转换函数通常是const函数,该加就要加

	}
private:
	int m_numerator;	//分子
	int m_denominator;	//分母
};

Fraction f(3,5);
double d = 4 + f;	//调用operator double() 将f 转换为0.6()调用的时候 

3.non-explicit-one-argument ctor

one-argument:指的是只需要一个实参(另一个有默认值),当然也可以给两个。

class Fraction
{
public:
	Fraction(int num,int den=1)
	: m_numerator(num),m_denominator(den){ }

	Fraction operator+(const Fraction& f){
		return Fraction(...);

	}
private:
	int m_numerator;	//分子
	int m_denominator;	//分母
};
Fraction f(3,5);
double d = f + 4;	//调用non-explicit ctor将4转为Fraction,然后调用operator+

二者共存

class Fraction
{
public:
	Fraction(int num,int den=1)
	: m_numerator(num),m_denominator(den){ }

	operator double() const {
		return (double) (m_numerator/m_denominator);
	Fraction operator+(const Fraction& f){
		return Fraction(...);

	}
private:
	int m_numerator;	//分子
	int m_denominator;	//分母
};
Fraction f(3,5);
Fraction d = f + 4;	//【Error】 ambiguous(编译器不知道走哪条路)

explicit

关键字explicit只有在构造函数的地方使用的到,但是在模板中也有运用(很少)。用于防止构造函数自动转换。

class Fraction
{
public:
	explicit Fraction(int num,int den=1)
	: m_numerator(num),m_denominator(den){ }

	operator double() const {
		return (double) (m_numerator/m_denominator);
	Fraction operator+(const Fraction& f){
		return Fraction(...);

	}
private:
	int m_numerator;	//分子
	int m_denominator;	//分母
};
Fraction f(3,5);
Fraction d = f + 4;	//【Error】conversion from 'double' to 'Fraction' request.

示例

标准库中的相关代码:

template<class Alloc>
class vector<bool, Alloc>
{
public:
	typedef __bit_reference reference;
protected:
	reference operator[](size_type n){
		return *(begin() + difference_type(n));
	}
...

struct __bit_reference{
	unsigned int* p;
	unsigned int mask;
	...
}
public:
	operator bool() const {return !(!(*p & mask)); }
...

4.pointer-like classes

关于智能指针

在这里插入图片描述

template<class T>
class share_ptr
{
public:
	T& operator*() const
	{ return *px; }
	T* operator->() const
	{ return px; }
	share_ptr(T* p): px(p) { }
private:
	T* px;
	long* pn;
...
};

struct Foo
{
	...
	void method(void) { } 
};

share_ptr<Foo> sp(new);
Foo f(*sp);

sp->method();(相当于px->method();)

关于迭代器

template<class T,class Ref,class Ptr>
struct __list_iterator{
	typedef __list_iterator<T,Ref,Ptr> self;
	typedef Ptr pointer;
	typedef Ref reference;
	//
	typedef __list_node<T>* link_type;
	link_type node;
	//
	bool operator == (const self& x) const { return node == x.node; }
	bool operaor != (const self& x) const { return node != x.node; }
	///
	reference operator*() const { return (*node).data; }
	pointer operator->() const { return &(operator*()); }
	///
	self& operator++() {node = (link_type) ((*node).next); return *this; }
	self operator++(int) {self tmp = *this; ++*this; return tmp; }
	self& operator--() {node = (link_type) ((*node).prev; return *this;  )}
	self operator--(int) {self tmp = *this; --*this; return tmp; }
}

在这里插入图片描述

	///
	reference operator*() const { return (*node).data; }
	pointer operator->() const { return &(operator*()); }
	///
list<Foo>::iterator iter
...
*ite;//获得一个Foo object()
ite->method();
//意思是调用Foo::method();
//相当于(*ite).method();
//相当于(&(*ite))->method();

5.function-like classes,所谓仿函数

template<class T>
struct identity {
	const T&;
	operator() (const T& x) const { return x; }
};
template <class Pair>
struct selectlst {
	const typename Pair::first_type & 
	operator() (const Pair& x) const
	{ return x.first; }
};
template <class Pair>
struct select2nd {
	const typename Pair::second_type&
	operator() (const Pair& x) const
	{ return x.second; }
};

template <class T1,class T2>
struct pair{
	T1 first;
	T2 second;
	pair(): first();
	pair(const T1& a,const T1& b):
	first(a),second(b){ }
....
};

结构体继承类,标准库里面有很多仿函数。

6.namespace经验

把代码块包起来,访问命名冲突。

namespace 命名
{

}

7.class template,类模板

template<typename T>
class complex
{
public:
	complex (T r = 0,T i = 0)
	: re(r),im(i) { }
	complex& operator += (const complex&);
	T readl() const { return re; }
	T imag() const { return im; }
private:
	T re,im;

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

{
	complex<double> x1(2.5,1.5);
	complex<int> c2(2,6);
	...
}

8.Function Template,函数模板

编译器会对function template进行实数推导


stone r1(2,3),r2(3,3),r3;
r3 = min(r1,r2);
//使用下面的函数模板进行比较
template <class T>
inline const T& min(const T& a,const T& b)
{
	return b < a ? b : a;
}
//使用下面的运算符重载进行比较,调用stone::operator<
class stone
{
public:
	stone(int w,int h,int we)
	: _w(w),_h(h),_weight(we) { }
	bool operaotr< (const stone& rhs) const
	{ return _weight < rhs._weight; }
private:
	int _w,_h,_weight;
}'

9.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()) { }
	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){ }

};

在这里插入图片描述

class base1 { };
class Derived1:public Base1 { };

class Base2 { };
class Derived2:public Base2 { };

pair<Derived1,Derived2> p;
pair<Base1,Base2> p2(p);
= 
pair<Base1,Base2> p2(pair<Derived1,Derived2>());

把一个由鲫鱼和麻雀组成的pair,放进(拷贝到)一个由鱼类和鸟类组成的pair中,可以吗?(可以,子类继承了父类)
反之可以吗?(不可以,父类不能调用子类)
在这里插入图片描述

template <typename _Tp>
class shared_ptr : public __shared_ptr<_Tp>
{
...
	template<typename _Tp1>
	explicit shared _ptr(_Tp1* __p)
		: __shared_ptr<_Tp>(__p){ }//智能指针为了实现这个功能这样写的,具体不太理解(打个记号)
...
};

Base1 * ptr = nde Derived();	//up-cast(这种写法可以,被称为指针上移)

shared_ptr<Base1> sptr(new Derived1);	//模拟up-cast

10.specialization,模板特化

tempalte <class Key> //泛化
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; }
};

template<>
struct hash<long>{
	size_t operator() (long x) const { return x; }
};

//调用
cout<<hash<long>() (1000);

11.partical sepcialization,模板偏特化

个数上的偏

将参数某个类型定下来的情况称为模板个数上的偏特化,必须要从左到右边进行偏特化

template<typename T,typename Alloc=......>
class vector
{
...
};
template<typename Alloc=......>
class vector<bool,Alloc>
{
...
}

范围上的偏

template <typename T>
class C
{
	...
};
//范围上的偏,指明类型
template<typename T>
class C<T*>
{
	...
};
//这样也可以
template <typename U>
class C<U*>
{
	...
};
//调用
C<string> obj1;
C<string*> obj2;

12.template template parameter,模板模板参数

template<typename T,
		template <typename T>
		class Container
	>
class XCls
{
private:
	
}

13.关于C++标准库

统统用一遍,统统测试一遍。
在这里插入图片描述
注意:在编译器中输出__cplusplus查看编译器支持的C++的版本。

14.三个主题

取出C++11中的三个主题进行描述。

variadit templates(since C++11),数量不定的模板参数

void print()//注意这个需要存在,当参数只有0个的时候会被调用
{

}

template<typename T,typename... Types>
void print(const T& firstArg,const Types&... args)
{
	cout<<firstArg<<endl;
	print(args...);
}

注意:此处代码中的...是函数参数。

//递归调用
print(7.5,"hello",bitset<16>(377),42);
//输出
7.5
hello
0000000101111001
42

如果想知道后面的一包是多少,可以使用sizeof...(args)来判断。

auto(since C++11)

语法糖,自动推测变量类型,在声明auto的时候必须赋值使得可以让编译器推测出类型。

list<string> c;
...
list<string>::iterator ite;
ite = find(c.begin(),c.end(),target);
//也可以使用下面的方法
list<string> c;
...
auto ite = find(c.begin(),c.end(),target);
//错误用法
list<string> c;
...
auto ite;(×,此处编译器无法推测)
ite =  find(c.begin(),c.end(),target);

ranged-base for(since C++11)

语法糖,方便快捷。

for(decl : coll){
	statement
}
for(int i : {2,3,5,7,9,13,17,19} ){//注意C++可以直接写一个容器包含元素{2,3,5,7,9,13,17,19}
	cout<< i << endl;
}
vector<double> vec;
...
for (auto elem : vec){
	cout << elem << endl;
}
for (auro& elem : vec){
	elem *= 3;
}

在这里插入图片描述

15.Reference

在这里插入图片描述
注意:
1.sizeof(r) == sizeof(x)
2.&x == &r;
object和其reference的大小相同,地址也相同(全都是假象)
Java里面所有变量都是reference

int x = 0;
int* p = &x;
int& r = x;	//r代表x。现在r,x 都是0(声明reference必须赋初值,且不能更改;指针可以)
int x2 = 5;

r = x2;	//r不能重新代表其他物品。现在r,x都是5
int& r2 = r;	//现在r2是5(r2代表r:亦相当代表x)

示例

object和其reference的大小相同,地址也相同(全都是假象),虽然在reference与其被引用对象相同,但是本质是指针。

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都是0

	cout<<sizeof(x)<<endl;	//8
	cout<<sizeof(p)<<endl;	//4
	cout<<sizeof(r)<<endl;	//8
	cout<<p<<endl;			//0065FDFC
	cout<<*p<<endl;			//0
	cout<<x<<endl;			//0
	cout<<r<<endl;			//0
	cout<<&x<<endl;			//0065FDFC
	cout<<&r<<endl;			//0065FDFC
	
	S s;
	S& re = s;
	cout<<sizeof(s)<<endl;	//16
	cout<<sizeof(rs)<<endl;	//16
	cout<<&s<<endl;			//0065FDE8
	cout<<&rs<<endl;		//0065FDE8

}

reference的常见用途

reference通常不用于声明变量,而用于参数类型(parameters type)和返回类型(return type)的描述。

void func1(Cls* pobj) { pobj->xxx(); }
void func2(Cls obj) { obj.xxx(); }
void func3(Cls& obj) { obj.xxx(); }
...					被调用段写法相同,很好
Cls obj;
func1(&obj);	————接口不同,困难
func2(obj);	————调用端接口相同,很好
func3(obj);	————调用端接口相同,很好

调用相同导致的问题,以下被视为"same signature"(所以二者不能同时存在):

double imag(const double& im) {...}
double imag(const double im) {...}	// Ambiguity

Q:const是不是函数签名的一部分?
A:是,两个函数一模一样,一个有const一个无const可以并存。

16.复合&继承关系下的构造和析构

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

在这里插入图片描述
base class的dtor必须是virtual,否则会出现undefined behavior
构造函数由内而外
析构函数由外而内

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

在这里插入图片描述
构造函数由内而外
析构函数由外而内

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

在这里插入图片描述
到底是先Base还是先Component呢,不同编译器之间有所不同,但是不影响逻辑;但是侯捷老师所使用的编译器顺序是(使用打印来观察):

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

17.关于vptr和vtbl(难点)

对象模型(Object Model):关于vptr和vtbl

在这里插入图片描述
在这里插入图片描述

class A{
public:
	virtual void vfunc1();
	virtual void vfunc2();
			void func1();
			void func2();
private:
	int m_datal,m_data2;
};

class B : public A{
public:
	virtual void vfunc1();
			void func2();
private:
	int m_data3;
};

class C : public B{
public:
	virtual void vfunc1();
			void func2();
private:
	int m_data1,m_data4;
}

函数继承是指继承权,而非继承大小。
C++看到一个函数调用时静态绑定还是动态绑定,动态绑定的三种情况:

  1. 通过指针进行调用;
  2. 这个指针向上转型(up-cast);
  3. 调用虚函数(虚机制);
//用C的语法实现
(*(p->vptr)[n])(p);(*p->vptr[n])(p);

18.关于this

在这里插入图片描述

对象模型(Object Model):关于this

在这里插入图片描述
this->Serialize(); 编译成 ((*this->vptr)[n])(this);

19.关于Dynamic Binding

动态绑定3要素:

  1. 是指针类型
  2. 调用虚函数
  3. 向上转型
    在这里插入图片描述

在这里插入图片描述

谈谈const

const member functions(常量成员函数)

sds
当成员函数的const和non-const版本同时存在,const object只会(只能)调用const版本,non-const object只会(只能)调用non-const版本。

const object (data members不得变动)non-const object(data members可变动)
const member functions(保证不更改deta members)
non-const member functions(不保证deta members)不变×
const String str("hello world");
str.print();//常量对象调用非常量函数

如果当初设计string::print()时未指明const,那么上行便是经由const object调用non-const member function,会出错,此非吾人所料。
non-const member functions可调用const member functions,反之则不行。

class template std::basic_string<...>
有如下两个member functions:
charT
operator[] (size_type pos) const
{
	...//不必考虑COW(两个版本同时存在,智能const对象调用)
}
reference operator[] (size_type pos)
{
...//必须考虑COW
}

COW:Copy On Write
const函数类型属于重载

20.关于New,Delete(operator new,operator delete)

new和delete虽然内部编译为对应步骤不能更改,但是可以重载对应的操作符。

重载::operator new,::operator delete,::operator new[],::operator delete[]

//小心这影响是全局的

void* myAlloc(size_t size)
{ return malloc(size); }
void myFree(void* ptr)
{ return free(ptr); }
//它们不可以被声明在一个namespace内
inline void* operator new(size_t size)
{ 
	cout<<"jjhu global new() \n";
	return myAlloc(size);
}
inline void* operator new[](size_t size)
{ 
	cout<<"jjhu global new[]() \n";
	return myAlloc(size);
}
inline void* operator delete(void* ptr)
{ 
	cout<<"jjhu global delete() \n";
	return myFree(ptr);
}
inline void* operator delete[](void* ptr)
{ 
	cout<<"jjhu global delete[]() \n";
	return myFree(ptr);
}

重载member operator new/delete

重载new和delete

class Foo{
public:
	void* operator new(size_t);
	void operator delete(void*,size_t);
	//...
}
Foo* p = new Foo;

...
delete p;

//接管到内存new
try{
	void* mem = operator new(sizeof(Foo));
	p = static_cast<Foo*>(mem);
	p->Foo::Foo();
}
/

//接管到内存delete
p->~Foo();
operator delete(p);
/

重载new[]和delete[]

class Foo{
public:
	void* operator new[](size_t);
	void operator delete[](void*,size_t);
	//...
}
Foo* p = new Foo;

...
delete p;

//接管到内存new
try{
	void* mem = operator new(sizeof(Foo)*N + 4);
	p = static_cast<Foo*>(mem);
	p->Foo::Foo();//N次
}
/

//接管到内存delete
p->~Foo();	//N次
operator delete(p);
/

示例,接口

class Foo
{
public:
	int _id;
	long _data;
	string _str;
public:
	Foo():_id(0) {cout<<"default ctor.this="<<this<<"id="<<_id<<endl; }
	Foo(int i):_id(i) {cout<<"default ctor.this="<<this<<"id="<<_id<<endl; }
//virtual
~Foo()	{ cout<<"dtor.this="<<this<<"id="<<_id<<endl; }
static void* operator new(size_t size);
static void operator delete(void* pdead,size_t size);
static void* operator new[](size_t size);
static void operator delete[](void* pdead,size_t size);
}
void* Foo::operator new(size_t size)
{
	Foo* p = (Foo*)malloc(size);
	cout<<....
	return p;
}
void Foo::operator delete(void* pdead,size_t size)
{
	cout<<....
	free(pdead);
}
void* Foo::operator new[](size_t size)
{
	Foo* p = (Foo*)malloc(size);
	cout<<....
	return p;
}
void Foo::operator delete[](void* pdead,size_t size)
{
	cout<<....
	free(pdead);
}
//调用
Foo* pf = new Foo;
delete pf;	//使用上面定义的接口函数(若无member就调用globals)
//下面强制时候用globals
Foo* pf = ::new Foo;	//void* ::operator new(size_t);
::delete pf;			//void ::operator delete(void*);

在这里插入图片描述
在这里插入图片描述

23.重载new(),delete(),$示例

我们可以重载class member operator new(),写出多个版本,前提是每一版本的声明都必须有独特的参数列,其中第一参数必须是size_t,其余参数以new所指定的placement arguments为初值。出现于new (... )小括号内的便是所谓的placement arguments。

Foo* pf = new (300,'c') Foo;

我们也可以重载class member operator delete(),写出多个版本。但它们绝不对被delete调用。只有当new所调用的ctor抛出exception,才会调用这些重载版operator delete()。它只可能这样被调用,主要用来抛出未能完全创建成功的object所占有的memory。

class Foo{
public:
	Foo() { cout<<"Foo::Foo"<<endl; }
	Foo(int) { cout<<"Foo::Foo"<<endl; throw Bad(); }//故意抛出异常
	//(1)这个就是一般的operator new()的重载
	void* operator new(size_t size){
		return malloc(size);
	}
	//(2)这个就是标准库已提供的placement new()的重载(的形式)
	//	(所以我也模拟standard placement new,就只是传回pointer)
	void* operator new(size_t size,void* start){
		return start;
	}
	//(3)这个才是崭新的placement new
	void* operator new(size_t size,long extra){
		return malloc(size+extrea);
	}
	//(4)这又是一个placement new
	void* operator new(size_t size,long extra,char init){
		return malloc(size+extra);
	}
	//(5)这又是一个placement new,但故意写错第一参数的type(那必须是size_t以符合正常的operator new)
	void* operator new(long extra,char init){
	//[Error]'operator new' takes type 'size_t'('unsigned int')
	//as first parameter [-fpermissive]
		return malloc(extra);
	}
	//以下是搭配上述placement new的各个所谓placement dalete。
	//当ctor发出异常,这儿对应的operator(placement) delete就会被调用。
	//其用途是释放对应之placement new分配所得的memory。
	//(1)这个就是一般的operator delete()的重载
	void operator delete(void*,size_t){
		cout<<"operator delete(void*,size_t)"<<endl;
	}
	//(2)对应上页的(2)
	void operator delete(void*,void*){
		cout<<"oeprator delete(void*,void*)"<<endl;
	}
	//(3)对应上页的(3)
	void operator delete(void*,long){
		cout<<"operator delete(void*,long)"<<endl;
	}
	//(4)对应上页的(4)
	void operator delete(void*,long,char){
		cout<<"operator delete(void*,long,char)"<<endl;
	}
private:
	int m_i;
};
//即使operator delete(...)未能一一对应于operator new(...),也不会出现任何报错,你的意思是:放弃处理ctor发出的异常。
//测试代码
Foo* p1 = new Foo;				//1
Foo* p2 = new (&start) Foo;		//2
Foo* p3 = new (100)Foo;			//3
Foo* p4 = new (100,'a') Foo;	//4
Foo* p5 = new (100) Foo(1);		//5
Foo* p6 = new (100,'a')Foo(1);
Foo* p7 = new (&start) Foo(1);
Foo* p8 = new Foo(1);

在这里插入图片描述ctor抛出异常(奇怪,不同编译器处理不同)

24.Basic_String使用new(extra)扩充申请量

template<...>
class basic_string
{
private:
	struct Rep{
		...
		void release() {if (--ref == 0) delete this; }
		inline static void* operator new (sie_t,size_t);
		inline static void operator delete (void*);
		inline static Rep* create(size_t);//对应下面
		...
	};
	...
};

template <class charT,class traits,class Allocator>
inline basic_string <charT,traits,Allocator>::Rep*
basic_string <charT,traits,Allocator>::Rep::create(size_t extra)
{
	extra = frob_size(extra + 1);
	Rep *p = new (extra) Rep;//对应下面
	...
	return p;
}

template<class charT,class traits,calss Allocator>
inline void * basic_string <charT,traits,Allocator>::Rep::
operator new(size_t s,size_t extra)
{
	return Allocator::allocate(s + extra * sizeof(charT));
}
template<class charT,class traits,class Allocator>
inline void basic_string<charT,traits,Allocator>
operator delete(void * ptr)

...

后记

更新完毕!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值