数组判断子阵和的大小 c语言,C语言之数组

本文详细讲解了C语言中的数组概念,包括一维数组的定义与访问,以及数组初始化、可变长数组的使用、字符型数组和字符串操作。重点介绍了二维数组的应用,越界问题的防范,以及C99新增的数组特性。适合初学者理解数组在程序设计中的灵活运用。
摘要由CSDN通过智能技术生成

C语言之数组

——TechZone(Harris)

学完了前面几种基础语法之后,你可能会渐渐发现,现有的数据的记录方式,已经无法很好地解决我们接下来要解决的问题。比如有一天,老师找你计算一下全班同学的平均成绩。那么你就会开始思考如何存储全班的成绩。按照之前学习的知识,我们可以定义a1 a2 a3 ……。但是,这样未免也太麻烦了,如果要记录全省人民的身高数据呢?如果下个学期班里学生人数有变化呢?

你会发现,种种原因,导致了我们编写的程序很繁杂,不够灵活。那么这样,编程也就没有太大的必要了。好在,C语言提供了一种存储数据的方式,叫做数组。

数组,就是存放一堆同类型的数据的容器。比如刚刚的例子,我们要存储学生的成绩,这时候数组就可以大显神威了。

定义一维数组

定义一维数组的方法很简单,只需要指定元素的类型和存放的数量即可。

类型 数组名[元素个数]

比如:

int Score1[50];//定义一个叫Score1的整型数组,有50个元素

float Score2[30];//定义一个叫Score2的浮点型数组,有30个元素

double Score3[20];//定义一个叫Score3的双精度浮点型数组,有20个元素

char Str[10];//定义一个叫Str的字符型数组,有10个元素

数组一旦被定义,在其生命周期内,就不可能被改变(其实是在内存中开辟了一段连续的空间了)。

访问数组

访问数组的方法和定义有点类似,但是如果混淆了的话,那可就不是什么好事了。

数组名[下标]

方括号里面的,实际上是指的数组的下标,也可以叫索引。需要注意的是,下标的计数是从0开始的,最大的下标是(元素个数-1)。也就是说,如果我int Score[10]之后,那么我想访问第一个元素,就是这样Score[0],如果要访问最后一个元素,就是Score[9]。

之前看到过一个段子,大概意思是说程序员数数都喜欢从0开始数。如果你是刚刚接触编程,那么你也要开始习惯从0开始计数的这种思路。

其实,并不是C语言才开始有数组,FORTRAN语言就有数组了。但是下标从0开始计数这种方式,是从C语言才开始有的。当时开发C语言编译器的人们就想让编译器能够更加简单,如果从0开始,那么编译器实际上能够少做很多事情,于是就多了这么一个设定。随着计算机科学的发展,后面出现的优秀的语言也越来越多。但是我们所说的"C-Like”语言,也就是参照C语言来开发的语言,也都继承了C语言这一“优良传统”,因此就有了程序员数数是从0开始的这么一种说法。

讲到这里,想必大家也就会明白之前为什么我们在循环的时候,初始值都是设定为0的了。像这样:

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

{

...

}

而不是像这样写(当然也没错):

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

{

...

}

这就是因为,我们在使用循环的时候,经常会配合数组一起来使用,那么我们循环设置成和数组下标的计数方法一样,有利于我们使用数组。

还是回到我们最初的那个问题,存储班里面学生的成绩,然后计算出平均值:

//Example 01

#include

int main(void)

{

int s[10];//假定我们班上有10个人

int i;

double sum = 0;

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

{

printf("请输入第 %d 位同学的成绩:", i + 1);

scanf("%d", &s[i]);

sum += s[i];

}

printf("成绩录入完毕,该次考试的平均分是:%.2f\n", sum/10);

return 0;

}

程序实现如下:

//Consequence 01

请输入第 1 位同学的成绩:80

请输入第 2 位同学的成绩:90

请输入第 3 位同学的成绩:70

请输入第 4 位同学的成绩:66

