C语言 指针笔记

一、变量和内存空间

1. 语言中变量的实质

超市中的物品寄存箱,我们可以对寄存箱中进行存和取两个操作,为了避免存错
(箱子已存有物品、箱子不存在) 和取错 (箱子没有物品、箱子是他人的、箱子不存在) , 因此我们需要对箱子进行编号,以便正确的进行存取操作。

内存就是计算机存放和读取数据的地方,我们存放、读取数据,因此我们就需要知道数据所在的位置,故要对内存进行一个编号,也就是我们所说的内存编址。内存按照字节进行编址,每个字节都有对应的编号,我们称之为内存地址。
请看下图:

Paste_Image.png

我们继续看看以下的C、C++语言变量申明:

int i;
char c;

我们在使用变量之前都要事先申明它。这两条申明语句就是在内存中申请了一个名为i的整型宽度的空间(32bit下占4个字节),和一个名为c的字符型宽度的空间(32bit下占1个字节)。
内存中的映象可能如下图:

Paste_Image.png

图中可以看出,变量i在内存起始地址为++0x00AFB0++向后申请了4个字节,并命名为i。c从起始地址为 ++0x00AFAC++向后申请了1个字节,并命名为c。这样我们就拥有两个不同类型的变量了。

2. 赋值给变量

变量赋值:

i = 30;
c = 't';

这两条语句是将30存入变量i的内存空间中,将字符't'存入变量c的内存空间中。我们可以这样的形象理解:

Paste_Image.png
    (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一定是个多么特别的东西了。其实,它也只过是一个变量而已。与上一篇中说的变量并没有实质的区别。看下图:

Paste_Image.png

说明:在32位系统中,整型指针变量的宽度是4个字节。

由图中可以看出,我们使用int pi 申明指针变量, 其实是在内存的某处申明一个一定宽度的内存空间,并把它命名为pi。
你能在图中看出pi与前面的i,c变量有什么本质区别吗,没有,当然没有!pi也只不过是一个变量而已嘛!那么它又为什么会被称为指针?
关键是我们要让这个变量所存储的内容是什么*。现在我要让pi成为真正有意义上的指针。请看下面语句:

pi=&i;

整句的意思就是返回变量i的地址编号给pi,也就是在变量pi中保存变量i的地址编号。结果如下图所示:

Paste_Image.png

你看,执行完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的值了。

与前一个例子对照一下吧!看以下的两点分析:

  1. pi因为有了const 的修饰,所以只是一个指针常量。也就是说pi值是不可修改的(即pi不可以重新指向i2这个变量了)(看第4行)。

  2. 整个*pi的前面没有const的修饰。也就是说,*pi是变量而不是常量,所以我们可以通过 *pi来修改它所指内存i1的值(看5行的注释)。

我最后总结两句:

  1. 如果const 修饰在*pi前则不能改的是*pi(即不能类似这样:*pi=50;赋值)。但可以修改pi。
  2. 如果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的。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容

  • 1.语言中变量的实质 要理解C指针,我认为一定要理解C中“变量”的存储实质, 所以我就从“变量”这个东西开始讲起吧...
    金巴多阅读 1,740评论 0 9
  • 学号:16030140019 姓名:莫益彰 【嵌牛导读】初学C或C++的你,想必肯定被书上所讲的指针搞得头皮发麻,...
    换个名字消消毒阅读 476评论 0 0
  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,430评论 3 44
  • 目的: 通过以下学习,希望能理解指针的概念,理解指针和数组的关系,理解指针的定义,掌握指针的用法。 1. 简述 用...
    读书郞阅读 1,361评论 5 22
  • 世上有一种伤痛,叫白发人送黑发人。那种痛彻心扉的感觉无法用文字描述,如杨绛先生《我们仨》中她的女儿,“你曾经有个女...
    问山问水阅读 236评论 2 3