深入讲解const

来源:互联网 发布:河南壁虎网络 编辑:程序博客网 时间:2024/05/01 11:52
const 修饰符可以把对象转换成常数对象,什么意思呢?就是说利用const进行修饰的变量的值在程序的任意位置将不能被修改,就如同常数一样。良好的程序,对const的利用频率是非常高的,它可以保证程序的安全性,同时也可以提高程序的可移植性。
1、 取代defineconst
#define D_INT 100
#define D_LONG 100.29
使用const
const int D_INT = 100;
const D_INT = 100;     //如果定义的int类型,可省略int.
const long D_LONG = 100.29;
const int& a = 100;
const替代define虽然增加分配空间,可它却保证了类型安全.在C标准中,const定义的数据相当于全局的,而C++中视声明的位置而定.
为什么说增加了空间,因为define预编译如上所有的D_INT都将用100替代,然后100将编译到代码里存放在代码区;而const产生了一个常量D_INT,在程序中编译的是D_INT只是它指向一个常数100,100同样也存在代码区,因此比define多需要一块空间。
2、const定义一般常量
       一般常量指简单类型的常量,此时const可以放在类型说明符前,也可以放在类型说明符后面。
       int const int_a = 100;
       const int int_a = 100;//都是对的
       const 定义一个常数组同样可以放在类型说明符前后都可以。应该在定义同时初始化数组,如:
       int const int_array[5] = {0,1,2,3,4};
3、const定义常对象
       常对象是指对象常量,定义格式如下:
       <类名> const <对象名>
       const <类型><对象名>
       const 定义的常对象同样会进行初始化,只是初始化之后,对象将不能再被更改。
4、const定义指针
const 定义指针,随着位置的不同,含义不同。const放在*左侧还是右侧是不同的定义,但是在*左边,在类型说明符左边还是右边是没有区别的。
Const char * prt_1 = stringprt1;    Char const * prt_1 = stringprt1;
两者意义相同,const 修饰的指针指向的变量,prt_1必须指向常量,即prt_1可以改变,但是它指向的变量不能改变。即:
prt_1 = stringprt2;// 合法
*prt_1 = “string”;// 非法
 
char * const prt_2 = stringprt1;
如果const在*右侧,则修饰的是指针指的方向,prt_2是常量指针,所以prt_2的指向将不能改变,但是可以改变指向的内容。
Prt_2 = stringprt2;// 非法
*prt_2 = “string”;// 合法
5、const定义引用
const 定义的引用叫常引用,即引用所指向的对象不能被更新。
格式如下:
const double & d;
常指针和常引用,常常用于函数形参,这样的参数可以称为常参数。这样做有两个好处,首先使用指针或者引用,将不会增加额外的空间,改善程序运行效率;其次,可以避免参数原值被更新。
int int_1 = 10;
void print(const int & i)
{
       cout << i << endl;
}
首先,如果都不用,直接用int i作为参数,则在调用的时候,程序会在内存中分配一块空间用来存储这个参数,存储的参数为原值int_1的一个拷贝,当程序运行结束,这个参数在内存中释放。此参数存在栈中。因此,会降低效率。
如果使用&,则参数是一个引用,这个时候函数print里的参数i相当于原值int_1的一个别名,虽然不会存在拷贝,提高了效率和空间利用率,但是会出现潜在威胁,比如,在print函数里对i的值进行修改后,会影响到int_1相应改变。
如果使用const &则避免了这种危险,在函数中将没有权限对i进行修改,而只能使用。
 
6、const对函数的修饰
A、修饰参数
       void Fun(const A* in)或void Fun(const A& in)
       这种情况上面也有提到,这样修饰参数的好处是可以在提高效率和空间利用率的同时避免在函数中意外修改调用的参数。注意,常参数函数调用某个变量并不会改变变量是否为常量的特性。
