C++面向对象程序设计提高篇

讨论主题:泛型编程和面向对象编程。主要说明C++中模板的应用。

类型转型

  • 转换函数,operator typename()
  • 转换构造函数,one-argument constructor
  • explicit关键字

类的两种形式

  • pointer-like classes,智能指针
  • function-like classes, 仿函数

模板

  • 类模板
  • 函数模板
  • 成员模板
  • 模板特化
  • 模板偏特化
  • 模板模板参数

C++ 11

  • auto
  • 可变模板参数
  • range-base for
conversion function转换函数
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;
};
{
	Fraction f(3, 5);
	double d = 4 + f;	//调用operator double()将f转换为3/5。
}

通过operator typename()可以将一个类转换为typename类型(转出)。

non-explicit-one-argument constructor转换构造函数
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);
	Fraction d2 = f + 4;	//调用f.operator+() 
}

Fraction构造函数有两个parameter,但有一个默认参数,可以仅需一个argument。同时,在构造函数前未申明为explicit,所以执行Fraction d2 = f + 4时,4将调用该构造函数,转型为Fraction,然后调用operator+()。
此时,讨论的转换构造函数,将单个argument转型为类对象(转入)。

转换函数 vs. 转换构造函数
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 d2 = f + 4;	//error,ambiguity
}

执行d2 = f + 4时,由于4可以调用转换构造函数转型为Fraction,这样符合operator+()调用形式。同样,f可以调用double()转型为double,计算结果在调用Fraction构造函数转型为Fraction。两者并不优劣,编译器无法选择。

explicit-one-argument constructor
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 d2 = f + 4;	//error,conversion from ‘double’ to ‘Fraction’ requested
}

加上explicit关键字,不允许编译器自动调用转换构造函数。这样,4不能再转为Fraction。进而,不满足operator+()的调用形式。
explicit关键字常用于one-argument的构造函数前,作用是防止构造函数得隐式转换。
当类构造函数参数大于等于2时,编译器是不会隐式转换。此时,explicit无效。

pointer-like classes,智能指针
template <class T>
class shared_ptr
{
	public:
		T& operator * () const
		{ return *px; }
		T* operator -> () const
		{ return px; }
		phared_ptr(T* p) : px(p) { }
	private:
		T* px;
		long* pn;
};

以智能指针为例,说明像指针的类的用法。shared_ptr重载操作符*和->,在使用该类时,可以像一个指针一样。

struct Foo{
…
	void method(void) { … }
};
shared_ptr<Foo> sp(new Foo);	//实例模板,并把new的对象托管给shared_ptr
Foo f(*sp);					//重载了operator * ( )
sp->method();				//重载了 operator -> ()
pointer-like classes,迭代器
template <class T>
strcut __list_node{
	void* prev;
	void* next;
	T data;
};
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;
	reference operator * (const self& x) const { return (*node).data; }
	pointer operator -> (const self& x) const { return &(operator*()); }
	bool operator == (const self& x) const { return node == x.node; }
	bool operator != (const self& x) const { return node != x.node; }
	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; }
};

遍历容器的迭代器有类似指针的用法。它也是一个类,不过重载了操作符*和->。
当然,迭代器相比较于shared_ptr,还有==、++、–等的需求。
operator++()重载前缀++,而operator++(int)重载后缀++。
在本例中,后缀++调用了前缀++而实现。

function-like classes,仿函数

类有类似函数的行为。这是因为仿函数重载了操作符()。

template <class T>
struct identity {
	const T&
	operator() (const T& x) const { return x; }
};
{
	identity<bool> ibool() ();
}

前一个括号调用默认的无参构造函数,后者括号调用operator(),看起来类似函数。

template <class T1, class T2>
struct pair{
	T1 first;
	T2 second;
	pair() : first( T1() ), second( T2() )	{  }	//无参构造函数,仍使用T()生成临时对象去赋值
	pair(const T1& a, const T2& b) : first(a), second(b) {  }
};
template <class pair>
struct select1st{
	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; }
};

select1st和select2nd两个类,均重载了(),分别返回pair中first和second的值。

class template,类模板

为了增加类的复用性和类型定义的弹性。在写类时,内部成员的类型可以在定义对象时指定。

