【C语言初阶】带你玩转C语言中的数组,并逐步实现冒泡排序,三子棋,扫雷 1

简介: 【C语言初阶】带你玩转C语言中的数组,并逐步实现冒泡排序,三子棋,扫雷

前言

Hello,这里是君兮_,今天为大家带来的是在C语言对数组的详解,废话不多说我们直接开始吧!


一维数组

1.一维数组的定义

C语言中给了数组的定义:一组相同类型元素的集合

数组的创建:

type_t arr_name [const_n];

type_t 是指数组的元素类型

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


我们有时可能会看到这种数组:

int count = 10;
int arr2[count];//数组时候可以正常创建吗?

注:数组创建,在C99标准之前, [ ] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念,数组的大小可以使用变量指定,但是数组不能初始化。

简单来说就是要看你的编译器,为了防止写出bug,我建议一般创建数组是还是不要这么写了。

数组的分类

根据元素类型的不同,数组大致可以分为三类:

整型数组

                         

                               int arr1[10]

字符数组

                              char arr2[10]

浮点型数组

                       

                            double arr3[10]

2.数组的初始化

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

话不多说,直接看代码:

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 };//省略数组的大小,数组必须初始化,数组的大小是根据初始化的内容来确定
char arr5[] = {'a','b','c'};
char arr6[] = "abcdef";
char arr7[]={"abcdef"}//与arr6一个意思,arr6是它的缩写

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

第一种越界情况

但是对于下面的代码要区分,内存中如何分配

int main()
{
  char arr1[] = {"abc"};
  char arr2[] = {'a','b','c'};
  printf("%s\n",arr1);
  printf("%s", arr2);
}

代码结果如图:

b9d83fbbd5c848da896ecf24977e3177.png

为啥会出现这种情况?

注意:当我们以%s形式打印上面数组内容时,遇到’\0’才会停止。第一个数组中的“abc”是一个字符串,在后面有一个我们看不见的’\0’,而第二个数组中是三个字符‘a’ ‘b’ ‘c’,后面是没有’\0’的,此时我想告诉你的是,你的第二个数组越界了!!此时这个数组后面存放的是随机值,打印出来就是随机的乱码,而直至它越界访问的地方出现0即’\0’,才会停止打印。

证明一下:

23a6a16c28ee47c1b915608326f0e57e.png

说明我们上面的说法是没错的。

3.数组的使用

对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。

数组的下标:

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

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


int arr[10];
int sz = sizeof(arr)/sizeof(arr[0]);//用整个数组的大小除以第一个元素的大小得到的就是数组中元素的个数

第二种越界情况

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

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

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

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

代码如下:


int main()
{
  int arr[10] = { 0 };//数组的不完全初始化
  //计算数组的元素个数
  int sz = sizeof(arr) / sizeof(arr[0]);
  //对数组内容赋值,数组是使用下标来访问的,下标从0开始
  int i = 0;
  for (i = 0; i < 10; i++)//这里写10,可以吗?
  {
    arr[i] = i;
  }
  //输出数组的内容
  for (i = 0; i < 10; ++i)
  {
    printf("%d ", arr[i]);
  }
  return 0;
}

这样改可以吗?

for (i = 0; i <=10; i++)

改完结果:

239ad80b7fe54af3b97e62f66140680b.png

这里连编译都走不过去,为什么?

越界访问!

注意:

1 当你写完上面的代码后,我想问问你,你的数组初始化时有几个元素呢?

int arr[10] = { 0 };//最多10个

2 数组的下标是从0开始的,也就是说最后一个元素的下标应该是9,即arr[9]。

3 当你把i的条件改为i<=10后,你会惊奇的发现,你的下标竟然能出现arr[10]!你数组里根本没有这个元素,这不是又越界吗了?

总结:

切记切记,上面两种越界情况是经常出现的,尤其是第二种。

我们经常在循环时因条件设置有误而导致越界,当代码多了之后,这种错误是非常难以发现的。我费这么大篇幅讲这两种越界情况就是为了提醒你们在以后使用数组的时候一定要小心谨慎,要多考虑考虑是否会出现越界情况,避免一个bug要找半天(博主的切身经历)

4.数组在内存中的存储

讲完了应用,我们再来讲讲数组的存储


示例代码如下:

#include <stdio.h>
int main()
{
int arr[10] = {0};
int i = 0;
  int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; ++i)
{
printf("&arr[%d] = %p\n", i, &arr[i]);//打印每个元素的地址
}
return 0;
}


d31fe5f264ea4e0e8a9dc831b7eea128.png


由于该数组为int型数组,每个元素占四个字节,即每一元素地址对应+4,实际还是从低地址到高地址连续存放

07f698ad30f243ccb8954d89521991ec.png


仔细观察输出的结果,我们知道,随着数组下标的增长,元素的地址,也在有规律的递增。

由此可以得出结论:数组在内存中是连续存放的。


目录
相关文章
|
10天前
|
C语言
扫雷游戏(用C语言实现)
扫雷游戏(用C语言实现)
46 0
|
1天前
|
存储 编译器 C语言
【c语言】数组
本文介绍了数组的基本概念及一维和二维数组的创建、初始化、使用方法及其在内存中的存储形式。一维数组通过下标访问元素,支持初始化和动态输入输出。二维数组则通过行和列的下标访问元素,同样支持初始化和动态输入输出。此外,还简要介绍了C99标准中的变长数组,允许在运行时根据变量创建数组,但不能初始化。
17 5
|
4天前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
10天前
|
存储 人工智能 BI
C语言:数组的分类
C语言中的数组分为一维数组、多维数组和字符串数组。一维数组是最基本的形式,用于存储一系列相同类型的元素;多维数组则可以看作是一维数组的数组,常用于矩阵运算等场景;字符串数组则是以字符为元素的一维数组,专门用于处理文本数据。
|
8天前
|
存储 C语言
C语言:一维数组的不初始化、部分初始化、完全初始化的不同点
C语言中一维数组的初始化有三种情况:不初始化时,数组元素的值是随机的;部分初始化时,未指定的元素会被自动赋值为0;完全初始化时,所有元素都被赋予了初始值。
|
10天前
|
算法 搜索推荐 C语言
【C语言】冒泡排序+优化版
【C语言】冒泡排序+优化版
|
10天前
|
C语言
C语言数组
C语言数组
12 0
|
10天前
|
C语言 C++
C语言 之 内存函数
C语言 之 内存函数
25 3
|
1天前
|
存储 缓存 C语言
【c语言】简单的算术操作符、输入输出函数
本文介绍了C语言中的算术操作符、赋值操作符、单目操作符以及输入输出函数 `printf` 和 `scanf` 的基本用法。算术操作符包括加、减、乘、除和求余,其中除法和求余运算有特殊规则。赋值操作符用于给变量赋值,并支持复合赋值。单目操作符包括自增自减、正负号和强制类型转换。输入输出函数 `printf` 和 `scanf` 用于格式化输入和输出,支持多种占位符和格式控制。通过示例代码详细解释了这些操作符和函数的使用方法。
17 10
|
5天前
|
存储 编译器 C语言
C语言函数的定义与函数的声明的区别
C语言中,函数的定义包含函数的实现,即具体执行的代码块;而函数的声明仅描述函数的名称、返回类型和参数列表,用于告知编译器函数的存在,但不包含实现细节。声明通常放在头文件中,定义则在源文件中。