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

坚持就是胜利

文章目录

  • 一、字符指针
    • 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.mfbz.cn/a/424832.html

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

相关文章

django的模板渲染中的【高级定制】:按数据下标id来提取数据

需求&#xff1a; 1&#xff1a;在一个页面中显示一张数据表的数据 2&#xff1a;不能使用遍历的方式 3&#xff1a;页面中的数据允许通过admin后台来进行修改 4&#xff1a;把一张数据表的某些内容渲染到[xxx.html]页面 5&#xff1a;如公司的新商品页面&#xff0c;已有固定的…

python进阶:可迭代对象和迭代器

一、Iterable&#xff08;可迭代对象&#xff09; 1、可迭代对象&#xff1a;能够进行迭代操作的对象。 可以理解为&#xff1a;能够使用for循环遍历的都是可迭代对象&#xff1b;**所有的可迭代对象&#xff0c;偶可以用内置函数iter转换为迭代器** 2、可迭代对象包括&…

Linux文本处理三剑客:awk(常用匹配模式)

在Linux操作系统中&#xff0c;grep、sed、awk被称为文本操作“三剑客”&#xff0c;上三期中&#xff0c;我们将详细介绍grep、sed、awk的基本使用方法&#xff0c;希望能够帮助到有需要的朋友。 1、前言 awk作为一门编程语言还有很多内容&#xff0c;我们继续学习awk。 网…

快速解决maven依赖冲突

我们在开发过程中经常出现maven依赖冲突&#xff0c;或者maven版本不匹配的情况&#xff0c;我们可以使用阿里云原生脚手架来做maven管理&#xff0c;添加需要的组件&#xff0c;然后点击获取代码&#xff0c;就可以获得对应的依赖文件。

【C语言】InfiniBand驱动mlx4_init和mlx4_cleanup

一、中文注释 Linux内核模块的初始化和清理过程&#xff0c;针对一个称为mlx4的网络设备驱动。以下是代码的逐行中文注释&#xff1a; static int __init mlx4_init(void) {int ret;if (mlx4_verify_params())return -EINVAL; // 检查设备参数是否有效&#xff0c;无效则返回…

.idea文件详解

.idea文件的作用&#xff1a; .idea文件夹是存储IntelliJ IDEA项目的配置信息&#xff0c;主要内容有IntelliJ IDEA项目本身的一些编译配置、文件编码信息、jar包的数据源和相关的插件配置信息。一般用git做版本控制的时候会把.idea文件夹排除&#xff0c;因为这个文件下保存的…

ABAP - SALV教程08 列设置热点及绑定点击事件

实现思路&#xff1a;将列设置成热点&#xff0c;热点列是可点击的&#xff0c;再给SALV实例对象注册点击事件即可&#xff0c;一般作用于点击单号跳转到前台等功能 "设置热点方法METHODS:set_hotspot CHANGING co_alv TYPE REF TO cl_salv_table...."事件处理方法M…

OSCP靶场--Hepet

OSCP靶场–Hepet 考点(1.邮件获取 2.msf恶意宏文档制作 3. 邮件投递恶意宏文档 4.服务配置不当提权) 1.nmap扫描 ┌──(root㉿kali)-[~/Desktop] └─# nmap -sV -sC -p- 192.168.188.140 --min-rate 2000 Starting Nmap 7.92 ( https://nmap.org ) at 2024-03-01 05:46 ES…

【网上商城系统的设计与开发】

目录 1.实训概况 1 1.1 实训题目 1 1.2实训时间 1 1.3实训目的 1 1.4 实训环境 1 1.5 实训内容 2 1.6 进度安排 3 2.需求分析 5 2.1 功能需求分析 5 2.1.1用户需求分析 5 2.2.2网站前台需求 5 2.2.3网站后台需求 6 2.2 可行性分析 7 2.2.1社会可行性 7 2.2.2技术可行性 8 3.系统…

通过多进程并发方式(fork)实现服务器(注意要回收子进程)

