指针的学习和理解

news2025/1/11 16:50:27

初级

1、指针的概念

在64位操作系统中,不管什么类型的指针都占8个字节

int a=1;
int* p=&a;//p就是一个整型的指针,保存了a的地址

2、指针和变量

int* p=&a;
   * p=100; // 等价于a=100
     p //p=&a

有两种定义:

  • 定义的时候(前面有类型),表示后面的变量是指针
  • 使用的时候,表示取值(取指针指向的内存空间里的值)

intp1 和 charp2 字节大小一样,但步长不一样。p1+1指向下一个整型,跨4个字节;p2+1指向下一个char型,跨1个字节

并不是指针一定能保存地址,而是要保存相同类型的地址。

指针作为函数参数

比如本来传给函数是一个结构体参数,但结构体成员很多,占很多字节;如果传入结构体的地址,就只需要8个字节,很省空间。
如果要交换实参,一定要传入地址

指针的运算

inta,*pa=&a,*pb;
pb=pa; //pa、pb都指向a,pa就是变量,pb=pa就是正常的变量赋值,只不过这个变量值是a的地址
经典笔试题
int x=3,y=0,*px=&x;
y=*px+5; //y=8
y=++*px; //y=4,x=4
y=(*px)++ //y=4,x=5
y=*px++; //y=5,px变成了野指针,指向了后面4个字节。先赋值,然后指针指向地址+1
//区分*px++、++*px
练习实现字符串数组赋值
#include <stdio.h>
void mystrcp(char* dest,const char*src)
{
	while(*src != '\0')
	{
		*dest++ = *src++; //*dest=*src ,然后两个指针各自后移下个地址
	}
}
int main()
{
	char s1[32]="hello";
	char s2[32]={0};
	mystrcp(s2,s1);
	printf("%s\n",s2);
    return 0;
}

然后是这里的const

void f()
{
	int num;
	const int *p1=&num; //const 修饰*p1,即指针指向的值不可以变
	p1++;
	
	int *const p2=&num; //const 修饰p2,即指针不可以变
	(*p2)++;
	
	const int *const p3=&num;
}

空指针和野指针

#include <stdio.h>
#include <stdlib.h>  //malloc
int main()
{
	int*p; //野指针
	*p=100; //段错误,访问了不能访问的内存
	int*p=NULL;//指向地址为0的内存,空指针也不能用,只是我们知道
	//如何合法 使用内存
	//1、系统分配的内存
	int a;
	int *p1=&a;//a这4个字节可以使用
	//2、申请内存(堆内存)
	//申请的内存得知道地址,编译时申请
	char *str=(char *)malloc(32);//返回void* 万能指针类型,看如何使用
	free(str);//释放内存,如果不释放,内存泄漏。 str为要释放内存的地址
	str=NULL;//如果不变为空指针,就成为了野指针
	return 0;
}

练习

去掉字符串中的空格
#include <stdio.h>

void f(char* dest,const char*src)
{
	while(*src++!='\0')
	{
		if(*src!=' ')
		{
			*dest++=*src;
		}
	}
}

int main()
{
	char s1[32]="abcd eef ed";
	char s2[32]={0};
	f(s2,s1);
	printf("%s\n",s2);
	return 0;
}

不使用数组

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

void delete_space(char*s)
{
	while(*s!='\0')
	{
		*s=*(s+1);
		s++;//s=s+1 地址移动
	}
}
int main()
{
	char *str=(char*)malloc(128);
	char ch;
	int i=0;
	while((ch=getchar())!='\n')
	{
		//str[i++]=ch; //等于数组了
		*(str + i++)=ch; //str+i,然后i=i+1,str没有变化
	}
	printf("%s\n",str);
	char*begin=str;
	while(*str!='\0')
	{
		if(*str==' ')
		{
			delete_space(str);
		}else
		{
			str++;
		}
	}
	printf("%s\n",begin);
	return 0;
}

当有一个字符数组 char str[N]; 或一个通过 malloc 分配的字符指针 char *str = (char *)malloc(N * sizeof(char)); 时,str 在表达式中可以被视为一个指向数组(或动态分配的内存块)首字符的指针。

3、指针和数组

数组名是常指针(地址常量),不可以修改。原先声明时是在栈里找到一块也用内存,现在往后挪,后面那个位置不一定可用。
p是一个指针,p++,也就是地址移动一次,原来指向h,现在指向e,指向下一个元素

int main()
{
	char str[32]="helloword";
	char*p="helloword";
	//str++ 报错
	p++;

	str[0]='x';
	//p[0]='x'; //字符串常量不能修改
	
	printf("%d\n",sizeof(str)); //32
	printf("%d\n",sizeof(p)); //8字节
	return 0;
}

