jjzjj

【C语言功法手册】第四话· 快乐数组小课堂(可做小游戏)

kikokingzz 2024-04-19 原文

💃🕺kiko小剧场


kiko:各位真的是好久不见,【百炼成神】专栏终于要回归了!

小明:真的是拖欠了好久哦🤯···我都不打算订阅了的说。

kiko:呜呜呜😭,我保证,以后一周至少两更!一周两更!

小明:行叭!那就赶紧开始叭~

 🌟🌟往期必看🌟🌟

【C语言百炼成神】功法一·常量与变量

【C语言百炼成神】功法二·语句与选择结构

【C语言百炼成神】功法三·循环结构

目录

🍺知识点1:数组

🍯1.1 一维数组

🥝1.一维数组的定义

🥝2.一维数组的初始化

🥝3.一维数组元素的引用

📜典型例题1. 将一个数组逆序输出。

🥝4.一维数组的地址

🍯1.2 二维数组

🥝1.二维数组的定义

🥝2.二维数组的地址

🥝3.二维数组的初始化

🥝4.二维数组的引用

📜典型例题1. 将一个3*3的二维数组进行转置(行列元素互换)

🍺知识点1:数组


Q1:什么是数组?

A1:到目前为止,我们所使用的的变量都只能存储一个数值。假如我要记录一些同学的考试分数,如果采用变量,我可能需要定义多个变量:

double score_A=99.7, score_B=65, score_c=88.5;

而数组可以用来存储一组数据类型相同的数,通过定义一个数组变量就可以处理一批类型相同的相关数据:

double score[]={99.7, 65, 88.5};

因此通过上述两类代码的对比可以发现,使用数组可以在很大程度上减少定义的变量数目,原来需要定义3个变量,使用数组后,我们仅使用score作为数组名,改变下标值,就可以表示这些变量了,比如:

printf("%d",socre[0]);//——>输出99.7
printf("%d",socre[1]);//——>输出65
printf("%d",socre[2]);//——>输出88.5

🍯1.1 一维数组


🥝1.一维数组的定义

Q1:什么是一维数组?

A1:一维数组是使用同一个数组名存储一组数据类型相同的数据,用索引或下标区分数组中的不同元素;一维数组的一般形式如下:

type_t  arr_name  [const_n]
  • type_t 是数组的元素类型。
  • arr_name 是数组名,需要满足标识符的命名规则。
  • const_n 是一个常量表达式,用来指定数组的大小。

因此我们通常有两种方式来定义一维数组:

//定义方式1
int age[5];
 
//定义方式2
#define NUM 5
int age[NUM];

关于上述数组定义的说明:

  1. 上面的两种形式都正确定义了名称为"age"的整型数组,该数组含有5个整型数据,这5个数据可以用不同的下标表示:age[0]、age[1]、age[2]、age[3]、age[4]
  2. 数组的下标是从0开始的,对于age[5]数组来说,数组元素下标的范围是0~4,而不是1~5,大于4的下标会产生数组溢出错误,下标更不能出现负数。
  3. 定义数组时,age[5]括号中的数字5表示的是定义数组中元素的总数;使用数组时,age[2]=1;这条赋值语句中的方括号内的数值2是数组下标,表示的是数组中第3个元素。
  4. 定义数组元素数目时,要求括号内一定要是常量,而不能是变量;在数组定义后,使用该数组的元素时,下标可以是常量、变量或表达式。
//定义数组时
int num=5;
double age[num];//×——>定义数组元素数目时使用了变量num

//使用数组时(数组已正确定义)
int main()
{
	#define NUM  5
	int age[NUM];//用符号常量来定义数组元素个数
	int n = 0;
	age[n] = 10;//使用数组时可以用变量
	age[n + 1] = 20;
	printf("age[0]=%d,age[1]=%d", age[n], age[n + 1]);
}

PS:关于常量与变量的更多细节和区分请见:【C语言百炼成神】功法一·常量与变量


🥝2.一维数组的初始化

初始化数组是指在创建数组的同时给数组赋值,初始化数组的方式和初始化变量时的方式相同。

(1)常规的初始化

int age[5]={17, 20, 18, 30, 45};//定义整型数组,同时初始化age数组的5个元素

(2)省略的初始化

int age[ ]={17, 20, 18, 30, 45};

这里的省略指的是在定义数组时省略数组元素的个数,即改为 int age[ ] ={··}这种形式。之所以可以省略"[ ]"中的数组,是因为"{ }"中是每个数组元素的初值,即{17,20,18,30,45}是数组age中各个元素的初值,相当于变向告诉了我们数组age中有5个元素,所以可以省略"[ ]"中的5。

