C语言学习分享(第六次)------数组

news2025/4/21 13:36:16

💓博主CSDN主页:杭电码农-NEO💓

⏩专栏分类:C语言学习分享⏪

🚚代码仓库:NEO的学习日记🚚

🌹关注我🫵带你学习更多C语言知识
  🔝🔝


在这里插入图片描述


数组详解

  • 1. 前言🔶
  • 2. 一维数组🔶
    • 2.1 一维数组的创建🔷
    • 2.2 数组的初始化🔷
    • 2.3 一维数组的使用🔷
    • 2.4 一维数组在内存中的存储🔷
  • 3. 二维数组🔶
    • 3.1 二维数组的创建🔷
    • 3.2 二维数组的初始化🔷
    • 3.3 二维数组的使用🔷
    • 3.4 二维数组在内存中的存储🔷
  • 4. 数组的越界访问🔶
  • 5. 数组作为函数参数🔶
    • 5.1 冒泡排序中数组传参的问题🔷
    • 5.2 数组名到底是什么?🔷
  • 6. 总结

1. 前言🔶

啊~~,很久没有更新C语言知识了,各位久等了,本篇文章在了解了数组的基本知识后, 着重于给大家实现两个小游戏:三子棋和扫雷


2. 一维数组🔶

2.1 一维数组的创建🔷

type_t   arr_name   [const_n];
//type_t 是指数组的元素类型
//arr_name是数组名,是自己取的
//const_n 是一个常量表达式

比如我们可以依次定义一个整型数组,一个浮点型数组,一个字符型数组:

int a[10];//可以存放十个整型的数组
float b[20];//可以存放二十个整型的数组
char c[10];

值得注意的是,方括号 [ ] 里面必须是常量表达式,假如我们这样初始化数组就会出问题:

int size=10;
int a[size];

这里 size 虽然被定义为10了,但是它是变量不是常量,所以这样不对,假如我们加上一个static关键字呢?答案是这样也是不可以的,因为被static关键字修饰的变量虽然具有的常量属性,但是本质上它还是一个变量.

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

特别的:
vs2022和2019编译器中不支持C99中变长数字组.


2.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 2 3,后面两个元素默认为0
	int arr6[];//错误的写法

   char arr1[] = "abc";
   char arr2[] = {'a', 'b', 'c'};
   char arr3[] = { 'a', 98, 'c' };
   return 0;
}

我们要区分下面这两种初始化数组的方式的区别:

char a[]="abc";
char b[]={'a','b','c'};

画个内存图理解一下:

在这里插入图片描述


2.3 一维数组的使用🔷

对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。我们来看代码:
(下标从0开始!)

#include <stdio.h>
int main()
{
    int arr[10] = {0};//数组的不完全初始化
    //计算数组的元素个数
    int sz = sizeof(arr)/sizeof(arr[0]);
 //对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以:
 int i = 0;//做下标
 for(i=0; i<10; i++)
 {
    arr[i] = i;
 } 
 //输出数组的内容
 for(i=0; i<10; ++i)
 {
    printf("%d ", arr[i]);
 }
 return 0;
}

值得注意的是,数组初始化时,方括号[ ]内不允许使用变量只能用常量,但是对数组的使用中,方括号[ ]内是可以为变量的


这里第一个引出求数组元素个数的一种求法: size= sizeof(数组名)/sizeof(任意一个数组中元素).虽然我们经常说,数组名代表首元素地址, 但是有特例!当数组名放在sizeof中时,这时求出的是整个数组所占空间的大小(单位是字节),然而把单个数组元素放进sizeof中即求出数组中单个元素所占空间大小,讲它们两个相除就可以得到数组元素个数.

比如我们来举个例子:

#include<stdio.h>
int main()
{
	int a[10] = { 1,2,3,4,5,6,7,8,9,0 };
	int x = sizeof(a);//等于40,4*10,一个整型元素四个字节,共10个
	printf("%d\n", x);
	int y = sizeof(a[0]);//等于4,大小为一个整型的大小.这里你也可以写成a[1].a[2].随便一个数组中的元素就行
	printf("%d", y);
	return 0;
}

2.4 一维数组在内存中的存储🔷

我们先定义一个整型数组并将它所有元素的地址打印出来:(%p是打印地址)

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

我们来研究一下数组中元素在内存中是怎么存放的:

在这里插入图片描述
我们知道数组在内存中存储时用的是16进制,所以这里我们来找一下这些地址的规律:(只看最后四个)

  • 第一个元素地址是F4E8,E8转换为10进制是232,第二个元素的EC转换为十进制为236,相差4.

  • 第二个元素十进制为236,第三个元素转换为10进制为240,也相差4.

  • 倒数第二个元素为08,最后一个元素为0C,C在十六进制是12的意思,这里8和12也相差4.

