携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情
前言
C语言中,数组很重要也比较基础,很多地方都要用到。
本文就来分享一波作者对数组的学习心得与见解。本篇属于第一篇,主要介绍一维数组相关的一些内容,后续还有,可以期待一下。
笔者水平有限,难免存在纰漏,欢迎指正交流。
一维数组
数组的创建
数组是一组相同类型元素的集合。
数组的创建方式:
type_t arr_name [const_n];
//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小
就比如int arr[10];告诉编译器要在内存里找一块内存来存放一组数据,这组数据都是int整型。
根据存放元素数据类型的不同,数组类型有所差异:
char arr3[10];
float arr4[1];
double arr5[20];
数组的初始化
数组的初始化是指在创建数组的同时给数组的内容一些合理初始值。
举些例子
int arr1[10] = {1,2,3};//前三个元素的位置依次放入1,2,3三个元素,后面的全部默认放入0
int arr2[] = {1,2,3,4};//不指定数组大小,但是通过初始化给的值的数量自动确定合适大小,这里大小就是4
char arr4[3] = {'a',98, 'c'};//字符数组放入三个字符元素
char arr5[] = {'a','b','c'};//不指定数组大小,但是通过初始化给的值的数量自动确定合适大小,这里大小就是3
char arr6[] = "abcdef";//这里放入的就是字符串常量,关于字符串放入字符数组,下面进行解释
C语言没有专门用于存储字符串的变量类型,字符串被存储在char类型数组中。
数组末尾位置是字符'\0',这是空字符,C语言中字符串以空字符为结束的标志,它的ASCII码值是0。
在使用scanf()读入字符串的时候,不用手动加入空字符,scanf()根据转换说明%s在读取输入时就已完成空字符的添加,同时字符串常量也不用手动添加空字符,编译器会根据双引号确定字符串并在其末尾加上空字符。
所以一般用字符数组存放字符串时要记得最少多留出一个位置来放置'\0',也就是字符数组大小>=字符串字符数+1。
数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。
但是对于下面的代码要区分,内存中如何分配。
char arr1[] = "abc";
char arr2[3] = {'a','b','c'};
指定初始化器(C99)
C99增加了一个新特性:指定初始化器,利用该特性可以初始化指定的数组元素。例如,值初始化数组中的最后一个元素,这对于以往的C语法来说是行不通的,必须初始化最后一个元素之前的所有元素才能初始化它,比如int arr[6] = {0, 0, 0, 0, 0, 129};。
而C99规定,可以在初始化列表中使用带方括号的下标指明待初始化的元素。
比如:
我们再来看看下面这个例子,想想看是怎么回事。
int main()
{
int days[] = { 31, 28,[4] = 31, 30, 31,[1] = 29 };
int i = 0;
for (i = 0; i < 7; i++)
{
printf("%d ", days[i]);
}
return 0;
}
首先,如果指定初始化器后面有更多的值,例如[4] = 31, 30, 31 ,那么这些值将被用于初始化指定元素后面的元素,这就是说,在days[4]被初始化为31后,days[5]和days[6]被分别初始化为30和31。
其次,注意到我在初始化时并没有指定数组大小没?那我为什么在后面的循环又设定i<7呢?咋知道是7个元素呢?当然是算出来的啦。如果未指定数组大小的话,编译器会把数组的大小设置为足够装得下初始化的值,如本例中,初始化列表中出现所初始化的最大下标的元素也就是days[6],那编译器自动设置数组大小为7([0]~[6])。
还有一件事,注意到days[1]在初始化列表的前面已经被初始化为28没?打印出来为什么是29呢?因为在初始化列表后面又将其初始化的值更改成了29,这也说明如果再次初始化已初始化的指定元素,那么后一次的初始化会取代之前的初始化。
一维数组的使用
对于数组的使用我们之前在讲操作符的时候介绍了一个操作符: [] ,下标引用操作符。它其实就是数组访问的操作符。
我们来看代码:
#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;
}
总结:
数组是使用下标来访问的,下标是从0开始。
数组的大小可以通过计算得到。
比如:int sz = sizeof(arr)/sizeof(arr[0]);
一维数组的边界
数组是有边界的,比如int arr[10]的边界就为0~9,使用下标超出指定范围的话就叫作数组越界。
编译器并不会检查数组下标是否使用得当,在C标准中,使用越界下标的结果是未定义的,这意味着即使程序看上去可以运行,但是运行结果会很奇怪或者异常中止。
我们最好记住:数组元素的编号(下标)是从0开始的,而且最好在声明数组时用符号常量来表示数组大小,既方便修改,又能确保整个程序中数组大小始终一致。
#include<stdio.h>
#define SIZE 10
int main()
{
int arr[SIZE];
int i = 0;
for(i = 0; i < SIZE; i++)
{
scanf("%d", &arr[i]);
}
return 0;
}
指定数组的大小
看点例子:
int arr1[10];//可以吗?
int arr2[4+6];//可以吗?
int count = 10;
int arr3[count];//可以吗?
数组的声明,在C99标准之前, [ ] 中要给一个整型常量表达式才可以,不能使用变量。sizeof表达式被视为整型常量,但是const值不是(与C++不同)。另外,表达式的值必须大于0。
相信看完这张图你就全部清楚了:
在C99标准支持变长数组的概念,也就是创建了一种新型数组,称为变长数组简称VLA(而C11标准把VLA设定为可选而非语言必备特性)。
后面会具体讲到变长数组。
一维数组在内存中的存储
接下来我们探讨数组在内存中的存储。
看代码:
#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;
}
仔细观察输出的结果,我们知道,随着数组下标的增长,元素的地址,也在有规律的递增。
由此可以得出结论:数组在内存中是连续存放的。
如图(图中地址随便给的):
数组的内存布局
铺垫——局部变量的内存布局
例1:
int main()
{
int a = 10;
int b = 20;
int c = 30;
printf("%p\n", &a);
printf("%p\n", &b);
printf("%p\n", &c);
return 0;
}
我们发现,先定义的变量,地址是比较大的,后续依次减小
这是为什么呢?
a,b,c都在main函数中定义,也就是在栈上开辟的临时变量。而a先定义意味着,a先开辟空间,那么a就先入栈,所以a的地址最高,其他类似。而栈中先使用的是高地址的空间。
图解:
正题——一维数组的内存布局
例2:
#define N 10
int main()
{
int a[N] = { 0 };
for (int i = 0; i < N; i++)
{
printf("&a[%d]: %p\n",i, &a[i]);
}
return 0;
}
我们发现,数组的地址排布是:&a[0] < &a[1] < &a[2] < ... < &a[9]。
该数组在main函数中定义,那么也同样在栈上开辟空间。
数组有多个元素,那么肯定是a[0]先被开辟空间啊,那么应该&a[0]地址最大呀,可是事实并非如此。
因为数组是整体申请空间的,然后将地址最低的空间,作为a[0]元素,以此类推。
图解:
以上就是本文全部内容,感谢观看,你的支持就是对我最大的鼓励~