C语言_指针(2)

news2024/11/16 19:57:16

1.指针与数组的关系

1.1 数组名

先看代码:

#include <stdio.h>
int main()
{
 	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("&arr[0] = %p\n", &arr[0]);
	printf("arr = %p\n", arr);
 	return 0;
 }

运行结果是这样的:
在这里插入图片描述
我们可以看到,数组名和数组首元素地址打印出的结果一模一样。其实,数组名就是数组首元素的地址。但有两个例外

  • sizeof(数组名)sizeof中单独放数组名,表示的是整个数组,计算的是整个数组的大小,单位是字节。
  • &数组名:取出的是整个数组的地址。(整个数组的地址和数组首元素的地址是有区别的)。

除此之外,任何地方的数组名只表示首元素地址。

我猜,这个时候,有些朋友会运行下面的代码,想找出arr&arr的差异:

#include <stdio.h>
int main()
{
 	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 	printf("&arr[0] = %p\n", &arr[0]);
 	printf("arr = %p\n", arr);
 	printf("&arr = %p\n", &arr);
 	return 0;
}

运行出来三个一模一样的地址,这时,屏幕面前的朋友会质疑区分arr&arr的必要性。

其实他们的差异可以体现在指针的运算上,如arrarr+1相差4个字节,而&arr&arr+1相差40个字节。他们本身的大小不同,运算的跨度自然也相异。

有兴趣的朋友可以运行一下下方的代码:

#include <stdio.h>
int main()
{
 	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 	printf("&arr[0] = %p\n", &arr[0]);
	printf("&arr[0]+1 = %p\n", &arr[0]+1);
 	printf("arr = %p\n", arr);
 	printf("arr+1 = %p\n", arr+1);
 	printf("&arr = %p\n", &arr);
 	printf("&arr+1 = %p\n", &arr+1);
 	return 0;
}

1.2 使用指针访问数组

代码如下:

#include<stdio.h>

int main(){
	int arr[10] = {0};
	int i = 0;
	int sz = sizeof(arr)/sizeof(arr[0]);
	int *p = arr;
	for(i=0; i<sz; i++){
		scanf("%d",p+i);//可以写arr+i
	}
	for(i=0; i<sz ; i++){
		printf("%d",*(p+i));//可以写*(arr+i)
	}
	return 0;
}

这段代码中说明了指针访问数组的方式。
我们知道数组元素的访问是用arr[i]的,那arr已经赋值给p,用p[i]可以达到同样效果吗?

答案是可以,有以下代码:

#include <stdio.h>

int main()
{
 	int arr[10] = {0};
 	int i = 0;
 	int sz = sizeof(arr)/sizeof(arr[0]);
 	int* p = arr;
 	for(i=0; i<sz; i++)
 	{
 		scanf("%d", p+i);
 	}
	for(i=0; i<sz; i++)
 	{
 		printf("%d ", p[i]);
 	}
 	return 0;
}

运行结果与第一段代码别无二致,所以我们可以知道本质上p[i]等价于*(p+i),同理arr[i]等价于*(arr+i)

数组元素的访问在编译器处理时,也是转换成首元素的地址+偏移量求出元素的地址,然后解引用来访问的。

1.3 一维数组传参的本质

先看代码:

#include<stdio.h>

void test(int arr[]){
	int sz2 = sizeof(arr)/sizeof(arr[0]);
	printf("sz2 = %d\n", sz2);
}

int main(){
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	int sz1 = sizeof(arr)/sizeof(arr[0]);
	printf("sz1 = %d\n", sz1);
	test(arr);
	return 0;
}

运行结果如下:

在这里插入图片描述
我们可以看到,在函数内部是没有正确获得数组的元素个数的。

我们知道,数组传参的时候,传递的是数组名,也就是说本质上数组传参传递的是数组首元素的地址。

所以函数形参理论上应该用指针变量来接收首元素的地址。正是因为函数的参数部分的本质是指针变量,所以当首元素地址传入函数时,数组名变成了一个彻彻底底的地址,sizeof(arr)返回的是一个地址的大小。

