C语言——指针的进阶——第1篇——(第26篇)

news2024/9/23 7:23:11

坚持就是胜利

文章目录

  • 一、字符指针
    • 1、面试题
  • 二、指针数组
  • 三、数组指针
    • 1、数组指针的定义
    • 2、&数组名 VS 数组名
    • 3、数组指针的使用
      • (1)二维数组传参,形参是 二维数组 的形式
      • (2)二维数组传参,形参是 指针 的形式
      • (3)总结
  • 四、数组传参和指针传参
    • 1、一维数组传参
    • 2、二维数组传参
    • 3、一级指针传参
    • 4、二级指针传参
  • 五、函数指针
    • 1、举例理解:
    • 2、分析两段有趣的代码
      • (1)代码1
      • (2)代码2
        • 学习 typedef 类型重命名
        • 代码2太复杂,简化为如下形式

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

一、字符指针

在指针的类型中,我们知道有一种指针类型为字符类型 char*
一般使用:

#include <stdio.h>

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

还有一种使用方式如下:

#include <stdio.h>

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

	const char* p = "abcde";   //本质是:指针变量p 指向 首字符 a 的地址
	                     //并且 "abcde" 是 "常量字符串",得加上 const

	printf("%s\n", p);   //结果:abcde  

	printf("%c\n", *p);  //结果:a

	return 0;
}
代码:const char* p = "abcde";

特别容易以为是 把字符串 “abcde” 放到字符指针 p 中,
但是本质是把 字符串 abcde 首字符 的地址放到了 p 中。

在这里插入图片描述

1、面试题

#include <stdio.h>

int main()
{
	char str1[] = "abcde";
	char str2[] = "abcde";
	const char* str3 = "abcde";
	const char* str4 = "abcde";

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

在这里插入图片描述

在这里插入图片描述
这里 str3 和 str4 指向的是一个同一个常量字符串。C/C++ 会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,它们实际会指向同一块内存。
但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以 str1 和 str2 不同,str3 和 str4 不同。

二、指针数组

指针数组 是一个 存放指针 的数组。

int* arr1[10];  //整型指针的数组
char* arr2[4];  //一级字符指针的数组
char** arr3[5];  //二级字符指针的数组
//使用 指针数组 模拟实现 二维数组

#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[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、数组指针的定义

类比:
整型指针——指向整型变量的指针,存放整型变量的地址的指针变量。
字符指针——指向字符变量的指针,存放字符变量的地址的指针变量。

数组指针——指向数组的指针,存放数组的地址的指针变量。

在这里插入图片描述

2、&数组名 VS 数组名

int arr[10];

arr 和 &arr 分别是什么?
arr 是 数组名,数组名 表示 数组首元素 的地址。

那 &arr 数组名 到底是什么?

#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。

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);   //类型:int *
	printf("%p\n", &arr[0]);  //类型:int *
	printf("%p\n", &arr);   //类型:int(*)[10]   数组指针类型

	return 0;
}

在这里插入图片描述

电脑中 int[10] * ,这么写是错误的,是为了方便你理解,其实应该写成 int( * )[10] , 数组指针类型

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%d\n", sizeof(arr));
	return 0;
}

在这里插入图片描述

总结:(数组名的理解)
数组名是数组首元素的地址。

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

3、数组指针的使用

#include <stdio.h>

int main()
{
	char  arr[6] = "abcde";
	char(*p)[6] = &arr;
	printf("%c \n", *(*p));  //p 中放的是 &arr , *p 就是 arr(数组名), *(*p) 就是 *(arr),就是首字母,就是对首字母地址解引用
	printf("%s", p);
	return 0;
}

在这里插入图片描述

#include <stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(p + i));
	}
	putchar('\n');
	for (i = 0; i < 10; i++)
	{
		printf("%d ", *(arr + i));
	}
	return 0;
}

用指针数组,来遍历输出数组的每个元素,感觉使用起来很变扭!

#include <stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;  //*&arr 那么*&就互相抵消,*&arr 就是 arr(数组名)
	int i = 0;
	for (i = 0; i < 10; i++)
	{                                //p 中存放的就是 &arr
									 //由于*&arr 就是 arr(数组名)
									 //*p 就是 *(&arr)  就是 arr(数组名)
		printf("%d ", *((*p) + i));  //(*p) 就是 arr(数组名) 
									//(*p)+i  就是 arr + i  
									//*(arr + i) 就是 遍历数组的每一个元素
		putchar('\n');

		printf("%d ", (*p)[i]);    //arr[i]
		                           //效果一样

	}
	return 0;
}

在这里插入图片描述
所以,数组指针 不是像 上面的代码那样使用,这样反而弄巧成拙了。

