指针是C语言中非常重要的数据类型,如果你说C语言中除了指针,其他你都学得很好,那你干脆说没学过C语言。究竟什么是指针呢?我们先来看一个概念。

直接引用

  1. 回想一下,之前我们是如何更改某个变量的值?
    我们之前是通过变量名来直接引用变量,然后进行赋值:
char a;
a = 10;
  1. 看上去是很简单,其实程序内部是怎么操作的呢?
    其实,程序对变量的读写操作,实际上是对变量所在的存储空间进行写入或取出数据。就上面的代码而言,系统会自动将变量名a转换为变量的存储地址,根据地址找到变量a的存储空间,然后再将数据10以2进制的形式放入变量a的存储空间中。
  2. 通过变量名引用变量,由系统自动完成变量名和其存储地址之间的转换,称为变量的"直接引用"方式

一、什么是指针?

1.我们已经知道,"直接引用"是直接通过变量名来读写变量

2.C语言中还有一种"间接引用"的方式(以变量a为例):首先将变量a的地址存放在另一个变量中,比如存放在变量b中,然后通过变量b来间接引用变量a,间接读写变量a的值。这就是"间接引用"。

如果程序通过"间接引用"的方式来修改a的值,可以这样做:先根据 变量名b 获取 变量b 的地址ffc2,取出变量b中存储的内容ffc1,也就是变量a的地址,再根据变量a的地址ffc1找到a的存储空间,然后修改里面的数据。

3.总结一句:用来存放变量地址的变量,就称为"指针变量"。在上面的情况下,变量b就是个"指针变量",我们可以说指针变量b指向变量a。

二、指针的定义

一般形式:类名标识符 *指针变量名;

int *p;float *q;

"*"是一个说明符,用来说明这个变量是个指针变量,是不能省略的,但它不属于变量名的一部分
前面的类型标识符表示指针变量所指向的变量的类型,而且只能指向这种类型的变量

三、指针的初始化

1.先定义后初始化

// 定义int类型的变量a
int a = 10;// 定义一个指针变量p
int *p;// 将变量a的地址赋值给指针变量p,所以指针变量p指向变量a
p = &a;

注意第8行,赋值给p的是变量a的地址&a

2.在定义的同时初始化

// 定义int类型的变量a
int a = 10;// 定义一个指针变量p
// 并将变量a的地址赋值给指针变量p,所以指针变量p指向变量a
int *p = &a;

3.初始化的注意

指针变量是用来存放变量地址的,不要给它随意赋值一个常数。下面的写法是错误的

int *p;
p = 200; // 这是错误的

四、指针运算符

1.给指针指向的变量赋值

char a = 10;
printf("修改前,a的值:%d\n", a);// 指针变量p指向变量a
char *p = &a;// 通过指针变量p间接修改变量a的值
*p = 9;printf("修改后,a的值:%d", a);

当程序刚执行完第5行代码时,内存中大概的分布情况是这样的
,a值是10,p值就是变量a的地址ffc3。
注意下第5、第8行,都有个"*",它们的含义是不一样的:

(1) 第5行的"*"只是用来说明p是个指针变量

(2) 第8行的""是一个指针运算符,这里的p代表根据p值ffc3这个地址访问对应的存储空间,也就是变量a的存储空间,然后将右边的数值9写入到这个存储空间,相当于 a = 9;,于是内存中就变成这样了

输出结果为:可以发现,我们通过变量p间接修改了变量a的值。

2.取出指针所指向变量的值

指针运算符除了可以赋值之外,还可以用于取值

char a = 10;char *p;
p = &a;char value = *p;
printf("取出a的值:%d", value);

输出结果:,第6行中的*p的意思是:根据p值(即变量a的地址)访问对应的存储空间,并取出存储的内容(即取出变量a的值),赋值给value

3.使用注意

在指针变量没有指向确定地址之前,不要对它所指的内容赋值。下面的写法是错误的

int *p;
*p = 10; //这是错误的

应该在指针变量指向一个确定的变量后再进行赋值。下面的写法才是正确的

// 定义2个int型变量
int a = 6, b;// 定义一个指向变量b的指针变量p
int *p;
p = &b;// 将a的值赋值给变量b
*p = a;

五、指针的用途举例

1.例子1
前面我们通过指针变量p间接访问了变量a,在有些人看来,觉得指针变量好傻B,直接用变量名a访问变量a不就好了么,干嘛搞这么麻烦。别着急,接下来举个例子,让大家看看指针还能做什么事情。

现在有个要求:写一个函数swap,接收2个整型参数,功能是互换两个实参的值。

1> 如果没学过指针,你可能会这样写

void swap(char v1, char v2) {printf("更换前:v1=%d, v2=%d\n", v1, v2);// 定义一个中间变量char temp;// 交换v1和v2的值temp = v1;v1 = v2;v2 = temp;printf("更换后:v1=%d, v2=%d\n", v1, v2);
}int main()
{char a = 10, b = 9;printf("更换前:a=%d, b=%d\n", a, b);swap(a, b);printf("更换后:a=%d, b=%d", a, b);return 0;
}

