首页 > 代码库 > 二、常量

二、常量

目录

1、#define定义常量,好与坏

2、const关键字(各种const对象,指针,引用,函数,对应的引用等等)

常量就是在运行期间,值一直不变。c语言用#define定义,宏常量。C++里面用#define和const定义常量。

转:http://blog.csdn.net/love_gaohz/article/details/7567856

http://blog.sina.com.cn/s/blog_60be7ec80100gzhm.html

1、define定义常量

定义的是全局常量

define宏是在预处理阶段展开

没有类型,仅仅是展开,不做类型展开

仅仅是展开,多少个地方使用,就有多少个地方展开。(宏定义不分配内存,变量定义需要分配内存)

只是替换字符串,容易产生意想不到的错误(边际效益)

 

2、const常量

2.1、 const和define比较

  • 在编译阶段运行使用
  • 有具体的类型,在编译阶段要进行类型检查
  • const常量会在内存中分配内存,但是只是进行一次分配
  • 集成化的调试工具可以进行调试(这个不知道)
  • 可以定义局部常量,也可以定义全局常量(我觉得可以用static,这个有待后面的搜集学习)

转别人的:

(1) 编译器处理方式不同  define宏是在预处理阶段展开。  const常量是编译运行阶段使用。(2) 类型和安全检查不同  define宏没有类型,不做任何类型检查,仅仅是展开。  const常量有具体的类型,在编译阶段会执行类型检查。(3) 存储方式不同  define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。(宏定义不分配内存,变量定义分配内存。)  const常量会在内存中分配(可以是堆中也可以是栈中)。(4)const  可以节省空间,避免不必要的内存分配。 例如:          #define PI 3.14159 //常量宏          const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ......          double i=Pi; //此时为Pi分配内存,以后不再分配!          double I=PI; //编译期间进行宏替换,分配内存          double j=Pi; //没有内存分配          double J=PI; //再进行宏替换,又一次分配内存!          const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝(因为是全局的只读变量,存在静态区),而 #define定义的常量在内存中有若干个拷贝。 (5) 提高了效率。 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。(6) 宏替换只作替换,不做计算,不做表达式求解;       宏预编译时就替换了,程序运行时,并不分配内存。const 与 #define的比较    C++ 语言可以用const来定义常量,也可以用 #define来定义常量。但是前者比后者有更多的优点:(1)   const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误(边际效应)。(2)   有些集成化的调试工具可以对const常量进行调试,但是不能对宏常量进行调试。
View Code

 

2.2、 C++中尽量使用const,避免使用define

对外被使用的常量应放在头文件,内部使用的常量放在源文件的头部。

可以将常量都定义在一个文件里面便于管理

 

2.3、类中的const成员

const定义的常量在函数执行和对象销毁之后其空间会被释放。

对于类中的仅仅用const修饰的常量,相对于对象来说是不变的,但是对于类来说是变的。因为一个类可以创建很多对象,这样在进行构造函数初始化的时候,可以进行初始化。

所以在类声明const成员的时候,不能就直接给const常量进行赋值。

class Test{    public:        const int size = 10;//error,不能在类声明中给const常量赋值        int array[size];//size未知
}

在类中,如何进行const赋值,只能通过构造函数的初始化列表进行赋值,而且还必须是在初始化列表里面进行初始化。这样的话就可以对于每一个对象有不同的const常量。

class Test{    public:        const int size;        Test(int s):size(s){            ...            ...        }};

那如何才能做到对于整个类来说不变的常量,可以使用:枚举,static const

枚举:

class Test{    public:        Test():constValue(9){}        enum {size1 = 100, size2 = 200};//枚举    private:        const int constValue;//这个是const常量,只能在构造函数的初始化列表里面进行初始化。        static int staticValue;//这个是静态变量。整个类可以使用,只有一次初始化,可以改变值        const static int allValue;// static const is ok. 这个是静态常量};int Test::staticValue = http://www.mamicode.com/10;//不能在构造函数初始化列表初始化,不能在声明处初始化,因为不属于某个对象const int Test::allValue = http://www.mamicode.com/10;//给静态变量赋值的时候,不用加static,上面的staticValue也没有加

总结:const成员的初始化:

  • 在类中,只有const修饰的常量,只能在构造函数的初始化列表进行初始化,类对应每个对象的const的常量值可以不一样
  • 在类中,有const 和static组合修饰,不能在构造函数初始化列表初始化,因为不属于对象。要在类外进行初始化,同时要加上const,不用加static。
  • 在函数内,const修饰的变量必须在声明的时候就进行初始化,且该变量不再重新变。
  • const对象默认为文件的局部变量

 

2.4、const默认为文件的局部变量

const默认为文件的局部变量。

在全局作用域里定义非const变量,它在整个程序里面都可以进行访问。

//file_1.ccint counter; //定义//file_2.ccextern int counter; //可以通过extern用counter++counter;

但是对于在全局作用域声明的const变量是定义为该文件的局部变量。只存在于那个文件,不能被其他文件访问,如果希望其他文件能够访问,必须在前面加上extern,这样,在整个程序中,都可以访问这个const变量。

//file_1.cc//const必须在声明的时候进行初始化(函数和全局变量)extern const int bufSize = fcn();//file_2.ccextern const int bufSize;//可以在这个文件使用if (int index = 0; index != bufSize; ++ index )......
  • 这是因为非const变量默认为extern,但是const变量没有这个默认
  • 如果要const其他文件能够访问,要显示的指定为extern。当然,在其他地方使用的文件处,也需要加上extern,这个对于非const和const都是必须的。

 

2.5、 const引用

引用(reference)是对象的另外一个名字,在对引用进行各种操作,也是对原始对象的各种操作。引用主要用作函数的行参。

引用是一种复合类型:用其他类型定义的类型。就是不能直接用常量定义。一定要关联到其他的类型。

不能定义引用类型的引用,但是可以定义任何其他类型的引用。

引用必须用其他类型初始化。必须初始化。

引用一旦绑定到一个对象,就不能再绑定到其他对象。

int iValue = http://www.mamicode.com/10;

int &rValue = http://www.mamicode.com/iValue;//这个就是引用

const引用是指const对象的引用。

