欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关于C语言的数组赋值和数组下标越界问题

程序员文章站 2022-06-11 10:44:24
...

一、数组赋值

数组名就代表着该数组的首地址,后面的所有元素都可以根据数组名加上偏移量取到。

1. 一维数组


第一个小例子:编程实现显示用户输入的月份(不考虑闰年)拥有的天数。**

#include<stdio.h>
#define MONTHS 12
int main(){
    int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
    int month;  // 1-12
    do{
        printf("Input a month:");
        scanf("%d", &month);
    }while(month<1 || month>12);  // 处理不合法数据
    // days数组是从0-11
    printf("The number of days is %d\n", days[month-1]);  
    return 0;
}

        上面这个例子我们简单的用一维数组存储了一年的年份。这个代码的功能想必题目说得很清楚了,我就不再重复赘述。中间有一段while循环的代码,那个是为了处理不合法数据的,因为当我们输入的值在1-12之间的时候,这个循环我们是出不去的,会让我们一直输入月份。
        在后面输出对应月份的天数的时候,我们用的下标是month-1,因为数组的下标是从0-11,我们输入的是1-12,所以-1后才是我们对应需要的月份。


2. 二维数组

一维数组比较简单,我们来看二维数组。下面是一个二维数组的小例子。

int main(){
	// 没有指定的位置,自动赋值为0
    int a[][3] = {{1,2,3}, {4,5}, {6}, {0}};  
    printf("%d, %d, %d\n", a[1][1], a[0][2], a[2][1]);
    return 0;
}
// 数组构造:
// 1   2   3
// 4   5   0
// 6   0   0
// 0   0   0

Input:
5, 3, 0

        这个例子我们可以看出来,这个二维数组,我们最初只是指定了列数,没有指定行数,也就是说二维数组可以只指定列数,行数可以在我们进行赋值的时候,自动确定。比如我这个例子,二维数组给定了三列,里面写了4个小的一维数组,所以最终这个二维数组的维度是4行3列


我们再来看下面这个更有趣的例子。

int main(){
	// 没有指定的位置,自动赋值为0
    int a[][3] = {1, 2, 3, 4, 5, 6, 7};  
    return 0;
}

        这个例子中没有一个小括号,虽然前面的定义是一个二维数组,但是里面的赋值就像一个一维数组一样,有点儿奇怪?其实他的内部是这样的。

1   2   3
4   5   6
7   0   0

        为什么会这样?因为他指定了三列,这是毋容置疑的,但是没有指定行数,所以里面的元素会按照三列的规则一行一行的排列下去,如果排到某一行,还有空的位置没有元素,就会自动被赋值为0,这个二维数组的维度就是3行3列

数组的奇特赋值方式我想大家已经理解了,那么我们来看 数组越界 的问题。


二、数组越界

1. 一维数组越界

        一维数组的越界,编译器(我使用的VC6.0)不会在你越界的提示你,而是会根据这个地址继续对该位置进行相关操作(比如赋值或者取值)。我们来看下面这个例子:

#include<stdio.h>
int main(){
	int a = 1, c = 2, b[5], i;
	printf("\na = %d, c = %d\n", a, c);
	
	// 这里明显对于b数组的赋值,已经越界
	for (i=0; i<=8; i++){
		b[i] = i;
	}
	
	printf("\n a = %d, c = %d\n", a, c);
	return 0;
}

运行结果如下:
关于C语言的数组赋值和数组下标越界问题
这个 ac 的值明显被改变了,我们下面来看一下数组越界后,发生了什么:
关于C语言的数组赋值和数组下标越界问题
        其实编译器之所以在编译的时候没有报错,是因为他的取值赋值是根据地址来做的。由于数组名就是该数组的首地址,也就是第一个元素的地址,所以当你的索引变化的时候,虽然数组已经没有对应元素给他使用了,但是他依然会根据偏移量往后继续找,这也就把后面跟着的 c 和 a 的值给修改了。(我的编译器虽然在运行的时候没有报错,但是运行结束后还是出现了异常,提示关闭程序)

关于偏移量,我举个简单的例子:
比如,在C语言中 char 字符类型是占一个字节的。
我们定义一个一维数组  char a[5];
假设首地址是 40,那我们去 a[5] ,这个元素的地址就是 40+5*1 
因为字符类型一个位置占一个字节。
那个5就是偏移量,也就是说如果你定义的 int a[5],那么后面就是 40+5*4 了。
因为在大多数编译器中 int 占4个字节,当然也有占2个字节的。

        上面是一维数组的越界情况。


2. 二维数组越界

        其实这个的越界和一维数组越界原理是一样的,都是根据偏移量来取值,不同的是二维数组有多行,也就是你在这一行越界后,他会跑到下一行,我们来看下面这个例子:

#include<stdio.h>
int main(){
    int i, j;
    char a[6][4] = {(char)0x00};
    printf("%p\n", a);  // 输出首地址
    a[0][0] = 0x01;  // 16进制
    a[1][0] = 0x10;
    a[0][4] = 0x04; // a[1][0]
    a[1][4] = 0x05; // a[2][0]
    a[5][4] = 0x20; // 完美刚好越界
    a[5][5] = 0x30; // 越界
    for (i=0; i<6; i++){
        for (j=0; j<4; j++){
            printf("%d\t", a[i][j]);
        }
        printf("\n");
    }
    return 0;
}

Input:
1    0    0    0
4    0    0    0
5    0    0    0
0    0    0    0
0    0    0    0
0    0    0    0

        代码中的注释我们可以看到,在一个二维数组 arr[6][4]中,a[0][4] 和 a[1][0] 是同一个元素,因为 a[0][4] 在第一排越界后,根据偏移量的计算,他跑到了第二列的第一个。

a[6][4] 是一个6行4列的二维数组,一行4个
a[0][4] = 0*4 + 4 = 4
a[1][0] = 1*4 + 0 = 4

a[i][j] = i*4 + j
表示前面已经走过了 4*i 个地址,然后在 i+1 行走了 j 个位置。
计算出来就是最终的地址。

对了,0x表示的十六进制的数组,比如 0x10 换算成十进制就是 16。
因为就像16进一一样,在第二位有一个1,说明就已经有个16了。

        以上就是二维数组越界的情况。


3. 下面我们看一下简单的二维数组小例子

#include<stdio.h>
#define MONTHS 12
int main(){
    int days[MONTHS] = {{31,28,31,30,31,30,31,31,30,31,30,31},  // 平年
                       {31,29,31,30,31,30,31,31,30,31,30,31}};  // 闰年
    int year, month;  // 1-12
    
    do{
        printf("Input a month:");
        scanf("%d, %d", &year, &month);
    }while(month<1 || month>12);  // 处理不合法数据
    
    if (((year%4==0) && (year%100!=0)) || (year%400==0)){  /* 闰年 */
    	printf("The number of days is %d\n", days[1][month-1]);  // days数组是从0-11
    }else{  /* 平年 */
        printf("The number of days is %d\n", days[0][month-1]);
    }
    
    return 0;
}