弄明白指针和数组在内存里如何存储的

另外,数组名通过参数传到函数时,变成了指针

void f(int a[])
{
	printf("%d\n",sizeof(a)/sizeof(a[0])); // 8/4=2
}
int main()
{
	int a[10]={0};
	
	printf("%d\n",sizeof(a)/sizeof(a[0])); //10
	f(a); //2
	return 0;
}

练习

p1[0] , p2[0] , p3[0] 的值分别为多少?
int a[5]={1,2,3,4,5};
int *p1=(int*)(&a+1);
int *p2=(int*)((int)a+1);
int *p3=(int*)(a+1);

&a表示数组的地址 ,a 表示数组首元素的地址。虽然两者值相等,但含义完全不一样,&a+1表示到下一个数组的地址, a+1下一个元素

p1: &a+1,int*作了一个类型强制转换,原来是一整块的地址,现在变成了首地址,所以p1是一个野指针

p2: (int)a 强转为了一个整数,然后+1,最后(int*)转为地址,现在p2指向首元素的第2个字节。这种访问是错误,c语言里访问一个整数必须从这个整数的第一个字节地址访问。

p3:指向2

p1[0] p2[0] p3[0] 等价于*p1[0] *p2[0] *p3[0]
指针的数组下标访问:在C语言中,使用指针数组下标是等价的。当你使用p[0]时,这实际上是在说“从指针p指向的位置开始,偏移0个元素的位置的值”。由于偏移是0,所以它直接指向p当前指向的元素。

#include <stdio.h>
int main()
{
	int a[]={1,2,3,6,7};
	int*p=a;
	printf("%d\n %d\n",p[3],*p);// 6 1
	return 0;
}

4、指针和字符串

比较直白简单的定义

char st[]="helloworld" //数组

char*s = "helloworld" //指针s指向了h的地址

复杂点的,指针数组

char* string[] = {"i love china","i am"};

c语言里,括号和中括号优先级高,因此string优先和 [ ] 结合,所以string是个数组,char*代表字符型指针,所以string是一个字符型指针数组,简称指针数组,每个元素是一个指针

int main()
{
	char* string[] = {"i love china","i am"};
	
	printf("%s\n",string); //不会输出,因为string不是一个字符串的地址,是一个地址的地址
	printf("%s\n",string[0]);
	return 0;
}

练习

编写C函数,将" i am from china hello" 倒置为 "hello china from am i " 将句子中的单词倒置,而不改变单词内部结构
#include <stdio.h>
#include <stdlib.h>

#define SIZE 5

int main()
{
	char* str[SIZE]={0};
	int i=0;
	for(;i<SIZE;i++)
	{
		str[i]=(char*)malloc(sizeof(char)*128);
		scanf("%s",str[i]);
	}
	char *t;
	for(i=0;i<SIZE/2;i++)
	{
		t=str[i];
		str[i]=str[SIZE-1-i];
		str[SIZE-1-i]=t;
	}
	for(i=0;i<SIZE;i++)
	{
		printf("%s ",str[i]);
		free(str[i]);
	}
	return 0;
}
假如不知道字符串长度

高级

5、指针 和 函数

函数指针:指向函数的指针
指针函数:返回值是指针的函数

a. 函数指针

在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。把函数的这个首地址(函数入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。我们把这种指向函数的指针变量称为函数指针变量

函数指针变量定义的一般形式为∶

类型说明符(*指针变量名));

例如︰

int(*pf)();  //声明

表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。

#include <stdio.h>


void f1()
{
	printf("hello\n");
}
int add(int x,int y)
{
	return x+y;
}
typedef int(*T)(int,int); //申明一个新的类型T表示函数指针
int main()
{
	void(*p)();
	p=f1;
	p();
	
	int (*q)(int,int)=add;
	printf("%d\n",q(1,2));
	T qq=add;
	return 0;
}

b.指针函数

在C语言中允许一个函数的返回值是一个指针(地址),这种返回指针值的函数称为指针型函数,简称指针函数。
注意:不能返回局部变量的地址!!!(因为局部变量在函数结束后就被释放了,返回他的地址没有意义)

定义指针型函数的一般形式为∶

类型说明符 * 函数名(形参表){
	/*函数体*/
}
char *init()
{
	//char str[32]={0}; //栈内存不可以,结束后会被释放
	char *str = (char*)malloc(128) //堆内存可以
	return str;
}

其中函数名之前加了*号表明这是一个指针型函数,即返回值是一个指针。类型说明符表示了返回的指针值所指向的数据类型。

区别  int (*p)() 和 int *p()

