const和非const函数重载
成员函数后面加const,表示在该函数中不能对类的数据成员进行改变,比如下面的代码:
#include <stdio.h> class A
{
private:
mutable int aa;
public:
A(){}
int x()
{
printf("no const\n");
return aa++;
}
int x() const
{
printf("const\n");
return aa++;
}
}; int main()
{
A a1;
a1.x(); const A a2;
a2.x(); return ;
}
代码的运行结果为:
首先说明成员函数后面加const这种机制,这是软件工程上的一种机制,在先前的软件开发中,类中各个函数的实现是由不同人完成了,所以大家共同操作同一组的数据成员,这样就会产生一种麻烦,会有些菜鸟或者不遵守规则的家伙胡乱的修改数据成员。当时只能用文档的形式来约束大家,在开发文档里清楚的写上你的这个函数实现不允许任何的修改数据成员,但是这样的文档的形式还是不能很好的约束那些家伙,于是C++语言的设计者Stroustrup想出来一个好的办法:
如果成员函数的后面加上了const,就表示这个成员函数不能对数据成员进行任何的修改动作。我把每个函数的声明都写好,不能修改数据成员的函数我在后面加上const。首先你的函数的实现也必须加上相应的const,否则函数的声明和实现不同,跑步起来;其次只要你在函数后面加上了const,就不能修改数据成员了。这样从编译器的水平拦截修改数据成员的行为,而不是从文档的角度限制这种行为。
当然有特殊情况,就是用mutable关键字修饰过的成员变量可以在声明为const 函数中被改变。
关于mutable:
关键字mutable是C++中一个不常用的关键字,他只能用于类的非静态和非常量数据成员。
我们知道一个对象的状态由该对象的非静态数据成员决定,所以随着数据成员的改变,对像的状态也会随之发生变化!
如果一个类的成员函数被声明为const类型,表示该函数不会改变对象的状态,也就是该函数不会修改类的非静态数据成员。
但是有些时候需要在该类函数中对类的数据成员进行赋值。这个时候就需要用到mutable关键字了。 但是上面的程序还有一个问题,在程序中实现了函数的重载,但是这个重载比较古怪,函数的参数列表是完全一样的,不一样的是函数后面有没有const修饰。程序的实现是这样的,你用const的对象去调用这个函数,就走加const的成员函数,用非const的对象去调用这个函数就走没有加const的成员函数。那么具体的实现怎么样呢?下面继续看:
这里先给出重载的结论:
我们知道,如果函数名相同,在相同的作用域内,其参数类型、参数个数,参数顺序不同等能构成函数重载。有趣的是如果同时在类中,对于函数名相同参数列表也相同的成员函数的const函数和非const函数能够构成重载。
它们被调用的时机为:如果定义的对象是常对象,则调用的是const成员函数,如果定义的对象是非常对象,则调用重载的非const成员函数。
上面的这句话是必须的,也就是如果你定义的对象是const的,那么你的这个对象只能调用带有const的成员函数,如果你调用非const的成员函数,编译器就会报错。
这个是理所当然的,试想你定义的对象是const的,意思就是你的对象的数据成员是不可以被改变的。如果允许你调用非const的成员函数,万一你在该函数中修改了对象的数据成员怎么办。与其说在你试图修改数据成员的时候再拦截你还不如在编译的时候就直接拦截你。看下面的代码:
#include <stdio.h> class A
{
private:
int aa;
public:
A(){}
int x()
{
printf("no const\n");
return aa++;
}
}; int main()
{
const A a2;
a2.x(); return ;
}
程序会报错:error C2662: 'x' : cannot convert 'this' pointer from 'const class A' to 'class A &'。
这让我想起来了C++老师说的另外的两个问题(太经典了,再一次感觉到了C++的体系结构,杨老师万岁):
(一)看代码:
const int x = ;
int *p;
p = &x;
这里是绝对会报错的:error C2440: '=' : cannot convert from 'const int *' to 'int *'
你当编译器是傻子啊,你先声明了一个const的变量,表示这个变量不能被修改,然后你又弄一个int类型指针指向它,表示你可以通过指针修改数据,自相矛盾。编译器是不会给你这样的漏洞的。
提示:const int x = 5;const修饰的变量在定义的时候一定要初始化(除非是extern的)。
在看另个一代码:
int x;
const int *p;
p = &x; x++;
(*p)++;
这里也会报错:error C2166: l-value specifies const object。
错误的语句是(*p)++;当然定义的x是可以被修改的,但是const int *p这个指针的意思是:指针可以随便的摆(就是p可以指向x,也可以指向y),但是有一条:我指向谁,谁不能动。所以const int *p并不是说p的值(p的值是对象的地址)不能改变,而是说p指向的对象的值不能被修改。
(二)重头戏来了
老师说过,C++的编译器会在你的每个非静态的成员函数的参数列表中增加一个this指针,因为所有对象的代码在内存中就一份,this指针的功能是谁调用这个成员函数,该成员函数的这个this指针就指向谁,从而处理谁的数据。(这个理论是这篇文章的重点,重中之重)
那么我们的带const的成员函数和不带const的成员函数的重载是怎么识别的呢?我上面只给了个结论,下面我就来揭开这个神秘的面纱:
其实带const的成员函数和不带const的成员函数的this指针是不同的:
const的成员函数的this指针是:const A*类型的;不带const的成员函数的this指针是:A*类型的;(这里的A就是代码中定义的类,借用一下上面的定义)。
这就恍然大悟了吧:
const成员函数不能修改数据成员的内部实现是这样的,一个对象调用了某个带const的成员函数,那么这个带const的成员函数的this指针就指向了这个对象,这就是上面(一)中的一种情况,const A*类型的指针指向谁,谁不能动(不能被修改)。所以带const的成员函数你休想修改数据成员。只要有调用,你的this指针就得指向这个对象,由于你的this指针是const A*类型的,只要指向了你就不能修改。哈哈~~让你得瑟。
还有一种情况:就是如果一个对象是const A a;这种的,也就是说常量对象。那么这个对象只能调用带const的成员函数,因为如果你调用了非const的成员函数,你就是(一)中的另一种情况,你事先定义了一个const变量,然后在用一个非const的指针指向他,你当编译器傻子啊。因为你的非const的成员函数的this指针是A*类型的,没有const修饰。
总结:const A a对象只能调用const修饰的成员函数,因为a是const修饰的,只能用带cosnt的this指针才可以指向他。
A a可以调用非const修饰的成员函数和const修饰的成员函数。但是编译器会首先进行检查,看看const修饰的成员函数有没有修改数据成员,然后调用非const修饰的成员函数(const修饰的和非const修饰的两个成员函数构成了重载)。虽然在不修改数据成员的情况下调用const修饰的成员函数也是可以的,但是有优先权(这里是我猜的,有时间问老师)。
洗了个澡,好像大概想通了,既然是函数重载,那么就根据重载的基础,参数列表的不同。为什么会优先调用非const修饰的成员函数呢?因为非const修饰的成员函数的参数列表是这样的(A * a,......) 带const修饰的成员函数的列表是这样的(const A* a,......),这里只写出他们的this指针,当然了,重载的区分也是靠的this指针,防止函数调用时发生二义性。A a这样的对象调用函数的时候要把自己的地址传给被调成员函数的this指针,a的地址就是相当于A*这样类型的指针,并没有const修饰啊,所以首先选择最相近的那个。当然了,如果只有一个带const修饰的成员函数,直接调用它也没不会报错的。因为具体的实现就好像这样:const int* p = &x;但是我没有对x修改,只是简单的动了copy了一下它的地址。这当然是允许的。
就像下面的代码:
#include <stdio.h> class A
{
private:
int aa;
public:
A(){} int x()const
{
printf("const\n"); return ;
}
}; int main()
{
A a2;
a2.x(); return ;
}
这个是可以正常跑的,不会报错。
#include <stdio.h> class A
{
private:
int aa;
public:
A(){} int x()
{
printf("no const\n"); return ;
} int x()const
{
printf("const\n"); return ;
}
}; int main()
{
A a2;
a2.x(); return ;
}
这个是会优先选择非const修饰的成员函数的。
所以,有const修饰的成员函数和没有const修饰的成员函数之间构成的重载,还是靠的参数列表区分的调用关系,两个函数的参数列表的不同点是编译器自动给你的每个成员函数(静态的成员函数除外,因为静态的成员函数没有this指针,具体分析在另一篇博文上)添加的this指针是不同的,一个是普通的,一个是带有const修饰的。
下面的这个例子是C++的运算符的重载,这里你就必须考虑到const修饰的对象的函数调用,也就是说必须做两套成员函数,一套给有const修饰的对象(比如常量字符串)用,一套给没有const修饰的对象用。 时间关系,今天先到这里,还有矩阵分析的作业没有做,下面的代码是别人的,有时间再回来弄
--------------------------------------------------------------------------------------------------------------------
再看为String类实现的[]操作符重载函数:
char& operator[](int posion) // function_1
{
return data[posion];
};
注意,这里该函数的返回值为一个引用,否则str[0] = 'c'这样的语句就会不合法,因为str[0]将是一个左值。
那么,是否提供这样一个function_1就足够了呢?看下面这段代码: const String str= "She"; char c = str[0]; // 错误!编译提示:error C2678: 二进制“[” : 没有找到接受“const String”类型的左操作数的运算符(或没有可接受的转换)
很显然,我们必须还要为const String提供一个const版本的opeartor[]。如下: char& operator[](int posion) const { return data[posion]; } 这样,当使用const的String对象使用[]操作符时,便会调用该const的重载版本。 但是,这样就OK了嘛?虽然上面的那段代码没有问题了,但是其中却隐藏了一个陷阱,看如下代码: const String str = "She"; str[0] = 'T'; 上面这段代码可以编译,运行通过,str变为了"The"!而str声明为const的!!
现在,你应该知道了,对于const的该操作符重载函数其返回值也应该是const的,否则就会出现可以通过其修改const对象的漏洞。修改如下: const char& operator[](int posion) const { return data[posion]; } 好了,现在没有问题了!
我们再回过头来看一下,为了给String提供一个[]操作符来读写指定位置的字符,需要提供如下两个函数,以分别对非const String对象和const String对象提供支持:
char& operator[](int posion)
{ return data[posion]; };
const char& operator[](int posion) const
{ return data[posion]; }
const和非const函数重载的更多相关文章
- 类的const和非const成员函数的重载
我们从一个例子说起,来看上一篇文章中的String类, 我们为它提供一个下标操作符([ ])以读写指定位置的字符(char). 只要了解过C++的操作符重载的语法,很快就可以写出下面这个[]操作符重载 ...
- const当做标记的函数重载,但是仅仅是限于类里面的成员函数
(1)我们知道函数的重载时根据函数的参数类型以及函数参数个数来重载的,不能用函数返回值来重载函数.但是有时候函数参数个数和函数参数类型重载函数会和默认参数发生冲突: int fun(int i,cha ...
- C++-const_cast只能用于指针和引用,对象的const到非const可以用static_cast
Static_cast可以对对象也可以对指针也可以对引用,但是const_cast只可以对指针和引用使用,后者不可以对对象用,如果你要把一个const值转化为非const值只能用隐式执行或通过使用st ...
- C/C++对bool operator < (const p &a)const的认识,运算符重载详解(杂谈)
下面来进行这段代码的分析: struct node { //定义一个结构体node(节点) int x; int y; int len; //node中有3个成员变量x,y,l ...
- 聊聊C++模板函数与非模板函数的重载
前言 函数重载在C++中是一个很重要的特性.之所以有了它才有了操作符重载.iostream.函数子.函数适配器.智能指针等非常有用的东西. 平常在实际的应用中多半要么是模板函数与模板函数重载,或者是非 ...
- const参数,const返回值与const函数
在C++程序中,经常用const 来限制对一个对象的操作,例如,将一个变量定义为const 的: const int n=3; 则这个变量的值不能被修改,即不能对变量赋值. const 这个关键字 ...
- 两个知识点的回顾(const指针和动态链接库函数dlopen)
昨天,看了一点<c++ primer>和<程序员的自我修养>,想起了自己以前的两个知识点,这里回顾,并且总结一下. 1. const指针的参数 看primer的时候,看到几个概 ...
- C++普通函数与模板函数以及特化函数重载的优先级问题
在面对C++模板的时候,需要十分注意,因为模板的复杂性有很多情况,所以最好学习模板的方法我个人认为就是用到就去学,用不到就尽量别去看各种奇门怪技,因为你就算看了,好不容易搞懂模板的实现内部了,包括元编 ...
- 【C++】C++函数重载的总结
函数重载: 出现在相同作用域中的两个函数,如果具有相同的名字而形参表不同,则称为重载函数(overloaded function).一定要注意函数重载的两个关键词:形参列表和作用域. 任何程序有且仅有 ...
随机推荐
- read op case $op in
read op case $op in
- session劫持以及预防
session劫持是一种广泛存在的比较严重的安全威胁,在session技术中,客户端和服务端通过session的标识符来维护会话, 但这个标识符很容易就能被嗅探到,从而被其他人利用.它是中间人攻击的一 ...
- T-SQL视图
视图(view) 用于存储封装一个select语句,使用方法和表一样.也可通过界面对视图进行操作. create view ordersWithNum --创建一个视图ordersWithNum as ...
- js实现超过长度的字符截取指定长度(中文字符算2个字符),超出部分以...显示
//超过长度的字符截取指定长度,超出部分以...显示 function subString(str, len) { var newLength = 0; var newStr = "&quo ...
- 贝叶斯网络基础(Probabilistic Graphical Models)
本篇博客是Daphne Koller课程Probabilistic Graphical Models(PGM)的学习笔记. 概率图模型是一类用图形模式表达基于概率相关关系的模型的总称.概率图模型共分为 ...
- eclipse中使用Lombok
1.下载Lombok.jar http://projectlombok.googlecode.com/files/lombok.jar 2.运行Lombok.jar: java -jar D:\00 ...
- Android 自定义shape圆形按钮
Shape的属性: solid 描述:内部填充 属性:android:color 填充颜色 size 描述:大小 属性: android:width 宽 android:height 高 gradie ...
- 在vim保存时获得sudo权限
在维护线上服务的时候,经常要编辑一些不属于操作用户的文件,比如只有r权限的文件,每次保存都会提示read only.这时可以使用如下命令代替原有的 :wq 命令 :w !sudo tee % 命令:w ...
- linux---finger命令
问题:CentOS7默认是没有安装finger这个程序的,所以finger命令执行不了. 解决方案: 1.安装finger yum -y install finger
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"的作用
为页面添加正确的DOCTYPE 很多设计师和开发者都不知道什么是DOCTYPE,DOCTYPE有什么用.DOCTYPE是document type的简写.主要用来说明你用的XHTML或者HTML是什么 ...