指针(一)【C语言进阶版】

大家好,我是深鱼~

【前言】:

指针的主题,在初阶指针章节已经接触过了,我们知道了指针的概念:

1.指针就是个变量,用来存放地址,地址的唯一标识一块内存空间(指针变量),内存单元是由编号的,编号==地址==指针

2.指针/地址/指针变量的大小是固定的4/8个字节(32位平台/64位平台)

3.指针是有类型的,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限

4.指针的运算


一、字符指针

一般使用:

#include<stdio.h>
int main()
{
	char ch = 'w';
	char* pc = &ch;
	*pc = 'w';
	return 0;
}

还有一种使用方式如下:

#include<stdio.h>
int main()
{
	char ch = 'w';
	char* p = "abcdef";
		   //[abcdef\0]
		  //char arr[]="abcdef"
    printf("%s\n,p);//打印的是整个字符串
    printf("%c\n,*p);//打印的是a
	printf("%c\n", "abcdef"[3]);//打印的是d
	return 0;
}

这里的字符串"abcdef“类似于数组,把这个字符串赋给p指针,也就是字符串的首地址赋给p指针

(假设是 32位平台,指针的大小也就是4个字节,但是这个字符串却有7个字节(算上\0),这样看是放不下的)

但是这个代码存在问题,p变量赋给了常量字符串,如果改变p变量,常量字符串是不会更改的

eg:这样代码很容易出错误,所以一般会在char *p之前写上const

char* p = "abcdef";
 *p = 'e';

更加安全的写法:

const char* p = "abcdef";

 下面再来看一道【经典面试题】

这道题的输出结果是啥?

#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;
}

【答案】:

str1 and str2 are not same
str3 and str4 are same

【解释】:

(1)首先创建两个数组,分别放入hello bit,因为是两个不同的数组,那么字符串就存在不同的位置,当判断str1和str2,也就是两个数组的首地址的时候,这两个h地址肯定不相同

(2)str3和str4指针都指向常量字符串,因为常量字符串不可修改,那么其实也没有必要再创建一个空间来存两个相同的常量字符串,如果两个指针同时指向这个常量字符串的时候,其实指向的也就是同一个地址,那么str3和str4也就相同


【拓展】:再加一个

if(&str3==&str4)

         printf("YES\n");

else

         printf("NO\n");

【答案】:NO

【图解】:指针变量不同,地址不同

p(指针变量):表示指针变量指向的内存地址

&p:取指针p的地址,表示编译器为变量p分配的内存地址(不同变量分配的地址不同),而不是这个指针p指向的地址


 二、指针数组

指针数组是数组

字符数组-存放字符的数组

整形数组-存放整形的数组

指针数组-存放指针的数组,存放在数组的元素都是指针类型的

eg: int *arr[5];  //存放整形指针的数组

        char * ch[6]; //存放字符指针的数组

【那指针数组一般怎么用呢?】

一般不会像这样使用:int *arr[3]={&a,&b,&c}

而是这样使用:可以用指针数组模拟一个二维数组

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	            //int*   int* int*
	//指针数组
	int* arr[] = { arr1,arr2,arr3 };
    for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

【图解】:指针数组放入三个数组,这三个数组的类型是int *指针类型,通过指针数组的打印可以模拟二维数组

【程序结果】: 


  再举个栗子:建立一个字符类型(int *)指针数组,并打印数组内的元素

#include<stdio.h>
int main()
{
	char* arr[5] = { "hello bit","hehe","penggeC","bitejiuyeke","C++" };
	for (int i = 0; i < 5; i++)
	{
		printf("%s\n", arr[i]);
	}
	return 0;
}

【图解】:分别把字符串的首元素地址传给了指针,然后把这些指针放在数组中

【程序结果】:


  三、数组指针

3.1数组指针的定义

数组指针是指针,类型于字符指针(指向字符的指针),整形指针(指向整形的指针),浮点型指针(指向浮点型的指针),那么数组指针就是指向数组的指针

int a=10;  int *p=&a;(整形指针)

char ch='a'; char *pc=&ch;(字符指针)

int arr[10];  int (*p)[10]=&arr;(数组指针)

下面我们来理解一下int(*p)[10]:

int (*p) [10]中的*表示p是一个指针,*p指向[10]表示指向数组的指针,int代表数组元素类型

3.2&数组名vs数组名 

首先数组名的理解:

数组名是首元素的地址但是存在两个例外:

1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节

2.&数组名,这里的数组名表示整个数组,取出来的是数组的地址

int main()
{
	int arr[10];

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

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

	printf("%p\n", &arr);//int *[10]这就是数组指针的类型
	printf("%p\n", &arr+1);
	//指针类型决定了指针+1到底+几个字节
	return 0

【注意】:这里的10不可省略,数组指针要明确指针指向的数组是几个元素

 【练习】:

char* arr2[5]的数组指针:char* (*p)[5](前面的char*是数组类型)

int arr3[]={1,2,3}的数组指针:int (*p)[3]   (数组指针要明确指向数组的大小,这个3必须写)

3.3数组指针的使用

先来看看用数组指针打印数组元素:这其实是多此一举的,本来可以直接arr打印数组元素,但是还要定义一个数组指针,再用数组指针来打印数组元素

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9 };
	int(*p)[10] = &arr;

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

	return 0;
}

 【那如果把打印的内容换成p[i],结果又如何呢?】

