C语言指针详解

2023-07-30 15:26:12

博主主页zoro-1
祝大家有个好心情,给大家分享一下我拍的彩虹
在这里插入图片描述
在这里插入图片描述

字符指针

1.如何定义

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

2.类型和指向的内容

譬如char pc=&ch;
将=左边pc去掉就是指针类型char

将*和pc去掉就是指针指向的内容char

3.代码例子

例1

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

这时我就要问一个问题了,pstr存储的是什么?
没错pstr存储的是hello的首元素地址

例2

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

大家猜猜这段代码会输出什么?
在这里插入图片描述
代码解释:str1,str2是存放char的数组的数组名,那么他们存放的就是数组首元素的地址,即使他们的内容相同,但地址是随机的,而str3,str4是被const修饰的指针变量,存放的也是字符的地址只不过这里的hello bit.在这里是存放在常量池的下一个str4不需要开辟新的空间,所以str3,str4他们指向的是同一个hello bit.所以相同

一个小知识点:
如果 const 用于修饰字符串常量,那么该字符串常量将存储在常量存储区(Constant Storage Area)。
常量存储区是用于存储常量字符串和全局常量的特殊内存区域,其中的数据在程序运行期间保持不变。

指针数组

1.如何定义

int main(){
int arr[5]={1,2,3,4,5};
int arr1[5]={2.3.4.5.6};
int arr2[5]={3,4,5,6,7};
int*p[3]={arr,arr1,arr};
}

2.类型和内容

去掉名字p就是类型
去掉p【3】就是存储的内容int*

数组指针

1.如何定义


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

2.类型和指向类型

去掉名字p就是类型
去掉*p就是指向内容

3.数组名vs&数组名

数组名是数组的首元素地址
但有两个例外:
1.&arr得arr表示整个数组的地址
2.sizeof(arr)表示整个数组的大小

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

大家猜猜这段代码执行结果是什么,如果看过我之前的指针初阶就应该知道他们的输出内容一样
在这里插入图片描述
代码解释:arr是数组首元素地址,&arr是整个数组的地址,虽然他们输出的内容一样,但是他们的权重不一样,
arr+1会跳过4字节,&arr+1会跳过整个数组也就是10*4,一共40个字节

#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的区别了

数组指针运用

我们很少用在一维数组,多数用在二维数组,接下来我用代码让大家感受一下数组指针
一维数组:

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

二维数组:

一个数组指针的使用:
学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:
#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]);//相当于*(*(arr+i)+j)
}
printf("\n");
}
}
#include <stdioh>
int main()
{
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
我们发现这两个遍历数组的1方法只有接收数组第一个形参不一样这里写成int arr[3][5]是为了大家好理解,
编译器会将int arr[3][5]转化成int (*arr)[5]
print_arr2(arr, 3, 5);
return 0;
}

在这里插入图片描述

数组参数&指针参数

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

一维数组传参

#include <stdio.h>
void test(int arr[])//ok?
显然是可以的传一维数组用一维数组接收
{}
void test(int arr[10])//ok?
显然是可以的只是加上了长度
{}
void test(int *arr)//ok?
这种可以int*arr是整型指针,一维数组传过来是第一个元素地址,第一个元素也是整型
{}
void test2(int *arr[20])//ok?
这种显然可以的传的是指针数组,也是用指针数组接收
{}
void test2(int **arr)//ok?
传的是指针数组的首元素地址,而元素又是指针所以用二级指针接收,二级指针解引用一次得到首元素(首元素就是指针int*{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}

二维数组传参

void test(int arr[3][5])//ok?
显然是可以的传二维数组用二维数组接收
{}
void test(int arr[][])//ok?
虽然形参是二维数组,但是不能省略列
{}
void test(int arr[][5])//ok?
行可以省略列不能省略所以可以
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?
二维数组的第一个元素是一维数组不能用整形指针接收而应该用数组指针接收
{}
void test(int* arr[5])//ok?
这是指针数组所以不对
{}
void test(int (*arr)[5])//ok?
这是数组指针所以可以

{}
void test(int **arr)//ok?
这是二级指针不行,因为传过来的是一维数组地址
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}

