zoukankan      html  css  js  c++  java
  • 【OOP】C++ const成员函数

    预备知识

    1、代码转换分析技巧

    在早期某些编译器会将C++代码翻译为C代码,然后使用C编译器生成可执行文件。其中翻译的一个转化就是:将this指针显式添加到成员函数的第一个参数位置上,并在成员函数调用时,自动将对象的地址传递给参数this。
     
    这个过程用如下代码解释:
    #include<iostream>
    #include<cstdio>
    using namespace std;
    
    class Dog
    {
    public:
        Dog(unsigned  en = 0) :energe(en){}  
    
        void bark() const    
        {
            for (size_t i = 0; i < energe; i++)
                cout << "wang wang!
    ";
        }
    
        void feed(unsigned add)   
        {
            energe += add;
        }
    
    private:
        unsigned  energe;
    };
    
    int main()
    {
    
        Dog dog;
    
        dog.feed(2);   
        dog.bark();    
    
        return 0;
    }

    翻译转换

    //原型转换为C代码后的样子
    void bark(const Dog*this);
    void feed(Dog*this,unsigned add);
    
    //调用时的转换
    dog.feed(2);   ----->  feed(&dog , 2); 
    dog.bark();    ----->  bark(&dog);
     
    现在的C++编译器可能不是这样的工作的,但是,如果你使用这个转换方式去应用到实际的编程的代码分析中,很多代码和语法特性就会迎刃而解。

    2、顶层const,底层const

    顶层const:指针变量本身是常量。(顶层const不适合引用,因为引用天生就是一个常量,始终引用一个对象,直到消亡)

               如   int* const p = &a;

    底层const:变量指向(或者引用)的对象被视为常量。(注意这里的用词:被视为,因为对象不一定就是常量,也可能是底层const指针、引用的一厢情愿)

               如    const int*p ;    int const *p;    这二者写法等价

                      const int& r;

    一个非底层const指针,它对它指向的对象有 读、写的权利,因此,为了确保数据安全,它仅能指向非常量对象。

    int a=1;
    
    const int b = 2;
    
    int*p1 = &a;  //OK
    
    p1  = &b;   //ERROR不允许

    一个底层const指针,他对它指向的对象仅有读的权利。因此,它可以指向常量和非常量。

    int a=1;
    
    const int b = 2;
    
    const int*p1 = &a;  //OK
    
    p1 = &b ;       //OK

    3、函数的参数是否为 底层const 可以作为重载的依据

    举例说,下面2个函数可以重载。

    void foo(const int*p);     //A
    void foo(int*p);           //B

    编译器在重载解析时,根据传递的参数来判断调用哪一个版本。

    当仅存在版本A时,若传递的是非常量int的指针,或常量int的指针,都可以成功调用。

    当仅存在版本B时,若传递的是非常量int的指针,可以成功调用,但不允许传递常量int的指针。

    当A、B 都存在,重载时,若传递的是非常量int的指针,则优先使用版本B,因为这是最匹配的。

                                       若传递的是常量int的指针, 则使用版本A。

    另外,参数本身是否是const 不作为重载的依据,下面的不能重载。

    void foo(const int a);
    void foo(int a);
    //C语言中,参数修饰为const和不使用const修饰 被编译器一样对待,C++为兼容C,也使用了这个策略。因此二者等价。

    如果一个成员函数在逻辑上不会修改对象的状态(字段),就应该定义为const函数

    在上面的Dog类代码中,如果去掉bark函数后的const修饰符,并试着用一个const 对象去调用bark函数,则发现编译器报错。

    void bark() 
    {
         for (size_t i = 0; i < energe; i++)
             cout << "wang wang!
    ";
    }
    
    /////////////////////////
    const Dog dog;
    
    dog.bark(); //错误提示为类型不兼容

    抛开代码,从业务逻辑上来看,bark函数只是在屏幕上输出消息,根本不会改变对象的状态,那即便是const 对象,也必须能成功调用啊。

    这里报错显然是bark函数的问题:应该定义为const函数,这点大家都是很清楚的。但是为什么要这样的呢?

    按照最开始介绍的分析方法,发生错误的代码等价与下面的C代码。

    const Dog dog;
    
    bark(&dog)      //而bark函数转化后的原型为:void bark(Dog*this);

    显然,将常量的指针赋值给非常量指针是不允许的。

    再比如,C++标准库中的string类,它的用于获取字符串长度的成员函数都是const修饰的。如果不是这样的话,那么字符串常量就不能获取他们的长度了,这简直荒谬!

    size_type size() const;
            
    size_type length() const;
    

    const成员函数可以形成重载

    成员函数同时存在 const版本和非const版本?可以重载?是的。

    同样,先按照最开始介绍的第1条分析方法,转换为C代码。然后根据第3条的分析就可以了。这里不再赘述。

    最常用的就是,按照约定当重载索引运算符 [ ] 时,会同时编写一个 const版本和非const版本。

    例如std::vector的[ ]运算符函数

    reference       operator[]( size_type pos );
            
    const_reference operator[]( size_type pos ) const;

    总结

    1、如果一个成员函数在逻辑上不会修改对象的状态(字段),就应该定义为const函数

    2、

    如果对象是const,则它只能调用const成员函数。

    如果对象是普通的非const对象:

                                            调用的某个成员函数是非const函数,则理所当然调用它。

                                            调用的某个成员函数是const函数,则当然也可以调用他。(底层const指针可以指向非常量对象)

                                            调用的某个成员函数同时存在 const版本和非const版本,则优先调用非const成员函数,编译器总是使用最匹配的版本。

     
  • 相关阅读:
    hdfs java.io.IOException: Mkdirs failed to create
    Linux文件权限授予
    插入排序
    Oracle中怎样设置表中的主键id递增
    单链表中是否有环之java实现
    excel 单元格的锁定 以及 JXL的实现方式
    用POI的HSSF来控制EXCEL的研究
    Oracle Job 语法和时间间隔的设定(转)
    您的JAVA代码安全吗?(转)
    Jdom 解析 XML【转】
  • 原文地址:https://www.cnblogs.com/lulipro/p/6855799.html
Copyright © 2011-2022 走看看