p[i]等价于*(p+i),而指针+1,直接就跳过一个数组地址,根本不可能打印出数组的元素

直接把数组名传给p指针,这样才能访问数组的元素:int *p=arr 

代码运行结果:


  【那么数组指针到底有什么用呢?】

数组指针用都是用在二维数组上,我们来看一个例子:打印一个二维数组


正常的方法:形参使用二维数组的形式

#include<stdio.h>
void print(int arr[3][5], int row, int col)
{
	for (int i = 0; i < 3;i++)
	{
		for (int 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;
}

 数组指针的方法:形参使用数组指针的形式

(1)首先数组名是首元素的地址,在二维数组中,首元素的地址就是第一行数组元素的地址

(2)形参int (*p)[5]代表:这个数组指针指向的是数组第一行5个元素的地址

(3)p[i][j]代表:p[i]就相当于*(p+i),每行的地址,解引用后就是每行的数组名,每行的数组名[j],就可以得到每行的数组元素

#include<stdio.h>
void print(int (*p)[5], int row, int col)
{
	for (int i = 0; i < 3;i++)
	{
		for (int j = 0; j < 5; 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} };
	print(arr, 3, 5);
	return 0;
}

学了指针数组和数组指针我们来一起回顾并看看下面代码的意思: 

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

<1>arr是一个能够存放5个整形数据的数组

<2>parr1是一个数组,数组10个元素,,每个元素的类型都是int *

<3>parr2是一个指针,该指针是指向数组的,指向的数组有10个元素,每个元素的类型都是int

<4>parr3是一个数组,是存放数组指针的数组(这个parr3【10】数组10个元素),存放数组指针(int (*    ) [5],指向的数组有5个元素,每个元素是int类型(这个比较难,不理解也没事)

对于4画图理解:parr3是10个元素的数组,每个元素中都是地址(数组指针),每个元素的类型都是int(* )[5],而对应数组中存放的数组指针,这个指针指向5个元素的数组


四、数组参数,指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数应该如何设计呢?

4.1一维数组传参

数组传参,形参是可以写成数组的形式的,也可以是指针,传参的本质是传递了数组首元素的地址

以下6种方式传参都是可以的

#include <stdio.h>
void test(int arr[])//ok,数组的形式传参,可以省略数组的元素个数
{}
void test(int arr[10])//ok,直接把数组拿过来,数组的形式传参
{}
void test(int* arr)//ok,指针的形式传参
{}
void test2(int* arr[20])//ok,直接把指针数组拿过来,数组的形式传参
{}
void test2(int* arr[])//ok,数组的形式传参,可以省略数组的元素个数
{}
void test2(int** arr)//ok,指针的形式传参
{}
int main()
{
	int arr[10] = { 0 };
	int* arr2[20] = { 0 };//指针数组
	test(arr);
	test2(arr2);
}

对于最后一种的理解:arr2每个元素的类型都是int *,arr2也就是取int *元素的首地址,一个指针的地址,那么就放到二级指针里面去


4.2二维数组传参

void test(int arr[3][5])//ok
{}
void test(int arr[][])//no,二维数组只能省略行,不可省略列
{}
void test(int arr[][5])//ok
{}
void test(int* arr)//no,二维数组首元素地址是第一行的地址,所以指针传参不能省略列
{}
void test(int* arr[5])//no,这是一个指针数组,而不是本来的二维数组
{}
void test(int(*arr)[5])//ok,数组指针,指针指向数组,数组中有5个元素
{}
void test(int** arr)//no,二级指针是用来接收一级指针的地址,而我们只需要第一行数组的地址,一级指针即可
{}
int main()
{
	int arr[3][5] = { 0 };
	test(arr);
}

二维数组传参:

<1>数组形式传参:只能省略行,不可省略列

<2>指针形式传参:参数形式应该是数组指针int  (*arr)【列】,而不能是指针数组

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

一级指针传参形式参数写成一级指针就可以


【思考】:

当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

(1)传一维数组的数组名

(2)传变量的地址

(3)传指针

4.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)一级指针的地址

(2)二级指针

(3)传指针数组(首元素的地址)

五、函数指针

类比:

数组指针-指向数组的指针-存放的是数组的地址-&数组名就是数组的地址

函数指针-指向函数的指针-存放的是函数的地址-那么怎么得到函数的地址呢

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//&函数名就是函数的地址
	//函数名也是函数的地址

	printf("%p\n", &Add);
	printf("%p\n", Add);

	int (*pf1)(int, int) = Add;//pf1就是函数指针变量
	int (*pf2)(int, int) = &Add;

	return 0;
}

&函数名就是函数的地址,函数名也是函数的地址

int (*pf1)(int, int) = Add这里的括号不可省略,不然前面的部分就跟函数声明一样 

利用函数指针进行Add加和:

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf2)(int, int) = &Add;
  //int (*pf2)(int, int) = Add;这样写也可以
	int ret = (*pf2)(2, 3);
  //int ret = pf2(2, 3);不用*也可以,但是用了*一定得加括号
  //int ret = *pf2(2, 3);这个就相当于*5
	printf("%d\n", ret);

	return 0;
}

  阅读两段有趣的代码:

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