问:数组指针怎么使用呢?
答:一般在 二维数组 上才方便。

(1)二维数组传参,形参是 二维数组 的形式

//二维数组传参,形参是二维数组的形式

#include <stdio.h>

void Print(int arr[3][5], int r, int c)
{
	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");
	}
}

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

(2)二维数组传参,形参是 指针 的形式

//二维数组传参,形参是指针的形式

#include <stdio.h>

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 是数组名,数组名表示数组首元素的地址

	Print(arr, 3, 5);

	return 0;
}

在这里插入图片描述
在这里插入图片描述

(3)总结

一维数组传参,形参的部分可以是数组(本质还是指针),也可以是指针

//一维数组传参,形参的部分可以是数组(本质还是指针),也可以是指针

void test1(int arr[5], int sz)    //int arr[5] 本质还是指针,只是写成了数组的形式
{}

void test2(int* p, int sz)
{}

int main()
{
	int arr[5] = { 0 };
	test1(arr, 5);
	test2(arr, 5);

	return 0;
}

二维数组传参,形参的部分可以是数组,也可以是指针

//二维数组传参,形参的部分可以是数组,也可以是指针

void test3(char arr[3][5], int r, int c)
{}

void test4(char(*p)[5], int r, int c)
{}

int main()
{
	char arr[3][5] = { 0 };
	test3(arr, 3, 5);
	test4(arr, 3, 5);

	return 0;
}

学了指针数组和数组指针,我们来一起回顾并看看下面的代码的意思:
在这里插入图片描述

四、数组传参和指针传参

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

1、一维数组传参

在这里插入图片描述

#include <stdio.h>

void test(int arr[])  //ok?  答:可行
{}                    

void test(int arr[10])  //ok?  答:可行
{}                      //[10]中的10,可以写成100、1000都行,反正 int arr[10] 本质就是指针

void test(int *arr)  //ok?  答:可行
{}

void test2(int *arr[20])  //ok?  答:可行
{}                        //原因:形参实参类型保持一致
 
void test2(int** arr)  //ok?  答:可行
{}                     //int *arr2[20] 就是数组20个元素,每个元素类型都是 int * 
                       //一级指针的地址,就是 二级指针
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);
	return 0;
}

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 是数组指针的地址,arr 是数组第一行的地址 arr == &arr[0]

void test(int* arr[5])  //ok?  答:不可行
{}                      //int* arr[5] 是指针数组,有5个元素,每个元素的类型是 int*

void test(int(*arr)[5])  //ok?  答:可行
{}                       //int (*arr)[5]数组指针,元素类型是 int[5],是数组某一行的指针

void test(int **arr)  //ok?  答:不可行
{}                    //arr的类型是 int*,明显错误
                      //二级指针 是 用来接收一级指针的地址的
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,10 };
	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[] = "abcde";

test(&ch);  //第一种
test(ptr);  //第二种
test(arr);  //第三种

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)
{}

int main()
{
	char c = 'b';
	char* pc = &c;
	char** ppc = *pc;
	char* arr[10];   //指针数组,类型是 char*
	           
	            //二级指针,是一级指针的地址
	test(&pc);
	test(ppc);
	test(arr);  //是数组元素类型 char* 的地址

	return 0;
}

五、函数指针

函数指针——指向函数的指针
在这里插入图片描述
函数名 是 函数的地址
&函数名 也是 函数的地址
在这里插入图片描述

int (*pf)(int , int) 去掉 指针名 pf ,剩下的就是指针类型 int ( * )(int , int)

在这里插入图片描述

1、举例理解:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2、分析两段有趣的代码

(1)代码1

//代码1
( *( void (*)() )0 )();

1、将 0 强制转换成 void (*)() 类型的函数指针
2、调用 0 地址处的这个函数

在这里插入图片描述

//0x0012ff40 可以理解为:int
//           也可以理解为:int*

//同理:

//0          可以理解为:int
//           也可以理解为:int*

//void (*p)()  -- p是函数指针
//void (*)() 是函数指针类型

//从0开始切入分析
//0 被强制类型转换为 void (*)() 类型的函数指针
// 0 此时变成了函数指针,也就是说 0 变成 地址了
//* 解引用  ()代表 函数
// 函数指针就是指针,指针就是地址
//调用 0 地址 处的这个函数

//代码1
( *( void (*)() )0 )();

我们自己写的应用程序是不可以访问 0 地址的。
举例的是 系统程序。

(2)代码2

//代码2
void (*signal(int , void(*)(int)))(int);

在这里插入图片描述

