解读 C 语言中的指针
我想对很多学习C语言的新手来说,指针无疑是一个难点。但是,我觉得指针也是C语言特别重要的一个特性。也许,你在除了C和C++以外的编程语言中,很少看到指针。而C++中,也多用引用,而非指针。指针,作为一种高效的工具,可谓是一把双刃剑——用得好,可以大大提高程序效率,但用的不好,就是很多bug的滋生地。
这或许也是人们对指针褒贬不一的原因吧。就我个人而言,我还是很喜欢这个特性,因为我需要经常和硬件以及一些底层的软件打交道。这个时候,指针便体现出它独特的魅力。指针的知识很多,有一本经典的书叫《C和指针》,如果有兴趣可以读一读。这里,我主要总结一些如何去解读指针(说实话这个东西实在是很容易让人困惑)的方法,一方面给自己做查询用,另一方面,希望可以给别人一些帮助。
一,基本概念
关于指针的基本概念,我就不详细介绍了,因为有许多书都介绍的很详细。这里我只介绍一部分。指针指向一个地址,而指针本身在大多数系统上都是一个无符号整数(在32bit机上是4byte,在64bit机上是8byte)。下面用一个例子来说明其机制:
在上面的例子中,先定义了一个指针p,它的类型是int,也就是说它只能指向一个int型的变量,而不能指向其他类型的变量。最后我们将a变量的地址赋给p。在这个过程中,涉及到两个内存块,一个是存放指针p的内存(用&p可得到内存地址),一个是存放a的值的内存块(用&a可以得到内存地址)。而第一个内存存的p的值经过赋值语句后也就是&a的值了。另外一个注意点是, *(星号)和变量类型以及变量名之间可以有任意个空格,也可以没有。比如下面三种方式都是一样的:
在上面的例子中,先定义了一个指针p,它的类型是int,也就是说它只能指向一个int型的变量,而不能指向其他类型的变量。最后我们将a变量的地址赋给p。在这个过程中,涉及到两个内存块,一个是存放指针p的内存(用&p可得到内存地址),一个是存放a的值的内存块(用&a可以得到内存地址)。而第一个内存存的p的值经过赋值语句后也就是&a的值了。另外一个注意点是, *(星号)和变量类型以及变量名之间可以有任意个空格,也可以没有。比如下面三种方式都是一样的:
解读方法:
看下面一个例子:
二,数组首地址a,&a,&a[0]
注:a,&a,&a[0]的含义虽然不同,但是他们三个的值是相等的!
以int a[3]为例说明:
a作为右值时,代表数组首元素的首地址,而非数组地址。 也就是a[0]的地址。int i = (a+1),这里a是右值,所以代表首元素的首地址,a+1代表下一个元素的首地址,即&a[1]。
a是整个数组的名字。所以sizeof(a)的值为sizeof(int) * 3 = 40,代表整个数组的大小。
&a即为取a的首地址,也即整个数组的首地址。所以sizeof(&a) = 4。 int p = (int)(&a+1)中的&a+1代表下一个数组的首地址,显然是越界的。
&a[0]代表首元素的首地址。 所以sizeof(&a[0]) = 4。
&a[3],很显然数组越界了,但它的sizeof是多少呢? 也是4,因为关键字sizeof求值是在编译的时候,虽然并不存在a[3]这个元素,但是这里并没有真正访问a[3],而是根据数组元素类型来确定其值的。所以sizeof(a[3])不会出错。
a[-1]代表什么意思?首先要明白下标的形式被编译器解析成指针的形式,即a[1]被解析成(a+1)。那么,a[-1]被解析成*(a-1)。
关于数组首元素的首地址和数组的首地址的区别:其实,数组首元素的首地址和数组首地址的值是相同的,即&a[0]和a(以及&a)是相等的,但是而这含义不一样。首元素的首地址加1后,是第二个元素的首地址(之所以一直说首地址,是因为有的类型存储时会占多个地址),但数组的首地址加1后是“下一个数组的地址”,这里的下一个数组只是为了说明加1时加了整个数组的大小,而不是一个元素的大小。
有一点比较容易混淆:a虽然代表整个数组,但(a+1)却代表下一个元素的首地址,即和(&a[0]+1)一样,下一个数组的形式为:(&a+1)。 下面以一个程序来说明:
输出结果:
说明(下面的行数只计算main函数内有代码的行):
程序第1行定义了一个具有3个元素的整型数组。
第2行打印了long型的大小。因为我是64bit的,所以一个long是8byte。
第3行打印了*(a+1)的值,结果和a[1]的值相等。说明a虽然代表整个数组,但作为右值时,的确代表首元素的首地址。
第4行输出值为12,是整个数组的大小。
第5行打印了一个出界元素的大小,没有报错,验证了上面第5条。
第6行打印了a[-1]和*(a-1),输出值相等。验证了上面第6条。
第7行打印了a和&a[0],值相等。说明数组的首地址和首元素的首地址是相等的。
第8行打印了a,(a+1),(&a+1),由结果就可以看出首元素的首地址加1是加了一个数组元素的大小,而数组首地址加1是加了一个数组的大小。
三,指针数组和数组指针
指针数组: 首先它是一个数组,数组的元素是指针,也成为“存储指针的数组”。
数组指针: 首先它是一个指针,它指向一个数组,也可以理解为“数组的指针”。 也可以利用前面的“解读方法”去分析。
四,函数指针和指针函数
函数指针: 指向函数的指针变量。
指针函数: 带指针的函数,也就是返回指针的函数。
五,指针常量和常量指针
怎么记?
可以先把类型名去掉,然后看const离谁近,就修饰谁。
也可以const在*左边的为常量指针,const在*右边的为指针常量。
三~五的万能钥匙
其实,关于“指针数组与数组指针、函数指针与指针函数、指针常量与常量指针”的判断,有一个万能钥匙。那就是根据我们强大的中文语法:前边是修饰词,后边才是主语。比如“指针数组”,前面的指针只是修饰词,后面的数组才是主语,所以它是一个数组。
六,野指针
野指针指没有确定指向的指针。造成野指针的情况有:
1. 指针变量创建但没有初始化。
2. 指针p被free或者delete之后,没有置为NULL。
版权声明:感谢原作者的辛苦创作,来源:伯乐在线专栏作者 -时间轨迹,链接:http://blog.jobbole.com/102052/。
解读 C 语言中的指针的更多相关文章
- C语言中的指针数组
C语言中的指针数组是什么,像 char *a[]={"ddd","dsidd","lll"}; 这里讲一下注意如果我们使用了a也就是首元素的 ...
- 【ZZ】C 语言中的指针和内存泄漏 & 编写高效的C程序与C代码优化
C 语言中的指针和内存泄漏 http://www.ibm.com/developerworks/cn/aix/library/au-toughgame/ 本文讨论了几种在使用动态内存分配时可以避免的陷 ...
- GO语言中的指针
http://www.tizgrape.com/?p=100 Go语言中的指针语法和C++一脉相承,都是用*作为符号,虽然语法上接近,但是实际差异不小. Go使用var定义变量: var v6 *in ...
- C语言中的指针笔记
C语言指针 得到变量的地址 可以使用&运算符找到变量保存在内存中的位置 int x = 1; printf("x的内存地址是"%p\n",&x) %p格式 ...
- C语言中的指针加减偏移量
C语言指针偏移技巧(也是一个要注意的坑) - 陈杰柱的博客 - CSDN博客 https://blog.csdn.net/cjzjolly/article/details/82116772 C语言中 ...
- C 语言中的指针和内存泄漏
引言对于任何使用 C 语言的人,如果问他们 C 语言的最大烦恼是什么,其中许多人可能会回答说是指针和内存泄漏.这些的确是消耗了开发人员大多数调试时间的事项.指针和内存泄漏对某些开发人员来说似乎令人畏惧 ...
- C语言中的指针和内存泄漏
引言 对于任何使用C语言的人,如果问他们C语言的最大烦恼是什么,其中许多人可能会回答说是指针和内存泄漏.这些的确是消耗了开发人员大多数调试时间的事项.指针和内存泄漏对某些开发人员来说似乎令人畏惧,但是 ...
- C语言中的指针学习(小黑板)
指针是C语言中的精华所在,也是C语言的危险之在,今天又重现温习了一下C语言,做了一下总结. 欢迎批阅. (1)指针的含义指针的本质也是数据类型,它是指向地址的变量.例如: { int a = 10; ...
- [C]C语言中的指针和内存泄漏几种情况
引言 原文地址:http://www.cnblogs.com/archimedes/p/c-point-memory-leak.html,转载请注明源地址. 对于任何使用C语言的人,如果问他们C语言的 ...
随机推荐
- .NET开发笔记(二十二) .NET VS Java
我们目前对.NET的理解大部分可以归纳为:起初它是Java平台(注意是平台,不要跟Java语言搞混淆)的一个克隆品,后来慢慢演变,有了自己的特性.由于Java平台最显著的特点就是“平台独立性”(或者说 ...
- swift 字符串和数字相互转化
//NSString 类型 转为整型,浮点型, var test:NSString = "3.3" println("test\(test)") println ...
- 将不确定变为确定~DateTime.MinValue和MaxValue引发的异常
回到目录 问题描述: SqlDateTime 溢出.必须介于 1/1/1753 12:00:00 AM 和 12/31/9999 11:59:59 PM 之间 概念相关 .Net中的DateTime结 ...
- 360路由器刷openwrt后设置wifi中继
上一篇文章(360路由器刷openwrt.不死uboot.双系统 .wifi中继 - 飞鸿影~ - 博客园)讲了如何在360路由器C301上安装openwrt以及安装双系统.这篇文章讲如何设置无线中继 ...
- js笔记——js里的null和undefined
以下内容摘录自阮一峰的<语法概述 -- JavaScript 标准参考教程(alpha)>章节『5.null和undefined』,以做备忘. null与undefined都可以表示&qu ...
- Atitit dsl exer v3 qb3 新特性
Atitit dsl exer v3 qb3 新特性 /atiplat_cms/src/com/attilax/dsl/DslParser.java V3 支持typeed参数,与简化的notyp参数 ...
- Mysql 函数分类
比较重要的 1 REVERSE(s) 将字符串s的顺序反过来 2 TRIM(s) 去掉字符串s开始和结尾处的空格 一.数学函数 数学函数主要用于处理数字,包括整型.浮点数等. ...
- 学习ASP.NET MVC(八)——“Code First Migrations ”工具
在本篇文章中,我们学习如何使用实体框架的“Code First Migrations ”(也称为代码先行功能)工具,使用其中的“迁移”功能对模型类进行一些修改,同时同步更新对应数据库的表结构. 默认情 ...
- 每天一个linux命令(61):vi命令 /企业常用的linux命令清单
vi/vim 的使用 基本上 vi/vim 共分为三种模式,分别是一般模式.编辑模式与指令列命令模式. 这三种模式的作用分别是: 一般模式:以 vi 打开一个档案就直接进入一般模式了(这是默认的模式) ...
- VS2013模块对于SAFESEH映像是不安全的解决方法
常见报错:error LNK2026: 模块对于 SAFESEH 映像是不安全的 解决方法:右键打开项目属性 -> 链接器 -> 命令行 -> 其他选项 (D) 中加入 /SAFE ...