C语言 指针

指针是 C 语言中的一个重要概念,也是 C 语言的一个重要特色。

一、指针是什么?

1.指针

如果在 C 语言中定义了一个变量,系统会为其分配空间(或自己用 malloc 申请空间)。内存中每一个字节都有一个编号,这就是“地址”,存储这个地址信息的变量便称为指针。

因为在 C 语言中,不同类型的数据会获得不同大小的地址,并且数据在内存上的存取方式也有不同。因此,如果希望能够找到这个地址,并且正确地访问它,在知道地址信息的基础上还需要知道该数据的类型信息。这就是为什么在 C 语言中,定义指针时还需要附上类型。

2.直接访问和间接访问

(1) 直接访问

系统通过变量名找到变量对应的地址,并对其进行访问。

(2) 间接访问

将变量 i 的地址存储在另一变量中,然后通过另一变量找到变量 i 的地址,并对其进行访问。

二、指针变量

1.什么是指针变量

存放地址的变量是指针变量,它用来指向另一个对象。

变量的值是值

指针的值是地址

2.如何定义指针变量

1
类型名 *指针变量名;

3.如何使用指针

1
2
3
4
*p --  值
p -- 址
a -- 值
&a -- 址

4.指针的算数运算

显然,对于地址而言,乘、除都是没有意义的。

(1) 加法

1
p + n

此表达式的值为 p 的地址加 n 个单元大小,指向 p 之后的第 n 个元素。

1
p++;

指针 p 自增,指向下一个单元。

(2) 减法

1
p - n

此表达式的值为 p 的地址减去 n 个单元大小,指向 p 之前的第 n 个元素。

1
p++;

指针 p 自减,指向上一个单元。

1
p - n

(3) 两指针相减

首先两个指针应该是同一类型,系统会计算出两指针之间相差几个单元(而不是相差多少地址)。

(4) 两指针比较

首先两个指针应该是同一类型,系统会对地址进行大小比较。

5.注意事项

(1) 类型很重要

对指针变量来说,存储单元的地址和存储单元的类型同样重要。

说明类型的时候不能简单地说 “a 是一个指针”,而应该是 “a 是一个整形指针”。

(2) 指针变量只能存放地址

就像变量只能存放值一样,指针变量只能存放地址。

(3) 指针应先指向后使用

1
2
int *p;
*p = 12;

这种做法是错误的,因为指针在定义时会被随机初始化。如果没有为它指定一个地址,它将会指向一个未知的空间,对其上的数据进行修改可能会导致不可预期的错误。

(4) 指针可以有空值

当不知道指针该指向哪里时,应 p = NULL 将其设为空值,表示哪里都不指向。

三、通过指针引用数组

1.指向数组元素的指针

所谓数组元素的指针就是指向数组元素的指针。

取数组地址和取数组首元素地址时,可以不用加 & 符号:

1
2
int *p = a;
int *p = &a[0];

取数组其它元素地址时,需要加 & 符号,表取址:

1
int *p = &a[3];

2.访问数组元素的两种方法

(1) 下标法

1
2
3
数组名[数组下标]

指向数组的指针[数组下标]

(2) 指针法

1
2
3
4
//此偏移量非彼偏移量,这里的偏移量是指偏移了几个单元(单元大小由指针类型决定)
数组地址 + 偏移量

指向数组的指针 + 偏移量

数组的下标访问实际上是一个“语法糖”,对于一个指向某种类型的指针 p ,一个整数 n 而言:

1
p[n] = [n]p = *(p + n)

指向数组元素的指针可以通过自增自减访问数组中的各个元素:

1
2
3
4
int *p = a;
for (i = 0; i < n; i++, p++){
printf("%d\n", *p);
}

而数组名不能做增减运算,因为数组地址无法改变。

数组可以看作“const”类型的指针,无法改变地址。

3.数组做函数参数

具体请看: C语言 第四章-函数

4.多维数组的指针访问法

可以将二维数组看作“数组的数组”,即二维数组 a 是由多个一维数组所组成的。

1
2
3
4
5
a ----------------- 第一个一维数组的地址,等价于 &a[0]
a + 2 ------------- 第三个一维数组的地址,等价于 &a[2]
*(a + 2) ---------- 第三个一维数组的第一个元素的地址,等价于 &a[2][0], a[2]
*(a + 2) + 1 ------ 第三个一维数组的第二个元素的地址,等价于 &a[2][1]
*(*(a + 2) + 1) --- 第三个一维数组的第二个元素,等价于 a[2][1]

如果程序恰巧使用了一个指向二维数组的指针,而且要通过该指针获取值时,最好用简单的数组表示法,而不是指针表示法。

5.指向一维数组的指针

1
类型 (*p)[列数]

例如:

1
2
3
int a[100][4];
int (*p)[4];
p = a;

p 先与 * 结合,表示这是一个指针,再与 [4] 结合,表示这是一个指向由 4 个元素组成的数组的指针。

  • 指针 p 将指向由 4 个整型组成的数组
  • 4 是必须的,并且应该和数组的列数相同,否则将无法赋值