输出结果:,虽然v1和v2的值被交换了,但是变量a和b的值根本就没有换过来。因为基本数据类型作为函数实参时,只是纯粹地将值传递给形参,形参的改变并不影响实参。

我们可以简要分析一下这个过程:

  • 在第20行中,将变量a、b的值分别传递给了swap函数的两个形参v1、v2

  • 在第8行中,将v1的值赋值给了temp

  • 在第9行中,将v2的值赋值给了v1

  • 在第10行中,将temp的值赋值给了v2

    就这样,v1和v2的值被交换了,但是a和b的值一直都没有改变

2> 如果学了指针,就应该这样写

void swap(char *v1, char *v2) {// 中间变量char temp;// 取出v1指向的变量的值temp = *v1;// 取出v2指向的变量的值,然后赋值给v1指向的变量*v1 = *v2;// 赋值给v2指向的变量*v2 = temp;
}int main()
{char a = 10, b = 9;printf("更换前:a=%d, b=%d\n", a, b);swap(&a, &b);printf("更换后:a=%d, b=%d", a, b);return 0;
}

先看看输出结果:,变量a和b的值终于换过来了。

解释一下:

(在16位编译器环境下,一个指针变量占用2个字节)

  • 先注意第20行,传递是变量的地址。因此swap函数的形参v1指向了变量a,v2指向了变量b

  • 第6行代码是取出v1指向的变量的值,也就是变量a的值:10,然后赋值给变量temp

  • 第9行代码是取出v2指向的变量(变量b)的值,然后赋值给v1指向的变量(变量a)

  • 第12行代码是将temp变量的值赋值给v2指向的变量(变量b)

相信你已经感受到指针的强大了,如果没有指针,在一个函数的内部根本改变不了外部的实参。

2.例子2
接下来再举一个指针的实用例子。默认情况下,一个函数只能有一个返回值,有了指针,我们可以实现函数有"多返回值"。

现在有个要求:写一个函数sumAndMinus,可以同时计算2个整型的和与差,函数执行完毕后,返回和与差(注意了,这里要返回2个值)

// 计算2个整型的和与差
int sumAndMinus(int v1, int v2, int *minus) {// 计算差,并赋值给指针指向的变量*minus = v1 - v2;// 计算和,并返回和return v1 + v2;
}int main()
{// 定义2个int型变量int a = 6, b = 2;// 定义2个变量来分别接收和与差int sum, minus;// 调用函数sum = sumAndMinus(a, b, &minus);// 打印和printf("%d+%d=%d\n", a, b, sum);// 打印差printf("%d-%d=%d\n", a, b, minus);return 0;
}

六、关于指针的疑问
刚学完指针,都可能有一大堆的疑惑,这里我列出几个常见的疑惑吧。

1.一个指针变量占用多少个字节的内存空间?占用的空间是否会跟随所指向变量的类型而改变?
在同一种编译器环境下,一个指针变量所占用的内存空间是固定的。比如,在16位编译器环境下,任何一个指针变量都只占用2个字节,并不会随所指向变量的类型而改变。

2.既然每个指针变量所占用的内存空间是一样的,而且存储的都是地址,为何指针变量还要分类型?而且只能指向一种类型的变量?比如指向int类型的指针、指向char类型的指针。
其实,我觉得这个问题跟"数组为什么要分类型"是一样的。

  • 看下面的代码,利用指针p读取变量c的值
int i = 2;
char c = 1;// 定义一个指向char类型的指针
char *p = &c;// 取出
printf("%d", *p);

这个输出结果应该难不倒大家:,是可以成功读取的。

如果我改一下第5行的代码,用一个本应该指向int类型变量的指针p,指向char类型的变量c

int *p = &c;

我们再来看一下输出:,c的原值是1,现在取出来却是513,怎么回事呢?这个要根据内存来分析

根据变量的定义顺序,这些变量在内存中大致如下图排布:

其中,指针变量p和int类型变量i各占2个字节,char类型的c占一个字节,p指向c,因此p值就是c的地址

1> 最初的时候,我们用char p指向变量c。当利用p来获取变量c的值时,由于指针p知道变量c是char类型的,所以会从ffc3这个地址开始读取1个字节的数据:0000 0001,转为10进制就是1

2> 后来,我们用int p指向变量c。当利用p获取变量c的值时,由于指针p认为变量c是int类型的,所以会从ffc3这个地址开始读取2个字节的数据:0000 0010 0000 0001,转为10进制就是513

可见,给指针分类是多么重要的一件事,而且一种指针最好只指向一种类型的变量,那是最安全的。
上一篇:【C语言】10-字符和字符串常用处理函数
下一篇:【C语言】12-指向一维数组元素的指针

