理解C++ — 变量与常量(1)

toyasimple 2007-07-07 05:03:12
变量与常量(1)

程序运行时,所用的数据首先要被放在内存。内存有两个最基本的属性,一个是它的地址(编号),另一个就是它存储的数据。就如一堆小箱子,编号用来区分到底是用到哪个箱子,数值就如箱子里面放着的东西。

数据放在内存,我们给它一个名字,名字只不过是个符号,符号本身都是没有什么意义的,符号代表的东西才有意义。取了名字之后可以根据名字来方便取回我的数就行了。名字到最后都会影射到地址。可以说,名字是只是给人看的,那个人最可能是你自己,所以为了自己, 也为了别人幸福,请花点心思去取个好名字。

数据放在内存之后,可以分为变量与常量,常者,不变也;量者,数值也。前面已经说了,内存有地址和存储的数据两个最基本的属性,因此常量与变量当然也有两个最基本的属性了,一是它分配到的内存地址,另一个就是地址所指内存里面的数值。常量与变量就是从地址里面的数值可否变化来区分的。程序执行时数值可变为变量,不可变为常量。常量的数值在程序执行之前已经确定下来的了。当然变量与常量还有其它的要素,比如名字和类型。名字最终会影射到地址,类型可以决定它们的大小和行为。类型有其自身的意义。

从地址里面的数值可以区分变量与常量。那么从分配的内存地址来看呢,又可以将变量与常量分为 静态(static),动态(dynamic),自动(auto)三种不相同的状态。C++中,关键字const含义为不变(常), 关键字static含义为固定(静)。照我理解(注意,是我自己的理解),常和静,变和动,都是一个意思,指变化和不变化,只不过常和变是对于内存存储的数而言,静和动是对于内存地址而言。

好啦。现在看看静态,动态,和自动到底是什么含义,怎么去区分? 当程序刚启动,系统会分配些栈啊,程序控制块啊,各个段啊之类,这可以算一个准备阶段,在这个准备阶段,程序代码还没有正式执行,常量与常量所需的内存已经分配好了,地址已经确定下来,这就为静态。当程序代码已经执行,才去分配内存,内存地址还没有确定,为动态或者自动。要是代码正在执行,需要内存分配,这一个分配行为由系统全部完成,不用你去操心,就为自动;要是需要程序员自己决定分配的时机,显式调用malloc,new 之类的分配函数,就为动态。概括的说,程序执行之前已经分配好内存,决定好内存地址,为静态; 程序执行之时再分配,分两种情况,1)系统自动完成,为自动类型。2)需要显式调用分配函数,自己决定时机,为动态。

将属性关联到数据的过程,叫做绑定(binding)。如果在程序执行之前,变量与常量的属性已经确定了,就叫静态绑定;要是要等到程序执行之时,属性才被关联,被确定,就叫动态绑定。看看C++的书籍,静态,动态,绑定的概念会老是出现的, 到这里应该有大概的理解了,主要是以属性确立的时机来区分。其实不单是变量,常量,有时候调用什么函数(也就是函数的地址)也需要在程序执行之时才能确定。这时候可以先将函数地址先存起来,或者做成一个表。可能有人问,怎么函数地址也可以放起来的吗? 当然了,函数也需要内存来放,既然放在内存,为了找回它,就一定要得回它的位置,也就地址。对于计算机来说,所有东西都是101010之类的数值,什么都已经没有区别了。既然如此,函数,代码,浮点,对象,跟int之类的整型没有什么两样。int可以先放着,函数为什么不可以?

