来源 :https://zlc.im/language/c/c_and_pointer.md#%E6%8C%87%E9%92%88

指针

本文介绍C语言的指针相关知识.

指针是什么?

指针和其他的int, float等类似, 是一种类型. 有类型就有相应类型的变量和常量. 本文主要讨论变量的情况.

指针变量就是一种变量, 和其他种类的变量类似, 但指针和其他变量又有区别.

首先C语言作为一种类型语言, 每个变量都会有几个属性.

  • 变量名称.

  • 变量类型.

  • 变量的值.

例如int a = 3, 变量名称就是a, 变量类型是int, 变量的值是3, 如果不提供初始值, 那么变量的值可能是一个随机值.

也就是说, 任何时候看到一个变量, 就会有这3个属性.

对于指针变量, 可以认为有4个属性.

  • 指针变量的名称.

  • 指针变量的类型, 即指针类型.

  • 指针变量的值, 即一个地址.

  • 指针变量的值所指向的内存里的数据类型. 本文称做"指向类型".

可以看到指针变量的关键在于指针所指向的内存里面数据的类型.

例如int a = 3; int *b = &a;, 指针变量名称是b, 指针变量类型是指针, 变量b的值是变量a的内存地址. 变量b所指向的内存的数据类型是int. 指针变量多了一个"变量b所指向的内存的数据类型是int”, 本文将指针变量所指向的内存的数据类型称做指向类型.

任何时候看到一个指针就需要关注4点内容: 名称, 指针类型, 指针值, 指向类型. 搞清楚这几个内容, 就可以弄明白指针怎么回事, 当然还要记忆 一些例外的情形.

类型

对于C语言来说, 搞清楚变量的类型相当重要, 涉及到指针的时候就更加重要. 看到一个指针变量后需要理解其指向类型.

例如char * const * (*next)(), next是一个指针, 那么其指向类型是什么? 这个声明/定义比较复杂, 日常编程可能就会碰到比较 复杂的情况, 所以要搞清楚指针首先要懂得怎么看一个声明/定义的变量的类型.

如果看到一个变量的声明或者定义, 那么就需要弄明白变量的类型. 在<>这本书中有一部分内容专门讲解怎么分析 一个变量的类型, 值得参考.

理解类型的规则

  1. 从变量名称开始读取, 然后依照优先级按顺序处理.

  2. 优先级从高到低 a. 括号内优先级高. b. 后缀操作符, ()表示一个函数, []表示一个数组. c. 前缀操作符, *表示"指向...的指针"

  3. 如果const, volatile后面为类型(int, long等), 那么作用于类型, 其他情况下作用于const, volatile左边的指针*.

char * const * (*next)()

按照上面的规则来理解next的类型

  1. 括号内的优先级最高, 即首先看(*next)

  2. next左边为*, 因此next是一个指针类型

  3. 然后后缀()的优先级更高, 因此next是一个指针, 指向一个函数.

  4. 接着是const右边的*, 表示next是一个指针, 指向一个函数, 该函数返回值类型为一个指针.

  5. char * const看作一个整体为指向字符的常量指针.

整个来说: next是一个指针, 指向一个函数, 函数的返回值也是一个指针, 指向一个类型为char的常量指针.

更详细的可以参考<>

类型有什么用?

C语言为类型语言, 即每个变量都有类型. 类型在变量的赋值, 函数传参, 编译检查等等方面都会用到.

类型可以确定数据的大小和操作.

例如int a = 3, 那么在内存中会存储一个数据3, 那么对于int类型具体来说.

  1. 这个数据3会占用4字节(常见32位机器与64位机器上int类型占用4字节). 实际上是有4字节的内存, 内容是0×00000003. 因此int类型就规定了占用的内存大小.

  2. 对于int类型就可以进行+,-,*,/等操作, 但是不能进行取指针值(*a)的操作. 能够进行什么操作, 也是由类型规定的.

那么对于指针来说, 其指向类型就非常重要, 指向类型就规定了指针的值所指向的内存的数据是什么类型, 也就是占用多大内存, 可以进行什么操作.

sizeof

只要类型确定, 那么便可以用sizeof计算类型占用的内存大小, 这个是编译阶段便可以确定的.

对于指针类型来说, 所有指针类型占用的内存大小基本都是一样的, 例如在32bit的机器上占用4字节, 在64bit的机器上占用8字节.

下面代码的变量a和变量b都是指针类型, 但是指向类型不同. 因此sizeof(a)和sizeof(b)的值相等, 但是sizeof(*a)和sizeof(*b)不相等.

int *a;double *b;

sizeof(a) == sizeof(b);sizeof(*a) != sizeof(*b);

指针类型的操作

可以对指针变量进行+操作.

double a[3] = {1, 2, 3};double *b   = a;

printf("b: %p, content: %f\n", b, *b);printf("b+1: %p, content: %f\n", b+1, *(b+1));

int c[3] = {1, 2, 3};int *d   = d;

printf("d: %p, content: %d\n", d, *d);printf("d+1: %p, content: %d\n", d+1, *(d+1));

运行结果

