c++常量详解

所属分类: 软件编程 / C 语言 阅读数: 113
收藏 0 赞 0 分享

概念

常量是存放固定且不可变值的,一旦确定初始值则在程序其它地方不可改变, 所以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!!
}

更多精彩内容其他人还在看

用标准c++实现string与各种类型之间的转换

这个类在头文件中定义, < sstream>库定义了三种类:istringstream、ostringstream和stringstream,分别用来进行流的输入、输出和输入输出操作。另外,每个类都有一个对应的宽字符集版本
收藏 0 赞 0 分享

C++如何通过ostringstream实现任意类型转string

再使用整型转string的时候感觉有点棘手,因为itoa不是标准C里面的,而且即便是有itoa,其他类型转string不是很方便。后来去网上找了一下,发现有一个好方法
收藏 0 赞 0 分享

C/C++指针小结

要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区
收藏 0 赞 0 分享

C++ 类的静态成员深入解析

在C++中类的静态成员变量和静态成员函数是个容易出错的地方,本文先通过几个例子来总结静态成员变量和成员函数使用规则,再给出一个实例来加深印象
收藏 0 赞 0 分享

C++类的静态成员初始化详细讲解

通常静态数据成员在类声明中声明,在包含类方法的文件中初始化.初始化时使用作用域操作符来指出静态成员所属的类.但如果静态成员是整型或是枚举型const,则可以在类声明中初始化
收藏 0 赞 0 分享

C++类静态成员与类静态成员函数详解

静态成员不可在类体内进行赋值,因为它是被所有该类的对象所共享的。你在一个对象里给它赋值,其他对象里的该成员也会发生变化。为了避免混乱,所以不可在类体内进行赋值
收藏 0 赞 0 分享

C++中的friend友元函数详细解析

友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类。友元函数的特点是能够访问类中的私有成员的非成员函数。友元函数从语法上看,它与普通函数一样,即在定义上和调用上与普通函数一样
收藏 0 赞 0 分享

static全局变量与普通的全局变量的区别详细解析

以下是对static全局变量与普通的全局变量的区别进行了详细的分析介绍,需要的朋友可以过来参考下,希望对大家有所帮助
收藏 0 赞 0 分享

C++ explicit关键字的应用方法详细讲解

C++ explicit关键字用来修饰类的构造函数,表明该构造函数是显式的,既然有"显式"那么必然就有"隐式",那么什么是显示而什么又是隐式的呢?下面就让我们一起来看看这方面的知识吧
收藏 0 赞 0 分享

教你5分钟轻松搞定内存字节对齐

随便google一下,人家就可以跟你解释的,一大堆的道理,我们没怎么多时间,讨论为何要对齐.直入主题,怎么判断内存对齐规则,sizeof的结果怎么来的,请牢记以下3条原则
收藏 0 赞 0 分享
查看更多