管窥C指针

基本概念

什么是指针?
我们把某个变量的地址称为“指向该变量的指针”。我们在写程序的时候怎么获得该变量的地址呢?很简单,只需要在变量的前面添加一个取地址运算符&就可以了:

    int a = 10;
    printf("变量a在内存空间当中的存储地址是:%p\\n",&a);
运行结果

这样我们就能在控制台中打印输出变量a在内存空间中的地址,也就是指向变量a的指针了。
正如我们习惯于用一个变量来存储数值一样,我们也用“指针变量”来存储指针:

    int a = 10;
    int* p;
    p = &a;
    printf("     指向变量a的指针: %p\\n",&a);
    printf("指针变量p中存储的指针: %p\\n", p);

运行结果

当指针变量p当中存储了变量a的地址(指向a的指针)后,我们称指针变量p指向了变量a。
那么当我们拿到一个指针变量的时候,怎么才能获得其指向的内容,也就是变量a呢,很简单,我们只需要在指针变量前添加一个运算符"*"即可:

    int a = 10;
    int* p;
    p = &a;
    printf("     指向变量a的指针: %p\\n",&a);
    printf("指针变量p中存储的指针: %p\\n", p);
    printf("             变量a: %d\\n",a);
    printf("指针变量p指向的内容是: %d\\n", *p);

运行结果

这里我们需要注意的是,用于取得指针变量所指向的内容的运算符""与声明指针变量时用的符号""虽然一样,但意义完全不同。声明指针变量时使用"*"只是用来说明声明的这个变量是一个指针变量。
指针变量,它也是一个变量,所以它自己也是有地址的,换句话说,指针变量也有指向它的指针变量。

    int a = 10;
    int* p;
    p = &a;
    printf("          指向变量a的指针: %p\\n",&a);
    printf("     指针变量p中存储的指针: %p\\n", p);
    printf("                  变量a: %d\\n",a);
    printf("     指针变量p指向的内容是: %d\\n", *p);
    printf("       指向指针变量p的指针:%p\\n",&p);
    printf("指向指针变量p的指针所指内容:%p\\n",*(&p));
    printf("被指针所指指针变量p所指内容:%d\\n",*(*(&p)));
运行结果

有点晕,但是尝试着写一下的话,也就不会晕了。

指针变量的自增运算

指针变量里面存储的是变量的地址,笔者前面的文章写到过,地址使用数字来标识的,就像门牌号一样。那么,作为数字,又是变量,也就说明能够进行运算了,这里只讨论自增运算,目的只有一个:指针变量的运算并不只是单纯的数字运算。明白了这一点后,其他的运算也就能举一反三了。

    int a[3] = {1,2,3};
    int* p = &a[0];
    printf(" p中存储的指针是:%d\\n",p);
    printf("指针p所指向的内容是:%d\\n",*p);
    printf("自增后p中的指针是:%d\\n",++p);
    printf("自增后p指向的内容是:%d\\n", *p);

运行结果

我们可以直观地看到,指针变量的值增加了4,而不是1,所以指针变量指向了这一片内存地址中所存储的下一个整数,而笔者前面的文章页写过整型变量占有四个字节,也就是四个内存地址。

    char a[3] = {'a','b','c'};
    char* p = &a[0];
    printf(" p中存储的指针是:%d\\n",p);
    printf("指针p所指向的内容是:%c\\n",*p);
    printf("自增后p中的指针是:%d\\n",++p);
    printf("自增后p指向的内容是:%c\\n", *p);

运行结果

而在这个例子中,指针变量指向的是char类型的变量,所以在增加一的时候确实只增加一,因为char类型的变量所占用的内存空间是一个字节。
由此,我们得出结论,指针变量在进行运算的时候值的改变根据其类型而有所不同。

指针与数组

先看一个例子:

    int a[3] = {1,2,3,4};
    printf("%p\\n",a);
    printf("%p\\n",&a[0]);
    printf("%p\\n",&a);
    printf("%d\\n",a[0]);
    printf("%d\\n",*a);

