c++常量详解

概念

常量是存放固定且不可变值的,一旦确定初始值则在程序其它地方不可改变, 所以const对象必须初始化。常量一般使用const关键字来修饰。

const 对象可以大致分为三类:

1. const int a

const int a =10;
int const b =10;

这两种格式是完全相同的。也就是说const 与int哪个写前都不影响语义。有了这个概念后,我们来看这两个家伙:const int * pi与int const * pi ,它们的语义有不同吗?

你只要记住一点,int 与const 哪个放前哪个放后都是一样的,就好比const int n;与int const n;一样。也就是说,它们是相同的。

2. const int * p

前面已经说了 const int * p与int const * p 是完全一样的。

我们根据下面的例子看看它们的含义。

int a =30;
int b =40;
const int * p=&a;
p=&b;   //注意这里,p可以在任意时候重新赋值一个新内存地址
b=80;   //想想看:这里能用*pi=80;来代替吗?当然不能
printf( “%d”, *p ) ;  //输出是80 

语义分析:

p的值是可以被修改的。即它可以重新指向另一个地址的,但是,不能通过*p来修改b的值。

首先const  修饰的是整个*p(注意,是*p而不是p)。所以*p(p指向的对象)是常量,是不能被赋值的(虽然p所指的b是变量,不是常量)。
其次,p前并没有用const 修饰,所以p是指针变量,能被赋值重新指向另一内存地址的。

你可能会疑问:那又如何用const 来修饰pi呢?其实,你注意到int * const pi中const 的位置就大概可以明白了。请记住,通过格式看语义。 我们看看下面的定义。

3. int* const p

这里的const修饰的p,而不是上面的*p,我们根据下面的例子来分析:

int a=30;
int b=40;
int * const p=&a;
//p=&b;
注意这里,p不能再这样重新赋值了,即不能再指向另一个新地址。
b=80;
//这里能用*p=80;来代替吗?可以,这里可以通过*p修改a的值。
//请自行与前面一个例子比较。
printf( “%d”, *p ) ;  //输出是80 

*p=100;

printf( “%d”, a ) ;  //输出是100

语义分析: 
    
p值是不能重新赋值修改了。它只能永远指向初始化时的内存地址了。并且可以通过*p来修改a的值了。与前一个例子对照一下吧!看以下的两点分析

1). p因为有了const 的修饰,所以只是一个指针常量:也就是说p值是不可修改的(即p不可以重新指向b这个变量了)。

2). 整个*p的前面没有const 的修饰。也就是说,*p是变量而不是常量,所以我们可以通过*p来修改它所指内存a的值。

总之一句话,这次的p是一个指向int变量类型数据的指针常量。
最后总结两句:
     1).如果const 修饰在*p前则不能改的是*p而不是指p
     2).如果const 是直接写在p前则p不能改。

4.补充三种情况。

这里,我再补充以下三种情况。其实只要上面的语义搞清楚了,这三种情况也就已经被包含了。不过作为三种具体的形式,我还是简单提一下吧!

情况一:int * pi指针指向const int n常量的情况

const int n1=40;
int *pi;
pi=&n1;//这样可以吗?不行,VC下是编译错。const int 类型的n1的地址是不能赋值给指向int类型地址的指针pi的。否则pi岂不是能修改n1的值了吗!
pi=(int* ) &n1;  // 这样可以吗?强制类型转换可是C所支持的。 VC下编译通过,但是仍不能通过*pi=80来修改n1的值。去试试吧!看看具体的怎样。

情况二:const int * pi指针指向const int n1的情况

const int n1=40;
const int * pi;
pi=&n1;//两个类型相同,可以这样赋值。n1的值无论是通过pi还是n1都不能修改的。

情况三:用const int * const pi申明的指针

int n;
const int * const pi=&n;
//你能想象pi能够作什么操作吗?pi值不能改,也不能通过pi修改n的值。因为不管是
//*pi还是pi都是const的。

5. 常量引用

int a = 10;
int& p1 = a;//正确
int& p2 = 2;//错误,需要引用左值
const int& p3 = a;//正确
const int& p4 = 4;//正确

关于引用的初始化有两点值得注意:

(1)当初始化值是一个左值(可以取得地址)时,没有任何问题,可以用常量引用也可以用非常量引用, 如p1,p3;
(2)当初始化值不是一个左值时,则只能对一个const T&(常量引用)赋值。如p2,p3。而且这个赋值是有一个过程的:

首先将值隐式转换到类型T,然后将这个转换结果存放在一个临时对象里,最后用这个临时对象来初始化这个引用变量。

