目录
前言
一、字符指针
二、数组指针
1、数组指针的定义
2、&数组名与数组名
3、数组指针的使用
1)数组指针的打印
2)在二维数组中的使用
4、练习题
三、数组参数、指针参数
1、一维数组传参
2、二维数组传参
3、一级指针传参
4、二级指针传参
四、函数指针
1、&函数名与函数名
2、函数指针的书写
3、函数指针的使用
4、代码分析
五、函数指针数组
1、函数指针数组书写
2、函数指针的作用—转移表
六、指向函数指针数组的指针
总结
在上一篇文章:【C语言】指针终结者-初阶中我们主要介绍了指针相关的基本知识:
在这篇文章中我们将要讲解指针更深层次的内容,建议有指针基础后在阅读本文。本文将介绍:字符指针、数组指针、参数、函数指针等内容。
在前面讨论指针的类型时我们提到过一种类型:字符指针类型-char*。接下来我们详细地讨论一下字符指针的使用。
int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'a';
return 0;
}
int main()
{
const char* pstr = "hello world";
printf("%s\n", pstr);
return 0;
}
这里有一个极其理解错误的地方:这里是把一个字符串放到pstr指针变量中吗?并不是,其本质是把一个字符串hello world首字符的地址放到了pstr中,上面代码的意思便是把常量字符串的首字符h的地址存放到指针变量pstr中。注意打印时不需要使用*符号,使用%s打印。
由于这种写法赋值的是常量字符串,故不可修改其中的内容。
这样写会使程序崩溃。
int main()
{
char arr[] = "abcdefg";
char* pc = arr;
printf("%s\n", arr);
printf("%s\n", pc);
return 0;
}
由于数组名是首元素的地址,那么可以直接将数组名赋值给字符指针,那么字符指针就指向整个数组。
接下来我们来看一道面试题:
#include
int main()
{
char str1[] = "hello world";
char str2[] = "hello world";
const char* str3 = "hello world";
const char* str4 = "hello world";
if (str1 == str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
其输出结果为:
str1 and str2 are not same
str3 and str4 are same
我们一起来分析其结果:当使用相同的常量字符串去初始化不同的数组的时候会开辟出不同的内存块了,所以str1和str2不同;C/C++会把常量字符串存储到一个内存区域,其允许几个指针同时指向该空间,所以str3与str4指向的是同一块空间,所以相同。
数组指针是一个指针还是一个数组呢?它是一个指针。我们在初阶的内容说到整型指针(int* pi;)是能够指向整型数据的指针,字符型指针(char* pc)是能够指向字符型数据的指针,依此类推,数组指针便是一个指向数组的指针。
我们来看看数组指针的代码书写,判断一下下面哪个是数组指针的定义形式:
int* p1[10];
int (*p2)[10];
上面两个代码中是数组指针的是:int (*p2)[10]; *与p2先结合表示p2是一个指针变量,然后指向的是一个大小为10个元素,每个元素为int类型的数组。
为什么不能像第一个代码那样写呢?这是由于[ ]的优先级要高于*号的,如果不加()提高其优先级,p1会与[ ]结合,这时p1就是一个数组了,所以必须加上()提升其优先级。
这个知识点我们已经在【C语言】指针终结者-初阶文章中已经详细介绍过,这里只做简单的回顾。
我们都知道到数组名表示首元素的地址,而有两种情况数组名不是表示数组的首元素地址,&数组名就是其中一种情况。我们看下面的代码:
#include
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("arr+1 = %p\n", arr + 1);
printf("----------\n");
printf("&arr = %p\n", &arr);
printf("&arr+1 = %p\n", &arr + 1);
return 0;
}
数组名与&数组名虽然值一样,但&数组名取出的是整个数组。&arr 的类型是:int(*)[10] ,是一种数组指针类型,数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40。
既然数组指针是指向数组的,那么其存放的就是数组的地址。其基本使用如下:
#include
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
return 0;
}
方式一:
for (int i = 0; i < 10; i++){
printf("%d ", (*p)[i]);
}
方式二:
for (int i = 0; i < 10; i++){
printf("%d ", *(*p+i)); //*p == arr;
}
方式三:
int* p = arr;
for (int i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
#include
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for(i=0; i|
数组名arr表示首元素的地址,但是二维数组的首元素是二维数组的第一行,所以这里传递的arr其实是相当于第一行的地址,是一维数组的地址,故可用数组指针来接受,如下:
void print_arr2(int (*arr)[5], int row, int col)
{
int i = 0;
for(i=0; i|
其访问元素:*(*( p + i ) + j)
写出下面各代码的意思:
int arr[5];
arr是一个数组,数组中有5个元素,每个元素类型为int
int *parr1[10];
parr1是一个数组,数组中有10个元素,每个元素的类型为int*
int (*parr2)[10];
parr2是一个指针,指向的是一个数组,这个数组有十个元素,每个元素的类型是int
int (*parr3[10])[5];
parr3是一个有10个元素的数组,它的每个元素是一个数组指针,整个数组指针能够指向一个五个元素,每个元素为int的数组,
我们在今后编写代码的时候我们难免会遇到需要将数组或指针作为实参传给函数,那这时函数的参数该如何来设计呢?
我们直接来看代码:
#include
void test(int arr[]) //正确
{}
void test(int arr[10])//正确
{}
void test(int *arr) //正确
{}
void test2(int *arr[20])//正确
{}
void test2(int **arr)//正确
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
由上述传参代码举例可知:
故以上传参方式均是可行的。
见下面的代码:
void test1(int arr[3][5])//正确
{}
void test2(int arr[][])//错误
{}
void test3(int arr[][5])//正确
{}
void test4(int *arr)//错误
{}
void test5(int* arr[5])//错误
{}
void test6(int (*arr)[5])//正确
{}
void test7(int **arr)//错误
{}
int main()
{
int arr[3][5] = {0};
test(arr);
return 0;
}
由上述传参代码举例可知:
一级指针传参需要一级指针来接收,见代码:
#include
void print(int *p, int sz) //使用一级指针来接收
{
int i = 0;
for(i=0; i
一级指针传参非常简单,我们来看一个思考:
当一个函数的参数部分(形参)为一级指针的时候,函数能接收什么参数?
即:
void test1(int *p) void test2(char* p)
{} {}
//test1函数能接收什么参数? //test2函数能接收什么参数?
二级指针传参需要二级指针来接收,见代码,代码中的pp就是一个二级指针:
#include
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);
return 0;
}
与一级指针类似我们来看:
当函数的参数为二级指针的时候,可以接收什么参数?
当形参为二级指针时,其实参部分可以为一级指针地址,以及存放一级指针地址的二级指针。
还有一种情况也是可以作为实参的,见下面的代码段:
int* arr[10];
test(arr);
由于arr是一个指针数组,故其数组名是int*的地址,即一级指针的地址。
函数指针顾名思义就是指向函数的指针,存放函数的地址。
我们使用代码来看看他们的关系:
#include
void test()
{
printf("hello world\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
代码运行结果如下:
由此可见:&函数名和函数名都是首元素的地址。
判断下面两个代码哪个能够存放函数的地址:
void (*pfun1)();
void* pfun2();
首先函数指针是一个指针,能够存放地址,那么我们就直接可以判断出pfun1是一个函数指针,pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。而pfun2是一个函数,其返回值为void*。
例子:
我们直接来看代码:
#include
int Add(int num1,int num2)
{
return num1 + num2;
}
void Print(const char* ptr)
{
printf("%s\n", ptr);
}
int main()
{
int (*pa)(int, int) = Add;
//多种打印方式
printf("%d\n", (*pa)(4, 5));
printf("%d\n", pa(4, 5));
printf("%d\n", Add(4, 5));
void (*pc)(const char*) = Print;
(*pc)("Hello world!");
return 0;
}
代码一:
(* ( void (*)() )0) ();
解读:该代码意思为
代码二:
void (*signal(int , void(*)(int) )) (int);
解读:该代码的意思为
对于代码二其很复杂,可以进行如下简化:
typedef void(*pfun_t) (int);
pfun_t signal(int,pfun_t);
根据前面一些概念的学习我想大家可以非常容易地分析出函数指针数组。
函数指针数组是一个数组,用于存放函数指针(即函数的地址)的数组。
同理,我们先一起来学习函数指针数组的定格式,分析下面的代码中哪个能够正确表示函数指针数组:
int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];
正确的是parr1,首先parr1先与[ ]结合,表示parr1是一个数组,有10个元素,每个元素的类型为 int (*) (),故其指向的函数参数为空,返回值为int。
若打印内容,代码如下:
int (*parr[4]) (int,int) = {add, sub};
for(int i=0; i<2; i++)
{
printf("%d\n", parr[i](2,3));
}
使用switch语句实现简单的计算器(思路比较简单,这里就不做过多的讲解):代码如下:
#include
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
使用转移表实现计算器:函数部分都一致,这里只给出主函数部分:
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf("输入有误\n");
printf("ret = %d\n", ret);
}
return 0;
}
这里定义了一个p为函数指针数组,将add、sub、mul、div函数的地址(函数名即其地址)f放入该数组中,再在后面的代码中通过(*p[input])(x, y)进行间接地调用相对应的函数。这就是转移表。
我们刚刚说的函数指针数组是一个数组,那么这数组也会有一个地址,则指向函数指针数组的指针就是用来指向函数指针数组的,存放函数指针数组的地址的。
其定义方法如下:
int (*pfArr[4]) (int,int); //pfArr是一个数组 - 函数指针数组
int (* (*ppfArr) [4]) (int,int) = &pfArr
ppfArr是一个数组指针,指针指向的数组有4个元素,指向的数组的每个元素的类型是一个函数指针 int (*) (int,int)
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
在这篇文章中深入得探讨了指针的相关内容,我相信大家学完之后会发现指针的奇妙与强大,但是要分清楚这篇文章中一些概念还需要更多的练习来加深印象,以便我们熟练运用。
感谢大家的支持。