C++面向对象补充(二)

一、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;
};

int main(){
	Fraction f(3, 5);
	double d = 4 + f; // 调用operator double() 将f转为0.6
}

转化函数一般不会改变data,所以需要加const限定才规范。

  • non-explicit-one-argument ctor
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;
};

int main(){
	Fraction f(3, 5);
	Fraction d2 = f + 4; // 调用 non-explicit ctor将4转为Fraction(4, 1)
						 // 然后再调用operator+
}

这里提到one-argument ctor的概念,即只要传入一个实参,如4,编译器就会默认创建Fraction对象(4,1),但需注意,只有前面没有加explicit时才可以,如果加了关键字explicit限定即explicit Fraction(int num, int den = 1),则编译器不会再自动将4转化为(4,1),因为explicit是具体的意思,此时需要你给出明确的指令才可以将4->(4,1)。

如果编译器在编译时出现有两种或以上情况选择的时候,编译器会认为出现歧义,无法做出判断选择,则会报错

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

int main(){
	Fraction f(3, 5);
	Fraction d2 = f + 4; // 此时编译器无法判断,是使用方法一将f转化为double值与4相加,
						 // 还是使用方法二将4转化Fraction类型,再调用operator+。		 
}

在上面这种情况下,编译器会报错 “[Error] ambiguous“ (即出现歧义)。
注意:在开头的情况下,double d = 4 + f,也有两种转化方式即4->(4,1)或 f -> double,但因为开头的情况下没有重载+号,所以编译器不会存在歧义,只能选择f -> double来进行编译运行。

加上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;
};

int main(){
	Fraction f(3, 5);
	Fraction d2 = f + 4; // 因为关键字explicit的存在,所以4不会再转化为(4,1)
						 // 此时编译器进行的是将f转化为double类型
}

但此时仍报错,只不过错误信息为 "[Error] conversion from ‘double’ to ‘Fraction’ requested’’ ,即double类型的结果无法转化为Fraction类型,因为在关键字explicit的限定下,需要有明确的指令使double转化为Fraction。

explicit关键字主要用于构造函数前

二、pointer-like classes

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

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

int main(){
	shared_ptr<Foo> sp(new Foo);
	Foo f(*sp);
	sp->method(); // px->method();
}

在这里插入图片描述
这里小圆圈表示shared_ptr类型的指针sp,里面包含着Foo类型的指针px。

shared_ptr sp(new Foo);
new Foo创建一个Foo类型的指针,调用shared_ptr的构造函数shared_ptr(T* p)【其传入参数为Foo类型的指针】,创建shared_ptr型指针对象sp(且Foo型指针被包含在内)。

Foo f(*sp);
* 为解引用,拿出指针所指向的具体内容。而在shared_ptr类定义里面,已经定义了对 * 的重载,所以 * 作用在sp上,然后就消耗掉了,即 *sp 表示返回Foo类型的指针 *px 所指向的内容。

sp->method();
这里用到了->的重载,首先->作用在sp上表示返回px,但继续保留->下来继续作用在px上(不像上面 * 那样作用完就消耗掉了),由此便可解释sp->method() 实际相当于px->method() 。

  • 迭代器
template<class T>
struct _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
		return (*node).data;
	pointer operator->() const
		return &(operator*());
}

在这里插入图片描述
下图右图中,生成一个list迭代器ite(即node,是个指针类型),而 *ite 则为取出对应地址存放的data,即获得一个Foo object。
对于 ite->method(),因为->的重载实际相当于调用了一次 * 的重载,然后再用取址符号&作用于其上,则ite->method()为先取到 ite 指针指向的那块内存地址的data内容,然后再取址符号&取到对应内容的地址,实则是&(Foo object),所以相当于Foo::method(),或者(*ite).method() 或者 (&(*ite))->method() 。
在这里插入图片描述

三、member template

设置了4个class,其关系如图
在这里插入图片描述

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


template<class T1, class T2>
struct pair{
	typedef T1 first_type;
	typedef T2 second_type;

	T1 first;
	T2 second;

	pair():first(T1()), second(T2()){}  // 类型+()就是调用这个类的默认构造函数,
										// 又clss T1为函数模板,则T1()表示调用T1函数类的默认构造函数
										// 则T1 first表示创建了个类型为T1的类对象first
	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){}
};

