C语言-指针详解

目录

  • 一、指针数组
  • 二、数组指针
  • 三、数组参数、指针参数
    • 3.1 一维数组传参
    • 3.2 二维数组传参
    • 3.3 一级指针传参
    • 3.4 二级指针传参
  • 四、函数指针
  • 五、函数指针数组
  • 六、指向函数指针数组的指针
  • 七、回调函数
  • 八、指针和数组的试题解析

一、指针数组

使用指针数组实现二维数组

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[3] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

二、数组指针

类比
  整型指针–指向整型变量的指针,存放整型变量的地址的指针变量
  字符指针–指向字符变量的指针,存放字符变量的地址的指着变量
  数组指针–指向数组的指针,存放的是数组的地址的指针变量

数组名的理解
  数组名是数组首元素的地址
  有两个例外:
1.sizeof(数组名),这里的数组名不是数组首元素的地址,数组名表示整个数组。sizeof(数组名)计算的是整个数组的大小,单位是字节。
2.&数组名,这里的数组名表示整个数组,&数组名取出的是整个数组的地址。
除此之外,所有地方的数组名都是数组首元素的地址

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);//int*
	printf("%p\n", arr+1);

	printf("%p\n", &arr[0]);//int*
	printf("%p\n", &arr[0]+1);

	printf("%p\n", &arr);//int(*)[10]数组指针类型
	printf("%p\n", &arr+1);

	return 0;
}

数组指针的使用

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int(*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p,类型是int(*)[10]
	return 0;
}
void print(int (*p)[5], int r, int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		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 };
	//arr 是数组名 
	//二维数组的每一行可以理解为二维数组的一个元素,每一行又是一个一位数组,所以呢?二维数组其实是一维数组的数组
	//二维数组的数组名,也是数组名,数组名就是数组首元素的地址
	//arr-首元素的地址
	//-第一行的地址
	//-一维数组的地址-数组的地址
	print(arr, 3, 5);
	return 0;
}

三、数组参数、指针参数

3.1 一维数组传参

#include <stdio.h>
void test(int arr[])//ok  数组传参用数组接收  大小可以省略
{}
void test(int arr[10])//ok  数组传参用数组接收  大小没有省略
{}
void test(int* arr)//ok    arr数组名表示首元素地址,可以用指针接收
{}

void test2(int* arr[20])//ok   数组传参每个类型是int*,用数组接收
{}
void test2(int** arr)//ok  arr2每个元素都是int*,传的是int*类型的地址,可以用二级指针接收
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
}

3.2 二维数组传参

void test(int arr[3][5])//ok  数组传参用数组接收
{}
void test(int arr[][])//ok?不行!   //形参部分,行可以省略,但是列不能省略(只能省略第一个[]的数字)
{}
void test(int arr[][5])//ok   //形参部分,行可以省略,但是列不能省略(只能省略第一个[]的数字)
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//ok?  不行!    //arr数组名是一行的地址,不是整型的地址
{}
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);
}

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

思考:当一个函数的参数部分分为一级指针的时候,函数能接收什么参数?

void test(char *p)
{}

char ch='2';
char *ptr=&ch;
char arr[]="abcdef";

test(&ch);
test(ptr);
test(arr);

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

思考:当函数的参数为二级指针的时候,可以接收什么参数?

void test(char** p)
{}

char n='a';
char* p=&n;
char** pp=&p;
char* arr[5];

test(&p);
test(pp);
test(arr);

四、函数指针

函数指针–指向函数的指针

int Add(int x, int y)
{
	return x + y;
}

int main()
{
	//int arr[10] = { 0 };
	//&arr;
	//int(*p)[10] = &arr;   //不写&表示数组首元素的地址(int的地址:int *),不是数组的地址(数组指针:int* [10])

	printf("%p\n", &Add);
	printf("%p\n", Add);
	//函数名是函数的地址
	//&函数名也是函数的地址

	int (*pf)(int, int) = &Add;//pf是函数指针变量
	//int (*)(int,int)是函数指针类型
	return 0;
}

  函数名是函数的地址。
  &函数名也是函数的地址。

void test(char* pc, int arr[10])
{}