运行结果

我们发现,数组的名字就是数组的第一个元素的地址,换句话说,数组名是指向数组第一个元素的指针。
但是,我们还发现了一个问题,那就是a和&a打印出来的东西是相同的,真的是相同的吗?我们可以用代码来验证一下:

    int a[3] = {1,2,3};
    printf("%d\\n",a);
    printf("%d\\n",&a);
    printf("%d\\n",a+1);
    printf("%d\\n",(&a)+1);
运行结果

我们发现,在给a,加一的时候,a指向了数组第二个元素的地址,而在给&a加一的时候,&a指向了存储在数组之外的东西,也就是成了数组之外元素的地址。由此我们得到结论:a是指向数组首元素的指针,而&a则是指向整个数组的指针。
那么二维数组呢?依此类推,用相似的方法写出代码即可验证。

指针变量作函数的参数

先来看一个经典的例子:

    void exchange(int a, int b){
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int a = 10;
    int b = 20;
    exchange(a, b);
    printf("a的值是:%d, b的值是:%d\\n", a, b);  
}

运行结果

我们发现a与b的值并没有发生交换。因为函数在传参数的时候,传入的是变量的副本,然后你在函数中不论对副本作什么改变,都不会影响到其本身的值。
那如果传入指向变量的指针呢:

void exchange(int* a, int* b){
    int temp = *a;
    *a = *b;
    *b = temp;
}
int main() {
    int a = 10;
    int b = 20;
    exchange(&a, &b);
    printf("a的值是:%d, b的值是:%d\\n", a, b);   
}

运行结果

可以看到,a,b的值相互交换了。为什么呢?因为传入的是指针的副本,所以副本依然是分别指向这两个变量的指针,所以在通过运算符来获取这两个变量的值的时候,获取到的就是原来的那两个变量,当用运算符获得原来的变量再通过赋值运算符来给两变量赋值的时候,改变的就是原来的变量的值。

const关键字
    int a = 10;
    int b = 20;
    const int *p;
    p = &a;
    *p = 20;

我们会发现,在给*p赋值的时候编译器会报错

错误提示

因为const int *意为定义一个指向整型常量的指针,而int const *与其意义相同,不过这种写法不被推荐。而int * const的意义则是定义一个指向整型的指针常量:

    int a = 10;
    int * const p;
    p = &a;
错误提示

既然是常量,在定义的时候就应该被赋值,然后在接下来的程序中就不能再改变指针常量中所存储的指针的值,但是指针常量所指向的整型变量的值还是可以被改变的

    int a = 10;
    int b = 20;
    int * const p = &a;
    *p = b;
    printf("a的值为: %d\\n", a);
    
运行结果

如果再加一个const呢?

    int a = 10;
    int b = 20;
    const int * const p = &a;
    *p = b;

编译器会再度报错:


错误提示

由此我们验证了const int * const的含义:一个指向整型常量的指针常量
以上就是对C语言指针的一个基本的了解,光看别人写出来的总结是不够的,如果我们想要真正的理解并且达到完全不会被绕晕的状态,还是需要多多实践,多写多用。

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

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,430评论 3 44
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,513评论 1 51
  • C语言指针的总结 1. 变量 不同类型的变量在内存中占据不同的字节空间。 内存中存储数据的最小基本单位是字节,每一...
    xx_cc阅读 3,714评论 11 39
  • 历经了黑色六月的战场拼杀,在人生的十字路口徘徊了许久,终于在2016年9月1日,我推开了英才的大门,面对眼前的一切...
    傲娇女王ing阅读 713评论 2 6
  • 月光男孩拿到这届奥斯卡最佳影片奖,也在情理之中,因为,它在各大颁奖典礼和影评榜单上的口碑好到一发不可收拾。 譬如,...
    祎七阅读 488评论 1 7