【C语言航路】第四站:数组(下)

简介: 【C语言航路】第四站:数组

我们会发现,还是每个元素差四个字节

也就是说二维数组也是和一维数组一样线性连续存储的!如下图所示,前四个为第一行,中间为第二行,后面为第三行,我们可以把一个二维数组当成一个一维数组来理解

那我们二维数组也就可以使用一维数组那样采用指针的方式来打印

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

输出结果为

而且我们二维数组每一行都可以理解为一个一维数组,这个一维数组的数组名为arr[0],arr[1]......如下图所示

所以二维数组也是一维数组的数组

所以我们的打印数组也可以这样进行

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

三、数组越界

数组的下标是有范围限制的。

数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1

所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。

C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就 是正确的,所以程序员写代码时,最好自己做越界的检查

比如下面的代码

#include <stdio.h>
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int i = 0;
  for (i = 0; i <= 10; i++)
  {
    printf("%d\n", arr[i]);//当i等于10的时候,越界访问了
  }
  return 0;
}

运行结果为,其中就出现了一个明显的错误。

二维数组的行和列也可能存在越界。

四、数组作为函数参数

往往我们在写代码的时候,会将数组作为参数传个函数,比如说我们想写出一个冒泡排序。

1.关于冒泡排序的经典错误标准零分

那么什么是冒泡排序呢?

比如说我们想要对下面一组数据进行升序的话

我们是这样想的,我们让9和8进行比较,如果9大于8,那么9和8进行交换,然后交换以后变成以下

此时我们让第二个和第三个位置数据继续进行比较,并进行交换。然后重复下去,直到最后会变成

我们发现9已经来到了他应该来到的最终位置,那么我们前面八个数还需要进行排序。我们继续采用相同的方法。我们会发现,前八个数字最终也会

此时我们发现8也来到了他应该来到的最终位置.

不妨我们就将这称作一趟冒泡排序,每一趟冒泡排序都可以使得一个数放到他应该来到的最终位置

那么问题来了,如果有10个数字,那么需要多少趟?

显然为9趟,对于任意n个数字,只需n-1趟冒泡排序。

于是我们就有冒泡排序的一个基本框架了

这里的for循环可以确定需要多少趟冒泡排序,而里面每一层都是一趟冒泡排序

那么一趟需要比较多少次呢?

我们回顾前面的那个案例,不难发现,第一趟,需要9次比较,第二趟需要8次比较,我们总结规律,n个元素需要n-i-1次比较。

