C语言进阶(五)—— 多维数组

news2024/10/7 6:49:57

1. 一维数组

  • 元素类型角度:数组是相同类型的变量的有序集合

  • 内存角度:连续的一大片内存空间

在讨论多维数组之前,我们还需要学习很多关于一维数组的知识。首先让我们学习一个概念。

1.1 数组名

考虑下面这些声明:

int a;
int b[10];

我们把a称作标量,因为它是个单一的值,这个变量是的类型是一个整数。我们把b称作数组,因为它是一些值的集合。下标和数名一起使用,用于标识该集合中某个特定的值。例如,b[0]表示数组b的第1个值,b[4]表示第5个值。每个值都是一个特定的标量。

那么问题是b的类型是什么?它所表示的又是什么?一个合乎逻辑的答案是它表示整个数组,但事实并非如此。在C中,在几乎所有数组名的表达式中,数组名的值是一个指针常量,也就是数组第一个元素的地址。它的类型取决于数组元素的类型:如果他们是int类型,那么数组名的类型就是“指向int的常量指针”;如果它们是其他类型,那么数组名的类型也就是“指向其他类型的常量指针”。

请问:指针和数组是等价的吗?

答案是否定的。数组名在表达式中使用的时候,编译器才会产生一个指针常量。那么数组在什么情况下不能作为指针常量呢?在以下两种场景下:

  1. 当数组名作为sizeof操作符的操作数的时候,此时sizeof返回的是整个数组的长度,而不是指针数组指针的长度。

  1. 当数组名作为&操作符的操作数的时候,此时返回的是一个指向数组的指针,而不是指向某个数组元素的指针常量。

int arr[10];
//arr = NULL; //arr作为指针常量,不可修改
int *p = arr; //此时arr作为指针常量来使用
printf("sizeof(arr):%d\n", sizeof(arr)); //此时sizeof结果为整个数组的长度
printf("&arr type is %s\n", typeid(&arr).name()); //int(*)[10]而不是int*

1.2 下标引用

int arr[] = { 1, 2, 3, 4, 5, 6 };

*(arr+ 3) ,这个表达式是什么意思呢?

首先,我们说数组在表达式中是一个指向整型的指针,所以此表达式表示arr指针向后移动了3个元素的长度。然后通过间接访问操作符从这个新地址开始获取这个位置的值。这个和下标的引用的执行过程完全相同。所以如下表达式是等同的:

*(arr + 3)
arr[3]

问题数组下标可否为负值?

请阅读如下代码,说出结果:

int arr[] = { 5, 3, 6, 8, 2, 9 };
int *p = arr + 2;
printf("*p = %d\n", *p);       //*p = 6
printf("*p = %d\n", p[-1]);    //*p = 3
printf("*p = %d\n", *(p - 1)); //*p = 3

那么是用下标还是指针来操作数组呢?对于大部分人而言,下标的可读性会强一些

1.3 数组和指针

指针和数组并不是相等的。为了说明这个概念,请考虑下面两个声明:

int a[10];
int *b;

声明一个数组时,编译器根据声明所指定的元素数量为数组分配内存空间,然后再创建数组名,指向这段空间的起始位置。声明一个指针变量的时候,编译器只为指针本身分配内存空间,并不为任何整型值分配内存空间,指针并未初始化指向任何现有的内存空间。

因此,表达式*a是完全合法的,但是表达式*b却是非法的。*b将访问内存中一个不确定的位置,将会导致程序终止。另一方面b++可以通过编译,a++却不行,因为a是一个常量值

1.4 作为函数参数的数组名

当一个数组名作为一个参数传递给一个函数的时候发生什么情况呢?我们现在知道数组名其实就是一个指向数组第1个元素的指针,所以很明白此时传递给函数的是一份指针的拷贝。所以函数的形参实际上是一个指针。但是为了使程序员新手容易上手一些,编译器也接受数组形式的函数形参。因此下面两种函数原型是相等的:

int print_array(int *arr);
int print_array(int arr[]);

