移动端网站和微信网页设计,网站建设的架构,wordpress维护页面插件,现在创业什么行业最好深入理解指针#xff08;3#xff09; 一、字符指针变量二、数组指针变量2.1数组指针变量是什么#xff1f;2.2数组指针变量怎么初始化#xff1f; 三、二维数组传参的本质四、函数指针变量4.1函数指针变量的创建4.2函数指针变量的使用4.3两段有趣的代码4.4 typedef关键字 … 深入理解指针3 一、字符指针变量二、数组指针变量2.1数组指针变量是什么2.2数组指针变量怎么初始化 三、二维数组传参的本质四、函数指针变量4.1函数指针变量的创建4.2函数指针变量的使用4.3两段有趣的代码4.4 typedef关键字 五、函数指针数组六、转移表七、总结 一、字符指针变量
在指针的类型中我们知道有⼀种指针类型为字符指针char*。 下面我们来看这样一段代码
#include stdio.h
int main()
{
int a 20;
a 200;
//3 5;
const char*p hello world;//常量字符串是不能被修改的。
printf(%c\n,*p);//h
printf(%s\n,p);
//*p q;//err
return 0;
}运行结果如下图 观察代码及运行结果这里我们看到其实是把hello world中的首字母的地址存放在p中在这里很多人会认为是把整个字符串放在指针p中。 《剑指offer》中有一道和字符串相关的笔试题我们一起来看一下代码如下
#include stdio.h
int main()
{
char str1[] hello bit.;
char str2[] hello bit.;
const char*str3 hello bit.;
const char*str4 hello bit.;
if(str1 str2)
printf(str1 and str2 are same\n);//1X
else
printf(str1 and str2 are not same\n);//2
if(str3 str4)
printf(str3 and str4 are same\n);//3
else
printf(str3 and str4 are not same\n);//4X
//常量字符串是不能被修改的内容相同的常量字符串只会在内存上存放1份。
return 0;
}运行结果如下图 通过观察我们发现用相同的字符串常量初始化不同的数组时就会开辟出不同的内存块所以str1和str2是不同的常量字符串是不能被修改的内容相同的常量字符串只会在内存上存放1份所以str3和str4是相同的。
二、数组指针变量
2.1数组指针变量是什么
之前我们学过指针数组是存放地址指针的数组。 那数组指针变量又是什么呢是指针变量还是数组 类比 字符指针 char *p 指向字符的指针存放的是字符的地址。 整型指针 int *p 指向整型的指针存放的是整型的地址。 数组指针 指向数组的指针是存放数组地址的指针变量。 答案不言而喻是指针变量是存放数组地址的指针变量。 数组指针变量
int (*p)[10];p先和*结合说明p是一个指针变量变量然后指着指向的是一个大小为10个整型的数组。所以p是一个指针指向一个数组叫数组指针。 注[ ]的优先级比 * 高所以必须要带括号保证 p 先和 * 结合。
2.2数组指针变量怎么初始化
数组指针变量是用来存放数组地址的那怎么获得数组的地址呢这就用到了我们之前学习的数组名 。 例
int(*p)[10] arr;我们再看看下面一段代码
#include stdio.h
int main()
{
int arr[10] {0};
int(*p)[10] arr;
return 0;
}调试结果如下图 通过调试后发现arr和p的类型是一样的。 注数组指针会在二维数组中使用。
三、二维数组传参的本质
有了数组指针的理解我们就能够了解一下⼆维数组传参的本质了。 以前我们有一个⼆维数组需要传参给⼀个函数的时候我们是这样写的
void test(int a[3][5],int r,int c)
{
int i 0;
int j 0;
for(i 0;i r;i)
{
for(j 0;j c;j)
{
printf(%d,a[i][j]);
}
printf(\n);
}
}
int main()
{
int arr[3][5] {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
test(arr,3,5);
return 0;
}这里实参是二维数组形参也写成⼆维数组的形式那还有什么其他的写法吗 首先我们再次理解一下二维数组二维数组其实可以看做是每个元素都是一维数组的数组也就是二维数组的每个元素是一个一维数组。那么⼆维数组的首元素就是第一行是个一维数组。 如图 arr是二维数组数组名是数组首元素的地址那么二维数组arr的数组名是谁呢 答案显而易见二维数组的首元素就是第一行一维数组每一行都是一个元素一维数组。 那么上述代码就可以改成以下代码
void test(int(*arr)[5],int r,int c)
{
int i 0;
int j 0;
for(i 0;i r;i)
{
for(j 0;j c;j)
{
printf(%d,*(*(p i) j));//(*(p i))[j]
}
printf(\n);
}
}
int main()
{
int arr[3][5] {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
test(arr,3,5);
return 0;
}二维数组传参的本质二维数组传参本质上也是传递了地址传递的是第一行这个一维数组的地址那么形参也是可以写成指针的形式的。
四、函数指针变量
4.1函数指针变量的创建
什么是函数指针变量呢 函数指针变量是用来存放函数地址的后面通过地址能够调用函数的。 那么函数是否有地址呢下面我们做一个测试代码如下
#include stdio.h
void test()
{
printf(hehe\n);
}
int main()
{
printf(test: %p\n, test);
printf(test: %p\n, test);
return 0;
}运行结果如下图 这里我们可以看出确实打印出了地址所以函数是有地址的函数名就是它的地址可以通过函数名或者函数名来获取地址。 函数指针变量写法与数组指针变量的写法非常类似代码如下
int Add(int a,int b)
{
return a b;
}
int main()
{
int arr[8] {0};
int (*pa)[8] arr;//pa是数组指针变量。
int (*pf)(int,int) Add;//pf就是函数指针变量。
return 0;
}4.2函数指针变量的使用
通过函数指针调⽤指针指向的函数。
#include stdio.h
int Add(int a,int b)
{
return a b;
}
int main()
{
int(*pf1)(int,int) Add;//int(*)(int,int)函数指针类型
int(*pf2)(int,int) Add;
int r1 (*pf1)(3,7);//int r1 (pf1)(3,7);//*是个摆设
int r2 (*pf2)(3,7);//int r2 (pf2)(3,7);
int r3 Add(3,7);
printf(%d\n,r1);
printf(%d\n,r2);
printf(%d\n,r3);
return 0;
}运行结果如下图
4.3两段有趣的代码
第一段代码
int main()
{//void(*p)();
(*(void (*)())0)();//函数调用
//1.将0强制类型转换成void(*)()类型的函数指针。
//2.调用0地址处放的这个函数。
return 0;
}运行时报错如下图 第二段代码
int main()
{
void (*signal(int , void(*)(int)))(int);
//函数声明声明的函数的名字叫signal
//signal函数有2个参数第一个参数的类型是int,第二个参数是void(*)(int)的函数指针类型该指针可以指向一个函数指向的函数参数是int返回类型是void。
//signal函数的返回类型是void(*)(int)的函数指针该指针指向一个函数指向的函数参数是int返回类型是void。
//void(*)int signal(int,void(*)(int));//err
return 0;
}运行结果如下图 注这两个代码来自《C陷阱和缺陷》这本书。
4.4 typedef关键字
typedef 是用来类型重命名的可以将复杂的类型简单化。 例如你觉得unsigned int 写起来不太方便如果能写成uint 就方便多了那么我们就可以使用
typedef unsigned int uint;
//将unsigned int重命名为uint那么指针类型是否可以重命名呢 可以代码如下
//typedef对指针类型重命名
typedef int*pint;
int main()
{
int* p1 NULL;
pint p2 NULL;
return 0;
}但是对于数组指针和函数指针稍微有点区别数组指针代码如下
typedef int(*parr_t)[5];//新的类型名必须在*的右边
//parr_t等价于int(*)[5]
int main()
{
int arr[5] {0};
int(*p)[5] arr;//p是数组指针变量p是变量的名字
//int(*)[5] —— 数组指针类型
parr_t p2 arr;
return 0;
}函数指针类型重命名代码如下
typedef void(*pf_t)(int);//新的类型名必须在*的右边对4.3中第二段代码进行简化
typedef void (*pf_t)(int);//新的类型名
void (*signal(int , void(*)(int)))(int);//简化前代码
pf_t signal(int,pf_t);//简化后代码接下来我们看下面这段代码
typedef int* ptr_t;
#define PTR_T int*;
int main()
{
//ptr_t p1;//p1是整型指针
//PTR_T P2;//p2是整型指针
ptr_t p1,p2;//p1,p2是整型指针
PTR_T p3,p4;//p3是指针p4是整型//p3 —— int* p4 —— int
//int*p3,p4;
}观察后不难发现#define定义的PTR_TPTR_T p3,p4;被分解为PTR_Tp3即int*p3和p4即int p4而typedef定义的ptr_t却被分解为ptr_t p1和ptr_t p2。 注函数指针 —— 指向函数的指针函数指针变量 —— 存放函数地址的变量。
五、函数指针数组
数组是一个存放相同类型数据的存储空间之前我们学习了指针数组如
int *arr[10];//数组的每个元素是int* 类比 整数数组是存放整型的数组 字符数组是存放字符的数组 指针数组是存放指针的数组 chararr1[5];//字符指针数组 intarr2[7];//整数指针数组 那要把函数的地址存到一个数组中那这个数组就叫函数指针数组那函数指针的数组如何定义呢
int (*parr1[3])();
int *parr2[3]();
int (*)() parr3[3];答案parr1parr1 先和[ ] 结合说明parr1是数组数组的内容是什么呢是int (*)() 类型的函数指针。
#include stdio.h
int Add(int x,int y)
{
return x y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x,int y)
{
return x * y;
}
int Div(int x,int y)
{
return x / y;
}
int main()
{
int (*pf1)(int,int) Add;
int (*pf2)(int,int) Sub;
int (*pf3)(int,int) Mul;
int (*pf4)(int,int) Div;
int(*pfArr[4])(int,int) {Add,Sub,Mul,Div};//pfArr就是函数指针数组
int i 0;
for(i 0;i 4;i)
{
int ret pfArr[i](8,4);
printf(%d\n,ret);
}
return 0;
}六、转移表
函数指针数组的用途转移表 举例计算器的一般实现
#include stdio.h
int Add(int x,int y)
{
return x y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x,int y)
{
return x * y;
}
int Div(int x,int y)
{
return x / y;
}
nemu()
{printf(*************************\n);printf( 1:add 2:sub \n);printf( 3:mul 4:div \n);printf( 0:exit \n);printf(*************************\n);
}
int main()
{
int input 0;
int x 0;
int y 0;
int ret 0;
//创建一个函数指针的数组
int(*pfArr[5])(int,int) {NULL,Add,Sub,Mul,Div};
do
{
nemu();
printf(请选择);
scanf(%d,input);//2
if(input 1 input 4)
{
printf(请输入2个操作数);
scanf(%d %d,x,y);
ret pfArr[input](x,y);
printf(%d\n,ret);
}
else if(input 0)
{
printf(退出计算器\n);
break;
}
else
{
printf(选择错误重新选择\n);
}
}while(input);
return 0;
}数组指针 —— 指向的是数组
int arr[5];
int(*p)[5] arr;函数指针 —— 指向的是函数
char*test(int n,char*s)
{
}
char*(*pf)(int,char*) test;//pf是函数指针变量指针是否可以存放到数组中呢—— 可以 指针数组
char*arr[5];
int*arr2[5];
double*arr3[9];
float*arr4[6];函数指针数组
char*(*pfArr[4])(int,char*);拓展指向函数指针数组的指针
char*(*(*p)[4])(int,char*) pfArr;
//取出的是函数指针数组地址p就是指向一个函数指针数组的指针。实现一个计算器代码如下
#include stdio.h
int Add(int x,int y)
{
return x y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x,int y)
{
return x * y;
}
int Div(int x,int y)
{
return x / y;
}
void nemu()
{printf(*************************\n);printf( 1:add 2:sub \n);printf( 3:mul 4:div \n);printf( 0:exit \n);printf(*************************\n);
}
void Calc(int(*pf)(int,int))
{
int x 0;
int y 0;
int ret 0;
printf(请输入2个操作数);
scanf(%d %d,x,y);
ret pf(x,y);
printf(%d\n,ret);
}
int main()
{
int input 0;
do
{
nemu();
printf(请选择);
scanf(%d,input);
switch(input)
{
case 1:Calc(Add);break;
case 2:Calc(Sub);break;
case 3:Calc(Mul);break;
case 4:Calc(Div);break;
case 0:printf(退出计算器\n);break;
default:printf(选择错误重新选择\n);break;
}
}while(input);
return 0;
}数组传参形参可以是数组也可以是指针数组指针实参是函数地址形参只能是函数指针。
七、总结
这篇文章详细介绍了C语言中指针相关的重要概念包括字符指针、数组指针、函数指针及其数组的使用。通过代码示例文章解释了如何利用指针进行内存管理以及如何实现更灵活、动态的功能调用。以下是对文章内容的总结。
首先文章介绍了字符指针char*它是指向字符的指针类型常用于操作字符串。通过代码实例展示了如何通过字符指针访问字符串内容特别是常量字符串的特点。常量字符串存储在只读内存中因此不能修改。作者通过对比字符数组与字符指针的内存分配强调了字符指针指向的是字符串的首字符地址而非整个字符串。
接着文章讲解了数组指针如int (*p)[10]它是指向数组的指针存储的是数组的地址。数组指针与指针数组有所不同数组指针指向的是整个数组而指针数组是存储指针的数组。通过示例代码读者可以清楚地理解如何声明和初始化数组指针以及如何利用数组指针处理二维数组。
文章还深入探讨了二维数组传参的本质。二维数组在内存中其实是由多个一维数组组成的每个元素都是一个一维数组。传递二维数组时实际上是传递了二维数组的首元素地址即第一行的地址。文章通过代码示例说明了如何用数组指针来传递二维数组这样避免了传递整个数组的额外开销。
此外文章详细讲解了函数指针变量。函数指针允许程序在运行时动态调用不同的函数。文章通过示例演示了函数指针的创建和使用包括如何声明、初始化并通过函数指针调用函数。作者还展示了如何使用 typedef 来简化函数指针的声明提高代码的可读性。
最后文章介绍了函数指针数组及其应用。函数指针数组是存储多个函数指针的数组允许根据不同的条件动态选择要调用的函数。通过实现一个简单的计算器文章展示了如何通过函数指针数组实现不同操作的选择。函数指针数组在实际编程中有广泛的应用尤其适用于需要根据输入选择不同操作的场景。
总的来说本文通过详细的代码实例帮助读者理解了C语言中指针的不同类型及其应用特别是如何通过指针和函数指针提高程序的灵活性和动态性。这些技巧不仅能简化复杂的代码还能增强程序的可维护性和扩展性。