c/c++ const的详解

来源:互联网 发布:发票查询真伪软件 编辑:程序博客网 时间:2024/06/05 11:21

C/C++语言里const是常见的限定符,有必要深刻理解const。在不同的地方const有不同的意思:

  • 修饰一个变量时表示对该变量只做一次赋值
  • 当作参数传入一个函数时,告诉这个函数这个参数是只读的
  • 当作函数的返回值时,告诉调用者返回的变量是只读的
  • 修饰一个函数时(C++),告诉编译器这个函数内部不会修改(*this)内容

 

因此,const是程序员和编译器之间的一个约定。这个约定带来的好处有:

  • 编译器会采取一些优化策略进行编译
  • 编译器进行语法检查,有助于减少代码错误
  • 增强了代码的可读性
  • 符合design by contract实践

 

[const变量的申明和定义]

1. C语言里申明一个const变量的时候不需要赋值,而定义的时候需要:

file1.c:

const int a = 5; // 定义

file2.c:

extern const int a; // 申明

// 下面可以使用a啦

 

如果a的作用域不是当前文件,那么编译器会给这个简单变量分配内存(因为其他文件可能用extern来申明并引用这个变量)。如果用static修饰这个const变量,编译器可能直接优化掉它,而并不分配内存。

 

2. C++的类成员变量使用const的方法:在class的申明里使用const限定,然后在构造函数初始化。比如:

c++ const class data member:

class A {

public:

  A();

private:

  const int a; // 这个是申明

};

 

A::A() : a(5) // 设置初值的方法,不可以在ctor内部使用赋值的方式初始化

{

}

 

3. C++里的全局const变量的申明和定义方法

因为C++里的const全局变量作用域是属于本文件的(internallinkage),类似于加了static的限定,外部文件是不能访问的:

file1.cpp

const float faa = 3.5; // 这个变量其他文件无法访问。编译器甚至可以不分配内存

 

如果需要跨文件使用,在申明和定义的时候都需要加extern:

file1.cpp

extern const float faa = 3.5; //这个变量其他文件可以访问

file2.cpp

extern const float; // 这个是申明,不要赋初值

 

用nm可以查看const变量符号:

0000000100000f90 s __ZL3faa // 没有加extern的情况下。有的时候甚至看不到这个符号,因为被编译优化掉了

0000000100000f94 S _faa // 加了extern的情况下

 

 

 

[const和指针]

有3种组合形式:

const T *p; // p指向的内存的内容是const,p可以指向新的地址

T * const p; // p是const,也就是不能改变指向的地址,但是指向的地址的内容可以改变

const T * const p; // p是const,*p也是const

 

记忆的小窍门:如果const在*左边,则*p受const限制,也就是p指向的内存是不能改的;如果const在*右边,则仅仅p受const限制,即p指向哪里是不可更改的。

 

int m = 6;

const int n = 5;

const int *p1 = &n;

int * const p2 = &m; // p2的内容不是const,所以不要指向n

const int * const p3 = &n;

 

 

[const和nonconst]

从nonconst得到const的指针或者引用总是OK,因为只是放弃修改的权利。如果从const得到nonconst的指针或者引用,相当于要求写的权利。当你并没有显式的提出来,谨慎的编译器会给一个warning,提醒你是不是笔误啦?

int n = 5;

const int *p1 = &n; // no problem

int *p2 = p1; // warning

 

如果你觉得不是笔误,进行显式的转化,编译器也就不会说什么了:

int *p2 = (int *) &n; // OK

或者

int *p2 = (int *) p1; // OK

 

[C++的const_cast]

在C++里,增加了特别的语法const_cast用于得到non const的引用或者指针:

void foo(int a)

{

   printf("a = %d\n", a);

}

 

int main()

{

   const int a = 5;

   int &b = const_cast<int &> (a);

   foo(a);

   return 0;

}

注意:不是改变原来的变量的const限定。语法:

outvar = const_cast<pointer or reference> (invar);

 

 

[const function]

在C++里,类的成员函数可以用const修饰:

class A {

public:

 int foo() const {

   printf("this is foo\n");

   a = 5; // 编译error:不可以改变this的成员变量

   return a;

 }

 

 int a;

};

 

这种const函数如果想改变某些成员变量,就需要把相关变量用mutable限定:

class A {

public:

 int foo() const {

   printf("this is foo\n");

   a = 5; // 允许

   return a;

 }

 

 mutable int a;

};

 

如果重载operator=,返回值不要用const限定(尽管语法正确),比如:

class A {

public:

    const A&operator=(const A& in) {… }

};

原因是,返回的引用无法修改内容了,下面这种用法就编译不了:

A a, b, c;

(a = b) = c;

 

[constexpr]

C++11扩展了const,以前这种写法无法编译:

const int count()

{

   return 5;

}

 

void bar()

{

   char buf[count()] = {'a'}; // error: variable-sized object may not beinitialized

}

 

用constexpr就可以编译过去了,因为编译器可以在编译时确定出count()的返回值

constexpr int count()

{

   return 5;

}

注意:

1. 很多编译器支持定义不定长数组(C99),比如:

void foo(int len) {

    int a[len];

    …

}

但是不允许给不定长的数组设置初始化数据。

2. C++11的编译方式:

例如:g++-std=c++11 main.cpp

 

 

 

 

[违反const约定]

1,通过指针修改const变量

编译器会检查const变量是否被改变:如果试图改变const变量的值,编译器会报错。但是如果程序员设置了指针指向该变量,仍然可以强行改变const变量的值,而编译器仅仅给个warning或者安静地pass过去了:

const int n = 5;

n = 6; // 编译error

int *p1 = &n; // 编译warning

int *p2 = (int *) &n; // OK

*p1 = 3; // OK

printf("n = %d\n", n); // 猜猜输出什么?

输出:

n = 5

可不是n= 3,意想不到吧?你欺骗了编译器,也要付出代价的!

内存里n确实被改成3了,但是编译器以为n是不变的,在printf语句里直接把n用5带入了,不会从内存里读取n的值。如果不是int这种简单类型,编译器不去优化这个变量的访问,那么这种通过指针进行修改const变量的方法还是有些用处的。

 

在有些情况下,通过指针的方式修改const变量不可行,比如:

const char *p = "a string";

这个字符串常量通常存放在代码段里,如果是在代码段存储的化,尝试修改会导致程序被kill。

 

 

2. const结构体的指针

一个结构体变量设置为const,其成员变量都是只读的。但是如果这个结构体包含成员指针变量,其指针指向的内容是不受保护的:

struct S {     int val;     int *ptr; };

void Foo(const S & s) {

  int= 42;

  s.val  = i; // Error: s is const, so val is aconst int

  s.ptr  = &i; // Error: s is const, so ptr is a const pointer to int

  *s.ptr = i;  // OK: the data pointed to by ptr is alwaysmutable,

                 //    even though this is sometimes not desirable

}

 

 


原创粉丝点击