(1)void(*)()是函数指针类型

(2)(类型)常量-强制类型转换-eg:int a=(int )3.14 

(3)有颜色的就是将0强制类型转换为函数指针类型,这个0就变成了地址(地址),然后调用0地址处的函数,这个函数没有参数,返回值是void


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

 这个代码是函数声明,声明的是signal函数,signal函数的参数有2个

一个是int 类型

一个是函数指针类型,该类型是void(*)(int)

           该函数指针指向的函数,参数是int,返回类型是void

signal函数的返回类型也是函数指针类型,该类型是void(*)(int)

           该函数指针指向的函数,参数是int,返回类型是void

【将这个代码简化】

typedef void(*pfun_t)( int );//也就是将void(*)(int)改给名字叫pfun_t

pfun_t signal(int ,pfun_t);


本次内容就到此啦,欢迎评论区或者私信交流,觉得笔者写的还可以,或者自己有些许收获的,麻烦铁汁们动动小手,给俺来个一键三连,万分感谢 ! 

 

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

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

相关文章

微信怎么添加更多的好友?通过后还能自动打招呼?

近期好多客户来问想把抖音、淘宝等平台的客户引流到自己的微信号里&#xff0c;打造自己的私域流量池&#xff0c;有什么办法能快捷地批量自动添加客户好友的微信呢&#xff1f;怎么规避加太多频繁的问题呢&#xff1f; 今天&#xff0c;我们来一一解决&#xff0c;都知道加好…

Shell 脚本常用命令