int main()
{
	void (*pf)(char*,int [10])=&test;
	//void (*pf)(char*, int*); // 或者使用 int[],但不需要大小  test的&也可以去掉
	return 0;
}
int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int (*pf)(int, int) = &Add;
	//int (*pf)(int, int) = Add;
	int r = Add(3, 5);
	printf("%d\n", r);

	int m = (*pf)(4, 5);//*也可以去掉
	printf("%d\n", m);
	return 0;
}
void(*p)()-p是函数指针
void (*) ()是函数指针类型

int main()
{
	//调用0地址处的函数
	//1.将0强制类型转换void (*) ()类型的函数指针
	//2.调用0地址处的这个函数
	( *( void (*) ())0 ) ();
	return 0;
}
int main()
{
	//signal 是一个函数声明
	//signal 函数有2个参数,第一个参数的类型是int,第二个参数的类型是void(*)(int)函数指针类型
	//该函数指针指向的函数有一个int类型的参数,返回类型是void
	//signal 函数的返回类型也是void(*)(int) 函数指针类型,该函数指针指向的函数有一个int类型的参数,返回类型是void
	
	typedef void(*pf_t)(int);
	pf_t signal(int, pf_t);

	void (*  signal(int, void(*)(int))  )(int);
	return 0;
}

五、函数指针数组

int(*p[4])(int x, int y) = {  add, sub, mul, div };

六、指向函数指针数组的指针

int (*pf)(int, int);//函数指针
int (*pfArr[4])(int, int);//函数指针数组
int(*(*p[4])(int, int)) = &pfArr;//函数指针数组的地址
//p就是指向函数指针数组的指针

七、回调函数

  回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

void的指针-无具体类型的指针
void
类型的指针可以接收任意类型的地址
这种类型的指针是不能直接解引用操作的
也不能直接进行指针运算的

八、指针和数组的试题解析

int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));//4*4=16
	printf("%d\n", sizeof(a+0));//数组名a是数组首元素的地址,a+0还是首元素的地址,地址打大小4/8
	printf("%d\n", sizeof(*a));//数组名a是数组首元素的地址,*a就是首元素,大小就是4个字节
	printf("%d\n", sizeof(a+1));//数组名a是数组首元素的地址,a+1是第二个元素的地址,地址的大小4/8
	printf("%d\n", sizeof(a[1]));//第二个元素的大小就是4个字节
	printf("%d\n", sizeof(&a));//&a是数组的地址,数组的地址也是地址,是地址4/8个字节
	printf("%d\n", sizeof(*&a));//16个字节
	//sizeof(*&a)-->sizeof(a)-16
	//&a-->int(*)[4]
	//*&a-->访问的是一个数组
	printf("%d\n", sizeof(&a+1));//&a+1相对于&a是跳过了整个数组,但是即使跳过了整个数组,&a+1依然是地址,是地址就是4/8字节
	printf("%d\n", sizeof(&a[0]));//&a[0]是首元素的地址,4/8个字节
	printf("%d\n", sizeof(&a[0]+1));//&a[0]是首元素的地址,&a[0]+1就是第二个元素的地址,是地址就是4/8个字节
	return 0;
}
int main()
{
	char arr[] = { 'a','b','c','d','e','f' };

	printf("%d\n", strlen(arr));//因为字符数组arr中没有\0,所以在求字符串长度的时候,会一直往后找,产生的结构就是随机值
	printf("%d\n", strlen(arr+0));//arr+0是首元素的地址,和第一个一样,也是随机值
	//printf("%d\n", strlen(*arr));//err   //arr是数组首元素的地址,*arr就是数组首元素,就是'a'-97
	//strlen函数参数的部分需要传一个地址,当我们传递的是'a'时,‘a’的ASCII码值是97,那就是将97作为地址传参
	//srelen就会从97这个地址开始统计字符串长度,这就非法访问内存了
	//printf("%d\n", strlen(arr[1]));//err (同上,这里传的是98)
	printf("%d\n", strlen(&arr));//&arr是数组的地址,数组的地址和数组首元素的地址,值是一样的,那么传递给strlen函数后,
	//依然是从数组的第一个元素的位置开始往后统计。 是随机值
	printf("%d\n", strlen(&arr+1));//随机值
	printf("%d\n", strlen(&arr[0]+1));//&arr[0]+1是第二个元素的地址,结果也是随机值

	printf("%d\n", sizeof(arr));//数组名单独放在sizeof内部,这里的arr表示整个数组,计算的是整个数组的大小,单位是字节,总共6个字节
	printf("%d\n", sizeof(arr+0));//arr表示数组首元素的地址,arr+0还是数组首元素的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(*arr));//arr表示数组首元素的地址,*arr就是首元素,大小1个字节
	printf("%d\n", sizeof(arr[1]));//arr[1]就是第二个元素,大小是1个字节
	printf("%d\n", sizeof(&arr));//&arr是数组的地址,但是数组的地址也是地址,是地址就是4/8
	printf("%d\n", sizeof(&arr+1));//&arr+1是跳过整个数组后的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(&arr[0]+1));//第二个元素的地址,是4/8个字节
	return 0;
}

