C语言第二十弹---指针(四)

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】

指针

1、字符指针变量

2、数组指针变量

2.1、数组指针变量是什么?

2.2、数组指针变量怎么初始化

3、⼆维数组传参的本质

4、函数指针变量

4.1、函数指针变量的创建

4.2、函数指针变量的使用

4.3、typedef关键字

5、函数指针数组

总结


1、字符指针变量

在指针的类型中我们知道有⼀种指针类型为字符指针 char* ;
⼀般使用:
int main()
{
 char ch = 'w';
 char *pc = &ch;
 *pc = 'w';
 return 0;
}
还有⼀种使用方式如下:
int main()
{
 const char* pstr = "hello bit.";//这里是把⼀个字符串放到pstr指针变量里了吗?
 printf("%s\n", pstr);
 return 0;
}
代码 const char* pstr = "hello bit."; 特别容易让同学以为是把字符串 hello bit
到字符指针 pstr ⾥了,但是 本质是把字符串 hello bit. 首字符的地址放到了pstr中。
上面代码的意思是把⼀个常量字符串的首字符 h 的地址存放到指针变量 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;
}
这里str3和str4指向的是⼀个同⼀个常量字符串。 C/C++会把常量字符串存储到单独的⼀个内存区域, 当几个指针指向同⼀个字符串的时候,他们实际会指向同⼀块内存。 但是 用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。 所以str1和str2不同,str3和str4相同。

2、数组指针变量

2.1、数组指针变量是什么?

之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)。
数组指针 变量是指针变量?还是数组?
答案是: 指针变量。
我们已经熟悉:
整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。
浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。
数组指针变量 应该是:存放的应该是 数组的地址,能够指向数组的指针变量。
下面代码哪个是数组指针变量?
int *p1[10];
int (*p2)[10];
思考⼀下:p1, p2分别是什么?
数组指针变量
int (*p)[10];
解释:p先和*结合,说明p是⼀个指针变量,然后指着指向的是⼀个大小为10个整型的数组。所以
p是⼀个指针,指向⼀个数组,叫数组指针。
这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

2.2、数组指针变量怎么初始化

数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?就是我们之前学习的 & 数组名
int arr[10] = {0};
&arr;//得到的就是数组的地址
如果要存放个数组的地址,就得存放在数组指针变量中,如下:
int(*p)[10] = &arr;
我们调试(生成解决方案)也能看到 &arr p 的类型是完全⼀致的。
数组指针类型解析:
int (*p) [10] = &arr;
 | | |
 | | |
 | | p指向数组的元素个数
 | p是数组指针变量名
 p指向的数组的元素类型

3、⼆维数组传参的本质

有了数组指针的理解,我们就能够讲⼀下⼆维数组传参的本质了。
过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:
#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+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;
}
总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式。

4、函数指针变量

4.1、函数指针变量的创建

什么是函数指针变量呢?
根据前面学习整型指针,数组指针的时候,我们的类比关系,我们不难得出结论:
函数指针变量应该是用来存放函数地址的,未来通过地址能够调用函数的。
那么函数是否有地址呢?
我们做个测试:
#include <stdio.h>
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("test: %p\n", test);
 printf("&test: %p\n", &test);
 return 0;
}
输出结果如下:
test: 005913CA
&test: 005913CA
确实打印出来了地址,所以函数是有地址的,函数名就是函数的地址,当然也可以通过 & 函数名 的方式获得函数的地址。
如果我们要将函数的地址存放起来,就得创建函数指针变量咯,函数指针变量的写法其实和数组指针非常类似。如下:
void test()
{
 printf("hehe\n");
}
void (*pf1)() = &test;
void (*pf2)()= test;
int Add(int x, int y)
{
 return x+y;
}
int(*pf3)(int, int) = Add;
int(*pf3)(int x, int y) = &Add;//x和y写上或者省略都是可以的
函数指针类型解析:
int (*pf3) (int x, int y)
 |    |     ------------ 
 |    |      |
 |    |   pf3指向函数的参数类型和个数的交代
 |  函数指针变量名
 pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型

4.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;
}
输出结果:

4.3、typedef关键字

