详解C语言指针(二)

news2024/11/26 20:30:56

文章目录

  • 1. 字符指针
  • 2. 指针数组
  • 3. 数组指针
    • 3.1 什么是数组指针?
    • 3.2 &数组名 VS 数组名
  • 4. 数组参数
    • 4.1 一维数组传参
    • 4.2 二维数组传参
  • 5. 函数指针
  • 6. 函数指针数组
  • 7. 指向函数指针数组的指针
  • 8. 回调函数

1. 字符指针

字符指针是指针类型的变量,其存储的是字符(字符数组或字符串)的地址。
例如:pc里面存储的就是字符ch的地址。

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

字符指针也可以初始化为指向字符数组或字符串的首字符的地址。

const char *str = "Hello_CSD"; // 初始化字符指针,指向字符串常量

上面代码的意思是把一个常量字符串的首字符 H 的地址存放到指针变量 str中。
在这里插入图片描述
紧接着,我们来看下面一道面试题目,并分析运行结果:

#include <stdio.h>
int main()
{
    char str1[] = "hello_word!";
    char str2[] = "hello_word!";
    const char *str3 = "hello_word!";
    const char *str4 = "hello_word!";
    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;
}

运行结果如下:
在这里插入图片描述
分析如下:

  • str1 和 str2 是字符数组,它们创建的时候是在栈上分配内存,每个都有自己的内存地址。上面只是用相同的常量字符串去初始化它们俩。
    str1和str2是数组名,在这里表示的是首元素的地址,str1和str2是两块独立空间的起始地址,因此,str1 == str2 的比较结果为 不相同,因为它们指向不同的内存位置。
    在这里插入图片描述
  • 然而,str3 和 str4 是指向常量字符串的指针,而C/C++会将这个常量字符串存储在程序的数据段中,只有一份拷贝。因此,它们实际上是指向了同一个常量字符串。
    然而把一个常量字符串赋给指针变量的时候,是把一个常量字符串的首字符的地址存放到了指针变量中,所以str3 == str4 的比较结果为 “相同”,因为它们指向相同的内存位置。
    在这里插入图片描述

2. 指针数组

详解C语言指针(一)在这篇文章中,已经讲到了指针数组的概念,需要的话,可以点击链接跳转去查看,这里就不在赘述。
指针数组可以用于模拟实现二维数组。这样我们就可以创建出具有不规则大小的二维数组,每行的长度可以不同。在这里,我们用指针数组,来模拟实现一个二维数组。

#include <stdio.h>
#include <stdlib.h>

int main() 
{
    int* arr[3]; // 指针数组,每个元素指向一行的整数数组

    // 分配内存并初始化每行的长度
    for (int i = 0; i < 3; ++i)
    {
    	arr[i] = (int*)malloc(sizeof(int) * 3);//每行3个元素
    }

    // 初始化二维数组的值
    for (int i = 0; i < 3; i++) 
    {
        for (int j = 0; j < 3; j++) 
        {
            *(*(arr + i) + j) = 0; // *(*(arr + i) + j) 给每个元素赋值
        }
    }

    // 访问和打印二维数组的值
    for (int i = 0; i < 3; i++) 
    {
        for (int j = 0; j < 3; j++) 
        {
            printf("%d ", *(*(arr + i) + j));//arr[i][j] == *(*(arr + i) + j)
        }
        printf("\n");
    }

    // 释放内存
    for (int i = 0; i < 3; i++) 
    {
        free(arr[i]);
        arr[i] = NULL;
    }

    return 0;
}

在这里插入图片描述

3. 数组指针

3.1 什么是数组指针?

数组指针是指针类型的变量,其存储的是整个数组的地址。
数组指针的定义方式:数组的数据类型 + (* 指针变量名) + [数组的元素个数] = &数组名
例如:p就是数组指针变量

int arr[10];
int (*p)[10] = &arr;//[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

在这里插入图片描述

3.2 &数组名 VS 数组名

上篇文章说道:数组名在大多数情况下,表示的是数组首元素的地址。但是有两个例外:

  • sizeof(数组名),计算的是整个数组的大小,单位是字节。
  • &数组名,取出的是整个数组的地址,其类型是数组指针。

数组在内存中是一块连续的空间,&数组名是指向整个数组的指针,而数组名是指向数组首元素的指针,它们都指向了整个数组的起始位置,因此它们的地址是相同的

#include <stdio.h>