int main()
{
	char arr[] = "abcdef";//[a b c d e f \0]

	printf("%d\n", strlen(arr));//6
	printf("%d\n", strlen(arr+0));//6
	//printf("%d\n", strlen(*arr));//err
	//printf("%d\n", strlen(arr[1]));//err
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr+1));//随机值
	printf("%d\n", strlen(&arr[0]+1));//5

	printf("%d\n", sizeof(arr));//7
	//char [7]
	printf("%d\n", sizeof(arr+0));//arr+0是首元素的地址   4/8
	printf("%d\n", sizeof(*arr));//*arr其实就是首元素   1个字节
	//*arr-->*(arr+0)==arr[0]
	printf("%d\n", sizeof(arr[1]));//arr[1]是第二个元素,1个字节
	printf("%d\n", sizeof(&arr));//&arr是数组的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(&arr+1));//&arr+1是跳过一个数组的地址  4/8
	printf("%d\n", sizeof(&arr[0]+1));//&arr[0]+1 是第二个元素的地址  4/8
	return 0;
}
int main()
{
	char* p = "abcdef";

	printf("%d\n", strlen(p));//6
	printf("%d\n", strlen(p+1));//5
	//printf("%d\n", strlen(*p));//err (传的是a-97)
	//printf("%d\n", strlen(p[0]));//err(传的是a-97)
	printf("%d\n", strlen(&p));//从p的地址开始数,随机值
	printf("%d\n", strlen(&p+1));//随机值
	printf("%d\n", strlen(&p[0]+1));//5  'b'的地址开始向后数

	printf("%d\n", sizeof(p));//p是一个指针变量//大小就是4/8
	printf("%d\n", sizeof(p+1));//p+1是‘b’的地址,是地址大小就是4/8个字节
	printf("%d\n", sizeof(*p));//*p就是‘a’,就是1个字节
	printf("%d\n", sizeof(p[0]));//p[0]-->*(p+0)-->*p  1个字节
	printf("%d\n", sizeof(&p));//4/8
	//&p--char**
	printf("%d\n", sizeof(&p+1));//4/8
	printf("%d\n", sizeof(&p[0]+1));//4/8, &p[0]+1得到的是‘b’的地址
	return 0;
}
int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));//3*4*4=48
	printf("%d\n", sizeof(a[0][0]));//4
	printf("%d\n", sizeof(a[0]));//a[0]是第一行一维数组的数组名
	//数组名算是单独放在sizeof内部了,计算的是整个数组的大小,大小是16个字节
	printf("%d\n", sizeof(a[0]+1));
	//a[0]作为第一行的数组名,没有单独放在sizeof内部,没有&
	//a[0]表示数组首元素的地址,也就是a[0][0]的地址
	//所以a[0]+1是第一行第二个元素的地址,是地址就是4/8个字节
	printf("%d\n", sizeof(*(a[0]+1)));//4
	//计算的就是第一行第2个元素的大小
	printf("%d\n", sizeof(a+1));//4/8
	//a是数组首元素的地址,是第一行的地址 int(*)[4]
	//a+1就是第二行的地址
	printf("%d\n", sizeof(*(a+1)));//16
	//*(a+1)-->a[1]-->sizeof(*(a+1))-->sizeof(a[1])计算的是第二行的大小
	//a+1-->是第二行的地址,int(*)[4]
	//*(a+1)访问的是第二行的数组
	printf("%d\n", sizeof(&a[0]+1));//4/8
	//&a[0]是第一行的地址 int(*)[4]
	//&a[0]+1  是第二行的地址 int(*)[4]
	printf("%d\n", sizeof(*(&a[0]+1)));//16  计算的是第二行的大小
	printf("%d\n", sizeof(*a));//计算的是第一行的大小-16
	//a是数组首元素的地址,就是第一行的地址
	//*a就是第一行
	//*a-->*(a+0)-->a[0]
	printf("%d\n", sizeof(a[3]));//16
	//a[3]-->int [4]
	return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/589635.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL | 第十篇】重新认识MySQL索引匹配过程

