C语言指针进阶_字符指针、指针数组、数组指针、函数指针等的介绍

news2024/10/7 14:31:32

文章目录

  • 前言
  • 一、字符指针
  • 二、指针数组
  • 三、 数组指针
    • 1. 数组名和 & 数组名
    • 2. 数组指针
    • 3. 数组指针解引用
  • 四、数组指针的使用
    • 二维数组的传参说明数组指针使用
      • 小测验
  • 五、数组传参和指针传参
    • 1. 一维数组传参总结
    • 2. 二维数组传参总结
    • 3. 一级指针传参
    • 4. 二级指针传参
  • 六、函数指针
    • 函数指针的使用
    • 函数指针的案例
  • 总结


前言

C语言指针进阶_字符指针、指针数组、数组指针、数组指针定义、数组指针的解引用、数组指针的使用、数组传参和指针传参、一维数组传参、二维数组传参、一级指针传参、二级指针传参、函数指针、函数指针的使用等的介绍。


一、字符指针

  • 指向字符的指针就是字符指针
  • 当用指针存储字符串时,存储的是字符串首字符的地址不是整个字符串
	const char* pc = "abcdef";

当写成上述形式时,“abcdef”是一个常量字符,不能被修改,所以定义时,应该加const修饰

#include <stdio.h>
int main()
{
	char* arr1 = "abcdef";
	char* arr2 = "abcdef";

	char arr3[] = "abcdef";
	char arr4[] = "abcdef";

	if (arr1 == arr2)
		printf("arr1 == arr2\n");
	else
		printf("arr1 != arr2\n");
	if (arr3 == arr4)
		printf("arr3 == arr4\n");
	else
		printf("arr3 != arr4\n");

	return 0;
}
  • 当常量字符串存入一个指针变量中时,这个常量在内存中的只读区不能修改
  • 同时,只读区的相同的数据只有一份,所以arr1 和 arr2 存放的地址是相同的。都指向只读区 a 的地址
  • 而当字符串存入不同数组中时,需要在内存中开辟不同的空间用来存放数据。
    所以以上代码的结果为
    在这里插入图片描述

二、指针数组

  • 指针数组本质上是一个数组。
  • 它是一个存放指针的数组。
  • 例如
int* arr[10];
  • arr数组名【10】数组中有10个元素int* 每个元素的类型是int*。
  • 指针数组可以模拟一个二维数组。
  • 但是二维数组在内存中的存储是连续的,而模拟的二维数组不一定连续。
#include <stdio.h>
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };

	int* parr[3] = { arr1, arr2, arr3 };

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", *(parr[i] + j));
			// parr[i] 获取第 i 个元素的内容,i = 0 则parr[i] 为 arr1, arr1就是首元素1的地址
			// parr[i] + j 相当于 各数组首元素地址向后改变,本质还是地址
			// 经过解引用获得 每个地址对应的元素
			// arr[i] 可以写成 *(arr+i) 的形式
			// 所以可以表示成 如下形式
			// printf("%d ", parr[i][j]); // 效果是一样的
		}
		printf("\n");
	}

	return 0;
}
  • 我们也可以通过函数来实现
  • 将parr 作为参数传给函数时,传入的是 parr 首元素的地址。
  • parr 的首元素 arr1 本身就是一个地址, 存放arr1这个地址的空间的地址,应该是一个二级指针。
  • 在函数中解引用,*arr 得到 arr1 即 arr1[10]数组首元素 1 的地址。
  • arr + i 表示 parr的第 i 个元素, *(arr + i) 表示数组(arr1, arr2, arr3)首元素的地址。
  • *(arr + i) + j 表示从数组首元素地址 + j 后的地址
  • 如下:
void print(int** arr)
{
	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");
	}
}
#include <stdio.h>
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };

	int* parr[3] = { arr1, arr2, arr3 }; // arr1 首元素 1 的地址, arr2 首元素 2 的地址....
	// parr 是一个指针数组,里边每个元素(arr1,arr2,arr3)都是地址
	print(parr);

	return 0;
}

