《C语言指针进阶》:字符指针、数组指针、指针数组、数组传参和指针传参、函数指针、函数指针数组、函数指针数组的指针

指针可以说是在C语言中非常重要的一个知识点,同时也是一道比较难以理解的一块内容。在刚学习指针的时候,我们已经了解了指针的一些基本概念:

  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
  4. 指针的运算。

相较于一些其他的语言来说,C语言中的指针独具一格,可以说它是C语言的灵魂所在,本篇文章创作目的旨在让想进一步了解指针运用的小伙伴能有所收获,同时如果大家对指针的某些内容不太熟悉或遗忘,能够为大家提供参考和回顾的作用。

一、字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char*。一般使用如下所示

1.单纯用于指向某个字符型变量
 

int main()
{
char ch = 'w';
char *pc = &ch;
*pc = 'w';
return 0;
}

值得说明的是字符指针所指向的字节大小时1个字节(任何一种指针所指向空间的大小是由它的类型决定的,例如除字符指针外int*、double*每次指向的空间大小单位分别为4个字节和8个字节)。因此如果我们想通过字符指针来改变一个整形类型的值时,应该使用4次字符指针。

2.用于模拟字符串数组

int main()
{
const char* pstr = "hello word.";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;
}

上述代码运行后输出“hello world”。那么我们应该如何来理解 上述的代码呢?

  • 首先const修饰的指针,说明*pstr的内容是不可改变的。之所以这样声明,是因为讲一段字符串复制给指针与数组是有区别的:简单来讲如果赋值给数组,那么这段字符串中的内容是是可变的;而如果是直接使用指针指向这段字符串,那么这段字符串就相当于是一个字符串常量,其内容是不可改变的,因此我们常常会使用const关键字进行修饰。

  • 其次我们知道指针指向的字符串,实质上是指向字符串首个字符的地址,之所以能够使用“%s”进行打印,是因为这里的指针的作用就相当于是字符型数组的数组名。

知识拓展:当两个不同的字符型指针所指向的字符串内容相同时,两个指针所指向的地址空间是否相同呢?例如下面这段代码,情分析输出的结果是什么?

#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");
    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;
}

当需要推断上述程序输出结果时,首先我们得先理解if条件语句中所表示的含义。两个if条件中所比较的内容分别是两个数组名和连个字符型指针变量,而不论是那种情况,实质上所比较的都是地址所在的位置,因此如果地址相同则输出if中的内容,否则输出else中的内容

再明白程序的含义之后,我们很清楚的知道不同的数组名所存储的地址是不相同的,因此第一个比较输出的结果应该是:“str1 and str2 are not same”。那么指针是否如同数组名一样呢?

接下来让我们一起验证一下实际结果:

我们惊讶的发现这里str3和str4指向的是一个同一个常量字符串。也就是说C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。

二、指针数组

指针数组顾名思义:它是一个数组,并且数组中存放的内容是指针。

为了让大家具体的了解其含义,一下是几种不同的指针数组:

int* arr1[10]; //存放整形指针的数组
char *arr2[4]; //存放字符指针的数组
char **arr3[5];//存放二级字符指针的数组

三、数组指针

1.数组指针的定义

数组指针他并不是数组而是一种指针,他所指向的对象是数组。

在此之前我们先回顾一下指针的基础内容:

整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针

那么能够指向数组的数组指针是如何声明的呢?下面我们将声明一个指向含有10个元素的整型数组指针:

int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
//指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

2.&数组名VS数组名

对于下面的数组:

int arr[10];

arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。那&arr数组名到底是啥?如果arr与&arr是完全相同的,&arr也是表示的数组的首元素地址,那这样做不会显得很多余吗,我们应该如何看待&arr呢?

我们不妨先来看一段代码:

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

所显示输出结果如下,可以看出打印的地址是相同的,可能在这里有些认为数组名和&数组名是相同的小伙伴心里比较开心了,不过小风想说先别急哈,好戏还在后头呢!

我们再看一段代码:

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

结果输出的结果如下:

看见这个结果是不是很出乎大家的意料呢?根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是整个数组的地址,而不是数组首元素的地址。
本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型
数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40。(望诸君牢记)

3.数组指针的应用

那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
//但是我们一般很少这样写代码
return 0;
}