以下内容为视频学习记录。 1、父进程accept后返回的文件描述符为cfd以及用于创建连接的lfd; 调用fork()创建子进程后&#xff0c;子进程继承cfd,lfd&#xff0c;通过该cfd与连接过来的客户端通信,lfd对子进程来说没用&#xff0c;可以直接close(lfd); 对于父进程来说&#x…

如何利用ChatGPT搞科研?论文检索、写作、基金润色、数据分析、科研绘图(全球地图、植被图、箱型图、雷达图、玫瑰图、气泡图、森林图等)

以ChatGPT、LLaMA、Gemini、DALLE、Midjourney、Stable Diffusion、星火大模型、文心一言、千问为代表AI大语言模型带来了新一波人工智能浪潮&#xff0c;可以面向科研选题、思维导图、数据清洗、统计分析、高级编程、代码调试、算法学习、论文检索、写作、翻译、润色、文献辅助…

2023天津公租房网上登记流程图,注册到信息填写

2023年天津市公共租赁住房网上登记流程图 小编为大家整理了天津市公共租赁住房网上登记流程&#xff0c;从登记到填写信息。 想要体验的朋友请看一下。 申请天津公共租赁住房时拒绝申报家庭情况会怎样&#xff1f; 天津市住房保障家庭在享受住房保障期间&#xff0c;如在应申…

安装 node 错误的配置环境变量之后使用 npm 报错,更换淘宝镜像 (cnpm)

安装 node 错误的配置环境变量之后使用 npm 报错&#xff0c;更换淘宝镜像 (cnpm) node:internal/modules/cjs/loader:1147 throw err; ^ Error: Cannot find module ‘F:\ACodeTools\Node\node_modules\npm\bin\node_modules\npm\bin\npm-cli.js’ at Module._resolveFilename…

JVM(6)

JMM JVM定义了一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到一致的内存访问效果.在此之前,C/C直接使用物理硬件和操作系统的内存模型,因此,会由于不同平台下的内存模型差异,有可能导致程序在一套平台上并发完全正常,而在另…

多输入多输出 | MATLAB实现GWO-Elman灰狼优化循环神经网络多输入多输出预测

多输入多输出 | MATLAB实现GWO-Elman灰狼优化循环神经网络多输入多输出预测 目录 多输入多输出 | MATLAB实现GWO-Elman灰狼优化循环神经网络多输入多输出预测预测效果基本介绍程序设计往期精彩参考资料 预测效果 基本介绍 Matlab实现GWO-Elman灰狼优化循环神经网络多输入多输出…

手把手教测试,全网内容最全有深度-jmeter-Flow Control Action

5.1.7.6.Flow Control Action(测试活动) Logical Action on Thread&#xff1a; Pause 暂停&#xff0c;配合 Duration 一起使用&#xff1b;Duration(milliseconds) 延迟时间&#xff0c;单位是毫秒 Start Next Thread Loop 开始本线程下一次循环 设置线程组线程数2&#xff…

(资源篇)2025届暑假实习春招全攻略路线

绝对的全攻略&#xff0c;资源完善程度绝对的全网唯一。 觉得有帮助的&#xff1a;随手一键三连关注就是对up主最大的激励。 绝对的宝藏up主&#xff01;&#xff01;&#xff01;&#xff0c;up主每天都会进行更新视频&#xff0c;算法视频or校招信息or八股讲解。 【暴躁老…

位运算第二弹

力扣191.位1的个数 public class Solution {// you need to treat n as an unsigned valuepublic int hammingWeight(int n) {int ret0;while(n!0){n(n&n-1);ret;}return ret;} } 推荐是自己去手动推一下&#xff0c;深刻理解一下&#xff0c;什么叫做最右侧的1。 力扣338.…

在docker中搭建selenium 爬虫环境(3分钟快速搭建)

1、安装docker 省略 2、拉取镜像 docker pull selenium/standalone-chrome-debug 3、运行容器 docker run -d -p 4444:4444 -p 5900:5900 -v C:\Users\Public\VNC_Donwnloads:/home/seluser/Downloads --memory6g --name selenium_chrome selenium/standalone-chrome-debu…

[VulnHub靶机渗透] CONNECT THE DOTS

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …
最新文章