C++数组的创建及使用
一、静态数组
静态数组:定义时就已经在栈**上分配了空间大小,在运行时这个大小不能改变
数组(array)是一种数据格式,能够存储多个同类型的值。
一维数组
1、一维数组的声明:
一般声明:typeName arrayName[arraysize];
要创建数组,可使用声明语句。数组的声明应该指出数组的
①存储在每个元素中的值的类型②数组名③数组中的元素数
注意:以上声明语句中的arraysize不能是变量,另外数组大小一旦确定之后就不能修改了!
例如:
short month[12] //create an array of 12 shorts
2、数组的初始化及使用:
只有在定义数组的时候才能初始化,之后就不能使用了,也不能将一个数组的值赋给另外一个数组,然而可以使用下标分别给数组中的元素赋值。
int array1[4] = {1,2,3,4}; //okay
int array2[4]; //okay
array1[4] = {5,6,7,8}; //not allowed
array2 = array1; //not allowed
int array3[4] = {2,7}; //okay 只初始化前两个元素,默认其他元素为0
int array4[] = {1,9,9,5}; //okay 编译器计算元素个数
double array5[4] {1.1, 2.2, 5.5, 1.7e4}; //okay in c++11,可省略=
double array6[20] {}; //okay, 所有元素被设为0
double array6[20] = {}; //okay, 同上,所有元素被设为0
long array7[] = {3.1,6,7}; //not allowed 浮点数转成整形是缩窄操作
char array8[5] {'h','i','120','65535','\0'}; //not allowed 因为65535超出了char的范围
其实在C++中,将数组名解释为地址。在多数情况下,C++将数组名解释为数组第一个元素的地址。例如以下程序:
int main()
{
using namespace std;
int months[3] = {5,7,12};
double workhours[3] = {120.7, 110.5, 57};
// Here ara two ways to get the address of an array
int * pm = months; // name of array = address
double * pw = &workhours[0]; // or use address operator
// With array elements
cout << "pm = " << pm << ", *pm = " << *pm << endl;
pm += 1;
cout << "add 1 to the pm pointer:" << endl;
cout << "pm = " << pm << ", *pm = " << *pm << endl << endl;
cout << "pw = " << pw << ", *pw = " << *pw << endl;
pw = pw + 1;//增加的值等于指向类型占用的字节数
cout << "add 1 to the pw pointer:" << endl;
cout << "pw = " << pw << ", *pw = " << *pw << endl << endl;
cout << "access two elements with array notation " << endl;
cout << "workhours[0] = " << workhours[0] << ", workhours[1] = " << workhours[1] << endl;
cout << "access two elements with pointer notation " << endl;
cout << "*workhours = " << *workhours << ", *(workhours+1) = " << *(workhours+1) << endl;
cout << sizeof(months) << " = size of months array" << endl;
cout << sizeof(pm) << " = size of pm pointer" << endl;
return 0;
}
程序的输出结果为:
pm = 000000CC6FEFF848, *pm = 5
add 1 to the pm pointer:
pm = 000000CC6FEFF84C, *pm = 7
pw = 000000CC6FEFF878, *pw = 120.7
add 1 to the pw pointer:
pw = 000000CC6FEFF880, *pw = 110.5
access two elements with array notation
workhours[0] = 120.7, workhours[1] = 110.5
access two elements with pointer notation
*workhours = 120.7, *(workhours+1) = 110.5
12 = size of months array
8 = size of pm pointer
二维数组:
c++没有提供二维数组类型,但是可以创建每个元素本身都是数组的数组。
1、二维数组的声明
例如:
int array[5][7];
该声明意味着array是一个包含5个元素的数组,其中每个元素都是一个由7个整数组成的数组,如下图所示。
2、二维数组的初始化及使用
创建二维数组时,可以初始化其所有元素。需要在一维数组的声明之上加上由逗号分隔的用花括号括起来的值列表,如:
int array[4][5] =
{
{0,1,2,3,4},
{1,2,3,4,5},
{2,3,4,5,6},
{3,4,5,6,7}
}
对于上述初始化的二维数组也可看做如下结构
其中array是数组名,a数组包含4行,即4个元素:array[0]、array[1]、array[2]、array[3],每个行元素可以看作含有5个元素的一维数组。也就是说array[0]、array[1]、array[2]、array[3]分别是这4个一维数组的数组名。既然是数组名,上面说到一维数组的数组名表示的就是数组第一个元素的地址,所以array[0]表示的就是元素array[0][0]的地址,即array[0] = &array[0][0],所以在二维数组a中,a[i]]表示的就是元素array[i][0]的地址,即
a[i] == & a[i][0]
在一维数组中,数组名a代表数组的首地址,即第一个元素的地址,而a+1代表数组第二个元素的地址…所以既然array[0]、array[1]…代表二维数组第0行、第1行的首地址,同理array[0] + 1就是元素array[0][1]的地址…所以有:
a[i]+j = &a[i][j]
由上两式得:
&a[i][0] + j = &a[i][j]
又由于a[i]和*(a+1)等价,即
a[i] = *(a+i)
则对于二维数组有:
*(a+i)+j == &a[i][j]
注意上式返回的是地址,与以下有所差别,下式表示的是元素的值
*(a[i]+j) == *(*(a+i)+j) == a[i][j]
3、二维数组的首地址和数组名:
对于一维数组,数组名表示的是数组第一个元素的地址,对于二维数组,其实数组名也代表了首元素的地址。但是对于以上的数组array而言,数组名array代表的不是元素array[0][0]的地址。上面提到过二维数组也是通过一维数组嵌套形成的,即二维数组就是一维数组。二维数组array[4][5]是有4个元素array[0]、array[1]、array[2]、array[3]的一维数组,所以二维数组的首元素应该是array[0],而不是array[0][0]。所以有:
a == &a[0]
而a[0]又是a[0][0]的地址,即:
a[0] == &a[0][0]
所以二维数组a和元素a[0][0]的关系为:
a = &(&(a[0][0]))
即二维数组名a是地址的地址,必须两次取值才可以取出数组中存储的数据,对于二维数组a[m][n],数组名a的类型为int(*)[n],所以如果定义一个指针变量p(int*p),并希望这个指针变量指向二维数组a,那么不能把a赋给p,因为他们的类型不一样。①要么把 a[0] 赋给 p,要么把 *a 赋给 p。因为 a==&(&a[0][0]),所以*a==*(&(&a[0][0]))==&a[0][0]。
②另外还可以把指针变量 p 定义成 int(*)[n] 型,这时就可以把 a 赋给 p,这种方法也经常使用。
把 a[0] 赋给 p或者把 *a 赋给 p的情况
int* p = a[0];
int* p = *a;
int* p = &a[0][0];
对于二维数组a[m][n],如果将&a[0][0]赋给p的话,那么通过指针p访问二维数组元素的公式为:
p + i*n + j == &a[i][j];
把二维数组名直接赋给指针p的情况
如果将二维数组名 a直接赋给指针变量 p,即把指针变量 p 定义成 int(*)[n] 型,则:
p == a
那么此时用p指向元素a[0][0]是以行为单位进行访问,数组名a代表第一个元素a[0]的地址,则a+1就代表a[1]的地址,即a+1==&a[1],则
a+i = &a[i]
于是就有
p+i = &a[i]
对上式两端做"*"运算可得:
*(p+i) == a[i]
进一步(两端同时加上j行)可得:
*(p+i) + j == &a[i][j]
注意:数组指针应区别于指针数组
指针数组实际上是一个数组,数组的每个元素存放的是一个指针类型的元素。例如:
int * arr[7] //[]的优先级比*高,说明arr是一个数组,而int*是数组里面的内容
//也就是arr是一个含有7个int*的数组
其结构形式如下:
数组指针实际上是一个指针,该指针指向一个数组。例如:
int (*arr)[7] //由于[]的优先级比*高,因此在写数组指针的时候必须用括号将*arr括起来
//这里arr先和*结合,表明arr是一个指针变量,也就是arr指向一个大小为7个整形的数组
可编程验证如下:两者输出的结果完全一致。
#include <stdio.h>
int main()
{
int a[3][4] = { 11, 12, 13, 14, 21, 22, 23, 24, 31, 32, 33, 34 };
int(*p)[4] = a; //把二维数组名直接赋给指针p,记住这种定义格式
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 4; ++j)
{
printf("%-2d\x20", *(*(p + i) + j));
//p+i表示a[i]的地址
//%-2d中, '-'表示左对齐(无'-'默认为右对齐);2表示这个元素输出时占两个空格的空间
}
printf("\n");
}
printf("\n");
int*p1 = *a; //把a[0]或者*a赋给指针p1
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 4; ++j)
{
printf("%-2d\x20", *(p1+ i*4 + j));
//p+i表示a[i]的地址
//%-2d中, '-'表示左对齐(无'-'默认为右对齐);2表示这个元素输出时占两个空格的空间
}
printf("\n");
}
printf("\n");
return 0;
}
补充:(字符串)
字符串是存储在内存的连续字节中的一系列字符。之所以放在这里是因为存储在连续字节中的一系列字符意味着可以将字符存储在char数组中
1、C风格字符串(char数组)
char cat[9] = {'m', 'i', 'n', ' ', 'a', 'p', 'p', 'l', 'e'}; //not a string
char cats[10] = {'m', 'i', 'n',' ', 'a', 'p', 'p', 'l', 'e', '\0'}; //a string
这两个数组都是char数组,但是只有第二个数组是字符串,C-风格字符串具有一种特殊的性质,以空字符(null character)结尾,即\0,其ASCII码为0。用来标记字符串的结尾。
以上示例中,将数组初始化为字符串的工作特别冗长,还使用了大量的单引号。为了简化以上数组初始化为字符串,可以用以下方法,用引号括起的字符串隐式地包括结尾的空字符。
char cats[10] = "min apple"; // \0被默认添加
char fish[] = "Bubbles"; // 让编译器自动计数
补充:拼接字符串常量,
2、string类型
string str1 = "this is an example!"; //initial string
srting str2; //create an empty string object;
str2 = str1; //allowed, object assignment ok
string str3 = str1 + str2; //allowed ,add string
二、动态数组
动态数组:运行时在堆上分配一定的存储空间,另外在运行时还可以改变其大小。虽然在栈上分配空间效率较高,但是栈空间有限,对于大型数据应使用new和delete在堆区分配空间。另外在很多情况下预编译过程阶段数组的长度是不能预先知道的,必须在程序运行的过程中动态给出。
一维数组
1、使用new创建动态数组
int * parray = new int[10]; // get a block of 10 ints
new运算符返回第一个元素的地址,以上示例中返回的第一个元素的地址被赋给指针parray。当程序使用完new分配的内存块时,需要使用delete来使用它们:
delete [] parray; //free a dynamic array
方括号告诉程序,应释放掉整个数组,而不仅仅是指针指向的元素,注意delete只是释放指针指向的存在,但不会删除指针本身,例如可以将parray重新指向另一个新分配的内存块。上述parray是指向一个int(数组的第一个元素)的指针。
2、使用动态数组
创建了动态数组之后,访问数组中的元素的话,对于第一个元素没有问题,因为parray指向数组中的第一个元素,因此*parray是第一个元素的值。这样还有9个元素,可以访问的方式为:只要把指针当作数组名使用即可:例如parray[0]表示第一个元素,parray[1]表示第二个元素…例如以下程序:
int main()
{
using namespace std;
double *p = new double[3]; //space for 3 doubles
p[0] = 1.1;
p[1] = 5.5;
p[2] = 7.7;
cout << "p[1] is " << p[1] << endl;
p = p + 1; //increment the pointer (ok for the pointer, wrong for the array name.)
cout << "now p[0] is " << p[0] << " and ";
cout << "p[1] is " << p[1] << endl;
p = p - 1; // point back to begging
delete [] p; // free the memory
return 0;
}
程序的输出结果为:
p[1] is 5.5
now p[0] is 5.5 and p[1] is 7.7
由以上可知,p+1后指针将指向下一个元素的地址,注意在delete之前p-1以便给delete提供正确的地址。
另外注意new和delete一定搭配使用,否则会造成内存泄露;还应注意野指针的问题,例如:
int* p = new int; //表示在堆区分配了一块int型的空间,但是指针p在栈区
p++; //原来指向int的指针没有了,但是new int还在,形成了内存泄漏,野指针。
最后要说明一个问题,在静态数组中如下定义是错误的,因为在定义数组时,必须明确给出数组的大小,要不然编译不通过。
int i=5;
int Array[i]; //错误 因为在编译阶段,编译器并不知道 i 的值是多少
就是基于以上,在很多预编译阶段,数组的长度是不能预先知道的,必须在程序运行时动态地给出。所以用new动态定义数组,这样下面的语句是正确的:
int size = 77;
int *p = new int[size];
二维数组
1.使用形如int (*p) [n]指针
type (*p)[N] = new type[][N]
其中type是某种数据类型,N是二维数组的列数。采用这种格式,列数必须指出,而行数无需指定。在这里指针p的类型是type*[N],即是指向一个有N列元素数组的指针。
同样讨论该二维动态数组,对于以下的语句是不能通过编译的:
int row=50,col=50;
int (*p)[col]=new int [row][col];
这是因为,首先new int [row][col]是在动态生成数组时确定的,所以后面的没有问题。但是前面的int (*p)[col],这是一个定义语句,定义语句要先经过编译器进行编译,当编译器编译到此时发现col不是常数,因此不能通过编译。而之所以编译器认为col不是常数,是因为编译阶段,编译器起的作用是查语法错误,和预分配空间,它并不执行程序,因此,没有执行那个赋值语句(只是对这个语句检查错误,和分配空间),因此编译阶段,它将认为col是个变量。所以上面的二维数组定义是错误的, 它不能通过编译。可以修改成:
int row = 50;
int (*p)[50]=new int [row][50]; //这样就可以编译通过了
由以上可得知这种动态分配数组,仅对一维数组空间是真正动态分配的。那么在预编译阶段不知道数组的列数时可以通过一维是真正的动态分配进行,如下所示使用int **p。
2.使用形如int **p二维动态数组指针
例如:
int **p;
注意与以上提到的二维静态数组指针int(*p)[n]的区别。int** a;表示一个内存空间,这个空间用来存放一个指针,这个指针指向一个存放指针的空间,并且指向的这个空间中的指针,指向一个整数; int (*p)[10];表示一个内存空间,这个空间用来存放一个指针,这个指针指向一个长度为10、类型为int的数组;和int** a的区别在于,++、+=1之后的结果不一样,其他用法基本相同。
补充: int (*a)(int);表示一个内存空间,这个空间用来存放一个指针,这个指针指向一个函数,这个函数有一个类型为int的参数,并且函数的返回类型也是int。
int **p的使用如下:
m = 5
n = 7;
int **p = new int*[n];
for(int i = 0; i < a; i++)
{
p[i] = new int[m];
}
//相应的delete方法为:
for(int i = 0; i< n; i++)
{
delete [] p[i]; // 要在指针前加[],否则就只释放p[i]所指的第一个单元所占的空间
}
delete [] p; //最后还要释放掉开辟的指针数组
最后在C++中也封装了vector以及array等类,可以使用这些类来定义数组。
例如:
vector<vector<int> > array;//注意后面> >中间的空格
例如:
#include<vector>
vector<int> a[n];
for(int i = 0;i<n;i++)
a[i].resize(m); //m、n为二维数组的行和列数