c语言学习--数组
Posted 庸人冲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c语言学习--数组相关的知识,希望对你有一定的参考价值。
文章目录
一维数组
数组是由数据类型相同的一系列元素组成。
一维数组的创建和初始化
数组的创建
type_t arr_name[const_int_n];
float f[100]; // 含有100个float类型元素的数组。
char c[200]; // 含有200个char类型元素的数组。
int i[250]; // 含有250个int类型元素的数组。
type_t
,表示数组内元素的数据类型。arr_name
, 表示数组名。const_int_n
,表示数组大小,必须是整型常量表达式。
变长数组
C99之前的标准前,创建数组时只能在方括号[]
中使用整型常量表达式。注意:sizeof
表达式被视为整型常量,#define
定义的标识符常量也是常量,而const
修饰的常变量在C语言中被视为具有常属性的变量。
在C99中引入了变长数组的概念,允许变量出现在[]
。(前提编译器支持C99)
#define NUM 10
const int a = 1;
int b = 1;
int arr[10]; // 合法
int arr1[5 * 2]; // 合法
int arr2[sizeof(int) * 1]; // 合法
int arr3[NUM]; // 合法
int arr6[-1]; // 不合法,数组大小必须大于0
int arr7[1.5]; // 不合法,数组
int arr4[a]; // C99前不支持
int arr5[b]; // C99前不支持
数组的初始化
数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
float f[3] = {1.0,2.0,3.0};
// 字符数组初始化的两种方式
char arr2[5] = {'a','b','c','d'};
char arr3[5] = "abcd"; // 字符串字面量默认尾部添加\\0,\\0占有位置
...
不完全初始化
当初始化的元素个数小于数组大小时,未被赋值的元素默认初始化为0。
int arr[10] = {1,2,3}; //不完全初始化
char arr[5] = {'a','b','c'};
char arr[5] = "abc";
字符数组内赋整型
- 给字符数组内赋值数字整型,如果在ASCII码值范围内,则会被解析成ASCII值对应的字符。
char arr[3] = {'a',98,'c'};
不指定元素个数
如果在创建数组时,不指定元素个数,则必须初始化,数组的大小由初始化的元素个数决定。
int arr[] = { 1,2,3 };
char arr1[] = { 'a','b','c' };
char arr2[] = "abc"; // 默认添加\\0
arr1
和arr2
同样初始化为abc
,但是元素的个数却不相同,从调试结果看出 arr2
默认在末尾添加了一个\\0
, 这也是双引号引起的字符串字面量的特点。
从输出结果可以看到,以字符串格式打印两个数组的内容,arr1
数组没有\\0
结束标志,所以在打印完abc后会继续打印随机值,直到遇到\\0
结束。
再对比strlen()
函数和sizeof
操作符的结果,可以看出sizeof
计算的数组所占空间的大小,初始化为3个元素则占3个byte,而strlen()
计算字符串长度时是以\\0
为结束标志,并且不统计\\0
。
数组未初始化
如果数组未初始化,直接访问数组获得的值会是内存相应位置上的现有值。(系统不同,输出的结果不同)
int main()
{
int arr[10];
int i = 0;
printf("%2s%14s\\n",'i','arr[i]');
for(i = 0;i < 10;i++)
{
printf("%2d%14d\\n",i,arr[i]);
}
return 0;
}
上面提到数组属于自动存储类别,指的是这些数组在函数内部声明,且声明时未使用static关键字。
不同的存储类别由不同的属性,对于一些其他存储类别的变量和数组。如果在声明时未初始化、编译器会自动把它们设置未0。
指定初始化器
指定初始化器是C99中新增的一个特性,利用该特性可以指定数组中哪个元素被初始化。
在C99之前,如果只想初始化一个下标非0的元素,必须要将它之前的元素全部初始化。
// 例如初始化下标为2个元素
int arr[5] = {0,0,3};
而在C99中规定,可以在初始化的{}
中使用带[]
的下标,来指明要初始化数组中的哪个元素。
int arr[5] = { [2] = 3 };
在使用指定初始化器时还应该注意两点
指定初始化器
之前如果有未初始化的元素,而之后也有元素,那么之后的元素将按照指定初始化器的下标往后继续排列。指定初始化器
可以覆盖前面已经被初始化元素的值。
int main()
{
int arr[10] = {1,2,[4]=5,6,7,8,9,10,[0]=3};
return 0;
}
从调试信息可以看到,arr[2]
、arr[3]
未初始化,指定初始化器[4]
后面的值,从arr[4]
往后排列,而arr[0]
的指定初始化器[0]
覆盖了。
一维数组的使用
要使用数组中的元素,需要通过[]
下标引用操作符,并在其中指定数组的下标来访问元素,数组的下标从0开始,到数组大小 - 1 结束。
在之前我们简单的学习过指针变量和解引用操作符*
,而其实数组变量和下标引用操作符[]
和它们很相似,数组的变量名是数组第一个元素的地址,而下标引用操作符[]
相当于*(数组名 + 下标)
通过解引用找到对应下标的元素。
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d %d", arr[0], arr[9]);
return 0;
}
数组元素赋值
- 声明数组后,如果未初始化,可以使用循环遍历数组来给数组中的每个元素赋值。
int main()
{
int arr[10];
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for(i = 0;i<sz;i++)
{
arr[i] = i;
}
return 0;
}
- 而如果想获取数组中每个元素的值也可以通过这种方式。
int main()
{
int arr[10];
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
// 赋值
for (i = 0; i < sz; i++)
{
arr[i] = i;
}
// 获取
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]); // 将循环变量作为数组的下标
}
return 0;
}
- 字符数组可以使用
%s
以字符串格式打印数组。
int main()
{
char arr[] = "abcdefg";
printf("%s\\n",arr);
return 0;
}
注意:C语言中不允许把数组作为一个单元赋给另外一个数组,初始化以外也不允许使用花括号{}
的形式来赋值。
int main()
{
int arr1[5] = {1,2,3,4,5};
int arr2[5];
arr2 = arr1; // 错误
arr2[5] = arr1[5]; // 下标越界
arr2[5] = {1,2,3,4,5} // 错误
return 0;
// 创建数组时[count_num] 指的是元素个数
// 而在使用时[num] 指的是下标号
}
下标越界
下标越界是指,在访问数组元素时,指定数组的下标是大于等于数组元素的个数,在给数组元素赋值时,赋予值的个数超过数组元素的个数。
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
for(i = 0;i <= sizeof(arr) / sizeof(arr[0]);i++ )
{
printf("%d ", arr[i]);
}
return 0;
}
可以看到使用了越界的下标虽然编译器未报错,但是打印出来的值内存当前位置上存在的值。因为在C标准中,使用越界下标的结果是未定义的。
为什么这么明显的错误,编译器却不报错呢?
因为如果不检查边界,C程序可以运行的更快,编译器没有必要捕获所有的下标错误,因为在程序运行前,数组下标值中可能尚未确定。如果了安全起见,编译器就必须在运行时添加额外代码检查数组的每个下标值,但是这会影响程序的运行速度。
int main()
{
int arr[5] = {1,2,3,4,5,6};
int i = 0;
for(i = 0; i < sizeof(arr) / sizeof(arr[0]);i++)
{
printf("%d ",arr[i]);
}
return 0;
}
一维数组在内存中的存储
数组元素在内存中的存储空间是连续的,我们可以通过遍历数组来打印每个元素的地址。
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for(i = 0;i < sz; i++)
{
printf("&arr[%d] == %p",i,&arr[i]);
}
return 0;
}
结果可以看出地址的显示格式为16进制,也就是1,2,3,4,5,6,7,8,9,a,b,c,d,e,f。通过简单的计算可以发现,每个地址间相差为4,而int
类型的变量在内存中所占空间也是4个字节,所以可以确定,数组的存放是连续的,数组的下标和地址都是从前往后连续增长的。
二维数组
二维数组本质上是以数组作为数组元素的数组,即“数组的数组”。二维数组又称为矩阵,行列数相等的矩阵称为方阵。
— 转载自百度百科
二维数组的创建和初始化
二维数组的创建同样需要指明数组元素的数据类型,数组名,和方括号1[count_int_n]
代表主数组内的数组元素的个数,方括号2[count_int_n]
每个数组元素内元素的个数。
类型说明符 数组名[count_int_n1][count_int_n2]
可以将第一个括号内的常量看作行数,第二个括号内的常量看作列数。
int arr[3][4]; // 3行4列,数组元素为int类型
图片中直观反映了,二维数组里每个元素的位置及它们的下标。
其他元素的创建方法同理
char arr1[3][4]; // 3行4列,数组元素为char类型
float arr2[5][6]; // 5行6列,数组元素为float类型
double arr3[7][8]; // 7行8列,数组元素为double类型
二维数组的初始化
二维数组的初始化建立再一维数组初始化的基础上,每一行其实就是一个一维数组,因此可以将{}
中嵌套若干个{}
中间用逗号,
分隔,表示每一行数组的初始化。
int main()
{
int arr[3][4] = {{0,1,2},{3,4,5},{7,8,9}};
return 0;
}
可以看到,每一行中只赋值了前3个元素,而最后一个元素默认初始化为0,因此二维数组中不完全初始化的结果与一维数组相同。
同时如果下标越界只有对越界的行有影响,并不会影响到其他行的初始化。
另外,二维数组的初始化也可以采用一维数组初始化的方式,即只用一个{}
来包含初始化的值,默认从[0][0]
开始赋值,本行结束再继续从下一行的首元素开始赋值。
int main()
{
int arr[3][4] = { 1,2,3,4,5,6 };
return 0;
}
从图中可以看到声明一个3行四列的二维数组,数组大小为12,初始化{1,2,3,4,5,6} 从[0][0]
开始赋值,本行结束继续从下一行首元素开始赋值,未初始的元素默认初始化未0。
列数不可以省略
二维数组再初始化时,行数可以省略,但列数一定不能省略。
// 行列同时省略
int main()
{
int arr[][] = { {0,1,2},{3,4,5},{7,8,9} };
int arr1[][] = { 1,2,3,4,5,6,7,8 };
return 0;
}
// 编译器会报错
// 省略列
int main()
{
int arr[3][] = { {0,1,2},{3,4,5},{7,8,9} };
int arr1[3][] = { 1,2,3,4,5,6,7,8 };
return 0;
}
// 编译器同样报错
// 省略行
int main()
{
int arr[][4] = { {0,1,2},{3,4,5},{7,8,9} };
int arr1[][4] = { 1,2,3,4,5,6,7,8 };
return 0;
}
// 编译器未报错
从上面测试可以发现,列数一定不能省略。同时,采用嵌套花括号{}
的方式初始化,行数是由内嵌{}
的个数决定的,而使用单花括号{}
的方式初始化,行数 = 初始化的元素个数 / 列数。
二维数组的使用
借用上面的图片可以发现,如果想访问一个元素只需要指定这个元素所在的行下标和列下标即可。
行下标从0开始,每一行的列下标也是从0开始。
int main(以上是关于c语言学习--数组的主要内容,如果未能解决你的问题,请参考以下文章
c语言对二维数组的某一行赋值 如u8 a[20][20]; 仅对a[10]这一行赋值
我的C语言学习进阶之旅解决 Visual Studio 2019 报错:错误 C4996 ‘fscanf‘: This function or variable may be unsafe.(代码片段
我的C语言学习进阶之旅解决 Visual Studio 2019 报错:错误 C4996 ‘fscanf‘: This function or variable may be unsafe.(代码片段