Part 0:为什么要写这篇文章

C语言中的指针是C语言的精髓,也是C语言的重难点之一。

然而,很少有教程能把指针讲的初学者能听懂,还不会引起歧义。

本文章会尝试做到这一点,如有错误,请指出。

Part 1:地址和&

我们先抛开指针不谈,来讲一个小故事:

一天,小L准备去找小S玩。但是小L不知道小S的家住在哪里,正当他着急的时候,他看到了一个路牌,上面写着:小S的家在神仙小区403

哦,真的是要素过多。为什么这么说?

  1. 小L和小S:我们可以看做是两个变量/常量。
  2. 小S的家:这里可以看做是变量/常量小S的地址。

    我们要搞清楚,每个变量/常量都和我们一样:我们每个人都有自己的家,正如变量也有自己的地址。通俗的理解,地址是给变量/常量来存放值的地点
  3. 路牌:注意注意注意!这里就指出了变量/常量小S的地址:神仙小区403

    事实上,我们等会会讲,输出一个变量的地址其实是个16进制的数字。

搞懂了上面,我们再来聊聊&

&这个符号我们一个不陌生,你最初用到应该是在:scanf("%d",&a)里边。

&叫做取址符,用来获取一个变量/常量的地址。

那么我们为什么要在scanf里边用&,不在printf里边用呢?

一开始我也很疑惑,后来我看到了这个例子:

你是一个新生,你要进教室。

但是你并不知道教室在哪里,这个时候你需要教室的地址。

下课了,你要出教室。

由于你已经在教室里了,你就不需要获取教室的地址就可以出去了。


Part 2:一定要记住的东西

一定要记住:指针就是个变量!

重要的事情说三次:

指针就是个变量!他储存的是地址!他自己也有地址!

指针就是个变量!他储存的是地址!他自己也有地址!

指针就是个变量!他储存的是地址!他自己也有地址!

为什么这么说?我们从指针的定义开始:

指针的定义方法:<类型名+*> [名称]

也就是说,指针的定义大概是这样的:

int* ip;            //类型是int*,名称是ip
float* fp; //类型是float*,名称是fp
double* dp; //类型是double*,名称是dp

有的书上会这么写:

int *ip;
float *fp;
double *dp;

这么写当然没问题,但是对于初学者来说,有两个问题:

  1. 有的初学者会把*p当做是指针名
  2. 有的初学者会把定义时出现的*p取值时出现的*p弄混

指针他有没有值?有!我们会在下一节给他赋值。

既然他的定义方式和变量一样,他也有值,他为什么不是变量呢?


Part 3:与指针相关的几个符号

与指针相关的符号有两个,一个是&,一个是*

先来聊聊&

&我们上面讲过,他是来取地址的。举个例子:

#include <stdio.h>
int main(){
int a = 10;
float b = 10.3;
printf("%p,%p",&a,&b);
}

%p用来输出地址,当然,你也可以写成%d或者%x。先不管这个,我们来看看他会输出什么:



那么也就是说,变量ab的地址是000000000062FE1C000000000062FE18

那么我们怎么把这个地址给指针呢?很简单:p = &a;,举个例子:

#include <stdio.h>
int main(){
int a = 10;
int* p;
p = &a;
printf("a的地址:%p\n",&a);
printf("指针p自身的地址:%p\n",&p);
printf("指针p指向的地址:%p",p);
}

得到输出:

a的地址:000000000062FE1C

指针p自身的地址:000000000062FE10

指针p指向的地址:000000000062FE1C

你发现了吗?如果我们有p = &a;,我们发现:直接输出p会输出a的地址,输出&p会输出p的地址(这就是为什么我一再强调p是个变量,他有自己的地址,正如路牌上有地址,路牌自身也有个地址一样)。

请注意!如果你的指针为int*,那么你只能指向int类型;如果是double*类型,只能指向double类型,以此类推

当然,void*类型的指针可以转化为任何一种不同的指针类型(如int*,double*等等)

那么,我们来聊聊第二个符号*

*有两个用法。第一个在定义指针时用到,第二个则是取值,什么意思?看下面这个例子:

#include <stdio.h>
int main(){
int a = 10;
int* p;
p = &a;
printf("a的地址:%p\n",&a);
printf("指针p自身的地址:%p\n",&p);
printf("指针p指向的地址:%p\n",p);
printf("指针p指向的地址的值:%d",*p);
}

得到输出:

a的地址:000000000062FE1C

指针p自身的地址:000000000062FE10

指针p指向的地址:000000000062FE1C

指针p指向的地址的值:10

哈,我们得到了a的值!

也就是说,当我们有p = &a,我们可以用*p得到a的值。

那能不能操作呢?当然可以。

我们可以把*p当做a的值,那么,我们尝试如下代码:

#include <stdio.h>
int main(){
int a = 10;
int* p;
p = &a;
printf("指针p指向的地址的值:%d\n",*p);
*p = 13;
printf("指针p指向的地址的值:%d\n",*p);
*p += 3;
printf("指针p指向的地址的值:%d\n",*p);
*p -= 3;
printf("指针p指向的地址的值:%d\n",*p);
*p *= 9;
printf("指针p指向的地址的值:%d\n",*p);
*p /= 3;
printf("指针p指向的地址的值:%d\n",*p);
*p %= 3;
printf("指针p指向的地址的值:%d\n",*p);
}