请输入第 5 位同学的成绩:77

请输入第 6 位同学的成绩:54

请输入第 7 位同学的成绩:67

请输入第 8 位同学的成绩:86

请输入第 9 位同学的成绩:78

请输入第 10 位同学的成绩:65

成绩录入完毕,该次考试的平均分是:73.30

数组的初始化

在定义数组的时候同时对其各个元素进行赋值,称为数组的初始化。在刚刚的代码中,我们定义了数组,但却没有在定义的时候就初始化,而是在循环中进行赋值。那么初始化数组一般有下面几种方法:

将数组中所有的元素初始化为0,可以这么写:

int a[10] = {0};

如果要赋予不同的值,用逗号分开即可:

int a[5] = {1, 2, 3, 4, 5};

给部分元素赋值,剩下的自动初始化为0:

int a[10] = {1, 2 ,3};//剩下的全部为0

也可以偷懒只给出每个元素的值,让编译器自己判断数组长度:

int a[] = {1, 2, 3, 4, 5};

C99中增加了一种特性,指定元素进行赋值,剩下的自动初始化为0。也就是说,可以针对不连续的几个元素赋值:

int a[10] = { [3] = 3, [5] = 5, [8] = 8 };//编译的时候记得加上-std=c99选项

那可能你会说了,”你刚刚提到的一个问题还没解决呢!要是班里的人数变了怎么办呢?“

没错,我们现在就来解决下这个问题。

可变长数组

在C99标准推出之前,要求定义数组的时候,数组的维度必须是常量表达式或者const常量,但是C99标准中,支持了变量定义数组,那么,我们就可以将第一次的代码改成这样:

//Example 02

#include

int main(void)

{

int Member;

printf("请输入班级人数:");

scanf("%d", &Member);

int s[Member];//使用用户输入的值来确定数组的大小

int i;

float sum = 0;

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

{

printf("请输入第 %d 位同学的成绩:", i + 1);

scanf("%d", &s[i]);

sum += s[i];

}

printf("成绩录入完毕,该次考试的平均分是:%.2f\n", sum/Menber);

return 0;

}

这样,在开始存储成绩之前,先让使用者告诉程序班里有多少学生,该开辟多大的数组,然后就完美解决了人数变动的问题。

注意,这里的”可变长数组“是指的数组在程序运行的时候才确定长度,也就是说每一次运行都不一定一样。但是数组一旦被创建,在其生命周期内就不会再改变了,这是数组的根本特性。

但是,如果有的同学使用的是Visual Studio的话,是不支持C99的这个特性的(我也不知道为什么巨硬不支持,明明这么好的特性),那么就只能使用动态分配的方法来创建数组。放在这里来讲的话有些超纲,后面会讲到。

字符型数组

还记得之前说过,C语言是没有字符串这种类型的。那么C语言处理字符串有两种方法:字符串常量和字符型数组。字符串常量是指用双引号括起来的字符串,一旦确定下来就无法改变。一般我们会更多地倾向于使用更加灵活的字符型数组。这样,数组中的每一个元素表示一个字符,当然还要多一位来表示\0。

那么接下来就讲讲字符串的一些方法,因为字符串实在是太重要了。

获取字符串的长度

计算字符串的长度使用strlen函数(这是长度,不是尺寸),这个函数包含在string.h中

#include

...

size_t strlen ( const char * str );

这个方法是不包含字符串末尾的\0的。且看下面的例子:

//Example 03

#include

#include

int main(void)

{

char str[] = "I love Clang!";

printf("sizeof str = %d\n", sizeof(str));

printf("strlen str = %u\n", strlen(str));

return 0;

}

运行结果如下:

//Consequence 03

sizeof str = 14

strlen str = 13

除了验证不包含\0以外,我们还可以看到,strlen函数返回的是size_t而不是int。size_t被定义在stddef.h中,实际上就是无符号整型。

复制字符串

估计在第一次见到这个词的时候,你的大脑浮现出来的就是使用赋值符号=,但是,这是错的……