四、指向函数的指针

1.什么是函数的指针

如果在程序中定义了一个函数,在编译时会把函数的源代码转换为可执行代码并分配一段存储空间。这段存储空间有一个起始地址,也被称为函数的入口地址,每次调用函数时都从该地址入口开始执行函数代码。函数名就是函数的指针,它代表函数的起始地址。

2.定义指向函数的指针

可以定义一个指向函数的指针变量,用来存放某一函数的起始地址。

1
返回值类型 (*指针名)(函数参数表列)

例如:

1
int (*p)(int, int);

定义 p 为一个指向函数的指针变量,它可以指向函数返回值类型为整形,且有两个整形参数的函数。

3.用函数指针变量调用函数

(1) 使指针指向函数

1
指针 = 函数;

给指针赋值时只需给出函数名而无需给出参数,否则就变成将函数返回值赋给指针了。

(2) 调用函数

1
(*指针名)(实参表)

用 *(指针名) 替代函数名

4.应用实例

(1) 实例1

代码:

1
2
3
4
5
6
7
8
9
10
11
12
int#include <stdio.h>
int fun(int n, char m){
printf("%d\n", n);
return m;
}

int main(){
int (*p)(int, char);
p = fun;
(*p)(3, 's');
printf("%c", (*p)(3, 's'));
}

运行结果:

1
2
3
3
3
s

首先定义指向函数的变量,然后用赋值操作使函数指向它。通过 (*p)(参数) 调用函数,函数功能和参数传递都和正常调用相同。

(2) 实例2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int min(int n, int m){
return n > m ? m : n;
}
int max(int n, int m){
return n > m ? n : m;
}
int main(){
int choice;
int a = 3, b = 4;
int (*p)(int, int);
printf("1求大 2求小,请输入:");
scanf("%d", &choice);
if (choice == 1) p = max;
else if (choice == 2) p = min;
else return 0;
printf("%d", (*p)(a, b));
}

根据输入的不同调用不同的函数,使程序更加简洁。

5.用指向函数的指针作函数参数

指向函数的指针变量的一个重要用途是把函数的入口地址作为参数传递到其它函数。

在函数的参数表中定义指向函数的指针这一形参,并在调用时传入其它函数名,就可以在函数中通过指针调用其它函数了。

因为函数是同级的,所以本来就可以在函数中直接调用其它函数。

此做法的用处是可以在不同的场合下传入不同的函数,这样用一个函数就可以实现不同的功能。

1
2
3
4
5
void fun(返回值类型 (*指针名)(函数参数表列), 返回值类型 (*指针名)(函数参数表列), int n){

}
···
fun(max, min, 3);

五、返回指针的函数

1.返回指针

1
类型名 *函数名(参数表列)

这样,函数就可以在运行之后返回一个对应类型的指针。

2.返回数组

具体请看:

C语言 从函数中返回数组

六、指针数组

1.什么是指针数组

若一个数组的每个元素都是指针,则可以称为指针数组。

2.定义指针数组

1
类型名 *数组名[数组长度]

例如:

1
int *a[4];

a 先与 [4] 结合, a[4] 为一个有 4 个元素的数组,最后与 * 结合,表示 a 为一个有 4 个元素,且每个元素都是一个指针的数组。

这样就定义了四个元素的数组,每个元素都是一个 int 型的指针。

3.指针数组作 main 函数的形参

(1) 无参数的 main 函数

1
int main()

1
int main(void)

(2) 传入参数的 main 函数

1
int main(int argc, char *argv[])

其中,

argc 为参数数量(包含文件名);argv为指针数组,指向用户传递过来的参数。

例如:

执行命令 file Hello World !

向 main 传递的内容为:

  • 一个整形数据,其值为 4 ,表示传递了 4 个参数
  • 一个指针数组,每个元素指向一个字符串的开头
    • argv[0] 为 “file”,程序本身的文件名也算作一个参数
    • argv[1] 为 “Hello”
    • argv[2] 为 “World”
    • argv[3] 为 “!”

七、多重指针

1
类型名 **指针名

可以类比定义数组和定义指针的方式记忆

1
2
int a[4];    int *p;
int *a[4]; int **p;

八、动态内存分配

详细请看:C语言 动态内存分配

九、总结

十、指针数组和数组指针

1.指针数组

1
类型名 *数组名[数组长度]

包含四个元素的数组,每个元素都是一个 int 型的指针。

a 先与 [4] 结合, a[4] 为一个有 4 个元素的数组,最后与 * 结合,表示 a 为一个有 4 个元素,且每个元素都是一个指针的数组。

2.数组指针

1
类型名 (*数组名)[数组长度]

一个指针,指向一个一维数组。

p 先与 * 结合,表示这是一个指针,再与 [4] 结合,表示这是一个指向由 4 个元素组成的数组的指针。

3.区别

可以这样理解,

*p[列数] 首先这是一个数组,然后其元素是指针。

(*p)[列数] 首先这是一个指针,然后指向 4 个元素;

参考