【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);


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

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

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

相关文章

集丰照明|汽车美容店设计,装修色彩灯光搭配方法

正确处理好店面的空间设计。 店铺各个功能区设计要合理&#xff0c;衔接合理&#xff0c;这样既能提高员工的工作效率也能提高顾客的满意度。合理安排店铺的空间分配&#xff0c; 要给顾客一种舒适度&#xff0c;既不能让顾客感觉到过于拥挤&#xff0c;又不能浪费店铺的有限空…

.NET Core 实现日志打印输出在控制台应用程序中

在本文中&#xff0c;我们将探讨如何在 .NET Core 应用程序中将日志消息输出到控制台&#xff0c;从而更好地了解应用程序的运行状况。 .NET Core 实现日志打印输出在控制台应用程序中 在 .NET Core 中&#xff0c;日志输出打印是使用 Microsoft.Extensions.Logging 命名空间…

【LeetCode-面试经典150题-day14】

目录 19.删除链表的倒数第N个结点 82.删除排序链表中的重复元素Ⅱ 61. 旋转链表 86.分隔链表 146.LRU缓存 19.删除链表的倒数第N个结点 题意&#xff1a; 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 【输入样例】head [1,2,3,4,5…

element-ui里el-table表格操作列多横线怎么解决

错误展示 错误原因 在vue中封装了element-ui表格&#xff0c;然后使用插槽&#xff0c;fixed定位等&#xff0c;导致样式出现了错乱 解决方案 1、线没有对齐 /* Element-UI 的table 组件出现表格线条不对齐的问题 */ body .el-table th.gutter {display: table-cell !impor…

基于MATLAB开发AUTOSAR软件应用层Code mapping专题-part 5 Signal/States标签页介绍

这一篇我们说下signals和State这两个怎么搞做映射,那首先我们要知道什么是Signal和state,我们看下模型, 在原来的模型里我增加了标红的圆圈处delay模块,这个delay模块就是一个state模块,表示离散的一个状态,这个是个模型的基本概念,后续我有个专栏交接simulink建模,那…

专题:平面、空间直线参数方程下的切线斜率问题

本文研究平面、空间直线在参数方程形式下&#xff0c;切线斜率&#xff08;即导数&#xff09;如何表示的问题。 如上图所示。 设 y f ( x ) &#xff0c; x φ ( t ) &#xff0c; y ψ ( t ) 当 t t 0 时&#xff0c; x x 0 &#xff0c; y y 0 &#xff0c;即点 A 坐…

为Claude的分析内容做准备:提取PDF页面内容的简易应用程序

由于Claude虽然可以分析整个文件&#xff0c;但是对文件的大小以及字数是有限制的&#xff0c;为了将pdf文件分批传入Claude人工智能分析和总结文章内容&#xff0c;才有了这篇博客&#xff1a; 在本篇博客中&#xff0c;我们将介绍一个基于 wxPython 和 PyMuPDF 库编写的简易的…

【电路设计】单节锂电池使用

前言 最近在研究如何利用单节锂电池给3.3V单片机供电。 找到两个比较好的教程 单节锂电池如何转3.3V&#xff1f;升压还是降压&#xff1f; 锂电池接了保护板&#xff0c;就可以用五伏电压直接充电了吗&#xff1f; 其中上面提到的LDO&#xff0c;这里有一个型号&#xff1…

【校招VIP】测试专业课之TCP/IP模型

考点介绍&#xff1a; 大厂测试校招面试里经常会出现TCP/IP模型的考察&#xff0c;TCP/IP协议是网络基础知识&#xff0c;但是在校招面试中很多同学在基础回答中不到位&#xff0c;或者倒在引申问题里&#xff0c;就丢分了。 『测试专业课之TCP/IP模型』相关题目及解析内容可点…

Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required

项目场景&#xff1a; 最近因为公司业务需要在搭一个新架构&#xff0c;用的springboot3和jdk17,在整合mybatis多数据源的时候报错 &#xff08;引用的mybatisplus 和 mybatisplusjion的是最新的包-2023-08-26&#xff09; Error creating bean with name ‘XXXServiceImpl’:…

