数组是应用最广泛的一种数据结构,常常被植入到编程语言中,作为基本的类型来使用,因此在一些介绍数据结构的文章中,数组没有被当做一种数据结构单独拿出来讲解。鉴于大家对数组都不陌生,可以将其作为学习数据结构的敲门砖。
数组的优势在于能够进行快速的查找,如果知道下标,则可以瞬间将元素取出,但是对于元素的删除和插入则非常慢。
所以,如果面临的问题需要对数据进行快速的查找,且很容易获得下标,此时就可以考虑使用数组对数据进行存储,因为数组可以非常快速的对数据进行取出。
数组是一个固定大小的由相同类型元素组成的集合。数组可以分为一维数组、二维数组等。在此我们只介绍最常用的两种一维数组和二维数组。
一维数组
一维数组的声明:
type arryName[arrySize]
type:数组中每个元素的数据类型
arrayName:数组的名字
arrySize:数组的大小
例如进行如下声明:
int array[10];
type:数组类型为整型
arrayName:数组名字为array
arraySize:数组大小为10
如果我们一开始就知道数组中每个元素的类型,并且知道数组的大小(或者数组的最大容量值)就可以按照如上的方式进行声明,但是上述方式分配的数组不能改变,即一旦分配完成数组大小不可变,有时候会造成内存的浪费(即申请了比数组长度多的多的内存),所以有时候我们就会根据需要动态申请内存,生成数组。动态申请内存有如下两种方式:
方式一:使用malloc/free标准库函数。
int size;
int* intBuffer = NULL;
intBuffer = (int*)malloc(size*sizeof(int));
...
free(intBuffer);
//malloc()函数的返回值为void*,所以需要进行类型的强制转换
方式二:使用new/delete关键字
int size;
int* intBuffer = new int[size];
...
delete[] intBuffer;
//new/delete关键字是C++语法特带的,所以在C语言中不能够使用new/delete关键字,只能使用malloc/free函数。
此时使用上述两种方法,我们申请了长度为size的数组,长度不多不少,并不会造成内存的浪费。但是切记一点一定要对申请的内存进行释放,否则会造成内存泄漏的,同时如果程序需要频繁的使用数组,建议不要使用动态分配,因为会产生内存碎片的。
不管我们是直接声明数组还是进行动态内存分配,其最终我们都需要知道数组的大小,如果我们并不知道数组大小怎么办呢?此时就需要使用C++ STL库中的vector替代数组,该容器自身可以动态增长,我们只需要向其内部插入数据即可。
一维数组的初始化:
int array[10] = {};//对数组全部初始化为0
int array[10] = {1,2};//数组前两个元素进行初始化1和2,其他全部初始化为0
int array[3] = {1,2,3,4};//错误,数组越界
int array[5] = {1,,2,3,4};//编译报错
int array[5] = {1,2,3,4,}//会报错。
有时候我们也可以不指定数组的大小,对其直接进行初始化
int array[] = {1,2,3,4,5};//在此初始化为一个大小为5的数组,这种方式只能在声明的时候使用
字符数组:
单独对字符型数组进行说明,是因为字符型数组的最后一位固定是’\0’,所以在定义大小以及声明的时候要特别注意,不能有越界行为。
char array[5] = ['a','b','c','d'];//正确,因为最后一位是'\0'
char array[5] = 'abcds';//错误,因为最后一位已经是'\0',不能越界。
char array[] = "wangdongfang"//声明一个大小为13的字符型数组,虽然才12个字符,但是还有最后一个'\0'
一维数组作为函数参数进行传递有两种传递方式:数组和指针。但是不论是数组还是指针,归根结底传递的还是指针,因为数组作为函数参数会退化为指针。
1、数组传递
int sum(int array[],int size)
{
//函数体
}
对于这种方式的声明,int array[]
是为了提醒用户,传递进去的是一个数组,如果不加后面的大括号int array
,我们会以为传递的是一个整形值。所以一定要加上这个大括号。此时传入函数的只是数组的第一个数值,对于数组长度等信息,需要我们单独使用一个参数进行传递。
当然使用数组作为函数参数时,我们也可以指定数组的大小,比如:
int sum(int array[10], int size)
{
//函数体
}
在此我们虽然给出了数组的长度,但是并不代表array
就是一个实际的数组,在此array
仍然是一个指针。只不过指明传进来的数组最多只能包含10个元素。
2、指针形式
int sum(int* array, int size)
{
//函数体
}
对于使用指针形式,指针指向数组第一个元素,但是如果想在函数体使用数组长度等信息,我们也需要单独传输。
二维数组
当数组具有两个下标时,我们认为数组为二维数组,是一个平面结构:
type array[lineCount][rowCount]
type:数组中所有元素的数据类型
array:二维数组的名字
lineCount:行数
rowCount:列数
二维数组的声明:
当我们知道二维数组的具体行数和列数时,我们可以进行如下的声明:
int lineCount = 2;
int rowCount = 3;
int array[lineCount][rowCount];
//在此声明一个2行3列的二维数组,数组中每个元素都是整数
对于二维数组我们也可以进行动态内存的分配:
int **array;
int lineCount = 2;
int rowCount = 3;
array = new int* [lineCount];
//在此array包含lineCount个整形指针元素,每个指针元素又都可以指向一个一维数组,所以就可以构成一个二维数组。
接下来可以对其进行如下的初始化:
for(int i = 0;i < lineCount;i++)
{
array[i] = new int[rowCount];
}
二维数组的初始化:
1、分行初始化:
int array[2][3] = {{1,2,3},{4,5,6}}
第一行的三个元素分别初始化为:1,2,3。
第二行的三个元素分别初始化为:4,5,6。
2、不分行初始化:
int array[2][3] = {1,2,3,4,5,6}
因为数组是分为2行3列,所以在此初始化结果为:
数组第一行结果为:1,2,3。
数组第二行结果为:4,5,6。
3、部分元素初始化:
int array[2][3] = {{1,2,3},{4}}
数组第一行结果为:1,2,3
数组第二行结果为:4,0,0
4、省略第一维的定义,但是不能省略第二维的定义
int array[][3] = {1,2,3,4,5,6}
因为数组列数为3个,所以初始化结果为:
数组第一行结果为:1,2,3
数组第二行结果为:4,5,6
当然在此我们也可以使用如下初始化方法:
int array[][3] = {{1,2,3},{4}}
字符型二维数组:
字符型二维数组其实可以看成每行都是一个一维的字符型数组,其使用方法和一维字符型数组是一样的。
char[2][5] = {"dong","fang"};
//数组只能存放<=4个元素,因为每一行的最后一位必须留个'\0',所以虽然长度为5,但是最多只能存放4个元素。
//二维字符型数组只在这个层面和其他数组类型不太一样,其他地方都差不多。
二维数组作为函数参数传递:二维数组做函数参数传递其实和一维数组差不多,也是只有两种方式,数组和指针。无论数组以什么样的形态进行传递,我们尽量都将其行数和列数作为函数的参数传递进去。
1、以数组形式进行传递
int out(int a[][3], int lineCount, int rowCount)
{
//在此其实数组a的列数要<=3。
}
2、以全数组的形式进行传递、
int out(int a[2][3], int lineCount, int rowCount)
{
//在此数组a的行数 <= 2,列数 <= 3。
}
绝对不允许用以下这种方式进行传递:
int out(int a[][],int lineCount,int rowCount)
{
//在此我们至少需要知道数组a的第二个参数
}
int main()
{
int array[2][3] = {{1,2,3},{4,5,6}};
out(array,2,3)//以数组的形式进行传递。
}
2、以指针的形式进行传递:
1、这种方式可以使用下标对数组进行访问,但是首先必须把数组拷贝进一个动态开辟的二维数组中,并且在使用完之后进行动态释放。
int out(int **a, int lineCount, int rowCount)
{
//函数体。
}
int main()
{
int array[2][3] = {{1,2,3},{4,5,6}};
int **p = new int* [2]
for(int i =0;i<2;i++)
{
p[i] = new int [3];
}
for(int i =0;i<2;i++)
{
for(int j =0;j<3;j++)
{
p[i][j] = array[i][j];
}
}
out(p,2,3);
for(int i =0;i<3;i++)
delete[] p[i];
delete[] p;
}
2、通过二级指针进行访问:这种方式在函数中不能使用下标访问数据,但是此种方式可以传递任意类型的数组,然后在输出的时候进行一下类型的强制转换。(建议大家使用这种方式)
void out(void **a, int lineCount; int rowCount)
{
for(int i =0;i<lineCount;i++)
{
for(int j=0;j<rowCount;j++)
{
cout<<*((int*)a + i*rowCount + j)<<endl;
}
}
}
int main()
{
int array[2][3] = {{1,2,3},{4,5,6}}
out((void**)array,2,3)
}