一、变量和内存空间
1. 语言中变量的实质
超市中的物品寄存箱,我们可以对寄存箱中进行存和取两个操作,为了避免存错
(箱子已存有物品、箱子不存在) 和取错 (箱子没有物品、箱子是他人的、箱子不存在) , 因此我们需要对箱子进行编号,以便正确的进行存取操作。
内存就是计算机存放和读取数据的地方,我们存放、读取数据,因此我们就需要知道数据所在的位置,故要对内存进行一个编号,也就是我们所说的内存编址。内存按照字节进行编址,每个字节都有对应的编号,我们称之为内存地址。
请看下图:
我们继续看看以下的C、C++语言变量申明:
int i;
char c;
我们在使用变量之前都要事先申明它。这两条申明语句就是在内存中申请了一个名为i的整型宽度的空间(32bit下占4个字节),和一个名为c的字符型宽度的空间(32bit下占1个字节)。
内存中的映象可能如下图:
图中可以看出,变量i在内存起始地址为++0x00AFB0++向后申请了4个字节,并命名为i。c从起始地址为 ++0x00AFAC++向后申请了1个字节,并命名为c。这样我们就拥有两个不同类型的变量了。
2. 赋值给变量
变量赋值:
i = 30;
c = 't';
这两条语句是将30存入变量i的内存空间中,将字符't'存入变量c的内存空间中。我们可以这样的形象理解:
(30)_{10}=(0x0000001E)_{16}
't'=(116)_{10}=(0x00000074)_{16}
3. 变量在哪里?(即我想知道变量的地址)
好了,接下来我们来看看&i是什么意思?是取变量i所在的地址编号嘛!我们可以这样读
&i:返回变量i的地址编号。
我要在屏幕上显示变量i的地址值的话,可以写如下代码:
printf("%d",&i);
以上图的内存映象所例,屏幕上 显示的不是i值30,而是显示i的内存地址编号0x00AFB0。
最后总结代码如下:
int main()
{
int i=39;
printf("%d\n",i); //变量i的值 30
printf("%d\n",&i); //变量i的地址 0x00AFB0
}
二、指针是什么东西
想说弄懂你不容易啊!我们许多初学指针的人都要这样的感慨。其实生活中处处都有指针。我们也处处在使用它。有了它我们的生活才更加方便了。没有指针,那生活才不方便。
那么我们C,C++ 中的指针又是什么呢?下面看一个申明整型指针变量的语句:
int *pi;
pi是一个指针,这我们当然知道啦,但是这样说,你就以为pi一定是个多么特别的东西了。其实,它也只过是一个变量而已。与上一篇中说的变量并没有实质的区别。看下图:
说明:在32位系统中,整型指针变量的宽度是4个字节。
由图中可以看出,我们使用int pi 申明指针变量, 其实是在内存的某处申明一个一定宽度的内存空间,并把它命名为pi。
你能在图中看出pi与前面的i,c变量有什么本质区别吗,没有,当然没有!pi也只不过是一个变量而已嘛!那么它又为什么会被称为指针?关键是我们要让这个变量所存储的内容是什么*。现在我要让pi成为真正有意义上的指针。请看下面语句:
pi=&i;
整句的意思就是返回变量i的地址编号给pi,也就是在变量pi中保存变量i的地址编号。结果如下图所示:
你看,执行完pi=&i; 后,在图中,pi的值是 0x00AFB0
.这 个 0x00AFB0
就是变量i的地址编号,这样pi就指向了变量i了。
因此,我们就把pi称为指针。所以你要记住,指针变量所存的内容就是内存的地址编号。好了,现在我们就可以通过这个指针pi来访问到i这个变量了,不是吗?。看下面语句:
printf("%d",*pi);
那么 *pi什么意 思呢?你只要这样读*pi:访问pi保存的地址上的变量。指针pi的内容是++0x00AFB0++,也就是说pi指向内存编号为++0x00AFB0++的空间。*pi嘛!就是它所指地址的内容,当然就是30的值了。
所以这条语句会在屏幕上显示30。也就是说printf(“%d”,*pi);语句等价于printf ( “%d”, i )。
总结:
- 操作符&:获得变量的地址,它的操作数必须是变量。
- 操作符*:取某地址里的内容
char a;
char *pa; //定义char类型的指针。
a = 10;
pa = &a; //返回变量a的地址给指针pa。
*pa = 20; //访问指针pa保存的地址上的变量,并赋值为20。
printf("%d", a);
三、指针和数组
1. 遍历数组有以下几种方式:
//方法一:
int i;
int a[] = {1,2,3,4,5,6,7,8,9,10};
for (i=0; i<=9; i++)
{
printf ("%d\n", a[i]); //注意 a[i]
}
//方法二
int i;
int a[] = {1,2,3,4,5,6,7,8,9,10};
for (i=0; i<=9; i++)
{
printf ("%d", *(a+i) ); //注意 *(a+i)
}
//方法三
int i;
int *pa;
int a[] = {1,2,3,4,5,6,7,8,9,10};
pa = a; //注意 数组名a直接赋值给指针 pa
for (i=0; i<=9; i++)
{
printf("%d", pa[i]);
}
//方法四
int i;
int *pa;
int a[] = {1,2,3,4,5,6,7,8,9,10};
pa = a;
for (i=0; i<=9; i++)
{
printf ("%d", *(pa+i));
}
看pa=a,即数组名赋值给指针,以及通过数组名、指针对元素的访问形式看,它们并没有什么区别,从这里可以看出数组名其实也就是指针。难道它们没有任何区别?有,请继续。
2. 数组名与指针变量的区别
#include <stdio.h>
int main()
{
int i;
int *pa;
int a[] = {1,2,3,4,5,6,7,8,9,10};
pa = a;
for (i=0;i<=9;i++)
{
printf("%d\n",*pa);
pa++; //注意这里,指针值被修改
}
}
可以看出,这段代码也是将数组各元素值输出。不过,你把 ++pa++;++
中的pa改成a试试。你会发现程序编译出错,不能成功。
看来指针和数组名还是有不同的。其实上面的指针pa是指针变量,而数组名a是一个指针常量。这个代码与上面的代码不同的是,指针pa在整个循环中,其值是不断递增的,即指针值被修改了 。数组名是指针常量,其值是不能修改的,因此不能类似这样操作:++a++++。
3. 申明指针常量
/*申明指针常量*/
int i;
int a[] = {1,2,3,4,5,6,7,8,9,10};
//注意 const的位置:
//const修饰的是 指针pa,即pa为常量
//而const int * pa,const修饰的是 *pa,即*pa为常量
int * const pa=a; //申明指针常量
for (i=0;i<=9;i++)
{
printf ("%d", *pa);
pa++ ; //注意这里,指针值被修改
}
这时候的代码能成功编译吗?不能。错误如下:
[Error] increment of read-only variable 'pa'
因为pa指针被定义为常量指针了。这时与数组名a已经没有不同。这更加说明了数组名就是常量指针。
四、const关键字
1. int i 说起
你知道我们申明一个变量时象这样int i ;这个i是可能在它处重新变赋值的。 如下:
int i=0;
//…
i = 20;//这里重新赋值了
不过有一天我的程序可能需要这样一个变量(暂且称它变量),在申明时就赋一个初始值。之后我的程序在其它任何处都 不会再去重新对它赋值。那我又应该怎么办呢?用const 。
const int ic =20;
//…
ic=40;
//这样是不可以的,编译时是无法通过,因为我们不能对 const 修饰的ic重新赋值的。
//这样我们的程序就会更早更容易发现问题了。
有了const修饰的ic 我们不称它为变量,而称符号常量,代表着20这 个数。这就是const 的作用。ic是不能在它处重新赋新值了。
认识了const 作用之后,另外,我们还要知道格式的写法。有两种:
const int ic=20;
int const ic=20;
它们是完全相同的。这一 点我们是要清楚。总之,你务必要记住const 与int哪个写前都不影响语义。有了这个概念后,我们来看 这两个家伙:
const int * pi;
int const * pi;
现在看,它们的语义有不同吗?没有,你只要记住一点,int 与const 哪个放前哪个放后都是一样的。
2. int const * pi
前面说过
int * const pa; //用来声明指针常量
那
const int * pi;
的作用是什么呢?看下面例子:
int i1=30;
int i2=40;
const int * pi=&i1;
pi = &i2; //4.注意 pi为指针变量,可以重新赋值一个新内存地址。
i2 = 80; //5.想想看:这里能用*pi=80;来代替吗?当然不能
//*pi=80 编译出错:[Error] assignment of read-only location '*pa'
printf( "%d", *pi); //6. 输出是80
语义分析:
可以看出,pi的值是可以被修改的。即它可以重新指向另一个地址的。但是,不能通过
*pi来修改i2的值。 首先 const 修饰的是整个*pi(注意,我写的是*pi而不是pi)。所以*pi是常量,是不能被赋值的(虽然pi所指的i2是变量,不是常量)。
其次,pi前并没有用const 修饰,所以pi是指针变量,能被赋值重新指向另一内存地址的。你可 能会疑问:那我又如何用const 来修饰pi呢?其实,你注意到int * const pi中const 的位置就大概可 以明白了。请记住,通过格式看语义。
3. int * const pi
确实,int * const pi与前面 的int const * pi会很容易给混淆的。
注意:前面一句的const 修饰的是pi,后面一句const 修饰的是 *pi。
int i1=30;
int i2=40;
int * const pi=&i1;
//pi=&i2; 4.注意这里,pi不能再这样重新赋值了,即不能再指向另一个新地址。
i1=80;
//5.想想看:这里能用*pi=80;来代替吗?可以,这 里可以通过*pi修改i1的值。
//请自行与前面一个例子比较。
printf( "% d", *pi) ; //6.输出是80
语义分析:
看了这段代码,你明白了什么?有没有发现 pi值是不能重新赋值修改了。它只能永远指向初始化时的内存地址了。相反,这次你可以通过*pi来修改 i1的值了。
与前一个例子对照一下吧!看以下的两点分析:
pi因为有了const 的修饰,所以只是一个指针常量。也就是说pi值是不可修改的(即pi不可以重新指向i2这个变量了)(看第4行)。
整个*pi的前面没有const的修饰。也就是说,*pi是变量而不是常量,所以我们可以通过 *pi来修改它所指内存i1的值(看5行的注释)。
我最后总结两句:
- 如果const 修饰在*pi前则不能改的是*pi(即不能类似这样:*pi=50;赋值)。但可以修改pi。
- 如果const 是直接写在pi前则pi不能改(即不能类似 这样:pi=&i;赋值)。但可以修改*pi。
4. 补充三种情况。
情况一:int * pi指针指向const int i常量的情况
const int i1=40;
int *pi;
pi=&i1;
//这样可以吗?不行,VC下是编译错。
//const int 类型的i1的地址是不能赋值给指向int 类型地址的指针pi的。否则pi岂不是能修改i1的值了吗!
pi=(int* ) &i1;
// 这样可以吗?强制类型转换可是C所支持的。
//VC下编译通过,但是仍 不能通过*pi=80来修改i1的值。去试试吧!看看具体的怎样。
情况二:const int * pi指针指向const int i1的 情况
const int i1=40;
const int *pi;
pi=&i1;
//两个类型相同,可以这样赋值。很显然,i1的值无论是通过pi还是i1都不能修改的。
情况三:用const int * const pi申明 的指针
int i
const int* const pi=&i;
//你能想象pi能够作什么操作吗?pi值不能改,也不能通过pi修改i的值。
//因为不管是*pi还是pi都是const的。