===================================================================
C++中,const是个很重要的关键字,施加了一种约束。有约束其实不是件坏事情,无穷的权利意味着无穷的灾难。应用了const之后,就不可以改变变量的数值了,要是一不小心改变了编译器就会报错,你就容易找到错误的地方。不要害怕编译器报错,正如不要害怕朋友指出你的缺点,编译器是写程序人的朋友,编译时期找到的错误越多,隐藏着的错误就会越少。所以,只要你觉得有不变的地方,就用const修饰,用得越多越好。比如你想求圆的周长,需要用到Pi, Pi不会变的,就加const,const double Pi = 3.1415926; 比如你需要在函数中传引用,只读,不会变的,前面加const; 比如函数有个返回值,返回值是个引用,只读,不会变的,前面加const; 比如类中有个private数据,外界要以函数方式读取,不会变的,加const, 这个时候就是加在函数定义末尾,加在末尾只不过是个语法问题。其实语法问题不用太过注重,语法只不过是末节,记不住了,翻翻书就可以了,接触多了,自然记得,主要是一些概念难以理解。你想想,const加在前面修饰函数返回值,这时候const不放在末尾就没有什么地方放了。

不过const修饰指针就需要注意一下了。要是修饰的类型不是指针,比如int之类,const放在int之前和int之后是一样的,比如
const int a = 2;
int const a = 2;
有着同样的效果。我自己偏向于第一种写法,其实想想,第二种写法更为合理,表示修饰变量a本身,所以a的值不可变。
当类型为指针时,以星号*为界, const加在左右两边,有不同的意思。
1) const int* pa = &a; (可以写成 int const* pa = &a; 注意是以星号为界)
2) int* const pa = &a;
写法1)表示pa所指向的变量,也就是a的值不可变。写法2)表示pa的指向,也就是pa本身的值不可以变,不可以现在指向a, 跟着指向b.
=======================================
int a = 2;
int b = 3;
const int* pt = &a;
//*pa = 1; Error
pa = &b; OK

=====================================
int a = 2;
int b = 3;
int* const pt = &a;
*pa = 1; OK
//pa = &b; Error

==================================
前面已经说过了,const用来指示内存中的数值不会变。指针本质上是一个地址(编号), 这个编号也需要放在内存。所以pa这个变量放在内存,数值是一个地址。当const在*右边,const直接修饰pa, 表示pa的数值不会变,所以也就不可以改变指向。当const在*左边,就修饰指向的变量,故*pa的值不能变。要是const int* const pa = &a; a的值不可变,pa的指向也不可变。请仔细想一想。这个问题困惑了我很久的了。

(注: 以上是个人观点,有理无理,回帖评论)
...全文
1996 40 打赏 收藏 转发到动态 举报
写回复
用AI写文章
40 条回复
切换为时间正序
请发表友善的回复…
发表回复
5t4rk 2011-08-12
  • 打赏
  • 举报
回复
来顶一下

楼主要出书吗
wang2007ling 2011-08-12
  • 打赏
  • 举报
回复
顶贴!学到了很多,谢谢楼主,希望多出好贴~~
xuejinn 2007-07-14
  • 打赏
  • 举报
回复
mark
bonny95 2007-07-14
  • 打赏
  • 举报
回复
本来是高手讨论的地,只是觉得楼上所有人的言论都很精彩,忍不住顶一下!
zjlufo 2007-07-11
  • 打赏
  • 举报
回复
一直以为这个问题很简单...日了
whycadi 2007-07-11
  • 打赏
  • 举报
回复
[quot]
loops(迷茫) ( ) 信誉:100 Blog 加为好友 2007-7-9 13:58:53 得分: 0



从机器的视野上来看,程序运行都要放入RAM,RAM的物理性质必然是可读可写的,所以常量、变量对于机器来说,没有意义。

从本质上说,常量、变量的差别就是在于内存分配方式的不同。可以预见的值,总是容易加以分配的。比如常量字符串"abc",作为编译器,知道它是常量,占4个大小字节大小,于是内存管理方式可以非常简洁,不用担心它会变大,也不用会担心它变小,因此无需采用堆或者栈之类的方式。

至于能不能修改,这是编译器的语法上保证的事情,与机器无关,当然可以设计操作系统予以保护的段,不停的对内存写操作加以检查,不过这样似乎将降低程序的效率。

说到底,内存只是内存,从0开始的一块线性的地址,可读可写,怎么使用和怎么解释,取决于人,当然,里面的学问就是一言难尽了。
[/quot]