c. 函数指针的应用:回调函数

把函数名作为另一个函数的参数
作用:修改函数的功能

d. 复杂类型声明

右左法则:先看变量右边,然后看左边
int*(*(*fp)(int))[10]; 
fp是指针,指向函数,函数有一个整型参数,返回值是指针,指向数组,数组有10个元素,每个元素是整型指针

int*(*(*array[5])())();
array是一个数组,有5个元素,类型是指针,指向函数,函数没有形参,函数的返回值是指针,指向函数没有参数,返回值是 int型的指针

6、指针和数组

指针数组:元素是指针的数组

类型说明符 *数组名 [数组长度]
int * pa[3]; pa是一个指针数组,三个元素,每个元素都是指向整型变量的指针

数组指针:指向数组的指针

int(*p)[5] // p+1加几个字节取决于p指向的对象,此时p+1 加20个字节
int *p[5]

a. 数组指针表示二维数组

二维数组:int a[3][4]

a[0]表示首行首元素地址, 4个字节 两者值相等,但是意义不同
a数组名表示首行地址 步长一行16字节
&a表示数组的地址 48个字节

7、指针的指针

编程练习

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

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

相关文章

【工具类】Java优雅的将XML转为JSON格式、XML转JSON

Java优雅的将XML转为JSON格式、XML转JSON 1. 导入依赖1.1 Maven使用1.2 Gradle使用 2. 代码编写3.运行示例 1. 导入依赖 1.1 Maven使用 <dependency><groupId>org.dom4j</groupId><artifactId>dom4j</artifactId><version>2.1.3</vers…

TCP连接过程

文章目录 TCP连接过程 附录TCP报文中关键术语字段 后面再完整出理论、出实战、出总结 TCP连接过程 三次握手&#xff08;Three-Way Handshake&#xff09;过程。 TCP抓包结果分析&#xff1a; step1&#xff1a;Client1客户端--->Server1服务器发送SYN&#xff08;同步…

【C++二分查找 前缀和】1658. 将 x 减到 0 的最小操作数

本文涉及的基础知识点 C二分查找 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 LeetCode1658. 将 x 减到 0 的最小操作数 给你一个整数数组 nums 和一个整数 x 。每一次操作时&#xff0c;你应当移除数组 nums 最左边或最右边的元素&am…

MambaCSR: 使用SSM的双交错扫描压缩图像超分辨率

MambaCSR: Dual-Interleaved Scanning for Compressed Image Super-Resolution With SSMs 2408.11758 (arxiv.org) GitHub - renyulin-f/MambaCSR: The code source of MambaCSR 摘要 本文提出了MambaCSR&#xff0c;这是一个基于Mamba的简单但有效的框架&#xff0c;用于解决…

ffmpeg读取时长、读取视频格式

ffmpeg读取时长、读取视频格式 ffmpeg读取时长ffmpeg读取视频格式 ffmpeg读取时长 命令命令介绍具体用法ffmpeg -i查看视频时长ffmpeg -i 视频链接 or 视频路径 2>&1 | grep Duration ffmpeg读取视频格式 命令命令介绍具体用法ffmpeg -i查看视频时长ffmpeg -i 视频链接…

集合及数据结构第八节(下)———— 队列(Queue)、队列的模拟实现和练习

系列文章目录 集合及数据结构第八节&#xff08;下&#xff09;———— 队列(Queue)、队列的模拟实现和练习 队列(Queue)、队列的模拟实现和练习 队列的概念队列的使用队列模拟实现循环队列双端队列练习题 文章目录 系列文章目录集合及数据结构第八节&#xff08;下&#x…

Chainlit接入DifyAI知识库接口快速实现自定义用户聊天界面

前言 由于dify只提供了一个分享用的网页应用&#xff0c;网页访问地址没法自定义&#xff0c;虽然可以接入NextWeb/ChatGPT web/open webui等开源应用。但是如果我们想直接给客户应用&#xff0c;还需要客户去设置配置&#xff0c;里面还有很多我们不想展示给客户的东西怎么办…

【C语言】文件操作 (详细!!)

1、为什么使用文件 使用文件的原因&#xff1a;使用文件主要是为了在程序的执行过程中保存、读取和交换数据。文件提供了一种持久化存储数据的方式&#xff0c;使得程序在关闭后&#xff0c;数据不会丢失&#xff0c;可以被其他程序或后续的程序执行周期重新读取和处理。 1.0 什…

实验2-1-3 输出三角形

本题要求编写程序&#xff0c;输出指定的由“*”组成的三角图案。 **输入格式&#xff1a; 本题无输入**输出格式&#xff1a; 按照下列格式输出由“*”组成的三角图案。 **** *** ** *程序: #define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> int main() {int i…