B、修饰返回值
       const A& Fun()或者const A* Fun()
       当返回的是引用或者指针时,要避免将函数内的局部变量作为返回值,因为此变量将在函数运行结束后释放,引用或者指针就成为了野引用或者野指针,为编程大忌。
 
7、修饰类中操作或属性
A、常成员函数
       类中的函数叫操作,也叫成员函数,用const修饰的成员函数叫常成员函数,修饰格式如下:<返回值类型>函数名(<参数表>)const {}
       a.上面3提到的常对象只能访问常成员函数
       b.常成员函数不可修改对象的数据,不管对方是否常数,如果修改会编译出错。
       注意,常对象要修改成员有两种情况:常对象不可修改成员,但是如果类定义的时候,某个成员用mutable修饰符修饰了,则在任意情况下,包括常对象,也可以对此成员进行修改;或者使用const_cast转换运算符取消掉某常量的常量属性后修改。具体mutalbe,const_cast用法请查阅msdn或者google。
       常成员函数非常有用,可以增加安全性。虽然你可以说,函数是你编写的,你可以保证你写的函数不会修改对象,而只是使用,但是你要知道,你写的程序以后可能会给别人看,也有可能会有别人来修改你的代码,使用const修饰的常成员函数相当于明确的告诉别人,我的这个成员函数是不修改对象的,也告诉别人,这个函数不能随便修改对象否则可能会出现bug。同样道理,在需要用const的时候请尽量多用const。
B、常数据成员
使用const修饰的数据成员是常数据成员。常数据成员必须在初始化列表中初始化。
Class A
{
       Public:
       A(int i);
       Const int a;
       Const int b;
       Int c;
       Static int d;
}
A::A(int i):a(i),b(a)
{
       C = a+b;
}
Int A::d = 10;
       在例子中,常数据成员a,b只能使用初始化列表初始化,而c则可以使用别的方法。另外还要注意,static静态数据成员必须使用如上直接初始化,因为static不同于别的数据成员,别的数据成员每一个对象中都会有一个实例化的成员,但是类中的static成员为类的所有成员共用。
写一个类,这个类包含一个操作,此操作的返回值为类被实例化的次数。
成功后再修改一下,使类包含另一个操作,此操作的返回值为当前类存在的对象数。
 
 

任务:
1、最好把上面讲到的,一些比较不太理解的地方,都写一个程序试试看。
2、我写的并没有都验证过,所以可能会有错误,请自己写程序验证。我刚刚验证了第4条常指针,发现我原来写的错了,正好相反了,现在改过来了。所以自己写程序验证了才能了解细节。同时还意外发现了两个问题:第一个,如果你定一个const int i;然后用&i取它的地址,这个地址的类型是const int * ,所以,只能用const int * prt = &i;如果你用一个int * const prt = &i;就会出错,这是两个意外发现。希望你也能自己尝试用编程来验证并发现更多有趣的地方。
我的验证4程序(VC6):
#include <iostream>
using namespace std;
 
void main()
{
       int temp;
       const int i1 = 10;
       int i2 = 20;
       int i3 = 30;
       const int * ptr_1 = &i1;
       //int * const ptr_2 = &i1;//error
       int * const ptr_2 = &i2;
       ptr_1 = &i2;
       ptr_2 = 30;
       cout << *ptr_1 << " | " << *ptr_2 << endl;
       cin >> temp;
}
 
 
 
经过动手实验之后,如果你觉得自己已经懂了const了,再看下一页内容:

问题1:
    const int ival = 1024;
    const int *p = &ival;
    const int *&ref1 = &ival;//非法
    const int *&ref2 = p;//合法
    const int *const &ref3 = &ival;//合法
*&是啥意思?
为什么ref1会非法,而ref3却又合法呢?
引用到底是什么?
 
问题2:
既然const定义一个变量为常数,那么可不可以这样
Const int n = 5;
Char c[n];
请分析结果。
 
问题3:
typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;
请问,上面会有一个错误,错误在什么地方,为什么?
 