不要一概而论。并不是所有的计算机系统代码都是在RAM中运行的,绝大部分的嵌入式系统,代码都是在不可改写的ROM中,此时弄清楚常量和变量是非常有必要的。即使在RAM中执行的计算机,比如X86,对代码段也有硬件级的写保护,正常情况下是可以认为无法改写的(可以采用黑客手段来改写,但是非常困难),对其的写操作会引起硬件异常。

就const来说,如果是声明的常值量,一般都不会分配内存空间,直接在指令代码中包含其值,比如
const b=1000000;
main()
{
a=b;
}
那么可以直接编译成 mov eax,1000000之类的代码,这种形式相当于#define
也有其它的,比如某些RISC指令不能直接加载32位常数,只能间接加载,那么以上的就会被编译成
dw 1000000 ;此处地址数值放1000000
……
……
;b=a的实现
ld r0,(存放值1000000位置的绝对地址 或相对地址 或一个包含其地址的指针寄存器,具体方式由CPU的不同而不同),这种方式下,你可以认为是给它分配了一个常量空间。

如果const指定的是编译时不能确定的值,那么只能作为一个变量在堆栈分配,一般全局变量和静态变量是在堆中分配,局部变量在栈中分配,这时的写限制就只是靠编译器来保证了,可以通过指针等方式来间接改变其值。

zhangyq73 2007-07-10
  • 打赏
  • 举报
回复
一般的const变量是不分配内存的,而且C++和C下的const还是有区别的。建议楼主好好看看“c++编程思想“(第二版)第一卷
wangwang1103 2007-07-09
  • 打赏
  • 举报
回复
mark
toyasimple 2007-07-09
  • 打赏
  • 举报
回复
回ff5dq4:
const给予一个约束,以免你不小心对数据作了修改而不知道。如果你用方法了修改const,这会比较麻烦,那就不是不小心,而是故意的了。那就代表你知道自己在做什么。C++相信你的判断。
  • 打赏
  • 举报
回复
学习!
toyasimple 2007-07-09
  • 打赏
  • 举报
回复
回jin123:
请先看看常量与变量(3)http://community.csdn.net/Expert/TopicView3.asp?id=5644522
可能对你有点帮助。还有什么,再回帖问.
jin123 2007-07-09
  • 打赏
  • 举报
回复
自己在以前看到的个例子,觉得不错留了下来

代码是在Release模式下debug的,因为DEBUG模式会对环境有依赖.

这个是源码:

#include<stdio.h>
#define SUCCESS 1
#define FAILURE 0
void main()
{
char *lpTest = "ABCD";
printf("The String is: %s n ",lpTest);
*(lpTest+1) = 'X';
printf("The String is: %s n ",lpTest);
}


发表于: 2005-01-15 11:35


在下新人,也想参与下讨论,哪里说的不对,请各位包涵并指出

因为学校的机器没有linux,所以我用了xp+vc6,文件名为test.c

代码是在Release模式下debug的,因为DEBUG模式会对环境有依赖.

这个是源码:

#include<stdio.h>
#define SUCCESS 1
#define FAILURE 0
void main()
{
char *lpTest = "ABCD";
printf("The String is: %s n ",lpTest);
*(lpTest+1) = 'X';
printf("The String is: %s n ",lpTest);
}


这是由vC6的Debug的汇编代码,其中带///////// 的为c代码,下面的是对应的汇编代码:

///void main() //这行自己加的,就从这里开始了
{
00401000 55 push ebp
00401001 8B EC mov ebp,esp
00401003 51 push ecx
6: char *lpTest = "ABCD"; ////////////////
00401004 C7 45 FC 30 70 40 00 mov dword ptr [lpTest],offset ___xt_z+8 (00407030)
7: printf("The String is: %s n ",lpTest);////////////////
0040100B 8B 45 FC mov eax,dword ptr [lpTest]
0040100E 50 push eax
0040100F 68 38 70 40 00 push offset ___xt_z+10h (00407038)
00401014 E8 1F 00 00 00 call _printf (00401038)
00401019 83 C4 08 add esp,8
8: *(lpTest+1) = 'X';////////////////
0040101C 8B 4D FC mov ecx,dword ptr [lpTest]
0040101F C6 41 01 58 mov byte ptr [ecx+1],58h
9: printf("The String is: %s n ",lpTest);////////////////
00401023 8B 55 FC mov edx,dword ptr [lpTest]
00401026 52 push edx
00401027 68 50 70 40 00 push offset ___xt_z+28h (00407050)
0040102C E8 07 00 00 00 call _printf (00401038)
00401031 83 C4 08 add esp,8
10: }
00401034 8B E5 mov esp,ebp
00401036 5D pop ebp
00401037 C3 ret