//signal 是一个函数声明
//signal 函数有 2 个参数
//第 1 个参数的类型是 int
//第 2 个参数的类型是 void(*)(int) 函数指针类型,该函数指针指向的函数有一个 int 类型的参数,返回类型是 void 
//signal 函数的返回类型也是 void(*)(int) 函数指针类型,该函数指针指向的函数有一个 int 类型的参数,返回类型是 void 


//代码2
void (*signal(int , void(*)(int)))(int);
学习 typedef 类型重命名
typedef unsigned int uint;  //将 unsigned int 重命名为 uint  //正确
typedef int* ptr_r;  //将 int* 重命名为 ptr_r  //正确

数组指针,这种书写格式是错误的

//对 数组指针 进行 类型重命名

typedef int(*)[10] parr_t;  //这么写就是错误的,不能以这种方式来书写

应该改为这样
在这里插入图片描述

//对 数组指针 进行 类型重命名

//错误的书写格式
//typedef int(*)[10] parr_t;  //这么写就是错误的,不能以这种方式来书写

//正确的书写格式
typedef int(*parr_t)[10];

函数指针类型 跟上面的书写格式一致

代码2太复杂,简化为如下形式

原代码:

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

简化后:

typedef void(*pfun_t)(int);  //将 void (*)(int)  重命名为 pfun_t
pfun_t signal(int, pfun_t);

微软雅黑字体
黑体
3号字
4号字
红色
绿色
蓝色

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Redis高可用性【重点】

参考链接 https://xiaolincoding.com/redis/cluster/master_slave_replication.html#%E7%AC%AC%E4%B8%80%E6%AC%A1%E5%90%8C%E6%AD%A5 高可用性 主从复制哨兵节点 主从复制 【面试题】 Redis主从节点时长连接还是短连接&#xff1f; 长连接 #怎么判断 Redis 某个节点是否正常…

【C++从练气到飞升】01---C++入门

&#x1f388;个人主页&#xff1a;库库的里昂 ✨收录专栏&#xff1a;C从练气到飞升 &#x1f389;鸟欲高飞先振翅&#xff0c;人求上进先读书。 目录 推荐 前言 什么是C C的发展史 &#x1f4cb;命名空间 命名空间定义 命名空间使用 命名空间的嵌套 std命名空间的使用 &#…

Windows Server 各版本搭建文件服务器实现共享文件(03~19)

一、Windows Server 2003 打开服务器&#xff0c;点击左下角开始➡管理工具➡管理您的服务器➡添加或删除角色 点击下一步等待测试 勾选自定义配置&#xff0c;点击下一步 选择文件服务器&#xff0c;点击下一步 勾选设置默认磁盘空间&#xff0c;数据自己更改&#xff0c;最…

【js】事件循环之promise的async/await与setTimeout

什么是事件循环 事件循环又叫消息循环&#xff0c;是浏览器渲染主线程的工作方式。 浏览器开启一个永不停止的for循环&#xff0c;每次循环都会从消息队列中取任务&#xff0c;其他线程只需要在合适的时候将任务加入到消息队列的末尾。 过去分为宏任务和微任务&#xff0c;现…

数据分析工具在不同行业中有什么不同的需求?

数据建模也是数据分析的一个分支 一、交管行业&#xff0c;对于数据建模的需求如下 1、根据分析方法可以将交管大数据模型分为统计分析类、业务规则类、预测预警类、异常分析类、画像分析类和综合评价类六大类&#xff0c;具体如下&#xff1a; 2、模型的实现过程 二、各类工…

写时复制简介

写时复制技术(Copy on Write)是比较常用的一种技术&#xff0c;它的主要目的是延迟减少以及延迟内存的分配&#xff0c;增加执行效率&#xff0c;只有在真正进行写操作的过程中才会真正分配物理资源。同时&#xff0c;也可以保护数据在系统崩溃时出现的丢失。比如&#xff0c;我…

视频云平台——搭建SRS5平台支持GB28181视频流的推送

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;新的征程&#xff0c;我们面对的不仅…

逆向案例四:360k静态和精灵数据动态AES解密,用js的方法

一、360K 网页链接:https://www.36kr.com/p/2672600261670407 页面中有静态的需要解密的内容&#xff0c;确定html包&#xff0c;确定方法 1.1方法步骤 在下方的搜索中输入decrypt(或者关键字window.initialState &#xff0c;进入js文件 在AES.decrypt处打上断点&#xff0…

MySQL面试题-事务(答案版)

事务 1、事务与存储引擎的关系 事务是由 MySQL 的引擎来实现的&#xff0c;我们常见的 InnoDB 引擎它是支持事务的。 不过并不是所有的引擎都能支持事务&#xff0c;比如 MySQL 原生的 MyISAM 引擎就不支持事务&#xff0c;也正是这样&#xff0c;所以大多数 MySQL 的引擎都…

