【C进阶】指针(一)
大家好,我是深鱼~
【前言】:
指针的主题,在初阶指针章节已经接触过了,我们知道了指针的概念:
1.指针就是个变量,用来存放地址,地址的唯一标识一块内存空间(指针变量),内存单元是由编号的,编号==地址==指针
2.指针/地址/指针变量的大小是固定的4/8个字节(32位平台/64位平台)
3.指针是有类型的,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限
4.指针的运算
【目录】
一、字符指针
二、指针数组
三、数组指针
3.1数组指针的定义
3.2&数组名vs数组名
3.3数组指针的使用
四、数组参数,指针参数
4.1一维数组传参
4.2二维数组传参
4.3一级指针传参
4.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代表数组元素类型
【注意】:
不能直接定义一个指针p,然后把&arr赋给p,&arr是整个数组的地址,&arr的类型是int (*)【10】,而指针p的类型是int *,不对等,所以&arr应该放在数组指针中
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…...

bug复刻,解决方案---在改变div层级关系时,导致传参失败
问题描述: 在优化页面时,为了实现网页顶部遮挡效果(内容滚动,顶部导航栏不随着一起滚动,并且覆盖),做法是将内容都放在一个div里面,为这个新的div设置样式,margin-top w…...

2023年Java核心技术面试第九篇(篇篇万字精讲)
目录 十七 . 并发相关基础概念 17.1 线程安全 17.2 保证线程安全的两个方法 17.2.1 封装 17.2.2 不可变 17.2.2.1 final 和 immutable解释 17.3 线程安全的基本特性 17.3.1 原子性(Atomicity) 17.3.2 可见性(Visibility) 17.3.3 有…...

解码Python JSON:从基础到高级,掌握使用的精髓
摘要: JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,在Python中通过json模块可以方便地处理JSON数据。本文将从JSON的基础概念讲解开始,逐步深入介绍json模块的各种功能和用法,带您从入门到…...

Qt --- 自定义工具类 持续更新... ...
QSettings使用 .h文件 #ifndef UTIL_H #define UTIL_H#include <QSettings>class Util {public:// 配置文件读写 {{static bool writeInit(QString path, QString user_key, QString user_value);static bool readInit(QString path, QString user_key, QString &u…...

GO语言圣经 第二章习题
练习2.1 向tempconv包添加类型、常量和函数用来处理Kelvin绝对温度的转换,Kelvin 绝对零度是−273.15C,Kelvin绝对温度1K和摄氏度1C的单位间隔是一样的。 conv.go package tempconv// CToF converts a Celsius temperature to Fahrenheit. func CToF(c …...

Java 语言实现线性查找算法
【引言】 线性查找算法是一种简单且直观的查找算法。它从数组的第一个元素开始逐个比较,直到找到目标元素,或者遍历完整个数组。本文将使用Java语言实现线性查找算法,并详细讲解其思想和代码实现。 【算法思想】 线性查找的核心思想是逐个比…...

xcode15 change
jump to define 由原先的 control command left click 改为command left click 可以方便使用TODO // TODO: 是不是这里要改成红色字体 将这个直接作为bookmark...

MySQL集群(mysql-cluster)
环境 系统环境:centos7 管理节点:192.168.127.128 数据节点:192.168.127.145,192.168.127.146 sql节点:192.168.127.145,192.168.127.146 约定 创建mysql用户,将程序部署到/home/mysql 目…...

基于神经网络的3D地质模型
地球科学家需要对地质环境进行最佳估计才能进行模拟或评估。 除了地质背景之外,建立地质模型还需要一整套数学方法,如贝叶斯网络、协同克里金法、支持向量机、神经网络、随机模型,以在钻井日志或地球物理信息确实稀缺或不确定时定义哪些可能是…...

Spring AOP教程_编程入门自学教程_菜鸟教程-免费教程分享
教程简介 AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容…...

1.linux的常用命令
目录 一、Linux入门 二、Linux文件系统目录 三、Linux的vi和vim的使用 四、Linux的关机、重启、注销 四、Linux的用户管理 五、Linux的运行级别 六、Linux的文件目录指令 七、Linux的时间日期指令 八、Linux的压缩和解压类指令 九、Linux的搜索查找指令 …...

XiaoFeng.Net 网络库使用
网络库介绍 XiaoFeng.Net网络库包含了 SocketServer,SocketClient,WebSocketServer,WebSocketClient四个类库 SocketServer 网络服务端同时支持Socket客户端连接,WebSocket客户端,浏览器WebSocket连接 SocketCleint 网络客户端 WebSocketServer WebSock…...

【ES6】—数组的扩展
一、类数组/ 伪数组 1. 类/伪数组: 并不是真正意义的数组,有长度的属性,但无法使用Array原型上的方法 let divs document.getElementsByTagName(div) console.log(divs) // HTMLCollection []let divs2 document.getElementsByClassName("xxx&q…...

Android 实现资源国际化
前言 国际化指的是当Android系统切换语言时,相关设置也随之改变,从而使用不同的国家地区; 简而言之,就是我们的Android App中的文字和图片会随着不同国家的地区变化从而切换为不同语言文字和不同国家的图片 文字图片国际化 只要…...