typedef 是用来类型重命名的,可以将复杂的类型,简单化。
比如,你觉得 unsigned int  写起来不方便,如果能写成 uint 就方便多了,那么我们可以使用:
typedef unsigned int uint;
//将unsigned int 重命名为uint
如果是指针类型,能否重命名呢?其实也是可以的,比如,将 int* 重命名为 ptr_t ,这样写:
typedef int* ptr_t;
但是对于数组指针和函数指针稍微有点区别:
比如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:
typedef int(*parr_t)[5]; //新的类型名必须在*的右边
函数指针类型的重命名也是⼀样的,比如,将 void(*)(int) 类型重命名为 pf_t ,就可以这样写:
typedef void(*pf_t)(int);//新的类型名必须在*的右边

5、函数指针数组

数组是⼀个存放相同类型数据的存储空间,我们已经学习了指针数组,
比如:
int *arr[10];
//数组的每个元素是int*
那要把 函数的地址存到⼀个数组中 ,那这个 数组就叫函数指针数组 ,那函数指针的数组如何定义呢?
int (*parr1[3])();//函数指针数组
int *parr2[3]();//指针数组
int (*)() parr3[3];
答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?
int (*)() 类型的函数指针。

总结


本篇博客就结束啦,谢谢大家的观看,如果公主少年们有好的建议可以留言喔,谢谢大家啦!

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

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

相关文章

蓝桥杯Web应用开发-CSS3 新特性

CSS3 新特性 专栏持续更新中 在前面我们已经学习了元素选择器、id 选择器和类选择器&#xff0c;我们可以通过标签名、id 名、类名给指定元素设置样式。 现在我们继续选择器之旅&#xff0c;学习 CSS3 中新增的三类选择器&#xff0c;分别是&#xff1a; • 属性选择器 • 子…

Mysql进阶(锁)

一、锁概述 两个事务的写操作之间的互相影响。隔离性要求同一时刻只能有一个事务对数据进行写操作&#xff0c;InnoDB通过锁机制来保证这一点。 锁机制基本原理&#xff1a; 事务在修改数据之前&#xff0c;需要先获得相应的锁&#xff1b;获得锁之后&#xff0c;事务便可以…

day03.C++函数与指针(含new与delete关键字,函数指针,指针函数,指针数组)

一.指针 #include<iostream> using namespace std; #define pi 3.14159//定义常量int main(){cout<<pi<<endl;int a10;int *pNULL;//定义指针p&a;cout<<"a的地址为&#xff1a;"<<p<<endl;cout<<"a"<&l…

python函数入参、类成员引用支持灵活参数可配

一、背景 python编码时&#xff0c;有可能在不同场景下输入修改的参数&#xff0c;不方便直接写死&#xff0c;因此需要灵活配置这些函数入参&#xff0c;类成员 二、函数入参支持灵活可配 场景&#xff1a;如下场景&#xff0c;对于hello函数&#xff0c;不同场景下想要对不…

如何修改远程端服务器密钥

前言 一段时间没改密码后&#xff0c;远程就会自动提示CtrlAltEnd键修改密码。但我电脑是笔记本&#xff0c;没有end键。打开屏幕键盘按这三个键也没用。 解决方法 打开远程 1、远程端WINC 输入osk 可以发现打开了屏幕键盘 2、电脑键盘同时按住CtrlAlt&#xff08;若自身电…

学习Spring的第十六天

