在计算机科学中, 指针 (Pointer) 是编程语言中的一个对象, 利用地址, 它的值直接指向 (points to) 存在电脑存储器中另一个地方的值. 由于通过地址能找到所需的变量单元, 可以说, 地址指向该变量单元. 因此, 将地址形象化的称为 "指针". 意思是通过它能找到以它为地址的内存单元.
在 C 语言的学习过程中, 指针是相当重要的一部分, 学好指针对 C 语言的学习有很重要的意义.
指针和内存
如果说内存相当于街道, 那么地址就相当于位于街道中房子的房号.
一个字节 (8bit) 包含一个地址. 内存中每一个位置都包含一个独一无二的地址标识, 而每一个位置都包含一个值.
注意:1区别指针变量的内容(即地址), 指针指向变量的内容, 指针的地址.
为什么程序中的数据会有自己的地址?
弄清这个问题我们需要从操作系统的角度去认知内存.
电脑维修师傅眼中的内存是这样的: 内存在物理上是由一组 DRAM 芯片组成的.
如果有想要学习 C/C++ 的小伙伴, 可以关注小编[C/C++ 企鹅群 496926338] ,wx 公众号: CPP_cx 小编也有 5 年编程经验了, 免费送一套比较系统的资料, 教程和工作经验, 就当是福利吧!
加 QQ 群: 496926338
而作为一个程序员, 我们不需要了解内存的物理结构, 操作系统将 RAM 等硬件和软件结合起来, 给程序员提供的一种对内存使用的抽象., 这种抽象机制使得程序使用的是虚拟存储器, 而不是直接操作和使用真实存在的物理存储器. 所有的虚拟地址形成的集合就是虚拟地址空间.
在程序员眼中的内存应该是下面这样的.
也就是说, 内存是一个很大的, 线性的字节数组(平坦寻址). 每一个字节都是固定的大小, 由 8 个二进制位组成. 最关键的是, 每一个字节都有一个唯一的编号, 编号从 0 开始, 一直到最后一个字节. 如上图中, 这是一个 256M 的内存, 他一共有 256x1024x1024 = 268435456 个字节, 那么它的地址范围就是 0 ~268435455 .
由于内存中的每一个字节都有一个唯一的编号, 因此, 在程序中使用的变量, 常量, 甚至数函数等数据, 当他们被载入到内存中后, 都有自己唯一的一个编号, 这个编号就是这个数据的地址. 指针就是这样形成的.
下面用代码说明
- #include
- int main(void)
- {
- char ch = 'a';
- int num = 97;
- printf("ch 的地址:%p\n",&ch); //ch 的地址: 0028FF47
- printf("num 的地址:%p\n",&num); //num 的地址: 0028FF40
- return 0;
- }
指针的值实质是内存单元 (即字节) 的编号, 所以指针 单独从数值上看, 也是整数, 他们一般用 16 进制表示. 指针的值 (虚拟地址值) 使用一个机器字的大小来存储, 也就是说, 对于一个机器字为 w 位的电脑而言, 它的虚拟地址空间是 0~2w - 1 , 程序最多能访问 2w 个字节. 这就是为什么 xp 这种 32 位系统最大支持 4GB 内存的原因了.
我们可以大致画出变量 ch 和 num 在内存模型中的存储.(假设 char 占 1 个字节, int 占 4 字节)
变量和内存
为了简单起见, 这里就用上面例子中的 int num = 97 这个局部变量来分析变量在内存中的存储模型.
已知: num 的类型是 int, 占用了 4 个字节的内存空间, 其值是 97, 地址是 0028FF40. 我们从以下几个方面去分析.
1, 内存的数据
内存的数据就是变量的值对应的二进制, 一切都是二进制. 97 的二进制是 : 00000000 00000000 00000000 0110000 , 但使用的小端模式存储时, 低位数据存放在低地址, 所以图中画的时候是倒过来的.
2, 内存数据的类型
内存的数据类型决定了这个数据占用的字节数, 以及计算机将如何解释这些字节. num 的类型是 int, 因此将被解释为 一个整数.
3, 内存数据的名称
内存的名称就是变量名. 实质上, 内存数据都是以地址来标识的, 根本没有内存的名称这个说法, 这只是高级语言提供的抽象机制 , 方便我们操作内存数据. 而且在 C 语言中, 并不是所有的内存数据都有名称, 例如使用 malloc 申请的堆内存就没有.
4, 内存数据的地址
如果一个类型占用的字节数大于 1, 则其变量的地址就是地址值最小的那个字节的地址. 因此 num 的地址是 0028FF40. 内存的地址用于标识这个内存块.
5, 内存数据的生命周期
num 是 main 函数中的局部变量, 因此当 main 函数被启动时, 它被分配于栈内存上, 当 main 执行结束时, 消亡.
如果一个数据一直占用着他的内存, 那么我们就说他是 "活着的", 如果他占用的内存被回收了, 则这个数据就 "消亡了".C 语言中的程序数据会按照他们定义的位置, 数据的种类, 修饰的关键字等因素, 决定他们的生命周期特性. 实质上我们程序使用的内存会被逻辑上划分为: 栈区, 堆区, 静态数据区, 方法区. 不同的区域的数据有不同的生命周期.
无论以后计算机硬件如何发展, 内存容量都是有限的, 因此清楚理解程序中每一个程序数据的生命周期是非常重要的.
我会在以后的文章中再对 C 语言的内存管理做出介绍, 敬请期待.
下面说一下 C 语言关于指针的注意事项
一, 指针的四个关键概念
1, 指针的类型
2, 指针指向的类型
3, 指针的值, 也就是指针指向的地址
4, 指针自己所占用的内存空间
注意: 指针变量所存的内容就是内存的地址编号!
例如:
int **pp = NULL;
1, 指针的类型是 int **
2, 指针指向的类型 int *
3, 指针的值为 NULL
4, 指针自己所站内存的大小 sizeof(pp)
二, 强制类型转换
强制类型不会改变内存中二进制的排列与顺序, 只会将二进制按照目标类型解释.
例如:
int i = 10;
二进制位 0110;
float f = (float)i;
变量 f 的二进制还是 0110
三, 关于 CONST 修饰变量
看 const 修饰变量的时候, 完全可以将数据类型名视而不见
例如
- int const *p; // 修饰 * p , p 可以变 , *p 不能够变
- const int *p; // 修饰 * p , p 可以变 , *p 不能够变
- const int * const p; // 修饰 p 和 * p , p 不可变, *p 也不可变
四, 关于变量类型
1, 数据类型的本质是固定内存空间大小的别名
2, 变量的本质是一段连续内存空间起始地址的别名
五, 关于变量声明的意义
1, 建立变量符号表
通过声明变量, 编译器可以建立变量符号表, 如此一来, 程序中用到了多少变量, 每个变量的类型是什么, 编译器非常清楚, 是否使用了没有声明的变量, 编译器在编译期间就可以发现. 从而帮助开发人员远离由于疏忽而将变量名写错的情况
2, 变量的数据类型指示系统为变量分配多少内存空间
3, 变量的数据类型指示了系统如何解释存储空间中的值, 同样的数值, 不同的类型将有不同的解释. int 占据 4 个字节, float 也占据 4 个字节, 在内存中同样是存储的二进制数, 并且这个二进制数也没有标志区分当前是 int 型还是 float 型. 如何区分? 就是通过变量的数据类型来区分. 由于声明建立了变量符号表, 所以系统知道变量该如何解释
4, 变量的数据类型确定了该变量的取值范围
例如短整型数据取值 - 32767~32767 之间
5, 不同的数据类型有不同的操作
如整数可以求余. C 语言用符号 "%" 表示求余. 整数可以, 实数不可
六, 关于函数调用传递指针的总结
1, 如果在被调函数中想修改主调函数中变量的值, 则需要将主调函数中的变量的地址 (指针) 传递到被调函数中
2, 如果主调函数要传递一个超大的数据到被调函数中时, 也可以将主调函数的变量的地址 (指针) 传递到被调函数中, 这样有利于提高程序的性能
3, 传递 N 级指针是为了修改 N-1 级指针的值,
例如 : int *p; 如果需要修改 p 的值, 则需要将 & p 传递到函数中, 才能修改 p 的值
七, 关于指针与数组互换的问题
在表达式中, 指针和数组是可以互换的, 因为他们在编译器里面的最终形式都是指针, 并且都可以进行取下标操作
八, 数组与指针的区别
数组: 一个数组就是一个地址, 并且该地址是一个常量值, 不能改变. 因此, 数组名不能作为左值.
指针: 一个指针就是一个地址的地址 , 并且该地址是可以改变的. 也就可以作为左值.
九, 指针与地址的区别
指针就是地址, 但地址并不是指针. 指针有类型, 地址没有类型.
十, 传值与传址的区别
c 语言其实只有一种传值方式, 就是按值传递
按值, 按址传递的区别在于, 在函数中使用的方式而已. 如果在函数中是要修改地址所指向内存的值, 则可以修改实参地址指向的内容. 因为在内存中内存地址是唯一的, 所以不论在什么地方, 都可以通过地址来修改内存中的值
如果有想要学习 C/C++ 的小伙伴, 可以关注小编[C/C++ 企鹅群 496926338] ,wx 公众号: CPP_cx 小编也有 5 年编程经验了, 免费送一套比较系统的资料, 教程和工作经验, 就当是福利吧!
加 QQ 群: 496926338
指针变量总结
来源: http://www.jianshu.com/p/2ef879e1fac1