int *p;
如上图所示:
我们把 p 称为指针变量,
p里存储的内存地址处的内存称为 p 所指向的内存。
指针变量 p 里存储的任何数据都将被当作地址来处理。
“*”号理解
举个例子:当你回到家门口时,你想进屋第一件事就是拿出钥匙来开锁。那你想想防盗门的锁芯是不是很像这个“*”号?你要进屋必须要用钥匙,那你去读写一块内存是不是也要一把钥匙呢?这个“*”号就是不是就是我们最好的钥匙?使用指针的时候,没有它,你是不可能读写某块内存的。
- char *p = “abcdef”;
- char a[] = “123456”;
以指针的形式访问和以下标的形式访问指针:
例子 A)定义了一个指针变量 p,p 本身在栈上占 4 个 byte,p 里存储的是一块内存的首 地址。这块内存在静态区,其空间大小为 7 个 byte,这块内存也没有名字。对这块内存的访 问完全是匿名的访问。比如现在需要读取字符‘e’,
我们有两种方式:
- 以指针的形式:*(p+4)。
先取出 p 里存储的地址值,假设为 0x0000FF00,然后加 上 4 个字符的偏移量,得到新的地址 0x0000FF04。然后取出 0x0000FF04 地址上的值。
- 以下标的形式:p[4]。
编译器总是把以下标的形式的操作解析为以指针的形式的操 作。p[4]这个操作会被解析成:先取出 p 里存储的地址值,然后加上中括号中 4 个元素的偏 移量,计算出新的地址,然后从新的地址中取出值。
也就是说以下标的形式访问在本质上 与以指针的形式访问没有区别,只是写法上不同罢了。
以指针的形式访问和以下标的形式访问数组:
例子 B)定义了一个数组 a,a 拥有 7 个 char 类型的元素,其空间大小为 7。数组 a 本身 在栈上面。对 a 的元素的访问必须先根据数组的名字 a 找到数组首元素的首地址,然后根据 偏移量找到相应的值。这是一种典型的“具名+匿名”访问。比如现在需要读取字符‘5’,
我们有两种方式:
- 以指针的形式:*(a+4)。
a 这时候代表的是数组首元素的首地址,假设为 0x0000FF00, 然后加上 4 个字符的偏移量,得到新的地址 0x0000FF04。然后取出 0x0000FF04 地址上的 值。
- 以下标的形式:a[4]。
编译器总是把以下标的形式的操作解析为以指针的形式的操 作。a[4]这个操作会被解析成:a 作为数组首元素的首地址,然后加上中括号中 4 个元素的 偏移量,计算出新的地址,然后从新的地址中取出值。
由上面的分析,我们可以看到,指针和数组根本就是两个完全不一样的东西。只是它们 都可以“以指针形式”或“以下标形式”进行访问。一个是完全的匿名访问,一个是典型 的具名+匿名访问。一定要注意的是这个“以 XXX 的形式的访问”这种表达方式。
另外一个需要强调的是:
上面所说的偏移量 4 代表的是 4 个元素,而不是 4 个 byte。只不过这里刚好是 char 类型数据 1 个字符的大小就为 1 个 byte。记住这个偏移量的单位是元素的个数而不是 byte 数,在计算新地址时千万别弄错了。
int a[5]={1,2,3,4,5}
a是数组首元素的首地址;也就是 a[0]的 首地址
&a是数组的首地址;
对指针进行加 1 操作,得到的是下一个元素的地址,而不是原有地址值直接加 1。
所以,一个类型为 T 的指针的移动,以 sizeof(T) 为移动单位。
&a + 1: 取数组 a 的首地址,该地址的值加上 sizeof(a) 的值,即 &a + 5*sizeof(int),
也就是下一个数组的首地址,显然当前指针已经越过了数组的界限。
(int *)(&a+1): 把上一步计算出来的地址,强制转换为 int * 类型。
a+1 是数组下一元素的首地址,即 a[1]的首地址。
指针和数组特性
指针 | 数组 |
---|---|
保存数据的地址, 任何存入指针变量 p 的数 据都会被当作地址来处理。 p 本身的地址由 编译器另外存储,存储在哪里,我们并不知道。 | 保存数据, 数组名 a 代表的是数组首元素的 首地址而不是数组的首地址。 &a 才是整个数组的首地址。 a 本身的地址由编译器另外存储,存储在哪里,我们并不知道。 |
间接访问数据, 首先取得指针变量 p 的内容,把它作为地址,然后从这个地址提取数据或向这个地址写入数据。 指针可以以指针的形 式访问*(p+i);也可以以下标的形式访问 p[i]。 但其本质都是先取 p 的内容然后加上 i*sizeof(类型)个 byte 作为数据的真正地址。 | 直接访问数据, 数组名 a 是整个数组的名字, 数组内每个元素并没有名字。只能通过“具名+匿名”的方式来访问其某个元素,不能把 数组当一个整体来进行读写操作。 数组可以 以指针的形式访问*(a+i);也可以以下标的形式访问 a[i]。但其本质都是 a 所代表的数组首 元素的首地址加上 i*sizeof(类型)个 byte 作为 数据的真正地址。 |
通常用于动态数据结构 | 通常用于存储固定数目且数据类型相同的元 素。 |
相关的函数为 malloc 和 free。 | 隐式分配和删除 |
通常指向匿名数据(当然也可指向具名数据) | 自身即为数组名 |
指针数组和数组指针
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身 决定。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在 32 位系统下永远是占 4 个字节, 至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。
A)int *p1[10]; 指针数组
B)int (*p2)[10]; 数组指针
二维数组
char a[3][4]
例子:
#include <stdio.h>
int main(int argc,char * argv[])
{
int a [3][2]={(0,1),(2,3),(4,5)}; // int a [3][2]={ 1, 3, 5};
int *p;
p=a [0];
printf("%d",p[0]);
}
逗号运算符确保操作数被顺序地处理:先计算左边的操作数,再计算右边的操作数。
右操作数的类型和值作为整个表达式的结果。
左操作数只是为了副作用需要而被计算,它其值会被丢弃
例子:
int a[5][5];
int (*p)[4];
p = a;
问&p[4][2] - &a[4][2]的值为多少? -4
二级指针
char **p;
定义了一个二级指针变量 p。
p 是一个指针变量,毫无疑问在 32 位系统下占 4 个 byte。
它与一级指针不同的是,
一级指针保存的是数据的地址,
二级指针保存的是一级指针的地址。
数组参数和指针参数
暂时理解不是很透彻,后续补充!
函数指针
顾名思义,函数指针就是函数的指针。
它是一个指针,指向一个函数。
看例子:
char * (*fun1)(char * p1,char * p2);
使用函数指针的好处在于,可以将实现同一功能的多个模块统一起来标识,这样一来更容易后期的维护,系统结构更加清晰。或者归纳为:便于分层设计、利于系统抽象、降低耦合度以及使接口与实现分开。
函数指针数组
char * (*pf[3])(char * p);
函数指针数组的指针
char * (*(*pf)[3])(char * p);