uni、js——点击与禁用(不可点击)、动态样式class
案例 没约满的时间可以点击进行选择,约满的就不能选择了。选择完之后变色变字。 核心思想就是创建一个第三方变量存起来,点击谁就存到第三方,在根据这个进行判断。 代码 <template><view class"content"><view cl…...

分布式技术
分布式没有权威的技术,只有实践经验和积累的组件。常见的分布式技术有发号机制、分布式数据库、分布式数据库事物、基于Redis的分布式缓存、分布式会话、分布式安全认证。 1、发号机制 在数据库(关系数据库)中,主键往往是一条记…...

Consul的简介与安装
1、Consul简介 Consul是一套开源的分布式服务发现和配置管理系统,由HashiCorp公司用Go语言开发,Consul提供了微服务系统中的服务治理、配置中心、控制总线等功能。这些功能中的每一个都可以根据需要单独使用,也可以一起使用以构建全方位的服…...

微服务框架 go-zero logx 日志组件剖析
addTenant api 和 rpc 的实现 上一篇我们说到咱们还剩下 addTenant 功能还未实现,不知道有没有兄弟感兴趣去实验一波的,本篇文章进行简要补充 根据上一篇文章分析,其实我们只需要执行如下几步即可: 编写 tenant.api,…...

基于Java+SpringBoot+Vue前后端分离图书电子商务网站设计和实现
博主介绍:✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取源码联系🍅 👇🏻 精彩专…...

C# Winfrom通过COM接口访问和控制Excel应用程序,将Excel数据导入DataGridView
1.首先要创建xlsx文件 2.在Com中添加引用 3. 添加命名空间 using ApExcel Microsoft.Office.Interop.Excel; --这样起个名字方面后面写 4.样例 //点击操作excelDataTable dt new DataTable();string fileName "D:\desktop\tmp\test.xlsx";ApExcel.Application exA…...

Linux-tomcat环境搭建、jpress部署实践、nginx反向代理
♥️作者:小刘在C站 ♥️个人主页: 小刘主页 ♥️努力不一定有回报,但一定会有收获加油!一起努力,共赴美好人生! ♥️学习两年总结出的运维经验,以及思科模拟器全套网络实验教程。专栏…...

SQLmap使用
文章目录 利用sqlmap 注入得到cms网站后台管理员账密获取数据库名称获取cms数据库的表名获取users表中的字段(内容)获取username字段和password字段的内容 salmap破解psot请求数据包salmap获取getshell 利用sqlmap 注入得到cms网站后台管理员账密 获取数…...

【Flutter】Flutter 使用 infinite_scroll_pagination 实现无限滚动分页
【Flutter】Flutter 使用 infinite_scroll_pagination 实现无限滚动分页 文章目录 一、前言二、安装和基本使用1. 添加依赖2. 基础配置和初始化 三、实际业务中的用法1. 与 API 集成2. 错误处理 四、完整示例1. 创建一个无限滚动列表2. 使用在你的应用中3. 完整代码示例 五、总…...

python爬虫的js逆向入门到进阶教程文章分享汇总~持续更新
目录 一、内容介绍二 、专栏内容-持续更新1、JS逆向入门2、Js逆向进阶3、爬虫基础知识4、工具与安装5、漫星内容分享 三、星球使用四、b站up主视频推荐 一、内容介绍 二 、专栏内容-持续更新 1、JS逆向入门 2023-08-25》11.常见加密>xx音乐RSA加密 https://articles.zsxq.c…...

面试常问:水平居中和垂直居中的方法
水平居中 文本居中 如果元素为行内元素,可以将父元素的text-align属性设置为center,这样子元素就会水平居中对齐 .text{text-align: center; }固定宽度的居中 如果元素宽度已知并固定,可以通过将左右margin设置为auto来实现水平居中。 .…...

第七届“蓝帽杯”初赛取证题目分享
前言: 刚刚打完比赛,准备晚上写一下wp,刚好整理一下题目,ctf的题目一般都有,取证不一定,所以我整理一下,方便大家复盘。 题目: 1:【APK取证】涉案apk的包名是ÿ…...

go语言学习之有关变量的知识
文章目录 变量的学习1.变量的使用步骤2.变量的注意事项3.变量使用的三种方式:4.程序中 号的使用5.变量的数据类型1)int数据类型2)小数类型浮点型3)**字符类型**4)**字符串(String)类型**5&…...

算法通过村第8关【青铜】| 二叉树的经典算法题
二叉树的双指针 1.相同的树 思路:递归的挨个比较是否相同 class Solution {public boolean isSameTree(TreeNode p, TreeNode q) {if((p null&&q!null) || (p ! null && q null) || (p!null&&q!null&&p.val ! q.val)){return f…...

Open3D 点云均值滤波
目录 一、算法原理1、均值滤波2、参考文献二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、算法原理 1、均值滤波 对待处理的当前采样点,选择一个模板,该模板由其邻近的若干个数据点组成,…...