int main()
{
	int arr[] = { 1, 2, 3, 4, 5 };
	printf("arr  == %p\n", arr);//数组首元素地址,类型是int*
	printf("&arr == %p\n", &arr);//数组的地址,类型是int (*)[5];
	return 0;
}

运行结果:
在这里插入图片描述
&arr和arr,虽然值是一样的,但其意义是完全不同的。

  • arr是数组名,表示的是数组首元素的地址,类型是int*。
  • &arr表示的数组的地址,类型是int (*)[5]。
  • arr+1,跳过一个整型(4个字节),而&arr+1跳过整个数组(元素个数*元素大小个字节:20个字节)。
#include <stdio.h>

int main()
{
	int arr[] = { 1, 2, 3, 4, 5 };
	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;
}

运行结果:
在这里插入图片描述

4. 数组参数

在写代码的时候难免要把 数组 或者 指针 传给函数,下面将详细介绍数组、指针传参时,函数的参数是如何设计的。

4.1 一维数组传参

  1. 函数参数用数组来接收数组传参。

函数可以接受一个一维数组作为参数,这样函数可以访问整个数组。函数声明中使用数组名,这里可以不指定数组的大小。

void processArray(int arr[], int size) 
{
    // 在函数中使用 arr 数组,size 表示数组的大小
    for (int i = 0; i < size; i++) 
    {
        // 处理数组元素
    }
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
    processArray(arr, size); // 将数组 arr 和 数组大小传递给函数
    return 0;
}
  1. 函数参数用指针来接收数组传参。

数组名是首元素的地址,当数组作为实参传递给函数参数(形参)时,其实是将数组的首元素的地址传递给了形参,因此,可以使用指针来接收数组传参。

void processArray(int* arr, int size) 
{
    //在函数中使用 arr 指针,size 表示数组的大小
    for (int i = 0; i < size; i++) 
    {
        // 处理数组元素
    }
}

int main() {
    int arr[] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
    processArray(arr, size); // 将数组 arr 和 数组大小传递给函数
    return 0;
}
  1. 即使上面数组传参,用数组来接收时,其实并没有创建一个新的数组来接收,本质上也是将数组首元素的地址传递给了形参。
    在这里插入图片描述

4.2 二维数组传参

二维数组作为实参传递给函数形参时,和一维数组传参类似,也可以使用数组或者指针来接收。

  1. 函数参数用数组来接收数组传参。

二维数组作为实参传递给函数形参时,可以使用一个二维数组来接收。需要注意的是,函数形参部分的二维数组的列数是不可以省略的,行数可以省略。但为了代码的可读性,通常建议明确指定二维数组的行数和列数。

#include <stdio.h>

// 定义函数,接受二维数组和行列数作为参数
void process2DArray(int arr[][3], int row, int col) 
{
    for (int i = 0; i < rows; i++) 
    {
        for (int j = 0; j < cols; j++) 
        {
            //处理数组元素
        }
    }
}

int main() 
{
    int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    
    // 调用函数并传递二维数组以及行列数
    process2DArray(arr, 3, 3);

    return 0;
}
  1. 函数参数用指针来接收数组传参。

我们知道,在大多数情况下,数组名是数组首元素地址,那么二维数组的的数组名是什么呢?其实,二维数组也没例外,其数组名表示的也是首元素的地址,其首元素是一个一维数组,所以首元素的地址也就是一维数组的地址,其类型为数组指针。
在这里插入图片描述
二维数组作为实参,传递给函数形参时,和一维数组一样,也是将数组首元素地址传递给了形参,因此参数可以用指针来接收。

#include <stdio.h>

// 定义函数,使用指针来接收
void process2DArray(int (*arr)[3], int row, int col) 
{
    for (int i = 0; i < rows; i++) 
    {
        for (int j = 0; j < cols; j++) 
        {
            //处理数组元素
        }
    }
}

int main() 
{
    int arr[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
    
    // 调用函数并传递二维数组以及行列数
    process2DArray(arr, 3, 3);

    return 0;
}

5. 函数指针

函数指针是指向函数的指针,其存放的是函数的地址。
函数指针的定义方式:函数返回类型 + (* 指针变量名)+ (函数参数1, 函数参数2, …)= &函数名(或者函数名)。

#include <stdio.h>

int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int (*pf)(int, int) = &Add;//()的优先级要高于*号的,所以必须加上()来保证pf先和*结合。
}

