热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C++const限定符全解

const限定符全解一、const修饰普通变量  intconsta500;  constinta600;  上述两种情况相同,都是声明一个const型的变量,它们
const限定符全解

一、const 修饰普通变量

    int const a = 500;
    const int a = 600;
    上述两种情况相同,都是声明一个const型的变量,它们的含义是:变量a的值不可改变!

二、const 修饰 指针

    int b = 500;
    const int * a = &b;               //情况1
    int const * a = &b;               //情况2
    int * const a = &b;               //情况3
    const int * const  a = &b;   //情况4

这四种情况,先来分析前三种。第四种是上面三种的一个组合,最后分析。
针对情况1、2、3 其实只是两类情况:在星号左边还是在星号右边。在星号左边则const修饰的是指针所指向的变量,即指针指向为常量;如果是在星号右边,const修饰的是指针本身,即指针本身是常量。下面具体分析一下。
情况1和情况2中const都是位于星号的左侧,情况相同,可以归为一类,都是指针所指向的内容为常量(与const放在变量声明符的位置无关),这种情况下不允许对内容进行更改操作。
针对情况1和情况2的变量声明,我们可以看如下情况:
(1) *a = 600;//错误,指针所指内容为常量!
(2) b = 700;    //此时*a 的值就是700了
(3) int c = 800;  a = &c;    //此时*a变成了800了
从上面(1)、(2)、(3)三种情况来看,情况1、情况2这种变量声明方式的意思在于:不能通过a指针来修改a指针所指地址中存放的内容,但是可以通过修改指针所指向的地址。
如图所示,声明变量时a指向0x00000000,其值是500。我们不同通过操作*a来改变0x00000000里面所存的值。但是我们可以改变a指针所指向的地址,比如将a指向0x00000004或者其它地址。也可以新定义一个变量来改变0x00000000里面的值,但是就是不能通过a指针来改变里面存的值。
在情况1、情况2下const int *a;可以在声明的时候不初始化

再来看一下情况3,情况3是指针为常量,也就是说a只能指向0x00000000这个地址。
*a = 600; 在情况3是正确的,它可以通过*a来操控这个地址中所存内容,但是不能改变a指针所指向的地址。
情况3其实还有一种写法:const (int *) a = &b; 因为在声明变量后无法再修改a指针所指向的地址,因此必须在声明的时候初始化

现在再来看看情况4,情况四星号左右两边都有const,也就是说它即是常指针(指针所指向的地址不能变更),其指向的内容也不能通过该指针进行修改。

两个判断对错题:
const int *a = new int;
int * b = a;
错误,因为声明指针的目的是为了对其指向的内容进行改变,而声明的指针e指向的是一个常量,所以不正确;

int * const a = new int;
int *b = a;
正确,因为声明指针所指向的内容可变;

三、const 修饰函数

(1)参数为指针
void function (const int a);               //情况1
void function (const int * a);            //情况2
void function (int const * a);            //情况3
void function (int * const a);            //情况4

情况1:传递过来的参数a在函数中不能被修改(无意义,因为本身就是形参,改不改都不会影响实参)
情况2与情况3相同:a指针所指的内容*a 不能修改
情况4:指针a为常量,其地址不能改动,但是*a可以修改(无意义,改不改a指针指向的地址都不影响实参)

(2)参数为引用

void function (const class & a);   //情况1
void function (const int & a);       //情况2
情况1:在函数内不能改变类对象a,a的成员变量的值也不能被修改
情况2:function()函数不能修改a,对a是只读的
这样的一个const引用传递和最普通的函数按值传递的效果是一模一样的,他禁止对引用的对象的一切修改,唯一不同的是按值传递会先建立一个类对象的副本,然后传递过去,而它直接传递地址,所以这种传递比按值传递更有效。另外只有引用的const传递可以传递一个临时对象,因为临时对象都是const属性,且是不可见的,他短时间存在一个局部域中,所以不能使用指针,只有引用的const传递能够捕捉到这个家伙。

 

(3)const 修饰函数返回值

const 修饰函数返回值其实用的并不是很多,他的含义和const修饰普通变量以及指针含义基本相同。
const int function();         //情况1
const int * function();      //情况2
int * const function();      //情况3
情况1:毫无意义,参数返回本身就是赋值,赋值加个const对被赋值的变量无影响。
情况2:调用时 const int * pvalue = function(); 我们可以将function()看成一个变量,那么就是我们前面说的const修饰指针的情况了。此时指针的内容是不能被该指针修改的。
情况3:调用时 int * const pvalue = function(); 同样可以将function()看成一个变量,那么也就是const修饰指针的情况了。


一般情况下,函数的返回值为某个对象时,如果将其声明为const时,多用于操作符的重载。
通常,不建议用const修饰函数的返回值类型为某个对象或对某个对象引用的情况,原因如下:如果返回值是某个对象并且为const(const A test = A 实例)或返回某个对象的引用为const(const A& test = A实例),则返回值具有const属性,则返回实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作,这在一般情况下很少用到。

四、类中const的使用

(1) const修饰成员函数、成员变量
class point{
    private:
        int x;
        int y;
        const int c;                 // 成员常量不能被修改
    public:
       point(int a=0):c(a) { };     //成员常量在初始化列表中赋值
       int get_x() const;        //类内声明
};