可以发现,每一个元素之间都相差四个字节,并且我们这里定义的数组是整型数组,每一个元素所占空间刚好也是四个字节,这刚好能说明数组中元素是连续存放的!

我们按照这个编译器打印出的地址再来画一个内存图理解一下:

在这里插入图片描述

我们之前提到过,一个整型占四个字节,四个字节拥有四个地址,这个整型的地址是第一个字节的地址,这里就说的通了! 数组确实是连续存放的

值得注意的是,不同电脑不同编译器在不同时刻为数组开辟的空间是不一样的,所以你的电脑的地址中不必和我一模一样,只需要查看每个元素之间是不是紧挨着的.



3. 二维数组🔶

3.1 二维数组的创建🔷

//数组创建
int arr[3][4];
char arr[3][5];
double arr[2][4];

和一维数组相似,只不过要多写一个方括号


3.2 二维数组的初始化🔷

有几种初始化的方式:

//数组初始化
int arr[3][4] = {1,2,3,4};//这样初始化代表第一行为1 2 3 4,而2,3行所有元素默认为0
int arr[3][4] = {{1,2},{4,5}};//这种加上大括号的表示第一行为1 2 0 0,第二行为4 5 0 0,第三行默认全部为0.
int arr[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略
  • 一种不加大括号的初始化方式就是挨着往后初始化,没有被初始化到的位置默认为0
  • 一种是加了大括号的初始化方式就是第一个大括号内代表第一行元素,没有被初始化到的位置,默认为0
  • 并且行可以省略但是列不行!

3.3 二维数组的使用🔷

假如我们想挨个打印二维数组中的内容,我们可以这样写:(二维数组的下标也是从0开始)

#include<stdio.h>
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} };
	printf("%d\n", arr[2][3]);//打印第三行第四列元素
	int i = 0;
	//行号
	for (i = 0; i < 4; i++)//最外层for循环代表行,i等于0就是第一行,进入第二个循环将第一行所有列打印出来再到第二行
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");//每打印完一列就换一次行
	}
	return 0;
}

我们就把每一个元素很好的打印出来了:
在这里插入图片描述

这里二维数组的使用与一维数组大同小异,直接往下走!


3.4 二维数组在内存中的存储🔷

这里和一维数组一样,我们先创建一个二维数组再将所有元素的地址打印出来:

#include <stdio.h>
int main()
{
 int arr[3][4];
 int i = 0;
 for(i=0; i<3; i++)
 {
 int j = 0;
 for(j=0; j<4; j++)
 {
 printf("&arr[%d][%d] = %p\n", i, j,&arr[i][j]);
 }
 }
 return 0;
}

在这里插入图片描述

和一维数组一样,二维数组每个元素之间相差也是四个字节,可见,二维数组在内存中的存放和我们看见打印出的矩阵有所不同,它每一行之间是相连的,而不是分开的.这里再画一个存储图理解一下

在这里插入图片描述

可以看见内存中真实的存储是这样的,我们可以把二维数组理解为存储数组的数组,你看每一个红方格里面存放了一个数组,实际上就是二维数组的第几行,然后一共有四行,代表这个数组存储了四个数组,被存储的数组每个数组元素为5个.


4. 数组的越界访问🔶

数组的下标是有范围限制的。
数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。
所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就
是正确的,所以程序员写代码时,应该自己检查越界问题

比如像下面这段代码,当i等于10时数组就越界了,但是你的编译器可能不会给你警告提醒你

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

5. 数组作为函数参数🔶

我们在写代码的时候常常会将数组作为参数传递给函数实现某些功能,这里我就总结一下一维数组和二维数组传参的方式:

  • 一维数组:
  1. void test(int a[])
  2. void test(int a[10])
  3. void test(int* a)//使用方法和上面一样
  • 二维数组
  1. void test(int a[3][5])
  2. void test(int a[][5])
  3. void test(int (*a)[5])
  4. void test(int** a)

我们举一个例子来说明一下数组传参有什么坑!


5.1 冒泡排序中数组传参的问题🔷

相信大家对冒泡排序已经不陌生了,我们下面就设计一个冒泡排序函数来讲数组中元素排成升序

#include <stdio.h>
void bubble_sort(int arr[])
{
 int sz = sizeof(arr)/sizeof(arr[0]);//这样对吗?
    int i = 0;
 for(i=0; i<sz-1; i++)
   {
        int j = 0;
        for(j=0; j<sz-i-1; j++)
       {
            if(arr[j] > arr[j+1])
           {
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
           }
       }
   }
}
int main()
{
    int arr[] = {3,1,7,5,8,9,0,2,4,6};
    bubble_sort(arr);//是否可以正常排序?
    int i = 0;
    for(i=0; i<sizeof(arr)/sizeof(arr[0]); i++)
   {
        printf("%d ", arr[i]);
   }
    return 0;
}