字符串的复制应该使用strcpy和strncpy来实现。

#include

...

char *strcpy (char *dest, const char *src);

char *strncpy (char *dest, const char *src, size_t n);

不多废话,且看下面的例子:

//Example 04

#include

#include

int main(void)

{

char str1[] = "Original String";

char str2[] = "New String";

char str3[100];

strcpy(str1, str2);

strcpy(str3, "Successfully Copied");

printf("\

str1: %s\n\

str2: %s\n\

str3: %s\n", \

str1, str2, str3);

return 0;

}

运行结果如下:

//Consequence 04

str1: New String

str2: New String

str3: Successfully Copied

但是其实这个程序是有缺陷的。

我们可以看到,两个数组的长度其实不一样,我们现在是把短的复制到长的里面,那么不会有问题。如果上面的str1和str2对调一下,那么就极有可能出问题,这就是我们等会儿要讲的数组越界问题。

那么如何解决复制时的这个隐式bug呢?

使用strncpy方法来复制

如果超出的字符不是很多,那么程序有可能能够成功地运行。但是如果两者悬殊的话,那编译运行之后,程序会报Segmentation fault。

因此在复制的时候,我们应该确保不越界,在复制之后不溢出。那么使用strncpy函数,由于增加了一个参数来指定复制的字符个数,我们在编写代码的时候就可以规避这样的问题。

举个例子:

//Example 05

#include

#include

int main(void)

{

char str1[] = "TechZone was made by HarrisWilde";

char str2[40];

strncpy(str2, str1, 8);

str2[8] = '\0';

printf("%s\n", str2);

return 0;

}

结果如下:

//Consequence 05

TechZone

有一个地方要格外小心,strncpy函数并不会在字符串的末尾添加\0,因此在使用的时候要注意加上。

连接字符串

如果你想把一个字符串拼接到另一个后面的话,就可以使用strcat和strncat两个函数来实现。

#include

...

char *strcat (char *dest, const char *src);

char *strncat (char *dest, const char *src, size_t n);

可以看到,这个函数的用法和上面复制字符串的用法完全相同,strncat也就是比strcat多了一个指定复制长度的参数罢了。

需要注意的是,这个函数会自动在末尾追加一个\0,这和复制不一样,要特别注意区分。

比较字符串

比较两个字符串,也和上面的一样,有两个类似的函数,strcmp和strncmp。

#include

...

char *strcmp (char *dest, const char *src);

char *strncmp (char *dest, const char *src, size_t n);

采用这套函数来比较两个字符串是否相同的时候,如果两个字符串完全一致,那么返回的值为0。这个函数的原理是,从第一个字符开始,依次对比两个字符串中每个字符的ASCII,如果第一个字符串的ASCII小于第二个字符串对应的字符,那么返回一个小于0的数值(通常是-1),如果大于,那就会返回一个大于0的值(通常是1)。

strncmp则是增加了一个参数,可以用来仅比较前面n个元素。

//Example 06

#include

#include

int main(void)

{

char str1[10] = "TechZone";

char str2[20] = "TechZone";

if (!strcmp(str1, str2))

{

printf("Same!\n");

}

else

{

printf("Different!\n");

}

return 0;

}

运行结果为:

//Consequence 06

Same!

多维数组

有时候,使用数组来存储还是不够方便,比如,老师让你做一个全班全部科目的成绩的分析。如果利用我们刚刚所学习的数组知识,你可能会这么写:

//Example 07

#include

int main(void)

{

int chinese[50];

int math[50];

int English[50];

int science[50];

...

}

但是如果我们使用二维数组的话,那么只需要定义一次就行了。

假设我们有6科。

那么就这样:

//Example 07 V2

#include

int main(void)

{

int score[6][50];

...

}

这其实就像一个表格一样,二维数组通常也被称为矩阵(matrix),将二维数组写成行和列的表示形式,可以形象地帮我们解决一些问题。