三、 数组指针

  • 指向字符的指针就是字符指针。
  • 指向整型的指针就是整型指针。
  • 同理可得
  • 指向数组的指针就是数组指针。
  • 在介绍数组指针之前先介绍 数组名和&数组名。

1. 数组名和 & 数组名

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };

	printf("%p\n", arr);
	printf("%p\n", arr+1);

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

	printf("%p\n", &arr);
	printf("%p\n", &arr+1);


	return 0;
}
  • 结果如下
    在这里插入图片描述

  • 由上可知

  • 数组名通常情况下值得是首元素的地址。

  • 两种情况除外

    1. sizeof(数组名) 表示整个数组的大小, 注意这里的sizeof()只能放数组名。
    1. &arr 取出的是整个数组的首地址。

2. 数组指针

由上可知,&arr 取得的是 整个数组的地址。解引用这个地址将指向整个数组。
所以就需要指针变量来接收 &arr。
假设指针变量为 p,1. 用 * p 表示它是一个指针。 2. (*p)[10] 表示p指向的数组有10个元素,3. int (*p)[10] 表示指针变量p指向的数组有10个元素并且每个元素的类型都是 int

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };

	int(*p)[10] = &arr;

	int j = 0;
	for (j = 0; j < 10; j++)
	{
		printf("%d ", *(*p + j));
	}

	return 0;
}

3. 数组指针解引用

  1. 数组指针指向整个数组,所以解引用数组指针指向的是整个数组
  2. 整个数组就用 数组名表示, 数组名就是首元素地址
  3. 所以上述*p指的是数组首元素地址+ j 地址向后移动解引用得到地址指向的元素

四、数组指针的使用

二维数组的传参说明数组指针使用

  • 当二维数组的数组名作为参数传入函数时,传入的是二维数组的首元素地址

  • 二维数组首元素地址是一个一维数组。也就是二维数组的第一行

  • 所以相当于传入函数的是一个数组的地址,所以需要用数组指针接收。
    在这里插入图片描述

  • 二维数组传参如下:

void print2(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++)
		{
			// *p 得到 真个数组 即数组名 即 首元素地址
			// *(p+i) 得到每一行的首元素地址
			// *(p+i) + j 得到一行每个元素的地址
			// *(*(p+j) + j) 得到一行每个元素
			printf("%d ", * (*(p + i) + j));
		}
		printf("\n");
	}
}

#include <stdio.h>
int main()
{
	int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };

	print2(arr, 3, 5);

	return 0;
}
  • 我们知道 arr[i] 可以表示成 *(arr + i), 所以 上述打印可以表示成 *(p[i] + j), 还可以继续表示成 p[i][j]

  • 当然,二维数组传参的接收也可以不采用指针的形式

void print1(int arr[3][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 ", arr[i][j]);
		}
		printf("\n");
	}
}
#include <stdio.h>
int main()
{
	int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };

	print1(arr, 3, 5);

	return 0;
}

两次传参的结果是一模一样的 如下:
在这里插入图片描述

小测验

int arr[5];                 //  arr 整型数组
int *parr1[10];             //  parr1 存放整型指针的数组
int (*parr2)[10];           //  parr2 指向数组元素为10,元素类型为int的数组指针
int (*parr3[10])[5];        //  parr3 是一个包含指向数组元素为5,元素类型为int的数组指针的数组

在这里插入图片描述

五、数组传参和指针传参

1. 一维数组传参总结

  • 数组传参,形参的部分可以写成数组,也可以写成指针。
void test(int arr[]) // arr是一维数组,可以用一维数组接收,一维数组可以不指定大小
{}

void test(int arr[10]) // 一维数组也可以指定大小
{}

void test(int* arr) // arr是首元素地址,用指针接收
{}

void test2(int *arr[20]) // arr2 是指针数组,可以用指针数组接收
{}

void test2(int **arr) // arr2 是指针数组首元素(首元素是一个地址)的地址,可以用二级指针接收
{}

