C++中const用法小结

const在C++中使用十分广泛,不同位置使用的意义也不尽相同,所以想写篇文章对其做一个总结。

首先,明确const是“不变”这个基本意义,但是不变不意味着什么都不变,下面将会看到。

1. const与变量

基本原则:const变量(对象)不能被修改

const在变量中的引入和魔数有关,所谓“魔数”指的是突然出现的一个常量值(也叫字面值常量)。

for(int i = 0; i < 512; i++)
{
// todo
}

上例中,512即为魔数,512突然出现在循环中,令人不能得知其意义,所以引入const。

const int length = 512;
for(int i = 0; i < length; i++)
{
// todo
}

这样就知道循环是在长度范围内。

1.1 const修饰一个变量(或者说对象),使其变成一个常量,表示该变量的值无法再被修改,正因为如此,所以定义一个常量的时候,必须初始化。

1.2 const常量的作用域:

我们知道,在全局作用域内声明一个变量(此处特指非const修饰的变量),其作用于整个程序,在其他文件中也能被引用,原因是在全局作用域声明一个变量,默认是extern修饰的。

在全局作用域内声明一个const变量,默认不是extern修饰,所以其只能作用于本文件内,若要在其他文件中访问,需要显式声明为extern

2. const与引用

基本原则:const引用是指向const变量(对象)的引用

const int ival = 1024;
const int &refVal = ival;

2.1 const引用可以指向一个相关类型(不是本类型)的const变量

double dval = 3.14;
const int &refVal = dval;

编译器将double转换成一个临时的int对象,然后让const引用绑定到这个临时对象,所以改变dval的值不会改变refVal,也就是说dval仍然是非const变量,refVal仍然是常量引用。

primer第四版是上面的说法,但我在VS2012中,const也可以指向一个本类型的非const变量,查找资料的原因大概是满足reference-campatible条件。

理论上,我们应该严格遵守,常量引用指向常量对象,非常量引用指向非常量对象,避免出错。

3. const与指针

const与指针的关系分为两种:const修饰的指针和指向const对象的指针,二者const的位置不相同

3.1 指向const对象的指针(const位于指针符号*前面)

对于一个const对象,必须用一个指向const的指针来指向它。原因在于,const修饰使得对象无法被改变,而指针如果不是指向const的指针,则可以通过指针来修改对象,这是不被允许的。

const int ival = 1;
const int *ptrVal = &ival;

反过来,对于一个指向const对象的指针,可以指向任意一个对象,这个该怎么理解呢?我们首先看看指针赋值的过程:

int *ptr = &val;

将val的地址赋值给ptr,因为赋值的只是地址,所以不知道ptr所指向的对象是否为const。

如果我们把一个地址赋值给一个指向const的指针,那么指针认为这是一个const的对象,也就是说,ptr指针指向了一个“自认为”是const的对象。

int ival = 1;
const int *ptrVal = &ival;

上面的程序是正确的,我们需要明确,ival是非const变量,所以我们可以通过给ival赋值更改ival的值。ptrVal指向了一个自认为是const的对象,所以我们无法通过*ptrVal来更改ival的值。

3.2 const修饰的指针(const位于指针符号*后面)

int *const ptr;

上式声明了一个const类型的指针,表示的意思是指针本身是一个常量,不能被修改。

如何理解?指针本身的值是一个地址,如果指针本身是一个常量,则这个地址值不能被修改,也就是说指针只能指向这个地址,不能指向其他地方。但指针所指向的地址的内容不属于指针本身的值,所以其所指向的内容可以改变。

int ival = 1;
int *const ptr = &ival;
*ptr = 2;          // ok
int ivalTwo = 11;
ptr = &ivalTwo       // error

综上,可以定义一个指向const对象的const指针

const int *const ptr = &ival;

3.3 typedef中易出错的const指针

typedef string *ptr;
const ptr s_ptr;

上式不能直接替换理解为const string *s_prt; 从而认为s_ptr是一个指向const string的指针。
首先,ptr是一个指针,const修饰的是一个指针,所以应该是string *const s_ptr; s_ptr是一个指向string的const指针。

4. const与数组

const与数组的点在于const在定义时必须初始化这个原则,所以使用动态分配数组时,如果数组存储的是const类型的对象,必须进行初始化(使用初始化符号())。