得到输出:

指针p指向的地址的值:10

指针p指向的地址的值:13

指针p指向的地址的值:16

指针p指向的地址的值:13

指针p指向的地址的值:117

指针p指向的地址的值:39

指针p指向的地址的值:0

棒极了!我们可以用指针来操作变量了。

那么,我们要这个干什么用呢?请看下一节:实现交换函数


Part 4:交换函数

交换函数是指针必学的一个东西。一般的交换我们会这么写:

t = a;
a = b;
b = t;

那么我们把它塞到函数里边:

void swap(int a,int b){
int t;
t = a;
a = b;
b = t;
}

好,我们满怀信心的调用他:

#include <stdio.h>
void swap(int a,int b){
int t;
t = a;
a = b;
b = t;
}
int main(){
int x = 5,y = 10;
printf("x=%d,y=%d\n",x,y);
swap(x,y);
printf("x=%d,y=%d",x,y);
}

于是乎,你得到了这个输出:

x=5,y=10

x=5,y=10

啊啊啊啊啊啊啊啊,为什么不行!!!

问题就在你的swap函数,我们来看看他们做了些啥:

swap(x,y);             --->把x赋值给a,把y赋值给b
///进入函数体
int t; --->定义t
t = a; --->t赋值为a
a = b; --->a赋值为b
b = t; --->b赋值为t

各位同学,函数体内有任何一点谈到了x和y吗?

所谓的交换,交换的到底是a和b,还是x和y?

我相信你这时候你恍然大悟了,我们一直在交换a和b,并没有操作x和y

那么我们怎么操作?指针!

因为x和y在整个程序中的地址一定是不变的,那么我们通过上一节的指针运算可以得到,我们能够经过指针操作变量的值。

那么,我们改进一下这个函数

void swap(int* a,int* b){
int t;
t = *a;
*a = *b;
*b = t;
}

我们再来试试,然后你就会得到报错信息。

我想,你是这么用的:swap(x,y)

问题就在这里,我们看看swap需要怎样的两个变量?int*int*类型。

怎么办?我告诉你一个小秘密:

任何一个变量加上&,此时就相当于在原本的类型加上了*

什么意思?也就是说:

int a;
&a ---> int*;
double d;
&d ---> double*;
int* p;
&p ---> int**;//这是个二级指针,也就是说指向指针的指针

那么,我们要这么做:swap(&a,&b),把传入的参数int换为int*

再次尝试,得到输出:

x=5,y=10

x=10,y=5

累死了,总算是搞好了


Part 5:char*表示字符串

char*这个神奇的类型可以表示个字符串,举个例子:


#include <stdio.h> int main()
{
char* str;
str = "YOU AK IOI!";
printf("%s",str);
}

请注意:输入和输出字符串的时候,都不能带上*&

你可以用string.h中的函数来进行操作


Part 6:野指针

有些同学他会这么写:

int* p;
printf("%p",p);

哦千万不要这么做!

当你没有让p指向某个地方的时候,你还把他用了!这个时候就会产生野指针。

野指针的危害是什么?

第一种是指向不可访问(操作系统不允许访问的敏感地址,譬如内核空间)的地址,结果是触发段错误,这种算是最好的情况了;

第二种是指向一个可用的、而且没什么特别意义的空间(譬如我们曾经使用过但是已经不用的栈空间或堆空间),这时候程序运行不会出错,也不会对当前程序造成损害,这种情况下会掩盖你的程序错误,让你以为程序没问题,其实是有问题的;

第三种情况就是指向了一个可用的空间,而且这个空间其实在程序中正在被使用(譬如说是程序的一个变量x),那么野指针的解引用就会刚好修改这个变量x的值,导致这个变量莫名其妙的被改变,程序出现离奇的错误。一般最终都会导致程序崩溃,或者数据被损害。这种危害是最大的。

不论如何,我们都不希望看到这些发生。

于是,养成好习惯:变量先赋值。

指针你可以这么做:int *p =NULL;让指针指向空

不论如何,他总算有个值了。


Part 7:总结

本文干货全部在这里了:

  1. 指针是个变量,他的类型是数据类型+*,他的值是一个地址,他自身也有地址
  2. 指针有两个专属运算符:&*
  3. 指针可以操作变量,不能操作常量
  4. 指针可以表示字符串
  5. 请注意野指针的问题

本文没有讲到的:

  1. char[],char,const char的区别与联系
  2. const修饰指针会怎么样?
  3. void*指针的运用
  4. 多级指针的运用
  5. NULL到底是什么
  6. malloc函数的运用

感谢观看!