int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };
	test(arr);
	test2(arr2);

	return 0;
}

2. 二维数组传参总结

void test(int arr[3][5]) // arr 是一个二维数组,可以用数组接收
{}

void test(int arr[][]) // 二维数组不能省略列数
{}  // err

void test(int arr[][5]) // 二维数组可以省略行数
{}

-----------------------
void test(int* arr) // 二维数组的首元素地址,是一个一维数组的地址,所以不能用整型指针接收 
{} // err

void test(int* arr[5]) // arr是首元素的地址,不能用(指针)数组接收
{} // err

void test(int (*arr)[5]) // 二维数组首元素地址,是一维数组的地址,可以用数组指针接收
{}

void test(int **arr) // 二维数组首元素的地址是一维数组的地址,不是地址的地址,所以不能用二级指针接收
{} // err

int main()
{
	int arr[3][5] = { 0 };

	test(arr);
	return 0;
}

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]);
	print(p, sz); // 将一级指针p传给函数
}

  • 如果函数的参数部分是指针,只要是传入函数参数的本质是一级指针就可以。
    1. 可以传入的类型有 &a, 变量a的地址。
    1. 一级指针变量ptr, 存放变量a的地址的指针变量。
    1. 一维数组的数组的数组名
void print(int*p)
{}

int main()
{

	int a = 0;
	int* ptr = &a;
	int arr[10];
	
	print(&a);
	print(ptr);
	print(arr);
	
	return 0;
}

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;
}
  • 如果函数的参数部分是一个二级指针,则需要传入函数的参数本质上是一个二级指针
    1. &p1, p1是一个一级指针变量
    1. p2, p2是一个二级指针变量
    1. arr ,arr是一个指针数组的数组名
    1. 三级指针解引用等
void test(int** ptr)
{}

int main()
{
	int* p1;
	int** p2;
	int* arr[10];// 指针数组

	test(&p1);
	test(p2);
	test(arr);
}

六、函数指针

  • 函数指针的写法与数组指针的写法非常相似
  • pf是函数指针变量,* 表示它是一个指针。
  • (*p)()表示这是一个函数指针
  • (*p)(int, int)表示函数的参数类型是int int
  • int (*p)(int, int)表示函数的返回值类型是int
int Add(int x, int y)
{
	return x + y;
}

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;
--------------------------------------------
	// 函数指针的写法与数组指针的写法非常相似
	int (*pf)(int, int) = &Add;

	int ret = (*pf)(2, 3);
	printf("%d ", ret);
	
	return 0;
}

函数取地址,可以不用& 直接 使用 Add即可

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

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;
-----------------------------------------
	// 函数指针的写法与数组指针的写法非常相似
	int (*pf)(int, int) = Add;

	int ret = (*pf)(2, 3);
	printf("%d ", ret);
	return 0;
}

函数指针的使用

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

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;
------------------------------------------
	// 函数指针的写法与数组指针的写法非常相似
	int (*pf)(int, int) = &Add;

	int ret = (*pf)(2, 3);
	int ret1 = pf(2, 3);
	int ret2 = (*****pf)(2, 3);
	printf("%d ", ret);
	printf("%d ", ret1);
	printf("%d ", ret2);

	return 0;
}

函数指针使用时,可以是

  1. (*pf)(函数参数,函数参数); 然后可以通过变量接收函数返回值。
  2. 还可以 pf(函数参数,函数参数);
  3. 还可以 (*****pf)(函数参数,函数参数);
    (*)可以不写,这里只是为了方便理解

函数指针的案例

  • 函数作为参数传递给另一个函数
#include <stdio.h>
void Add(int x, int y)
{
	return x + y;
}

void calc(int (*pf)(int, int))
{
	int a = 3;
	int b = 5;

	printf("%d ",(*pf)(a, b));
}
int main()
{
	calc(Add);

	return 0;
}

总结

C语言指针进阶_字符指针、指针数组、数组指针、数组指针定义、数组指针的解引用、数组指针的使用、数组传参和指针传参、一维数组传参、二维数组传参、一级指针传参、二级指针传参、函数指针、函数指针的使用等的介绍。

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

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