kiko🎃:为了大家可以更好地理解这一种初始化的方式,我们就来举一个例子,大家可以猜猜下面这段代码的输出结果分别是什么呢?

char arr4[]="abcdef";      
printf("%d\n",sizeof(arr4)); 
printf("%d\n",strlen(arr4)); 

之所以会出现这样的结果是因为字符串尾部的隐含了结束标志\0,而sizeofstrlen对于尾部\0的实际计算方法却各不相同:

  • sizeof运算符sizeof求的是字符串所在内存中的长度,所以它是加上最最后的结束标志'\0'的。
  • strlen函数strlen 测量的是字符的实际长度,以'\0' 结束,strlen计算的结果是不加最后的结束标志'\0'。

kiko🎃:因此sizeof计算出来的结果为7,strlen计算出来的结果为6。

(3)不完全初始化

int arr[10]={1,2,3};    //不完全初始化,剩下的元素默认初始化为0
char arr2[5]={'a',98};  //不完全初始化,剩下的元素默认为3个\0  字符里98相当于'b'
char arr5[5]={'a','b'}; //arr2与arr5相同
char arr3[5]="ab";      //这里“ab”里隐含了一个\0,因此剩余元素默认给了2个\0

不完全初始化的情况就是诸如上述这些,编译器仅对提供了的元素数值进行初始化,而其余元素编译器会为其自动进行初始化。


🥝3.一维数组元素的引用

数组的特点是多个数据使用同一个变量名,利用下标引用不同数据,对此我们就需要引入一个运算符[ ] —— 下标引用运算符。

Q1:什么是下标引用运算符?

A1:下标引用运算符可以获取数组中单独的元素,它需要两个操作数。在最简单的情况下,一个操作数是一个数组名称,而另一个操作数是一个整数,例如:

age[2];//操作数age是数组名称,操作数2是一个整数。

通常情况我们可以使用循环结构控制数组下标值,进而访问不同的数组元素,这也是最常见的一种数组引用的实际案例。

#include<stdio.h>
int main()
{
    char arr[] = "abcdef";//[a][b][c][d][e][f][\0]
    for (int i = 0; i < strlen(arr); i++)//strlen默认返回一个无符号整型
    {
        printf("%c\n", arr[i]);
    }
    return 0;
}

📜典型例题1. 将一个数组逆序输出。

满足如下要求:原数组为[1,2,3,4,5],逆序输出为[5,4,3,2,1]。

kiko🎃:本题的本质其实就是将数组的首尾元素进行互换,中间使用一个tmp变量作为交换,唯一需要注意的点就是在进行交换操作时for循环的判断条件,这边推荐各位可以用笔算一下,不管是奇数个元素还是偶数个元素都是这个判断条件!

int main()
{
    int arr[N] = { 0 };
    int i = 0;
    for (i = 0; i < N; i++)
    {
        scanf("%d", &arr[i]);
    }
    printf("原数组为:");
    for (i = 0; i < N; i++)
    {
        printf("->%d", arr[i]);
    }
    int tmp;
    for (i = 0; i < N/2; i++)
    {
        tmp = arr[i];
        arr[i] = arr[N - 1 - i];
        arr[N - 1 - i] = tmp;
    }
    printf("\n转置后数组为:");
    for (i = 0; i < N; i++)
    {
        printf("->%d", arr[i]);
    }
}


🥝4.一维数组的地址

数组的地址在内存中占据一块连续的存储区域,这一特点对于一维数组、二维数组、多维数组都一样适用。为了更好地进一步了解数组地址的细节,我们先来看一个典型案例:

//案例1:打印数组中的各元素地址
#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf("&arr[%d]=%p\n", i, &arr[i]);
    }
    printf("arr的首地址是=%p\n", &arr);
    return 0;
}

通过上述的输出结果我们不难发现数组地址的两个特点:

  • 数组在内存中是连续存放的:arr[1]的地址就是在arr[0]的地址基础上加上sizeof(int)个字节,即4个字节;同理arr[3]的地址就是在arr[0]的地址基础上加上3*sizeof(int)个字节,即3*4=12个字节。所以对于数组,我们只要知道了数组首地址,就可以根据偏移量计算出待求数组元素的地址。
  • 数组名就是数组的首地址,比如我们要输出数组的首地址,就可以使用下面的方式:
printf("arr的首地址是=%p\n", &arr);//数组名代表了数组首地址;&arr相当于&arr[0]

🍯1.2 二维数组


对于处理n个学生单门课的成绩我们可以使用一维数组来解决,但如果我们现在要处理n个学生n门课的成绩时该怎么办呢?这时我们有两个办法可以进行解决:

  • 方法一:使用n个一维数组,每个一维数组包含n个元素。
  • 方法二:直接使用一个n*n的二维数组。

