《More Effective C++》 笔记

条款1: 仔细区分 pointers 和 references

pointers 是一个变量,其本身存放实际内容的地址
references 是一个引用,其就是实际内容的别名

两者都支持多态但是还是有一定区别的

pointer 在进行创建的时,不一定要立即给定一个准确值,虽然发生这种情况时,通常会赋予一个 null,来区别当前指针未进行初始化,杜绝由此产生的野指针,而 pointer 也是可以进行后续修改的,如果后续有该种需求,一个 pointer 可能会指向不同的对象,并利用多态来实现接口实现的切换,如设计模式中的 策略模式 ,就该用 pointer

    //如果在某处定义了一个指针,在使用前必须要先进行检测才进行使用
    string* _s = nullptr;
    /*
    ...
    */
    if(_s){
        /*dosth*/
    }
    
    

reference 是一个引用,相对于 pointer ,其在声明时就必须被指向具体的内容,也就是必须要有一个初始值,且在后续逻辑中不能进行改变, 当实现某些操作符的时,也需要使用 reference 否则语法会让人产生歧义 如 operator[]

    char* _c_p = nullptr;
    char& _c_ref = *_c_p;

这种写法是不允许存在的, reference 不能指向 null ,会产生未定义的结果

所以从两者中进行选择不会有太大的困难,如果后续有切换指向内容的需要就使用指针 pointer ,否则使用引用 reference

条款2: 最好使用 C++ 转型操作符

程序中很难避免发生转型操作,而转型操作通从用小括号加上对象类型来进行转换,

    (type) expression

很难在外部统计的时候知晓当前程序是否有转型操作,所以有了以下几种转型操作符

    static_cast<type> expression

该操作符可以进行类型的转换

//如果需要将一个 double 转换成 int
double _d = 12.3;
//C形式转换
int _i_old =  (int)_d
//新转型操作符转换
int _i_new = static_cast<int> (_d)
    const_cast<type> expression

该操作符可以改变变量的常量性或易变性

    int _val = 10;
    const int& _i_const = _val;
    int& _i_non_const = const_cast<int&>(_i_const);
    _i_non_const = 20;
    std::cout << _val << std::endl;

类似于上述例子,该操作符可以改变 const 的属性,添加或删除,但是也仅限于此了,不能进行类型的转换

    dynamic_coast<type> expression

动态类型转换,如果转换不成功,指针会为0,引用会抛出异常.
用该转型操作符,由于可以知晓是否转换成功,所以可以安全的进行转换.

    reinterpret_cast<type> expression

强制类型转换,其会无视一切规则强行转换,所以很危险,在非绝境时不要进行这种转换.

条款3: 绝对不要以多态方式处理数组

    class BST {};
    class Derived : public BST {};
    BST _b_arr[10];
    
    for (int i = 0; i < 10; i++) {
        _b_arr[i];
    }

上述情景中,_b_arr是很危险的,当对其进行遍历时,编译器会按照父类BST 的大小进行遍历,但是如果数组中存在一个子类 Derived ,那就会出现未定义的行为.
通常认为,子类的大小是大于父类的,在编译器遍历的时候,将不知晓当前对象的实际类型,产生错误的跨度,导致未定义的结果

条款4:非必要不提供 default constructor

如非必要不需要提供 default constructor

default constructor 指的是默认构造函数,也就是一个对象即使不提供任何参数也可以被构造出来

    SClass _c();

但是这么做实际上会造成很多实际逻辑中的困扰,如果一个对象必须有一个 唯一 id 来进行 初始化,那么进行了默认构造初始化,在某一个时刻,将会允许没有 id 的对象产生,这样明显是危险的,但是如果不提供 default constructor ,那么在进行 stl 使用时无法进行方便的使用,因为 stl 往往需要一个 def ctor 来进行初始构造,且在一个继承链中,所有的子类也必须要向上进行跟踪了解构造函数的意义,保证不会出现差错.

根据书上对于 stl 的解决方案可以知道即使不提供 def ctor ,我们也是有很多办法来解决上述问题,并且减少了很多的复杂度

操作符

条款5: 对定制的"类型转换函数"保持警觉

    class Rational{
    public:
        operator double(){}
    }
    

类型转换函数虽然很方便,但有些时候会造成编译器的强制匹配造成一些无法预见的结果,所以如果真的有转换的需求,最好是通过明显的方式来进行转换

    class Rational{
    public:
        double asDouble(){}
    }
    

这样的行为明显是通过大众认可的,就比如string 类型需要转换成 const char *时,并不是提供类型转换函数,而是通过 c_str 进行的显示转换

条款6:区别 increment/decrement 操作符的前置(refix)和后置(postfix)的形式

//refix
Int& Int::operator++(){
    *this += 1
    return *this;
}

//postfix
const Int Int::operator++(int ){
    Int _old_value = *this;
    ++*this;
    return _old_value;
}

关于前置和后置的问题可以通过源码进行分析,包括一些奇妙用法,

通过源码可以看出, 后置由于一个临时变量,书面上来说,效率就会低于前置操作符,所以在编写代码时候尽量使用前置操作符

再说复杂的操作符使用方式

Int _i = 10;
++++_i
_i++++

从代码角度分析,上述两种写法只有++++_i可能会达到我们的编写目的,其返回的自身的引用,而_i++++后一个后置操作符操作对象其实是一个临时变量,且 operator 是一个 none const member function ,也不能执行该种操作

条款7:千万不要重载 && || , 三个操作符

当操作符号被修改的,假如以下形式:

    operator&&(expression1,expression2)

那么 expression1,expression2 的其内容的先后顺序将是未定义的,取决服编译器

 i++&&i--

会发生未定义的结果, 可能i++先执行也可能i–先执行

并且如果重载了操作符,那么对其他维护人员来说将是一场灾难,并不会有人有人想到会有人去重载,即使可以进行重载,并且当重载内容完全颠覆了缘由的含义,那么会造成更严重的后果

条款8:了解各种不同意义的 new 和delete

通常我们使用的 new 都是new operator 操作符,在声请内存的同时,调用构造函数进行初始化,如果我们希望只申请而不调用构造函数,可以使用 operator new

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值