程序运行时,其实lpTest很明显是存放在堆栈里的(0x012ff7c) 从汇编上看到,lpTest开始时,push ecx,并将lpTest指向ecx寄存器的内容为地址的区域,在
mov dword ptr [lpTest],offset ___xt_z+8 (00407030)
后,lpTest才真正指向了定义的"ABCD".而这个"ABCD"的存放地址是0x00407030 . 可见赋值号右边的常量其实是开辟在一个单独的区域里,而且似乎所有的字符常量都是这样.在printf()里的"The String is: %s"也是存放在0x00407030之后的地址上,调用时push的.

这样,对于lpTest本身来说,它其实就是变量而已,可以改变它指向的内容是很正常的.(不正常C语言早浮云了^_^ ) 而对于赋值号右边的字符串常量,它们是单独存放在一个区域的,指针依靠地址来使用他们.

其实只要将 char *lpTest = "ABCD" 改为: const char *lpTest = "ABCD"
就不可以再改变它的数值了.而加上const后的汇编代码,和没有加上时其实是一样的,"ABCD"仍然放在0x00407030. 只是如果你加了const,还试图改变lpTest的数值,那么编译时就会失败,这个看起来是由编译器保证的.这个我不能肯定,编译原理还没学,说不清楚 (>_<)

那么如果是: const char Arry[]="WXYZ" 呢?

00401000 55 push ebp
00401001 8B EC mov ebp,esp
00401003 83 EC 0C sub esp,0Ch
8: const char Arry[]="WXYZ"; ////////////////
00401006 A1 30 70 40 00 mov eax,[___xt_z+8 (00407030)]
0040100B 89 45 F8 mov dword ptr [Arry],eax
0040100E 8A 0D 34 70 40 00 mov cl,byte ptr [___xt_z+0Ch (00407034)]
00401014 88 4D FC mov byte ptr [ebp-4],cl
.........................

其实还是一样,字符串常量都是开辟在0x00407030的,但是用了const,就会把这个地址的内容,拷贝到堆栈中去,然后利用
mov cl,byte ptr [___xt_z+0Ch (00407034)]
mov byte ptr [ebp-4],cl
来调整一下,使Arry的地址精确指向堆栈中的4个字节,这样就是数组字符串常量了

那再改改,写成这样:
const char Arry="WXYZ"; //bt写法,请勿模仿! ^^
嘿嘿~~编译有个警告,运行,就报错拉~~

00401000 55 push ebp
00401001 8B EC mov ebp,esp
00401003 83 EC 08 sub esp,8
8: const char Arry="WXYZ";/////////////////////////
00401006 B8 30 70 40 00 mov eax,offset ___xt_z+8 (00407030)
0040100B 88 45 FC mov byte ptr [Arry],al
............................

这样写语法是没有错误,但是再执行时,eax中存放的并不是来自0x00407030区域的内容,而是它的地址:
mov eax,offset ___xt_z+8 (00407030)
这样再调用printf时,就会出错了,因为 mov byte ptr [Arry],al 会把
0x00000030这样一个数值赋值给Arry,那么printf使用时,会引用这个地址做字符串首地址,那么自然就是非法了.这么低的地址能给你随便访问么~~
这样的话,你只要改下 printf语句,写成:printf("The String is: %d n ",Arry);或者是printf("The String is: %c n ",Arry); 就可以正常运行了.因为这时候Arry的内容不再做地址,而是做了数值,这样当然没有问题了

呼~呼,高手看了勿笑,菜鸟的一点小见解,有错之处,请斑竹和高手们多多包含

