方太官方网站的建设情况,seo的优化技巧有哪些,网站与后台,重庆铜梁网站建设价格一、操作符号重载 虽然c/c内置了大量各类操作符#xff0c;开发者可以很方便将其应用数学运算、成员访问、类型转换、内存分配等执行语句中#xff0c;但很多时候#xff0c;也需要根据项目应用需要#xff0c;通过操作符重载#xff0c;能够针对类类型的操作数定义不同的…一、操作符号重载 虽然c/c内置了大量各类操作符开发者可以很方便将其应用数学运算、成员访问、类型转换、内存分配等执行语句中但很多时候也需要根据项目应用需要通过操作符重载能够针对类类型的操作数定义不同的操作符版本尤其是针对于自定义类型对象进行操作时。 1.1 语句表达式 c/c是运算符和它们的操作数的序列排列组合成的一项计算。C 定义了许多内置类型间的操作符和自动转换。使用这些操作及转换能够编写丰富的混合类型表达式。表达式分为 1求值表达式可以产生一个结果比如 22 的求值产生结果 4也可能产生副作用比如对 std::printf(%d,4) 的求值在标准输出上打印字符 4。 2不求值表达式运算符 typeid、sizeof、noexcept 和 decltype (C11 起) 的操作数是不求值表达式除非运算符为 typeid 而操作数是多态泛左值因为这些运算符仅查询其操作数的编译期性质。 3弃值表达式用来实施操作数其副作用的表达式。从这种表达式计算的值被舍弃。这样的表达式包括任何表达式语句的完整表达式内建逗号运算符的左边的实参以及转型到类型 void 的转型表达式的实参。弃值表达式的计算结果永远不进行数组到指针和函数到指针转换。当且仅当该表达式是 volatile 限定的泛左值并是标识、数组下标、成员访问、成员指针、条件、逗号等表达式时进行左值到右值转换。 C 允许我们重定义操作符用于类类型对象时表达通常c/c提供了如下常见运算符
赋值:
ab ab a-b a*b a/b a%b ab a|b a^b ab ab
自增自减:
a --a a a--
算术:
a -a ab a-b a*b a/b a%b ~a ab a|b a^b ab ab
逻辑:
!a ab a||b
比较:
ab a!b ab ab ab ab ab
成员访问:
a[b] *a a a-b a.b a-*b a.*b
其他:
a(...) a,b ?: 其中部分内置操作符不能进行重载
:: .* . ?: 其他特殊操作符
类型转换
static_cast dynamic_cast const_cast reinterpret_cast C风格强制转型
内存分配
new new[] delete delete[]
常量
sizeof typeid
sizeof... noexcept alignof //C11 起 1.2 重载操作符-operator 重载操作符是具有特殊名称的函数保留字 operator 后接需定义的操作符号。像任意其他函数一样重载操作符具有返回类型和形参表。如果需要可以像内置转换那样使用类类型转换将一个类型的对象隐式转换到另一类型。尤其是自定义类型时需要配套创建相关操作符号。
//test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_#include ostreamclass Obj
{
public:Obj(int val_);~Obj();Obj operator(const Obj rhs); //一元操作符赋值运算Obj operator(const int rhs); //一元操作符隐式赋值运算friend Obj operator(const Obj, const Obj);//二元操作符friend std::ostream operator(std::ostream, const Obj);//io运算操作符
private:int val;
};#endif //_TEST_1_H_
//test1.cpp
#include test1.hObj::Obj(int val_) : val(val_)
{};Obj::~Obj()
{};Obj Obj::operator(const Obj rhs)
{if(thisrhs)// 防止自赋值return *this;val rhs.val;return *this;
};Obj Obj::operator(const int rhs)
{val rhs;return *this;
};Obj operator(const Obj lhs, const Obj rhs)
{Obj obj(lhs);obj.valrhs.val;return obj;
};std::ostream operator(std::ostream os, const Obj obj)
{os obj.val;return os;
};
//main.cpp
#include test1.h#include iostreamint main(int argc, char* argv[])
{Obj a(10);std::cout a a \n;//io运算符调用Obj b 11;//隐式转换std::cout b b \n;Obj c a;//赋值运算std::cout c c \n;Obj d bc;//运算std::cout d d \n;std::cout abc abc \n;return 0;
};
//out log
a 10
b 11
c 10
d 21
abc 31 上述示例中为Obj类型创建了赋值运算符、int转Obj转换赋值运算、IO移位操作符及加号运算操作符使Obj类型的使用像内置类型一样直观就是如同内置类型一样自如运用表达式。 为类型创建重载操作符必须选择是将操作符设置为类成员还是普通非成员函数。在某些情况下开发者没有选择操作符必须是成员在另一些情况下可以依据业务需要决定将操作符设置为类成员还是普通非成员函数
赋值、下标[]、调用()和成员访问箭头-等操作符必须定义为成员将这些操作符定义为非成员函数将在编译时标记为错误。像赋值一样复合赋值操作符通常应定义为类的成员与赋值不同的是不一定非得这样做如果定义非成员复合赋值操作符不会出现编译错误。改变对象状态或与给定类型紧密联系的其他一些操作符如自增、自减和解引用通常就定义为类成员。对称的操作符如算术操作符、相等操作符、关系操作符和位操作符最好定义为普通非成员函数。1.3 重载操作符注意事项 重载操作符必须具有至少一个类类型或枚举类型的操作数。即重载操作符不能重新定义用于内置类型对象的操作符的含义内置类型的操作符其含义不能改变例如开发者不能在自定义整型类型的加号操作符
int operator(int, int); //error 非成员运算符要求类类型或枚举类型的参数
int operator(const int, const int); //error 非成员运算符要求类类型或枚举类型的参数
int operator(const int, const int); //error 非成员运算符要求类类型或枚举类型的参数
int operator(const int, const int); //error 非成员运算符要求类类型或枚举类型的参数
int* operator(const int, const int); //error 非成员运算符要求类类型或枚举类型的参数 不要重载具有内置含义的操作符例如
赋值操作符、取地址操作符和逗号操作符对类类型操作数有默认含义。如果没有特定重载版本编译器就自己定义以下这些操作符。合成赋值操作符进行逐个成员赋值使用成员自己的赋值操作依次对每个成员进行赋值。默认情况下取地址操作符和逗号操作符.在类类型对象上的执行与在内置类型对象上的执行一样。取地址操作符返回对象的内存地址逗号操作符从左至右计算每个表达式的值并返回最右边操作数的值。内置逻辑与和逻辑或||操作符使用短路求值。如果重新定义该操作符将失去操作符的短路求值特征。通过为给定类类型的操作数重定义操作符可以改变这些操作符的含义。重载逗号、取地址、逻辑与、逻辑或等等操作符通常不是好做法。这些操作符具有有用的内置含义如果我们定义了自己的版本就不能再使用这些内置含义。 定义操作符为类的成员左操作数将只能是该类类型的对象。作为类成员的重载函数this 作为隐藏的左操作数有一个隐含的 this 形参限定为第一个操作数其形参看起来比操作数数目少 1大多是一元操作符。重载一元操作符如果作为成员函数就没有显式形参如果作为非成员函数就有一个形参。类似的二次操作符时形参1。通常将算术和关系操作符定义非成员函数而将赋值操作符定义为成员
Obj operator(const Obj rhs);//成员函数一元操作符
friend Obj operator(const Obj, const Obj);//普通函数二元算术操作符Obj Obj::operator(const Obj rhs)
{val rhs.val;return *this;
}Obj operator(const Obj lhs, const Obj rhs)
{Obj obj(lhs);obj.valrhs.val;return obj;
}; 通常在操作符定义为非成员函数时操作符通常需要访问类的私有部分通常将它们设置为所操作类的友元。
friend Obj operator(const Obj, const Obj);//二元操作符Obj operator(const Obj lhs, const Obj rhs)
{Obj obj(lhs);obj.valrhs.val; //直接操作私有变量return obj;
}; 如果害怕友元会破环类型数据的封装性可以采用类似功能的成员操作符替代来达成目的例如对于号操作符可以用public 成员 operator 实现
Obj operator(const Obj rhs);//一元操作符
Obj Obj::operator(const Obj rhs)
{val rhs.val;return *this;
};即使重载了相关的操作符也不会改变这些操作符的优先级、结合性或操作数目不管操作 数的类型和操作符的功能定义如何。
//两者的优先级、结合次序并没有不同
//总是将两实参绑定到 operator并且将结果用作 operator 右操作数。
int x0; y1;
int k xy;//两者的优先级、结合次序并没有不同Obj a0; b1;
Obj c ab; //两者的优先级、结合次序并没有不同 约束限制
不能重载运算符 ::作用域解析、.成员访问、.*通过成员指针的成员访问及 ?:三元条件。不能创建新运算符例如 **、 或 |。不可能更改运算符的优先级、结合方向或操作数的数量。重载的运算符-必须要么返回裸指针要么按引用或值返回同样重载了运算符-的对象。运算符 与 || 的重载失去短路求值。重载操作符并不保证操作数的求值顺序尤其是不会保证内置逻辑 AND、逻辑 OR和逗号操作符的操作数求值。在 和 ||的重载版本中两个操作数都要进行求值而且对操作数的求值顺序不做规定。因此重载 、|| 或逗号操作符不是一种好的做法。 1.4 重载操作符使用 重载操作符和内置操作符一样都支持显式调用和隐式调用
Obj a 10; Obj b a;//隐式
std::cout ab ab \n;//隐式
std::cout ab operator(a,b) \n;//显式向使用函数一样 调用成员操作符函数与调用任意其他函数是一样的指定运行函数的对象然后使用点或箭头操作符获取希望调用的函数同时传递所需数目和类型的实参。
Obj a 10; b 11; //隐式
a b; //隐式
a.operator(b); //显式成员函数调用方式 二、操作符重载设计设计习惯 有时我们需要定义自己的操作符时表达逻辑最好和内置操作类型行为保持一致例如不要重载operator操作符却做了减法的工作。如果行为不能和内置类型存在差异最好采用成员函数或普通函数来实现给这些函数其一个知其意的名字比重载操作符更好。 重载的赋值运算应在赋值的内置含义基础上进行定制而不是完全绕开例如类成员变量是vector那么重载操作符内部操作就直接使用vector操作而非另起灶炉除非成员变量没有提供支持。 大多数操作符对类对象没有意义为类重载操作符时除了提供几个常规的操作符如赋值、相等外其余的根据开发进度过程涉及到需要在按需添加。另外操作符号通常是按归类一起提供的 2.1 赋值操作符重载设计 赋值操作符应该是每个自定义类型必备的赋值必须返回对 *this 的引用。即使开发者不显示定义编译器也会自动创建同类赋值操作符开发者也可以为一个类定义许多附加的赋值操作符这些赋值操作符会因右操作符类型不同而不同。当然、-操作符也是赋值操作符范畴它也是返回自身但是通常它们会与、-操作符配套一起。同样先a*b a/b a%b ab a|b a^b ab ab运算操作符号都类似操作符一样this作为左操作数并返回对 *this 的引用。
//test1.h
class Obj
{
public://...otherObj operator(const Obj rhs); //一元操作符赋值运算,不主动定义一般编译器会默认定义这个Obj operator(const int rhs); //一元操作符隐式赋值运算Obj operator(const char rhs); //一元操作符隐式赋值运算Obj operator(const Obj rhs);//一元操作符Obj operator-(const Obj rhs);//一元操作符-
private:int val;
};
//test1.cpp
Obj Obj::operator(const Obj rhs)
{if(thisrhs)return *this;val rhs.val;return *this;
};Obj Obj::operator(const int rhs)
{val rhs;return *this;
};Obj Obj::operator(const char rhs)
{val rhs;return *this;
}; 2.2 算术操作符重载设计 如果一个类有算术操作符或位操作符一般定义为非成员函数除以自身为左操作数的通常也提供相应的复合赋值操作符一般是个好的做法。例如Obj类定义了 操作符逻辑上它也应该定义 。使其操作行为与内置操作符一样复合赋值的行为应与 之后接着 类似。算术操作符a -a ab a-b a*b a/b a%b ~a ab a|b a^b ab ab等都是类似的实现原理。
//test1.h
class Obj
{
public://...otherObj operator(const Obj rhs);//一元操作符Obj operator-(const Obj rhs);//一元操作符-friend Obj operator(const Obj, const Obj);//二元操作符friend Obj operator-(const Obj, const Obj);//二元操作符-
private:int val;
};Obj Obj::operator(const Obj rhs)
{val rhs.val; //会改变对象状态return *this; //返回本身
}
Obj Obj::operator-(const Obj rhs)
{val - rhs.val; //会改变对象状态return *this; //返回本身
}
//加法操作符并不改变操作符的状态操作符是对 const 对象的引用
Obj operator(const Obj lhs, const Obj rhs)
{Obj obj(lhs);//它产生并返回一个新的 Obj 对象该对象初始化为 lhs 的副本。obj.valrhs.val;return obj; //不会改变对象状态返回拷贝对象
};
Obj operator-(const Obj lhs, const Obj rhs)
{Obj obj(lhs);//它产生并返回一个新的 Obj 对象该对象初始化为 lhs 的副本。obj.val-rhs.val;return obj; //不会改变对象状态返回拷贝对象
}; 2.3 关系操作符重载设计 比较及关系操作符号一般定义为非成员函数例如类定义 操作符最好也能提供、、 这些操作符提供类操作符号最好也能提供!操作符号。
//test1.h
class Obj
{
public://...otherstatic int cmp_Obj(const Obj obj1, const Obj obj2);
private:int val;
};inline bool operator(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) 0; }
inline bool operator!(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) ! 0; }
inline bool operator(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) 0; }
inline bool operator(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) 0; }
inline bool operator(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) 0; }
inline bool operator(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) 0; }
//test1.cpp
int Obj::cmp_Obj(const Obj obj1, const Obj obj2)
{return obj1.val - obj2.val;
} 2.4 自增自减操作符重载设计 C 语言不要求自增操作符或自减操作符一定作为类的成员但是因为这些操作符改变操作对象的状态所以更倾向于将它们作为成员。对内置类型而言自增操作符和自减操作符有前缀和后缀两种形式。那么为我们自己的类定义自增操作符和自减操作符时同样也可提供前缀和后缀两种实现方式。
//test1.h
class Obj
{
public://...otherObj operator();//一元操作符,前缀式操作符Obj operator--();//一元操作符--前缀式操作符Obj operator(int rhs);//一元操作符后缀式操作符Obj operator--(int rhs);//一元操作符--后缀式操作符
private:int val;
};
//test1.cpp
//为了与内置类型一致前缀式操作符应返回被增量或减量对象的引用
Obj Obj::operator()
{val;return *this;
}
Obj Obj::operator--()
{--val;return *this;
}
//为了与内置操作符一致后缀式操作符应返回旧值即尚未自增或自减的值
//并且应作为值返回而不是返回引用。
Obj Obj::operator(int rhs)
{Obj ret(*this);*this;return ret;
}
Obj Obj::operator--(int rhs)
{Obj ret(*this);--*this;return ret;
} 操作符的后缀式比前缀式复杂一点先记住对象在加 1减 1 之前的当前状态。这些操作符定义了一个局部 Obj对象将它初始化为 *this 的副本即 ret 是这个对象当前状态的副本。 保存了当前状态的副本后操作符调用自己的前缀式操作符分别进行加 1 或减1*this 调用这个对象的Obj前缀自增操作符返回之后对象本身加了 1但返回的是尚未自增的原值。 自增和自减--操作符经常由诸如迭代器这样的类实现这样的类提供类似于指针的行为来访问序列中的元素。例如可以定义一个类该类指向一个数组并为该数组中的元素提供访问检查。 2.5 输入输出操作符重载设计 支持 I/O 操作的类所提供的 I/O 操作接口一般应该与标准库 iostream为内置类型定义的接口相同因此许多类都需要重载输入和输出操作符。输出操作符 的重载是较常实现的一个操作符号为了与 IO 标准库一致操作符应接受 ostream 作为第一个形参对类类型 const 对象的引用作为第二个形参并返回对ostream 形参的引用。
//test1.h
class Obj
{
public://...otherfriend std::ostream operator(std::ostream, const Obj);//io运算操作符
private:int val;
};
//test1.cpp
std::ostream operator(std::ostream os, const Obj obj)
{os obj.val;return os;
}; 输出操作符 的重载其左操作数为 ostream 类型因此只能定义非成员函数。需要建议的是输出操作符应输出对象的内容进行最小限度的格式化例如换行符等就尽量不要给出那是操作符调用者该操心的事。 输入操作符 的重载这个相对如操作符一般使用到较少但是作为配对使用原则类似设计者一般也会同版本给出
//test1.h
class Obj
{
public://...otherfriend std::istream operator(std::istream in, Obj);//io运算操作符
private:int val;
};
//test1.cpp
std::istream operator(std::istream in, Obj obj)
{//最好安全检查in obj.val; //取出数据写入valreturn in;
} 2.6 逻辑操作符重载尽量避免 前面也讲述过重载操作符并不保证操作数的求值顺序尤其是不会保证内置逻辑 AND、逻辑 OR和逗号操作符的操作数求值。因此重载 、|| 或逗号操作符不是一种好的做法。 2.7 成员访问操作符设计 为了支持指针型类例如迭代器C 语言允许重载解引用操作符*和箭头操作符-。箭头操作符必须定义为类成员函数。解引用操作不要求定义为成员。 解引用操作符和箭头操作符常用在实现智能指针的类中例如在本专栏的C/C开发无可避免的内存管理篇四-智能指针备选_py_free-物联智能的博客-CSDN博客。的“2.1 仿auto_ptr 的类模板”知识点就做过类似的成员访问操作符设计实现
#ifndef _AUTO_PTR_H_
#define _AUTO_PTR_H_#include iostreamtemplatetypename T
class AutoPtr
{
public:explicit AutoPtr(T* p0) : m_ptr(p) {std::cout AutoPtr create!\n;};~AutoPtr() { std::cout AutoPtr delete!\n;delete m_ptr; };T operator*() const { return *m_ptr; };T* operator-() const { return m_ptr;};private:T* m_ptr; // dumb pointer
};void func1(void)
{AutoPtrint a(new int(100));std::cout *a *a \n;std::cout a-() a-() \n;
}#include autoptr.cpp
#endif // _AUTO_PTR_H_ 解引用操作符是个一元操作符。在这个类中解引用操作符定义为成员因此没有显式形参该操作符返回对 AutoPtr所指向的 T的引用。 箭头操作符与众不同。它可能表现得像二元操作符一样接受一个对象和一个成员名。对对象解引用以获取成员。不管外表如何箭头操作符不接受显式形参。
a-(); 本质上是 (a-)(); 这里没有第二个形参因为 - 的右操作数不是表达式相反是对应着类成员的一个标识符。没有明显可行的途径将一个标识符作为形参传递给函数相反由编译器处理获取成员的工作。 下标操作符即operator[]可以从容器中检索单个元素的容器类一般会定义下标操作符下标操作符必须定义为类成员函数。本专栏博文c/c开发无可避免的模板编程实践篇九)-c11的新顺序容器_py_free-物联智能的博客-CSDN博客
就讲述如何定义一个仿std::array容器类时就做了典型的operator[]操作符实现
#include cstring
#include casserttemplatetypename T,int SIZE
struct my_array
{my_array() {memset(Buf,0,SIZE);};~my_array(){};T operator[]( const int pos ){assert(pos 0 pos SIZE);//边界判断return Buf[pos];}T at(const int pos){assert(pos 0 pos SIZE);return Buf[pos];}//其他实现T Buf[SIZE];
};void myarray_test(void)
{//调用my_arrayint,3 a; //int类型数据长度大小3a[0] 9;a.at(1) 10;std::cout a.at(0) a.at(0) \n;std::cout a[1] a[1] \n;
}; 定义下标操作符比较复杂的地方在于它在用作赋值的左右操作符数时都应该能表现正常。下标操作符出现在左边必须生成左值可以指定引用作为返回类型而得到左值。只要下标操作符返回引用就可用作赋值的任意一方。可以对 const 和非 const 对象使用下标也是个好主意。应用于 const 对象时返回值应为 const 引用因此不能用作赋值的目标。类定义下标操作符时一般需要定义两个版本一个为非 const 成员并返回引用另一个为 const 成员并返回 const 引用如果只选择定义一个建议选择后者。
三、特殊操作符重载设计习惯 3.1 调用操作符及函数对象 调用操作符即operator()该操作符可以有零或多个形参和返回值。当用户定义的类重载了函数调用运算符 operator() 时它就成为函数对象 (FunctionObject) 类型。这种类型的对象能用于函数调用式的表达式。
//test1.h
class Obj
{
public://...otherint operator()();int operator()(int val_);
private:int val;
};
//test1.cpp
int Obj::operator()()
{return val;
}
int Obj::operator()(int val_)
{return val_0?-val_:val_;
}
//main.cpp
//Obj obj_op(10);std::cout obj_op() obj_op() \n;std::cout obj_op(-100) obj_op(-100) \n;
//out log
obj_op() 10
obj_op(-100) 100 尽管 Obj 是一个对象而不是函数我们仍然可以“调用”该对象效果是运行由 Obj 对象定义的重载调用操作符。一个类可以定义函数调用操作符的多个版本由形参的数目或类型、返回约束加以区别。 定义了调用操作符的类其对象常称为函数对象即它们是行为类似函数的对象在标准库中经常用作通用算法的实参关于函数对象在本专栏的博文C/C开发无可避免的多线程篇四.线程与函数的奇妙碰撞_py_free-物联智能的博客-CSDN博客有更多的描述大家可移步参考这里就不展开论述。 3.2 布尔运算符 布尔运算符主要是基于类类型 bool安全问题引入的在 C11 引入显式转换函数之前设计一个能用于布尔语境的类比如if(obj) { ... }会出现问题给定一个用户定义转换函数如T::operator bool() const;则隐式转换序列允许再多一步标准转换序列也就是 bool 结果会转换成 int允许诸如 obj 1; 或 int i obj; 这样的代码。 一个早期的解决方案可参见 std::basic_ios它定义 operator! 和 operator void*(C11 前)使得如 if(std::cin) {...} 的代码能编译因为 void* 能转换为 bool但int n std::cout; 不能因为 void* 不可转换至 int。这仍然允许无意义代码能编译如 delete std::cout;。许多 C11 前的第三方库设计带有更为复杂的解决方案称作安全 Bool 手法。 显式 bool 转换也能用于解决安全 bool 问题
explicit operator bool() const { ... } // (C11 起) 用户定义类在有意用于布尔语境时可以定义bool重载运算符 operator bool而 operator! (布尔取反运算符)的受期待行为是返回 operator bool 的取反由于内建运算符 ! 进行按语境到 bool 的转换用户定义类可以只提供 operator bool 而无需重载 operator!。
//test1.h
class Obj
{
public://...otherexplicit operator bool() const;
private:int val;
};
//test1.cpp
Obj::operator bool() const
{return 0!val;
}
//main.cpp
//Obj obj_b(0);if(!obj_b)std::cout obj_b() obj_b() \n;obj_b 1;if(obj_b)std::cout obj_b() obj_b() \n;
//out log
obj_b() 0
obj_b() 1 3.3 内存分配操作符-operator new/delete 在自己的类模板中通过定义自己的名为 operator new 和 operator delete 的成员可以屏蔽标准库operator new 和 operator delete 函数的调用类就可以管理用于自身类型的内存。编译器看到类类型的 new 或 delete 表达式的时候它查看该类是否有operator new 或 operator delete 成员如果类定义或继承了自己的成员new 和 delete 函数则使用那些函数为对象分配和释放内存否则调用这些函数的标准库版本。关于这方面的详细说明请参考本专栏的博文C/C开发无可避免的内存管理篇三-规划好内存_py_free-物联智能的博客-CSDN博客的“二、new和delete表达式的秘密”去深入了解。
//BaseObj.h
#ifndef _BASE_OBJ_H_
#define _BASE_OBJ_H_#include memorytemplate typename T
class BaseObj
{
public:void *operator new(std::size_t);void operator delete(void *, std::size_t);virtual ~BaseObj() { }
protected:T *next;
private:static void add_to_freelist(T*);static std::allocatorT alloc_mem;static T *freeStore;static const std::size_t chunk;
};#include BaseObj.cpp
#endif //_BASE_OBJ_H_
//BaseObj.cpp
#include BaseObj.h#include iostream
#ifdef __linux__
#include stdio.h
#include exception
#endif
//
template typename T
std::allocatorT BaseObjT::alloc_mem;template typename T
T *BaseObjT::freeStore 0;template typename T
const std::size_t BaseObjT::chunk 24;//operator new 使用这个函数将新分配的对象放到自由列表删除对象
template typename T
void *BaseObjT::operator new(size_t sz)
{std::cout BaseObj operator new func is call!\n;// new should only be asked to build a T, not an object// derived from T; check that right size is requestedif (sz ! sizeof(T)){#ifdef WIN32throw std::runtime_error(BaseObj: wrong size object in operator new);#elsestd::cout BaseObj: wrong size object in operator new\n;#endif}if (!freeStore) {// the list is empty: grab a new chunk of memory// allocate allocates chunk number of objects of type TT * array alloc_mem.allocate(chunk);// now set the next pointers in each object in the allocated memoryfor (size_t i 0; i ! chunk; i){add_to_freelist(array[i]);}}T *p freeStore;freeStore freeStore-BaseObjT::next;return p; // constructor of T will construct the T part of the object
}
//operator delete 也使用该函数将对象放回自由列表
template typename T
void BaseObjT::operator delete(void *p, size_t)
{std::cout BaseObj operator delete func is call!\n;if (p ! 0){// put the deleted object back at head of freelistadd_to_freelist(static_castT*(p));}
}template typename T
void BaseObjT::add_to_freelist(T *p)
{p-BaseObjT::next freeStore;freeStore p;
} operator new /delete 操作符重载主要难点在与内存分配及回收上是通过内置类型allocator内存分配器来实现的allocator 类的 construct 及destroy 成员函数可以支持到内存分配及释放。 3.4 转换操作 在布尔操作符时大家可注意到是用一个 explicit 的布尔操作符函数定义一个显式转换若无explicit 标识则认为是隐式转换例如前面见到的operator 。当提供了实参类型的对象而需要一个类类型的对象时编译器将使用该转换。这种构造函数定义了到类类型的转换。除了定义到类类型的转换之外我们还可以定义从类类型的转换。即我们可以定义转换操作符给定类类型的对象该操作符将产生其他类型的对象。像其他转换一样编译器将自动应用这个转换。 转换操作符是一种特殊的类成员函数通用形式是operator type()type 表示内置类型名、类类型名或由类型别名定义的名字。它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明在保留字 operator 之后跟着转换的目标类型。
//test1.h
class Obj
{
public://...otherObj(int val_);operator int() const;
private:int val;
};
//test1.cpp
Obj::Obj(int val_) : val(val_)
{};
//各种以内置类型或自定义类型的函数名都可以
Obj::operator int() const
{return val;
}
//main.cppObj obj_i 10;int i_ obj_b; 对任何可作为函数返回类型的类型除了 void 之外都可以定义转换函数。一般而言 不允许转换为数组或函数类型转换为指针类型数据和函数指针以及引用类型是可以的。 转换函数必须是成员函数不能指定返回类型并且形参表必须为空。转换函数一般不应该改变被转换的对象。因此转换操作符通常应定义为 const 成员。 虽然转换函数不能指定返回类型但是每个转换函数必须显式返回一个指定类型的值。例如operator int 返回一个 int 值如果定义 operator Obj它将返回一个 Obj对象诸如此类通过类型转换操作符可以很方便支持混合类型表达式同时转换减少所需操作符的数目便于代码书写及理解。 转换函数只能应用一个类类型转换如果存在多个会产生歧义则编译报错自定义的和内置算术类型的转换起了冲突
//接前面例子代码
if(i_obj_i) //需要注释inline bool operator(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) 0; }std::cout obj_b() obj_b() \n; 与使用重载操作符一样转换操作符的适当使用可以大大简化类设计的工作并使得类的使用更简单。但是有两个潜在的缺陷定义太多转换操作符可能导致二义性代码一些转换可能利大于弊。避免二义性最好的方法是保证最多只有一种途径将一个类型转换为另一类型。最好的办法是限制转换操作符的数目尤其是到一种内置类型应该只有一个转换。例如如果类 Foo 具有接受类 Bar 的对象的构造函数不要再为类 Bar 定义到类型 Foo 的转换操作。 当然c11标准库还提供了标准转换从一个类型到另一类型的隐式转换。
//隐式转换
static_cast 转换一个类型为另一相关类型
dynamic_cast 在继承层级中转换
const_cast 添加或移除 cv 限定符
reinterpret_cast 转换类型到无关类型
//显示转型转换
使用 C 风格写法和函数式写法
(type)val
四、本博文演示源码 通过定义内置操作符的重载版本我们可以为自己的类型即类类型或枚举类型的对象定义同样丰富的表达式集合。重载操作符必须具有至少一个类类型或枚举类型的操作符。应用于内置类型时重载操作符与对应操作符具有同样数目的操作数、同样的结合性和优先级。 大多数重载操作符可以定义为类成员或普通非成员函数赋值操作符、下标操作符、调用操作符和箭头操作符必须为类成员。操作符定义为成员时它是普通成员函数。具体而言成员操作符有一个隐式 this 指针该指针一定是第一个操作数即一元操作符唯一的操作数二元操作符的左操作数。 重载了 operator()的类的对象称为“函数对象常用语定义与算法产生很好的结合使用的谓词函数。类可以定义转换可以很好帮助优化代码及设计但是必须注意避免避免定义一个类型与另一类型之间的多个转换产生二义性。 本博文撰写的代码如下部分在其他博文中可以详细了解文中以及给出跳转这里就不给出那些代码。编译指令g main.cpp test1.cpp -o test.exe -stdc11。 test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_#include istream
#include ostreamclass Obj
{
public:Obj(int val_);~Obj();operator int() const;int operator()();int operator()(int val_);explicit operator bool() const;Obj operator(const Obj rhs); //一元操作符赋值运算Obj operator(const int rhs); //一元操作符隐式赋值运算Obj operator(const char rhs); //一元操作符隐式赋值运算Obj operator(const Obj rhs);//一元操作符Obj operator-(const Obj rhs);//一元操作符-//Obj operator();//一元操作符,前缀式操作符Obj operator--();//一元操作符--前缀式操作符Obj operator(int rhs);//一元操作符后缀式操作符Obj operator--(int rhs);//一元操作符--后缀式操作符//static int cmp_Obj(const Obj obj1, const Obj obj2);friend Obj operator(const Obj, const Obj);//二元操作符friend Obj operator-(const Obj, const Obj);//二元操作符-friend std::ostream operator(std::ostream, const Obj);//io运算操作符friend std::istream operator(std::istream in, Obj);//io运算操作符
private:int val;
};// inline bool operator(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) 0; }
inline bool operator!(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) ! 0; }
inline bool operator(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) 0; }
inline bool operator(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) 0; }
inline bool operator(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) 0; }
inline bool operator(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) 0; }#endif //_TEST_1_H_ test1.cpp
#include test1.hObj::Obj(int val_) : val(val_)
{};Obj::~Obj()
{};Obj::operator int() const
{return val;
}int Obj::operator()()
{return val;
}
int Obj::operator()(int val_)
{return val_0?-val_:val_;
}Obj::operator bool() const
{return 0!val;
}Obj Obj::operator(const Obj rhs)
{if(thisrhs)return *this;val rhs.val;return *this;
};Obj Obj::operator(const int rhs)
{val rhs;return *this;
};Obj Obj::operator(const char rhs)
{val rhs;return *this;
};Obj Obj::operator(const Obj rhs)
{val rhs.val;return *this;
}Obj Obj::operator-(const Obj rhs)
{val - rhs.val;return *this;
}Obj Obj::operator()
{val;return *this;
}
Obj Obj::operator--()
{--val;return *this;
}
Obj Obj::operator(int rhs)
{Obj ret(*this);*this;return ret;
}
Obj Obj::operator--(int rhs)
{Obj ret(*this);--*this;return ret;
}int Obj::cmp_Obj(const Obj obj1, const Obj obj2)
{return obj1.val - obj2.val;
}Obj operator(const Obj lhs, const Obj rhs)
{Obj obj(lhs);obj.valrhs.val;return obj;
};Obj operator-(const Obj lhs, const Obj rhs)
{Obj obj(lhs);obj.val-rhs.val;return obj;
};std::ostream operator(std::ostream os, const Obj obj)
{os obj.val;return os;
};std::istream operator(std::istream in, Obj obj)
{//最好安全检查in obj.val; //取出数据写入valreturn in;
}main.cpp
#include test1.h#include iostreamint main(int argc, char* argv[])
{Obj a(10);std::cout a a \n;Obj b 11;std::cout b b \n;Obj c a;std::cout c c \n;Obj d bc;std::cout d d \n;std::cout abc abc \n;//Obj obj_op(10);std::cout obj_op() obj_op() \n;std::cout obj_op(-100) obj_op(-100) \n;//Obj obj_b(0);if(!obj_b)std::cout obj_b() obj_b() \n;obj_b 1;if(obj_b)std::cout obj_b() obj_b() \n;//Obj obj_i 10;int i_ obj_b;if(i_obj_i) //需要注释inline bool operator(const Obj obj1, const Obj obj2) { return Obj::cmp_Obj(obj1, obj2) 0; }std::cout obj_b() obj_b() \n;return 0;
};