信息发布→ 登录 注册 退出

零基础详解C语言指针进阶

发布时间:2026-01-11

点击量:
目录
  • 前言
  • 1.字符指针
    • 例题 1
  • 2.指针数组
    • 例题 2
  • 3.数组指针
    • 3.1数组指针的定义
    • 3.2 &数组名与数组名
    • 3.3 数组指针的使用
  • 4.数组与指针在函数里的传参
    • 4.1 一维数组的传参
    • 4.2 二维数组的传参
    • 4.3 一级指针的传参
    • 4.4 二级指针的传参
  • 5.函数指针
    • 6. 函数指针数组
      • 用函数指针数组实现一个计算器
    • 7.回调函数

      前言

      这是指针的进阶,如果想入门指针的朋友可以关注我的另外一篇文章—c语言 指针零基础讲解

      坚持看完,一定会有很大的收获~~

      那接下来—起航

      1.字符指针

      我们目前知道整形指针,浮点型指针,字符指针跟他俩类型

      字符指针—顾名思义就是指针,一个char*类型的指针 

      在讲解字符指针前,我先提一下怎么连续创建多个指针

      连续创建多个指针的方法:

      你可能会想到用:

      int a ,b;
       
      int* a,b;

      或者

      #define PINT int*
       
      int main(){
       
      int a ,b;
       
      p a,b;
       
      }

      但是实际上这样创建的结果是:

      创建一个整形指针int*a与整形变量int b.

      创建指针的正确打开方式:

      //方案一,直接定义两次
      int a,b;
      int*a; int*b;
      //方案二,采用typedef重定义
      typedef int* pint
      {
      	int a,b;
      pint pa, pb;//此时就是定义int *pa与int *pb都是指针变量
      return 0;
      }

      下面给出一个简单的代码:

      char ch='w';
      char* p=ch;
      char* p="abcde";

      定义一个char类型的变量ch,将ch地址放在指针变量p中

      此时p存放的就是字符w的地址

      这个很容易理解,那么char* p="abcde"是什么意思呢

      实际上这次的p存放的是字符串abcde的首元素的地址,也就是a的地址

      有了首地址,就很容易找到后续元素的地址

      例题 1

          判断下面代码是否相等
          char arr1[] = "abcdef";
          char arr2[] = "abcdef";
       
          const char* str1 = "abcdef";
          const char* str2 = "abcdef";
      #include<stdio.h>//证明相等关系
      int main()
      {
      	char arr1[] = "abcdef";
      	char arr2[] = "abcdef";
      	const char* str1 = "abcdef";
      	const char* str2 = "abcdef";
      	if (arr1 == arr2)
      		printf("arr1==arr2\n");
      	else
      		printf("arr1!=arr2\n");
      	if (str1 == str2)
      		printf("str1==str2\n");
      	else
      		printf("str1!=str2\n");
       
      	return 0;
      }

      为什么arr1!=arr2; str1==str2

      首先创建数组,arr1与arr2,先向系统申请两个不同的空间,然后将abcdef放入两个不同的空间里,所以这两个空间的地址当然就不相同

      其次是str1与str2的abcdef只存储在只读存储区(这跟const无关,const起到强调的作用,实际上有无const的意思是相同的),就是不能更改其中的元素;

      这时候两字符串的内容相同,系统就不会在浪费多余的空间去储存两个不同的内容~

      所以就形成str1==str2,实际上这两个变量储存的都是a的地址

      2.指针数组

      在这之前我们知道整形数组,浮点型数组等

      意思就是储存整形与浮点型数子的数组

      指针数组就是存放指针的数组,实际上还是数组,里面存放着不同类型的指针

      int*arr[5];//整形指针数组
      char*arr[5];//一级字符指针数组
      char**arr[5];//二级字符指针数组

      知道定义,那如何使用呢

      例题 2

      多组打印字符串

      #include<stdio.h>
      int main()//指针数组
      {
      	char* arr[] = { "abcdef","ghi" ,"jklmn" };
      	//打印
      	int i = 0;
      	int sz = sizeof (arr) / sizeof (arr[0]);
      	for (i = 0; i < sz; i++)
      	{
      		printf("%s\n",arr[i]);
      	}
      	return 0;
      }

      其中的    char* arr[] = { "abcdef","ghi" ,"jklmn" }就是指针数组,存放的就是char类型的指针,

      他的作用就相当于:

      char arr1[] = "abcdef";
      char arr2[] = "ghi";
      char arr3[] = "jklmn";

      打印的结果就是:

      ✨✨打印整形的数组

      #include<stdio.h>
      int main()
      {
      	//打印整形数
      	int arr1[] = { 1, 2, 3, 4, 5};
      	int arr2[] = { 2, 3, 4, 5, 6};
      	int arr3[] = { 3, 4, 5, 6, 7};
      	int* arr[] = { arr1,arr2,arr3 };
      	int i = 0; int j = 0;
      	for (i = 0; i < 3; i++)
      	{
      		for (j = 0; j < 5; j++)
      		{
      			printf("%d", arr[i][j]);
      		}
      		printf("\n");
      	}
      	return 0;
      }

      3.数组指针

      3.1数组指针的定义

      有了指针数组的概念,相信很多人就知道数组指针的概念

      跟你们想的一样

      数组指针—就是指针,什么指针呢,存放数组的指针

      int *pint;//能够指向整形数据的指针
      char *p;//能够指向字符数据的指针
      char (*p)[10];能够指向数组的指针

      它的类型包括int(*)[],  char(*)[]

      解读一下,其中的(*)就代表是一个指针[]就代表是一个数组的指针,char或者int就代表数组中的数据是啥类型的元素

      那么int * p1[10]与 int (*) p2[10]

      其中的p1 p2是什么意思呢

      前者是一个数组(指针数组)变量

      后者是一个指针(数组指针)变量

      3.2 &数组名与数组名

      我们知道数组名就是首元素的地址(数组名直接与sizeof相连与&数组名除外)

      那么&数组名是什么意思呢

      实际上&数组名就是整个数组的地址

      #include<stdio.h>
      int main()
      {
      	int arr[20] = { 0 };
      	printf("%p\n", arr);
      	printf("%p\n", arr+1);
      	printf("%p\n", &arr);
      	printf("%p\n", &arr+1);
      	return 0;
      }

      有打印的结果中很容易看出

      arr 与 arr+1隔四个字节,也就是一个整形的大小

      而&arr与&arr+1之间隔八十个字节,就是二十个整形的大小

      接下来在看一个代码

      int *p[10];
      int *(*pp)[10];

      此时的int *p[10],p就是指针数组变量

      int *(*pp)[10]这句的意思就是定义一个指针变量,什么指针?数组指针,而这个数组里的元素都是int*,有十个int*类型的数据。这就是数组指针~~

      故arr在一般情况下都是这个数组首元素的地址,&arr就是整个数组的地址

      3.3 数组指针的使用

      说完数组指针的定义和使用规则,下面讲解数组指针的使用

      给定三个数组,分别打印出他们的值

      #include<stdio.h>
      print(int (*p)[5],int r,int c)//此时的int (*p)[5]就是一个数组指针
      {
      	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)就相当于拿到第一行的地址
      		}
      	printf("\n");
      	}
       
      }
      int main()
      {
      	//打印数字
      	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
      	print(arr,3,5);
      	return 0;
      }

      打印的结果:

      这道题运用了一个二维数组

      在这里我将二维数组看作是一维数组,也就是把一行当作一个元素

      所以此数字有三个元素

      就算是说二维数组的数组名就是第一行的的地址

       当然一维数组里也可以用到数组指针的思想,但实际上也不需要,这样反而变得麻烦,反而在二维数组中可以很好的利用。

      那下面来总结一下:

      int *arr;
      int *parr1[10];
      int (*parr2)[10];
      int(*parr3[10])[10]

      int *arr

      是一个指针,类型是int*的指针;

      int *parr1[10]

      是一个数组,数组的类型是int *[],且是一个指针数组

      int (*parr2)[10]

      是一个指针,指针的类型是int(*)[],且是一个指针数组,数组中有十个整形元素

      int(*parr3[10])[10]

      是一个数组,此时把parr3去掉就得到nt(*)[],这里就是一个数组指针,所以这是一个存有十个数组指针的数组。

      4.数组与指针在函数里的传参

      函数传参的前提就是形参与实参是对应的关系,也就是说实参是什么类型,那么形参也是相应的类型,比如说实参是数组,那形参就是数组,参数是指针,那么形参也是指针。

      4.1 一维数组的传参

      #include<stdio.h>
      test(arr1[])
      {}
      test(arr1[10])//上面两种方法,实参是数组,用数组来接收,因为在函数中本身不创建空间,所以无论【】{}            //中的值为多少,都能达到目的~
       
      test(*arr1)
      {}          //前面我提到数组名就是首元素的地址,此时就把实参中的数组名当作首元素的地址,此时用指 
                  //针来接收
       
      test(*arr2[10])
      {}          //这里也跟第一二两个相同,也是数组传参,数组来接收
       
      test(**arr2)
      {}          //这里采用二级指针来接收,实参是一级指针,那么一级指针的指针就可以用二级指针来接收
      int main()
      {
      int arr1[10]={0};
      int* arr2[10]={0};
      test(arr1);
      test(arr2);
      return 0;
      }

      4.2 二维数组的传参

      #include<stdio.h>
      //通过数组
      test(int arr[10][10])      //二级指针传参时以数组的形式,【】中的行可以省略,但是列不能省略
      {}                         //列可以让系统知道一行有多少的元素,从而分配多少的空间,便于知道每一 
                                 //个元素的地址,故test(int arr[10][10])与test(int arr[][10])是可以 
                                 //进行传参的,但是test(int arr[][])显然是不可以的。
      test(int arr[][])
      {}
      test(int arr[][10])
      {}
      //通过指针
      test(int *arr)
      {}
      test(int (*arr)[10])      //我们知道实参传的是首元素的地址,这里的首元素的地址就是第一行元素的 
      {}                        //地址,也就是数组的地址,所以需要数组指针来接收
                                //故需要test(int (*arr)[10])来接收,而一级指针与二级指针显然是不可的 
       
      test(int** arr)
      {}
      int main()//二级指针传参
      {
      	int arr[10][10] = { 0 };
      	test(arr);
      	return 0;
      }

      4.3 一级指针的传参

      #include<stdio.h>
      test(int *p)
      {}
      int main()
      {
      	int a = 10;
      	int* par = &a;
      	int arr[10];
       
      	test(par);
      	test(arr);
      	test(*a);      //当一级指针传参时,形参是指针,那么实参就可以用这三种方式传参
       
      	return 0;
      }

      4.4 二级指针的传参

      #include<stdio.h>     //当二级指针传参时,形参是二级指针,那实参有哪些方式呢
      test(int** p)
      {}
      int main()
      {
      	//二级指针传参
      	char a = 'w';
      	char* p = &a;
      	char** pp = &p;
      	char* arr[10] = { 0 };
       
      	test(&p);        //通过一级指针p的取地址
      	test(pp);        //通过二级指针传参,用二级指针来接收
      	test(arr);       //通过char*()类型指针数组名来传递
       
      	return 0;
      }

      5.函数指针

      数组指针就是数组的地址

      那么函数指针也是存放函数地址

      那问题是函数有指针吗

      #include<stdio.h>
      int Add(int x,int y)
      {
      	int sum = 0;
      	sum = x + y;
      }
      int main()
      {
      	//证明函数有地址
      	int a = 0;
      	int b = 0;
      	printf("%p", &Add);
          printf("%p\n", Add);
      //定义一个函数指针变量
          int (*pf)(int,int)=&Add;  //此时的pf就是函数的指针变量,用pf就可以调用函数
      	return 0;                 //括号中是参数的类型
      }

      这段代码证明,函数也有地址,而且取地址加函数名与函数名的作用是相同的,既然函数有地址就可以通过指针调用这个函数

      调用这个函数:

      #include<stdio.h>
      int  Add(int x,int y)
      {
      	int sum = 0;
      	sum = x + y;
      	return sum;
      }
      int main()
      {
      	int (*pf)(int,int) = &Add;
      	int ret1=(*pf)(2, 3); //括号别忘了
      	int ret2 = (pf)(2, 3);//这里的括号可以省略
      	printf("%d\n", ret1);
      	printf("%d\n", ret2);
       
      	return 0;
      }

      打印的结果:

      可以看到,主函数中的pf与*pf的使用效果是一样的,打印的结果也是一样的,所以是否有*都可以达到相同的目的,但是*代表着解引用,容易理解~

      下面给出了一个有趣的代码:

      void(* signal (int,void (*) (int)) )(int)

      这段代码的意思是什么呢,是不是看着有点晕

      实际上把signal (int,void (*) (int))提出来剩下的就是void(*)(int),其中的signal是函数声明。

      首先这是一个函数指针,有两个参数,一个参数是int,还有一个是函数指针,其返回值就是void(*)(int),也就是一个函数指针。

      这个函数可以简化一下,也就是把void(*)(int)重新定义为一个新的变量

      你可能认为是typedef void(*)(int)  pfun_t

      这样的确好理解一些,毕竟跟我们所学习的结构体是一样的

      但是真正的结果是typedef void(*pfun_t)(int) ,其中的pfun_t放在中间,我这样学也起到了强调的作用。

      void(* signal (int,void (*) (int)) )(int)简化的结果是pfun-t signal(int,void (*) (int))

      pfun-t是类型名,并不是类型名

      6. 函数指针数组

      前面我们学了整形指针数组,字符指针数组,和他们相同,函数指针数组也是一个数组,只不过数组里的元素是整形指针。

      函数指针数组可以一次性实现多个函数的调用

      #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 (*pfAdd[4])(int, int) = { &Add ,&Sub,&Mul,&Div };//数组指针可以很好的实现多次的定义
      	int i = 0;
      	for (i = 0; i < 4; i++)
      	{
      		printf("%d\n", *(pfAdd[i])(8, 4));
      	}
      	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;
      }
      menu()
      {
      	printf("*****************************************\n");
      	printf("********  1.Add     2.Sub     ***********\n");
      	printf("********  3.Mul     4.Div     ***********\n");
      	printf("********  0.exit              ***********\n");
      	printf("*****************************************\n");
       
      }
      int main()//用函数指针来实现一个计算器
      {
      	int x = 0;
      	int y = 0;
      	int input = 0;
      	int ret = 0;
      	int (*pfArr[5])(int, int) = { 0, &Add ,& Sub,& Mul,& Div };
      	do
      	{
      		menu();
      		printf("请选择:>");
      		scanf_s("%d", &input);
      		if (input == 0)
      		{
      			printf("计算机以关闭");
      		}
      		else if (input >= 1 && input <= 4)
      		{
      			printf("请输入x与y:>");
      			scanf_s("%d %d", &x, &y);
      			ret = pfArr[input](x, y);
      			printf("%d\n", ret);
      		}
      		else
      			printf("选择错误");
      	} while (input);
      	return 0;
      }

      7.回调函数

      回调函数就是通过函数指针调用的函数,如果把一个函数的指针(地址)当作参数,传给另一个函数,当这个函数调用所指的函数时,我们就说这是回调函数。

      下面来用回调函数来实现上一次用函数指针数组实现的计算器

      #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;
      }
      menu()
      {
      	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;
      	printf("请输入两个数:>");
      	scanf_s("%d %d", &x, &y);
      	int ret = pf( x, y);
      	printf("%d\n", ret);
      }
      int main()
      { 
      	int input = 0;
      	do
      	{
      		printf("请选择:\n");
      		menu();
       		scanf_s("%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("退出计算器");
      			break;
      		default:
      			printf("选择错误");
      			break;
      		}
      	} while (input);
      	return 0;
      }

      回调函数很好的省略了case内部的重复代码过程,此时的加减乘除函数的指针当作参数传入calc函数里,这就是回调函数。

      结语:这一章,c语言的指针相关的知识就结束了

      在线客服
      服务热线

      服务热线

      4008888355

      微信咨询
      二维码
      返回顶部
      ×二维码

      截屏,微信识别二维码

      打开微信

      微信号已复制,请打开微信添加咨询详情!