深入理解C语言中的指针与数组:从基础到高级

news/2024/9/25 22:33:47/

文章目录:

  • 前言
  • 一、 字符指针
  • 二、 指针数组
  • 三、 数组指针
  • 3.1、 数组指针的定义
  • 3.2、 数组名和&数组名
  • 3.3、 数组指针的使用
  • 四、 指针和数组传参
    • 4.1、 一维数组传参
  • 4.2、 二维数组传参
    • 4.3、 一级指针传参
    • 4.4、 二级指针传参
    • 结束

前言

在之前的学习中,我们已经初步了解了指针的概念。如果你还没有阅读过相关内容,可以先移步到【C语言】初阶指针详解。接下来,我们将深入探讨指针的更多细节。由于内容较长,我们将分为上、中、下三篇进行讲解。现在,让我们进入深入了解指针的第一篇。

一、 字符指针

在指针的类型中,有一种特殊的指针类型叫做字符指针 char*

一般的使用方式如下:

int main()
{char ch = 'w';char* pc = &ch;*pc = 'h';return 0;
}

另一种使用方式如下:

int main()
{char arr[20] = "hello bit";char* pc = arr;return 0;
}

需要注意的是,上面的代码只是将字符数组的首元素地址赋给了字符指针,而不是将整个字符数组的内容存储在字符指针中。

下面有一道题可以尝试一下:

在这里插入图片描述

输出结果如下:
在这里插入图片描述

这里 str3str4 指向的是同一个字符串常量。C/C++ 会将常量字符串存储在单独的内存区域,当多个指针指向同一个字符串时,它们实际上指向的是同一个内存单元。然而,使用相同字符串初始化的不同数组会开辟自己独立的空间,因此 str1str2 不同,而 str3str4 相同。

二、 指针数组

指针数组是一个存放指针的数组。我们在指针初阶已经了解过这一点。下面我们来复习一下,以下指针数组的意思:

int* arr1[10];  // 整形指针的数组
char *arr2[4];  // 一级字符指针的数组
char **arr3[5]; // 二级字符指针的数组

三、 数组指针

3.1、 数组指针的定义

数组指针本质上是一个指向数组的指针。下面哪一个是指向数组的指针?

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

p1p2 分别是什么?

我们先回顾一下操作符的优先级:
在这里插入图片描述

  • p1 先与 [] 结合,说明 p1 是一个数组,数组元素类型为 int*,所以 p1 是指针数组。
  • p2 先与 * 结合,说明 p2 是一个指针,指向的数据类型为 int[10],所以 p2 是数组指针。

需要注意的是,[] 的优先级高于 *,所以要用 () 保证 p2 先与 * 结合。

3.2、 数组名和&数组名

对于数组 int arr[10];&arrarr 有什么区别呢?

我们知道 arr 是数组首元素的地址,那么 &arr 是什么呢?

我们来看代码:

#include <stdio.h>
int main()
{int arr[10] = {0};printf("%p\n", arr);printf("%p\n", &arr);return 0;
}

输出结果如下:
在这里插入图片描述

我们可以看到 &arrarr 的地址值是一样的。那么 arr&arr 就是一样的吗?我们通过算术运算来看一下。

#include <stdio.h>
int main()
{int arr[10] = {0};printf("arr = %p\n", arr);printf("&arr= %p\n", &arr);printf("arr+1 = %p\n", arr+1);printf("&arr+1= %p\n", &arr+1);return 0;
}

输出结果如下:

在这里插入图片描述

我们可以看到 arr&arr 的地址还是一样的,但 arr+1arr 相差 4 个字节,&arr+1&arr 相差 40 个字节。从这里我们可以看出 arr&arr 值一样但意义不一样。

实际上,arr 表示数组首元素的地址,&arr 表示整个数组的地址,所以 arr+1 跳过一个元素的大小,而 &arr+1 跳过整个数组的大小。

3.3、 数组指针的使用

我们讲了数组指针的定义,接下来讲如何使用。

过去我们有⼀个⼆维数组的需要传参给⼀个函数的时候,我们是这样写的:

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{int i = 0;for(i=0; i<row; i++){int j=0;for(j=0; j<col; j++){printf("%d ", arr[i][j]);}printf("\n");}
}int main()
{int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};print_arr1(arr, 3, 5);return 0;
}

这⾥实参是⼆维数组,形参也写成⼆维数组的形式,那还有什么其他的写法吗?

这里我们先分析一下二维数组,二维数组可以看成每个元素都为一维数组的数组,也就是⼆维数组的每个元素是⼀个⼀维数组。那么⼆维数组的⾸元素就是第⼀⾏,是个⼀维数组。如下图:

在这里插入图片描述

所以,根据数组名是数组⾸元素的地址这个规则,⼆维数组的数组名表⽰的就是第⼀⾏的地址,是⼀维数组的地址。根据上⾯的例⼦,第⼀⾏的⼀维数组的类型就是 int [5],所以第⼀⾏的地址的类型就是数组指针类型 int(*)[5]。那就意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:

void print_arr2(int (*arr)[5], int row, int col)
{int i = 0;for(i=0; i<row; i++){int j=0;for(j=0; j<col; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};print_arr2(arr, 3, 5);return 0;
}

总结:二维数组传参,可以写成二维数组的形式,也可以写成数组指针的形式。

四、 指针和数组传参

我们学习了数组和指针后,在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

4.1、 一维数组传参

#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?
{}
int main()
{int arr[10] = {0};int *arr2[20] = {0};test(arr);test2(arr2);
}

对于 arr

  1. arr 是一个一维数组,形参可以写成一维数组的形式。
  2. arr 表示数组首元素的地址,是一个指向整形的指针常量,形参可以写成一级指针的形式。

对于 arr2

  1. arr2 是一个指针数组,其本质也是一个一维数组,形参也可以写成一维数组的形式。
  2. arr2 是数组首元素的地址,数组元素类型为 int* 的指针类型,arr2 表示为二级指针,形参可以写成二级指针的形式。

4.2、 二维数组传参

void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int (*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{int arr[3][5] = {0};test(arr);
}

void test(int* arr[5])void test(int **arr) 上面黄色的代码可以接收二维数组吗?

我们分析一下形参的类型:

  • arr 先与 * 结合,说明是一个指针,指向的数据类型为 int*,所以形参的数据类型为二级指针,但我们传的是一个二维数组,数据类型不匹配,所以二级指针不可以接收二维数组的传参。
  • arr 先与 [5] 结合,说明是一个数组,各元素的数据类型为 int*,所以形参的数据类型为指针数组,但我们传的是一个二维数组,数据类型不匹配,所以指针数组不可以接收二维数组的传参。

总结:

  • 二维数组传参,函数形参的设计只能省略第一个 [] 的数字。 因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素,这样才方便运算。
  • 二维数组传参的指针形式写成 type (*)[ ]

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

思考:

当一个函数的参数为一级指针时,可以传什么参数?

答案:

  • 一维数组的数组名
  • 一级指针

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

思考:

当函数的参数为二级指针的时候,可以接收什么参数?

答案:

  • 二级指针
  • 指针数组的数组名

结束

通过本文的学习,我们深入了解了指针和数组的相关知识,包括字符指针、指针数组、数组指针以及指针和数组的传参方式。


http://www.ppmy.cn/news/1530483.html

相关文章

DTH11温湿度传感器

DHT11 是一款温湿度复合传感器&#xff0c;常用于单片机系统中进行环境温湿度的测量。以下是对 DHT11 温湿度传感器的详细讲解&#xff1a; 一、传感器概述 DHT11 数字温湿度传感器是一款含有已校准数字信号输出的温湿度复合传感器。它应用专用的数字模块采集技术和温湿度传感…

群晖NAS使用Docker本地部署网页版Ubuntu系统并实现无公网IP远程访问

文章目录 前言1. 下载Docker-Webtop镜像2. 运行Docker-Webtop镜像3. 本地访问网页版Linux系统4. 群晖NAS安装Cpolar工具5. 配置异地访问Linux系统6. 异地远程访问Linux系统7. 固定异地访问的公网地址 前言 本文旨在详细介绍如何在群晖NAS部署docker-webtop&#xff0c;并结合c…

毕业设计选题:基于ssm+vue+uniapp的面向企事业单位的项目申报小程序

开发语言&#xff1a;Java框架&#xff1a;ssmuniappJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;M…

在 Windows 上安装和配置 NVIDIA 驱动程序、CUDA、cuDNN 和 TensorRT

在 Windows 上安装和配置 NVIDIA 驱动程序、CUDA、cuDNN 和 TensorRT 1. 安装 NVIDIA 图形驱动程序2. 安装 CUDA Toolkit3. 安装 cuDNN4.安装 TensorRT5. 常见问题1. 安装 NVIDIA 图形驱动程序 首先需要安装兼容 CUDA 的 NVIDIA 驱动程序。 下载最新驱动: 访问 NVIDIA 官网,…

使用分布式调度框架时需要考虑的问题——详解

引言 随着企业系统的规模不断扩大&#xff0c;特别是在分布式计算和云计算环境下&#xff0c;如何协调多个节点或服务执行任务成为一个关键问题。分布式调度框架在这种背景下应运而生&#xff0c;它可以调度成千上万的任务&#xff0c;在多个节点上分配、执行和监控任务&#…

STM32(十五):I2C通信

I2C通信 I2C&#xff08;Inter IC Bus&#xff09;是由Philips公司开发的一种通用数据总线&#xff0c;实现单片机读写外部模块寄存器的功能。 两根通信线&#xff1a;SCL&#xff08;Serial Clock&#xff09;、SDA&#xff08;Serial Data&#xff09; 同步&#xff0c;半双工…

WPF 依赖属性改变触发响应事件

WPF 依赖属性改变触发响应事件 在书写依赖属性时&#xff0c;如果后台数据发生了变化&#xff0c;我们会发现依赖属性如果不为他设置对应的响应事件&#xff0c;他是不会做任何操作的 解决方案&#xff1a; 当我们使用 DependencyProperty.Register 函数注册依赖属性时&#xf…

Golang | Leetcode Golang题解之第419题棋盘上的战舰

题目&#xff1a; 题解&#xff1a; func countBattleships(board [][]byte) (ans int) {for i, row : range board {for j, ch : range row {if ch X && !(i > 0 && board[i-1][j] X || j > 0 && board[i][j-1] X) {ans}}}return }