搞清楚C语言指针的更多相关文章

  1. 难搞的C语言指针你搞懂了多少

    C语言指针说难不难但是说容易又是最容易出错的地方,因此不管是你要做什么只要用到C指针你就跳不过,今天咱们就以 十九个例子来给大家简单的分析一下指针的应用,最后会有C语言视频资料提供给大家更加深入的参考 ...

  2. 彻底搞定C语言指针(精华版)

    1.语言中变量的实质 要理解C指针,我认为一定要理解C中“变量”的存储实质, 所以我就从“变量”这个东西开始讲起吧! 先来理解理解内存空间吧!请看下图: 内存地址→ 6 7 8 9 10 11 12 ...

  3. C语言指针学习

    C语言学过好久了,对于其中的指针却没有非常明确的认识,趁着有机会来好好学习一下,总结一下学过的知识,知识来自C语言指针详解一文 一:指针的概念 指针是一个特殊的变量,里面存储的数值是内存里的一个地址. ...

  4. (转载)c语言指针学习

    前言 近期俄罗斯的陨石.四月的血月.五月北京的飞雪以及天朝各种血腥和混乱,给人一种不详的预感.佛祖说的末法时期,五浊恶世 ,十恶之世,人再无心法约束,道德沦丧,和现在正好吻合.尤其是在天朝,空气,水, ...

  5. C语言指针的陷阱

    C语言指针的陷阱   分类: C/Cpp 转自:http://blog.csdn.net/porscheyin/article/details/3461670 “C语言诡异离奇,陷阱重重,却获得了巨大 ...

  6. c语言指针学习【转】

    前言 近期俄罗斯的陨石.四月的血月.五月北京的飞雪以及天朝各种血腥和混乱,给人一种不详的预感.佛祖说的末法时期,五浊恶世 ,十恶之世,人再无心法约束,道德沦丧,和现在正好吻合.尤其是在天朝,空气,水, ...

  7. 关于C语言指针的一些新认识(1)

    Technorati 标签: 指针,数组,汇编,C语言 前言 指针是C语言的精华,但我对它一直有种敬而远之的感觉,因为一个不小心就可能让你的程序陷入莫名其妙的麻烦之中.所以,在处理字符串时,我总是能用 ...

  8. C语言指针专题——指针难学的4点原因

    前一篇跟大家聊了聊指针的概念,可是就算了解了指针是什么,为什么依然感觉难学?我试着从几个点切入,聊聊指针难学之处. 文末会给大家推荐几本书,有需要的朋友可以看看! 难点1. 讨厌的星号 定义指针变量p ...

  9. Go语言 - 指针 | new | make

    区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针. 要搞明白Go语言中的指针需要先知道3个概念:指针地址.指针类型和指针取值. 概念 任何程序数据载入内存后,在内存都有他们的地 ...

随机推荐

  1. Mockito如何mock一条链式调用

    在写单元测试的时候,不免可能需要mock一些对象出来,并且mock一些方法调用去返回一个自己想要的对象.一般的使用是这样的: FinalPumpkin pumpkin = mock(FinalPump ...

  2. python-经典类和新式类区别

    经典类和新式类区别 Eg: class A(object):    def x(self):        print('A')class B(A):    def x(self):        p ...

  3. 我的linux学习日记day7

    一.文件权限: r:read 读取文件列表的权限, 数字4表示 w:write 写入.删除.修改的权限,数字2表示 x:execute 进入目录的权限,数字1表示 权限分配:文件所有人.文件所属主.其 ...

  4. bcdedit 替代easybcd 编辑grub

    bcdedit这个命令是win7下,继续安装xubuntu时遇到的.当时xubuntu的语言设置为en-us,时区改为HK,可是仍有很多不太适应的地方.于是,删了xubuntu,重装它.同时,grub ...

  5. 00005-js 获取uuid

    admin.guid = function () { function S4() { return (((1+Math.random())*0x10000)|0).toString(16).subst ...

  6. [Unity UGUI序列帧]简单实现序列帧的播放

    在使用序列帧之前需要准备好序列帧的图集,打图集的操作参考 [Unity UGUI图集系统]浅谈UGUI图集使用 准备好序列帧图集,序列帧的播放原理就是获取到图集中的所有图片,然后按照设置的速度按个赋值 ...

  7. 百度智能云平台调用食物识别api Java实现

    纪录一下我小学期2天花了20小时写的菜品识别java程序. 1.2. 百度智能云简介 1.2.1 百度图像识别服务 百度图像识别服务,基于深度学习及大规模图像训练,准确识别图片中的物体类别.位置.置信 ...

  8. 【蓝桥杯C/C++组】备赛基础篇之差分算法

    一.个人理解 前面学习了前缀和算法,对于访问任意区间的速度是比较快的,但如果我们要修改某个区间的数呢,对于前缀和算法来说这还是有点棘手. 所以我们来学学新的算法:差分算法! 前缀和数组储存的是前n个数 ...

  9. MyCat基本知识

    一.Mycat基本元素 1.逻辑库,mycat中存在,对应用来说相当于mysql数据库,后端可能对应了多个物理数据库,逻辑库中不保存数据 2.逻辑表,逻辑库中的表,对应用来说相当于mysql的数据表, ...

  10. React Native 架构演进

    写在前面 上一篇(React Native 架构一览)从设计.线程模型等方面介绍了 React Native 的现有架构,本篇将分析这种架构的局限性,以及 React Native 正在进行的架构升级 ...