C语言——深入解析C语言指针:从基础到实践从入门到精通(四)

🏡润下个人主页
🔥个人专栏 : 《C语言基础》

🏔️山高万仞,只登一步!



文章目录

前言:指针类型的不同会导致操作结果存在显著差异。本章将详细解析各类指针变量的特性差异,并通过具体实例展示其实际应用场景。

一.字符指针变量

字符型指针:char *

c 复制代码
int main()
{
	char ch[] ="a";
	char* pc = &ch;
	printf("%c\n", *pc);
	char arr[] = { "abcdef" };
	char* pa = arr;
	printf("%s\n", pa);
	return 0;
}

1.字符或者字符串可以放在数组中,将数组地址传递给字符指针变量
当然字符或者字符串也可以不用创建数组的方式。

2.字符可以直接将变量地址给字符指针变量,字符串可以直接创建在字符指针变量中

c 复制代码
int main()
{
	char ch = 'a';
	char* pc = &ch;
	printf("%c\n", *pc);
	char* pa = "abcdef";
	printf("%s\n", pa);
	return 0;
}

这两种创建方式的不同在于,通过数组的方式字符串 赋值给字符指针变量,其中的字符串内容是可以修改的。如果直接将字符串赋值 给字符指针,其中字符串内容无法修改的。是" 常量字符串 "

c 复制代码
int main()
{
	char* pa = "abcdef";//常量字符串不可改
	*pa = 'abc';
	printf("%s\n", pa);
	return 0;
}


为了防止出现低级错误通常用const修饰

c 复制代码
const char* pa = "abcdef";//常量字符串不可改

字符串abcdef是把首字符的地址放在字符指针变量中

一道有趣的代码: 😮

c 复制代码
#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;
}

运行结果:

❗️解析:

注:strcmp函数后期介绍

二.数组指针变量

2.1 数组指针变量是什么

之前学习指针数组,指针数组是一组存放指针(地址)的数组,是数组

那么数组指针本质上是指针变量

❗️类比不同类型的指针

字符指针:char *p 是指向字符的指针,存放的是字符的地址

c 复制代码
char* p;
char ch;
p = &ch;

整型指针: int * p是指向整型的指针,存放的是整型的地址

c 复制代码
int* p;
int a;
p = &a;

浮点型指针:float *p 是指向浮点型的指针,存放的是浮点型的地址

c 复制代码
float *pb;
float b;
pb = &b;

那么

数组指针:int(*p) [ 数组大小] 是指向数组的指针,存放的是指向数组的地址
*和p先结合说明p是指针变量,指针指向了一个数组大小为10的整型数组,p是指针指向了一个数组,叫数组指针

数组指针变量 指针数组
int (*p) [10] int * p[10]

数组指针变量中间的()不能去掉;去掉后p先和[ ]结合变成了 指针数组

2.2 数组指针变量如何初始化

通过取地址符&来取数组的地址

c 复制代码
int arr[10] = { 0 };
int (*p)[10] = &arr;


p的类型和&arr的类型完全一样
注:数组指针一次跳过的字节数

c 复制代码
int main()
{
	int arr[10] = { 0 };
	int (*p1)[10] = &arr;
	printf("&arr    =%p\n", *p1);
	printf("&arr+1  =%p\n", *(p1+1));
	return 0;
}  

三.二维数组传参的本质

之前在二维数组传参给一个函数的时候

c 复制代码
#include<stdio.h>
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 ", arr[i][j]);
		}
	}
}
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;
}

形参写成的是二维数组的形式,还可以写成数组指针的形式

🔑 原因

二维数组可以看做是一维数组的数组,二维数组的每一个元素都是一个一维数组。二维数组的首元素第一行一维数组的地址

所以二维数组的数组名就表示为第一行的地址,即一维数组的地址。

二维数组本质上也是传递的地址,传递的是第一行一维数组的地址,那么把二维数组的形参就可以用数组指针来接收!
就可以改写为:

c 复制代码
#include<stdio.h>
void test(int (*p) [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));
		}
		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;
}

🚩二维数组传参,形参可以写成数组,也可以写成指针的形式

四.函数指针变量

有前面的不同指针类型,可以类比函数指针变量 是用来存放函数地址的,可以通过地址来调用函数

指针类型 存放的地址类型 指向的变量 类型
字符指针 字符的地址 指向的是字符变量 char *p
整形指针 整形的地址 指向的是整型变量 int * p
数组指针 数组的地址 指向的是数组变量 int (* p )[数组大小]
函数指针 函数的地址 指向的是函数变量 type(*函数指针变量名)(参数...)