kiko🎃:显然方法二更加简单直接,通过这个例子我们便可以开始进行二维数组的学习啦!


🥝1.二维数组的定义

二维数组定义的一般形式为:

类型说明符  数组名  [常量表达式][常量表达式];

因此我们可以按照上面的形式先来定义一个二维数组:

int a[3][4]; //定义数组a为3行4列的数组
a[0][0]a[0][1]a[0][2]a[0][3]
a[1][0]a[1][1]a[1][2]a[1][3]
a[2][0]a[2][1]a[2][2]a[2][3]

由上图二维数组的存储图可见,如果定义了数组a[3][4],则元素下标的变化范围为:

  • 行号范围是0~2
  • 列号范围是0~3

🥝2.二维数组的地址

数组元素在内存中占用一块连续的存储区域,一维数组的内存地址是按照下标的顺序排列存储的,如下图所示。

int a[3][4]; //定义数组a为3行4列的数组

二维数组的存储方式则是按行存储的,每个整型元素占sizeof(int)个字节,即4个字节。先依次保存第一行所有元素,再依次保存第二行所有元素,···,直到所有行元素全部保存完毕。

a[0][0]a[0][1]

a[0][2]

a[1][0]·····

Q1:已知a[0][0]在内存中的地址,a[1][0]的地址是多少呢?

A1:二维数组的计算方法其实与一维数组大同小异,也是通过计算首地址+偏移量来计算最终结果的,其计算方法如下:

a[1][1]的地址=a[0][0]地址+4*4字节

4*4字节=(第1行*3列+第2行*1列)*单个元素所占字节

对此我们同样可以举一个例子来进行验证:

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

通过这个输出我们也可以明显的发现a[1][0]的存储地址就位于a[0][3]之后,这也间接证明了二维数组的存储方式是先存储完一行元素,再紧接着存储后边一行元素。


🥝3.二维数组的初始化

二维数组的初始化原理同一维数组相同,因此我也将其分为以下三类:

(1)常见的初始化

int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };

由于二维数组在内存中是按照线性顺序存储的,因此行括号可以省去,进而就可以这样定义:

int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };

(2)省略的初始化

int a[][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
or
int a[][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };

我们可以对行进行省略,但是不能对列进行省略,例如上述程序就可将[  ]中的3省略,但是列数[4]不能省去。

Q1:为什么可以省去行数,不能省去列数呢?

A1:这是因为编译器会根据所赋数值的个数以及数组的列数,自动计算出数组的行数;例如上边告知编译器二维数组的列数为4,所附数值的个数为12个,那么它就可以按每1行4列来配置元素,自然配置完12个元素后就可以计算得出行数为3。

但如果告诉编译器行数,不告诉列数的话,即定义为a[3][ ],编译器可不想我们人类这么聪明,在脑海里进行除法,因为编译器当时是不知道赋值个数有多少,因此它就不知道在一行可以分配多少个元素,所以会产生报错的情况!

(3)不完全初始化

//情况1:不包含{}
int arr1[3][4]={1,2,3,4,5,6,7};//不完全初始化--后面补零

这种不完全初始化方式中没有使用{ },因此在arr1中,编译器会按顺序将这些元素赋值到二维数组中,也就是从a[0][0]开始一直赋值到a[1][2],二维数组中剩下的位置补0。

//情况2:包含{}
int arr2[3][4]={{1,2},{3,4},{5,6}};
int arr3[][4]={{1,2},{3,4},{5,6}};

在这种情况下,由于在定义二维数组的过程中使用了{ }进而确定了每一行的元素个数,因此将按{ }确定的格式进行赋值,在二维数组中剩下的位置补0。


🥝4.二维数组的引用

二维数组元素的操作和一维数组基本相同,通常我们会使用双重循环的方式来遍历数组元素,用外层循环控制数组的行标,用内层循环控制数组的列标,通常的引用方式如下:

···
for (i = 0; i < 3; i++)        //控制行标
{
    for (j = 0; j < 4; j++)    //控制列标
    {
        arr[i][j] = i + j;
    }
}

📜典型例题1. 将一个3*3的二维数组进行转置(行列元素互换)

kiko🎃:这一道例题用极为基础的语言来进行二维数组的重复,使用双层循环来进行二维数组的调取,同理如果是三维数组,就使用三层循环,希望大家可以简单处理一下这道题,各位可以把自己的题解发在评论区哦!后期可能会加入到本文中滴!

#include<stdio.h>
#define LIN 2 //使用宏常量是为了后期可以方便更改矩阵的大小
#define COL 2
int main()
{
	int arr1[LIN][COL] = { 0 };
	int arr2[LIN][COL] = { 0 };
	int i = 0;
	printf("请输入2*2数组的元素:\n");
	for (i = 0; i < LIN; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			scanf("%d", &arr1[i][j]);//输入2*2的数组
		}
	}
	for (i = 0; i < LIN; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			printf("%d\t", arr1[i][j]);//打印一遍输入的2*2数组
		}
		printf("\n");
	}
	for (i = 0; i < LIN; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			arr2[j][i] = arr1[i][j];//进行数组转置
		}
	}
	printf("转置后的数组为:\n");
	for (i = 0; i < LIN; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			printf("%d\t", arr2[i][j]);//输出转置后的数组
		}
		printf("\n");
	}
}