Shell 脚本常用命令 一.日志打印1.信息打印效果2.占位符打印效果 二.文件检索1.文件夹查找2.文件名称3.文件内容检索 三.字符串处理1.字符串截取2.字符串长度获取3.字符串判断4.数字判断5.数字运算 四.日期获取五.文件遍历1.遍历获取修改时间和MD52.Find 查找 六.循环重试1.Whi…

机器学习:什么是分类/回归/聚类/降维/决策

目录 学习模式分为三大类&#xff1a;监督&#xff0c;无监督&#xff0c;强化学习 监督学习基本问题 分类问题 回归问题 无监督学习基本问题 聚类问题 降维问题 强化学习基本问题 决策问题 如何选择合适的算法 我们将涵盖目前「五大」最常见机器学习任务&#xff1a…

Java面向对象——多态、Object类、instanceof关键字以及final关键字

多态的概念 1.多态是指同一个方法调用可以在不同的对象上有不同的表现&#xff0c;即同一种方法调用方式适用于不同的数据类型。 编译时和运行时&#xff1a;编译时期调用的是父类中的方法&#xff0c;但运行时期会根据实际的对象类型来调用适当的方法。这种行为称为动态绑定&…

【已解决】记 Vue3+SpringBoot 前后端分离项目部署时的一次跨域问题

问题背景是在一次部署开发环境时&#xff0c;由于是前后端分离项目&#xff08;前端Vue3&#xff0c;后端SpringBoot&#xff09;&#xff0c;SpringBoot 使用 Docker 部署&#xff0c;前端访问服务接口时出现跨域问题。 不知道什么是跨域问题的小伙伴可以查看另一篇文章&…

Redis中的缓存穿透、雪崩、击穿的原因以及解决方案

一&#xff0c;什么是缓存穿透、雪崩、击穿&#xff1f; 1、缓存穿透&#xff1a; 是指用户查询数据&#xff0c;在数据库没有&#xff0c;自然在缓存中也不会有。这样就导致用户查询的时候&#xff0c;在缓存中找不到&#xff0c;每次都要去数据库再查询一遍&#xff0c;然后…

CSS 选择器

前言 基础选择器 以下是几种常见的基础选择器。 标签选择器&#xff1a;通过HTML标签名称选择元素。 例如&#xff1a; p {color: red; } 上述样式规则将选择所有<p>标签 &#xff0c;并将其文字颜色设置为红色。 类选择器&#xff1a;通过类名选择元素。使用类选择…

docker cURL error 6: Could not resolve host

场景&#xff1a; 微信小程序 获取 用户 openpid&#xff0c;在此之前&#xff0c;我需要先 "获取稳定版接口调用凭据"&#xff0c;根据手册提示的&#xff0c;要先调用 https://api.weixin.qq.com/cgi-bin/stable_token 我这边就开始了请求&#xff0c;结果返回了…

函数性能探测:更简单高效的 Serverless 规格选型方案

作者&#xff1a;拂衣、丛霄 2019 年 Berkeley 预测 Serverless 将取代 Serverful 计算成为云计算新范式。Serverless 为应用开发提供了一种全新系统架构。借助 2023 年由 OpenAI 所带来的 AIGC 风潮&#xff0c;以阿里云函数计算 FC、AWS Lambda 为代表的 Serverless 以其更高…

Flink的常用算子以及实例

1.map 特性&#xff1a;接收一个数据&#xff0c;经过处理之后&#xff0c;就返回一个数据 1.1. 源码分析 我们来看看map的源码 map需要接收一个MapFunction<T,R>的对象&#xff0c;其中泛型T表示传入的数据类型&#xff0c;R表示经过处理之后输出的数据类型我们继续往…

在ARM服务器上一键安装Proxmox VE(以在Oracle Cloud VPS上为例)(甲骨文)

前言 如题&#xff0c;具体用到的说明文档如下 virt.spiritlhl.net 具体流程 首先是按照说明&#xff0c;先得看看自己的服务器符不符合安装 Proxmox VE的条件 https://virt.spiritlhl.net/guide/pve_precheck.html#%E5%90%84%E7%A7%8D%E8%A6%81%E6%B1%82 有提到硬件和软…

