首先,我们先简单介绍一下数组,人们通常要借助计算机完成处理大量相同类型的数据,例如每月的支出、日常降雨、季度销售额等任务。所以在C语言中就避免不了要出现数组的概念,即数组由数据类型相同的一系列元素组成。
一维数组的创建和初始化
数组的创建:
//元素类型 数组名 常量表达式,用来指定数组的大小。
int arr1 [10];
//特例:
int n=10;
int art[n]; //C99中引入了变长数组的概念,允许数组的大小用变量来指定,如果编译器不支持C99变长数组,那就不能使用。
//在vc2022中是不支持变长数组的,linux系统中支持c99。
//并且在c99中变长数组不能初始化。
数组的初始化:
int arr1[10]={1,2,3,4}; //不完全初始化,只初始化一部分,剩下部分自动初始化为0.
int arr2[10]={1,2,3,4,5,6,7,8,9,10}; //完全初始化。
char ch1[]={'a','b','c'}; //初始化时没有设定数组的大小,具体大小是由初始化内容的项数决定。
char ch2[]={'a',98,'c'}; //ch1和ch2这两种表示方法相同,
char arr3[]="abc"; //这里默认数组的大小是4,因为字符串,结尾有'\0'
char arr4[]={'a','b','c'}; //这里默认数组的大小是3。
int arr3[10]; //当我们只创建,不初始化,里面的内容是随机值
补充:静态变量和全局变量在未初始化下,会默认初始化为0.
(1.静态变量可以分为静态全局变量和静态局部变量,即由static所修饰的全局变量和局部变量。2.并且我们还需要区分静态全局变量和全局变量的区别,虽然说未初始化下它都会默认为0.但是这两个概念是完全不同的,具体细节可以留言,我会再出一章进行解释。3.因为存放在静态区,所以这种方式也称为静态存储类别。)
局部变量和形式参数在未初始化下,是随机值。
(这是由auto关键字定义的变量,auto一般会省略,将这种变量存放在栈区,栈区的变量并不会进行默认初始化。并且这种方式也叫做动态存储类别。 其实本质上就是因为它们放在内存的不同区域)
关于初始化的问题我们只简单介绍到这里,其实还有内容的,关于C99中增加了一个新特性:指定初始化器。有兴趣的可以自行搜索。
一维数组的使用
首先我们先介绍一个操作符:[ ]----下标引用操作符,用于数组访问的。
例如:int arr[10]; //arr和4是[ ]该操作符的两个操作数。
#include<stdio.h>
int main()
{
int arr[10] = {0};
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;
}
//简单的对数组进行赋值,然后输出。
注:1.我们只是在创建和初始化的时候数组下标不能使用变量,而我们在使用时可以使用变量。
2.上面的代码中使用循环给数组的元素一次赋值。C语言不允许把数组作为一个单元赋给另外一个单元,除初始化外也不允许使用花括号列表的形式赋值
//演示错误复制。
int arr1[5]={5,3,2,4};
int arr2[5];
arr2=arr1; //不允许
arr2[5]=arr1[5]; //数组下标越界
arr2[5]={2,3,4,5}; //不允许,总之这些形式都是错误的赋值方式。
一维数组在内存中的存储
一维数组在内存中是连续存放的!随着数组下标的增长,地址是由低到高变化的
思考一下下面的代码:
&arr[i] 和 p+i 为什么是一样的功能?
arr[i]数组是一个int整形的数组。一个元素占4个字节,所以在下标+1是跳过了4个字节.
p表示该数组的首元素的地址,且变量p是指向int整形常量,所以指针变量p+1,意思是指向下一个int整形元素的地址,而一个int型的元素占四个字节,也就是跳过了4个字节。
回到正题,到底为什么功能一样,本质是因为数组在内存中是连续存放的。
同样我们也可以用指针打印元素:
二维数组的创建和初始化
首先我们先简单理解一下二维数组:
试着想一下统计五年每月的降水量,我们应该怎么定义这个二位数组呢?
float rain[5][12]; //很明显我们因该这么定义,但是我们应该怎么理解这个数组呢
//可以理解: 内含5个数组元素的数组,每个数组元素内涵12个float类型的元素。
//查看中间部分rain[5],rain是一个内含5个元素的数组。
//再看其余部分 float [12],每个元素是一个内含12个float元素的数组
二维数组的创建和初始化
int main()
{
int arr[3][5]; //二维数组的创建。
int arr[3][5]={1,2,3,4,5,6}; //不完全初始化。未初始化的默认为0.
int arr[3][5]={{1,2},{3,4},{5,6}};//这种也是不完全初始化。只是元素位置不一样
int arr[][5]={1,2,3,4,5,6}; //初始化时行可以省略掉行,因为我们可以根据多少列多少元素,计算出行。但列不可以省略掉。
return 0;
}
二维数组的使用
这里我们仔细观察这里的行和列的求法,可以结合上面数组理解方法。
二维数组在内存中的存储
也由于二维数组是连续存放的,因此我们在初始化时候不能省略列的存在。
可以把二维数组理解成一维数组:
第一行认为一维数组的第一个元素,第二行作为一维数组的第二个元素。
数组越界
在使用数组时,要防止数组下标超出边界,也就是说,必须确保下标是有效的值。
例如:int arr[10];
那么在使用该数组的时,要确保程序中使用的数组下标在0~9的范围内,因为编译器不会检查这种错误。(但是,一些编译器会发出警告,然后继续编译程序)
使用越界的数组下标会导致程序改变其他变量的值,或者导致程序运行终止。
那为什么从语言为允许这种麻烦的事情发生呢?1.主要归功于C语言信任程序员的原则。2.不检查边界,可以程序运行的更快。所以我们在编写代码的时候因自行检查下标越界。
数组作为函数参数
数组名就是数组首元素的地址:
有2个例外:
1.sizeof(数组名) ;这里数组名不是首元素的地址,数组名表示整个数组。计算的是整个数组的大小。
2.&数组名;数组名不是首元素的地址,数组名表示整个数组,取出的是整个数组的地址
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
printf("%p\n",arr);
printf("%p\n",&arr[0]);
printf("%p\n",&arr);
return 0;
}
结果是:
都是同一份地址,那怎么区分这几种表达呢?
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
printf("%p\n",arr);
printf("%p\n",arr++1);
printf("%p\n",&arr[0]);
printf("%p\n",&arr[0]+1);
printf("%p\n",&arr);
printf("%p\n",&arr+1);
return 0;
}
结果是:
这就说明前两种arr、&arr[0]表示方法,指向的只是一个int型的元素。
而&arr的表示方法,指向int的型的整个数组。
那么,接下来我们思考下面的代码:
void bubble_sort(int arr[])
{
int sz = sizeof(arr) / sizeof(arr[0]);//这样对吗?
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j < sz - i - 1; j++)
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 3,1,7,5,8,9,0,2,4,6 };
bubble_sort(arr);//是否可以正常排序?
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
return 0;
}
为什么结果是:
其实通过上面个的结论,我们可以知道我们传递过去的只是指向第一个数组元素的地址。
并且,虽然在形参中我们用int arr[];来接收数组首元素地址,但本质上是一个指针,因为地址就应该用指针接收,等价于 int* arr;这种写法。
而形参接收时,本质上创建了一个整形指针变量 int* arr; 虽然实参和形参名字一样,但意义截然不容,实参代表数组的首元素地址,在sizeof()中代表整个数组,即这是一个数组名。而形参arr只是一个指向整形的指针变量名。
所以我们在编译上面这行代码的时候。
int sz = sizeof(arr) / sizeof(arr[0]);
sz最后结果是1。问题就在于这里的arr并不是数组名,而是变量名。
指针和数组其实还有很多内容,这里由于篇幅问题,后期进行跟新,有问题可以留言。
如有错误,感谢指出!
参考书籍:C Primer Plus 第六版