int main(){
	pari<Derived1, Derived2> p;
	pari<Base1, Base2> p2(p);
	// 以上两步相当于
	// pair<Base1, Base2> p2(pair<Derived1, Derived2>());
}

对于代码pair<Base1, Base2> p2(pair<Derived1, Derived2>())
首先pair<Derived1, Derived2>(),直接调用pair的默认构造函数pair()创建了个对象,然后再将创建的对象作为参数传入,所以调用的是pair(const pair<U1, U2>& p)的构造函数。
在这里插入图片描述

pair(const pair<U1, U2>& p): first(p.first), second(p.second){}
另外在调用这个构造函数时需注意,在这个例子中,p.first表示鲫鱼(Derived1),而鲫鱼只能放在鱼类(Base1)中,所以first(p.first),而不是second(p.first);同理,p.second表示麻雀(Derived2),只能放在鸟类(Base2)中,即second(p.second)。

template<typename _Tp>
class shared_ptr: public _shared_ptr<_Tp>{
	...
	template<typename _Tp1>
	explicit shared_ptr(_Tp1* _p): _shared_ptr<_Tp>(_p){} 
	...
};


Base1* ptr = new Derived1; // up-cast 指针上移即子类指针上移成父类指针
shared_ptr<Base1>sptr(new Derived1);  // 模拟 up-cast

explicit shared_ptr(_Tp1* _p): _shared_ptr<_Tp>(_p){}
因为shared_ptr的构造函数加了关键字explicit限定,所以只有在明确传入指针参数的情况下才会调用explicit shared_ptr(_Tp1* _p)这个构造函数。
所以shared_ptrsptr(new Derived1)这里,先new出Derived1子类对象指针,然后作为传入参数,调用explicit shared_ptr(_Tp1* _p)这个构造函数,则_p代表传入的new Derived,然后赋给_shared_ptr<_Tp>即_shared_ptr,即相当于Base1* sptr = new Derived1。

四、template specialization(模板特化)

template特化,即绑定了特定的参数,则template<>里面为空,而一般类定义时设置了对应的参数,如下:

// 模板泛化,Key是任意的,可随意指定
template <class Key> 
struct hash{};

// 模板特化
template<> // 下面的struct设置了特定的参数char
struct hash<char>{
	size_t operator()(char x) const{
		return x;
	}
};

template<> // 下面的struct设置了特定的参数int
struct hash<int>{
	size_t operator()(int x) const{
		return x;
	}
};

template<> // 下面的struct设置了特定的参数long
struct hash<long>{
	size_t operator()(long x) const{
		return x;
	}
};

int main(){
	cout << hash<long>()(1000) << endl;
	return 0;
}

hash<long>()(1000)
第一个空的小括号表示设置的是临时对象
而第二个小括号表示调用了struct hash的成员函数size_t operator()(long x),因为为带参传递调用,则会调用对应的成员函数。
另外hash<long>()(1000)调用的是struct hash<long>{}而不是其他的特化版本,也不调用泛化的struct hash{},因为在指定特化的情况下会选择调用对应的特化版本。

partial specialization

- 个数的偏
// 模板泛化的情况
template <typename T, typename Alloc=...>
class vector{
	...
};
// 模板偏特化的情况,相对于上面,其绑定了第一个参数typename T为 bool,
// 所以此时template<typename Alloc=...>,只需传入剩下的参数即可
template<typename Alloc=...>
class vector<bool, Alloc>{
	...
};

在这里插入图片描述

- 范围的偏
// 模板泛化的情况
template < typename T >
class C{
	...
};
// 模板偏特化的情况,相对于上面,其限定模板类型必须为指针类型,即在指针类型的范围内去任意指定模板参数
template< typename T >
class C < T* >{
	...
};

int main(){
	C<string> obj1; // 调用的是第一种情况
	C<string*> obj2; // 调用的是第二种情况
	return 0;
}

在这里插入图片描述

五、template template parameter(模板模板参数)

template <typename T, template<typename T> class Container > 
class XCls{
private:
	Container<T> c;
public:
	...
};

int main(){
	XCls<string, list> mylst1; // 此是错误的!!
}

此时,模板里面有模板即第二个模板参数为模板template<typename T> class ContainerContainer只是一个符号名称,如也可设置为T1,U…之类的,表示可指定任意的类型。