AOP底层两种生成Proxy的方式 我来解释这两种方式 1 目标类有接口 , 调用JDK的动态代理实现 2 目标类没有接口 , 用Cglib实现 , 即生成目标类的子类 , 来实现动态代理 , 所以要求目标类不能时final修饰的 . (若有接口 , 也可用Cglib方式实现 , 需要手动配置<aop: config pr…

java设计模式- 建造者模式

一 需求以及实现方式 1.1 需求描述 我们要创建一个表示汽车的复杂对象&#xff0c;汽车包含发动机、轮胎和座椅等部分。用传统方式创建&#xff0c;代码如下 1.2 传统实现方式 1.抽象类 public abstract class BuildCarAbstaract {//引擎public abstract void buildEng…

Swift Combine 管道 从入门到精通三

Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二 1. 用弹珠图描述管道 函数响应式编程的管道可能难以理解。 发布者生成和发送数据&#xff0c;操作符对该数据做出响应并有可能更改它&#xff0c;订阅者请求并接收这些数据。 这…

Python程序员面试题精选(1)

本文精心挑选了10道Python程序员面试题&#xff0c;覆盖了Python的多个核心领域&#xff0c;包括装饰器、lambda函数、列表推导式、生成器、全局解释器锁(GIL)、单例模式以及上下文管理器等。每道题都附有简洁的代码示例&#xff0c;帮助读者更好地理解和应用相关知识点。 题目…

Python模拟点赞助手

背景 之前公司一直会安排各种学习任务&#xff0c;视频章节很多的视频&#xff0c;有些是技术的有些是政策相关的&#xff0c;如果没有按时完成学习任务会被通报。但是&#xff0c;这些学习内容不一定有帮助&#xff0c;所以大家就养成了刷课的习惯&#xff0c;也就是点开视频…

服务器运存使用率多少正常?

服务器运存使用率多少正常&#xff0c;这是一个相对主观的问题&#xff0c;因为服务器的正常运行不仅取决于运存使用率&#xff0c;还与服务器的工作负载、应用程序的特性和需求、服务器的配置和用途等多种因素有关。然而&#xff0c;一般来说&#xff0c;大多数服务器在运存使…

###C语言程序设计-----C语言学习(10)#函数再探

前言&#xff1a;感谢您的关注哦&#xff0c;我会持续更新编程相关知识&#xff0c;愿您在这里有所收获。如果有任何问题&#xff0c;欢迎沟通交流&#xff01;期待与您在学习编程的道路上共同进步。 目录 一. 基础知识的学习 1.不返回结果函数 2.局部变量 3.全局变量 4.…

AIGC ChatGPT4 实战案例教你VLOOKUP用法

实际需求&#xff1a; 像这样的案例可以通过VLOOKUP来完成&#xff0c;如果不会使用VLOOKUP也没有关系&#xff0c;我们可以先让ChatGPT来帮我们介绍一下VLOOKUP函数的具体用法。 VLOOKUP是Excel中的一个非常有用的函数&#xff0c;用于查找和检索数据。"V"在VLOOKUP…

Java面向对象 this

this 在Java中&#xff0c;this 是一个特殊的引用变量&#xff0c;它引用了当前对象实例。当在类的非静态方法或构造方法中使用时&#xff0c;this 关键字指代当前的对象实例。它经常用于区分对象的成员变量和局部变量&#xff0c;或者调用其他重载的方法。 以下是一些使用 t…

锁(二)队列同步器AQS

一、队列同步器AQS 1、定义 用来构建锁或者其他同步组件的基础框架&#xff0c;它使用了一个int成员变量表示同步状态&#xff0c;通过内置的FIFO队列来完成资源获取线程的排队工作。是实现锁的关键。 2、实现 同步器的设计是基于模板方法模式的&#xff0c;也就是说&#…

Ubuntu22.04切换系统cuda版本

由于最近项目要求的cuda版本有差异&#xff0c;而在Ubuntu中可以通过切换cuda来满足需求&#xff0c;现记录如下。 1、按照 Ubuntu22.04与深度学习配置 中的cuda安装章节&#xff0c;将需要的cuda版本下载到本地并进行安装。 2、cuda安装完成后修改bashrc文件内容 sudo gedit …

Spring核心基础:全面总结Spring中提供的那些基础工具类!

内容概要 Spring Framework 提供了众多实用的工具类&#xff0c;这些工具类在简化开发流程、提升代码质量和维护性方面发挥了重要作用&#xff0c;以下是部分关键工具类的总结及其使用场景&#xff1a; StringUtils&#xff1a;不仅提供了基础的字符串操作&#xff0c;如拼接…

倍增c++

快速幂 区间最小值&#xff08;用倍增思想去求&#xff09; #include<bits/stdc.h> using namespace std; #define maxn 110000 #define ll long long int n, a[maxn], f[maxn][40]; int query (int l, int r) {int k (int)(log((r - l 1) * 1.0) / log(2.0));retur…

JWT令牌 | 一个区别于cookie/session的更安全的校验技术

目录 1、简介 2、组成成分 3、应用场景 4、生成和校验 5、登录下发令牌 &#x1f343;作者介绍&#xff1a;双非本科大三网络工程专业在读&#xff0c;阿里云专家博主&#xff0c;专注于Java领域学习&#xff0c;擅长web应用开发、数据结构和算法&#xff0c;初步涉猎Pyth…

selenium+egdedriver+opencv 爬虫使用教程

一、Edge安装 二、Edge版本查看 1、安装内核要保证内核版本已浏览器版本一致 2、Window环境在edge浏览器中输入&#xff1a;edge://version/ 3、Ubuntu环境在浏览器目录执行 ./microsoft-edge-beta -verison 三、EdgeDriver安装 1、下载地址&#xff1a;https://developer.m…