当我们运行查看结果时,会发现我们的升序并没有排好,当我们去调试程序时会发现:
在这里插入图片描述

我们之前说的求数组长度的式子,这里求出来和数组元素个数不符合.我们说数组名是首元素地址,所以这个地方我们将arr[ ]数组传过去时,实际上这个arr是一个指针,用来接受数组首元素地址的指针,所以当我们使用sizeof求arr的时候相当于是求了指针变量的大小,我们机器是64位,所以指针大小为8个字节,然而一个整型是4个字节,将它们相除就得到了2.

所以这个地方是错误的写法,我们将它修改一下:

void bubble_sort(int arr[], int sz)//参数接收数组元素个数
{
 int i = 0;
 for(i=0; i<sz-1; i++)
   {
        int j = 0;
        for(j=0; j<sz-i-1; j++)
       {
            if(arr[j] > arr[j+1])
           {
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
           }
       }
   }
}
int main()
{
    int arr[] = {3,1,7,5,8,9,0,2,4,6};
    int sz = sizeof(arr)/sizeof(arr[0]);
    bubble_sort(arr, sz);//是否可以正常排序?
    for(i=0; i<sz; i++)
   {
        printf("%d ", arr[i]);
   }
    return 0;
}

将数组的大小做为函数的参数传入函数中就可以解决这个问题了🫵🫵


5.2 数组名到底是什么?🔷

我们用一段打印地址的代码来为大家阐述:

#include <stdio.h>
int main()
{
    int arr[10] = {1,23,4,5};
 printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    printf("%d\n", *arr);
    //输出结果
    return 0;
}

在这里插入图片描述

我们可以发现,数组名和首元素地址是相同的,所以其实数组名就是首元素的地址,将数组名解引用就可以得到首元素.

但是,这里有两个特例中,数组名不是首元素地址:

  • 当数组名放在sizeof当中时,这时数组名代表整个数组的地址

  • 当数组名前面加一个取地址符号的,这时代表整个数组的地址


6. 总结

当我们有了数组相关知识之后,我们就可以尝试去写一些小程序了,比如经典的小游戏:三子棋和扫雷等.

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

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

相关文章

使用 spring 的 IoC 的实现账户的CRUD(2)双层实现

spring实现service和dao的数据的查找 dao层设置接口实现dao层的接口service设置接口通过注入dao层&#xff0c;来实现接口 //dao层的接口&#xff0c;定义了根据id查询的方法 public interface Accountdao {Account findByid(int id); }实现接口&#xff1a;实现了查询的方法 …

【模板】拓扑排序

import java.util.Scanner; import java.util.*;// 注意类名必须为 Main, 不要有任何 package xxx 信息 public class Main {public static void main(String[] args) {Scanner in new Scanner(System.in);int point in.nextInt();int side in.nextInt();int[][] arr new i…

MacOS下安装和配置Nginx

一、安装brew /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"按回车后&#xff0c;根据提示操作&#xff1a;输入镜像序号 --> 输入Y&#xff0c;回车等待brew安装完成即可。 在终端输入brew -v后&#xff0c;会提示…