4.1函数指针变量的创建

数组有地址,函数也有地址

c 复制代码
#include<stdio.h>
void test()
{
	printf("hehehe\n");
}
int main()
{
	printf("test   =%p\n", test);
	printf("&test  =%p\n", &test);
	return 0;
}

函数有地址,函数名就是函数的地址 ,也可以通过&函数名来获得地址

想把函数名(函数地址)存起来就要放在函数指针变量中

c 复制代码
#include<stdio.h>
void test ()
{
	printf("hehe\n");
}
int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int (*pa)(int, int) = &Add;
	int r = Add(10, 20);
	printf("%d\n", r);
	void (*pb)() = &test;
	test();
	return 0;
}

函数指针类型解析:

c 复制代码
int (*p)(int x,int y)
 |     |        |
 |     |      指向函数的参数类型和个数
 |     |
 |  函数指针变量名
 p指向函数的返回类型
 int (*)(int x,int y)//该函数指针变量的类型

4.2函数指针变量的使用

在学习整形指针和字符指针的时候,可以通过 * 解引用找到元素值,在指针函数变量中也可以通过 *解引用函数名进行传参

c 复制代码
#include<stdio.h>
int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int* pa = &a;
	printf("%d\n", *pa);


	int (*pf)(int, int) = &Add;
	int r = (*pf)(10, 20);
	printf("%d\n", r);
	return 0;
}

在之前我们都是通过调用函数都是直接通过函数名来进行传参调用

c 复制代码
int r=Add(10,20);
printf("%d\n",r);

Add的地址能直接调用,那么(*pf)中存放的是函数Add的地址,也可以直接调用

c 复制代码
int (*pf)(int int)=Add;
int r=pf(10,20);
printf("%d\n",r);

函数调用:

1.可以通过函数名直接调用

2.可以通过函数指针变量名(间接)调用。


实例

c 复制代码
#include<stdio.h>
int Add(int a, int b)
{
	return a + b;
}
int main()
{

	int (*pf)(int, int) = &Add;
	int w = (*pf)(10, 20);
	int r = pf(10, 20);
	printf("%d\n", w);
	printf("%d\n", r);
	return 0;
}

两段有趣的代码😆

c 复制代码
1.( *( void( *)())0)()

代码分析:

1.上述代码是一次函数的调用,这个函数没有参数 列如:(*pf)()(pf)是函数名

2.代码中void(*)()函数指针类型 ,这个指针指向的函数没有参数返回值为void,(void(*)())0表示把 0 强制转化 为了该函数指针类型 ,相当于0地址处有这样一个返回值为void,没有参数的函数。

3.0的类型为函数指针变量,那么就可以解引用*(0)()

最终得到( *( void( *)())0)()


c 复制代码
2.void(*signal(int,void(*)(int)))(int);

代码分析:

1.上述代码是一次函数声明。

2.函数名字叫:signal,函数有两个参数,intvoid(*)(int),第二个参数为函数指针类。signal函数的返回值也是void(*)(int)

函数声明,调用和定义:

以函数Add()为例

函数 Add
函数调用 Add()
函数定义 int Add(int x,int y)
函数声明 int Add(int,int)

对函数概念有疑惑的朋友可以看这篇文章:

🚀C语言------函数(超详细分析)

4.3 typedef关键字

typedef是用来类型重命名 的,可以把复杂类型简单化

例如:

4.3.1普通类型:

感觉unsigned int书写起来不方便可以用typedef重命名uint

typedef unsigned int uint

c 复制代码
#include<stdio.h>
typedef unsigned int uint;
int main()
{
	unsigned int num1= 100;
	uint num2 = 100;
	return 0;
}

num1和num2类型相同

4.3.2 指针类型

重命名:把int*重命为p_t

typedef int* p_t

c 复制代码
typedef int* p_t;
int main()
{
	int* p1, p2;
	p_t p3, p4;
	return 0;
}

注:int *p1,p2为(int类型)

4.3.3数组指针类型:

把int(*)[5]重新命名为parr_t

typedef int(*parr_t)[5]

c 复制代码
typedef int (*parr_t)[5];
int main()
{
	int arr[5] = { 0 };
	int (*p)[5] = &arr;
	parr_t p2 = &arr;
	return 0;
}

4.3.4函数指针类型:

typedef int(*pf_t)(int, int)

c 复制代码
#include<stdio.h>
typedef int(*pf_t)(int, int);//新的函数名必须在*右边
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = &Add;
	pf_t pf2 = Add(10,20);
	printf("%d\n", pf2);
	return 0;
}