template < typename T>
class complex{
	private:
		T re, im;
	public:
		complex(T r=0, T i=0)
			: re(r), im(i)
		{}
};
{
	complex<double> c1(1.2, 2.5);
	complex<int> c2(1,4);
}
function template,函数模板

当函数功能相同,但参数类型不同时,C风格就需要写很多类似的函数,显得累赘。C++中引入函数模板,这样不仅实用于各种类型,函数名还一致。
在调用函数模板时,自动根据传入形参类型推导出函数模板参数类型。

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

在该例中,如果T非内置类型,而是自定义的类,则需要重载<运算符。

member template,成员模板

在类成员中,使用函数模板。进一步提高类的弹性。

template <class T1, class T2>
struct pair{
	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);		//用p去初始化p2
}

在此例pair中,使用成员模板,使得构造函数不仅可以接受T1和T2类型,还可以是任何可以正常赋值给T1和T2类型的变量。
对象p是pair<Derived1,Derived2>类型,而p2是pair<Base1, Base2>类型,但也可以将派生类赋值给父类。

{
	Base1 *ptr = new Derived1;				//up-cast
	shared_ptr<Base1> sptr(new Derived1);
}

基类指针既可以指向基类对象也可以指向父类对象。

specialization,模板特化
template <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; }
};

模板特化,是将模板hash中某些类型单独提出来,单独处理。在使用类模板时,如果定义类型已经特化,则优先使用特化模板。

cout << hash<long> () (1000);	//优先使用特化模板
partial specialization,模板偏特化
template <typename T, typename Alloc=…>
class vector{
	…
};
template < typedef Alloc=…>
class vector<bool, Alloc> {		//将T绑定为bool,但是Alloc仍未确定
	…
};

以上时模板参数个数的偏特化。

template <typename T>
class C{
	…	
};
template <typename T>
class C<T*> {
	…
};

上诉是模板参数的范围偏特化,将任意类型指定为指针类型。

template template parameter,模板模板参数
template <typename T, 
				template <typename T>
					class Container
			   >
class XCls{
	private:
		Container<T> c;
	public:
		…
};
{
	template <typename T>
	using Lst = list<T, allocator<T> >;	
	XCls<string, Lst> mylst;
}

该例子的目的是定义对象mylst中成员变量c是类型为list。

template <typename T, 
				template <typename T>
					class SmartPtr
			   >
class XCls {
	private:
		SmartPtr<T> sp;
	public:
		XCls() : sp(new T) { }
};
{
	XCls<string, shared_ptr> p1;
	XCls<long, auto_ptr> p2;
}

shared_ptr构造函数仅需一个argument,这样p1中sp的类型为shared_ptr,p2中sp的类型为auto_ptr。

variadic templates,可变模板参数
void print() 
{
}
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);

每一次调用print,将参数分为两个部分,firstArg和args,前者为一个对象,后者是一包。递归调用print,最后args…为空,则调用无参print函数(空函数,但必不可少)。
要查看args一包中参数的个数,可以调用sizeof…(args)。

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);
}

但是不能单独定义一个auto对象,编译器无法进行推导。

auto ite;	//error
ranged-base for
vector<double> vec;
for( auto elem : vec ){
	cout << elem << endl;		//pass by value
}
for( auto & elem : vec ) {
	elem *= 3;					//pass by reference
}

对容器遍历有一定的便利性。

reference
int x = 0;
int* p = &x;
int & r = x;		//r代表x,现在r和x都是0
int x2 = 5;
r = x2;				//r不能重新代表其他对象,此处是赋值
int& r2 = r;		//现在r,r2都代表x

reference仍然是使用指针实现,在32位机实质上都是4bytes。但是编译器制造了一个假象。使得,sizeof® == sizeof(x)和&x == &r。即,引用类型大小等于被引用类型大小,且两者占用同一片地址。

reference通常不用于申明变量,二十修饰参数类型和返回值类型。

{
	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);		//调用端接口相同,很好
}

函数签名,包括函数名和参数列(返回值不算)。
double imag(const double& im) {}
double imag(const double im) {}
引用不属于签名,所以上诉的两个的签名一致,即如果两个函数存在,属于重复定义。
但是const关键字属于签名。
double imag(const double im) const{}
double imag(const double im) {}
所以,两者不算重复定义。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值