由此得到代码实现如下所示

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void sort(int arr[])
{
  int sz = sizeof(arr) / sizeof(arr[0]);
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    int j = 0;
    for (int 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[] = { 4,5,1,3,7,10,100,45,323,852};
  sort(arr);
  for (i = 0; i < 10; i++)
  {
    printf("%d\n", arr[i]);
  }
  return 0;
}

然而当我们进行运行的时候,我们发现结果并非我们所期望的

并没有进行交换,这是为什么呢?其实问题就出在了sizeof,我们数组的传参传的其实是一个指针,而非整个数组,所以sizeof(arr)计算出来的并不是一个数组的长度,而是arr这个指针的大小,所以我们就应该将sz给放到主函数中,然后在传参的时候传入sz

代码实现如下

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void sort(int arr[],int sz)
{
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    int j = 0;
    for (int 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[] = { 4,5,1,3,7,10,100,45,323,852};
  int sz = sizeof(arr) / sizeof(arr[0]);
  sort(arr,sz);
  int i;
  for (i = 0; i < sz; i++)
  {
    printf("%d\n", arr[i]);
  }
  return 0;
}

运行后结果为

可见符合我们的预期

2.数组名到底是什么呢

我们在冒泡排序中,数组名一会是指针,一会代表整个数组,一会代表首元素地址。相信到了这块很多人都晕了,数组名到底是什么呢?

(1)一般的,数组名是首元素地址

一般情况下数组名是首元素地址,那么很多人就更加困惑了,什么是非一般情况呢?先不要着急,我们先来看看数组名是首元素地址的案例

int main()
{
  int arr[10] = { 0 };
  printf("%p\n", &arr[0]);
  printf("%p\n", arr);
  return 0;
}

运行之后

可见数组名就是首元素地址,而地址就是我们的指针。

(2)数组不是首元素地址的两个特例

第一个特例,sizeof(数组名)

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

运行结果为

其实,sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节

第二个特例,&数组名

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

运行结果为

可见这里的数组名表示的是整个数组,&数组名取出的是数组的地址

(3)总结

一般情况下,数组名是首元素的地址,但有两个例外,一个是sizeof(数组名),一个是&数组名,这两种情况下,数组名代表的是整个数组。

(4)&数组名和直接使用数组名的区别

在这里,又有人有了一些困惑,arr直接打印出来的地址和&arr出来的地址有什么区别呢?看上去打印出来的值都一样啊?

假设上面是我们的arr数组,我们运行如下代码

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

运行后结果为

可见,第一组+1后的地址是+4,第二组+1后的地址也是+4,而第三组+1后的地址是加了40

我们发现,虽然结果一样,也就是他们的起点一样,但是他们的含义不同,&arr+1是直接跳过整个数组,而前两者仅仅跳过一个元素。

3.再次研究冒泡排序

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void sort(int arr[],int sz)
{
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    int j = 0;
    for (int 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[] = { 4,5,1,3,7,10,100,45,323,852};
  int sz = sizeof(arr) / sizeof(arr[0]);
  sort(arr,sz);
  int i;
  for (i = 0; i < sz; i++)
  {
    printf("%d\n", arr[i]);
  }
  return 0;
}

我们现在应该能够理解了这段代码中,为什么sz要放在主函数内部了,虽然我传的是一个数组名,但是此时的数组名代表着首元素地址,而我们的形参虽然人模狗样的写着一个数组,但是其实他本质上是一个指针。也就说我们可以将他改为一个指针变量。

我们可以将其改为指针试一试

void sort(int* arr, int sz)
{
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    int j = 0;
    for (int 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;
      }
    }
  }
}

我们运行后,这个代码仍然是正确的,所以我们传数组可以使用一个指针来接受。当然我们也可以使用一个数组的形式来进行接受,这样其实更加便于初学者理解。

而我们传入一个指针以后,我们就可以对这个指针进行+1,而整型指针+1,向后移动4个字节。刚好就是下一个数据。所以采用指针可以顺藤摸瓜,拿到整个数组的值。

当然有人就有一些困惑了,为什么我们平时函数传参的时候是直接拷贝过去,而数组却是传地址呢?这一点大家可以思考以下,如果我有10000个元素的数组,那么我们还要传整个数组的话,那么对这对于计算机内存上,运行速度上都会产生极大的浪费。所以使用指针传参也是很合理的。


总结

本节主要讲解了数组的一些基本知识点,基本概念,以及数组传参时候的一些坑,如果对你有一些帮助的话,不要忘记点赞加收藏哦!!!

相关文章
|
1月前
|
传感器 算法 安全
【C语言】两个数组比较详解
比较两个数组在C语言中有多种实现方法,选择合适的方法取决于具体的应用场景和性能要求。从逐元素比较到使用`memcmp`函数,再到指针优化,每种方法都有其优点和适用范围。在嵌入式系统中,考虑性能和资源限制尤为重要。通过合理选择和优化,可以有效提高程序的运行效率和可靠性。
152 6
|
2月前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
89 5
|
2月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
2月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
2月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
2月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
71 4
|
3月前
|
存储 编译器 C语言
【c语言】数组
本文介绍了数组的基本概念及一维和二维数组的创建、初始化、使用方法及其在内存中的存储形式。一维数组通过下标访问元素,支持初始化和动态输入输出。二维数组则通过行和列的下标访问元素,同样支持初始化和动态输入输出。此外,还简要介绍了C99标准中的变长数组,允许在运行时根据变量创建数组,但不能初始化。
74 6
|
3月前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
3月前
|
存储 C语言
C语言:一维数组的不初始化、部分初始化、完全初始化的不同点
C语言中一维数组的初始化有三种情况:不初始化时,数组元素的值是随机的;部分初始化时,未指定的元素会被自动赋值为0;完全初始化时,所有元素都被赋予了初始值。
|
3月前
|
C语言
C语言数组
C语言数组
35 0

热门文章

最新文章