leetcode 49 字母异位分词

正文 基础解法 首先&#xff0c;我们创建一个字典对象&#xff0c;然后遍历整个字符串列表&#xff0c;并且使用 sorted() 函数对字符串列表进行排序&#xff0c;所有的异位分词经过排序后它们的组成和顺序会趋于一致。但是需要注意的是 sorted 对字符串进行排序后会变成一个由…

基于element-ui 日期选择器el-date-picker, 即对日期做区间限制

需求&#xff1a; 有时候需求会让我们对日期选择器做限制&#xff0c;即控制最多可跨越多少个月份&#xff0c;其中涉及到不同年份该如何计算。 HTML&#xff1a; <el-date-pickerv-model"timePeriod"type"monthrange"value-format"yyyyMM"…

Linux系统之部署俄罗斯方块网页小游戏(三)

Linux系统之部署俄罗斯方块网页小游戏(三) 一、小游戏介绍1.1 小游戏简介1.2 项目预览二、本次实践介绍2.1 本地环境规划2.2 本次实践介绍三、检查本地环境3.1 检查系统版本3.2 检查系统内核版本3.3 检查软件源四、安装Apache24.1 安装Apache2软件4.2 启动apache2服务4.3 查看…

【CANoe使用大全】——cdd导入CANoe流程详解

&#x1f64b;‍♂️【CANoe使用大全】系列&#x1f481;‍♂️点击跳转 文章目录 1.1.CDD导入1.1 CDD文件导入流程 2. CDD文件导后配置2.1.协议配置2.2.寻址方式配置2.3.0x27 解密DLL导入2.4.诊断ID配置 3.导入效果4.CDD操作台使用4.1.指令发送 5.Fault Memory5.1 0x19 045.2…

解释图像的边缘检测算法中的Canny算法

Canny 算法是图像处理领域中一种经典的边缘检测方法&#xff0c;由 John F. Canny 在 1986 年提出。Canny 算法以其高效、可靠的边缘检测效果在图像处理和计算机视觉领域广泛应用。它具有良好的噪声抑制能力、精确的边缘定位能力以及单像素宽度的边缘输出特性。 Canny 边缘检测…

TIM输出比较之PWM驱动LED呼吸灯应用案例

文章目录 前言一、应用案例演示二、电路接线图三、应用案例代码四、应用案例分析4.1 基本思路4.2 相关库函数介绍4.3 初始化PWM模块4.3.1 RCC开启时钟4.3.2 配置时基单元4.3.3 配置输出比较单元4.3.4 配置GPIO4.3.5 运行控制 4.4 PWM输出模块4.5 主程序 前言 提示&#xff1a;…

无人机培训与装配维修技术详解

一、无人机基础理论 无人机&#xff0c;即无人驾驶航空器&#xff0c;凭借其灵活性、高效性和广泛应用性&#xff0c;已成为现代科技领域的热点之一。在学习无人机培训与装配维修技术之前&#xff0c;掌握无人机的基础理论是必不可少的。这包括但不限于&#xff1a; 1. 无人机…

Alpaca 汉化版 v2.9.3 — 免费 PS 智能 AI 插件

Alpaca是一款免费的PS智能AI插件&#xff0c;包含了6大AI功能&#xff0c;包括提示词生图、图像转绘画风格、生成式填充、文本转图像、计算图像模型、提高图像分辨率。汉化版本安装简单&#xff0c;只需解压到PhotoShop安装目录\Plug-ins文件夹即可。安装启动PhotoShop - 增效工…

基于Springboot和BS架构的宠物健康咨询系统pf

TOC springboot509基于Springboot和BS架构的宠物健康咨询系统pf 第一章 课题背景及研究内容 1.1 课题背景 信息数据从传统到当代&#xff0c;是一直在变革当中&#xff0c;突如其来的互联网让传统的信息管理看到了革命性的曙光&#xff0c;因为传统信息管理从时效性&#x…

前端本地代理配置方式

Whistle 介绍 Whistle 是一个基于 Node.js 的跨平台 Web 调试工具。允许捕获、查看和修改 HTTP/HTTPS 网络请求。通过使用 Whistle&#xff0c;可以轻松地进行接口代理、抓包、模拟数据、修改请求和响应等操作&#xff0c;以便在前端开发中调试网络请求。 Proxy SwitchyOmega…

记录一个变量溢出的bug

文章目录 如题 如题 count2变量溢出了&#xff08;超过了255&#xff09;&#xff0c;结果导致busOff_16bitRecordHILTime变量莫名其妙被清0