C++_7_const的一些使用

7 const

参考链接:

const的用法,特别是用在函数前面与后面的区别!

没有学不会的 C++:logic constness 和 bitwise constness

Advanced C++ – Logic Constness and Bitwise Constness

7.1 用const修饰函数的参数

  • 参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const修饰.

  • const只能修饰输入参数:

    如果输入参数采用**“指针传递”**,那么加const修饰可以防止意外地改动该指针,起到保护作用。

    //source 是输入参数,如果函数体内的语句试图改动source,编译器将指出错误
    void Func(const char *source);
    

    如果输入参数采用**“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不需要加const修饰**。例如不要将函数voidFunc1(int x) 写成voidFunc1(const int x)

  • 对于非内部数据类型的参数而言:

    voidFunc(A a)这样声明的函数注定效率比较底。因为函数体内将产生A类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。为了提高效率,可以将函数声明改为voidFunc(A &a),因为**“引用传递”仅借用一下参数的别名而已,不需要产生临时对象**。 但是**“引用传递”有可能改变参数a**,为了解决这个问题很容易需要加const修饰,因此函数最终成为voidFunc(const A &a)

    以此类推,是否应将voidFunc(int x) 改写为voidFunc(const int&x),以便提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。

7.2 用const修饰函数的返回值

  • 如果给以**“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针**。例如函数:

    const char * GetString(void);
    //如下语句将出现编译错误:
    char*str = GetString();
    //正确的用法是
    const char *str =GetString();
    
  • 如果函数返回值采用**“值传递方式”**,由于函数会把返回值复制到外部临时的存储单元中,const修饰没有任何价值

    int GetInt(void);
    // 例如不要把上面函数写成下面
    const int GetInt(void);
    //=========================================
    A GetA(void);
    //同理不要把上面函数写成下面,其中A为用户自定义的数据类型。
    const A GetA(void);  
    
  • 如果返回值不是内部数据类型,将函数A GetA(void) 改写为const A &GetA(void)的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。
    函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。
    例如:

    classA
    {
      A & operate = (const A &other); // 赋值函数
    };
    Aa, b, c; // a, b, c 为A的对象
    //==============================================
    a= b = c; // 正常的链式赋值
    (a= b) = c; // 不正常的链式赋值,但合法
    //如果将赋值函数的返回值加const修饰,那么该返回值的内容不允许被改动。上例中,语句a= b = c 仍然正确,但是语句(a= b) = c 则是非法的。
    

7.3 const 成员函数

int func(void) const
{
    // ....
}
  • 任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。

    以下程序中,类stack的成员函数GetCount仅用于计数,从逻辑上讲GetCount应当为const函数。编译器将指出GetCount函数中的错误。

    classStack
    {
    public:
        void Push(int elem);
        int Pop(void);
        int GetCount(void) const; // const 成员函数
    private:
        int m_num;
        int m_data[100];
    };
    
    int Stack::GetCount(void) const
    {
        ++ m_num; // 编译错误,企图修改数据成员m_num
        Pop();    // 编译错误,企图调用非const函数
        return m_num;
    }
    
  • const 成员函数的声明看起来怪怪的:const关键字只能放在函数声明的尾部,大概是因为其它地方都已经被占用了
    关于const函数的几点规则:

    • const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.
    • const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的.
    • const成员函数不可以修改对象的数据,不管对象是否具有const性质。在编译时,对于const成员函数,以是否修改成员数据为依据,进行检查.
    • 然而加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的
  • 补充:const放在后面跟前面有区别么,准确的说const是修饰**this指向的对象**。

    classA{
    public:
        f(int);
    };
    //这里f函数其实有两个参数,第一个是A*const this, 另一个才是int类型的参数
    

    如果我们不想f函数改变参数的值,可以把函数原型改为f(const int),但如果我们不允许f改变this指向的对象呢?因为this是隐含参数,const没法直接修饰它,就加在函数的后面了,表示this的类型是const A* const this
    const修饰*this是本质,至于说“表示该成员函数不会修改类的数据。否则会编译报错”之类的说法只是一个现象,根源就是因为*thisconst类型的

7.4 使用const的一些建议

1 要大胆的使用const,这将给你带来无尽的益处,但前提是你必须搞清楚原委;
2 要避免最一般的赋值操作错误,如将const变量赋值,具体可见思考题;
3 在参数中使用const应该使用引用或指针,而不是一般的对象实例,原因同上;
4 const在成员函数中的三种用法要很好的使用;
5 不要轻易的将函数的返回值类型定为const;
6 除了重载操作符外一般不要将返回值类型定为对某个对象的const引用;

7.5 bitwise const 与 logic const是什么意思?

首先介绍一下什么是Logic Constness和Bitwise Constness,

  • Logic Constness指的是function 后的const修饰,我们只知道这个function被const修饰了,但是并没有任何变量被const。
  • Bitwise Constness指的是变量(指针,或者引用等,基本上任何的二进制储存值)被const修饰。

什么是 Logic constness

有以下类 BigArray,其成员 vector<int> v; 是一个数组数据结构,为了让外部可以访问该数组,此类提供了一个 getItem 接口,除此之外,为了计算外部访问数组的次数,该类还设置了一个计数器 accessCounter ,可以看到用户每次调用 getItem 接口,accessCounter 就会自增,很明显,这里的成员 v 是核心成员,而 accessCounter 是非核心成员,我们希望接口 getItem 不会修改核心成员,而不考虑非核心成员是否被修改,此时 getItem 所具备的 const 特性就被称为 logic constness

class BigArray {
    vector<int> v; 
    int accessCounter;
public:
    int getItem(int index) const { 
        accessCounter++;
        return v[index];
    }
};

什么是 Bitwise constness,这里使用了关键字mutable

但是,上面的代码不会通过编译(const 成员函数,却修改了类的数据),因为编译器不会考虑logic constness ,于是就有了 bitwise constness 这个术语,可以理解为字面上的 constness 属性,编译器只认 bitwise constness。为了解决这种矛盾,可以把 accessCounter 声明为 mutable 的成员,即

class BigArray {
    mutable int accessCounter; 
    // const_cast<BigArray*>(this)->accessCounter++; // 这样也行,但不建议这么做
    // ...
};

此时既保持了 logic constness 特性,编译器又可以通过编译

反过来,如果你的成员是指针类型,在函数中我们修改了指针所指的数据,此时字面上指针变量并没有被修改,编译器依然只会维护 bitwise constness,即便我们将这样的函数声明为 const,依然是没有问题的,例如下面的代码可以通过编译

class BigArray {
    int* v2;
    // ...
    void setV2Item(int index, int x) const {
        v2[index] = x;
    }
};

但逻辑上,这个函数不应该被声明为 const,所以这里最好把 const 关键字去掉。

结论

logic constness 和 bitwise constness 的重要性排序:logic constness > bitwise constness

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值