零基础学 C 语言,深入理解指针 (4) 字符指针、数组指针与函数指针
[TOC]
零基础学C语言,深入理解指针(4)
1 字符指针变量
我们知道字符指针类型是 char * ,一般的用法是这样:
1 | #include <stdio.h> |
还有一种情况是这样:
这里看着像把一个字符串存到了 p 里,但是其实是把字符串首元素的地址存到了 p 里。
《剑指offer》里面有一个题目:
输出结果是:
我们看到 str 1 和 str 2 是不一样的, str 3 和 str 4 是一样的。那这是为什么呢?
要了解这个问题,我们首先要简单知道一些内存的知识。内存有栈区,堆区,还有静态区等等。这里的数组是临时变量,储存在栈区里面,每一个临时变量的创建都需要向内存申请不同的空间,所以这里 str 1 和 str 2 指向的位置就是不一样的。
而 str 3 和 str 4,指向的是常量字符串的首元素,常量字符串是不能再被改变的,它储存在内存的代码块里,因为它是不可改变的,为了节省空间,只需要有一个就可以了。所以, str 3 和 str 4 指向的是同一个地址。
2 数组指针变量
2.1 数组指针变量的定义
顾名思义,数组指针变量就是指向一个数组的指针。在之前的讲解中,我们了解了数组名是数组首元素的地址,但是有两种特殊情况。其中 & 数组名,取出的就是整个数组的地址。用数组指针变量来存储。
如图:
int ( * p ) [10] 就是数组指针变量。
*是指针的标志, 说明 p 是一个指针变量。因为 [ ] 的优先级比 * 高,所以要加一个括号包起来。
2.2 数组指针变量的初始化
如图:
就是数组指针变量的初始化。类比一下 int * 类型的指针指针的初始化,int 是指针指向的元素的类型。 这里的 int [10],也是指针指向的元素的类型,也就是这个数组的类型。
我们怎么证明一下呢?如图:
我们发现 p 和 arr 指向的地址是相同的,而 arr + 1 跳过了四个字节的地址, p + 1 跳过了四十个字节的地址,正好是这个 arr 数组的地址。
3 二维数组传参的本质
我们首先要来重新理解一下二维数组,二维数组的元素除了原理的理解方式,还可以理解为:二维数组的元素是一维数组。
如图:
这是一个二维数组 arr [3][5],我们是不是可以把它看成是:一个二维数组,它的元素是三个元素个数为 5 的一维数组,分别是 arr [0] ,arr [1] ,arr [2]。
了解了这个以后,我们再来类比一下一位数组的传参,我们知道一位数组的传参可以写成数组的形式,也可以写成指针的形式,所以,二维数组的传参同样可以写成数组和指针的形式。
写成数组的形式:
1 | void test (int arr [3] [5]) |
那么写成指针的形式呢?这个指针要指向二维数组的首元素,刚才我们理解到二维数组的元素是一维数组,所以这里的指针指向的就是第一个一维数组的地址:
1 | void test (int ( * p ) [10]) |
4 函数指针变量
和数组指针变量类似,函数指针变量就是指向函数的指针。
函数也是有地址的,我们来验证一下:
既然函数也有地址,我们同样可以通过这个函数的地址来调用它,指向函数的指针变量就是函数指针变量。
4.1 函数指针变量的创建和使用
函数指针变量的创建形式是这样的,假设它指向一个函数 返回类型是 int 两个参数类型是 int 如: int test (int x,int y)
那么这个函数指针变量就要这么定义:
1 | int ( * p )( int , int ) = & test; |
函数名就是函数的地址,这里也可以不用取地址。
解释一下,第一个 int 是函数的返回类型,* 是指针的标志,后面括号里的两个 int 是函数的参数的类型。用括号 \ * p 包起来是因为如果不包起来的话就变成了
1 | int * p (int ,int ) |
这是一个返回类型 int * ,名字是 p ,参数类型是 int int 的函数的声明。
那么一个函数指针变量怎么使用呢?
如图:
因为函数指针变量的值就是函数的地址,解引用了以后还是函数的地址,所以解不解引用都可以,多解几个也可以。
4.2 两段有趣的代码
代码一:
1 | (*(void (*)())0)(); |
代码二:
1 | void (*signal(int , void(*)(int)))(int); |
代码一是什么意思是呢?
void ( * ) () 是一个类型,它加上括号是强制类型转换,再加上0 意思是把0 强制类型转换成 一个函数地址,然后解引用,最后调用它。
(注)0 这个地址不允许用户访问的。
代码二是什么意思呢?
我们把它拆开看,void (*signal(int , void(*)(int)))(int); 把里面拿出来,就是
void (*)(int);
signal(int, void(*)(int))
所以它表示的就是 signal 函数,返回类型是一个指向 返回值是 void 参数是 int 的函数。
4.3 typedef 关键词
typedef 是类型重定义关键词,可以重命名类型,怎么用呢?很简单:
1 | typedef int hello; |
这样我们就把 int 这个类型重命名成了 hello 。以后写 int 的地方就可以写 hello 。
1 | #include <stdio.h> |
d对于数组指针类型和函数指针类型,typedef 的使用方法有一点不同,如:
1 | typedef int (*hello)[10]; |
1 | typeder int (*hello)(int,int); |
把重命名后的类型放到括号里面就好了。
5 函数指针数组
函数指针数组就是一个数组里面装的都是函数指针变量,本质是数组。
那么我们怎么初始化呢?
1 | #include <stdio.h> |
6 转移表
函数指针数组其实就是转移表,举例:计算器的实现:
1 | #include <stdio.h> |
这是不用函数指针数组的写法,可以看到有很多重复的部分。
下面我们用函数指针数组来实现一下:
1 | #include <stdio.h> |
这就是转移表的实现方式,当然还涉及了回调函数,我们下一期再讲。
本篇完。



