访问二维数组也和普通的数组一样,也是从0开始计数的,只不过下标随着维度的变化会增加罢了(比如二维数组就有2个下标)。

二维数组的初始化

二维数组在内存中是线性存放的,因此可以将所有的数据写在一个大括号内:

int a[2][3] = {1, 2, 3, 4, 5, 6};

这样就是先将第一行的三个元素初始化,然后再初始化第二行的元素。

为了更直观地表达我们可以这么写:

int a[2][3] = {

{1, 2, 3},

{4, 5, 6}

};

二维数组也可以仅对部分元素赋值:

int a[2][3] = {{1}, {4}};

这样写只是对各行的第一列元素赋值,其余的全部为0.

如果希望全部为0,那么可以这么写:

int a[2][3] = {0};

C99中增加的指定赋值的特性,这里也可以适用。其余未被操作的元素为0。

int a[2][3] = {[0][0] = 1, [1][2] = 6};

二维数组也可以偷懒,但是只有第一维度的元素个数可以不写,其他的都要写上:

int a[][3] = {

{1, 2, 3},

{4, 5, 6}

};

数组越界

我们刚刚说过了,我们在写程序的时候,尽量要把越界的情况通过代码的努力来规避。那么,可能有的小伙伴比较感兴趣,如果越界了,会发生什么呢?

那好,咱们就来试试。

//Example 08

#include

void f();

int main(void)

{

f();

return 0;

}

void f()

{

int a[10];

a[10] = 0;//这里我们写到了一个不存在的下标里面

}

我们来跑一下这个程序。

笔者使用的Visual Studio 2019给出了以下的错误提示:

//Consequence 08

Run-Time Check Failure #2 - Stack around the variable 'a' was corrupted.

它发现了我在写入一个错误的地址。并且还给了我两个warning:

警告 C6201 索引“10”超出了“0”至“9”的有效范围(对于可能在堆栈中分配的缓冲区“a”)。

警告 C6386 写入到“a”时缓冲区溢出: 可写大小为“40”个字节,但可能写入了“44”个字节。

如果我们像普通程序员一样,不管代码warning,直接强制执行,试试会发生什么。

为了更直观体现,我们把代码改成这样:

//Example 08

#include

void f();

int main(void)

{

f();

printf("Here\n");//我们加了这句,如果函数正常执行完毕了,就可以看到这个语句的输出

return 0;

}

void f()

{

int a[10];

a[10] = 0;

}

还是出现了这句:

Run-Time Check Failure #2 - Stack around the variable 'a' was corrupted.

控制台上面没有看到Here的输出,说明函数还没有执行完,程序就已经崩溃了,根本没办法执行到输出。

但是,为什么编译器没有给我error,而是给了我warning呢?

有的编译器可能连warning都没有。

实际上,我们在对a[10]写入的时候,其实是成功了的。只不过我们把a[10]写在了一个不该写的地方(实际上就是这段数组内存的后面),干扰到了其他东西的运行,程序就有可能会崩溃。如果后面的内存为空或者是没有被回收的垃圾内存,那么就没关系,但是如果是有用的内存,出问题就很正常了。

有时候我们写了一个程序,可能这次运行没问题,下一次运行就出错,或者是在我的电脑上可以,在你的电脑上就不行了等等,都有可能是数组越界,或者是我们后面要学的指针出错了。我们作为创造代码的人,有责任通过代码上的设计,来规避这样的问题,避免程序的崩溃。

长度为0的数组?

有的同学可能会异想天开,说,我可不可以定义一个长度为0的数组呢?

类似于这样:

int a[0];

答案是,完全没问题!

不信的话可以去试试,编译可以通过的,只不过这样的数组不存在任何意义,因为没有符合要求的下标。我们说,最大的下标就是元素个数-1,那么

math?formula=0-1%3D-1,-1显然不是一个合法的下标。所以这样的操作可行,但是没有任何意义。

好了,本节内容就到这里了,希望你能够从中有所收获哦!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值