【一维和二维数组以及数组越界和冒泡排序的百分百干货】

简介: 【一维和二维数组以及数组越界和冒泡排序的百分百干货】

前言

儿童节快到啦,提前祝大家六一快乐 ♡(´∀`*)人(*´∀`)♡

大家都和谁一起过呀,一组可爱的表情包送给大家,快发给和你一起过六一的人叭

ヽ( ̄ω ̄( ̄ω ̄〃)ゝ

接下来进入正题啦啦啦啦



一、一维数组的创建和初始化

1.1. 数组的创建

数组是一组相同类型元素的集合

数组的创建方式:

type_t arr_name [const_n];

type_t 是指数组的元素类型

const_n是一个常量表达式,用来指定数组的大小

数组创建的实例

int arr[10];

char ch

注:数组创建,在C99标准之前,[ ]中要给一个常量才可以,不能使用变量。

在C99标准支持了变长数组的概念,数组的大小可以使用变量指定,但是数组不能初始化。

1.2 数组的初始化

数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。

数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。

什么是初始化?

int main()
{
  int arr1[10] = {1,2,3,4,5,6,7,8,9,10};//完全初始化
    int arr2[10] = { 1,2,3 };//不完全初始化,剩余的元素默认都是0
  int arr3[10] = { 0 };//不完全初始化,剩余的元素默认都是0
  int arr4[] = { 0 };//错误写法,省略数组的大小,数组必须初始化,数组的大小是根据初始化的内容来确定
  int arr5[] = { 1,2,3 };

1.3 一维数组的使用

打印1~10的数字

int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//创建变量的时候不可以用变量
  //              0 1 2 3 4 5 6 7 8 9 
  //printf("%d\n", arr[5]);//[] 下标引用操作符
  //printf("%d\n", arr[0]);//[] 下标引用操作符
  int i = 0;
  int sz = sizeof(arr) / sizeof(arr[0]);//10
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);//访问元素的时候可以用变量
  }
  return 0;
}

打印10~1的数字

int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  //              0 1 2 3 4 5 6 7 8 9 
  //printf("%d\n", arr[5]);//[] 下标引用操作符
  //printf("%d\n", arr[0]);//[] 下标引用操作符
  int i = 0;
  int sz = sizeof(arr) / sizeof(arr[0]);//10
  for (i = sz-1; i >= 0; i--)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

计算数组元素个数的方法

int main()
{
  int arr[10] = {0};//10 * 4
  printf("%d\n", sizeof(arr));//40 - 计算的是数组的总大小,单位是字节
  printf("%d\n", sizeof(arr[0]));//4
  int sz = sizeof(arr) / sizeof(arr[0]);
  printf("%d\n", sz);
  return 0;
}

总结;

1.数组是使用下标来访问的,下标是从0开始。

2.数组的大小可以通过计算得到。

1.4 一维数组在内存中的存储

16进制的值有:0 1 2 3 4 5 6 7 8 9 a b c d e f

关于2C变成30的计算方法如下:

c是12,每一个元素4个字节,12+4=16,逢16进1位。

仔细观察输出的结果,可以看到,随着数组下标的增长,元素的地址,也在有规律的递增。由此可以得出结论:数组在内存中是连续存放的。

二、二维数组的创建和初始化

2.1 二维数组的创建

int arr[3][4];
char arr[3][5];
double arr[2][4];

2.2 二维数组的初始化

如果要创建几组数

比如:

12345

23456

34567

56789

int main()
{
  //数组的初始化
  int arr[4][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7},{5,6,7,8,9} };
  int arr2[4][5] = { {1,2,3 }, {2, 3, 4 }, {3, 4, 5, 6, 7}, {5, 6, 7, 8, 9} };//列不够5个数,会补0
  //行是可以省略的,但是列是不能省略的
  int arr3[][5] = { {1,2,3 }, {2, 3, 4 }, {3, 4, 5, 6, 7}, {5, 6, 7, 8, 9} };
  return 0;
}

2.3 二维数组的使用

二维数组的使用也是通过下标的方式。

如果要访问数组的某个元素,通过行和列定位到具体某个元素。

二维数组的行和列都是从0开始的。

练习① 打印第二行第三列的6

练习② 打印整个数组的元素

i表示行,j表示列,用for循环打印每行每列,第一个for循环打印行,里面再嵌套一个for循环打印列,当i=0时,就能打印第0行的所有元素,然后换行,i++,i=1,进入for循环,打印第1行的所有j,依次循环下去就能打印完所有元素。

2.4 二维数组在内存中的存储

由上图所知二维数组在内存中也是连续存放的

所以为什么二维数组的行可以省略,列不能省略呢?

因为连续存放的时候,虽然不知道有多少行,但是知道有多少列,也就是一行有几个元素,就知道什么时候到下一行,比如数组有20个元素,知道一行有五个元素,那下一行就从6开始,所以行可以省略,列不能省略。

三.数组越界

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

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

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

C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的。

所以程序员写代码时,做好自己做越界的检查。

比如打印0~10的数

