• ###《More Effective C++》- 操作符


    More Effective C++

    #@author:       gr
    #@date:         2015-05-21
    #@email:        forgerui@gmail.com
    

    五、对定制的“类型转换函数”保持警觉

    5.1. C++中存在的转型

    1. C++语言默认提供的隐式转型,包括将int转换为short,将double转换为char
    2. 实现自己类型可能存在的转型:单自变量constructors隐式类型转换操作符

    单自变量constructors:
    指能够以单一自变量成功调用的constructors。可能声明单一参数;也可能声明多个参数,但除第一个参数外都有默认值。

    class Name{
    public:
    	Name(const string& s);			//可以把string转换为Name
    	//....
    };
    class Rational{
    public:
    	Rational(int numerator = 0, int denominator = 1);	//可以把int转换为Rational
    };
    

    隐式类型转换操作符:
    指拥有奇怪名称的member function:关键词operator之后加上一个类型名称。不能指定返回值,因为返回值类型基本上已表现于函数名称上。

    class Rational{
    public:
    	operator double() const;
    };
    
    //在下面情况下自动调用 
    Rational r(1,2);
    double d = 0.5 * r;			//将r转换为double
    

    5.2. 隐式转换存在的问题

    1. 隐式类型转换操作符

       Rational r(1, 2);
       cout << r;					//实际上想调用<<,但忘定义<<,将会调用double进行转换,与期望不符
      

      如上面的这种情况,当类型不符时,会自动转换,不会报错提醒程序员。
      解决方法:不要使用类型转换操作符,另取它名,需要时,手动调用成员函数实现转换。

       class Rational{
       public:
       	double asDouble() const;
       };
      
       Rational r(1, 2);
       cout << r.asDouble() << endl;
      

      C++标准委员会在实现string时,也没有提供默认类型转换,提供了一个c_str()函数转换成const char *

    2. 单变量的constructors

      在C++中提供了explicit关键字,避免隐式调用构造函数转换,必须显示调用。
      如果编译器不能使用explicit,那么必须手动设计,以避免隐式转换。方法是增加一个Proxy做中间层。

       //template <class T>
       class Array{
       public:
       	class ArraySize{							//增加新类ArraySize做Proxy
       	public:
       		ArraySize(int numElements) : theSize(numElements){}
       		int size() const {return theSize;}
       	private:
       		int theSize;
       	};
       	
       	Array(int lowBound, int highBound);
       	Array(ArraySize size);			//这个声明使用ArraySize类型,而不是int类型
       };
      
       Array<int> a(10);					//Array<int>的构造函数需要ArraySize,编译器会先把10转换为ArraySize类型
       if (a == b[i]){}					//这里会出现问题,a是Array<int>,b 是int类型,没有直接的类型比较,因此会报错
      

    六、区别increment / decrement操作符的前置和后置形式

    6.1. 前置和后置函数的声明

    前置和后置函数都没有参数,那么如何判断是前置式还是后置式呢?方法是将后置式加一个int参数,用以区分。

    class UPInt{
    public:
    	UPInt& operator++();
    	const UPInt operator++(int);
    	UPint& operator+=(int);
    };
    
    UPInt i;
    ++i;		//前置式,调用i.operator++()
    i++;		//后置式,调用i.operator++(0)
    

    6.2. 后置式返回const对象

    UPInt i;
    i++++;		//如果后置式不是返回const,就可以连续调用
    

    调用i++++的结果,并不像预期一样可以累加两次,所以要限制这种行为。方法就是使返回的对象为const,这样第二次调用++时编译器会检查不允许通过。

    6.3. 后置式应以前置式为基础

    UPInt& UPInt::operator++()
    {
    	*this++;
    	return *this;
    }
    const UPInt UPInt::operator(int)	//省略参数名,防止编译器报警告
    

    {逗号表达式,又称为“顺序求值运算符”。逗号表达式的一般形式为
    表达式1,表达式2,表达式3……表达式n
    求解过程是:先求解表达式1,再求解表达式2,...。整个逗号表达式的值是最后一个表达式n的值。
    例如这里的“i++,p++”,先求i++的值,然后求p++的值,整个表达式的值是p++的运算结果

    另外、逗号运算符是所有运算符中级别最低的

    	UPInt temp = *this;
    	++*this;		//调用前置式
    	return temp;
    }
    

    后置式实现应以前置式为基础,这样只要维护前置式版本,后置式版本会自动调整为一致行为。

    七、千万不要重载&&||,操作符

    7.1. 真值表达式的骤死式

    含义:一旦真假值确定,即使表达式中还有部分尚未检验,整个评估工作仍告结束。

    “用户定制类型”的&&||操作符,会将expr1 && expr2解析成expr1.operator&&(expr2),会存在如下两个问题:

    1. 当函数调用动作被执行时,所有动作都被评估完毕,没有所谓的骤死式语义。
    2. expr1expr2两个表达式的评估顺序不定,而"骤死式"总是由左向右评估。

    所以不要重载&&||

    八、了解各种不同意义的newdelete

    8.1. operator new vs new operator

    new operator:1. 分配对象的内存空间;2. 调用constructor为分配的内存设定初值。

    // new operator
    Widget *p = new Widget();
    //上面的代码会产生如下的动作
    // operator new
    void *p = operator new(sizeof(Widget));		//分配空间
    p->Widget();					//调用构造函数
    

    operator new:只分配空间,不调用构造函数。

    operator new声明如下:

    void * operator new (size_t size);
    

    可以重载operator new,加上额外的参数,但第一个参数必须总是size_t类型。

    // 调用operator new
    void *rawMemory = operator new (sizeof(string));
    //调用new operator
    string *ps = new string("Memory Management");
    

    8.2. Placement new

    有时候有一些分配好的原始内存,需要在上面构建对象,有一个特殊版本的operator new,称为placement new

    class Widget{
    	public:
    		Widget (int widgetSize);
    		//....
    };
    Widget * constructWidgetInBuffer(void *buffer, int widgetSize)
    {
    	return new (buffer) Widget(widgetSize);
    }
    

    这是new operator的用法之一,其隐式调用operator new, 它的形式如下:

    void * operator new (size_t, void *location)
    {
    	return location;
    }
    

    这样的operator new 即称为placement new。它不需要做任何事情,只需将原始地址返回,之后operator new会调用构造函数构造对象。

    8.3 删除(Deletion)与内存释放(Deallocation)

    delete operatoroperator deletenew一样:

    delete ps;
    // 等价如下代码
    ps->~string();
    operator delete(ps);
    

    如果只打算处理原始的内存,使用new operatordelete operator就行了,它的行为像malloc
    free

    void *buffer = operator new(50 * sizeof(char));
    operator delete(buffer);
    

    8.4 数组

    string * ps = new string[10];
    

    上述使用的new仍然是个new operator,内存不再以operator new分配,而是由operator new[]实现。

    new []时需要调用delete[]删除。

    delete[] ps;
  • 相关阅读:
    使用Python快速生成虚拟的超大文件
    常用的 adb 命令合集
    Jmeter 性能测试之反向代理录制性能测试脚本
    论医院网络时钟系统(NTP时钟服务器)的重要性
    北斗时钟装置(卫星时间同步系统)应用自动化系统探讨
    解决前端部署到Nginx非根目录下页面出现空白的问题
    解决docker中Easyexcel因缺少字体无法导出的问题
    看图认识HTML5
    看图知Docker
    ASIS CTF Finals 2020
  • 原文地址:https://www.cnblogs.com/gr-nick/p/4564658.html
Copyright © 2020-2023  润新知