问题4:
char *p = "i'm hungry!";
p[0]= 'I'; 
这个代码会出现什么问题吗?为什么
 
问题5:
请问char a[3] = "abc" 合法吗?使用它有什么隐患?
 
问题6:
const char *pContent;
const (char *) pContent;
char* const pContent;
描述一下这三者的const情况
 
问题7:运行如下代码:
#include <iostream>
using namespace std;
 
void main()
{
       int temp;
 
       const int x = 4;
       int* y =(int*)(&x);
       (*y) = 9;
       cout<<y<<endl;
       cout<<&x<<endl;
       cout<<*y<<endl;
       cout<<x<<endl;
      
       cin >> temp;
}
 
你会发现y,和x地址一样,但是值却不一样,思考一下为什么?难道同一个地址可以存两个数吗?
 
 
 
 
请思考后很久,如果想不通,再看后面答案

答案1:
引用相当于是给变量起一个别名,另取一个变量名。
*&是对指针的引用,即给一个const int型指针起别名
因为引用是给变量起别名,而&ival是一个地址,而不是变量,这个地址保存的数是常数不能更新,而这个地址同样是不能更新的,如果const int *&ref1 = &ival;//非法可以编译通过的话,ref1将可以修改这个常数保存的地址了,这显然是不可能的。所以,除非在引用前再加一个const修饰符告诉编译器,我这个引用并不会修改地址,才能通过。
 
答案2:
在ansi C中,这种写法错误,因为在标准C语言中,const定义的是一个只读变量,虽然不能修改但是也还是变量,在内存中是占用空间的,而数据大小必须由常数来定义。
但是在VC中,这种写法正确。
 
答案3:
首先,想到一个问题,就是const变量同样存在栈中,而不是代码区。
然后这个问题中,错误出现在p2++;
const char m;这个式子中,m不可变,可推出
const char *pm;*pm不可变,但是pm则可变了,同理
const newType m;中,m又不可变了,因为pStr已经是一个新的数据类型了。
 
答案4:
不同编译器可能不同,在VC6中,编译可以通过,因为上面并没有显式const来表明p不能被修改,但是运行时会出错,因为,"i'm hungry!"其实是一个字符串常量,编译后保存在只读内存区。而p实际上是这一块字符串常量的头地址。所以如果试图修改这块只读内存区的内容就会出错。
 
答案5:
在标准C中这是合法的,但是它的生存环境非常狭小;它定义一个大小为3的数组,初始化为“abc”,,注意,它没有通常的字符串终止符'/0',因此这个数组只是看起来像C语言中的字符串,实质上却不是,因此所有对字符串进行处理的函数,比如strcpy、printf等,都不能够被使用在这个假字符串上。
 
答案6:
const char *pContent; //*pContent是const, pContent可变
const (char *) pContent;//pContent是const,*pContent可变
char* const pContent; //pContent是const,*pContent可变
因为(char *)已经作为一个整体了,相当于const type pContent,显然pContent是const,*pContent可变
 
答案7:
你可以这么试,你会发现cout<<*(&x)<<endl;此时值为4
cout<<*(int *)(&x)<<endl;此时,值为9
说明一个问题,首先,const int x是保存在一块只读内存区中的,因此,只要是x的值,都是4,这是不会变得,类似于define定义的宏替换一样。其次,当用(int *)修饰x的地址时,将在内存中另外开辟一块地址,也就是程序中显示的地址,这个地址下的值是可以改变的,程序中改变成了9,但是x的值依然没有变。
 
 
 
关于const的问题,不止这些,还有很多很多,如果有兴趣可以去google或者csdn中找,建议你在csdn中申请一个帐号,如果想了解有关编程的东西,或者有什么问题,那是一个绝好的地方。                                          
另外,还有一个任务:                                              
请写一个int型节点的静态链表,可是实现链表的创建、插入节点、删除节点、查询节点、删除链表等功能。