首页 > 代码库 > 二、常量
二、常量
目录
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常量进行调试,但是不能对宏常量进行调试。
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这个关键字。