Redis缓存问题(穿透, 击穿, 雪崩, 污染, 一致性)

目录 1.什么是Redis缓存问题&#xff1f; 2.缓存穿透 3.缓存击穿 4.缓存雪崩 5.缓存污染&#xff08;或满了&#xff09; 5.1 最大缓存设置多大 5.2 缓存淘汰策略 6.数据库和缓存一致性 6.1 4种相关模式 6.2 方案&#xff1a;队列重试机制 6.3 方案&#xff1a;异步更新缓…

基于YOLOV8模型的西红柿目标检测系统(PyTorch+Pyside6+YOLOv8模型)

摘要&#xff1a;基于YOLOV8模型的西红柿目标检测系统可用于日常生活中检测与定位西红柿目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的目标检测&#xff0c;另外本系统还支持图片、视频等格式的结果可视化与结果导出。本系统采用YOLOv8目标检测算法训练数…

day20 飞机大战射击游戏

有飞行物类 飞行 爆炸 的连环画&#xff0c; 飞行的背景图 &#xff0c; 子弹图&#xff0c; 还有游戏开始 暂停 结束 的画面图。 设计一个飞机大战的小游戏&#xff0c; 玩家用鼠标操作hero飞行机&#xff0c; 射出子弹杀死敌机&#xff0c;小蜜蜂。 敌机可以获得分数&…

浅谈java自定义中类两个对象的比较

目录 实现比较两个对象是否相同 1.前置代码 1.学生类 2.示例 3.输出 4.原因 2.那么我们要怎么做呢? 1.对Student类中重新实现quals方法(即对equals方法重写) 2.完整代码如下: 3.具体操作 4.演示 1.示例 2.输出 3.原因 实现比较两个对象的大小 第一种: 用…

Android 9.0 Vold挂载流程解析(下)

Android 9.0 Vold挂载流程解析&#xff08;上&#xff09; 前言 上一篇介绍了Android 文件系统中Vold挂载机制的总体框架&#xff0c;我们分析了vod进程的main.cpp.接下来我们分析下存储卡挂载和卸载的流程。 存储卡挂载 在上篇文章文章提到&#xff0c;监听驱动层挂载和卸…

【是C++,不是C艹】 手把手带你实现Date类(附源码)

&#x1f49e;&#x1f49e;欢迎来到 Claffic 的博客&#x1f49e;&#x1f49e; &#x1f449; 专栏&#xff1a;《是C&#xff0c;不是C艹》&#x1f448; 前言&#xff1a; 恍惚间&#xff0c;已经两个月没更新了 &#xff08;&#xff1b;д&#xff40;&#xff09;ゞ 我忏…

听GPT 讲Alertmanager源代码--dispatch/silence/inhibit等

目前Alertmanager项目共计53M大小&#xff0c;其中.git占了46M&#xff0c;总的go代码行数不足6万行(包括.pb.go等文件)&#xff0c;不算是一个大项目。 但实现了告警的分发&#xff0c;静默等功能&#xff0c;值的研究&#xff0c;尤其是dispatch中的route部分。 在Prometheus…

2022年03月 C/C++(三级)真题解析#中国电子学会#全国青少年软件编程等级考试

第1题&#xff1a;和数 给定一个正整数序列&#xff0c;判断其中有多少个数&#xff0c;等于数列中其他两个数的和。 比如&#xff0c;对于数列1 2 3 4, 这个问题的答案就是2, 因为3 2 1, 4 1 3。 时间限制&#xff1a;10000 内存限制&#xff1a;65536 输入 共两行&#x…

图数据库_Neo4j学习cypher语言_使用CQL_构建明星关系图谱_导入明星数据_导入明星关系数据_创建明星关系---Neo4j图数据库工作笔记0009

首先找到明星数据 可以看到有一个sheet1,是,记录了所有的关系的数据 然后比如我们搜索一个撒贝宁,可以看到撒贝宁的数据 然后这个是构建的CQL语句 首先我们先去启动服务 neo4j console 然后我们再来看一下以前导入的,可以看到导入很简单, 就是上面有CQL 看一下节点的属性