上述的代码其实很少这样来使用数组指针,主要是为了让大家理解其含义。数组指针的应用通常可以用来接收二维数组参数的传递,例如下面代码(可以参照print_arr1来理解print_arr2,两者实现的功能是完全一样的)

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
    int i = 0;
    for(i=0; i<row; i++)
    {
        for(j=0; j<col; j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
void print_arr2(int (*arr)[5], int row, int col)
{
    int i = 0;
    for(i=0; i<row; i++)
    {
        for(j=0; j<col; j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
int main()
{
    int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
    print_arr1(arr, 3, 5);
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以数组指针来接收
    print_arr2(arr, 3, 5);
    return 0;
}

四、数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

1.一维数组传参

#include <stdio.h>
//可行的传递参数方式
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
int main()
{
    int arr[10] = {0};
    int *arr2[20] = {0};
    test(arr);
    test2(arr2);
}

2.二维数组传参

二维数组的行数可省,但列数一定不能省!

void test(int arr[3][5])//ok
{}
void test(int arr[][])//no
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
int main()
{
    int arr[3][5] = {0};
    test(arr);
}

3.一级指针传参及应用

#include <stdio.h>
void print(int *p, int sz)
{
    int i = 0;
    for(i=0; i<sz; i++)
    {
        printf("%d\n", *(p+i));
    }
}
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9};
    int *p = arr;
    int sz = sizeof(arr)/sizeof(arr[0]);
    //一级指针p,传给函数
    print(p, sz);
    return 0;
}

4.二级指针传参及应用

#include <stdio.h>
void test(int** ptr)
{
    printf("num = %d\n", **ptr);
}
int main()
{
    int n = 10;
    int*p = &n;
    int **pp = &p;
    test(pp);
    test(&p);
    return 0;
}

五、函数指针

函数指针指的是指向函数的指针

 1.函数指针的定义

在讲解函数指针前,我们不妨先看一段代码:

#include <stdio.h>
void test()
{
    printf("hehe\n");
}
    int main()
{
    printf("%p\n", test);
    printf("%p\n", &test);
    return 0;
}

这段程序输出的是两个地址,这两个地址是 test 函数的地址。我们说有地址的地方就肯定可以使用指针去指向它,那么我们函数指针的是如何来接收函数(或指向函数)的呢?

2.使用函数指针调用函数

在上节内容中我们已经学会了如使用函数指针接收参数,那么我们又该如和使用函数指针来调用函数呢?请参考下列代码中的实现方法:

#include <stdio.h>
int add(int x, int y)
{
    return x + y;
}
int main()
{
   int x = 1, y = 2;
   int (*ptr)(int, int) = add;  //使用函数指针接收函数
   int ret = (*ptr)(x, y);  //使用函数指针调用函数
   printf("%d", ret);
   return 0;
}

 不过可能有些小伙伴不禁有这样的一个疑问:我直接调用函数好像更简单吧哈哈!

在这里呢,小风所展示的实例代码最主要还是带着大家能理解函数指针的运用,循序渐进由浅入深,而不在于程序本身,希望大家能够理解。

六、函数指针数组(函数指针的进阶运用)

1.函数指针数组的定义

数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组,那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

//函数指针数组的定义
int (*parr1[10])(type1, type2...);
//*parr1[10]指的是指针数组,存储的指针类型是int(*)(type1, type2...)
//类型的函数指针

2.函数指针数组的应用(模拟实现计算器)

请大家仔细品析下面两种实现代码,相互对比,可能大家读完那之后就会觉的函数指针真的泰裤辣!

实现方式一(普通方法)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

#include <stdio.h>
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;
}

在上面的这段代码中,我们不难发现Switch分支结构中出现了很多相类似的代码片段,导致整体显得有些庞杂,不太美观。但当我们想要进行缩减时又好像无从下手,这是我们就需要使用到我们刚刚学习到的函数指针数组啦!

实现方式二(函数指针数组or回调函数)

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

#include <stdio.h>
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;
	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;
}

看到这里,大家是否被函数指针的作用给惊艳到了呢哈哈!

事实上这种方法应该称作回调函数的应用,相信大家通过上述的案例也能了解到该种方法的重要性吧!不过小风在这本篇文章中就不做过多展开了,如果有想要继续深入学习小伙伴可以打开小风的主页查看专门讲解回调函数的相关内容(《C语言回调函数详解——模拟实现qsort万能排序函数》-CSDN博客),同样也是干货满满呢!

七、函数指针数组指针

指向函数指针数组的指针是一个指针,指针指向一个数组 ,数组的元素都是函数指针

 看到上述的定义,大家是否被绕晕了呢哈哈,有点像套娃感觉,大家可以参考之前所了解的内容自己进行分析一下函数指针数组指针又该如何定义。

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;
}

这一块的内容使用情况也比较少,大家简单了解一下即可。

总结

本篇文章涉及指针的进阶使用,覆盖指针的知识面还是比较全面的,适合有对指针一定基础的小伙伴们参考。小风希望本篇文章能够对大家带来一定的帮助,同时如果有疑题的话可以私信或者在评论区留言,小风看到了会第一时间回复大家!

最后如果觉得文章内还不错的话,就关注一下博主吧哈哈!大家的肯定也是小风不断创作更优质内容的动力源泉!

  • 6
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

whelloworldw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值