先了解一下数组的结构,然后接着了解动态数组的结构。
数组
数组:
(1)数组大小固定。
(2)数组是一种复合类型
(3)存放类型相同的对象的容器,需要通过所在位置访问这些对象。
(4)数组的元素应该为对象,因此不存在引用的数组
(5)维度:数组中元素的个数,必须大于0,编译的时候维度应该是已知的,也就是说,维度必须是一个常量表达式。
(6)不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。
(7)在使用数组下标的时候,通常将其定义为size_t类型。size_t是一种机器相关的无符号类型,它被设计的足够大以便能表示内存中任意对象的大小。
(8)通过数组名字和数组中首元素的地址都能得到指向首元素的指针。
//不允许拷贝和赋值
#include <iostream>
using namespace std;
int main(){
int a[] = {0,1,2};
//int a2[] = a; //错误:不允许使用一个数组初始化另一个数组
//a2 =a; //错误:不能把一个数组直接赋值给另一个数组
return 0;
}
//数组的维度
#include <iostream>
using namespace std;
int main(){
unsigned uint = 10; //不是常量表达式
constexpr unsigned cuint = 10; //常量表达式
//string str[uint]; //编译报错:uint不是常量表达式
string str[cuint]; //含有10个整数的数组
string *cstr[cunit]; //含有10个整型指针的数组
return 0;
}
复杂数组定义的解读方法:由名字开始由内向外解读
(1)默认情况下,类型修饰符从右向左依次绑定,对于ptrs:首先我们定义的是一个大小为10的数组,它的名字是prts,然后知道数组中存放的是指向int的指针。
(2)但是对于parray来说,从左到右理解,即由内向外阅读就更合适。首先是圆括号括起来的部分,(*parray)意味着parray是一个指针,接下来观察右边(*parray)[10],可以知道parray是个指向大小为10的数组的指针,然后在观察左边,int (*parray)[10],知道数组的元素是int。这样就知道parray是一个指针,它指向一个int数组,数组中包含10个元素。
(3)int *(&array)[10] = ptrs;首先知道array是个引用,然后观察右边知道引用的对象是一个大小为10的数组,然后观察左边,数组的元素是一个指向int的指针。这样,就得出了array就是一个含有10个int型指针的数组的引用。
//复杂数组的声明
#include <iostream>
using namespace std;
int main(){
int *ptrs[10]; //含有10个整形指针的数组
int arr[10];
int (*parray)[10] = &arr; //parray 指向 一个含有10个整数的数组
int (&arrRef)[10] = arr; //arrRef 引用 一个含有10个整数的数组
int *(&array)[10] = ptrs; //
return 0;
}
//数组的初始化
#include <iostream>
using namespace std;
int main(){
const unsigned sz = 3;
int a1[3] = {0,1,2}; //含有3各元素的数组,元素分别是0,1,2
int a2[] = {0,1,2}; //维度是3的数组
int a3[5] = {0,1,2}; //等价于a3[] = {0,1,2,0,0}
string a4[3] = {"i","and"}; //等价于a4[] = {"i","and",""}
//int a5[2] = {0,1,2}; //错误:初始值太多
return 0;
}
字符数组
有一种额外的初始化形式,可以通过字符串字面量对此类数组初始化
,使用这种方式需要注意字符串字面值的结尾处还有一个空字符,这个空字符也会像字符串的其它字符一样被拷贝到字符数组中去。
//字符数组的初始化
#include <iostream>
using namespace std;
int main(){
char a1[] = {'c','+','+'}; //列表初始化,没有空字符串
char a2[] = "c++"; //自动添加表示字符串结束的空字符
//const char a4[5] = "Hello"; //错误。"Hello"看起来只有5个字符,但是数组大小必须是6,其中5个位置存放字面值的内容,另外一个存放结尾处的空字符
return 0;
}
数组和指针
(1)很多用数组名字的地方,编译器会自动地将其替换为一个指向数组首元素的指针
(2)指针加上一个整数得到的结果还是一个指针
(3)
#include <iostream>
using namespace std;
int main(){
string nums[] = {"i","love","you","haha"};
string *p = &nums[0]; //p指向nums的第一个元素
string *p2 = nums; //等价于string *p2 = &nums[0] ,此时p2指向nums[0]
++p2; //此时p2指向nums[1];
string *last = &nums[4]; //得到数组尾元素之后那个并不存在的元素的地址。尾后指针不可以执行解引用和递增操作
return 0;
}
#include <iostream>
#include <iterator>
using namespace std;
int main(){
int a[] = {0,2,4,5,7,8};
int s = *a; //a[0]的值,即为0
int s1 = *(a + 3); //a[4]的值,即为5
int s2 = *a + 3; //a[0] + 3的值,即为3
int *p = &a[2]; //p指向索引为2的元素
int val = p[1]; //p[1]等价于 *(p+1),也就是a[3]表示的那个元素
int val1 = p[-2]; //p[-2]等价于 *(p-2),也就是a[0]表示的那个元素
return 0;
}
数组的遍历
#include <iostream>
#include <iterator>
using namespace std;
int main(){
string nums[] = {"i","love","you"};
//通过指针遍历
string *p = nums; //此时p指向nums[0],数组首元素的指针
string *last = &nums[3]; //得到数组尾元素之后那个并不存在的元素的地址
for(;p != last;++p){
cout << *p << endl;
}
//通过标准库函数begin和end遍历
int *begin = begin(nums); //指向nums首元素的指针
int *end = end(nums); //指向nums尾元素的下一个位置的指针
while(begin != end){
cout << *begin << endl;
++begin;
}
return 0;
}
多维数组
#include <iostream>
using namespace std;
int main(){
int a[3][4]; //大小为3的数组。每个元素是含有4个整数的数组
int (*p)[4] = a; //p指向含有4个整数的数组
for(auto &row:a){
for(auto &col:row){
cout << *col << " ";
}
cout << endl;
}
for (int (*p)[4] = begin(a);p != end(a);p++){
for (int *q = begin(*p);q != end(*p); q++){
cout << *q <<endl;
}
}
return 0;
}
动态数组
new和delete运算符都是一次分配/释放一个对象。
但是像vector和string都是在连续内存中保存它们的元素,因此,当容器需要重新分配内存时,必须一次性为很多元素分配内存。
通过new来分配一个对象数组
分配一个数组会得到一个元素类型的指针。虽然我们称new T[]分配的内存为“动态数组”,当用new分配一个数组时,我们并未得到一个数组类型的对象,而是得到一个数组元素类型的指针。由于分配的内存并不是一个数组类型,因此不能对动态数组调用begin或end。也不能用for语句来处理动态数组中的元素。
#include <iostream>
#include <list>
using namespace std;
int main(){
int *pa = new int[10]; //10个未初始化的int ,pa指向第一个int元素
int *pa2 = new int[10](); //10个值初始化为0的int
int *pa3 = new int[10]{0,1,2,3,4,5,6,7,8,9}; //使用初始化器初始化
string *spa = new string[10]; //10个空string
string *spa2 = new string[10](); //10个空string
string *spa3 = new string[10]{"a","b","c",string(3,'x')}; //前四个使用初始化器初始化,后面的就默认进行值初始化
return 0;
}
释放动态数组
通过delete []a,销毁a指向的数组中的元素,并释放对应的内存。数组中的元素按照逆序进行销毁。