总结:
一维整形数组传参数,可以用一维整形数组接收,也可以用整形指针接收
一维指针数组,可以用一维指针数组接收,也可以用二维指针接收
二维整形数组传参数,可以用二维整形数组接收,也可以用数组指针接收

一级指针传参

#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);
//也可以直接写成printf(p,sz);
return 0;
}

二级指针传参

当函数参数是二级指针,可以接收哪些参数

void test(char **p)
{
}
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
test(arr);//Ok?
return 0;
}

函数指针

上面介绍了整形指针,数组指针,那么函数有没有指针呢?
答案是有

1.如何定义

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

2.类型和指向内容

去掉名字p就是类型
去掉*p就是指向内容

3.函数名vs&函数名

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

注:函数名是函数地址,&函数名也是函数地址,没有区别
在这里插入图片描述

4.两个有趣的代码

:推荐《C陷阱和缺陷》
这本书中提及这两个代码。
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

*代码1解释是将0看做成地址强制转化成函数指针然后解引用,调用函数
代码2解释signal()是一个函数有两个参数int,viod(*)(int),返回值是一个函数指针
代码2简化
typedef void(pfun_t)(int);
pfun_t signal(int, pfun_t);

函数指针数组

有指针数组,那么有没有函数指针数组呢
答案是有

1.如何定义


#include <stdio.h>
#include <string.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};
	//
	return 0;
}

2.类型和内容

去掉pfArr就是类型
去掉pfArr【4】就是存储的内容函数指针

3.1代码例子(switch语句实现计算器)

如果让你们实现自算器的简易功能,你们会怎么实现

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 menu()
{
	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;
	do
	{
		menu();
		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;
}

有没有感觉上面这段代码很冗杂需要这么多次case
接下来我用函数指针数组再来实现一下

3.2代码例子(函数指针数组实现计算器)



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 menu()
{
	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};
	//                            0     1    2    3    4
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("ret = %d\n", ret);
		}
		else if(input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("选择错误,重新选择\n");
		}
	} while (input);

	return 0;
}

这里我们将函数储存在数组里面,直接通过下标调用是不是感觉很方便

指向函数指针数组的指针

这里给大家表演一个套娃,是不是感觉很绕口,这里我们不过多解释只讲定义(用处不多);

1.如何定义

void test(const char* str)
{
	printf("%s\n", str);
}

int main()
{
	void (*pf)(const char*) = test;//pf是函数指针变量
	void (*pfArr[10])(const char*);//pfArr是存放函数指针的数组
	void (* (*p) [10])(const char*) = &pfArr;//p指向函数指针数组的指针

	return 0;
}

2.类型和指向内容

去掉p就是类型
去掉*p就是指向内容

回调函数

在这里插入图片描述

这个意思就是有函数A,B,main方法调用函数A将函数B地址作为参数传到函数A,A中利用函数地址调用函数B
这个我会在下一篇博客讲解qsort时举例讲解,

一个小知识点如何找到指针和数组的类型和(指向)存储内容

*类型都是去掉名字
指针指向的内容是去掉(指针名字)
数组存储内容是去掉名字和【】(中括号)

更多指针相关内容请听下回讲解,看到这里了,不妨给博主给个三连,要是想持续收听,也可以关注博主, 让我们一起变得更强吧,大家加油!!!!!

在这里插入图片描述

更多推荐

springboot

Springboot入门Springboot入门springboot提供了一种快速使用spring项目的方式,而不是对spring功能的增强,本文参考https://www.bilibili.com/video/BV1Lq4y1J77x?p=12&vd_source=0882f549dac54045384d4a9215

vue的事件处理

1、监听事件我们可以使用v-on指令(通常缩写为@符号)来监听DOM事件,并在触发事件时执行一些JavaScript。用法为v-on:click=“methodName”或使用快捷方式@click=“methodName”例如:<divid="basic-event"><button@click="counter+=1

