在C语言里,数组是最基础也最常用的数据结构之一。要使用数组,第一步就是学会正确地声明它。数组声明的本质是告诉编译器:我需要一块连续的内存空间,用来存放若干个相同类型的数据。声明的方式并不复杂,但里面藏着不少细节,比如长度怎么写、初始化怎么给、多维数组怎么排布,这些都会影响程序的正确性。本文将从基本语法入手,把C语言数组声明的方方面面讲清楚。

一维数组的基本声明语法
一维数组的声明格式是:类型名 数组名[长度];。例如声明一个能存放10个整数的数组,写法如下:
int scores[10];
这行代码的含义是:向编译器申请一块能容纳10个int类型元素的内存,数组名叫scores。C语言标准并不要求这10个元素在物理上相邻排列,但几乎所有编译器实现中,数组成员都是连续存放的。这也是数组可以通过下标快速访问的原因。
声明数组时有几个硬性规定需要牢记。第一,长度必须是一个大于零的整数,通常写成常量或者常量表达式,例如int arr[10];或int arr[4 * 5];都是合法的。第二,在C89标准中,长度必须是编译期就能确定的常量,不能是变量;不过C99之后支持变长数组(VLA),允许用变量作为长度,例如:
int n = 8; int arr[n]; /* C99支持的变长数组,部分编译器或环境下可能不支持 */
变长数组虽然方便,但可移植性不如传统写法,很多嵌入式开发环境或者以C89为基准的项目会禁用它。实际工程中更常见的做法是用宏定义或者枚举来确定数组长度,这样修改一处即可全局生效:
#define MAX_STU 50 int scores[MAX_STU];
数组的初始化写法
声明数组的同时可以给它赋初值,基本形式是用一对花括号把初始值列出来:int arr[5] = {1, 2, 3, 4, 5};。初始化的写法灵活多样,下面几种都是合法的:
int a[5] = {1, 2, 3}; /* 后两个元素自动补0 */
int b[5] = {0}; /* 全部元素初始化为0 */
int c[] = {10, 20, 30}; /* 长度由初值个数决定,等于3 */
int d[5] = {[2] = 7}; /* 指定初始化器,C99语法 */
第一种写法值得特别注意:如果初值个数少于数组长度,编译器会把剩余的元素自动初始化为0。利用这个特性,int arr[100] = {0};就成了把整个数组清零的经典写法。第三种写法省略了长度,编译器会根据初值的个数自动推断,所以数组c的长度是3。
如果不进行初始化,数组元素的值是未定义的。对于局部数组(在函数内部声明的),不初始化的话元素内容是栈上的残留数据,直接读取会得到随机值;而对于全局数组或静态数组,即使不初始化,元素也会被自动置为0。这个区别在调试时非常重要,很多新手发现数组数值异常,往往就是没初始化又误以为是越界问题。
还要注意初始化常见错误:初值个数不能超过数组长度,int a[3] = {1, 2, 3, 4};会直接导致编译报错;另外花括号只能用于声明时的初始化,声明之后再写arr = {1, 2, 3};是不允许的,数组名不能作为赋值运算的左值。
二维数组与字符数组的声明
二维数组的声明在格式上多了一对方括号:类型名 数组名[行数][列数];。例如声明一个3行4列的整数矩阵:
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
二维数组在内存中实际是按行优先的顺序线性存放的,也就是说matrix[0][3]的下一个位置就是matrix[1][0]。理解这一点对于指针操作和内存布局分析很有帮助。初始化时也可以只给部分初值,未指定的部分同样补0,例如int m[3][4] = {{1}, {5}, {9}};表示每行只有第一个元素有值。
字符数组是数组中的一个重要分支,因为C语言没有独立的字符串类型,字符串就是靠字符数组加结束符\0来实现的。字符数组的声明和初始化有几种典型写法:
char str1[10] = {'h', 'e', 'l', 'l', 'o', '\0'};
char str2[10] = "hello"; /* 自动在末尾补\0 */
char str3[] = "hello"; /* 长度为6,包含\0 */
需要注意str3的长度是6而不是5,因为双引号形式的字符串字面量会在末尾自动带上一个\0。而逐字符初始化的str1如果不手动写\0,它就只是一个普通字符数组,用printf("%s")输出时可能会一直读下去直到碰巧遇到0,产生乱码。
声明数组时的常见坑点
第一个坑是越界访问。C语言编译器不会检查数组下标是否越界,int a[5];之后访问a[5]、a[10]都能通过编译,但运行时行为不可预测,轻则读到垃圾值,重则程序崩溃。因此写循环遍历数组时,要牢记下标范围是0到长度减1,习惯上写成for (i = 0; i < n; i++)而不是i <= n。
第二个坑是把数组名当成普通变量使用。数组名代表数组首元素的地址,它不能被整体赋值,也不能做自增运算。数组作为函数参数传递时,实际传递的是指针,函数内部用sizeof得到的不再是整个数组的大小而是指针大小,所以惯用做法是把数组长度作为另一个参数一并传进去:
int sum(int arr[], int len) {
int total = 0;
for (int i = 0; i < len; i++) {
total += arr[i];
}
return total;
}
第三个坑是sizeof与数组长度的换算。计算数组元素个数的标准写法是sizeof(arr) / sizeof(arr[0]),即整个数组占用的字节数除以单个元素的字节数。这个技巧在数组作为实参之前的代码中很好用,但一旦进入函数内部就对形参失效了,因为形参已经退化成指针。
总的来说,C语言数组的声明核心就一句话:确定元素类型、确定数组名、确定长度,需要的话再加上花括号初始化。把一维、二维、字符数组的声明语法掌握牢固,再避开越界、初始化遗漏、数组名误用这些坑,后续学习指针、结构体数组乃至动态内存分配时就会顺利得多。动手写几段小程序验证本文的示例,是巩固这些知识最有效的办法。