【C语言】11-指针相关推荐

  1. 问题 B: C语言11.2

    问题 B: C语言11.2 时间限制: 1 Sec  内存限制: 32 MB 献花: 141  解决: 107 [献花][花圈][TK题库] 题目描述 定义一个结构体student,存储学生的学号.名 ...

  2. C语言函数指针(结构体函数指针)

    文章目录 20211126 这篇讲得好 参考文章1:C语言函数指针(指向函数的指针)详解 函数指针的定义形式: returnType (*pointerName)(param list); 示例代码: ...

  3. c语言字符串加减_C语言中指针的介绍

    C语言中指针的介绍 指针是C语言中广泛使用的一种数据类型. 运用指针编程是C语言最主要的风格之一.利用指针变量可以表示各种数据结构:能很方便地使用数组和字符串: 并能象汇编语言一样处理内存地址,从而编 ...

  4. c语言 乘号 指针 避免,C语言指针篇(一)指针与指针变量

    指针 1. 什么是指针? 2. 指针可不可怕? 3. 指针好不好玩? 4. 怎么学好指针? C语言是跟内存打交道的语言,指针就是内存地址.指针无处不在,指针并不可怕,相反,等你学到一定程度后,会有一种 ...

  5. c语言函数指针学习心得,c语言 函数指针 学习C语言笔记

    c语言 函数指针 大一学的C语言,课,算是学了一遍,后来接触Java,C++,易语言,python,还有写单片机用的类似C语言,可以说后来根本没有好好学,C也忘个差不多了,但是这次我课选了OC,不喜欢 ...

  6. c语言程序设计 指针 .ppt,C语言程序设计 指针.ppt

    C语言程序设计 指针.ppt 第6章 指 针,C 语言程序设计,北京科技大学 计算机系,2018/8/156.1 概述6.2 指针和指针变量6.3 指针与数组6.4 指针和函数6.5 动态存储分配,第 ...

  7. c语言有三级指针,C语言 三级指针的应用

    //三级指针的使用 #define _CRT_SECURE_NO_WARNINGS #include #include #include //三级指针做输出 int getmun(char ***po ...

  8. 深入浅出C语言:(三)C 语言数组指针(指向数组的指针)

    目录 一.C 语言数组指针(指向数组的指针) 二.C 语言字符串指针(指向字符串的指针) 三.C 语言指针数组(数组每个元素都是指针) 四.二维数组指针(指向二维数组的指针) 五.指针数组和二维数组指 ...

  9. c语言中用指针的必要性,浅析C语言使用指针的必要性.pdf

    第 21 卷第 8 期长春大学学报Vol 21No 8 2011 年 8 月 JOURNAL OF CHANGCHUN UNIVERSITYAug 2011 收稿日期 2011 06 20 作者简介 ...

  10. [C语言]用指针保存小于或等于lim的所有素数

    [C语言]用指针保存小于或等于lim的所有素数 1.题目 2.完整代码 2.1 C语言版本 2.2 C++版本 3.截图 1.题目 编写一个函数,用指针保存小于或等于 lim 的所有素数 要求: 1. ...

最新文章

  1. BH38旋转编码器初步测试
  2. C语言动态内存相关函数
  3. EL表达式的内置对象(待梳理)
  4. 想学数据分析(人工智能)需要学哪些课程?
  5. Rust LeetCode 练习:929 Unique Email Addresses
  6. Wordpress 模版技术手册 - WordPress Theme Technical manuals
  7. 首届 “女生科技体验节” 大爆料!
  8. crmeb重新安装_CRMEB
  9. sql判断邮箱是否合法_用正则表达式验证邮箱与手机号的合法性
  10. QQ快速登录实现原理分析之localhost.ptlogin2.qq.com 怎么会映射到 127.0.0.1问题
  11. 安装CDH6.3版本的时候遇到 ‘ERROR main:com.cloudera.server.cmf.Main: Server failed’的解决方案
  12. 自动控制原理:反馈控制系统的复域分析
  13. 汇编指令初步(ATT格式)
  14. Day6——yaml简介
  15. 安卓高德地图 - (附近信息点Poi与标记Marker2)
  16. 如何以正确地姿势AK SQL查询50题(精华篇)
  17. Pytorch混合精度训练
  18. python 网络字节序转换_python网络编程:ntohl、htonl、ntohs、htons
  19. 纳米结构中的磁斯格明子
  20. 知乎宣布完成2.7亿美元融资 引入前蜜芽合伙人孙伟为CFO

热门文章

  1. android各版本对应的SDK level(包括android 10)
  2. InputMethodManager内存泄漏的原因及解决方案
  3. Android ADT 无法在线安装,离线下载ADT压缩包方法。SDK Manager无法更新
  4. [2018.09.08 T1] 炉石
  5. vue-cli3的命令行创建项目-(慕课网笔记)
  6. setdate java_在PreparedStatement中使用setDate
  7. 词频统计软件_单词词频统计小软件
  8. CDATA不支持html,我应该在HTML5中使用(Should I use in HTML5?)
  9. 蕊动矿机linux cpu超频,蚂蚁l3矿机超频方法!最佳超频参数!  |  呆毛网
  10. dw中html5快捷键,DW快捷键大全