hnust 湖南科技大学 2023 综合实训3(软件工程)课设 完整代码及数据库+报告+uml等图源文件+指导书

hnust 湖南科技大学 2023 综合实训3&#xff08;软件工程&#xff09;课设 完整代码及数据库报告uml等图源文件指导书 介绍 老师考核等级为优&#xff0c;系统多次测试&#xff0c;未发现bug 项目前后端分离&#xff0c;前端vue2工程项目&#xff0c;后端springboot&#xff…

知乎语音下载(mediadown)

知乎语音下载(mediadown) 一、介绍 知乎语音下载,能够帮助你下载知乎知学堂课程中的语音和视频。它不能帮你越过会员权限,下载你没权限访问的语音和视频。 二、下载地址 本站下载:知乎语音下载(mediadown) 百度网盘下载:知乎语音下载(mediadown) 三、安装教程 …

创业者的智选:知识付费小程序定制开发服务解析

探索知识付费领域的新时代&#xff0c;选择专业的知识付费小程序定制开发服务&#xff0c;打造个性化、高效的知识传播平台。无论您是企业、机构还是个体创作者&#xff0c;都能助您成功变现知识资产。 知识付费小程序的开发是一个涉及多方面技术的综合性工程。下面提供一些关…

利用redis实现秒杀功能

6、秒杀优化 这个是 图灵 的redis实战里面的一个案例 6.1 秒杀优化-异步秒杀思路 我们来回顾一下下单流程 当用户发起请求&#xff0c;此时会请求nginx&#xff0c;nginx会访问到tomcat&#xff0c;而tomcat中的程序&#xff0c;会进行串行操作&#xff0c;分成如下几个步骤…

Linux:Kubernetes(k8s)基础理论笔记(1)

我笔记来源的图片以及共享至GitHub&#xff0c;本章纯理论。这是k8s中部分的基础理论 &#x1f447; KALItarro/k8spdf: 这个里面只有一个pdf文件 (github.com)https://github.com/KALItarro/k8spdf&#x1f446; 什么是kubernetes kubernetes 是一个开源的&#xff0c;用于管…

二、TensorFlow结构分析(1)

目录 1、TF数据流图 1.1 TensorFlow结构分析 1.2 案例 2、图与TensorBoard 2.1 图结构 2.2 图相关操作 2.2.1 默认图 2.2.2 创建图 2.3 TensorBoard&#xff1a;可视化学习 2.3.1 数据序列化 - events文件 2.3.2 启动TensorBoard 2.4 OP 2.4.1 常见OP 2.4.2 指令…

呵护宝贝的肌肤当然要从小做起啦~

亲子系列~来个防晒衣 防晒还真的挺重要的 尤其是女生&#xff0c;防晒要从小做起哈 这款做的防晒面料&#xff0c;有抗紫外线吊牌 可以放心去穿&#xff0c;质地柔软轻盈又透气 防晒护肤双合一&#xff0c;四个颜色每个都很好看 很适合春夏季&#xff0c;很干净清爽 泳衣…

LeetCode.232. 用栈实现队列

题目 232. 用栈实现队列 分析 先了解一下栈和队列的特点&#xff1a; 栈&#xff1a;先进后出队列&#xff1a;先进先出 想用栈实现队列的特点&#xff0c;就需要使用两个栈。因为两个栈就可以将列表倒序。 假设第一个栈 s1 [1,2,3]&#xff0c;第二个栈 s2 [] 。若循环…

外卖点餐多门店商家积分商城小程序开发

开源版的点餐外卖多门店多商家积分商城小程序是一款集合了多种功能于一身的移动应用。以下是该小程序的核心功能概述&#xff1a; 点餐与外卖服务&#xff1a;用户能够轻松浏览不同门店的菜单&#xff0c;根据个人口味和需求挑选菜品、口味和规格&#xff0c;并将其加入购物车…

高通QNX基线编译原理

下面代码以高通智驾平台为例。 1 QNX应用程序编译原理 在高通提供的qnx开发包中,qnx的内核已经由qnx所提供,所以qnx的编译,其实就是大量应用程序的编译,以及最后利用buildfile文件,把内核,库文件以及应用程序打包在一起的过程。 1.1 qnx的工程目录 应用程序的编译,可…

AirPods Pro 2 耳机推送新固件,苹果Find My功能助力产品成长

苹果公司面向 AirPods Pro 2&#xff08;包括 USB-C 和 Lightning 版本&#xff09;&#xff0c;推出了全新的测试版固件更新&#xff0c;版本号为 6E188&#xff0c;高于 12 月份发布的 6B34 固件。 苹果和往常一样&#xff0c;并没有提供详细的更新日志或者说明&#xff0c…