int point :: get_value() const   //类外定义,注意const不能省!
{
    return x;
}
const 成员函数可以访问类中的所有成员变量(const或非const成员变量),但是都不能修改任何一成成员;
const 成员函数只能调用类中的const成员函数,而不能调用类中的非const成员函数;
如果在非const成员函数中,this指针指示一个类类型的;
如果在const成员函数中,this指针式一个const类类型的;
如果在volatile成员函数中,this指针就是一个volatile类类型的。
数据成员 非const成员函数 const成员函数
非const的数据成员 可以访问,也可以修改值 允许访问,但不能修改值
const数据成员 可以访问,也可以修改值 允许访问,但不能修改值
const对象的数据成员 不允许访问 允许访问,但不能修改值

常数据成员是不能被赋值的!初始化类内部的常量的两种方法
一种方法就是static和const并用,在外部初始化:
class A
{
    public:
        A(){}
    private:
        static const int i;
};
const int A :: i =3;

另一种很常见的方法就是初始化列表:
class A
{
    public:
        A (int i =0): test (i) { } 
    private:
        const int test;
};


(2)指向对象的常指针
Point a,b;
Point * const p = &a;
P = &b;  //错误,p指针声明并赋值后便不能再修改其指向的地址了

(3)指向常对象的指针变量

Point a;
Point const * p;
p = &a; 
p指针指向的内容*p不能被p指针修改,p->x= 19;类似的语句在这种情况下就是错误的。

对于const类对象/指针/引用,只能调用类的const成员函数,因此,const修饰成员函数的最重要作用就是限制对于const对象的使用。

(4) const修饰类对象/对象指针/对象引用
·  const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。
·  const修饰的对象,只能调用该对象的const成员函数,该对象的任何非const成员函数都不能被调用(除了由系统自动调用的隐式构造函数和析构函数),因为任何非const成员函数会有修改成员变量的企图。
示例:
class AAA

    void func1( ); 
    void func2( ) const; 

const AAA aObj; 
aObj.func1( ); 错误
aObj.func2( ); 正确

const AAA* aObj = new AAA(); 
aObj-> func1( ); 错误
aObj-> func2( ); 正确

一道思考题:
 以下定义的赋值操作符重载函数可以吗?    
class A
{
    const A& operator=(const A& a);  //赋值函数
}

A a,b,c:
(a=b)=c;
a = b= c;


(a=b)=c;  错误,在const A::operator=(const A& a)中,参数列表中的const的用法正确,而当这样连续赋值的时侯,问题就出现了:因为a.operator=(b)的返回值是对a的const引用,不能再将c赋值给const常量。
a = b= c;  正确!


五、const常量与define宏定义的区别

Point a;
(1)编译器处理方式不同
define宏是在预处理阶段展开
const常量是在编译运行阶段使用
(2)类型和安全检查不同
define宏没有类型,不做任何类型检查,仅仅是展开
const常量有具体的类型,在编译阶段会执行类型检查
(3)存储方式不同
define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存
const常量会在内存中分配(可以是堆中,也可以是栈中)

六、将const类型转换为非const类型的方法

采用const_cast 进行转换。  

用法:const_cast  (expression) 
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
·  常量指针被转化成非常量指针,并且仍然指向原来的对象;
·  常量引用被转换成非常量引用,并且仍然指向原来的对象;
·  常量对象被转换成非常量对象。

示例:
#include
using namespace std;

const int * find( int val, const int *t, int n);

int main()
{
    int a[ ] = {2, 4, 6};
    int * ptr;
    ptr = const_cast(find(4, a, 3));
    if(ptr == 0)
        cout<<"not found\n";
    else 
        cout<<"found; value = " <<*ptr <<‘\n‘;
    return 0; 
}

const int * find( int val, const int * t, int n)
{
    int i;
    for( i=0; i
        if( t[i] == val)
            return &t[i];
    return 0;  // not found
}


七、使用const的一些建议

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


C++ const限定符全解,,

C++ const限定符全解


推荐阅读
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文介绍了使用Java实现大数乘法的分治算法,包括输入数据的处理、普通大数乘法的结果和Karatsuba大数乘法的结果。通过改变long类型可以适应不同范围的大数乘法计算。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • 本文讨论了如何优化解决hdu 1003 java题目的动态规划方法,通过分析加法规则和最大和的性质,提出了一种优化的思路。具体方法是,当从1加到n为负时,即sum(1,n)sum(n,s),可以继续加法计算。同时,还考虑了两种特殊情况:都是负数的情况和有0的情况。最后,通过使用Scanner类来获取输入数据。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • Spring特性实现接口多类的动态调用详解
    本文详细介绍了如何使用Spring特性实现接口多类的动态调用。通过对Spring IoC容器的基础类BeanFactory和ApplicationContext的介绍,以及getBeansOfType方法的应用,解决了在实际工作中遇到的接口及多个实现类的问题。同时,文章还提到了SPI使用的不便之处,并介绍了借助ApplicationContext实现需求的方法。阅读本文,你将了解到Spring特性的实现原理和实际应用方式。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 拥抱Android Design Support Library新变化(导航视图、悬浮ActionBar)
    转载请注明明桑AndroidAndroid5.0Loollipop作为Android最重要的版本之一,为我们带来了全新的界面风格和设计语言。看起来很受欢迎࿰ ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • 本文介绍了iOS数据库Sqlite的SQL语句分类和常见约束关键字。SQL语句分为DDL、DML和DQL三种类型,其中DDL语句用于定义、删除和修改数据表,关键字包括create、drop和alter。常见约束关键字包括if not exists、if exists、primary key、autoincrement、not null和default。此外,还介绍了常见的数据库数据类型,包括integer、text和real。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
author-avatar
此恨缠绵_793
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有