  • 既然是指向const对象的引用,const对象不能改变,其引用也不能够被改变。

   所以const对象的引用必须满足:1、若对象为const,则引用为const

   const int ival = 1024;

   const int &rval = ival;//这个引用就是指向const对象的引用,不能改变,因为rval与ival相关联,ival不能改变,其rval也不能改变,所以两个都必须为const。

  • 对象可以不为const,引用可以为const

    int i = 42;

    const int &r = 42;

    const int &r2 = r + i;

    或者

    double dval = 3.14;

    const int &ri = dval;

    因为编译器会把代码转换为以下形式:

    int temp = dval;

    const int &ri = temp;

    引用绑定到了ri上面。dval的改变不会影响到ri的值。

总结

非const引用只能绑定到与该引用同类型的对象。

const引用的赋值可以绑定到:1、相同类型的const,非const对象。2、相关类型的const、非const对象。3、绑定到右值

利用const引用避免了赋值。

书上的话:如果使用引用行参的唯一目的是避免复制,则应定义为const

 

2.6 const与指针

这个比较复杂,而且很难记;const和指针的关系有两个:指向const对象的指针,const指针

2.6.1 指向const对象的指针

 如果指针是指向const对象,则不允许用指针改变其所指的const值。

const double *cptr;//该为指向const对象的指针

const限定的是cptr指针所指的对象,并非cptr指针,cptr并不具有const特性,所以在定义的时候并不需要进行初始化。可以对cptr进行重新赋值,即可以指向其他对象。但是不能同构cptr修改所指向的内容。

*cptr = 42;//错误的,不能改变所指向对象的内容

++cptr;//这个是对的,因为该指针没有const特性。

  • 注意:::const的对象的地址只能赋值给指向const对象的指针。如果赋值给指向非const对象的指针会出错,因为这个指针可以改变所指向的内容。
const double pi = 3.14;double *ptr = π//错误,用指向非const对象的指针指向const对象const double *cptr = π//这个是正确的为指向const对象的指针指向了const对象

不能用void*保存const对象的指针,必须用const void *保存const对象的指针。

  • 可以把非const对象的地址赋值给指向const对象的指针。
double dval = 3.14;const double *cptr = &dval;

尽管dval不是const的,也不能通过cptr来改变dval的值,可以通过其他方式改变dval的值。

总结

  • 指向const对象的指针不管怎么样都不能改变自己所指向的对象。
  • 指向const对象的指针可以指向const对象,也可以指向非const对象。
  • 指向const对象的指针常用作函数的行参。确保传递给函数的实际对象在函数中不因为行参而被破坏
  • 所指向的基础对象不能改变,但是该指针可以改变,这个指针可以指向多个对象。

2.6.2 const指针

const指针:指针不能更改,但是指针所指向的对象的值可以更改(如果对象不是const,可以更改,如果是const,则不能更改)。即这个指针只能指向一个对象,可以通过这个指针改变对象的值。

int errNumb = 0;

int *const curErr = &errNumb;

++curErr;//出错,因为指针不能更改

*curErr = 1;//对的

 

2.6.3 这两个不好记:

先忽略类型名,看const离哪个近,就修饰谁;

const *p;//const修饰*p,p是指针,*p是指针指向的对象,不可变

*const p;//const修饰p,p不可变,p指向的对象可变。

const *const p;//前一个修饰*p,后一个修饰p

总结

  • 指向const对象的指针,指针可变,对象内容不可变,不管指向的对象是不是const,都不能通过指针来修改这个对象的内容
  • const指针,指针不可变,对象则根据对象的类型判断可变。

有一个需要注意:

string s;

typedef string *pstring;

const pstring cstr1 = &s;

pstring const cstr2 = &s;

string *const cstr3 = &s;

这三个都是一样的,都是:指向string类型对象的const指针

 

2.7 const参数,函数

const参数:对于参数在函数中不需要被改变,将参数定义为const是较好的选择,因为这样可以传递常量进来。

int strlen(const char* str);//这个就可以传递非const字符串,const字符串,以及"fjdajf"这种字符常量。如果没有const的话,则不能传递最后一种情况的字符串。前两种刚好验证了指向const对象的特性。

const函数:将函数定义为const,相当于修饰返回值,不可改变

int fun(void) const;

 

 

const差不多就这些吧。如果还有,以后再加。

 

写到这,突然觉得行参可以弄个专题:因为涉及到引用传递数组这个问题。还有volatile这个关键字。