✽js的快速入门5 window对象

BOM浏览器对象模型:是规范浏览器对js语言的支持(js调用浏览器本身功能)BOM的具体实现是window对象window对象使用1.不用new直接进行使用即可,类似Math的使用方式,window关键字可以省略不写window.document等等2.框体方法<!DOCTYPEhtml><htmllang="en">

vue中的 render 和 h() 详解

vue中的render和h()详解当使用Vue.js进行前端开发时,理解和掌握"render"函数和"h()"函数是非常重要的,因为它们是Vue组件的核心构建和渲染部分render和h()是在Vue.js中常用的两个概念,它们通常用于创建和渲染Vue组件。什么是"render"函数?"render"函数是Vue组件的一

Vue系列之入门篇

前言:目录一,关于Vue的简介1.什么是Vue?2.使用Vue框架的好处?3.库和框架的区别:4.MVVM的介绍5.Vue的入门案例二,Vue的生命周期一,关于Vue的简介1.什么是Vue?Vue是一个构建用户界面(UI)的渐进式JavaScript框架2.使用Vue框架的好处?简单易学:Vue的API设计简洁,易于理

(一)探索随机变量及其分布:概率世界的魔法

文章目录🍋引言🍋什么是随机变量?🍋离散随机变量🍋连续随机变量🍋随机变量的概率分布🍋离散概率分布🍋0-1分布(Bernoulli分布)🍋二项分布(Binomial分布)🍋泊松分布(Poisson分布)🍋几何分布(Geometric分布)🍋连续概率分布🍋均匀分布(UniformDistributio

基于Java+微信小程序实现《微信阅读平台》

博主介绍:✌全网粉丝30W+,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌🍅文末获取源码联系🍅👇🏻精彩专栏推荐订阅👇🏻不然下次找不到哟2022-2024年最全的计算机软件毕业设计选题

软件定义世界,工程引领未来——中山大学软件工程学院 软件工程导论大作业

目录软件工程,理解加深个人困惑软件与软件工程的定义学习思路的启发软件危机的认识及思考软件测试的初步认识科技前沿,守正创新代码有智能,教育有情怀深入浅出,引人入胜再接再厉,未来可期“软件定义世界”是软工人的响亮口号,“工程引领未来”是我在上完导论课后的总结与思考。软件工程导论课作为软件工程学院开设的特色课程以及专业必修课

03、JSP核心技术

1JSP的概述(熟悉)1.1JSP的概念JSP是JavaServerPages的简称,跟Servlet一样可以动态生成HTML响应,JSP文件命名为xxx.jsp。与Servlet不同,JSP文件以HTML标记为主,然后内嵌Java代码段,用于处理动态内容。1.2JSP的示例1.3JSP与Servlet的关系2JSP的

设计模式之模板模式

文章目录豆浆制作问题模板方法模式基本介绍模板方法模式原理类图对原理类图的说明-即(模板方法模式的角色及职责)模板方法模式解决豆浆制作问题模板方法模式的钩子方法模板方法模式的注意事项和细节豆浆制作问题编写制作豆浆的程序,说明如下:制作豆浆的流程选材—>添加配料—>浸泡—>放到豆浆机打碎通过添加不同的配料,可以制作出不同口

强化学习从基础到进阶-案例与实践[6]:演员-评论员算法(advantage actor-critic,A2C),异步A2C、与生成对抗网络的联系等详解

【强化学习原理+项目专栏】必看系列:单智能体、多智能体算法原理+项目实战、相关技巧(调参、画图等、趣味项目实现、学术应用项目实现专栏详细介绍:【强化学习原理+项目专栏】必看系列:单智能体、多智能体算法原理+项目实战、相关技巧(调参、画图等、趣味项目实现、学术应用项目实现对于深度强化学习这块规划为:基础单智能算法教学(g

热文推荐