我们可以使用任何一种声明,但哪一个更准确一些呢?答案是指针。因为实参实际上是个指针,而不是数组。同样sizeof arr值是指针的长度,而不是数组的长度。

现在我们清楚了,为什么一维数组中无须写明它的元素数目了,因为形参只是一个指针,并不需要为数组参数分配内存。另一方面,这种方式使得函数无法知道数组的长度。如果函数需要知道数组的长度,它必须显式传递一个长度参数给函数

2. 多维数组

如果某个数组的维数不止1个,它就被称为多维数组。接下来的案例讲解以二维数组举例。

void test01(){
    //二维数组初始化
    int arr1[3][3] = {
        { 1, 2, 3 },
        { 4, 5, 6 },
        { 7, 8, 9 }
    };
    int arr2[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int arr3[][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };

    //打印二维数组
    for (int i = 0; i < 3; i++){
        for (int j = 0; j < 3; j ++){
            printf("%d ",arr1[i][j]);
        }
        printf("\n");
    }
}

2.1 数组名

一维数组名的值是一个指针常量,它的类型是“指向元素类型的指针”,它指向数组的第1个元素。多维数组也是同理,多维数组的数组名也是指向第一个元素,只不过第一个元素是一个数组。例如:

int arr[3][10]

可以理解为这是一个一维数组,包含了3个元素,只是每个元素恰好是包含了10个元素的数组。arr就表示指向它的第1个元素的指针,所以arr是一个指向了包含了10个整型元素的数组的指针

2.2 指向数组的指针(数组指针)

数组指针,它是指针,指向数组的指针。

数组的类型由元素类型数组大小共同决定:int array[5] 的类型为 int[5];C语言可通过typedef定义一个数组类型

定义数组指针有以下三种方式

//方式一 先定义出数组的类型,再通过类型创建数组指针
void test01(){

    //先定义数组类型,再用数组类型定义数组指针
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    //有typedef是定义类型,没有则是定义变量,下面代码定义了一个数组类型ArrayType
    typedef int(ArrayType)[10];
    //int ArrayType[10]; //定义一个数组,数组名为ArrayType

    ArrayType myarr; //等价于 int myarr[10];
    ArrayType* pArr = &arr; //定义了一个数组指针pArr,并且指针指向数组arr
    for (int i = 0; i < 10;i++){
        printf("%d ",(*pArr)[i]);
    }
    printf("\n");
}

//方式二 先定义数组指针的类型,再创建数组指针变量
void test02(){

    int arr[10];
    //定义数组指针类型
    typedef int(*ArrayType)[10];
    ArrayType pArr = &arr; //定义了一个数组指针pArr,并且指针指向数组arr
    for (int i = 0; i < 10; i++){
        (*pArr)[i] = i + 1;
    }
    for (int i = 0; i < 10; i++){
        printf("%d ", (*pArr)[i]);
    }
    printf("\n");

}

//方式三 直接定义
void test03(){
    
    int arr[10];
    int(*pArr)[10] = &arr;

    for (int i = 0; i < 10; i++){
        (*pArr)[i] = i + 1;

    }
    for (int i = 0; i < 10; i++){
        printf("%d ", (*pArr)[i]);
    }
    printf("\n");
}

2.3 指针数组(元素为指针)

2.3.1 栈区指针数组

//数组做函数函数,退化为指针
void array_sort(char** arr,int len){

    for (int i = 0; i < len; i++){
        for (int j = len - 1; j > i; j --){
            //比较两个字符串
            if (strcmp(arr[j-1],arr[j]) > 0){
                char* temp = arr[j - 1];
                arr[j - 1] = arr[j];
                arr[j] = temp;
            }
        }
    }

}

//打印数组
void array_print(char** arr,int len){
    for (int i = 0; i < len;i++){
        printf("%s\n",arr[i]);
    }
    printf("----------------------\n");
}

void test(){
    
    //主调函数分配内存
    //指针数组
    char* p[] = { "bbb", "aaa", "ccc", "eee", "ddd"};
    //char** p = { "aaa", "bbb", "ccc", "ddd", "eee" }; //错误
    int len = sizeof(p) / sizeof(char*);
    //打印数组
    array_print(p, len);
    //对字符串进行排序
    array_sort(p, len);
    //打印数组
    array_print(p, len);
}

2.3.2 堆区指针数组

//分配内存
char** allocate_memory(int n){
    
    if (n < 0 ){
        return NULL;
    }

    char** temp = (char**)malloc(sizeof(char*) * n);
    if (temp == NULL){
        return NULL;
    }

    //分别给每一个指针malloc分配内存
    for (int i = 0; i < n; i ++){
        temp[i] = malloc(sizeof(char)* 30);
        sprintf(temp[i], "%2d_hello world!", i + 1);
    }

    return temp;
}

//打印数组
void array_print(char** arr,int len){
    for (int i = 0; i < len;i++){
        printf("%s\n",arr[i]);
    }
    printf("----------------------\n");
}

//释放内存
void free_memory(char** buf,int len){
    if (buf == NULL){
        return;
    }
    for (int i = 0; i < len; i ++){
        free(buf[i]);
        buf[i] = NULL;
    }

    free(buf);
}

void test(){
    
    int n = 10;
    char** p = allocate_memory(n);
    //打印数组
    array_print(p, n);
    //释放内存
    free_memory(p, n);
}

2.4 二维数组三种参数形式

2.4.1 二维数组的线性存储特性

void PrintArray(int* arr, int len){
    for (int i = 0; i < len; i++){
        printf("%d ", arr[i]);
    }
    printf("\n");
}

//二维数组的线性存储
void test(){
    int arr[][3] = {
        { 1, 2, 3 },
        { 4, 5, 6 },
        { 7, 8, 9 }
    };

    int arr2[][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int len = sizeof(arr2) / sizeof(int);

    //如何证明二维数组是线性的?
    //通过将数组首地址指针转成Int*类型,那么步长就变成了4,就可以遍历整个数组
    int* p = (int*)arr;
    for (int i = 0; i < len; i++){
        printf("%d ", p[i]);
    }
    printf("\n");

    PrintArray((int*)arr, len);
    PrintArray((int*)arr2, len);
}

2.4.2 二维数组的3种形式参数#

//二维数组的第一种形式
void PrintArray01(int arr[3][3]){
    for (int i = 0; i < 3; i++){
        for (int j = 0; j < 3; j++){
            printf("arr[%d][%d]:%d\n", i, j, arr[i][j]);
        }
    }
}

//二维数组的第二种形式
void PrintArray02(int arr[][3]){
    for (int i = 0; i < 3; i++){
        for (int j = 0; j < 3; j++){
            printf("arr[%d][%d]:%d\n", i, j, arr[i][j]);
        }
    }
}

//二维数组的第三种形式
void PrintArray03(int(*arr)[3]){
    for (int i = 0; i < 3; i++){
        for (int j = 0; j < 3; j++){
            printf("arr[%d][%d]:%d\n", i, j, arr[i][j]);
        }
    }
}

void test(){
    
    int arr[][3] = { 
        { 1, 2, 3 },
        { 4, 5, 6 },
        { 7, 8, 9 }
    };
    
    PrintArray01(arr);
    PrintArray02(arr);
    PrintArray03(arr);
}

3. 总结

3.1 编程提示

  • 源代码的可读性几乎总是比程序的运行时效率更为重要

  • 只要有可能,函数的指针形参都应该声明为const

  • 在多维数组的初始值列表中使用完整的多层花括号提高可读性

3.2 内容总结

  • 在绝大多数表达式中,数组名的值是指向数组第1个元素的指针。这个规则只有两个例外,sizeof和对数组名&。

  • 指针和数组并不相等。当我们声明一个数组的时候,同时也分配了内存。但是声明指针的时候,只分配容纳指针本身的空间。

  • 当数组名作为函数参数时,实际传递给函数的是一个指向数组第1个元素的指针。

  • 我们不单可以创建指向普通变量的指针,也可创建指向数组的指针。

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

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

相关文章

6、Fatfs系统移植

注意&#xff1a;挂载Fatfs笔记 Fatfs系统读写文件的时间是不固定的&#xff0c;随机性 搭载Fatfs的外设通信方式建议开启DMA方式&#xff0c;否则应避免中断打断时序&#xff0c;导致Fatfs出现FR_DISK_ERR&#xff08;A hard error occurred in the low level disk I/O layer&…

金三银四丨黑蛋老师带你剖析-安全开发岗

作者丨黑蛋在之前呢&#xff0c;我们聊了二进制这块的病毒岗位&#xff0c;漏洞岗位&#xff0c;逆向岗位以及CTF这块的岗位。今天我们就来聊一聊安全开发类的工作岗位。首先网络安全方向中安全开发岗位都有哪些&#xff0c;安全开发主要指安全研发工程师或安全开发工程师&…

手写线程池实例并测试

前言&#xff1a;在之前的文章中介绍过线程池的核心原理&#xff0c;在一次面试中面试官让手写线程池&#xff0c;这块知识忘记的差不多了&#xff0c;因此本篇文章做一个回顾。 希望能够加深自己的印象以及帮助到其他的小伙伴儿们&#x1f609;&#x1f609;。 如果文章有什么…

运动戴耳机哪种款式比较好、最好用的运动耳机

很多人喜欢运动时听音乐,因为在运动场景中,听歌的节奏与步频匹配的时候&#xff0c;的确是可以起到很好的激励和缓解情绪的作用。认认真真地选择一副适合自己跑步的运动耳机&#xff0c;成了很多跑步爱好者的实际需求&#xff0c;专门为运动打造的耳机也不少!那么,如何挑选一款…

macm1安装qt6

macm1安装qt6 本文目录macm1安装qt6前提下载在线安装包使用安装包进行安装QT creator测试运行环境前提 需要安装xcode以及command line tools 需要先注册账号密码 根据官方提示&#xff0c;5.15版本以上就不支持离线安装了&#xff0c;需要下载在线安装包 OFFLINE_README.txt…

Anaconda和PyCharm的一些安装问题和命令

今天更新了Windows上的Anaconda到2.3.2&#xff0c;PyCharm到2022.3。 ——发现是纯纯的犯贱orz。出了一堆问题。在这里记录一下供后来者参考。 Anaconda安装 将.\anaconda3\Scripts 和.\anaconda3\Library\bin添加到系统环境变量中。 新建环境的目录在.\anaconda3\envs下 N…

Retrofit源码分析

文章目录一、简介二、源码分析2.1Retrofit的本质流程2.2源码分析2.2.1 创建Retrofit实例步骤1步骤2步骤3步骤4步骤5总结2.2.2创建网络请求接口的实例外观模式 & 代理模式1.外观模式2. 代理模式步骤3步骤4总结2.2.3执行网络请求同步请求OkHttpCall.execute()1.发送请求过程2…

解决 NestHost requires ASM7 (shrink、kotlin metadata)

① 场景 Caused by: java.lang.RuntimeException: NestHost requires ASM7Failed to resolve class org/vigame/demo/CrashHandler$1.class[transform input:not foundproject input:not foundaar input:not found]Caused by: java.lang.UnsupportedOperationException: NestH…

flstudio21中文版下载安装图文教程

fl studio21中文版是一款免费的音乐编曲制作软件&#xff0c;有了它你可以制作出色的音乐。它为您提供了一个集成的开发环境&#xff0c;使用起来非常简单有效&#xff0c;您的工作会变得更有条理。同时FL Studio为用户提供了更先进和原创的音乐制作理念&#xff0c;用户可以轻…

简单学生管理系统

文章目录1. 学生类2. 学生管理类3. 运行结果1. 学生类 包含四个属性&#xff0c;学号、姓名、年龄及地址。类中包含一个无参构造、一个有参构造以及各属性的 get、set 方法。 package com.zxe;public class Student {private String id;private String name;private String a…

Vue+element ui遇到的一些疑难问题总结(一)

element ui 疑难总结1. el-date-picker时间区间控制2. el-cascader 获取name3. el-tree 搜索判断是否匹配到值1. el-date-picker时间区间控制 控制只能选区间&#xff08;7天&#xff0c;一个月&#xff0c;两个月等等&#xff09;重点为 :picker-options"pickerOptions&…

Python爬虫(10)selenium爬虫后数据,存入csv、txt并将存入数据并对数据进行查询

之前的文章有关于更多操作方式详细解答&#xff0c;本篇基于前面的知识点进行操作&#xff0c;如果不了解可以先看之前的文章 Python爬虫&#xff08;1&#xff09;一次性搞定Selenium(新版)8种find_element元素定位方式 Python爬虫&#xff08;2&#xff09;-Selenium控制浏览…

【原创】java+swing+mysql大学生竞赛管理系统设计与实现

上一篇文章我们介绍了使用swingtxt进行系统设计和数据存储&#xff0c;今天我们还是回归现实&#xff0c;使用javaswingmysql去设计开发一个大学生竞赛管理系统&#xff0c;以方便管理员对大学竞赛的一些信息进行管理。 功能分析&#xff1a; 大学生竞赛管理系统主要是提供给…

黄金短期陷入低位震荡颠簸

基本面&#xff1a; 周二&#xff08;2月21日)黄金价格维持1843-1830区间震荡&#xff0c;日线收带上下影线小阴线。 今日数据 无重要数据 技术面&#xff1a; 日线上&#xff0c;黄金日线收带上下影线小阴线&#xff0c;目前处于短期线附近及下方运行&#xff0c;5日与1…

CAN现场总线基础知识总结,看这一篇就理清了(CAN是什么,电气属性,CAN通协议等)

【系列专栏】&#xff1a;博主结合工作实践输出的&#xff0c;解决实际问题的专栏&#xff0c;朋友们看过来&#xff01; 《QT开发实战》 《嵌入式通用开发实战》 《从0到1学习嵌入式Linux开发》 《Android开发实战》 《实用硬件方案设计》 长期持续带来更多案例与技术文章分享…

FPGA纯Vhdl实现MIPI CSI2RX视频解码输出,OV13850采集,提供工程源码和技术支持

目录1、前言2、Xilinx官方主推的MIPI解码方案3、纯Vhdl方案解码MIPI4、vivado工程介绍5、上板调试验证6、福利&#xff1a;工程代码的获取1、前言 FPGA图像采集领域目前协议最复杂、技术难度最高的应该就是MIPI协议了&#xff0c;MIPI解码难度之高&#xff0c;令无数英雄竞折腰…

Vue3电商项目实战-商品详情模块3【07-★规格组件-SKUSPU概念、08-★规格组件-基础结构和样式、09-★规格组件-渲染与选中效果】

文章目录07-★规格组件-SKU&SPU概念08-★规格组件-基础结构和样式09-★规格组件-渲染与选中效果07-★规格组件-SKU&SPU概念 官方话术&#xff1a; SPU&#xff08;Standard Product Unit&#xff09;&#xff1a;标准化产品单元。是商品信息聚合的最小单位&#xff0…

二月天-课后程序(JAVA基础案例教程-黑马程序员编著-第五章-课后作业)

【案例5-5】 二月天 【案例介绍】 1.任务描述 二月是一个有趣的月份&#xff0c;平年的二月有28天&#xff0c;闰年的二月有29天。本例要求编写一个程序&#xff0c;从键盘输入年份&#xff0c;根据输入的年份计算这一年的2月有多少天。在计算二月份天数时&#xff0c;可以使…

【微信小程序】--创建第一个小程序项目项目文件的作用(二)

&#x1f48c; 所属专栏&#xff1a;【微信小程序开发教程】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#…