类XCls的定义是希望可任意指定一个容器如list,然后任意指定容器list里面的元素是string类型,即设计者希望设计出来的XCls类是具有一定的弹性的,所以第二个模板参数便设置为模板模板参数类型。
比如,XCls类也可也指定为vector,然后容器vector里面的元素类型是int型。

XCls<string, list> mylst1 从模板模板参数的写法上来说是无错误的,其表示指定的Container是list容器,然后指定list里面的元素是string类型,即生成的类对象mylst1里面生成了个容器 list<string> c。
但语法上会出现错误,因为容器如list是有第二模板参数的,有的容器还有第三模板参数(平时我们用容器list的时候虽然可以list<int>,省略了第二模板参数,但那是因为有默认值定义了),而在此处用于模板模板参数时,容器有多少个模板参数就都需要定义出来,而不能省略。
所以应设置为以下形式:

template <typename T, template<typename T> class Container > 
class XCls{
private:
	Container<T> c;
public:
	...
};

template <typename T>
using Lst = list<T, allocator<T>>;

int main(){
	XCls<string, Lst> mylst1; // 此是正确的,因为其将容器list的所有模板参数都进行了设置而无省略。
	// 注:此时第二个参数Lst是个未定的模板参数,
    // 虽然在设计上实际是由string指定的了,但从第一层面来说其仍只是个未定的模板参数。
}

再比如,模板模板参数为智能指针的情况:

template <typename T, template<typename T> class SmartPtr > 
class XCls{
private:
	SmartPtr<T> sp;
public:
	XCls(): sp(new T){}
};

int main(){
	XCls<long, unique_ptr> p1; // 此是错误的!!
	XCls<long, weak_ptr> p2; // 此是错误的!!
	
	XCls<long, shared_ptr> p3; // 此是正确的。
	XCls<long, auto_ptr> p4; // 此是正确的。
}

因为智能指针上面列举的都只指定一个模板参数即可,所以这里后面两个shred_ptr和auto_ptr可以使用正确,而前面两个使用错误是因为weak_ptr和unique_ptr的一些独特特性不可以这样使用而出错,并不是上面设计出错的问题。

注意:下面这种情况不是template template parameter !!

template <class T, class Sequence = deque<T>>
class stack{
	friend bool operator== <> (const stack&, const stack&);
	friend bool operator< <> (const stack&, const stack&);
protected:
	Sequence c;
...
};

int main(){
	stack<int> s1;
	stack<int, list<int>> s2;
}

这是标准库里对stack容器类的标准定义的一部分,由stack<int> s1 可以看出,平时我们使用时只需指定第一个模板参数即可,第二个模板参数若不指定时则是默认的。

辨析:
有人认为Sequence是个模板类型名称符号,而deque<T>又是个模板参数,则class Sequence = deque<T>作用在template<>的第二个参数里面不就相当于一个模板模板参数吗?
其实对于第二个参数其已不是模板参数,因为stack<int, list<int>> s2中的第二个参数list<int>实际上已经是确定下来的了,很明确的一个参数了,而不是像XCls<string, Lst> mylst1中的第二个模板参数Lst = list<T, allocator<T>>表示的是未确定的模板参数。

六、variadic templates(数量不定的模板)

void print(){} // 此为当传入参数为空时,调用的情况。

template<typename T, typename... T1> // typename... T1表示后面的指定模板参数有数量不定个
void print(const T& firstArg, const T1&... args){
	cout << firstArg << endl;
	cout << sizeof...(args) << endl; //  sizeof...()统计有多少个参数
	print(args...); // 不断递归传递数量不定的模板参数进去
}

int main(){
	print(7.5, "hello", bitset<16>(377), 42);
	// 最终打印出来的结果:
	// 7.5
	// hello
	// 00000001011111001
	// 42
	return 0;
}

这里,
此时是表示一个语法符号了,表示一个pack(包)
用于template parameters,就是template parameters pack(模板参数包)
用于function parameters,就是function parameters pack(函数参数包)
用于function parameter types,就是function parameter types pack(函数参数类型包)
注意 的不同位置放置使用!!

当参数递归到最后的42时,此时传入参数为空,则调用的不再是函数void print(const T& firstArg, const T1&… args),而是函数void print()。
在这里插入图片描述
另外,由bitset<16>(377)可以打印出来00000001011111001,则表明bitset类里面肯定重载了<<,否则无法打印出来对应的值。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值