在这里插入图片描述
了解上述以后,来分析如下两段代码:

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

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

6. 函数指针数组

函数指针数组是一个数组,数组的每个元素都是一个指向函数的指针。
函数指针数组定义方式:函数返回值类型 + (* 数组名[数组元素个数])+(函数参数1, 函数参数2, …);
例如:parr就是一个函数指针数组。

int (*parr[10])();

在这里插入图片描述
函数指针数组其中一个重要用途是实现转移表。它包含了一组函数指针,用于根据某个输入条件或标识来选择执行不同的操作或函数。
如下代码就是使用函数指针数组来实现函数的选择和调用。

#include <stdio.h>

void menu()
{
	printf("**************************\n");
	printf("****  1. Add  2. Sub  ****\n");
	printf("****  3. Mul  4. Div  ****\n");
	printf("****      0. exit     ****\n");
	printf("**************************\n");
}

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 input = 0;
	int x = 0;
	int y = 0;
	int(*cal[5])(int, int) = { NULL, Add, Sub, Mul, Div };

	do
	{
		menu();//打印菜单
		printf("请输入你的选择:");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数:");
			scanf("%d%d", &x, &y);
			int ret = cal[input](x, y);
			printf("ret == %d\n", ret);
		}
		else
		{
			printf("选择错误,请重新输入\n");
		}
		
	} while (input);
	return 0;
}
  • 在上面的代码中,我们首先定义了四个不同的函数(加法、减法、乘法、除法),它们的地址都与 int(*)(int, int)类型相匹配。

  • 接下来,我们声明了一个函数指针数组 cal,其中的每个元素都是一个指向函数的指针,这些函数的指针都具有相同类型。我们用这些函数的地址来初始化这个函数指针数组。

  • 最后,我们通过选择 input 变量来决定要执行的操作,然后使用函数指针数组来调用相应的函数,并输出结果。

7. 指向函数指针数组的指针

指向函数指针数组的指针,其存放的是函数指针数组的的地址。

void test(const char* str)
{
 printf("%s\n", str);
}
int main()
{
 	//函数指针pfun
 	void (*pfun)(const char*) = test;
 	//函数指针的数组pfunArr
 	void (*pfunArr[5])(const char* str);
 	pfunArr[0] = test;
	 //指向函数指针数组pfunArr的指针ppfunArr
 	void (*(*ppfunArr)[5])(const char*) = &pfunArr;
 	return 0;
}

在这里插入图片描述
这里了解一下就好,不是很重要。

8. 回调函数

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

例如,排序函数可以接受用户定义的比较函数作为回调来自定义排序规则。(冒泡排序排任意类型的数据)。

#include <stdio.h>

//用户自定义的比较函数
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}
//打印数组内容
void Print(int* arr, int sz)
{
	for (int i = 0; i < sz; ++i)
	{
		printf("%d ", arr[i]);
	}

	printf("\n");
}
//交换两个数据
void swap(char* e1, char* e2, size_t size)
{
	for (size_t i = 0; i < size; ++i)
	{
		char tmp = *e1;
		*e1 = *e2;
		*e2 = tmp;
		e1++;
		e2++;
	}
}

//冒泡排序排任意类型的数据
void bubble_sort(void* base, size_t num, size_t size, int(*cmp)(const void*, const void*))
{
	//排序趟数
	for (size_t i = 0; i < num - 1; ++i)
	{
		//每一趟排序比较的趟数
		for (size_t j = 0; j < num - 1 - i; ++j)
		{
			//通过函数指针调用比较函数进行比较.
			if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0)
			{
				//交换两个数据
				swap((char*)base + j * size, (char*)base + (j + 1) * size, size);
			}
		}
	}
}

int main()
{
	int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };
	size_t sz = sizeof(arr) / sizeof(int);

	printf("排序前:");
	Print(arr, sz);

	bubble_sort(arr, sz, sizeof(int), cmp_int);

	printf("排序后:");
	Print(arr, sz);

	return 0;
}