int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int i = 0;
  //0~10
  //越界访问
  //
  for (i = 0; i <=10; i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

分析错误❌:

数组的下标范围是0~9,但是for循环中的下标范围是0 ~10,虽然编译器没有报错,但打印的结果,跟要求不符,多打了一个随机值。

四.数组作为函数参数

数组传参,传递的是地址,传递的是首元素的地址

往往我们在写代码的时候,会将数组作为参数传个函数,比如:我要实现一个冒泡排序(这里要讲算法思想)函数

4.1 冒泡排序函数的正确设计

排序的方法:

1.冒泡排序:两两相邻的的元素进行比较(升序)

2.选择排序

3.插入排序

4.快速排序

冒泡排序 - 升序方法如下:

一趟冒泡排序完成一个数的排序

又完成了一趟冒泡排序,这次解决了一个8

一趟解决一个元素,假设有10个,要多少趟冒泡排序呢/

答案是9趟,那n个元素,就需要n-1趟

方法总结:

1.确定冒泡排序的趟数

2.一趟冒泡排序的趟数

练习:输入10个整数,对这组数进行排序

代码如下:

void bubble_sort(int* arr, int sz)//这里的arr的本质是指针
{
  int i = 0;
  for (i = 0; i < sz - 1; i++)
  {
    int j = 0;
    //每一趟开始前就假设已经有序了
    int flag = 1;
    //一趟每部比较的对数
    for (j = 0; j < sz - 1 - i; j++)
    {
      if (arr[j] > arr[j + 1])
      {
        //交换
        int tmp = arr[j];
        arr[j] = arr[j + 1];
        arr[j + 1] = tmp;
        flag = 0;
      }
    }
    if (flag == 1)//如果来到这flag依旧是1,
//说明数组本身就是有序,没有进行排序,则跳出循环,效率更高。
      break;
  }
}
int main()
{
  int arr[10] = { 0 };
  //输入
  int i = 0;
  int sz = sizeof(arr) / sizeof(arr[0]);
  for (i = 0; i < sz; i++)
  {
    scanf("%d", &arr[i]);
  }
  //冒泡排序 - 升序
  //趟数
  //arr作为数组进行了传参
  //数组传参,传递的是地址,传递的是首元素的地址
  //
  bubble_sort(arr, sz);//arr 是数组首元素的地址
  //输出
  for (i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

4.2 数组名

数组名通常情况下就是数组首元素的地址

如上图所示:arr和&arr[0]的地址一样,说明arr就是首元素的地址。

但是有2个例外:

  1. sizeof(数组名),数组名 单独放在sizeof()内部,这里的数组名表示整个数组,计算的是整个数组的大小
  2. &数组名,这里的数组名也表示整个数组,这里取出的是整个数组的地址 除此之外所有遇到的数组名都表示数组首元素的地址

    可以看到虽然&arr的地址跟arr和&arr[0]的地址一样,但是&arr+1之后,地址是直接加了40,说明&arr表示整个数组的大小。

总结

本章包含了关于数组的一系列知识点,如有错误的地方,请指正✺( ^ ▽ ^✺) ✺( ^ O ^)✺ (✺^ ▽ ^)✺

相关文章
|
2天前
|
算法 测试技术 C#
二分查找:LeetCode2035:将数组分成两个数组并最小化数组和的差
二分查找:LeetCode2035:将数组分成两个数组并最小化数组和的差
|
2天前
|
搜索推荐 算法 C语言
【排序算法】C语言实现随机快排,巨详细讲解
【排序算法】C语言实现随机快排,巨详细讲解
|
2天前
|
搜索推荐 算法 索引
【排序算法】深入解析快速排序(霍尔法&&三指针法&&挖坑法&&优化随机选key&&中位数法&&小区间法&&非递归版本)
【排序算法】深入解析快速排序(霍尔法&&三指针法&&挖坑法&&优化随机选key&&中位数法&&小区间法&&非递归版本)
|
10月前
|
搜索推荐
图解:快速排序算法之双边循环法
之前我们学习了冒泡排序,有没有比冒泡排序更快的排序算法呢?当然有,例如快速排序,归并排序,堆排序。接下来即将介绍的快速排序就是由冒泡排序演变而来的。
123 0
图解:快速排序算法之双边循环法
|
10月前
|
存储 算法 编译器
学C的第十二天【深入了解数组:一维和二维数组的创建和初始化;一维和二维数组的使用;一维和二维数组在内存中的存储;数组越界;数组作为函数参数;冒泡排序(对数组名的理解)】-2
5.二维数组的使用 操作符 [ ] :下标引用操作符,它其实就是数组访问的操作符,使用两个[ ],访问行和列 二维数组的行和列都是从0开始的
|
10月前
|
算法
图解:快速排序之单边循环法
单边循环法是快速排序的算法之一,之前的双边循环法从数列的两边交替遍历元素,虽然更加直观,不过代码实现起来相对复杂。而单边循环法就要简单多了,只需要从数组的一边对元素进行遍历和交换即可。
148 0
图解:快速排序之单边循环法
|
10月前
|
搜索推荐 算法 索引
快排图文详解:快速排序算法的实现 - 【双边循环法与单边循环法 & 递归与非递归(栈的方式)的实现】(一)
快排图文详解:快速排序算法的实现 - 【双边循环法与单边循环法 & 递归与非递归(栈的方式)的实现】
143 0
|
10月前
|
存储 搜索推荐 索引
快排图文详解:快速排序算法的实现 - 【双边循环法与单边循环法 & 递归与非递归(栈的方式)的实现】(二)
快排图文详解:快速排序算法的实现 - 【双边循环法与单边循环法 & 递归与非递归(栈的方式)的实现】
107 0
|
存储 算法 索引
【每日挠头算法题】LeetCode 1337. 矩阵中战斗力最弱的 K 行 —— 二分 + 排序 / 堆
【每日挠头算法题】LeetCode 1337. 矩阵中战斗力最弱的 K 行 —— 二分 + 排序 / 堆
102 0
【每日挠头算法题】LeetCode 1337. 矩阵中战斗力最弱的 K 行 —— 二分 + 排序 / 堆