有关【C语言功法手册】第四话· 快乐数组小课堂(可做小游戏)的更多相关文章

  1. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  2. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  3. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  4. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

  5. ruby - 我需要从 facebook 游戏中抓取数据——使用 ruby - 2

    修改(澄清问题)我已经花了几天时间试图弄清楚如何从Facebook游戏中抓取特定信息;但是,我遇到了一堵又一堵砖墙。据我所知,主要问题如下。我可以使用Chrome的检查元素工具手动查找我需要的html-它似乎位于iframe中。但是,当我尝试抓取该iframe时,它​​是空的(属性除外):如果我使用浏览器的“查看页面源代码”工具,这与我看到的输出相同。我不明白为什么我看不到iframe中的数据。答案不是它是由AJAX之后添加的。(我知道这既是因为“查看页面源代码”可以读取Ajax添加的数据,也是因为我有b/c我一直等到我可以看到数据页面之后才抓取它,但它仍然不存在)。发生这种情况是因为

  6. ruby - 如何保持我不常用的编程语言技能 - 2

    关闭。这个问题是off-topic.它目前不接受答案。想改进这个问题吗?Updatethequestion所以它是on-topic用于堆栈溢出。关闭11年前。Improvethisquestion我不经常使用ruby​​-通常它加起来相当于每两个月或更长时间编写一次脚本。我的大部分编程都是使用C++进行的,这与ruby​​有很大不同。由于我与ruby​​之间的差距如此之大,我总是忘记语言的基本方面(比如解析文本文件和其他简单的东西)。我想每天练习一些基本的东西,我想知道是否有一些我可以订阅的网站,并且会向我发送当天的Ruby问题或类似的东西。有人知道这样的站点/Internet服务吗?

  7. ruby-on-rails - 如果特定语言环境中缺少翻译,如何配置 i18n 以使用 en 语言环境? - 2

    如果特定语言环境中缺少翻译,如何配置i18n以使用en语言环境翻译?当前已插入翻译缺失消息。我正在使用RoR3.1。 最佳答案 找到相似的question这里是答案:#application.rb#railswillfallbacktoconfig.i18n.default_localetranslationconfig.i18n.fallbacks=true#railswillfallbacktoen,nomatterwhatissetasconfig.i18n.default_localeconfig.i18n.fallback

  8. python - Ruby 或 Python 的 3d 游戏引擎? - 2

    关闭。这个问题不符合StackOverflowguidelines.它目前不接受答案。要求我们推荐或查找工具、库或最喜欢的场外资源的问题对于StackOverflow来说是偏离主题的,因为它们往往会吸引自以为是的答案和垃圾邮件。相反,describetheproblem以及迄今为止为解决该问题所做的工作。关闭9年前。Improvethisquestion是否有适用于这些的3d游戏引擎?

  9. ruby-on-rails - 如何通过 URL 更改语言环境? - 2

    在我的双语Rails4应用程序中,我有一个像这样的LocalesController:classLocalesController用户可以通过此表单更改其语言环境:deflocale_switcherform_tagurl_for(:controller=>'locales',:action=>'change_locale'),:method=>'get',:id=>'locale_switcher'doselect_tag'set_locale',options_for_select(LANGUAGES,I18n.locale.to_s)end这有效。但是,目前用户无法通过URL更改

  10. ruby - 一种语言如何被自身解释(如 Rubinius)? - 2

    我使用Ruby编程已经有一段时间了,现在只使用Ruby的标准MRI实现,但我一直对我经常听到的其他实现感到好奇。前几天我在读有关Rubinius的文章,这是一个用Ruby编写的Ruby解释器。我试着在不同的地方查找它,但我很难弄清楚这样的东西到底是如何工作的。我在编译器或语言编写方面从来没有太多经验,但我真的很想弄明白。一门语言究竟如何才能被自己解释?编译中是否有一个我不明白这有意义的基本步骤?有人可以像我是个白痴一样向我解释这个吗(因为无论如何这都不会太离谱) 最佳答案 它比你想象的要简单。Rubinius并非100%用Ruby编

随机推荐