5. const与函数返回值

修饰函数的返回值,用于返回一个常量。

const int foo();

5.1 返回通过值传递

如果函数返回时采用值传递,比如返回一个int类型,那么函数会把返回的值(比如47)复制到外部临时存储单元中(产生临时副本),所以加const修饰毫无意义

int foo();
const int foo();

二者完全相同。需要注意的是,值传递产生临时副本,效率低(下面const与函数参数有讲),所以通常采用引用传递来返回。

5.2 返回通过引用传递(并不多见)

如果返回值不是内部类型,通常使用引用传递来返回结果,因为引用传递的是本身,不需要产生临时副本。但需要注意的是,此时仅仅返回一个别名。

ClassType &foo();
const ClassType &foo();

const修饰的返回引用值,表示函数调用的结果只能赋值给一个同类型的const引用。

5.3 返回通过指针传递

const ClassType *foo();

表示函数返回一个ClassType类型的指针,这个指针指向一个const对象,指针所指的内容不能被修改,所以函数的返回值只能赋值给指向一个const的同类型的指针。

const ClassType *ptr = foo();  //ok
ClassType *ptr = foo();     //error

6. const与函数参数

首先需要明确,const修饰的目的就在于保护所修饰的内容不被改变。
在C++中,函数参数分为值传递,指针传递和引用传递。

6.1 值传递

值传递在函数调用时产生一个临时副本,函数中对传入参数的修改和操作是对副本的操作,不改变实参本身的值,所以无需const来保护。
值传递的保护很好,但值传递存在缺点,需要产生临时副本,如果传入的是对象,那么需要进行构造、复制、和析构等操作,效率不高。这时候可以考虑引用传递

void foo1(int x);
void foo2(ClassType instance);     //开销较大

下面这种保护无意义:

void foo1(const int x);
void foo2(const ClassType instance);

6.2 引用传递

通过传入实参的引用,降低开销。因为引用即本身,不需要去产生一个临时副本。

void foo1(int &x);
void foo2(ClassType &ref);

对于上述两个函数,函数调用和值传递的形式完全一样,不同的是函数内部得到的x和ref是调用传入实参的引用。也正因为如此,引用可以通过函数改变传入的参数来改变实参。这对与实参来说,比较危险,这时候需要通过const修饰来保护传入的引用不被修改。

void foo1(const int &x);
void foo2(const ClassType &ref);

通常来说,对于基本内部类型,不存在对象的构造等操作,所以下面两种保护参数不被修改的方式效率基本一样。

void foo1(int x);
void foo1(const int &x);

6.3 指针传递

指针传递在保护参数不被修改上和引用传递是一样的,指针传递还有一个功能是可以扩大接收参数的范围:

void foo1(const ClassType *ptr);

结合上面const与指针,我们知道,ptr指向一个自认为是const类型的对象,所以!传入的对象不一定是const修饰的对象,可以是const对象,也可以非const对象。反过来,如果没有const修饰函数的形参,则只能传入非const对象。

需要明确,无论传入的是否是const对象,都无法通过指针来修改这个对象,这和上面指针与const的关系是一致的。

最后需要知道:const只能修饰一个输入参数,如果是输出参数,无论是引用传递还是指针传递,都不能使用const来修饰

7. const与类的数据成员

const修饰的数据成员不能在构造函数中进行初始化,只能使用成员初始化列表进行初始化。
我的理解是,因为在构造函数执行之前,使用成员初始化列表对数据成员进行初始化,如果在构造函数中对数据成员进行初始化,相当于对const进行二次赋值,这是不被允许的。

之所以这么理解,可以参照类中引用类型的初始化,也是必须在初始化列表中进行初始化,因为引用类型也是在定义的时候必须初始化,要求和const一样,所以二者都只能使用成员初始化列表来进行初始化。

8. const与类的成员函数

const成员函数中,const位于函数的参数列表后面(函数声明前面表示函数的返回值是一个常量)

const修饰的成员函数表示成员函数是一个只读的作用,不改变成员变量。
const成员函数真正的含义在于,const其实修饰的成员函数的是隐含参数this指针,也就是说传入的是const ClassType *this,因为this指向一个const对象,所以不能修改。

因为this是指向对象的指针,所以我们需要再次结合const与指针的知识:

(1)const修饰了this,得到const ClassType *this,this指向一个“自认为”是const的对象(也就是本身),所以任何对象(const或者非const)都可以调用一个const成员函数,因为传入的指针都把自身这个对象看作是const对象,所以不能被修改。
(2)对于一个const对象,当其调用成员函数的时候,默认都传入this指针参数,因为this此时指向一个const对象(本身),所以相当于成员函数被const修饰,成员函数是一个const成员函数,所以反过来说,const对象只能调用const成员函数,因为非const修饰的成员函数,this指针不是指向const对象。
(3)在8.2的基本上,进一步,每个成员函数都可以调用其他成员函数,每个成员函数都传入this指针,所以成员函数相互调用必须保持this指针的一致性,所以const成员函数只能调用const成员函数,因为二者传入的this指针都是const修饰的。对于非const成员函数,其传入非const修饰的this指针,所以不能被调用。

搞清楚const真正的含义就明白了,一定要保持const成员函数传入的是const指针这个意识,对象调用就需要看对象(本身,指针,引用)是否是const。

(0)

相关推荐

  • C++类中的static和const用法实例教程

    static和const是C++程序设计中非常重要的概念,本文实例列举了C++类中的static和const的规则和用法.供大家参考借鉴.具体说明如下: 首先以代码用来举例说明.示例代码如下: class A { public: A():m(10) //const成员必须在构造函数的初始化构造列表中初始化 { q = 40; } void fun1()const { m++; //错误.const成员是常量,不能改变其值. n++; //正确.static变量n属于类,但是每个对象的函数都可以访

  • C++常对象精讲_const关键字的用法

    const关键字: 用const修饰的定义对象称为常对象: 用const修饰的声明成员函数称为常成员函数: 用const修饰的声明数据成员称为常数据成员. 变量或对象被 const修饰后其值不能被更新.因此被const修饰的变量或对象必须要进行初始化. 常对象说明:常对象是指对象的数据成员的值在对象被调用时不能被改变.常对象必须进行初始化,且不能被更新.不能通过常对象调用普通成员函数,但是可以通过普通对象调用常成员函数.常对象只能调用常成员函数.常对象的声明如下: const       <类名

  • 详解C++中const_cast与reinterpret_cast运算符的用法

    const_cast 运算符 从类中移除 const.volatile 和 __unaligned 特性. 语法 const_cast < type-id > ( expression ) 备注 指向任何对象类型的指针或指向数据成员的指针可显式转换为完全相同的类型(const.volatile 和 __unaligned 限定符除外).对于指针和引用,结果将引用原始对象.对于指向数据成员的指针,结果将引用与指向数据成员的原始(未强制转换)的指针相同的成员.根据引用对象的类型,通过生成的指针.引

  • C/C++中CONST用法总结(推荐)

    1.修饰常量时: const int temp1; //temp1为常量,不可变 int const temp2; //temp2为常量,不可变 2.修饰指针时: 主要看const在*的前后,在前则指针指向的内容为常量,在后则指针本身为常量: const int *ptr; //*ptr为常量: int const *ptr; //*ptr为常量: int* const ptr; //ptr为常量: const int * const ptr; //*ptr.ptr均为常量: 3.const修饰

  • C++编程中的const关键字常见用法总结

    1.定义常量 (1)const修饰变量,以下两种定义形式在本质上是一样的.它的含义是:const修饰的类型为TYPE的变量value是不可变的. TYPE const ValueName = value; const TYPE ValueName = value; (2)将const改为外部连接,作用于扩大至全局,编译时会分配内存,并且可以不进行初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义. extend const int ValueName = value; 2.指针使用CONS

  • C++中const的用法详细总结

    1. const修饰普通变量和指针 const修饰变量,一般有两种写法: const TYPE value;TYPE const value; 这两种写法在本质上是一样的.它的含义是:const修饰的类型为TYPE的变量value是不可变的. 对于一个非指针的类型TYPE,无论怎么写,都是一个含义,即value只不可变. 例如: const int nValue: //nValue是constint const nValue: // nValue是const 但是对于指针类型的TYPE,不同的写

  • C++中const用法小结

    const在C++中使用十分广泛,不同位置使用的意义也不尽相同,所以想写篇文章对其做一个总结. 首先,明确const是"不变"这个基本意义,但是不变不意味着什么都不变,下面将会看到. 1. const与变量 基本原则:const变量(对象)不能被修改 const在变量中的引入和魔数有关,所谓"魔数"指的是突然出现的一个常量值(也叫字面值常量). for(int i = 0; i < 512; i++) { // todo } 上例中,512即为魔数,512突然

  • C#中const用法详解

    本文实例讲述了C#中const用法.分享给大家供大家参考.具体用法分析如下: const是一个c语言的关键字,它限定一个变量不允许被改变.使用const在一定程度上可以提高程序的安全性和可靠性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程序也有一些帮助.另外const在其他编程语言中也有出现,如c++.php5.c#.net.hc08 c const 一般修饰 的变量为只读变量 const定义应该为在定义的时候初始化 以后不能改变他的值 例: 复制代码 代码如下: c

  • MySql数据库中Select用法小结

    一.条件筛选 1.数字筛选:sql = "Select * from [sheet1$] Where 销售单价 > 100" 2.字符条件:sql = "Select * from [sheet1$] Where 物品名称 ='挡泥板'" 3.日期条件:sql = "Select * from [sheet1$] Where 物品名称 ='挡泥板'" 4.区间条件:sql = "Select * from [sheet1$] Wh

  • PHP中header用法小结

    本文实例总结了PHP中header用法.分享给大家供大家参考,具体如下: PHP 中 header()函数的作用是给客户端发送头信息. 什么是头信息? 这里只作简单解释,详细的自己看http协议. 在 HTTP协议中,服务器端的回答(response)内容包括两部分:头信息(header) 和 体内容,这里的头信息不是HTML中的<head></head>部分,同样,体内容也不是<BODY>< /BODY>.头信息是用户看不见的,里面包含了很多项,包括:服务

  • checkbox在vue中的用法小结

    前言 关于checkbox多选框是再常见不过的了,几乎很多地方都会用到,这两天在使用vue框架时需要用到checkbox多选功能,实在着实让我头疼,vue和原生checkbox用法不太一样,之前对于vue中用到过的checkbox也只是别人写好的组件,这次在自己实现时走了很多坑,特意写这篇文章记录下来,给后来者提供一个参考 在这之前,先看看原生checkbox搭配jquery取值的用法 <input type="checkbox" name="hobby" v

  • Javascript中typeof 用法小结

    在js里用到数组,比如 多个名字相同(的)input, 若是动态生成(的), 提交时就需要判断其是否是数组. if(document.mylist.length != "undefined" ) {} 这个用法有误. 正确(的)是 if( typeof(document.mylist.length) != "undefined" ) {} 或 if( !isNaN(document.mylist.length) ) {} typeof(的)运算数未定义,返回(的)就

  • C/C++ 中const关键字的用法小结

    C++中的const关键字的用法非常灵活,而使用const将大大改善程序的健壮性. Const作用 NO. 作用 说明 参考 1 可以定义const常量 const int Max = 100; 2 便于进行类型检查 const常量有数据类型,而宏常量没有数据类型.编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误 void f(const int i) { ---} //对传入的参数进行类型检查,不匹配进行提示 3 可以保护被修

  • C语言与C++中const的用法对比

    目录 一.C语言中的const 二.C++中的const 三.进一步比较C和C++中的const 四.const与宏的区别 五.小结 一.C语言中的const const修饰的变量是只读的,本质还是变量 const 修饰的局部变量在栈上分配空间 const修饰的全局变量在只读存储区分配空间 const只在编译期有用,在运行期无用 C语言中的const使得变量具有只读属性 const将具有全局生命周期的变量存储于只读存储区 const修饰的变量不是真的常量,它只是告诉编译器该变量不能出现在赋值符号

  • 详解C/C++中const关键字的用法及其与宏常量的比较

    1.const关键字的性质 简单来说:const关键字修饰的变量具有常属性. 即它所修饰的变量不能被修改. 2.修饰局部变量 const int a = 10; int const b = 20; 这两种写法是等价的,都是表示变量的值不能被改变,需要注意的是,用const修饰变量时,一定要给变量初始化,否则之后就不能再进行赋值了,而且编译器也不允许不赋初值的写法: 在C++中不赋初值的表达一写出来,编译器即报错,且编译不通过. 在C中不赋初值的表达写出来时不报错,编译时只有警告,编译可以通过.而

随机推荐