文章目录 10.重新认识MySQL索引匹配过程10.1匹配规则10.2举例&#xff1a;联合索引遇到范围查询&#xff08;>、<、between、like&#xff09;10.2.1例子一&#xff1a;>10.2.2例子二&#xff1a;>10.2.3例子三&#xff1a;between10.2.4例子四&#xff1a;like 10…

SQL数据库

一.什么是数据库 数据库&#xff1a;存储数据的仓库&#xff0c;数据是有组织的进行存储。&#xff08;database 简称DB&#xff09; 数据库管理系统&#xff1a;管理数据库的大型软禁&#xff08;DataBase Management System 简称DBMS&#xff09; SQL&#xff1a;操作关系…

Deep Learning Part Seven基于RNN生成文本--24.5.2

不存在什么完美的文章&#xff0c;就好像没有完美的绝望。 ——村上春树《且听风吟》 本章所学的内容 0.引子 本章主要利用LSTM实现几个有趣的应用&#xff1a; 先剧透一下&#xff1a;是AI聊天软件&#xff08;现在做的ChatGPT&#xff08;聊天神器&#xff0c;水论文高手…

Windows Server安装DHCP和DNS

前言 本期将教大家如何在Windows server上部署DHCP服务和DNS服务&#xff0c;用于模拟给内网主机分配IP地址。虽然用于演示的系统比较老&#xff0c;如果在新版本如Windows server2016、19、22上部署&#xff0c;操作基本一致。在此之前先给大家科普一波理论&#xff0c;需略过…

【docker 】push 镜像到私服

查看镜像 docker images把这个hello-world 推送到私服 docker push hello-world:latest 报错了。不能推送。需要标记镜像 标记Docker镜像 docker tag hello-world:latest 192.168.2.1:5000/hello-world:latest 将Docker镜像推送到私服 docker push 192.168.2.1:5000/hello…

Django数据库创建存储及管理

一、什么是ORM Django的ORM(Object-Relational Mapping)是Django框架中一个非常重要的组件。ORM可以让开发者以面向对象的方式操作数据库,而不需要直接编写SQL语句。 具体来说,Django ORM提供了以下功能: 模型定义:开发者可以在Django应用中定义Python类来表示数据库表,这些…

基于寄存器的STM32操作流程

寄存器点灯 寄存器操作STM32的好处是不需要依靠外部文件&#xff0c;自由度更高&#xff0c;更为底层&#xff0c;但也更加繁杂。 通过寄存器点灯&#xff0c;需要按照电路结构与手册配置寄存器&#xff1a; 电路结构如下&#xff1a;可知需配置的GPIO为GPIOB5与GPIOE5。 在…

Docker构建LNMP部署WordPress

前言 使用容器化技术如 Docker 可以极大地简化应用程序的部署和管理过程&#xff0c;本文将介绍如何利用 Docker 构建 LNMP 环境&#xff0c;并通过部署 WordPress 来展示这一过程。 目录 一、环境准备 1. 项目需求 2. 安装包下载 3. 服务器环境 4. 规划工作目录 5. 创…

excel怎么删除条件格式规则但保留格式?

这个问题的意思就是要将设置的条件格式&#xff0c;转换成单元格格式。除了使用VBA代码将格式转换外&#xff0c;还可以用excel自己的功能来完成这个任务。 一、将条件格式“留下来” 1.设置条件格式 选中数据&#xff0c;点击开始选项卡&#xff0c;设置条件格式&#xff0…