【牛客刷题专栏】0x25:JZ24 反转链表(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录 前言问…

Jedis客户端和SpringDataRedis客户端

目录 3.Redis的Java客户端 3.1.Jedis客户端 3.1.1.快速入门 3.1.2.连接池 3.2.SpringDataRedis客户端 3.2.1.快速入门 3.2.2.自定义序列化 3.2.3.StringRedisTemplate 3.Redis的Java客户端 3.1.Jedis客户端 Jedis的官网地址&#xff1a; GitHub - redis/jedis: Redis…

单片机中时钟分析与快速读懂时序图的方法

目录 一、时钟电路 二、周期 三、时序 我们都知道在学校是通过铃声来控制所有班级的上下课时间&#xff0c;那个单片机是通过什么样的办法进行取指令&#xff0c;执行指令和其它操作的呢&#xff1f;在这里引入了一个时序的概念。 一、时钟电路 单片机时钟电路有三种方式…

LoadRunner的简单使用

目录 1、LoadRunner工具介绍 2、VUG的使用 3、Controller的使用 3.1、场景设计 3.2、场景运行及结果 4、Analysis的使用 1、LoadRunner工具介绍 Virtual User Generator&#xff1a;主要用来生成性能测试脚本Controller&#xff1a;创建测试场景&#xff0c;运行测试脚本、…

民用电力远程监控解决方案

民用电力远程监控解决方案 项目背景 随着我国城市现代化的飞速发展&#xff0c;城市配电系统的不断改造更新&#xff0c;信息化、网络化和智能化的快速发展&#xff0c;要求箱变安全稳定运行&#xff0c;出现故障能够及时排除保证快速供电。 但是&#xff0c;电力行业的监控…

如何挖掘闲置硬件资源的潜力-PrestoDB缓存加速实践小结

用户体验的追求是无限的&#xff0c;而成本是有限的&#xff0c;如何平衡&#xff1f; 用户体验很重要&#xff0c;降本也很重要。做技术的都知道&#xff0c;加机器堆资源可以解决绝大多数的用户觉得慢的问题&#xff0c;但要加钱。没什么用户体验是开发不了的&#xff0c;但…

阿里高P谈内卷,基础牢固才能破局,你的技术栈深度跟广度真的够么?

​ ​ 最近内卷严重&#xff0c;各种跳槽裁员&#xff0c;分享一套学习笔记 / 面试手册&#xff0c;准备跳槽的朋友可以好好刷一刷&#xff0c;还是挺有必要的&#xff0c;它几乎涵盖了所有的软件测试技术栈&#xff0c;非常珍贵&#xff0c;肝完进大厂&#xff01;妥妥的。相信…

PID算法(位置式pid算法和增量式pid算法)

这里写目录标题 PID算法介绍比例环节比例积分环节比例积分微分环节 位置式PID增量式PIDPID参数整定采样周期选择PID参数整定方法![请添加图片描述](https://img-blog.csdnimg.cn/849bf1672243484699b131b487f05a55.png)试凑法临界比例法一般调节法 PID算法介绍 PID 算法是闭环…

使用Process Monitor探测Windows系统高DPI缩放设置的注册表项

目录 1、在高显示比例下部分软件界面显示模糊问题 2、如何设置才能使得软件显示的清晰一些&#xff1f; 3、使用Process Monitor监测上述设置对应的注册表的操作 4、最后 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff…

分布式 04 nginx 的使用

01.Nginx可以理解成为一个代理运营商在计算机网络中。用户发送的请求在Nginx中处理&#xff0c;而后分配给相关的服务器 02.在Nginx文件中conf文件件&#xff0c;中修改nginx.conf文件 首先先去监听和获取请求&#xff0c;使用关键字server 这个是浏览器url中输入localhost时…

如何在本地部署运行ChatGLM-6B

在本篇技术博客中&#xff0c;将展示如何在本地获取运行代码和模型&#xff0c;并配置环境以及 Web GUI&#xff0c;最后通过 Gradio 的网页版 Demo 进行聊天。 官方介绍 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM)…

Flutter 自定义裁剪之圆形豁口/缺口

目录 Flutter自定义裁剪Flutter的自定义裁剪类CustomClipper裁剪的实际代码思路分析注意点完整代码总结如图所示,图中的圆形缺口,需要我们自定义裁剪,才能实现。 Flutter自定义裁剪 裁剪,我们想到的是剪刀,实际上,Flutter的裁剪原理,和我们现实物理世界的剪刀是一样的…

木夕的IC日记——Vim使用【一】

Vim使用日记【一】 Vim的运行方式进入Vim第一步&#xff1a;打开文件保存文件并退出Vim三种模式下能做哪些事命令模式编辑模式底行模式Visual Block功能 Vim的运行方式 作为Linux系统中最常用的文本编辑器&#xff0c;Vim体现了Linux“万物皆是文件”的设计哲学。通过Vim&…

flink集群安装部署

1.下载 官网下载&#xff1a;Downloads | Apache Flink 阿里网盘下载&#xff08;包含依赖包&#xff09;&#xff1a;阿里云盘分享 提取码&#xff1a;9bl2 2.解压 tar -zxvf flink-1.12.7-bin-scala_2.11.tgz -C ../opt/module 3.修改配置文件 cd flink-1.12.7/conf/ …

[C++]string的使用

目录 string的使用&#xff1a;&#xff1a; 1.string类介绍 2.string常用接口说明 string相关习题训练&#xff1a;&#xff1a; 1.仅仅反转字母 2.找字符串中第一个只出现一次的字符 3.字符串里面最后一个单词的长度 4.验证一个字符串是否是回文 5.字符串相加 6.翻转字符串…

[Dubbo] 重要接口与类 (三)

文章目录 1.dubbo的整体调用链路2.dubbo的源码整体设计3.重要接口和类 1.dubbo的整体调用链路 消费者通过Interface进行方法调用&#xff0c;统一交由消费者的Proxy处理&#xff08;Proxy通过ProxyFactory来进行代理对象的创建&#xff09; Proxy调用Filter模块&#xff0c;做…

linux中fork函数与vfork函数的区别

fork函数跟vfork函数一样能够创建进程&#xff0c;它们主要有两个区别 &#xff08;1&#xff09;区别一&#xff1a; vfork直接使用父进程存储空间&#xff0c;不拷贝。 &#xff08;2&#xff09;区别二&#xff1a; vfork保证子进程先运行&#xff0c;当子进程调用exit退…