简化代码:

c 复制代码
void(*signal(int,void(*)(int)))(int)

把void(*)(int)简化为pf_t

c 复制代码
#include<stdio.h>
typedef void(*pf_t)(int);
void test()
{

}
pf_t signal(int, pf_t);//函数声明
int main()
{
	pf_t signal(int, pf_t);
	signal(100, &test);//函数调用

	return 0;
}
pf_t signal(int n, pf_t p)//函数定义
{
	return p;
}

五.函数指针数组

有字符数组,整型数组,指针数组就可以类比出函数指针数组

函数地址 存放至数组 中就是函数指针数组

数组类型 存放的数据类型 格式[大小]
字符数组 字符 char [5]
整型数组 整型 int [5]
指针数组 指针 char *arr [5]
函数指针数组 函数指针(地址) int(*parr[ 5 ])()

实例:

c 复制代码
#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 (*pArr[4])(int, int) = { Add,Sub,Mul,Div };
	int i = 0;
	for ( i = 0; i < 4; i++)
	{
		int r = pArr[i](12, 4);
		printf("%d\n", r);
	}
	return 0;
}

六.转移表

函数数组指针的应用

c 复制代码
#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;
}
void 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, y;
	int input = 1;
	int ret = 0;
	do
	{
	Menu();
	printf("请选择:\n");
	scanf("%d", &input);
	switch (input)
	{
	case 1:
		printf("请输入操作数:");
		scanf("%d %d", &x, &y);
		ret = Add(x, y);
		printf("%d\n", ret);
		break;
	case 2:
		printf("请输入操作数:");
		scanf("%d %d", &x, &y);
		ret = Sub(x, y);
		printf("%d\n", ret);
		break;
	case 3:
		printf("请输入操作数:");
		scanf("%d %d", &x, &y);
		ret = Mul(x, y);
		printf("%d\n", ret);
	case 4:
		printf("请输入操作数:");
		scanf("%d %d", &x, &y);
		ret = Div(x, y);
		printf("%d\n", ret);
		break;
	case 0:
		printf("退出\n");
		break;
	default:
		printf("输入错误,请重新输入\n");
		break;
	}
	} while (input);

	return 0;
}

优化:

c 复制代码
#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;
}
void 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, y;
	int input = 1;
	int ret = 0;
	int (*pArr[5])(int, int) = { 0,Add,Sub,Mul,Div };
	do
	{
		Menu();
		printf("请选择:\n");
		scanf("%d", &input);
		if ((input > 0) && (input <= 4))
		{
			printf("请输入操作数:");
			scanf("%d %d", &x, &y);
			ret = (*pArr[input])(x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出转移表\n");
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}
		
	} while (input);

	return 0;
}

通过函数指针数组的优化把代码冗余的部分给省略了

总结

指针部分内容到此分享结束!感谢大家的支持!

指针所有分享!

🚀 C语言------深入解析C语言指针:从基础到实践从入门到精通(三)

🚀C语言------深入解析C语言指针:从基础到实践从入门到精通(二)

🚀C语言------深入解析C语言指针:从基础到实践从入门到精通(一)

相关推荐
weixin_307779134 小时前
在Linux服务器上使用Jenkins和Poetry实现Python项目自动化
linux·开发语言·python·自动化·jenkins
koo3644 小时前
李宏毅机器学习笔记25
人工智能·笔记·机器学习
Empty_7774 小时前
Python编程之常用模块
开发语言·网络·python
余俊晖4 小时前
如何让多模态大模型学会“自动思考”-R-4B训练框架核心设计与训练方法
人工智能·算法·机器学习
hzp6664 小时前
Magnus:面向大规模机器学习工作负载的综合数据管理方法
人工智能·深度学习·机器学习·大模型·llm·数据湖·大数据存储
小火柴1234 小时前
利用R绘制箱线图
开发语言·r语言
hui梦呓の豚4 小时前
YOLO系列目标检测算法全面解析
人工智能·计算机视觉·目标跟踪
一水鉴天4 小时前
整体设计 逻辑系统程序 之27 拼语言整体设计 9 套程序架构优化与核心组件(CNN 改造框架 / Slave/Supervisor/ 数学工具)协同设计
人工智能·算法
wheeldown4 小时前
【Linux】Linux 进程通信:System V 共享内存(最快方案)C++ 封装实战 + 通信案例,4 类经典 Bug 快速修复
linux·运维·服务器·开发语言