代码的主要部分和执行流程:

  • cmp_int 函数是一个比较函数,用于比较两个整数的大小。将它传递给 bubble_sort 函数以进行元素比较。

  • Print 函数用于打印整数数组的内容。

  • swap 函数用于交换两个元素的内容,它是通用的,可以处理不同数据类型的元素

  • bubble_sort 函数实现了冒泡排序算法。它接收一个指向待排序数组的指针 base,数组的元素个数 num,每个元素的大小 size,以及一个进行元素比较的函数 cmp。在排序过程中,它使用传递的比较函数来决定元素的顺序,并使用 swap 函数来交换元素的位置。

  • 在 main 函数中,创建了一个整数数组 arr,然后调用 bubble_sort 函数对数组进行排序,并使用 cmp_int 函数进行比较。最后,打印排序前和排序后的数组内容。

运行结果:
在这里插入图片描述

上面的代码就展示了如何使用函数指针以及通用的交换函数来实现冒泡排序。

至此,本片文章就结束了,若本篇内容对您有所帮助,请三连点赞,关注,收藏支持下。

创作不易,白嫖不好,各位的支持和认可,就是我创作的最大动力,我们下篇文章见!

如果本篇博客有任何错误,请批评指教,不胜感激 !!!
在这里插入图片描述

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

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

相关文章

文本自动输入/删除的加载动画效果

效果展示 CSS 知识点 绕矩形四周跑的光柱动画实现animation 属性的 steps 属性值运用 页面基础结构实现 <div class"loader"><!-- span 标签是围绕矩形四周的光柱 --><span></span><span></span><span></span>&l…

Git 学习笔记 | 使用码云

Git 学习笔记 | 使用码云 Git 学习笔记 | 使用码云注册登录码云&#xff0c;完善个人信息设置本机绑定SSH公钥&#xff0c;实现免密码登录创建远程仓库 Git 学习笔记 | 使用码云 注册登录码云&#xff0c;完善个人信息 网址&#xff1a;https://gitee.com/ 可以使用微信&…

SpringBoot结合dev-tool 实现IDEA项目热部署

什么是热部署&#xff1f; 应用正在运行的时候升级功能, 不需要重新启动应用对于Java应用程序来说, 热部署就是在运行时更新Java类文件 通俗的来讲&#xff0c;应用在运行状态下&#xff0c;修改项目源码后&#xff0c;不用重启应用&#xff0c;会把编译的内容部署到服务器上…

【Acwing1010】拦截导弹(LIS+贪心)题解

题目描述 思路分析 本题有两问&#xff0c;第一问直接用lis的模板即可&#xff0c;下面重点看第二问 思路是贪心&#xff1a; 贪心流程&#xff1a; 从前往后扫描每一个数&#xff0c;对于每个数&#xff1a; 情况一&#xff1a;如果现有的子序列的结尾都小于当前的数&…

stm32的GPIO寄存器操作以及GPIO外部中断,串口中断

一、学习参考资料 &#xff08;1&#xff09;正点原子的寄存器源码。 &#xff08;2&#xff09;STM32F103最小系统板开发指南-寄存器版本_V1.1&#xff08;正点&#xff09; &#xff08;3&#xff09;STM32F103最小系统板开发指南-库函数版本_V1.1&#xff08;正点&a…

【重拾C语言】七、指针(一)指针与变量、指针操作、指向指针的指针

目录 前言 七、指针 7.1 指针与变量 7.1.1 指针类型和指针变量 7.1.2 指针所指变量 7.1.3 空指针、无效指针 7.2 指针操作 7.2.1 指针的算术运算 7.2.2 指针的比较 7.2.3 指针的递增和递减 7.3 指向指针的指针 前言 指针是C语言中一个重要的概念正确灵活运用指针 可…

单元测试该怎么写

单元测试对于开发人员来说很熟悉&#xff0c;各种语言都提供了单元测试的框架&#xff0c;用于自动化执行单元测试并生成测试报告。它通常提供了一组API和工具&#xff0c;使开发人员能够编写和运行测试用例&#xff0c;比较预期行为和实际行为之间的差异&#xff0c;并准确地识…

Android Studio新建项目缓慢解决方案

关于Android Studio2022新建项目时下载依赖慢的解决方案 起因解决方案gradle下载慢解决方案kotlin依赖下载慢解决方案 结尾 起因 新建Android Studio项目时&#xff0c;常会因为网络问题导致部分依赖下载缓慢&#xff0c;其中gradle和kotlin最拖慢进度。 解决方案 gradle下载…

Spring源码解析——IOC属性填充

正文 doCreateBean() 主要用于完成 bean 的创建和初始化工作&#xff0c;我们可以将其分为四个过程&#xff1a; 最全面的Java面试网站 createBeanInstance() 实例化 beanpopulateBean() 属性填充循环依赖的处理initializeBean() 初始化 bean 第一个过程实例化 bean在前面一篇…

