C++面向对象高级开发-兼谈对象模型-转换函数、智能指针、迭代器、仿函数与命名空间
Notes
- 转换函数
- 非explicit单实参构造函数
- 转换函数与非explicit单实参构造函数
- explicit单实参构造函数
- 转换函数的模板偏特化实例
- 智能指针
- 迭代器
- 仿函数
- namespace命名空间
转换函数
#include <iostream>
#include <cstdlib>
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(int argc, const char* argv[]) {
Fraction f(3,5);
double d = 4+f; //调用operator double()将f转换为0.6
std::cout << d << std::endl;
return EXIT_SUCCESS;
}
若要将类对象进行类型转换,可增加转换函数处理类型转换。如上述代码中的Fraction分数类,其结果分子除以分母做为double值是允许的。operator double() const
函数即允许将Fraction类转换为double值。转换函数的参数列表均为空。返回值类型不需写出。函数名即为返回值类型。转换函数不改变数据值,故通常增加const关键字。当执行到double d = 4+f;
时,编译器会寻找是否有全局操作符+重载,若存在首参为整型,第二个参数为Fraction类对象时,优先调用全局操作符重载。若无全局操作符重载,则尝试f转换为double即可通过语法规范。则3/5转换为0.6后加上4赋给d。关于转换函数,在合理情况下,其数量不限。
非explicit单实参构造函数
#include <iostream>
#include <cstdlib>
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(int argc, const char* argv[]) {
Fraction f(3,5);
Fraction d2 = f + 4; //调用non-explicit ctor将4转换为Fraction(4,1)后调用operator+
return EXIT_SUCCESS;
}
Argument(Actual Parameter)
指的是函数调用时的实际参数。Parameter是指函数定义中参数,而Argument指的是函数调用时的实际参数。Parameter=形参(formal parameter)
, Argument=实参(actual parameter)
。Fraction类的构造函数为两个Parameters,一个argument。一个argument代表为一个实参即可。编译器读至语句Fraction d2 = f + 4;
寻找operator+
的操作符重载,其参数列表中为与Fraction相加,语句中为4非Fraction类,编译器尝试转换4为Fraction类。由非explicit单实参构造函数,可将4传入构造函数,第二个参数为默认值。故非explicit单实参构造函数可将其他类型的对象转换为此类对象。
转换函数与非explicit单实参构造函数
#include <iostream>
#include <cstdlib>
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(int argc, const char * argv[]) {
Fraction f(3,5);
Fraction d2 = f + 4; // [ERROR] ambiguous
return EXIT_FAILURE;
}
同上,编译器读至Fraction d2 = f+4;
时,通过operator+
尝试将4转换为Fraction类对象,语法规范通过。或将f通过转换函数operator double() const
转换为double后相加使用Fraciton非explicit单实参构造函数后也可通过语法规范检测。此时造成歧义,两种方法均可通过,编译器报错。
explicit单实参构造函数
#include <iostream>
#include <cstdlib>
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(int argc, const char * argv[]) {
Fraction f(3,5);
Fraction d2 = f + 4; // [ERROR] conversion from 'double' to 'Fraction' requested.
return EXIT_FAILURE;
}
此时构造函数前含有explicit关键字,其意义为当新建类对象时才调用构造函数,不可以通过类型转换来调用构造函数。此时执行语句Fraction d2 = f + 4;
编译器不会再将4通过构造函数转换为Fraction类对象。explicit关键字通常情况下用在构造函数前。还可能在模板中用到,后续笔记补充。
转换函数的模板偏特化实例
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)); }
...
}
容器类中存放的元素为Boolean值,其operator[]
函数为取指定位置的boolean值,返回类型为reference,定义为__bit_reference
,并非boolean值,此设计方法为Proxy代理。故struct __bit_reference
中自然含有bool类型的转换函数operator bool() const
。
智能指针
C++ Class完成后的类对象与函数或指针相似。与指针相似的类对象类可称为pointer-like classes
。与指针类似的应用之一即为智能指针。智能指针扩充了原指针的功能。
上图中的代码右侧的指向图中,较大的圆圈为智能指针,智能指针中一定含有普通指针。shared_ptr
类为模板类,故其指针px类型为T*
。对于指针而言,其常用操作符为*
和->
。shared_ptr<Foo> sp(new Foo);
语句即为调用shared_ptr
类的构造函数,参数传入的Foo对象将值赋值给px,使用者即可以通过Foo f(*sp);
或sp->method();
的方法调用调用智能指针。语句sp->method();
中,操作符->
作用至sp,调用shared_ptr
类中操作符重载函数T* operator -> () const
。该函数直接返回指针px。故原语句可直接替换为px->method();
,在C++中,->
符号所得结果会持续作用。通过查阅标准库可知,std::shared_ptr是通过指针保持对象共享所有权的智能指针,多个 shared_ptr对象可占有同一对象。详细参考链接
迭代器
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;
bool operator == (const self& x) const { return node == x.node; }
bool operator != (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;
}
};
迭代器为指向对象的一个元素,所以与智能指针/指针类似。迭代器不仅需要处理智能指针所含有的对*
操作符与->
操作符的重载,还需对==
、!=
、++
、--
等操作符进行重载。
代码中的link_type node
与图中的node
相同,由typedef类型__list_node<T>*
可知node含有data与前后双向指针。对迭代器使用*
符号即为取当前节点的data值。对迭代器使用->
符号与*
意义相同,由于operator*()
已取得对象值,则operator->()
只需取得其指针返回即可。
仿函数
C++ Class完成后的类对象与函数或指针相似。与函数相似的类对象类可称为function-like classes
。操作符()
可被称为function call operator
,即函数调用操作符。对()
操作符重载,即为与函数类似的类。
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 T>
struct identity : public unary function<T,T> {
const T& operator () (const T& x) const {
return x;
}
};
template <class Pair>
struct select1st : public unary_function<Pair, typename Pair::first_type> {
const typename Pair::first_type& operator() (const Pair& x) const {
return x.first;
}
};
template <class Pair>
struct select2nd : public unary_function<Pair, typename Pair::second_type> {
const typename Pair::second_type& operator() (const Pair& x) const {
return x.second;
}
};
代码中的三个struct均对操作符()
重载,即为仿函数。struct identity
为身份认证,传入x,传回x。 select1st<Pair>()()
调用操作符重载函数const typename Pair::first_type& operator(const Pair& x) const
返回x.first
的值。再看另一段仿函数的代码:
template <class T>
struct plus : public binary_function<T, T, T> {
T operator() (const T& x, const T& y) const {
return x + y;
}
};
template <class T>
struct minus : public binary_function<T, T, T> {
T operator() (const T& x, const T& y) const {
return x - y;
}
};
template <class T>
struct equal_to : public binary_function<T, T, bool> {
bool operator() (const T& x, const T& y) const {
return x == y;
}
};
template <class T>
struct less : public binary_function<T, T, bool> {
bool operator() (const T& x, const T& y) const {
return x < y;
}
};
上述代码均取自标准库,仿函数的struct均继承自奇特的基类,且在操作符重载内部并无注明。其特殊基类的源码为:
template <class Arg, class Result>
struct unary_function {
typedef Arg argument_type;
typedef Result result_type;
};
template <class Arg1, class Arg2, class Result>
struct binary_function {
typedef Arg1 first_argument_type;
typedef Arg2 second_argument_type;
typedef Result result_type;
};
上述代码中的两个基类无数据,无函数,只有typedef,其大小应为0,在代码中对基类执行sizeof所得结果为1。标准库中有很多仿函数都继承奇特的基类。其作用在后续笔记中补充。
namespace命名空间
在软件开发过程中,大型软件的开发不同部门间可能会产生函数名称相同、数据名称相同的情况。可将一个部门所写的一段程序放入一个namespace中,可保证不同namespace相同名称的函数调用不冲突。
#include <iostream>
#include <memory>
namespace fa1c0n01 {
void test_member_template() {
...
}
} //namespace1
#include <iostream>
#include <list>
namespace fa1c0n02 {
template <typename T>
using Lst = list<T,allocator<T>>;
void test_template_template_param() {
...
}
}
int main(int argc, const char * argv[]) {
fa1c0n01::test_member_template();
fa1c0n02::test_template_template_param();
return EXIT_SUCCESS;
}
调用函数的全名为namespace::function_name()
,namespace中可放namespace中的“全局变量”,“全局函数”,与其他的namespace中的变量和函数名称无关。