注:
一维数组传参,函数形参的部分既可以写成数组的形式,也可以写成指针的形式。如下函数代码段:

void test(int* arr){//指针形式的形参
	printf("%d",sizeof(arr));//计算的是一个指针变量的大小
}

1.4 指针数组

指针数组,听到这个名字,有些朋友就会郁闷了,指针数组是指针还是数组?

我们可以类比一下,整形数组,即存放整形的数组,既然这样,那指针数组不就是存放指针的数组咯。

1.4.1 指针数组的定义

我们在定义整形数组和字符数组时,分别用了intchar,所以数组的内容是什么类型就用什么类型来定义。

如定义一个字符指针数组整形指针数组

char* arr[5] = {NULL};
int* arr1[6] = {NULL};

1.4.2 指针数组模拟二维数组

代码实现如下:

#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* p[3] = {arr1,arr2,arr3};
	int i = 0;
	int j = 0;
	for(i=0;i<3;i++){
		for(j=0;j<5;j++){
			printf("%d\t",p[i][j]);//等同于 *(*(p+1)+j)
		}

	}
	return 0;
}

上方代码模拟出了二维数组的效果,但与二维数组有所差异,它的行与行之间不是连续的

1.5 数组指针变量

1.5.1 对数组指针变量形式的理解

数组指针变量,顾名思义,是用来存放数组地址的。形式如下:

int (*p)[10]

解释:p先与*结合,说明p是一个指针变量,然后指针指向的是一个大小为10个整形的数组。所以p是一个指针,指向一个数组,叫数组指针。

数组指针的初始化方式如下:

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


[ ]的优先级高于 * 号,所以必须加上()保证p先和 * 结合。

1.5.2 二维数组传参的本质

二维数组传参代码如下:

#include<stdio.h>

