指针(一)【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);//打印的是aprintf("%c\n", "abcdef"[3]);//打印的是dreturn 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);这个就相当于*5printf("%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);
本次内容就到此啦,欢迎评论区或者私信交流,觉得笔者写的还可以,或者自己有些许收获的,麻烦铁汁们动动小手,给俺来个一键三连,万分感谢 !

相关文章:
指针(一)【C语言进阶版】
大家好,我是深鱼~ 【前言】: 指针的主题,在初阶指针章节已经接触过了,我们知道了指针的概念: 1.指针就是个变量,用来存放地址,地址的唯一标识一块内存空间(指针变量)&a…...
回归预测 | MATLAB实现SA-BP模拟退火算法优化BP神经网络多输入单输出回归预测(多指标,多图)
回归预测 | MATLAB实现SA-BP模拟退火算法优化BP神经网络多输入单输出回归预测(多指标,多图) 目录 回归预测 | MATLAB实现SA-BP模拟退火算法优化BP神经网络多输入单输出回归预测(多指标,多图)效果一览基本介…...
springMVC 已解密的登录请求
问题描述: 解决方案: 1.对用户所输入的密码在页面进行MD5加密并反馈至密码输入框。 2. 手动生成SSL安全访问证书;在此不做介绍,相关方法可通过网上查找; 3. 将产品HTTP访问方式改为SSL安全访问方式;在Ap…...
机器学习赋能乳腺癌预测:如何使用贝叶斯分级进行精确诊断?
一、引言 乳腺癌是女性最常见的恶性肿瘤之一,也会发生在男性身上。每年全球有数百万人被诊断出乳腺癌,对患者的生活和健康造成了巨大的影响。早期的乳腺癌检测和准确的诊断对于提高治疗的成功率至关重要。然而,乳腺癌的早期诊断面临着许多挑战…...
Java后端开发面试题——框架篇
Spring框架中的bean是单例的吗?Spring框架中的单例bean是线程安全的吗? singleton : bean在每个Spring IOC容器中只有一个实例。 prototype:一个bean的定义可以有多个实例。 Spring bean并没有可变的状态(比如Service类和DAO类),…...
【新版】系统架构设计师 - 系统测试与维护
个人总结,仅供参考,欢迎加好友一起讨论 文章目录 架构 - 系统测试与维护考点摘要软件测试软件测试 - 测试类型软件测试 - 静态测试软件测试 - 动态测试软件测试 - 测试阶段软件测试 - 测试阶段 - 单元测试软件测试 - 测试阶段 - 集成测试软件测试 - 测试…...
使用 useEffect 和 reactStrictMode:优化 React 组件的性能和可靠性
使用useEffect和React.StrictMode是一种优化React组件性能和可靠性的常见做法。下面是关于如何使用这两个特性的示例: import React, { useEffect } from react;function MyComponent() {useEffect(() > {// 在组件挂载/更新时执行的副作用代码// 可以进行数据获…...
商业智能BI是什么都不明白,如何实现数字化?
2021年下半年中国商业智能软件市场规模为4.8亿美元,2021年度市场规模达到7.8亿美元,同比增长34.9%,呈现飞速增长的趋势。数字化时代,商业智能BI对于企业的落地应用有着巨大价值,逐渐成为了现代企业信息化、数字化转型中…...
【五子棋】
五子棋 文章目录 五子棋前言一、登录功能二.哈希表管理用户的会话和房间三.基于Websocket连接开发的功能1.匹配功能2.游戏房间3.挑战功能4.人机对战5.聊天功能 前言 这篇博客主要详细介绍我的五子棋项目的核心功能的实现细节,也就是详细介绍五子棋各个功能是如何实…...
docker 01(初识docker)
一、docker概念 Docker是一个开源的应用容器引擎;诞生于2013年初,基于Go 语言实现,dotCloud公司出品(后改名为Dockerlnc);Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中,然后发布到任何流行的Linux …...
爬虫逆向实战(十九)--某号站登录
一、数据接口分析 主页地址:某号站 1、抓包 通过抓包可以发现登录接口 2、判断是否有加密参数 请求参数是否加密? 通过查看“载荷”模块可以发现有一个jsondata_rsa的加密参数 请求头是否加密? 无响应是否加密? 无cookie是否…...
linux环境docker安装mysql
1:docker拉取mysql镜像(可有自己选择mysql版本) [rootlocalhost ~]# docker pull mysql:8.02: Docker 中启动 MySQL 容器,可以使用以下命令: docker run -d --name mysql_container -v ./sql:/docker-…...
taro h5 formData上传图片的坑-Required request part ‘file‘ is not present
描述:用formData上传图片 1、生成formData const formData new FormData() formData.append(file, data) // data是file formData.append(xxx, xxx) // 添加其他参数2、用taro.request请求 Taro.request({url: xxxx,data: formData,header: {Content-Type: mult…...
GNU GRUB version 2.06 Minimal Bash-lke line editing is supported 问题修复
一、问题背景 博主喜欢折腾系统,电脑原来有一个windows系统,想整一个Linux双系统,结果开机时出现以下画面: GNU GRUB version 2.06 Minimal Bash-lke line editing is supported. TAB lists possible comand completions, Anywh…...
Embedding 向量生成GPT数据使用相关
如果使用python3.6的版本,使用pycharm创建工程,那么默认会使用 docx包,这样运行程序会爆异常,突然想起以前请教的一个大神,想当 初,这个问题困扰了我 两天时间,在此记录一下: pytho…...
Jenkins工具系列 —— 配置邮箱 每个job下动态设置临时发送人
文章目录 安装插件添加邮箱认证邮箱申请(以QQ邮箱网页为例)jenkins添加邮箱认证 jenkins设置邮箱相关信息配置全局邮件单个JOB邮箱配置 安装插件 点击 左侧的 Manage Jenkins —> Plugins ——> 左侧的 Available plugins 添加邮箱认证 邮箱申请…...
华纳云:ubuntu中怎么查看进程占用的端口
在Ubuntu中,你可以使用以下命令来查看进程占用的端口: 打开终端(Terminal)。 使用 netstat 命令来查看进程占用的端口。以下是几个常用的命令示例: 查看所有进程占用的端口和地址: netstat -tuln 查看TCP端…...
【学会动态规划】 最长递增子序列(26)
目录 动态规划怎么学? 1. 题目解析 2. 算法原理 1. 状态表示 2. 状态转移方程 3. 初始化 4. 填表顺序 5. 返回值 3. 代码编写 写在最后: 动态规划怎么学? 学习一个算法没有捷径,更何况是学习动态规划, 跟我…...
Azure虚拟网络对等互连
什么是Azure虚拟网络对等互联 Azure虚拟网络对等互联(Azure Virtual Network peering)是一种连接两个虚拟网络的方法,使得这两个虚拟网络能够在同一地理区域内进行通信。它通过私有IP地址在虚拟网络之间建立网络连接,不论是在同一…...
CTFhub-sql-整数注入
判断存在 sqli 注入 1 1 and 11 1 and 12 因为 11 为真,12 为假,且 11 与 1 显示的数据一样,那么就存在 sqli 注入 查询该数据表的字段数量 一、 2 3 1,2成功带出数据,3没有数据,所以有两个字段 二、 1 order by …...
iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘
美国西海岸的夏天,再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至,这不仅是开发者的盛宴,更是全球数亿苹果用户翘首以盼的科技春晚。今年,苹果依旧为我们带来了全家桶式的系统更新,包括 iOS 26、iPadOS 26…...
理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端
🌟 什么是 MCP? 模型控制协议 (MCP) 是一种创新的协议,旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议,它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility
Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
稳定币的深度剖析与展望
一、引言 在当今数字化浪潮席卷全球的时代,加密货币作为一种新兴的金融现象,正以前所未有的速度改变着我们对传统货币和金融体系的认知。然而,加密货币市场的高度波动性却成为了其广泛应用和普及的一大障碍。在这样的背景下,稳定…...
蓝桥杯 冶炼金属
原题目链接 🔧 冶炼金属转换率推测题解 📜 原题描述 小蓝有一个神奇的炉子用于将普通金属 O O O 冶炼成为一种特殊金属 X X X。这个炉子有一个属性叫转换率 V V V,是一个正整数,表示每 V V V 个普通金属 O O O 可以冶炼出 …...
管理学院权限管理系统开发总结
文章目录 🎓 管理学院权限管理系统开发总结 - 现代化Web应用实践之路📝 项目概述🏗️ 技术架构设计后端技术栈前端技术栈 💡 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 🗄️ 数据库设…...
QT3D学习笔记——圆台、圆锥
类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体(对象或容器)QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质(定义颜色、反光等)QFirstPersonC…...
[免费]微信小程序问卷调查系统(SpringBoot后端+Vue管理端)【论文+源码+SQL脚本】
大家好,我是java1234_小锋老师,看到一个不错的微信小程序问卷调查系统(SpringBoot后端Vue管理端)【论文源码SQL脚本】,分享下哈。 项目视频演示 【免费】微信小程序问卷调查系统(SpringBoot后端Vue管理端) Java毕业设计_哔哩哔哩_bilibili 项…...
深入理解Optional:处理空指针异常
1. 使用Optional处理可能为空的集合 在Java开发中,集合判空是一个常见但容易出错的场景。传统方式虽然可行,但存在一些潜在问题: // 传统判空方式 if (!CollectionUtils.isEmpty(userInfoList)) {for (UserInfo userInfo : userInfoList) {…...