b: 0x7fff5f9ec7e0, content: 1.000000b+1: 0x7fff5f9ec7e8, content: 2.000000d: 0x7fff5f9ec7d0, content: 1d+1: 0x7fff5f9ec7d4, content: 2

可以看到b+1的值比b要大8. d+1的值比b要大4. b+1实际上是指向a[1]的内存地址. d+1是指向c[1]的内存地址.

有如下公式成立, 指针做加法后的指针变量值和指向类型占用的内存大小相关.

指针变量 + 数字 = 指针变量值 + 数字 * sizeof(指向类型)

可以看到指向类型除了告诉你指针指向的内存里面的数据类型, 在指针变量的相关运算上也是有用的.

数组类型

数组与指针有一定的相似, 同时又很不一样.

数组与指针的关键区别在于数组名是一个常量(和const常量不同). const常量表示变量的内容不会变化, 实际上还是一个变量. 这里所说的数组名为一个常量, 可以理解数组名称是一个内存地址值, 例如0×7fff5f9ec7d4.

以下面的例子来说, a本身不会占用内存, 占用内存的是a[0], a[1], a[2], 实际上a所表示的这块内存才是数组变量.

int a[10] = {0};int *b    = a;int (*d)[10]= &a;int c;

c = a[1];c = b[1];

那么a[1]b[1]的区别就在于数组是一个常量, 而不是变量(变量本身需要占用内存).

执行c = a[1]是直接从a表示的内存地址偏移4字节的内存中取数据. 仅包含一次内存读操作.

执行c = b[1]是首先从内存中取出变量b的值, 然后将变量b的值偏移4字节, 然后从这个地址的内存中取数据. 包含2次内存读操作. 第一次是读取变量b的值.

数组的其他几个需要注意的地方

  1. 数组名称相当于地址常量, 那么这个地址指向一段内存, 因此这个地址本身会有指向类型, 其指向类型就是数组的元素类型. 例如int a[10], 那么a的指向类型就是int, 因此a+1结果实际上指向a[1].

  2. sizeof(a)是计算整个数组的类型. sizeof(*a)是计算其指向类型的大小.

  3. 可以对数组名进行&a操作(取地址), 实际上&a的指针值和a的指针值一样, 而且也是个地址常量, 但是&a的指向类型 是int [10], 即指向一个包含10个int元素的数组, 所以sizeof(*&a), 计算&a的指向类型的占用内存大小就是40.

  4. 数组作为函数参数传递后, 在函数内使用等价于指针. 因为函数传参是进行值传递, 相当于有一个指针变量记录数组的地址值.

可以看到有的时候a看作一个数组(例如sizeof(a)是计算数组的内存占用), 有时候a看作一个地址常量(例如计算sizeof(*a)和a+1的时候). 还有的时候完全是比较特殊的使用(例如&a得到的指向类型为int [10]的地址常量).

函数指针

函数名本身也是一个地址常量, 其指向类型为一个函数. 实际指向的是函数在内存中的指令集合的起始位置.

int foo(int a){return a;}

int (*p_foo)(int a) = foo;

printf("%d, %d, %d\n", sizeof(foo), sizeof(*foo), sizeof(&foo));printf("%d, %d\n", sizeof(p_foo), sizeof(*p_foo));

输出值如下:

1, 1, 88, 1
  1. 对函数名本身计算类型占用内存大小, 其值为1, 对于函数名的指向类型计算内存占用大小其值也为1.

  2. foo, *foo, &foo的类型相同, 但是sizeof(&foo)结果为8.

  3. 函数指针可以进行多次解引用, *****p_foo == *p_foo = p_foo.

  4. 函数指针可以进行调用, p_foo(3);

以上几点可以认为是函数的特殊情形, 直接记忆.

可以将函数的指令看作是一个unsigned char []的数组. 这样函数名就好像是一个数组名一样, 都是地址常量, 其指向类型为unsigned char类型. 但是函数指令的数组的长度是未知的, 因此编译器默认输出sizeof(foo)为1, sizeof(*foo)相当于是sizeof(unsigned char)为1.

强制类型转换

很多时候涉及到指针和强制类型转换就会感觉比较麻烦, 实际上只要抓住类型这个关键点也可以很简单.

强制类型转换的关键是一段内存, 这段内存里面的数据你把它当作什么类型来看待.

double a = 23.456;int   *b = (int *) &a;

那么变量a有一段内存(8个字节), 里面存储了23.456(按标准浮点格式存储). 然后指针b指向这段内存, 而且指针b的指向类型是int, 因此指针b认为这段内存里面存储的是一个int类型的数据.

小结

本文内容不算全面, 但关键点都有.

每次看到指针的时候, 记住4个特征, 不管如何进行类型转换, 多少级指针, 是否包含函数指针等, 看到指针 就思考下面4点, 尤其是3,4. 练习多了之后就会发现指针本身不是很难, 难的是怎么判断数据的类型。