void test(int a[3][5], int r, int c){
	int i = 0;
	int j = 0;
	for(i=0; i<r; i++){
		for(j=0; j<c; j++){
			printf("%d",a[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}};
	test(arr, 3, 5);
	return 0;
}

二维数组可以看做每个元素为一维数组的数组。那么二维数组的首元素就是第一行,是一个一维数组。如下图所示:

在这里插入图片描述
二维数组传参传的也是首元素的地址,也就是第一行这个一维数组的地址,拿上方代码举例子,二维数组中元素是int [5];所以首元素地址的类型就是数组指针类型 (int(*)[5]) 。当然,函数形参也可以写成指针形式的。如下方函数代码段:

#include<stdio.h>

void test(int (*p)[5], int r, int c){
	int i = 0;
	int j = 0;
	for(i=0; i<r; i++){
		for(j=0; j<c; j++){
			printf("%d",*(*(p+1)+j));
		}
		printf("\n");
	}
}

2. 指针与函数的关系

2.1 函数指针变量

函数指针变量是用来存放函数地址的,函数地址可以通过函数名和&函数名的方式获得

函数指针类型解析如下:
在这里插入图片描述

2.1.1 函数指针变量的创建

函数指针变量的创建有两种情况,代码如下:

无参数时

void test(){
	printf("hello world!!");
}

void (*pf1)() = test;
void (*pf2)() = &test;

有参数时

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

void (*pf3)(int, int) = Add;//x和y写上或省略都可以
void (*pf4)(int x,int y) = &Add;

2.1.2 函数指针变量的使用

先看代码:

#include<stdio.h>

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

int main(){
	int (*pf3)(int, int ) = Add;
	printf("%d\n", (*pf3)(2, 3));
 	printf("%d\n", pf3(3, 5));
	return 0;
}

输出结果:
在这里插入图片描述
可以通过函数指针调用指针指向的函数。

分享俩个有趣的代码:
代码一:

(*(void (*)())0)();

代码二:

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

这两段代码都出自《C陷阱与缺陷》

2.2 函数指针数组

函数指针数组的定义形式如下:

int (*parr[2])();

parr先和 [ ] 结合,说明 parr 是数组,数组的内容是 int (*)( ) 类型的函数指针。

函数指针数组的用途:转移表

例如,计算器的一般实现:

#include<stdio.h>

int add(int a, int b)
{
 	return a + b;
}
int sub(int a, int b)
{
 	return a - b;
}
int mul(int a, int b)
{
 	return a * b;
}
int div(int a, int b)
{
 	return a / b;
}
int main()
{
 	int x, y;
 	int input = 1;
 	int ret = 0;
 	do
 	{
 		printf("*************************\n");
 		printf(" 1:add 			   2:sub \n");
		printf(" 3:mul 			   4:div \n");
 		printf(" 0:exit                  \n");
 		printf("*************************\n");
 		printf("请选择:");
 		scanf("%d", &input);
 		switch (input)
 		{
 		case 1:
 			printf("输⼊操作数:");
 			scanf("%d %d", &x, &y);
 			ret = add(x, y);
 			printf("ret = %d\n", ret);
 			break;
 		case 2:
 			printf("输⼊操作数:");
 			scanf("%d %d", &x, &y);
 			ret = sub(x, y);
 			printf("ret = %d\n", ret);
 			break;
 		case 3:
 			printf("输⼊操作数:");
 			scanf("%d %d", &x, &y);
 			ret = mul(x, y);
 			printf("ret = %d\n", ret);
 			break;
 		case 4:
 			printf("输⼊操作数:");
 			scanf("%d %d", &x, &y);
 			ret = div(x, y);
 			printf("ret = %d\n", ret);
 			break;
 		case 0:
 			printf("退出程序\n");
 			break;
 		default:
 			printf("选择错误\n");
 			break;
 		}
	}while(input);

	return 0;

使用函数指针数组的实现:

#include<stdio.h>

int add(int a, int b)
{
 	return a + b;
}
int sub(int a, int b)
{
 	return a - b;
}
int mul(int a, int b)
{
 	return a * b;
}
int div(int a, int b)
{
 	return a / b;
}
int main()
{	
	int x, y;
	int input = 1 ;
	int ret = 0;
	int (*p[5])(int x.int y) = { 0, add, sub, mul, div };//转移表
	do
	{
		printf("*************************\n");
 		printf(" 1:add 			   2:sub \n");
		printf(" 3:mul 			   4:div \n");
 		printf(" 0:exit                  \n");
 		printf("*************************\n");
 		printf("请选择:");
 		scanf("%d", &input);
 		if(input<=4 && input >= 1)
 		{		
 			printf( "输⼊操作数:" );
 			scanf( "%d %d", &x, &y);
 			ret = (*p[input])(x, y);
 			printf( "ret = %d\n", ret);
		}
		else if(imput = 0){
			printf("退出计算器\n")}
		else{
			printf("输入有误\n");
		}

	}while(input);
	
	return 0;
}

3. 字符指针变量

字符指针char* 有两种使用方式。
一般使用:

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

还有一种使用方式如下:

int main(){
	const char* pstr = "hello";
	return 0;
}

代码 const char* pstr = “hello”;的本质是把字符串hello首字符的地址放到了pstr中。

《剑指offer》中有一到和字符串相关的题,我们来一起看一下:

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

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

这段代码中str3str4指向的是⼀个同⼀个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但要是用相同的常量字符串取初始化两个不同的字符数组时,就会开辟出不同的内存块。所以str1str2不同。

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

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

相关文章

数据结构 ——— 数组 nums 包含了从 0 到 n 的所有整数,但是其中缺失了一个,请编写代码找出缺失的整数,并且在O(N)时间内完成

目录 题目要求 代码实现 方法1&#xff08;异或法&#xff09;&#xff1a; 异或算法的时间复杂度&#xff1a; 方法2&#xff08;等差数列公式&#xff09;&#xff1a; 等差数列公式的时间复杂度&#xff1a; 题目要求 整型数组 nums 包含了从 0 到 n 的所有整数&…

【有啥问啥】 Self-Play技术:强化学习中的自我进化之道

Self-Play技术&#xff1a;强化学习中的自我进化之道 在人工智能的快速发展中&#xff0c;强化学习&#xff08;Reinforcement Learning, RL&#xff09;已成为推动智能体自主学习与优化的关键力量。Self-Play技术&#xff0c;作为强化学习领域的一项前沿创新&#xff0c;通过…

Java语法-类和对象(上)

1. 面向对象的初步认识 1.1 什么是面向对象 概念: Java是一门纯面向对象的语言(Object Oriented Program&#xff0c;简称OOP)&#xff0c;在面向对象的世界里&#xff0c;一切皆为对象。 1.2 面向对象VS面向过程 如:洗衣服 面向过程: 注重的是洗衣服的过程,少了一个环节也不…

SPSS26统计分析笔记——3 假设检验

1 假设检验原理 假设检验的基本原理源于“小概率事件”原理&#xff0c;是一种基于概率性质的反证法。其核心思想是小概率事件在一次试验中几乎不会发生。检验的过程首先假设原假设 H 0 {H_0} H0​成立&#xff0c;然后通过统计方法分析样本数据。如果样本数据引发了“小概率事…

《数据压缩入门》笔记-Part 2

一篇文章显得略长&#xff0c;本文对应原书6-10章。序言、前言、第1-5章&#xff0c;请参考Part 1&#xff0c;第11-15章&#xff0c;请参考Part 3。 自适应统计编码 位置对熵的重要性 统计编码有一个问题&#xff1a;在编码开始之前都需要遍历一次数据&#xff0c;以计算出…

Linux:八种重定向详解(万字长文警告)

相关阅读Linuxhttps://blog.csdn.net/weixin_45791458/category_12234591.html?spm1001.2014.3001.5482 本文将讨论Linux中的重定向相关问题&#xff0c;在阅读本文前&#xff0c;强烈建议先学习文件描述符的相关内容Linux&#xff1a;文件描述符详解。 重定向分为两类&#x…

智能感知,主动防御:移动云态势感知为政企安全护航

数字化时代&#xff0c;网络安全已成为企业持续运营和发展的重要基石。随着业务扩展&#xff0c;企业资产的数量急剧增加&#xff0c;且分布日益分散&#xff0c;如何全面、准确地掌握和管理资产成为众多政企单位的难题。同时&#xff0c;传统安全手段又难以有效应对新型、隐蔽…

【unity进阶知识1】最详细的单例模式的设计和应用,继承和不继承MonoBehaviour的单例模式,及泛型单例基类的编写

文章目录 前言一、不使用单例二、普通单例模式1、单例模式介绍实现步骤&#xff1a;单例模式分为饿汉式和懒汉式两种。 2、不继承MonoBehaviour的单例模式2.1、基本实现2.2、防止外部实例化对象2.3、最终代码 3、继承MonoBehaviour的单例模式3.1、基本实现3.2、自动创建和挂载单…

苏轼为何要写石钟山记?时间节点是关键

《石钟山记》不仅是苏轼的旅行笔记&#xff0c;亦是其人生哲学与思想的深邃自省。文中不仅详述了他对石钟山的实地勘察&#xff0c;亦体现了其对历史、自然及人生之独到见解。黄州生涯及其对政治与文化的洞悉&#xff0c;为这篇作品注入了深厚底蕴。 苏轼的黄州岁月 黄州期间…

后端回写前端日期格式化

问题 不进行格式化处理&#xff0c;就会导致传递的字符串很奇怪 解决方案 注解&#xff08;字段&#xff09; <dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.2</…

9.创新与未来:ChatGPT的新功能和趋势【9/10】

创新与未来&#xff1a;ChatGPT的新功能和趋势 引言 在探讨人工智能的发展历程时&#xff0c;我们可以看到它已经从早期的图灵机和人工神经网络模型&#xff0c;发展到了今天能够模拟人类智能的复杂系统。人工智能的起源可以追溯到20世纪40年代&#xff0c;而它的重要里程碑包…

构建预测睡眠质量模型_相关性分析,多变量分析和聚类分析

数据入口&#xff1a;睡眠质量记录数据集 - Heywhale.com 本数据集目的是探究不同因素是如何影响睡眠质量和整体健康的。 数据说明 字段说明Heart Rate Variability心率变异性&#xff1a;心跳时间间隔的模拟变化Body Temperature体温&#xff1a;以摄氏度为单位的人工生成体…

Multisim简体中文版百度云下载(含安装步骤)

如大家所熟悉的&#xff0c;Multisim是一款基于电路仿真的软件&#xff0c;可用于电子工程师、电子爱好者和学生进行电路设计、分析和调试。Multisim具有完整的电路设计和仿真功能&#xff0c;可支持模拟电路、数字电路&#xff0c;以及混合电路。 Multisim可以模拟不同电路的…

【数据结构】排序算法系列——归并排序(附源码+图解)

归并排序 归并排序从字面上来看&#xff0c;它的大致核心应与归并有关——归并拆分开来&#xff0c;变成归类和合并&#xff0c;归类则是将数组进行有序化&#xff0c;合并则是将两个有序的数组进行合并变成一个有序的数组。 它的特点在于并不是一开始就将整个数组进行归类和…

MODBUS TCP 转 CANOpen

产品概述 SG-TCP-COE-210 网关可以实现将 CANOpen 接口设备连接到 MODBUS TCP 网络中。用户不需要了解具体的 CANOpen 和 Modbus TCP 协议即可实现将CANOpen 设备挂载到 MODBUS TCP 接口的 PLC 上&#xff0c;并和 CANOpen 设备进行数据交互。 产品特点 &#xf…

Qt 构建目录

Qt Creator新建项目时&#xff0c;选择构建套件是必要的一环&#xff1a; 构建目录的默认设置 在Qt Creator中&#xff0c;项目的构建目录通常是默认设置的&#xff0c;位于项目文件夹内的一个子文件夹中&#xff0c;如&#xff1a;build-项目名-Desktop_Qt_版本号_编译器类型_…

【Linux-基础IO】文件描述符重定向原理缓冲区

文件描述符 文件描述符的概念和原理 通过上述内容&#xff0c;我们知道使用 open 系统调用打开文件时&#xff0c;系统会返回一个文件描述符。这个描述符用于后续的文件操作。 在C语言中默认会打开三个输入输出流&#xff0c;分别是stdin&#xff0c;stdout&#xff0c;stde…

JSP(Java Server Pages)基础使用二

简单练习在jsp页面上输出出乘法口诀表 既然大家都是来看这种代码的人了&#xff0c;那么这种输出乘法口诀表的这种简单算法肯定是难不住大家了&#xff0c;所以这次主要是来说jsp的使用格式问题。 <%--Created by IntelliJ IDEA.User: ***Date: 2024/7/18Time: 11:26To ch…

Web端云剪辑解决方案,提供多轨视频、音频、特效、字幕轨道可视化编辑

传统视频剪辑软件的繁琐安装、高昂硬件要求以及跨平台协作的局限性&#xff0c;让无数创意者望而却步。美摄科技作为云端视频编辑技术的领航者&#xff0c;携其革命性的Web端云剪辑解决方案&#xff0c;正重新定义视频创作的边界&#xff0c;让专业级视频剪辑触手可及&#xff…

LeetCode 909. 蛇梯棋

LeetCode 909. 蛇梯棋 给你一个大小为 n x n 的整数矩阵 board &#xff0c;方格按从 1 到 n2 编号&#xff0c;编号遵循 转行交替方式 &#xff0c;从左下角开始 &#xff08;即&#xff0c;从 board[n - 1][0] 开始&#xff09;的每一行改变方向。 你一开始位于棋盘上的方格 …