如果是对一个常量进行引用,则编译器首先建立一个临时变量,然后将该常量的值置入临时变量中,对该引用的操作就是对该临时变量的操作。对常量的引用可以用其它任何引用来初始化;但不能改变。

6. 常量函数、常量引用参数、常量引用返回值

例1:bool verifyObjectCorrectness(const myObj &obj); //const reference parameter
例2:void Add(const int &arg) const; //const function
例3:IStack const & GetStack() const { return _stack; } //return const reference

  6.1 常量函数
  1. 一个函数通过在其后面加关键字const,它将被声明为常量函数
  2. 在C++,只有将成员函数声明为常量函数才有意义。带有const作后缀的常量成员函数又被称为视察者(inspector),没
         有const作后缀的非常量成员函数被称为变异者(mutator)
  3. 与const有关的错误总是在编译时发现
  4. [摘]If the function is not declared const, in can not be applied to a const object, and the compiler will 
         give an error message. A const function can be applied to a non-const object
  5. 在C++中,一个对象的所有方法都接收一个指向对象本身的隐含的this指针;常量方法则获取了一个隐含的常量this指针
      void func_name() const;
      以上说明函数func_name()不会改变*this。当你把this指针看成函数func_name()的一个不可见参数就理解了
      void func_name(T *this) (no const)
      void func_name(const T *this) (const)
  6. 常量函数可以被任何对象调用,而非常量函数则只能被非常量对象调用,不能被常量对象调用,如:

class Fred {
 public:
  void inspect() const;  // This member promises NOT to change *this
  void mutate();     // This member function might change *this
 };

 void userCode(Fred& changeable, const Fred& unchangeable)
 {
  changeable.inspect();  // OK: doesn't change a changeable object
  changeable.mutate();  // OK: changes a changeable object

  unchangeable.inspect(); // OK: doesn't change an unchangeable object
  unchangeable.mutate(); // ERROR: attempt to change unchangeable object
 }

7. 在类中允许存在同名的常量函数和非常量函数,编译器根据调用该函数的对象选择合适的函数
当非常量对象调用该函数时,先调用非常量函数;
当常量对象调用该函数时,只能调用常量函数;
如果在类中只有常量函数而没有与其同名的非常量函数,则非常量与常量对象都可调用该常量函数;如:

#include <iostream>
using namespace std;

struct A
{
  void f() const { cout<<"const function f is called"<<endl; }
  void f() { cout<<"non-const function f is called"<<endl; }
  void g() { cout<<"non-const function g is called"<<endl; }
};

void main()
{
  A a;
  const A& ref_a = a;
  a.f(); //calls void f(),输出:non-const function f is called<br>  ref_a.f();//calls void f () const, 输出:const function f is called

 a.g(); //ok, normal call <br>  ref_a.g(); //error, const object can not call non-const function
}

8. const关键字不能用在构造函数与析构函数中。因为构造函数的目的是初始化域值,因此它必须更改对象,析构函数同理

  6.2 常量引用参数

  本例中,一个myObj类型的对象obj通过引用传入函数verifyObjectCorrectness。为安全起见,使用了const关键字来确保函数verifyObjectCorrectness不会改变对象obj所引用的对象的状态。此外,通过声明参数常量,函数的使用者可以确保他们的对象 不会被改变,也不必担心在调用函数时带来副作用。以下代码试图对声明为常量引用的形参进行修改,从而不会通过编译!

#include <iostream>
using namespace std;

class Test
{
public:
  void f(const int& arg);
private:
  int value;
};

void Test::f(const int& arg) {
  arg=10; //试图修改arg的值,此行将引起编译器错误 //error C2166: l-value specifies const object
  cout<<"arg="<<arg<<endl;
  value=20;
} 

void main()
{
 int i=7;
 Test test;
 test.f(i);
 cout<<"i="<<i<<endl;
}

6.3 常量引用返回值

  如果你想从常量方法(函数)中通过引用返回this对象的一个成员, 你应该使用常量引用来返回它,即const X&
  也就是说你想通过引用返回的东西如果从逻辑上来讲是this对象的一部分(与它是否在物理上嵌入在this对象中无关),那么常量方法需要通过常量引用或者通过值来返回,而不能通过非常量引用返回

class Person {
public:
  const string& name_good() const; // Right: the caller can't change the name
  string& name_evil() const;    // Wrong: the caller can change the name
  .
};

void myCode(const Person& p) // You're promising not to change the Person object
{
  p.name_evil() = "Igor"; // but you changed it anyway!!
}
(0)