_________________


恩,请教下aero斑竹,如果是
char *lpTest = "ABCD" 的话,
应该怎么设置编译器参数,使得lpTest指向的内容不可修改呢?

常量,今天上来看到这么多常量的讨论......
其实不管是常量还是变量,必然有地址.包括printf("xxxxx") 这个"xxxxx"照样有地址,vc6下应该在0x00407030开始,往后的某个位置上, (取决于你程序中定义的位置了).只是你定义方式的不同,编译器处理起来就会不同,具体看我发的上个回复.

编译器除了语法检查外,看到的全是地址和数据.const是靠编译器的语法保证的.就象你在C++里取类私有函数的地址,就很困难,是因为编译器在C++编译方式下就不允许,而不是别的什么.

但是就算是const 修饰,也有办法,直接用指针取到地址就行,编译器会给你个类型匹配的警告,不过运行会正常. 用汇编也可以:

#include<stdio.h>
#define SUCCESS 1
#define FAILURE 0
void main()
{
const char Arry[]="WXYZ";

// const char *lpTest = "ABCD";

// printf("The String is: %s n ",lpTest);
//*(lpTest+1) = 'X';
printf("The String is: %s n ",Arry);
__asm
{
mov [Arry],31h
mov [Arry+1],31h
mov [Arry+2],31h
mov [Arry+3],31h
}

printf("The String is: %s n ",Arry);
}

//用int型也是一样

#include<stdio.h>
#define SUCCESS 1
#define FAILURE 0
void main()
{
// const char Arry[]="WXYZ";
const int Arry = 1;
// const char *lpTest = "ABCD";

// printf("The String is: %s n ",lpTest);
//*(lpTest+1) = 'X';
printf("The Number is: %d n ",Arry);
__asm
{
mov [Arry],99
// mov [Arry+1],31h
// mov [Arry+2],31h
// mov [Arry+3],31h
}

printf("The Number is: %d n ",Arry);
}


运行了看结果吧 xp+vc6通过.
chary8088 2007-07-09
  • 打赏
  • 举报
回复
up
我啃 2007-07-09
  • 打赏
  • 举报
回复
其实red_berries说得很对,可以90%情况下的CONST可以这样理解,
toyasimple理解还有些欠缺,可以用反汇编器看看反汇编的结果~
ff5dq4的理解更有问题,const是正规的常量定义,“提供修改const的方法”只是野方法不是提倡的,一般不用只是挑战本质时会用到的,“C++相信你的判断”很好~!

补充一下red_berries那10%就是常量在模版元编程里面的体现,具现阶段完全没有了变量的特性,完全就是一个编译期的信息

tmhlcwp 2007-07-09
  • 打赏
  • 举报
回复
还不错,期待下文中~
Jim@luckeeinc.com 2007-07-09
  • 打赏
  • 举报
回复
记号
xiongyan13 2007-07-09
  • 打赏
  • 举报
回复
mark
loops 2007-07-09
  • 打赏
  • 举报
回复
从机器的视野上来看,程序运行都要放入RAM,RAM的物理性质必然是可读可写的,所以常量、变量对于机器来说,没有意义。

从本质上说,常量、变量的差别就是在于内存分配方式的不同。可以预见的值,总是容易加以分配的。比如常量字符串"abc",作为编译器,知道它是常量,占4个大小字节大小,于是内存管理方式可以非常简洁,不用担心它会变大,也不用会担心它变小,因此无需采用堆或者栈之类的方式。

至于能不能修改,这是编译器的语法上保证的事情,与机器无关,当然可以设计操作系统予以保护的段,不停的对内存写操作加以检查,不过这样似乎将降低程序的效率。

说到底,内存只是内存,从0开始的一块线性的地址,可读可写,怎么使用和怎么解释,取决于人,当然,里面的学问就是一言难尽了。

freshui 2007-07-09
  • 打赏
  • 举报
回复
不错不错

:)
zhanan 2007-07-08
  • 打赏
  • 举报
回复
很不错的文章,写得很通俗很详细,非常适合我这个入门者。
加载更多回复(20)

64,701

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