【C语言基础】:深入理解指针(三)

文章目录

    • 深入理解指针
      • 一、冒泡排序
      • 二、二级指针
      • 三、指针数组
        • 3.1 指针数组模拟二维数组
      • 四、字符指针变量
      • 五、数组指针变量
        • 5.1 数组指针变量是什么?
        • 5.2 数组指针变量的初始化
      • 六、二维数组传参的本质

深入理解指针

指针系列回顾
【C语言基础】:深入理解指针(一)
【C语言基础】:深入理解指针(二)

一、冒泡排序

冒泡排序的核心思想就是:两两相邻的元素进行比较。

#include<stdio.h>void bubble_sort(int arr[], int sz)
{int i = 0;for (i = 0; i < sz - 1; i++){int j = 0;for (j = 0; j < sz - i - 1; j++){if (arr[j] > arr[j + 1]){int tmp = 0;tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}
}int main()
{int arr[] = { 10,9,8,7,6,5,4,3,2,1 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}return 0;
}

在这里插入图片描述
可以看到,这段代码对arr数组进行了排序,但这个代码还有一些缺陷,那就是无论数组内部的元素是否有序,他都会循环9次,这样肯定是不合理的,要进行优化一下。

我们在bubble_sort函数的第一层循环里面定义一个变量flag,进入第二层循环就修改flag的值,第二层循环结束时给变量flag来个判断,如果变量flag没有发生改变,说明没有进入第二层循环,也就是说这时数组里的元素是有序的,就会直接跳出第一层循环。另外,我们还可以在最外面定义一个全局变量用来计数,每进行一次元素交换就会自增1。

#include<stdio.h>
int count = 0;
bubble_sort(int arr[], int sz)  //参数接收数组元素个数
{int i = 0;for (i = 0; i < sz - 1; i++){int flag = 1;  //假设这⼀趟已经有序了int j = 0;for (j = 0; j < sz - i - 1; j++){count++;if (arr[j] > arr[j + 1]){flag = 0;  //发⽣交换就说明,⽆序int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}if (flag == 1)  //这⼀趟没交换就说明已经有序,后续⽆序排序了break;}
}int main()
{int arr[] = { 0,9,8,6,5,3,1,2,4,7 };int sz = sizeof(arr) / sizeof(arr[0]);  // 求出数组中的元素个数bubble_sort(arr, sz);int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");printf("%d\n", count);return 0;
}

在这里插入图片描述
运行之后可以发现,代码的运行效率明显提升了,理论来说上述数组元素在未优化的代码下运行是要进行45次交换的,现在明显次数减少了。

二、二级指针

通过前面的知识我们知道,指针变量也是一个变量,只要是一个变量就会有地址,那么,指针变量的地址放在哪里呢?
我们把存放地址的指针称为二级指针,也就是指针的指针

#include<stdio.h>
int main()
{int a = 10;int* pa = &a;int** ppa = &pa;return 0;
}

而三级指针就是存放二级指针变量的地址,四级指针、五级指针以此类推…

对于二级指针的运算有:

  • *ppa通过对ppa进行解引用,就可以找到pa,*ppa其实访问的就是pa
int b = 20;
*ppa = &b;  // 等价于pa = &b
  • **pa先通过*ppa找到pa,然后对pa进行解引用操作:*pa,找到的就是a。
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;

三、指针数组

首先我们要弄懂一个问题:指针数组到底是一个指针还是一个数组?
这里我们可以类比一下:

整数数组:就是一个存放整型的数组
字符数组:就是一个存放字符的数组

那指针数组呢?没错,就是存放指针的数组
在这里插入图片描述
指针数组里的每个元素都是用来存放地址(指针)的。

如下图:
在这里插入图片描述
通过这张图可以看到,指针数组里的每一个元素都是一个指针,而这里面的每一个指针又可以指向一块区域。

3.1 指针数组模拟二维数组
#include<stdio.h>
int main()
{int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };//数组名是数组首元素的地址,类型是int*的,就可以存放在parr数组中int* parr[] = { arr1, arr2, arr3 };int i = 0;int j = 0;for (i = 0; i < 3; i++){for (j = 0; j < 5; j++){printf("%d ", parr[i][j]);}printf("\n");}return 0;
}

在这里插入图片描述
在这里插入图片描述
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组,parr[i][j]就是整型⼀维数组中的元素。
注意:这只是利用指针数组模拟的二维数组,并非是真的二维数组,因为每一行的地址并非连续的

四、字符指针变量

在指针的类型中我们知道有一种指针类型为字辅指针char*
一般使用:

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

还有一种使用方式如下:

#include<stdio.h>
int main()
{const char* pc = "hello world";printf("%s\n", pc);//这里是把一个字符串放到pc指针变量里了吗?return 0;
}

在这里插入图片描述
注意:这里const char* pc = “hello world”; 特别容易让人认为是把字符串hello world放到字符指针pc里了,但其实本质上是字符串hello world 首字符的地址放到了pc中。
在这里插入图片描述
上面的代码的意思是把一个常量字符串的首字符h的地址存放到指针变量pc中。

下面是一道和字符串相关的题,我们可以学一下:

#include<stdio.h>
int main()
{char str1[] = "hello world";char str2[] = "hello world";const char* str3 = "hello world";const char* str4 = "hello world";if (str1 == str2)printf("str1 and str2 are same\n");  // 序号1elseprintf("str1 and str2 are not same\n");  // 序号2if (str3 == str4)printf("str3 and str4 are same\n");  // 序号3elseprintf("str3 and str4 are not same\n");  // 序号4return 0;
}

在这里插入图片描述
运行之后我们会发现,打印的是序号2和序号3的语句,这是为什么呢?
这里的str3和str4指向的是同一个常量字符串。C/C++会把常量字符串存储到一个单独的内存区域,当几个指针指向同一个字符串的时候,它们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

五、数组指针变量

5.1 数组指针变量是什么?

上面在学指针数组的时候我们知道,指针数组是数组元素为指针的数组。那么数组指针是什么呢?
上面学习指针数组的时候类比了一下,那么这里不妨也来类比一下:

  • 整型指针变量int * pint; 存放的是整型变量的地址,能够指向整形数据的指针。
  • 浮点型指针变量float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。

那么数组指针就是存放数组的地址,能够指向数组数据的指针变量

我们判断一下下面那个是数组指针

int *p1[10];
int (*p2)[10];

int * p1[10]; 是一个指针数组,是 10 个整型指针的数组。
int ( * p2)[10]; 是数组指针,表示一个指向包含 10 个整型数据的数组的指针。因为 p2 是一个指针,所以 p2 是一个指针类型。

数组指针变量

int (*p2)[10];

这里p会先和 * 号结合,说明p是一个指针变量,然后指向的是一个大小为10个整型数据大小的数组,所以p是一个指针,指向一个数组,叫数组指针
注意:[]的优先级是高于 * 号的,所以必须要加上()来保证 p会先和 * 结合。

5.2 数组指针变量的初始化

数组指针变量是用来存放数组地址的,那怎么获得数组的地址呢?其实就是我们之前学的 &数组名

int main()
{int arr[10] = { 0 };&arr;  //得到的就是数组的地址return 0;
}

如果要存放个数组的地址,就得存放在数组指针变量中,如下:

int main()
{int arr[10] = { 0 };int(*pa)[10] = &arr;return 0;
}

在这里插入图片描述
在调试的过程中我们也可以看到 &arrpa的类型是完全一致的。

数组指针类型解析:

int (*p) [10] = &arr;|    |    ||    |    ||    |    | p指向数组的元素个数|    p是数组指针变量名p指向的数组的元素类型

六、二维数组传参的本质

有了数组指针的理解,我们就能够讲⼀下二维数组传参的本质了。
过去我们有一个二维数组的需要传参给⼀个函数的时候,我们是这样写的:

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

在这里插入图片描述
总结:二维数组传参,形参的部分可以写成数组,也可以写成指针形式。

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

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

相关文章

UOS 与 Ubuntu 命令行打开安装包界面,双击打开界面调用安装包界面展示

UOS 使用deepin-deb-installer安装程序 deepin-deb-installer xxxxxxx.deb & Ubuntu snap-store --local-filename /home/seven/wps-office_1xxxxxxx.deb &

ubuntu20.04安装ros并配置相关环境以及驱动AUBO i5机械臂

ubuntu20.04安装ros并配置相关环境以及驱动AUBO i5机械臂 安装ros安装rosdep(小鱼的rosdepc,又快又好用)环境配置下载并编译aubo roslib库环境变量配置aubo gazeboaubo rviz驱动真实机械臂 安装ros 搜索鱼香ros网站https://fishros.com/&#xff0c;根据一键安装ros里提供的指…

Chromium内核浏览器编译记(四)Linux版本CEF编译

转载请注明出处&#xff1a;https://blog.csdn.net/kong_gu_you_lan/article/details/136508294 本文出自 容华谢后的博客 0.写在前面 本篇文章是用来记录编译Linux版本CEF的步骤和踩过的坑&#xff0c;以防止后续再用到的时候忘记&#xff0c;同时也希望能够帮助到遇到同样问…

Crow 编译和环境搭建

Crow与其说是编译&#xff0c;倒不如说是环境搭建。Crow只需要包含头文件&#xff0c;所以不用编译生成lib。 Crow环境搭建 boost&#xff08;可以不编译boost&#xff0c;只需要boost头文件即可&#xff09;asio &#xff08;可以不编译&#xff0c;直接包含头文件。不能直接…

【Linux】软件管理器yum和编辑器vim

&#x1f525;博客主页&#xff1a; 小羊失眠啦. &#x1f3a5;系列专栏&#xff1a;《C语言》 《数据结构》 《C》 《Linux》 《Cpolar》 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 文章目录 一、Linux下安装软件的方案1.1 源代码安装1.2 rpm安装1.3 yum安装 二、Linux软件…

Spring Boot 配置热部署

前言 对于 Spring Boot 项目之中, 在刚开始学习的时候, 每当代码进行变动的时候, 想要生效那就必须要手动重启. 为什么要重启呢 ? 原因在于写的代码是依靠运行之后的 class 文件运行的, 当我们的代码更新以后, 如果不去手动重启, 那么就无法生成新的 class 文件, 执行的就是旧…

蓝桥杯物联网竞赛_STM32L071_11_知识体系的查漏与补缺

太久没学单片机了&#xff0c;再重新过一遍查漏补缺&#xff0c;对其中之前没怎么在意的&#xff0c;而现在又发觉的问题进行再分析与补充 1. debug serial wire是干什么用的 这个东西我勾选不勾选都对我的程序没有什么影响&#xff0c;我很好奇是干什么用的&#xff0c;网上查…

vue3+elementPlus:el-table-column表格列动态设置单元格颜色

:cell-style属性 //html<el-tableempty-text"暂无数据":data"datalist.table":max-height"height"row-key"id"border:cell-style"cellStyle"> <el-table>//js //动态设置单元格颜色 const cellStyle ({ row, c…

Java后台面试相关知识点解析

文章目录 JavaJava中四种引用类型及使用场景集合HashMap源码及扩容策略HashMap死循环问题ConcurrentHashMap与HashtableConCurrentHashMap 1.8 相比 1.7 判断单链表是否有环&#xff0c;并且找出环的入口IO线程池线程池的几种创建方式判断线程是否可以回收线程池的7大核心参数线…

【MySQL】lower_case_table_names作用及使用

知识点&#xff1a; lower_case_table_names 是mysql设置大小写是否敏感的一个参数。 场景&#xff1a;在使用dataease时&#xff0c;连接外部数据库&#xff0c;启动报错&#xff01;后查看官方文档&#xff0c;特别要求改数据库配置文件&#xff1a;lower_case_table_names …

Mybatis从入门到CRUD到分页到日志到Lombok到动态SQL再到缓存

Mybatis 入门 1.导入maven依赖 <dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>x.x.x</version> </dependency>2.配置核心文件 <?xml version"1.0" encoding"U…

【MySQL知识体系】第1章 初始 MySQL

文章目录 第1章 初始 MySQL1.1 MySQL 介绍1.1.1 什么是 MySQL&#xff1f;1.1.2 MySQL 的特点&#xff1f;1.1.3 MySQL 默认端口&#xff1f; 1.2 安装 MySQL1.2.1在MacOS上安装MySQL1.2.2 在Windows上安装MySQL 1.3 如何选择 MySQL 客户端1.3.1 在MacOS上安装Workbench1.3.2 在…

【吊打面试官系列】Java虚拟机JVM篇 - 三道最简单最常问的JVM面试题

大家好&#xff0c;我是锋哥。今天分享三道最简单最常问的JVM面试题&#xff0c;希望对大家有帮助&#xff1b; 一&#xff0c;请问JDK与JVM有什么区别&#xff1f; 简单来说&#xff1a; 1. JVMJava 运行器&#xff1b; 2. JREJVM Java 基础&核心类库&#xff1b; 3. JD…

【贪心算法】专题练习二

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;折纸花满衣 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;【LeetCode】winter vacation training 目录 &#x1f449;&#x1f3fb;买卖股票的最佳时机&#x1f449;&…

吴恩达深度学习笔记:神经网络的编程基础2.1-2.3

目录 第一门课&#xff1a;神经网络和深度学习 (Neural Networks and Deep Learning)第二周&#xff1a;神经网络的编程基础 (Basics of Neural Network programming)2.1 二分类(Binary Classification)2.2 逻辑回归(Logistic Regression) 第一门课&#xff1a;神经网络和深度学…

【机器学习】详解正则化思想

我们的生活当中真正有意义或者有价值的部分可以概括为两句话&#xff1a;一句话是&#xff1a;弄清楚某个东西是怎么一回事&#xff0c;另一句话是&#xff0c;弄清楚某个东西是怎么一回事。头一句话&#xff0c;我们弄清楚的那个东西对于我们而言是未知的&#xff0c;但是已经…

【数据结构】二、线性表:4.循环链表的定义及其基本操作(循环单链表,循环双链表的初始化、判空、判断头结点、尾结点、插入、删除)

文章目录 4.循环链表4.1循环单链表4.1.1初始化4.1.2判断单链表是否为空4.1.3判断p结点是否为循环单链表的表尾结点 4.2循环双链表4.2.1初始化4.2.2判断循环链表是否为空4.2.3判断结点p是否为循环双链表的表尾结点4.2.4双链表的插入4.2.5双链表的删除 4.循环链表 4.1循环单链表…

Android自定义横向滑动菜单的实现

本文讲述了Android自定义横向滑动菜单的实现。分享给大家供大家参考&#xff0c;具体如下&#xff1a; 前言 开发安卓过程中&#xff0c;经常会用到标题栏的样式&#xff0c;有时候传统方式不能满足开发者的需要&#xff0c;这时候就需要自定义控件来实现。&#xff08;注意&…

Mac版2024 CleanMyMac X 4.14.6 核心功能详解以及永久下载和激活入口

CleanMyMac 是 macOS 上久负盛名的系统清理工具&#xff0c;2018 年&#xff0c;里程碑式版本 CleanMyMac X 正式发布。不仅仅是命名上的变化&#xff0c;焕然一新的 UI、流畅的动画也让它显得更加精致。新增的系统优化、软件更新等功能&#xff0c;使得在日常使用 macOS 时有了…

短视频矩阵系统----矩阵系统源码搭建(技术门槛?)

短视频矩阵是什么意思&#xff1f;短视频矩阵的含义可以理解为全方位的短视频账号&#xff0c;通过不同的账号实现全方位的品牌展示。实际上是指一个短视频账号&#xff0c;通过不同的链接实现品牌展示&#xff0c;在不同的粉丝流量账号中互相转发同一个品牌&#xff0c;在主账…