2024年 Java 面试八股文——SpringMVC篇

目录 1.简单介绍下你对springMVC的理解? 2.说一说SpringMVC的重要组件及其作用 3.SpringMVC的工作原理或流程 4.SpringMVC的优点 5.SpringMVC常用注解 6.SpringMVC和struts2的区别 7.怎么实现SpringMVC拦截器 8.SpringMvc的控制器是不是单例模式&#xff1f;如果是&am…

XUbuntu24.04之更换国内高速源(二百二十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

无人机+飞行汽车:低空经济新引擎,有望爆发式增长

无人机和飞行汽车作为低空经济的新引擎&#xff0c;正在引领一场全新的交通革命。随着技术的不断进步和政策的支持&#xff0c;低空经济有望成为未来经济发展的重要领域&#xff0c;实现爆发式增长。 首先&#xff0c;无人机和飞行汽车具有独特的优势和应用场景。无人机可以在…

confluence 设置https代理

使用nginx反待confluence并开启https后&#xff0c;登录confluence会一直提示&#xff1a;scheme、proxyName、proxyPort设置错误。 解决办法&#xff1a; find / -name server.xmlvi /opt/atlassian/confluence/conf/server.xml HTTP反代配置 HTTPS反代配置

ue引擎游戏开发笔记(28)——实现第三人称越肩视角

1.需求分析 实现一个第三人称越肩视角 2.操作实现 1.思路&#xff1a;建立一个弹簧臂和摄像机&#xff0c;调整两者位置达到越肩效果。 2.直接在蓝图操作&#xff1a;添加摄像机和弹簧臂&#xff1a; 3.对弹簧臂勾选使用pawn控制旋转&#xff0c;并适当调整摄像机和弹簧臂位置…

[NSSCTF]prize_p1

前言 之前做了p5 才知道还有p1到p4 遂来做一下 顺便复习一下反序列化 prize_p1 <META http-equiv"Content-Type" content"text/html; charsetutf-8" /><?phphighlight_file(__FILE__);class getflag{function __destruct(){echo getenv(&qu…

Vue 组件的三大组成部分

Vue 组件通常由三大组成部分构成&#xff1a;模板&#xff08;Template&#xff09;、脚本&#xff08;Script&#xff09;、样式&#xff08;Style&#xff09; 模板部分是组件的 HTML 结构&#xff0c;它定义了组件的外观和布局。Vue 使用基于 HTML 的模板语法来声明组件的模…

【算法入门教育赛1E】最长公共前缀 - 字符串哈希 | 二分 | C++题解与代码

题目链接&#xff1a;https://www.starrycoding.com/problem/163 题目描述 牢 e e e在 S t a r r y C o d i n g StarryCoding StarryCoding的入门教育赛报名单上遇到了许多名字 s 1 , s 2 , . . . , s n s_1, s_2,...,s_n s1​,s2​,...,sn​&#xff0c;他想知道由这些人的…

网络安全风险里的威胁建模

文章目录 前言一、威胁建模的必要性二、威胁建模的过程三、威胁建模框架及方法1、NIST威胁模型框架2、STRIDE Model框架3、DREAD框架4、PASTA流程5、LINDDUN框架6、TRIKE知识库7、安全决策树四、威胁建模应用实践前言 网络安全的本质是攻防双方的对抗与博弈。然而,由于多种攻…

python学习笔记B-20:序列实战--处理千年虫

将2位数表达的年份&#xff0c;转换为用4位数表达&#xff1a; print("将列表中的2位数年份转换为4位数年份") lst[88,89,90,00,99] print(lst) for index in range(len(lst)):if len(str(lst[index]))2:lst[index] 1900int(lst[index])elif len(str(lst[index]))1…

微信小程序demo-----制作文章专栏

前言&#xff1a;不管我们要做什么种类的小程序都涉及到宣传或者扩展其他业务&#xff0c;我们就可以制作一个文章专栏的页面&#xff0c;实现点击一个专栏跳转到相应的页面&#xff0c;页面可以有科普类的知识或者其他&#xff0c;然后页面下方可以自由发挥&#xff0c;添加联…