四位十进制数字频率计VHDL,仿真视频、代码

名称&#xff1a;四位十进制数字频率计VHDL&#xff0c;quartus仿真 软件&#xff1a;Quartus 语言&#xff1a;VHDL 代码功能&#xff1a; 使用直接测频法测量信号频率&#xff0c;测频范围为1~9999Hz&#xff0c;具有超量程报警功能 演示视频&#xff1a;四位十进制数字频…

5分钟理解什么是卷积的特征提取

大家好啊&#xff0c;我是董董灿。 卷积算法之所以重要&#xff0c;关键在于其提取特征的能力。 5分钟入门卷积算法中提到&#xff0c;卷积模仿的就是人眼识图的过程&#xff0c;以“感受野”的视角去扫描图片&#xff0c;从而获取不同区域的图片信息。 在这一过程中&#x…

Scratch3.0下载

通俗易懂&#xff0c;直接上链接 链接&#xff1a;https://pan.baidu.com/s/1n-QFEQWT8im8BHQu1wIjtg?pwd1016 提取码&#xff1a;1016

高级IO(Linux)

高级IO 五种IO模型高级IO重要概念同步通信 vs 异步通信阻塞 vs 非阻塞 非阻塞IOfcntl实现函数SetNoBlock轮询方式读取标准输入 I/O多路转接之select初识selectselect函数原型参数解释参数timeout取值关于fd_set结构关于timeval结构函数返回值三级目录 理解select执行过程socket…

多功能频率计周期/脉宽/占空比/频率测量verilog,视频/代码

名称&#xff1a;多功能频率计周期、脉宽、占空比、频率测量verilog 软件&#xff1a;Quartus 语言&#xff1a;Verilog 代码功能&#xff1a; 多功能频率计&#xff0c;可测量信号的周期、脉冲宽度、占空比、频率&#xff0c;语言为verilog&#xff0c;quartus软件设计仿真…

B (1089) : DS单链表--合并

Description 假定两个单链表是递增有序&#xff0c;定义并实现以下函数&#xff0c;完成两个单链表的合并&#xff0c;继续保持递增有序 int LL_merge(ListNode *La, ListNode *Lb) Input 第1行先输入n表示有n个数据&#xff0c;接着输入n个数据 第2行先输入m表示有M个数据…

扭线机控制

扭线机属于线缆加工设备&#xff0c;线缆加工设备种类非常多。有用于网线绞合的单绞&#xff0c;双绞机等&#xff0c;有关单绞机相关算法介绍&#xff0c;大家可以查看专栏相关文章&#xff0c;有详细介绍&#xff0c;常用链接如下&#xff1a; 线缆行业单绞机控制算法&#…

MySQL命令行中文乱码问题

MySQL命令行中文乱码问题&#xff1a; 命令行界面默认字符集是gbk&#xff0c;若字符集不匹配会中文乱码或无法插入中文。 解决办法&#xff1a;执行set names gbk; 验证&#xff1a; 执行命令show variables like ‘char%’;查看默认字符集。 创建数据库设置字符集utf8&…

Nginx详细学习记录

1. Nginx概述 Nginx是一个轻量级的高性能HTTP反向代理服务器&#xff0c;同时它也是一个通用类型的代理服务器&#xff0c;支持绝大部分协议&#xff0c;如TCP、UDP、SMTP、HTTPS等。 1.1 Nginx基础架构 Nginx默认采用多进程工作方式&#xff0c;Nginx启动后&#xff0c;会运行…

多线程锁-synchronized字节码分析

从字节码角度分析synchronized实现 javap -c(v附加信息) ***.class 文件反编译 synchronized同步代码块 >>>实现使用的是monitorenter和monitorexit指令 synchronized普通同步方法 >>>调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置&#xf…

HTTPS工作过程,国家为什么让http为什么要换成https,Tomcat在MAC M1电脑如何安装,Tomcat的详细介绍

目录 引言 一、HTTPS工作过程 二、Tomcat 在访达中找到下载好的Tomcat文件夹&#xff08;这个要求按顺序&#xff09; zsh: permission denied TOMCAT的各部分含义&#xff1a; 引言 在密码中一般是&#xff1a;明文密钥->密文&#xff08;加密&#xff09; &#xff…