相关推荐

  • 简单总结C++中指针常量与常量指针的区别

    我们先回顾下,什么是指针?什么是常量?指针是一种特殊的变量,它里面存储的内容是内存地址.常量是指其里面存储的内容不能发生改变的量.明白了这两个概念后,我们现在正式进入指针常量与常量指针. 1.指针常量与常量指针的概念 指针常量就是指针本身是常量,换句话说,就是指针里面所存储的内容(内存地址)是常量,不能改变.但是,内存地址所对应的内容是可以通过指针改变的. 常量指针就是指向常量的指针,换句话说,就是指针指向的是常量,它指向的内容不能发生改变,不能通过指针来修改它指向的内容.但是,指针自身不是常量

  • C++编程中的数据类型和常量学习教程

    C++数据类型 计算机处理的对象是数据,而数据是以某种特定的形式存在的(例如整数.浮点数.字符等形式).不同的数据之间往往还存在某些联系(例如由若干个整数组成一个整数数组).数据结构指的是数据的组织形式.例如,数组就是一种数据结构.不同的计算机语言所允许使用的数据结构是不同的.处理同一类问题,如果数据结构不同,算法也会不同.例如,对10个整数排序和对包含10个元素的整型数组排序的算法是不同的. C++的数据包括常量与变量,常量与变量都具有类型.由以上这些数据类型还可以构成更复杂的数据结构.例如利

  • 剖析C++中的常量表达式与省略号的相关作用

    C++ 常量表达式 常量值是指不会更改的值.C + + 提供了两个关键字,它们使你能够表达不打算修改对象的意图,还可让你实现该意图. C++ 需要常量表达式(计算结果为常量的表达式)以便声明: 数组边界 case 语句中的选择器 位域长度规范 枚举初始值设定项 常量表达式中合法的唯一操作数是: 文本 枚举常量 声明为使用常量表达式初始化的常量的值 sizeof 表达式 必须将非整型常量(显式或隐式)转换为常量表达式中合法的整型.因此,以下代码是合法的: const double Size = 1

  • C++中的常量定义小结

    本篇笔记总结自一次代码检视. 一般来说,使用C语言编程时我们都习惯在代码当中使用C当中的宏定义来定义一个数值常量: #define MY_CONST 7 在C++开发项目时,也会经常存在沿袭C当中常量用法情况.所以,如下的一种写法看起来反倒觉得有些不正宗了: class MyClass { static const int my_const = 7; // ... }; 站在编译器的角度,这两种写法都是合法的,但在使用的时候总得二中择一,究竟哪种更合适呢?之前有所耳闻在C++中应该尽可能少的使用

  • C++类中的常量介绍

    由于#define 定义的宏常量是全局的,不能达到目的,于是想当然地觉得应该用 const 修饰数据成员来实现.const 数据成员的确是存在的,但其含义却不是我们所期望的.const 数据成员只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其 const 数据成员的值可以不同. 不能在类声明中初始化 const 数据成员.以下用法是错误的,因为类的对象未被创建时,编译器不知道 SIZE 的值是什么. 复制代码 代码如下: class A {  cons

  • 详解C++中常量的类型与定义

    常量是固定值,在程序执行期间不会改变.这些固定的值,又叫做字面量. 常量可以是任何的基本数据类型,可分为整型数字.浮点数字.字符.字符串和布尔值. 常量就像是常规的变量,只不过常量的值在定义后不能进行修改. 整数常量 整数常量可以是十进制.八进制或十六进制的常量.前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制. 整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long).后缀可以是大写,也可以是小

  • C++常量详解一(常量指针与常量引用的初始化)

    1.常量 1.1.常量的初始化: const对象一旦创建后其值就不能再改变,所以const对象必须初始化.这里我们要注意一点,像const int *p和const int &r都并不是const对象.因为const int *p只是表示不能通过p改变p所指的对象的值,p的值是可以变的,所以p可以不用初始化.至于r ,引用本身就不是对象,所以r也并不是const对象,r之所以一定初始化,是因为引用必须初始化.对于以上内容,你也可以理解为底层const 修饰的并不是const对象,还要注意像con

  • C++常量详解二(常量形参,常量返回值,常量成员函数)

    1.常量形参 当形参有顶层const或者底层const的时候,传给它常量对象或者非常量对象都是可以的,在这里我们主要考虑形参无const, 而实参有const的情况,其实这里也十分简单,只需要记住一点:底层const的限制,就可以了. 2.常量返回值 这里也很简单,略过不提. 3.常量成员函数 常量函数里的const是用来修饰*this的,其形式如下: int f() const{```} 然后这里就有意思了 *this的默认类型是type *const this,this指针有一个顶层cons

  • C++ 常量成员常量返回值详解

    总结: 1.常量数据成员,形式:const Type m_tData; 1)常量数据成员,需要在构造函数列表中给出,构造函数中可以用常量赋值,也可以实例化的时候赋值. 2)赋值函数中不能赋值,起到保护常量数据成员的作用,和友元作用相反. 2.常量成员函数,形式:type funname(type1 arg1,type2 arg2,...) const 1)常量成员函数,不能修改类数据成员,不能调用非常量函数. 2)常量成员函数的作用,可以有效的将类的函数分为可以修改类的函数,和不能修改类的函数:

  • php 魔术常量详解及实例代码

    php 魔术常量详解 实例: class MoShu{ public function moshu() { echo '当前类名:' . __CLASS__ . "<br />"; echo '当前方法名:' . __FUNCTION__ . "<br />"; echo '当前文件中所在的行数:' . __LINE__ . "<br />"; echo '当前文件绝对路径:' . __FILE__ . &qu

  • java中变量和常量详解

    变量和常量 在程序中存在大量的数据来代表程序的状态,其中有些数据在程序的运行过程中值会发生改变,有些数据在程序运行过程中值不能发生改变,这些数据在程序中分别被叫做变量和常量. 在实际的程序中,可以根据数据在程序运行中是否发生改变,来选择应该是使用变量代表还是常量代表. 变量 变量代表程序的状态.程序通过改变变量的值来改变整个程序的状态,或者说得更大一些,也就是实现程序的功能逻辑. 为了方便的引用变量的值,在程序中需要为变量设定一个名称,这就是变量名.例如在2D游戏程序中,需要代表人物的位置,则需

  • 基于数据类型转换(装箱与拆箱)与常量详解

    隐式转换[自动类型转换]: 两种类型要兼容,原类型值域要小于目标类型值域,可以简单的理解为由小转大. public class Test { private void Start() { int a = 10; float b = a;//int 类型隐式转换为 float 类型 } } 显示转换[强制类型转换]: 两种类型要兼容,原类型值域要大于目标类型值域,可以简单的理解为由大转小. [缺点]:1.数据溢出.2.精度丢失. 数值类型之间的转换. public class Test { pri

  • JS ES5创建常量详解

    目录 前言 ES 5 创建常量 Object.defineProperty 的基础用法 创建常量 常量居然可以修改值? 兼容性 前言 ES6 刚推出的时候,let和const应该是大多数人学习ES6的第一个知识点. 其中const可以用来定义 常量 ,将不需要改变的数据定义成一个常量. 但其实在ES6之前我们也是有办法定义常量的. ES 5 创建常量 Object.defineProperty 的基础用法 在ES6之前是没有const的,如果需要定义常量,可以使用Object.definePro

  • Golang学习之无类型常量详解

    目录 什么是无类型常量 无类型常量的特性 默认的隐式类型 类型自动匹配 无类型常量带来的便利 无类型常量的坑 总结 因为虽然名字很陌生,但我们每天都在用,每天都有无数潜在的坑被埋下.包括我本人也犯过同样的错误,当时代码已经合并并发布了,当我意识到出了什么问题的时候为时已晚,最后不得不多了个合并请求留下了丢人的黑历史. 为什么我要提这种尘封往事呢,因为最近有朋友遇到了一样的问题,于是勾起了上面的那些“美好”回忆.于是我决定记录一下,一来备忘,二来帮大家避坑. 由于涉及各种隐私,朋友提问的代码没法放

  • c++常量详解

    概念 常量是存放固定且不可变值的,一旦确定初始值则在程序其它地方不可改变, 所以const对象必须初始化.常量一般使用const关键字来修饰. const 对象可以大致分为三类: 1. const int a const int a =10; int const b =10; 这两种格式是完全相同的.也就是说const 与int哪个写前都不影响语义.有了这个概念后,我们来看这两个家伙:const int * pi与int const * pi ,它们的语义有不同吗? 你只要记住一点,int 与c

  • C语言 常量详解及示例代码

    C 常量 常量是固定值,在程序执行期间不会改变.这些固定的值,又叫做字面量. 常量可以是任何的基本数据类型,比如整数常量.浮点常量.字符常量,或字符串字面值,也有枚举常量. 常量就像是常规的变量,只不过常量的值在定义后不能进行修改. 整数常量 整数常量可以是十进制.八进制或十六进制的常量.前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制. 整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long).

  • Kotlin如何捕获上下文中的变量与常量详解

    Lambda表达式或匿名函数可以访问或修改其所在上下文中的变量和常量,这个过程被称为捕获. fun main(args: Array<String>) { //定义一个函数,该函数的返回值类型为()->List<String> fun makeList(ele: String): () -> List<String> { //创建一个不包含任何元素的List var list: MutableList<String> = mutableListO

随机推荐