预备知识

1、代码转换分析技巧

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

翻译转换

//原型转换为C代码后的样子
void bark(const Dog*this);
void feed(Dog*this,unsigned add); //调用时的转换
dog.feed(); -----> feed(&dog , );
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=;

const int b = ;

int*p1 = &a;  //OK

p1  = &b;   //ERROR不允许

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

int a=;

const int b = ;

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 = ; i < energe; i++)
cout << "wang wang!\n";
} /////////////////////////
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成员函数,编译器总是使用最匹配的版本。

 

【OOP】C++ const成员函数的更多相关文章

  1. const成员函数

    尽管函数名和参数列表都相同,void foo( ) const成员函数是可以与void foo( )并存的,可以形成重载! 我们假设调用语句为obj.foo(),如果obj为non-const对象,则 ...

  2. c++ 学习之const专题之const成员函数

    一些成员函数改变对象,一些成员函数不改变对象. 例如: int Point::GetY() { return yVal; } 这个函数被调用时,不改变Point对象,而下面的函数改变Point对象: ...

  3. 关于 const 成员函数

    成员函数如果是const意味着什么? 有两个流行概念:物理常量性和逻辑常量性. C++对常量性的定义采用的是物理常量性概念,即const 成员函数不可以更改对象内任何non-static成员变量.例如 ...

  4. C++ Const成员函数

    一些成员函数改变对象,一些成员函数不改变对象. 例如:  int Point::GetY() { return yVal; }  这个函数被调用时,不改变Point对象,而下面的函数改变Point对象 ...

  5. C++类的const成员函数、默认的构造函数、复制形参调用函数(转)

    C++类的const成员函数 double Sales_item::avg_price() const { } const关键字表明这是一个const成员函数,它不可以修改Sales_item类的成员 ...

  6. c++中的const参数,const变量,const指针,const对象,以及const成员函数

    const 是constant 的缩写,“恒定不变”的意思.被const 修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性.所以很多C++程序设计书籍建议:“Use const whe ...

  7. 拷贝构造函数和const成员函数

    实验原因 说明如何使用const描述保护类数据不会意外修改. 编译环境 vc6sp6 + win7x64 工程下载 copyConstruction_constMemberFunction.zip   ...

  8. 12.C++-构造函数与析构函数调用顺序,const成员函数,const对象

    单个对象创建时,构造函数的调用顺序 1.首先判断该对象的类是否拥有父类,若有则先调用父类的构造函数 2.判断该对象的成员是否是其它类的成员,若是则调用成员变量的构造函数(调用顺序和声明顺序相同) 3. ...

  9. const 成员函数

    我们知道,在成员函数中,如果没有修改成员变量,应该给成员函数加上 const 修饰符,例如 #include <iostream> using namespace std; class F ...

随机推荐

  1. C# asp.net中导出Excel表时总出现"只能在执行 Render() 的过程中调用 RegisterForEventValidation

    C# asp.net中导出Excel表时总出现"只能在执行 Render() 的过程中调用 RegisterForEventValidation 后台添加以下方法:/// <summa ...

  2. Xcode不太常见又实用的小技巧

    备份, 原文: http://rocry.com/2012/12/17/xcode-tips/ 让代码中的TODO和FIXME变成Warning 选中某个Target > Build Phase ...

  3. 各大公司Java面试题超详细总结

    ThreadLocal(线程变量副本)Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量.采用空间换时间,它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副 ...

  4. Oracle:oracle 12.1.0.2 升级到12.2.0.1 后,自动任务报错:ORA-20001: Statistics Advisor: Invalid task name for the current user

    具体错误如下:关键字:ORA-12012.ORA-20001 ORA-12012: error on auto execute of job "SYS"."ORA$AT_ ...

  5. MVC使用 Elmah 日志记录组件

    在后台管理中,有一些操作是需要增加操作日志的,尤其是对一些比较敏感的金额类的操作,比如商城类的修改商品金额.删除商品.赠送金额等人工的操作.日志中记录着相关操作人的操作信息,这样,出了问题也容易排查. ...

  6. SpringMVC使用@ResponseBody时返回json的日期格式及可能产生的问题

    http://blog.csdn.net/z69183787/article/details/40375831 遇到的问题: 1 条件: 1.1.表单里有两个时间参数,都是作为隐藏项随表单一起提交: ...

  7. Chrome扩展应用

    现在越来越多的用户将chrome浏览器设置为自己默认的浏览器,不仅是因为他的界面美,最重要的是他对html5和CSS3完美的支持,且调试工具非常好用,还有丰富的扩展库.如何安装自己的扩展呢? 点击自定 ...

  8. PopupWindow错误:PopupWindow$1.onScrollChanged 出现 NullPointerException和PopupViewContainer.dispatchKeyEvent 出现 NullPointerException【转载】

    PopupWindow错误:PopupWindow$1.onScrollChanged 出现 NullPointerException和PopupViewContainer.dispatchKeyEv ...

  9. 解决app频繁更新方案

    目前由于我们项目的特定场景,app的主要问题在于如何做到可以频繁更新而不需要频繁发布,尤其是ios,发布app store的周期基本上是2周左右,虽然可以通过企业号解决,但是后期的打包,分发给用户仍较 ...

  10. actor binary tree lab4

    forward 与 ! (tell) 的差异,举个例子: Main(当前actor): topNode ! Insert(requester, id=1, ele = 2) topNode: root ...