相关文章

React Context

Context https://juejin.cn/post/7244838033454727227?searchId202404012120436CD549D66BBD6C542177 context 提供了一个无需为每层组件手动添加 props, 就能在组件树间进行数据传递的方法 React 中数据通过 props 属性自上而下(由父及子)进行传递&#xff0c;但此种用法对…

Git使用指北

目录 创建一个Git仓库本地仓库添加文件文件提交到本地仓库缓冲区添加远程仓库地址本地仓库推送到远程仓库创建新的分支拉取代码同步删除缓冲区的文件&#xff0c;远程仓库的文件.gitignore文件 创建一个Git仓库 Git仓库分为远程和本地两种&#xff0c;远程仓库如Githu上创建的…

每天五分钟深度学习框架pytorch:如何创建多维Tensor张量元素?

本文重点 上节课程我们学习了如何创建Tensor标量,我们使用torch.tensor。本节课程我们学习如何创建Tensor向量,我们即可以使用torch.Tensor又可以使用torch.tensor,下面我们看一下二者的共同点和不同点。 Tensor张量 tensor张量是一个多维数组,零维就是一个点(就是上一…

Java零基础入门到精通_Day 9

1.ArrayList 编程的时候如果要存储多个数据&#xff0c;使用长度固定的数组存储格式&#xff0c;不一定满足我们的需求&#xff0c;更适应不了变化的需求&#xff0c;那么&#xff0c;此时该如何选择呢? 集 合 集合类的特点:提供一种存储空间可变的存储模型&#xff0c;存储的…

EMP.DLL是什么东西?游戏提示EMP.DLL文件缺失怎么解决

emp.dll文件是Windows操作系统中的一种动态链接库文件&#xff0c;它被设计为可以被多个程序共享使用的模块化文件。这种设计旨在提高系统效率&#xff0c;减少内存消耗&#xff0c;并简化软件的维护和更新。DLL文件通常包含了一系列相关的函数和变量&#xff0c;这些函数和变量…

C++入门系列-内联函数

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 以inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用内敛函数的地方展开&#xff0c;这就意味着使用内联函数可以提升程序的运行的效率&#xff0c;减小了调用所需…

微软开源 MS-DOS「GitHub 热点速览」

上周又是被「大模型」霸榜的一周&#xff0c;各种 AI、LLM、ChatGPT、Sora、RAG 的开源项目在 GitHub 上“争相斗艳”。这不 Meta 刚开源 Llama 3 没几天&#xff0c;苹果紧跟着就开源了手机端大模型&#xff1a;CoreNet。 GitHub 地址&#xff1a;github.com/apple/corenet 开…

如何判断自己是不是表演型人格障碍?

表演型人格障碍&#xff0c;也叫寻求注意&#xff0c;寻求关注型人格&#xff0c;癔症型人格&#xff0c;是比较常见的人格障碍类型之一。其核心特征包括有&#xff1a;过分的情绪化&#xff0c;夸张的表现形式&#xff0c;渴望成为别人的关注点&#xff0c;其行为往往是幼稚的…

HDR和WDR有什么区别

HDR指高动态范围成像&#xff0c;与之相对的还有一个概念WDR&#xff08;wide dynamic range&#xff09;宽动态范围成像&#xff0c;从概念上来说二者并无本质区别&#xff0c;为了提高图像的动态范围&#xff0c;解决光照条件不一致时图像过暗或过亮的问题。但它们的原理和应…

QT5带UI的常用控件

目录 新建工程&#xff0c;Qmainwindow带UI UI设计器 常用控件区 Buttons 按钮 containers 容器 控件属性区域 对象监视区 布局工具区 信号与槽区 简单例子1 放置一个按钮控件&#xff0c;改文本为发送&#xff0c;该按键为Button1&#xff1b; 按钮关联信号和…

DRF版本组件源码分析

DRF版本组件源码分析 在restful规范中要去&#xff0c;后端的API中需要体现版本。 3.6.1 GET参数传递版本 from rest_framework.versioning import QueryParameterVersioning单视图应用 多视图应用 # settings.pyREST_FRAMEWORK {"VERSION_PARAM": "versi…

C 深入指针(1)

目录 一、const 1、const修饰变量 2、const修饰指针 2.1 const int* p&#xff08;int const* p&#xff09; 2.2 int* const p 2.3 结论 二、指针运算 1、指针 - 整数 2、指针 - 指针 3、指针的关系运算 三、指针的使用 1、模拟实现 strlen 2、传值调用和传址调用…

读天才与算法:人脑与AI的数学思维笔记16_音乐图灵测试

1. 艾米 1.1. 人工智能作曲家 1.1.1. 分析机可能会生成任意复杂程度、精细程度的科学的音乐作品 1.1.1.1. 阿达洛夫莱斯 1.1.2. 巴赫的作品是大多数作曲家开始学习创作的起点&#xff0c;也是大多数计算机开始学习作曲的起点…

汽车车灯罩通常使用的主要材料包括什么?汽车车灯的灯罩如果破损破裂破洞了要怎么修复?

1.聚碳酸酯&#xff08;PC&#xff09;&#xff1a;PC具有高强度、高韧性和耐冲击性&#xff0c;因此常用于汽车车灯的外罩和透镜部分。它还具有良好的透光性&#xff0c;使得车灯能够有效地发光。 2.丙烯酸酯&#xff08;PMMA&#xff09;&#xff1a;PMMA&#xff0c;也称为…

基于GWO灰狼优化的CNN-LSTM-Attention的时间序列回归预测matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1卷积神经网络&#xff08;CNN&#xff09;在时间序列中的应用 4.2 长短时记忆网络&#xff08;LSTM&#xff09;处理序列依赖关系 4.3 注意力机制&#xff08;Attention&#xff09; 4…

SpringBoot实现图片上传(个人头像的修改)

SpringBootlayui实现个人信息头像的更改 该文章适合对SpringBoot&#xff0c;Thymeleaf&#xff0c;layui入门的小伙伴 废话不多说&#xff0c;直接上干货 Springbootlayui实现头像更换 前端公共部分代码 HTML页面代码 <div class"layui-card-header" style&quo…

[实例] Unity Shader 利用顶点着色器模拟简单水波

我们都知道顶点着色器可以用来改变模型各个顶点的位置&#xff0c;那么本篇我们就利用顶点着色器来做一个模拟简单水波的应用。 1. 简谐运动 在进行模拟水波之前&#xff0c;我们需要了解简谐运动&#xff08;Simple Harmonic Motion&#xff09;公式&#xff1a; 其中&#…

有公网IP的好处?

1. 维护远程连接需求的解决方案 公网IP是指可以通过互联网直接访问的IP地址&#xff0c;相对于私有IP地址而言具有重要的好处。公网IP的最大好处之一是解决了各行业客户的远程连接需求。由于天联组网操作简单、跨平台应用、无网络要求以及独创的安全加速方案等原因&#xff0c…

Python 全栈体系【四阶】(三十八)

第五章 深度学习 八、目标检测 3. 目标检测模型 3.2 YOLO 系列 3.2.1 YOLOv1&#xff08;2016&#xff09; 3.2.1.1 基本思想 YOLO&#xff08;You Only Look Once &#xff09;是继 RCNN&#xff0c;fast-RCNN 和 faster-RCNN 之后&#xff0c;Ross Girshick 针对 DL 目…

人工智能论文:BERT和GPT, GPT-2, GPT-3 的简明对比和主要区别

在BERT的论文里面&#xff1a; 2018.10 BERT: Pre-training of Deep Bidirectional Transformers for Language Understanding&#xff0c;BERT已经解释了BERT&#xff0c;GPT&#xff0c;ELMo的区别。 *ELMo为双向RNN&#xff0c;请忽略。 主要区别&#xff1a; BERT使用的是…