c语言输出方框□怎么回事_值得收藏的 C语言指针讲解文章,确实不错!相关推荐

  1. c语言输出数字漏斗图形_为什么你觉得C语言什么都不能做,学了没用?不可能的...

    对于大部分初学者,学习C语言的目的是希望做一名合格的程序员,开发出靠谱的软件来.但是学了C语言的基本语法后,发现只能开发"黑底白字"的DOS程序,完全没有漂亮的界面和生动的交互.于 ...

  2. c语言输出数字漏斗图形_为什么你觉得C语言什么都不能做,学了没用?错!大错特错!!!...

    对于大部分初学者,学习C语言的目的是希望做一名合格的程序员,开发出靠谱的软件来.但是学了C语言的基本语法后,发现只能开发"黑底白字"的DOS程序,完全没有漂亮的界面和生动的交互.于 ...

  3. c语言输出方框□怎么回事_C语言打印数据的二进制格式-原理解析与编程实现

    问题引出 C语言中,在需要用到16进制数据的时候,可以通过printf函数的%x格式打印数据的16进制形式.在某些位标记.位操作的场合,需要用到2进制格式的数据,但printf函数不能输出2进制格式, ...

  4. python菜鸟入门_值得收藏|菜鸟学Python【入门文章大全】

    这是菜鸟学Python的第106篇原创文章 阅读本文大概需要3分钟 菜鸟学python已经写了好多好多文章,我自己也没有想到能写这么多累计已经有110篇了,从入门篇写到了数据篇. 我觉得还有好多绝招, ...

  5. 语言防止鼠标连点_全球化设计系列 | 多语言设计的“小锦囊”

    hello,又见面了-在上一篇文章中,我们提到了国际化产品大致可分为两种:一是根据不同国家/地区分别设计不同的信息架构和用户界面:二是出于成本考虑用相同的架构和设计做不同语言的适配.(文章还探讨了关于 ...

  6. 绝对值得收藏的,关于癌症的文章

    绝对值得收藏的,关于癌症的文章 http://anforen.5d6d.com/ 研究表明: ① 癌症不能在弱碱性的人体中形成: ② 癌症只能在酸性身体中形成: ③ 如果你有癌症,说明身体是酸性的: ...

  7. centos7中ps显示的内容_值得收藏,史上最全Linux ps命令详解

    原标题:值得收藏,史上最全Linux ps命令详解 一.程序员的疑惑 大概在十多年前,我当时还是一个产品经理.由于一些工作的原因,需要向运维工程师学习一些linux常用命令. 当使用linux ps这 ...

  8. pythonfor循环语句例子_值得收藏!16段代码入门Python循环语句

    原标题:值得收藏!16段代码入门Python循环语句 导读:本文重点讲述for语句和while语句.for语句属于遍历循环,while语句属于当型循环.除了两个循环语句外,还介绍了break.cont ...

  9. c语言求数列的和_例15:C语言求Fibonacci数列的前30个数

    例15:求Fibonacci数列的前30个数.这个数列有以下特点:第1,2两个数为1,1,.从第三个数开始,该数是其前两个数之和.(斐波那契不死神兔) 解题思路:从前两个月的兔子数可以推出第3个月的兔 ...

最新文章

  1. Python超越Java,Rust持续称王!Stack Overflow 2019开发者报告
  2. GPU — Overview
  3. hdu 5019 第k大公约数
  4. [翻译]IE8下VML的变化
  5. JAVA_WEB--jsp概述
  6. matlab区分卷积和相关
  7. win7 path环境变量被覆盖了怎么恢复_系统小技巧:还原Windows10路径环境变量
  8. 全球最畅销的10款手机:iPhone 11继续无敌,能对拼的只有它!
  9. 6 个实用的 Code Review 实践技巧
  10. 设计佣金问题的java程序_三角形、nextday、佣金问题实验报告.doc
  11. 【文献阅读笔记】(1):一篇手把手教你做GWAS的Guideline文献解读
  12. java下载什么软件有题库_java题库app
  13. 二分查找算法(Java)
  14. Android使用NanoHttpd在app内搭建https server(二)
  15. 关于windriver WD_NO_LICENSE错误和解决办法
  16. MDK5中F103C8T6的RCC时钟配置时指向RCC_AHB1PeriphClockCmd但报错identifier “RCC_AHB1Periph_GPIOB is undefined的解决
  17. 关于神经网络的输出神经元个数的思考
  18. Spring中的DataSource
  19. 双问号??在 js 中的应用
  20. 一个形式良好的XML文档

热门文章

  1. QQ 被曝搜集浏览器历史记录;饿了么回应骑手自焚;原锤子团队被合并暂停手机研发;| 极客头条...
  2. iPhone:你知道这 13 年我是怎么过的吗?
  3. 超详细!一文带你了解 LVS 负载均衡集群!
  4. 没了Macbook的英特尔还好吗?比你想象的好
  5. 四大科技支撑大健康生态 360保险输出标准化、定制化两大核心力
  6. 原来 Kylin 的增量构建,大有学问! | 原力计划
  7. 第一个国产Apache 顶级项目 Kylin,了解一下!| 原力计划
  8. 程序员为什么应该旗帜鲜明地反对“最佳实践”?
  9. 仿真技术为“工业 4.0”插上想象的翅膀
  10. 10193 条票房数据告诉你《流浪地球》领跑的电影档战果如何?