有限与无限游戏 | 真北荐书

2023佛山敏捷之旅暨DevOps Meetup志愿者为进行大会的组织与准备&#xff0c;每周三晚有一个例会。 例会前等人的时间&#xff0c;涌现出一个小的分享环节。今天分享这本书&#xff1a;《有限与无限游戏》。 大家选择成为志愿者&#xff0c;是一个无限游戏。而组织活动和完成各种…

Qt XML文件解析 QDomDocument

QtXml模块提供了一个读写XML文件的流&#xff0c;解析方法包含DOM和SAX,两者的区别是什么呢&#xff1f; DOM&#xff08;Document Object Model&#xff09;&#xff1a;将XML文件保存为树的形式&#xff0c;操作简单&#xff0c;便于访问。 SAX&#xff08;Simple API for …

视频监控/视频集中存储/云存储平台AI智能分析网关V3——功能简介

安防监控/视频集中存储/云存储平台AI智能分析网关V3内置了20多种AI算法&#xff0c;可针对安全生产、通用园区、智慧食安、石油化工等场景&#xff0c;提供基于视频智能检测技术的个性化行业解决方案。今天来具体介绍下v3版本的智能分析网关有哪些算法功能配置。 一、硬件 安防…

DDR与PCIe:高性能SoC的双引擎

SoC芯片无处不在&#xff0c;小到家电控制的MCU&#xff0c;大到手机芯片&#xff0c;我们都会接触到。如今大部分芯片设计公司都在开发SoC芯片&#xff0c;一颗SoC芯片可以集成越来越多的功能&#xff0c;俨然它已成为IC设计业界的焦点。 高性能、高速、高带宽的互联和存储的…

深入分析负载均衡情景

本文出现的内核代码来自Linux5.4.28&#xff0c;为了减少篇幅&#xff0c;我们尽量不引用代码&#xff0c;如果有兴趣&#xff0c;读者可以配合代码阅读本文。 一、有几种负载均衡的方式&#xff1f; 整个Linux的负载均衡器有下面的几个类型&#xff1a; 实际上内核的负载均衡…

如何保障Facebook账号登录稳定

当谈到保障Facebook账号的稳定性时&#xff0c;我们不得不提到那些令人头疼的情况——Facebook账号被封。尽管我们已经踏入数字化的未来&#xff0c;但是被封号似乎是一个时常困扰着社交媒体用户的问题。那么&#xff0c;让我们来看看一些常见的Facebook账号被封的原因&#xf…

Python Requests模块session的使用建议

本篇主要讲解Python Requests模块session的使用建议及整个会话中的所有cookie的方法。 测试代码 服务端&#xff1a;下面是用flask做的一个服务端&#xff0c;用来设置cookie以及打印请求时的请求头。 # -*- coding: utf-8 -*- from flask import Flask, make_response, req…

arm:day9

1。思维导图 2..I2C实验&#xff0c;检测温度和湿度 iic.h #ifndef __IIC_H__ #define __IIC_H__ #include "stm32mp1xx_gpio.h" #include "stm32mp1xx_rcc.h" #include "gpio.h" /* 通过程序模拟实现I2C总线的时序和协议* GPIOF ---> AHB4…

RabbitMQ默认监听的ip地址

RabbitMQ 默认监听所有可用 ip 地址&#xff0c;当Rabbitmq 所在的服务端节点上存在多 ip 时&#xff0c;只要客户端能与服务端任一 ip 通信&#xff0c;即可向 RabbitMQ 发送消息

统计学补充概念-13-逻辑回归

概念 逻辑回归&#xff08;Logistic Regression&#xff09;实际上是一种用于解决分类问题的统计学习方法&#xff0c;尽管其名称中带有"回归"一词&#xff0c;但它主要用于处理分类任务。逻辑回归用于预测一个事件发生的概率&#xff0c;并将其映射到一个特定的输出…