1.数组
1.一维数组的基本概念
数组是一组数据类型相同的变量,可以存放一组数据。
特点:
①数组中的每个数据元素具有相同的数据类型。
②数组占用一段连续的内存空间。
1)创建数组
声明数组的语法:数据类型 数组名[数组长度];
注意:数组长度必须是整数,可以是常量,也可以是变量和表达式。
C90规定必须用常量表达式指明数组的大小,C99允许使用整型非常量表达式。经测试,在VS中可以用整型非常量表达式,不能用变量;但是,Linux中还可以用变量。
2)数组的使用
可以通过下标访问数组中元素,数组下标从0开始。
数组中每个元素的特征和使用方法与单个变量完全相同。
语法:数组名[数组下标]
注意:
●数组下标也必须是整数,可以是常量,也可以是变量。
●合法的数组下标取值是:0~(数组长度-1)。
3)数组占用内存的情况
数组在内存中占用的空间是连续的。
用sizeof(数组名)可以得到整个数组占用内存空间的大小(只适用于C++基本数据类型)。
4)数组的初始化
声明的时候初始化:
数据类型 数组名[数组长度] = { 值1,值2,值3, ...... , 值n};
int arr[length] = {1, 2, 3...};
数据类型 数组名[ ] = { 值1,值2,值3, ...... , 值n};
int arr[] = {1, 2, ..., n};
数据类型 数组名[数组长度] = { 0 }; // 把全部的元素初始化为0。
int arr[length] = {0};
数据类型 数组名[数组长度] = { }; // 把全部的元素初始化为0。
int arr[length] = { };
注意:如果{}内不是数组长度个数据,剩余数据用0补全,但是,不建议这么用,你可能在数组中漏了某个值。如果想把数组中全部的元素初始化为0,可以在{}内只填一个0或什么也不填。
C++11标准可以不写等于号。
5)清空数组
用memset()函数可以把数组中全部的元素清零。(只适用于C++基本数据类型)
函数原型:
void *memset(void *s, int c, size_t n);
memset是C和C++标准库中的一个函数,其功能是把s指向的内存区域的前n个字节设置成字符c(转换为unsigned char类型)。
注意,在Linux下,使用memcpy()函数需要包含头文件#include <string.h>
6)复制数组
用memcpy()函数可以把数组中全部的元素复制到另一个相同大小的数组。(只适用于C++基本数据类型)
函数原型:
void *memcpy(void *dest, const void *src, size_t n);
注意,在Linux下,使用memcpy()函数需要包含头文件#include <string.h>
示例:
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
int bh[] = {3, 6, 1,6,7,4,3,5,6,7,8,322,2,3,9}; // 超女编号。
string name[3]; // 超女姓名。
for (int ii = 0; ii < sizeof(bh)/sizeof(int); ii++)
{
cout << "bh["<<ii<<"]=" << bh[ii] << endl;
}
int bh1[sizeof(bh) / sizeof(int)]; // 数组长度必须是整数,可以是常量,也可以是变量和表达式。
memcpy(bh1, bh, sizeof(bh)); // 把数组bh中的内容复制到bh1。
for (int ii = 0; ii < sizeof(bh1) / sizeof(int); ii++)
{
cout << "bh1[" << ii << "]=" << bh1[ii] << endl;
}
}
2.一维数组和指针
1)指针的算术
将一个整型变量加1后,其值将增加1。
但是,将指针变量(地址的值)加1后,增加的量等于它指向的数据类型的字节数。
2)数组的地址
a)数组在内存中占用的空间是连续的。
b)C++将数组名解释为数组第0个元素的地址。
c)数组第0个元素的地址和数组首地址的取值是相同的。
d)数组第n个元素的地址是:数组首地址+n
e)C++编译器把 数组名[下标] 解释为 *(数组首地址+下标)
3)数组的本质
数组是占用连续空间的一块内存,数组名被解释为数组第0个元素的地址。C++操作这块内存有两种方法:数组解释法和指针表示法,它们是等价的。
4)数组名不一定会被解释为地址
在多数情况下,C++将数组名解释为数组的第0个元素的地址,但是,将sizeof运算符用于数据名时,将返回整个数组占用内存空间的字节数。
可以修改指针的值,但数组名是常量,不可修改。
示例:
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
char a;
cout << "sizeof(char) =" << sizeof(char) << endl; // 1字节
short b;
cout << "sizeof(short) =" << sizeof(short) << endl; // 2字节
int c;
cout << "sizeof(int) =" << sizeof(int) << endl; // 4字节
double d;
cout << "sizeof(double) =" << sizeof(double) << endl; // 8字节
cout << "a的地址是:" << (void*)&a << endl;
cout << "a的地址+1是:" << (void*)(&a + 1) << endl;
cout << "b的地址是:" << (void*)&b << endl;
cout << "b的地址+1是:" << (void*)(&b + 1) << endl;
cout << "c的地址是:" << (void*)&c << endl;
cout << "c的地址+1是:" << (void*)(&c + 1) << endl;
cout << "d的地址是:" << (void*)&d << endl;
cout << "d的地址+1是:" << (void*)(&d + 1) << endl;
return 0;
}
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
double a[5];
cout << "a的值是:" << (long long) a << endl;
cout << "&a的值是:" << (long long)&a << endl;
cout << "a[0]的地址是:" << (long long) &a[0] << endl;
cout << "a[1]的地址是:" << (long long) &a[1] << endl;
cout << "a[2]的地址是:" << (long long) &a[2] << endl;
cout << "a[3]的地址是:" << (long long) &a[3] << endl;
cout << "a[4]的地址是:" << (long long) &a[4] << endl;
double* p = a;
cout << "p的值是:" << (long long)p << endl;
cout << "p+0的值是:" << (long long)(p+ 0) << endl;
cout << "p+1的值是:" << (long long)(p + 1) << endl;
cout << "p+2的值是:" << (long long)(p + 2) << endl;
cout << "p+3的值是:" << (long long)(p + 3) << endl;
cout << "p+4的值是:" << (long long)(p + 4) << endl;
return 0;
}
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
int a[5] = { 3 , 6 , 5 , 8 , 9 };
// 用数组表示法操作数组。
cout << "a[0]的值是:" << a[0] << endl;
cout << "a[1]的值是:" << a[1] << endl;
cout << "a[2]的值是:" << a[2] << endl;
cout << "a[3]的值是:" << a[3] << endl;
cout << "a[4]的值是:" << a[4] << endl;
// 用指针表示法操作数组。
int* p = a;
cout << "*(p+0)的值是:" << *(p+ 0) << endl;
cout << "*(p+1)的值是:" << *(p + 1) << endl;
cout << "*(p+2)的值是:" << *(p + 2) << endl;
cout << "*(p+3)的值是:" << *(p + 3) << endl;
cout << "*(p+4)的值是:" << *(p + 4) << endl;
}
5)指针也是迭代器
vector 和string的迭代器支待的运算,数组的指针全都支待。例如,允许使用递增运算符将指向数组元素的指针向前移动到下一个位置上:
int arr[]= {0,1,2,3,4,5,6,7,8,9};
int *p= arr;//p指向arr的第一个元素
++p;
//p指向arr[1]
我们可以设法获取数组尾元素之后的那个并不存在的元素的地址:
int *e = &arr[10];//指向arr尾元素的下一位置的指针
这里显然使用下标运算符索引了一个不存在的元素,arr有10个元素,尾元素所在位置的索引是9,接下来那个不存在的元素唯一的用处就是提供其地址用于初始化e。就像尾后迭代器一样,尾后指针也不指向具体的元素。
6)指针运算
和迭代器一样,两个指针相减的结果是它们之间的距离。参与运算的两个指针必须指向同一个数组当中的元素:
auto n = end(arr) - begin(arr);//n的值是5,也就是arr中元素的数量
两个指针相减的结果的类型是种名为ptrdiff_t的标准库类型,和size_t一样,ptrdiff_t也是一种定义在cstddef头文件中的机器相关的类型。因为差值可能为负值,所以ptrdiff_t是一种带符号类型。
只要两个指针指向同一个数组的元素,或者指向该数组的尾元素的下一位置,就能利用关系运算符对其进行比较。例如,可以按照如下的方式遍历数组中的元素:
int *b = arr, *e = arr + sz;
while(b < e)
{
//使用*b
++b;
}
如果两个指针分别指向不相关的对象,则不能比较它们:
inti= 0, sz = 42;
int *p = &i, *e = &sz;
//未定义的:p和e元关,因此比较毫无意义!
while (p < e)
尽管作用可能不是特别明显,但必须说明的是,上述指针运算同样适用于空指针和所指对象并非数组的指针。在后一种情况下,两个指针必须指向同一个对象或该对象的下一位置。如果p是空指针,允许给p加上或减去一个值为0的整型常量表达式。两个空指针也允许彼此相减,结果当然是0。
3.一维数组用于函数的参数
1)指针的数组表示
在C++内部,用指针来处理数组。
C++编译器把数组名[下标] 解释为 *(数组首地址+下标)
C++编译器把地址[下标] 解释为 *(地址+下标)
2)一维数组用于函数的参数
一维数组用于函数的参数时,只能传数组的地址,并且必须把数组长度也传进去,除非数组中有最后一个元素的标志。
书写方法有两种:
void func(int* arr, int len);
void func(int arr[], int len);
注意:
在函数中,可以用数组表示法,也可以用指针表示法。
在函数中,不要对指针名用sizeof运算符,它不是数组名。
示例:
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
char a[20]; // 这是一个长度为20的字符型数组。
int* p = (int *)a; // 让整型指针p指向数组a的内存。
for (int ii = 0; ii < 6; ii++)
{
p[ii] = ii + 300; // 用数组表示法操作指针。
}
for (int ii = 0; ii < 6; ii++)
{
cout << "*(p+" << ii << ")的值是:" << *(p + ii) << endl; // 地址[下标] 解释为 *(地址+下标)。
}
}
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
// void func(int *arr,int len)
void func(int arr[],int len)
{
for (int ii = 0; ii < len; ii++)
{
cout << "arr[" << ii << "]的值是:" << arr[ii] << endl; // 用数组表示法操作指针。
cout << "*(arr+" << ii << ")的值是:" << *(arr + ii) << endl; // 地址[下标] 解释为 *(地址+下标)。
}
}
int main()
{
int a[] = {2,8,4,6,7,1,9};
func(a, sizeof(a) / sizeof(int));
}
4.用new动态创建一维数组
普通数组在栈上分配内存,栈很小;如果需要存放更多的元素,必须在堆上分配内存。
动态创建一维数组的语法:数据类型 *指针=new 数据类型[数组长度];
释放一维数组的语法:delete[] 指针;
注意:
●动态创建的数组没有数组名,不能用sizeof运算符。
● 可以用数组表示法和指针表示法两种方式使用动态创建的数组。
●必须使用delete[]来释放动态数组的内存(不能只用delete)。
● 不要用delete[]来释放不是new[]分配的内存。
●不要用delete[]释放同一个内存块两次(否则等同于操作野指针)。
●对空指针用delete[]是安全的(释放内存后,应该把指针置空nullptr)。
●声明普通数组的时候,数组长度可以用变量,相当于在栈上动态创建数组,并且不需要释放。
● 如果内存不足,调用new会产生异常,导致程序中止;如果在new关键字后面加(std::nothrow)选项,则返回nullptr,不会产生异常。
●为什么用delete[]释放数组的时候,不需要指定数组的大小?因为系统会自动跟踪已分配数组的内存。
示例:
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
int *arr=new int[8]; // 创建8个元素的整型数组。
for (int ii = 0; ii < 8; ii++)
{
arr[ii] = 100 + ii; // 数组表示法。
cout << "arr[" << ii << "]=" << *(arr + ii) << endl; // 指针表示法。
}
delete[]arr;
}
5.一维数组的排序qsort
qsort()函数用于对各种数据类型的数组进行排序。
函数的原型:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
第一个参数:数组的起始地址。
第二个参数:数组元素的个数(数组长度)。
第三个参数:数组元素所占的大小(sizeof(数组的数据类型))。
第四个参数:回调函数的地址。
回调函数决定了排序的顺序,声明如下:
int compar(const void *p1, const void *p2);
1)如果函数的返回值<0,那么p1所指向元素会被排在p2所指向元素的前面。
2)如果函数的返回值==0,那么p1所指向元素与p2所指向元素的顺序不确定。
3)如果函数的返回值>0,那么p1所指向元素会被排在p2所指向元素的后面。
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
qsort()函数的其它细节:
●形参中的地址用void是为了支持任意数据类型,在回调函数中必须具体化。
●为什么需要第三个形参size_t size?
●size_t是C标准库中定义的,在64位系统中是8字节无符号整型(unsigned long long)。
typedef unsigned long long size_t
●排序的需求除了升序和降序,还有很多不可预知的情况,只能用回调函数。
示例:
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int compasc(const void* p1, const void* p2) // 升序的回调函数。
{
return *((int*)p1) - *((int*)p2);
}
int compdesc(const void* p1, const void* p2) // 降序的回调函数。
{
return *((int*)p2) - *((int*)p1);
}
int main()
{
int a[8] = { 4,2,7,5,8,6,1,3 };
qsort(a,sizeof(a)/sizeof(int),sizeof(int),compasc); // 对数组a进行升序排序。
for (int ii = 0; ii < 8; ii++)
{
cout << "a[" << ii << "]=" << a[ii] << endl;
}
qsort(a, sizeof(a) / sizeof(int), sizeof(int), compdesc); // 对数组a进行降序排序。
for (int ii = 0; ii < 8; ii++)
{
cout << "a[" << ii << "]=" << a[ii] << endl;
}
}
6.一维数组的查找-折半查找
折半查找也叫二分查找,只适用于已排序的数组(升序降序都可以)。
示例:
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
// 在arr中查找key,成功返回key在arr中的数组下标,失败返回-1。
int search(int arr[], int len, int key)
{
int low = 0, high = len - 1, mid; // 初始化:low=0,high=数组长度-1。
while (low <= high)
{
mid = (low + high) / 2; // 计算mid指针的位置。
if (arr[mid] == key)
return mid; // 查找成功。
else if (arr[mid] > key)
high = mid - 1; // 继续在前半区查找。
else
low = mid + 1; // 继续在后半区查找。
}
return -1; // 查找失败。
}
int main()
{
int a[10] = { 7,9,12,16,21,25,30,35,41,48 }; // 必须是已排好序的数组。
if (search(a, 10, 30) >= 0) cout << "在数组a中查找30成功。\n";
else cout << "在数组a中查找30失败。\n";
}
7.二维数组
一维数组的数学概念是线性表,二维数组的数学概念是矩阵。
1)创建二维数组
声明二维数组的语法:数据类型 数组名[行数][列数];
注意:数组长度必须是整数,可以是常量,也可以是变量和表达式。
C90规定必须用常量表达式指明数组的大小,C99允许使用整型非常量表达式。经测试,在VS中可以用用整型非常量表达式,不能用变量;但是,Linux中还可以用变量。
2)二维数组的使用
可以通过行下标和列下标访问二维数组中元素,下标从0开始。
二维数组中每个元素的特征和使用方法与单个变量完全相同。
语法:数组名[行下标][列下标]
注意:
●二维数组下标也必须是整数,可以是常量,也可以是变量。
●合法的行下标取值是:0~(行数-1)。
●合法的列下标取值是:0~(列数-1)。
3)二维数组占用内存的情况
用sizeof(数组名)可以得到整个二维数组占用内存空间的大小(只适用于C++基本数据类型)。
二维数组在内存中占用的空间是连续的。
4)二维数组的初始化
声明的时候初始化:
数据类型 数组名[行数][列数] = { {数据1,数据2 } ,{数据3,数据4 },...... };
数据类型 数组名[行数][列数] = { 数据1,数据2,数据3,数据4, ......};
数据类型 数组名[ ][列数] = { 数据1,数据2,数据3,数据4,......};
数据类型 数组名[行数][列数] = { 0 }; // 把全部的元素初始化为0。
数据类型 数组名[行数][列数] = { }; // 把全部的元素初始化为0。
注意:如果{}内不足数组长度个数据,剩余数据用0补全,但是,不建议这么用,你可能在数组中漏了某个值。如果想把数组中全部的元素初始化为0,可以在{}内只填一个0或什么也不填。
C++11标准可以不写等于号。
5)清空二维数组
用memset()函数可以把二维数组中全部的元素清零。(只适用于C++基本数据类型)
函数原型:void *memset(void *s, int c, size_t n);
注意,在Linux下,使用memcpy()函数需要包含头文件#include <string.h>
6)复制二维数组
用memcpy()函数可以把二维数组中全部的元素复制到另一个相同大小的数组(没说多少维)。(只适用于C++基本数据类型)
函数原型:void *memcpy(void *dest, const void *src, size_t n);
注意,在Linux下,使用memcpy()函数需要包含头文件#include <string.h>
示例:
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
// int bh[2][3] = { {11,12,13},{21,22,23} }; // 声明一个两行三列的二维数组,存放超女的编号。
// int bh[2][3] = { 11,12,13,21,22,23 };
int bh[][3] = { 11,12,13,21,22,23 };
/*bh[0][0] = 11; bh[0][1] = 12; bh[0][2] = 13;
bh[1][0] = 21; bh[1][1] = 22; bh[1][2] = 23;*/
/*cout << "bh[0][0] = " << bh[0][0] << " bh[0][1] = " << bh[0][1] << " bh[0][2] = " << bh[0][2] << endl;
cout << "bh[1][0] = " << bh[1][0] << " bh[1][1] = " << bh[1][1] << " bh[1][2] = " << bh[1][2] << endl;*/
for (int ii = 0; ii < 2; ii++) // 第一层循环表示行数,循环继续的条件是计数器小于行数。
{
for (int jj = 0; jj < 3; jj++) // 第二层循环表示列数,循环继续的条件是计数器小于列数。
{
cout << "&bh["<<ii<<"]["<<jj<<"] = " << (long long) & bh[ii][jj] << " "; // 处理二维数组的每个元素。
}
cout << endl; // 每处理一行数据后,输出一个换行。
}
int* p = (int *)bh;
for (int ii = 0; ii < 6; ii++)
{
cout << "p[" << ii << "]=" << p[ii] << endl; // 一维数组的数组表示法。
}
}
8.二维数组用于函数的参数
int* p; // 整型指针。
int* p[3]; // 一维整型指针数组,元素是3个整型指针(p[0]、p[1]、p[2])。
int* p(); // 函数p的返回值类型是整型的地址。
int (*p)(int ,int); // p是函数指针,函数的返回值是整型。
1)行指针(数组指针)
声明行指针的语法:数据类型 (*行指针名)[行的大小]; // 行的大小即数组长度。
int (*p1)[3]; // p1是行指针,用于指向数组长度为3的int型数组。
int (*p2)[5]; // p2行指针,用于指向数组长度为5的int型数组。
double (*p3)[5]; // p3是行指针,用于指向数组长度为5的double型数组。
一维数组名被解释为数组第0个元素的地址。
对一维数组名取地址得到的是数组的地址,是行地址。
2)二维数组名是行地址
int bh[2][3] = { {11,12,13},{21,22,23} };
bh是二维数组名,该数组有两行元素,每一个元素本身又是一个数组长度为3的整型数组。
bh被解释为数组长度为3的整型数组类型的行地址。
如果存放bh的值,要用数组长度为3的整型数组类型的行指针。
int (*p)[3]=bh;
int bh[4][2][3];
bh是三维数组名,该数组有4个元素,每一个元素本身又是一个2行3列的二维数组。
bh被解释为2行3列的二维数组类型的二维地址。
如果存放bh的值,要用2行3列的二维数组类型的二维指针。
int (*p)[2][3]=bh;
3)把二维数组传递给函数
如果要把bh传给函数,函数的声明如下:
void func(int (*p)[3],int len);
void func(int p[][3],int len);
示例:
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
int main()
{
int a[10];
cout << "数组a第0个元素的地址:" << a<< endl;
cout << "数组a的地址:" << &a << endl;
cout << "数组a第0个元素的地址+1:" << a + 1 << endl; // 地址的增加量是4。
cout << "数组a的地址+1:" << &a + 1 << endl; // 地址的增加量是40。
}
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
// void func(int(*p)[3], int len)
void func(int p[][3], int len)
{
for (int ii = 0; ii < len; ii++)
{
for (int jj = 0; jj < 3; jj++)
cout << "p[" << ii << "][" << jj << "]=" << p[ii][jj] << " " ;
cout << endl;
}
}
int main()
{
int bh[2][3] = { {11,12,13},{21,22,23} };
func(bh,2);
}
#include <iostream> // 包含头文件。
using namespace std; // 指定缺省的命名空间。
void func(int (*p)[2][3])
{
int ii = 1;
// 遍历三维数组p,给它的每个元素赋值。
for (int a = 0; a < 4; a++)
for (int b = 0; b < 2; b++)
for (int c = 0; c < 3; c++)
p[a][b][c] = ii++;
}
int main()
{
int bh[4][2][3]; // 假设有4个超女方阵,每个方阵有2行,每行有3个超女。
memset(bh, 0, sizeof(bh));
func(bh);
for (int a = 0; a < 4; a++)
{
for (int b = 0; b < 2; b++)
{
for (int c = 0; c < 3; c++)
cout << bh[a][b][c] <<"\t";
cout << endl; // 每显示一行后,输出一个换行符。
}
cout << endl<<endl; // 每显示一个方阵后,输出两个换行符。
}
}
参考资料:
来源于慕课网和C++ Primer
9.STL中的array
1.array介绍
在C++标准库中,array是固定大小的序列容器,array中包含特定个数并且严格按照线性序列排序的元素。因此array允许对元素进行随机访问,指向某一元素的指针可以通过偏移访问其他元素。在array内部,它只保存自己包含的元素,其他任何信息都不保存,包括自身的大小。
array是C++ 标准模板库的一部分,因此,想要使用array,需要在程序中包含头文件array
#include <array>
2.array定义和初始化
格式:
包含头文件array之后,我们可以使用下边的格式定义array
std::array<object_type,size> variable_name;
object_type
规定了array中可以存放哪种类型的元素。
size
为array的大小。
variable_name
为array名。
方式: 定义array的常用方式如下所示:
std::array<int, 5> n;
std::array<int, 5> n = {1, 2, 3, 4, 5};
std::array<int, 5> n { {1, 2, 3, 4, 5} };
示例代码
3.array的迭代器
array中的迭代器包括以下几个,分别为:
array.begin():指向array首元素的迭代器
array.end():指向array尾元素下一个位置的迭代器
array.rbegin():指向array尾元素的反向迭代器,即rbegin()指向尾元素,rbegin-1指向倒数第二个元素
array.rend():指向array头元素前一个位置的反向迭代器,即rend()指向头元素前一个位置元素,rbegin-1指向第一个元素
array.cbegin():指向array首元素的迭代器
array.cend():指向array尾元素下一个位置的迭代器
array.crbegin():指向array尾元素的反向迭代器
array.crend():指向array头元素前一个位置的反向迭代器
代码示例如下:
#include <array>
#include <iostream>
using std::array;
using std::cout;
using std::endl;
int main()
{
array<int, 5> myarray = { 1, 2, 3, 4, 5 };
cout << "初始化后array为: ";
for (auto num : myarray)
{
cout << num << " ";
}
cout << endl;
// array.begin()为指向array头元素的迭代器
auto begin_iterator = myarray.begin();
cout << "begin() 指向的元素:" << *begin_iterator << endl;
// array.end()为指向array尾元素后一个位置的迭代器,则myarray.end()-1指向尾元素
auto end_iterator = myarray.end();
cout << "end()-1 指向的元素:" << *(end_iterator - 1) << endl;
// array.rbegin()为指向尾元素的迭代器,即反向(r)的头(begin)迭代器
auto rbegin_iterator = myarray.rbegin();
cout << "rbegin() 指向的元素:" << *rbegin_iterator << endl;
// array.rend()为指向头元素的前一个位置的迭代器,即反向(r)尾(end)迭代器,则myarray.rend()-1指向头元素
auto rend_iterator = myarray.rend();
cout << "rend()-1 指向的元素:" << *(rend_iterator - 1) << endl;
// array.cbegin()为指向array头元素的const迭代器,与begin()不同的是返回迭代器类型为array<int>::const_iterator
auto cbegin_iterator = myarray.cbegin();
cout << "cbegin() 指向的元素:" << *cbegin_iterator << endl;
// array.cend()为指向array尾元素下一个位置的const迭代器,与end()不同的是返回迭代器类型为array<int>::const_iterator
auto cend_iterator = myarray.cend();
cout << "cend()-1 指向的元素:" << *(cend_iterator - 1) << endl;
// array.crbegin()为指向尾元素的const迭代器,即反向(r)的const(c)头(begin)迭代器
auto crbegin_iterator = myarray.crbegin();
cout << "crbegin() 指向的元素: " << *crbegin_iterator << endl;
// array.crend()为指向头元素下一个位置的const迭代器,即反向(r)的const(c)尾(end)迭代器
auto crend_iterator = myarray.crend();
cout << "crend()-1 指向的元素: " << *(crend_iterator - 1) << endl;
return 0;
}
输出:
初始化后array为: 1 2 3 4 5
begin() 指向的元素:1
end()-1 指向的元素:5
rbegin() 指向的元素:5
rend()-1 指向的元素:1
cbegin() 指向的元素:1
cend()-1 指向的元素:5
crbegin() 指向的元素: 5
crend()-1 指向的元素: 1
4.array大小(容量)相关方法
4.1 size()——元素个数
要想知道array中有多少元素,使用array.size()方法,作用是返回array中元素的个数。示例如下:
示例代码
#include <iostream>
#include <array>
using std::cout;
using std::endl;
using std::array;
int main()
{
array<int, 5> myarray = { 1,2,3,4,5 };
cout << "初始化后的myarray为:";
for (auto num : myarray)
{
cout << num << " ";
}
int sizeOfArray = myarray.size();
cout << "\nmyarray中的元素个数为:" << sizeOfArray;
return 0;
}
输出:
初始化后的myarray为:1 2 3 4 5
myarray中的元素个数为:5
4.2 max_size()——最多能容纳元素个数:
要想知道array最多可以有多少元素,使用array.max_size()方法,作用是返回array中最多能容纳元素个数。在array容器中,array中最多能容纳的元素个数即为array中实际元素个数,因为array容器固定,不可以扩展或者收缩。
示例如下:
#include <iostream>
#include <array>
using std::cout;
using std::endl;
using std::array;
int main()
{
array<int, 5> myarray = { 1,2,3,4,5 };
cout << "初始化后的myarray为:";
for (auto num : myarray)
{
cout << num << " ";
}
int sizeOfArray = sizeof(myarray)/sizeof(int);
cout << "\nmyarray.size为:" << sizeOfArray;
int max_sizeOfArray = myarray.max_size();
cout << "\nmyarray.max_size为:" << max_sizeOfArray;
return 0;
}
4.3empty()——检查array是否为空
想要检查array是否为空,使用array.empty()方法,如果为空返回true,否则返回false。
示例如下:
#include <array>
#include <iostream>
using std::array;
using std::cout;
using std::endl;
int main()
{
array<int, 5> myarray = { 1, 2, 3, 4, 5 };
array<int, 0> myarray2;
cout << "初始化后的myarray为:";
for (auto num : myarray)
{
cout << num << " ";
}
cout << "\n初始化后的myarray2为:";
for (auto num : myarray2)
{
cout << num << " ";
}
bool isEmpty = myarray.empty();
bool isEmpty2 = myarray2.empty();
cout << "\nmyarray.empty() = " << isEmpty << endl;
cout << "myarray2.empty() = " << isEmpty2 << endl;
return 0;
}
输出:
初始化后的myarray为:1 2 3 4 5
初始化后的myarray2为:
myarray.empty() = 0
myarray2.empty() = 1
5.array常用操作
5.1at()——访问array元素
使用元素的索引来访问array中的元素,在array中,可以使用
array.at(index)
或者array[index]
访问索引为index的元素,示例代码如下:
#include<iostream>
#include<array>
using std::endl;
using std::cout;
using std::array;
int main()
{
array<int, 5> myarray = { 1,2,3,4,5 };
cout << "myarray索引为0的元素: " << myarray.at(0) << endl;
cout << "myarray索引为1元素: " << myarray.at(1) << endl;
cout << "myarray索引为2元素: " << myarray[2] << endl;
return 0;
}
输出:
myarray索引为0的元素: 1
myarray索引为1元素: 2
myarray索引为2元素: 3
两种访问方法的优劣
虽然使用 array.at(index)和array[index]的方式都能访问索引为index的元素,但是,如果此元素不存在,即索引越界时, array.at(index)会抛出一个异常,但是array[index]会返回一个垃圾值,因此,最好使用array.at(index),在其他容器中也是这样。
5.2front()——访问array头元素
front()返回array第一个元素
示例如下:
#include <array>
#include <iostream>
using std::array;
using std::cout;
using std::endl;
int main()
{
array<int, 5> myarray = { 1, 2, 3, 4, 5 };
cout << "初始化后的myarray为:";
for (auto num : myarray)
{
cout << num << " ";
}
int front = myarray.front();
cout << "\nmyarray中的第一个元素为:" << front;
return 0;
}
输出:
初始化后的myarray为:1 2 3 4 5
myarray中的第一个元素为:1
5.3back()——访问array尾元素
front()返回array最后一个元素
示例如下:
#include <array>
#include <iostream>
using std::array;
using std::cout;
using std::endl;
int main()
{
array<int, 5> myarray = { 1, 2, 3, 4, 5 };
cout << "初始化后的myarray为:";
for (auto num : myarray)
{
cout << num << " ";
}
int back = myarray.back();
cout << "\nmyarray中的最后一个元素为:" << back;
return 0;
}
输出:
初始化后的myarray为:1 2 3 4 5
myarray中的最后一个元素为:5
5.4data()——返回指向array中第一个元素的指针
使用array.data()会返回指向array中第一个元素的指针,因为array中元素线性排列,则可以使用data()的偏移来访问array中其他的元素。
示例如下:
#include <iostream>
#include <array>
using std::cout;
using std::endl;
using std::array;
int main()
{
array<int, 5> myarray = { 4,3,1,2,6 };
cout << "初始化后的myarray为:";
for (auto num : myarray)
{
cout << num << " ";
}
int* sizeOfArray = myarray.data();
cout << "\nmyarray中第一个元素为:" << *sizeOfArray;
cout << "\nmyarray中第二个元素为:" << *(sizeOfArray + 1);
cout << "\nmyarray中第五个元素为:" << *(sizeOfArray + 4);
return 0;
}
输出:
初始化后的myarray为:4 3 1 2 6
myarray中第一个元素为:4
myarray中第二个元素为:3
myarray中第五个元素为:6
5.5fill(n)——使用n填充array
可以使用array.fill(n)来填充array,执行的结果是数组中的元素都变为n。
示例如下:
#include <iostream>
#include <array>
using std::cout;
using std::endl;
using std::array;
int main()
{
// 定义一个array,未初始化
array<int, 5> myarray;
myarray.fill(2);
cout << "fill(2)后的myarray为:";
for (auto num : myarray)
{
cout << num << " ";
}
myarray.fill(5);
cout << "\nfill(5)后的myarray为:";
for (auto num : myarray)
{
cout << num << " ";
}
return 0;
}
输出:
fill(2)后的myarray为:2 2 2 2 2
fill(5)后的myarray为:5 5 5 5 5
5.6swap()——交换两个array容器中的元素
可以使用swap()来交换两个array中的元素,前提是两个array容器中存储的元素类型以及元素个数都相同 。
示例如下:
#include <array>
#include <iostream>
using std::array;
using std::cout;
using std::endl;
int main()
{
array<int, 5> myarray1 = { 1, 2, 3, 4, 5 };
array<int, 5> myarray2 = { 11, 22, 33, 44, 55 };
cout << "初始化后的myarray1为:";
for (auto num : myarray1)
{
cout << num << " ";
}
cout << "\n初始化后的myarray2为:";
for (auto num : myarray2)
{
cout << num << " ";
}
myarray1.swap(myarray2);
cout << "\n交换元素后myarray1为:";
for (auto num : myarray1)
{
cout << num << " ";
}
cout << "\n交换元素后myarray2为:";
for (auto num : myarray2)
{
cout << num << " ";
}
return 0;
}
输出:
初始化后的myarray1为:1 2 3 4 5
初始化后的myarray2为:11 22 33 44 55
交换元素后myarray1为:11 22 33 44 55
交换元素后myarray2为:1 2 3 4 5
6.array容器和数组的区别与联系
联系:
●都使用连续空间存储元素,可以进行随机访问
●元素个数都固定
区别:
●数组是不安全的,array是比较安全的,array避免访问越界
●array提供了更好的数据访问机制,即可以使用front()和back()以及at()访问方式,使得访问更加安全。而数组只能通过下标访问,在写程序中很容易出现越界的错误
●array容器支持迭代器,访问遍历更加方便
●array提供了size()和Empty(),而数组只能通过sizeof()/strlen()以及遍历计数来获取大小和是否为空
参考:C++array容器用法解析,它与普通数组究竟有何不同?
10.STL中的vector
1.vector概述
vector是最常用的容器之一,功能十分强大,可以储存、管理各种类型的数据。在很多情况下可以用来代替功能比较局限的普通数组,因为我们知道,普通数组只能实现一对一的映射而不能实现一对多的映射,vector就是专门为了解决这个问题而诞生的。vector也可以称为动态数组,因为其大小是根据实时更新而变化的,正因为如此vector显得更加灵活易用。
vector的数据安排以及操作方式,与array非常相似,两者的唯一差别在于空间的运用的灵活性。Array是静态空间,一旦配置了就不能改变,要换大一点或者小一点的空间,可以,一切琐碎得由自己来,首先配置一块新的空间,然后将旧空间的数据搬往新空间,再释放原来的空间。Vector是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。因此vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必害怕空间不足而一开始就要求一个大块头的array了。
Vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率,一旦vector旧空间满了,如果客户每新增一个元素,vector内部只是扩充一个元素的空间,实为不智,因为所谓的扩充空间(不论多大),一如刚所说,是”配置新空间-数据移动-释放旧空间”的大工程,时间成本很高,应该加入某种未雨绸缪的考虑,稍后我们便可以看到vector的空间配置策略。
C++语言既有类模板(class template),也有函数模板,其中vector是个类模板。
模板本身不是类或函数,相反可以将模板看作为编译器生成类或函数编写的份说明。编译器根据模板创建类或函数的过程称为实例化(instantiation),当使用模板时,需要指出编译器应把类或函数实例化成何种类型。
NOTE:vector是模板而非类型,由vector,生成的类型必须包含vector中元素的类型,例如vector<int>。
vector能容纳绝大多数类型的对象作为其元素,但是因为引用不是对象,所以不存在包含引用的vector。除此之外,其他大多数(非引用)内置类型和类类型都可以构成vector对象,甚至组成vector的元素也可以是vector。
2.vector的定义和初始化
使用vector需要包含vector头文件如下:
#include<vector>
using std::vector;
和任何种类类型一样,vector模板控制着定义和初始化向量的方法。表3.4列出了vector对象的常用方法
表3.4: 初始化vector对象的方法 | |
vector<T> v1 | v1是一个空vector,它潜在的元素是T类型的,执行默认初始化 |
vector<T> v2(v1) | v2中包含有v1所有元素的副本 |
vector<T> v2 = v1 | 等价于v2(v1), v2中包含有v1所有元素的副本 |
vector<T> v3(n, val) | v3包含了n个重复的元素,每个元素的值都是val |
vector<T> v4(n) | v4包含了n个重复地执行了值初始化的对象 |
vector<T> v5{a,b,c...} | v5包含了初始值个数的元素,每个元素被赋予相应的初始值 |
vector<T> v5={a,b,c...} | 等价于v5{a,b,c...} |
例子:
vector<int> v1;//声明一个int型向量v1,默认初始化为0
vector<int> v2(v1);//声明并用向量v1初始化向量v2,v2是包含有v1所有元素的副本
vector<int> v2 = v1;//等价于v2(v1),声明并用向量v1初始化向量v2,v2是包含有v1所有元素的副本
vector<int> v3(10, 1);//向量v3包含了10个重复的元素,每个元素都是1
vector<int> v4(10);//向量v1包含了10个重复地执行了值初始化的对象
vector<int> v5{a, b, c,..};//向量v1包含了初始值个数的元素,每个元素被赋予相应的初始值
vector<int> v5={ a, b, c,.. };//等价于v5{a, b, c,..};
vector<int> v6(a.begin(), a.begin() + 3);//将v6向量中从第0个到第2个(共3个)作为向量b的初始值
vector<string> v7 = { "hi","my","name","is","lee" };
int arr[] = { 0,1,2,3,4,5,6 };
vector<int>v8 = { 0,1,2,3,4 };//列表初始化
vector<int>v9(arr, arr + 3);//将arr数组的前是三个元素作为向量v9的初始值,区间为[arr,arr+3)
vector<int>v10(&arr[1], &arr[4]);//将arr[1]~arr[4]之间的元素作为向量v8的初始值
(1)值初始化
通常情况下,可以只提供vector对象容纳的元素数量而不用略去初始值。此时库会创建一个值初始化的(value-initialized)元素初值,并把它赋给容器中的所有元素。这个初值由vector对象中元素的类型决定。
如果vector对象的元素是内置类型,比如int,则元素初始值自动设为0。如果元素是某种类类型,比如string,则元素由类默认初始化 :
vector<int> ivec(10);//10个元素,每个都初始化为0
vector<string> svec(10);//10个元素,每个都是空string
对这种初始化的方式有两个特殊限制:其一,有些类要求必须明确地提供初始值,如果vector对象中元素的类型不支持默认初始化,我们就必须提供初始的元素值。对这种类型的对象来说,只提供元素的数量而不设定初始值无法完成初始化工作。
其二,如果只提供了元素的数量而没有设定初始值,只能使用直接初始化:
vector<int> vi= 10;//错误:必须使用直接初始化的形式指定向量大小
这里的10是用来说明如何初始化vector对象的,我们用它的本意是想创建含有10个值初始化了的元素的vector对象,而非把数字10 “拷贝” 到vector中。因此,此时不宜使用拷贝初始化。
3.vector迭代器
迭代器 | 功能 |
---|---|
begin() | 返回指向向量第一个元素的迭代器 |
end() | 返回指向向量最后一个元素之后位置的迭代器 |
rbegin() | 返回指向向量最后一个元素的反向迭代器 |
rend() | 返回指向向量第一个元素之前位置的反向迭代器 |
cbegin() | 返回指向向量第一个元素的迭代器,不能用于修改元素 |
cend() | 返回指向向量最后一个元素之后位置的迭代器,不能用于修改元素 |
crbegin() | 返回指向向量最后一个元素的反向迭代器,不能用于修改元素 |
crend() | 返回指向向量第一个元素之前位置的反向迭代器,不能用于修改元素 |
4.向vector对象中添加元素
(1)a.assign(b.begin(), b.begin()+3); //b为向量,将b的0~2个元素构成的向量赋给a
(2)a.assign(4,2); //是a只含4个元素,且每个元素为2
(3)a.back(); //返回a的最后一个元素
(4)a.front(); //返回a的第一个元素
(5)a[i]; //返回a的第i个元素,当且仅当a[i]存在2013-12-07
(6)a.clear(); //清空a中的元素
(7)a.empty(); //判断a是否为空,空则返回ture,不空则返回false
(8)a.pop_back(); //删除a向量的最后一个元素
(9)a.erase(a.begin()+1,a.begin()+3); //删除a中第1个(从第0个算起)到第2个元素,也就是说删除的元素从a.begin()+1算起(包括它)一直到a.begin()+ 3(不包括它)
(10)a.push_back(5); //在a的最后一个向量后插入一个元素,其值为5
(11)a.insert(a.begin()+1,5); //在a的第1个元素(从第0个算起)的位置插入数值5,如a为1,2,3,4,插入元素后为1,5,2,3,4
(12)a.insert(a.begin()+1,3,5); //在a的第1个元素(从第0个算起)的位置插入3个数,其值都为5
(13)a.insert(a.begin()+1,b+3,b+6); //b为数组,在a的第1个元素(从第0个算起)的位置插入b的第3个元素到第5个元素(不包括b+6),如b为1,2,3,4,5,9,8 ,插入元素后为1,4,5,9,2,3,4,5,9,8
(14)a.size(); //返回a中元素的个数;
(15)a.capacity(); //返回a在内存中总共可以容纳的元素个数
(16)a.resize(10); //将a的现有元素个数调至10个,多则删,少则补,其值随机
(17)a.resize(10,2); //将a的现有元素个数调至10个,多则删,少则补,其值为2
(18)a.reserve(100); //将a的容量(capacity)扩充至100,也就是说现在测试a.capacity();的时候返回值是100.这种操作只有在需要给a添加大量数据的时候才 显得有意义,因为这将避免内存多次容量扩充操作(当a的容量不足时电脑会自动扩容,当然这必然降低性能)
(19)a.swap(b); //b为向量,将a中的元素和b中的元素进行整体性交换
(20)a==b; //b为向量,向量的比较操作还有!=,>=,<=,>,<
对vector对象来说,直接初始化的方式适用于三种情况:初始值已知且数量较少、初始值是另一个vector对象的副本、所有元素的初始值都一样。然而更常见的情况是:创建一个vector对象时并不清楚实际所需的元素个数,元素的值也经常无法确定。还有 些时候即使元素的初值已知,但如果这些值总量较大而各不相同,那么在创建vector对象的时候执行初始化操作也会显得过于烦琐。
举个例子,如果想创建一个vector对象令其包含从0到9共10个元素,使用列表初始化的方法很容易做到这一点:但如果vector对象包含的元素是从0到99或者从0 到999呢?这时通过列表初始化把所有元素都一一罗列出来就不太合适了。对于此例来说,更好的处理方法是先创建一个空vector,然后在运行时再利用vector的成员函数 push_back向其中添加元素。push_back负责把一个值当成vector对象的尾元素“压到(push)”vector对象的“尾端(back) ”。例如:
vector<int> vec;
for (int i = 0; i != 100; i++)
{
vec.push_back(i);
}
// 循环结束后vec中100个元素,值从0到99
同样的,如果直到运行时才能知道vector对象中元素的确切个数,也应该使用刚刚这种方法创建 vector对象并为其赋值。例如,有时需要实时读入数据然后将其赋予vector对象:
//从标准输入中读取单词,将其作为vector对象的元素存储
string word;
vector<string> text; //空vector对象
while (cin >> word)
{
text.push_back(word);//把word添加到text后面
}
和之前的例子一样,本例也是先创建一个空vector,之后依次读入未知数量的值并保存到test中。
关键概念:vector对象能高效增长
C++标准要求vector应该能在运行时高效快速地添加元素。因此既然vector对 象能高效地增长,那么在定义vector对象的时候设定其大小也就没什么必要了,事实上如果这么做性能可能更差。只有一种例外情况,就是所有 (all) 元素的值都一样。一旦元素的值有所不同,更有效的办法是先定义一个空的vector对象,再在运行时向其中添加具体值。此外,允许我们进一步提升动态添加元素的性能。
开始的时候创建空的vector对象在运行时再动态添加元素,这一做法与C语言及其他大多数语言中内置数组类型的用法不同。特别是如果用惯了C或者Java,可以预计在创建vector对象时顺便指定其容量是最好的。然而事实上,通常的情况是恰恰相反。
(1)向vector对象添加元素蕴含的编程假定
由于能高效便捷地向vector对象中添加元素,很多编程工作被极大简化了。然而,这种简便性也伴随着一些对编写程序更高的要求:其中一条就是必须要确保所写的循环正确无误,特别是在循环有可能改变vector对象容量的时候。
随着对vector的更多使用,我们还会逐渐了解到其他一些隐含的要求,其中一条是现在就要指出的:如果循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环,因为在范围for语句中,预存了尾部位置相关的标记位置的值,增减元素会改变其值。
5.vector类常用的函数
5.1构造函数
vector():创建一个空vector
vector(int nSize):创建一个vector,元素个数为nSize
vector(int nSize,const T& t):创建一个vector,元素个数为nSize,且值均为t
vector(const vector&):复制构造函数
vector(begin,end):复制[begin,end)区间内另一个数组的元素到vector中
例1:
//#include "stdafx.h"
#include<iostream>
#include<vector>
using namespace std;
class A
{
//空类
};
int main(int argc, char* argv[])
{
//int型vector
vector<int> vecInt;
//float型vector
vector<float> vecFloat;
//自定义类型,保存类A的vector
vector<A> vecA;
//自定义类型,保存指向类A的指针的vector
vector<A*> vecPointA;
return 0;
}
例2:
// vectorsample.cpp : 定义控制台应用程序的入口点。
//
//#include "stdafx.h"
#include<iostream>
#include<vector>
using namespace std;
class A
{
//空类
};
int main(int argc, char* argv[])
{
//int型vector,包含3个元素
vector<int> vecIntA(3);
//int型vector,包含3个元素且每个元素都是9
vector<int> vecIntB(3, 9);
//复制vecIntB到vecIntC
vector<int> vecIntC(vecIntB);
int iArray[] = { 2,4,6 };
//创建vecIntD
vector<int> vecIntD(iArray, iArray + 3);
//打印vectorA,此处也可以用下面注释内的代码来输出vector中的数据
/*for(int i=0;i<vecIntA.size();i++)
{
cout<<vecIntA[i]<<" ";
}*/
cout << "vecIntA:" << endl;
for (vector<int>::iterator it = vecIntA.begin(); it != vecIntA.end(); it++)
{
cout << *it << " ";
}
cout << endl;
//打印vecIntB
cout << "VecIntB:" << endl;
for (vector<int>::iterator it = vecIntB.begin(); it != vecIntB.end(); it++)
{
cout << *it << " ";
}
cout << endl;
//打印vecIntC
cout << "VecIntB:" << endl;
for (vector<int>::iterator it = vecIntC.begin(); it != vecIntC.end(); it++)
{
cout << *it << " ";
}
cout << endl;
//打印vecIntD
cout << "vecIntD:" << endl;
for (vector<int>::iterator it = vecIntD.begin(); it != vecIntD.end(); it++)
{
cout << *it << " ";
}
cout << endl;
return 0;
}
输出:
vecIntA:
0 0 0
VecIntB:
9 9 9
VecIntB:
9 9 9
vecIntD:
2 4 6
例3:
#include<iostream>
#include<vector>
using namespace std;
void printVector(vector<int>& v)
{ //利用迭代器打印 v
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
void text01()
{
vector<int> v1;
for (int i = 0; i < 5; ++i)
{
v1.push_back(i);//向v1末尾添加数据
}
vector<int> v2(v1.begin(), v1.end());
vector<int> v3(5, 5);
vector<int> v4(v3);
cout << "打印v2: ";
printVector(v2);
cout << "打印v3: ";
printVector(v3);
cout << "打印v4: ";
printVector(v4);
}
int main(int argc, char* argv[])
{
text01();
return 0;
}
输出:
打印v2: 0 1 2 3 4
打印v3: 5 5 5 5 5
打印v4: 5 5 5 5 5
5.2增加函数
void push_back(const T& x):向量尾部增加一个元素X
iterator insert(iterator it,const T& x):向量中迭代器指向元素前增加一个元素x
iterator insert(iterator it,int n,const T& x):向量中迭代器指向元素前增加n个相同的元素x
iterator insert(iterator it,const_iterator first,const_iterator last):向量中迭代器指向元素前插入另一个相同类型向量的[first,last)间的数据
例子:
// vectorsample.cpp : 定义控制台应用程序的入口点。
// vectorsample.cpp : 定义控制台应用程序的入口点。
//#include "stdafx.h"
#include<iostream>
#include<vector>
using namespace std;
int main(int argc, char* argv[])
{
//int型vector,包含3个元素
vector<int> vecIntA;
//插入1 2 3
vecIntA.push_back(1);
vecIntA.push_back(2);
vecIntA.push_back(3);
int nSize = vecIntA.size();
cout << "vecIntA:" << endl;
//打印vectorA,方法一:
for (int i = 0; i < nSize; i++)
{
cout << vecIntA[i] << " ";
}
cout << endl;
//打印vectorA,方法二:
for (int i = 0; i < nSize; i++)
{
cout << vecIntA.at(i) << " ";
}
cout << endl;
//打印vectorA,方法三:
for (vector<int>::iterator it = vecIntA.begin(); it != vecIntA.end(); it++)
{
cout << *it << " ";
}
cout << endl;
return 0;
}
上述代码对一个整形向量类进行操作,先定义一个整形元素向量类,然后插入3个值,并用3种不同的方法输出,程序运行结果如下:
vecIntA:
1 2 3
1 2 3
1 2 3
例子2:
// vectorsample.cpp : 定义控制台应用程序的入口点。
//
//#include "stdafx.h"
#include<iostream>
#include<vector>
using namespace std;
class A
{
public:
int n;
public:
A(int n)
{
this->n = n;
}
};
int main(int argc, char* argv[])
{
//int型vector,包含3个元素
vector<A> vecClassA;
A a1(1);
A a2(2);
A a3(3);
//插入1 2 3
vecClassA.push_back(a1);
vecClassA.push_back(a2);
vecClassA.push_back(a3);
int nSize = vecClassA.size();
cout << "vecClassA:" << endl;
//打印vecClassA,方法一:
for (int i = 0; i < nSize; i++)
{
cout << vecClassA[i].n << " ";
}
cout << endl;
//打印vecClassA,方法二:
for (int i = 0; i < nSize; i++)
{
cout << vecClassA.at(i).n << " ";
}
cout << endl;
//打印vecClassA,方法三:
for (vector<A>::iterator it = vecClassA.begin(); it != vecClassA.end(); it++)
{
cout << (*it).n << " ";
}
cout << endl;
return 0;
}
上述代码通过定义元素为类的向量,通过插入3个初始化的类,并通过3种方法输出,运行结果如下:
vecClassA:
1 2 3
1 2 3
1 2 3
上述代码通过定义元素为类指针的向量,通过插入3个初始化的类指针,并通过3种方法输出指针指向的类,运行结果如下:
vecClassA:
1 2 3
1 2 3
1 2 3
5.3删除函数
iterator erase(iterator it) :删除向量中迭代器指向元素
iterator erase(iterator first, iterator last) : 删除向量中[first, last)中元素
void pop_back() :删除向量中最后一个元素
void clear() : 清空向量中所有元素
例1:
// vectorsample.cpp : 定义控制台应用程序的入口点。
//#include "stdafx.h"
#include<iostream>
#include<vector>
using namespace std;
int main(int argc, char* argv[])
{
//int型vector,包含3个元素
vector<int> vecIntA;
//循环插入1 到10
for (int i = 1; i <= 10; i++)
{
vecIntA.push_back(i);
}
vecIntA.erase(vecIntA.begin() + 4);
cout << "删除第5个元素后的向量vecIntA:" << endl;
//打印vectorA
for (vector<int>::iterator it = vecIntA.begin(); it != vecIntA.end(); it++)
{
cout << *it << "\t";
}
cout << endl;
//删除第2-5个元素
vecIntA.erase(vecIntA.begin() + 1, vecIntA.begin() + 5);
cout << "删除第2-5个元素后的vecIntA:" << endl;
//打印vectorA
for (vector<int>::iterator it = vecIntA.begin(); it != vecIntA.end(); it++)
{
cout << *it << "\t";
}
cout << endl;
//删除最后一个元素
vecIntA.pop_back();
cout << "删除最后一个元素后的vecIntA:" << endl;
//打印vectorA
for (vector<int>::iterator it = vecIntA.begin(); it != vecIntA.end(); it++)
{
cout << *it << "\t";
}
cout << endl;
return 0;
}
程序运行结果如下:
删除第5个元素后的向量vecIntA:
1 2 3 4 6 7 8 9 10
删除第2-5个元素后的vecIntA:
1 7 8 9 10
删除最后一个元素后的vecIntA:
1 7 8 9
5.4遍历函数
reference at(int pos) :返回pos位置元素的引用
reference front() : 返回首元素的引用
reference back() : 返回尾元素的引用
iterator begin() : 返回向量头指针,指向第一个元素
iterator end() : 返回向量尾指针,指向向量最后一个元素的下一个位置
reverse_iterator rbegin() : 反向迭代器,指向最后一个元素
reverse_iterator rend() : 反向迭代器,指向第一个元素之前的位置
5.5判空函数
bool empty() const:判断向量是否为空,若为空,则向量中无元素
5.6大小函数
int size() const:返回向量中元素的个数
int capacity() const:返回当前向量所能容纳的最大元素值
int max_size() const:返回最大可允许的vector元素数量值
5.7其他函数
void swap(vector&):交换两个同类型向量的数据
void assign(int n,const T& x):设置向量中第n个元素的值为x
void assign(const_iterator first,const_iterator last):向量中[first,last)中元素设置成当前向量元素
resize(num)重新指定队列的长度。(往往用来增加vector的长度,小->大 ok 大->小 没用!)
reserve()保留适当的容量。
front() 返回指向向量第一个元素的引用
back()返回指向向量最后一个元素的引用
1.assign函数介绍
函数原型:
void assign(const_iterator first, const_iterator last);
void assign(size_type n, const T& x = T());
void assign( std::initializer_list<T> ilist );//(C++11 起) (C++20 前)
constexpr void assign( std::initializer_list<T> ilist );//(C++20 起)
void assign(const_iterator first, const_iterator last);
功能:将区间[first,last)的元素赋值到当前的vector容器中,或者赋n个值为x的元素到vector容器中,这个容器会清除掉vector容器中以前的内容。
例1:
// vector assign
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
std::vector<double> first{ 1.9, 2.9, 3.9, 4.9, 5.9 }; /*初始化源数组*/
std::vector<double> second; /*声明空数组*/
std::vector<int> third;
std::vector<string> forth;
std::vector<double>::iterator it;
it = first.begin();
second.assign(it, first.end());
std::cout << "Size of second: " << int(second.size()) << '\n';
for (int i = 0; i < second.size(); i++)
cout << second[i] << " ";
cout << endl;
//结果:
//Size of second: 5
//1.9 2.9 3.9 4.9 5.9
second.assign(it + 1, first.end() - 1);
std::cout << "Size of second: " << int(second.size()) << '\n';
for (int i = 0; i < second.size(); i++)
cout << second[i] << " ";
cout << endl;
//Size of second: 3
//2.9 3.9 4.9
third.assign(it, first.end());
std::cout << "Size of third: " << int(third.size()) << '\n';
for (int i = 0; i < third.size(); i++)
cout << third[i] << " ";
cout << endl;
//Size of third: 5
//1 2 3 4 5
int myints[] = { 1776,7,4 };
third.assign(myints, myints + 3); /* assign with array*/
std::cout << "Size of third: " << int(third.size()) << '\n';
for (int i = 0; i < third.size(); i++)
cout << third[i] << " ";
cout << endl;
//Size of third: 3
//1776 7 4
//third.assign (myints,myints+4); /*error usage,有结果但是行为错误*/
//1776 7 4 787800
// third = first; /*error usage*/
// forth.assign(it,first.end()); /*error usage*/
return 0;
}
输出:
Size of second: 5
1.9 2.9 3.9 4.9 5.9
Size of second: 3
2.9 3.9 4.9
Size of third: 5
1 2 3 4 5
Size of third: 3
1776 7 4
void assign(size_type n, const T& x = T());
功能:将调用者中内容替换成n个x
例2:
// vector assign
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<double> first{ 1.9, 2.9, 3.9, 4.9, 5.9 }; /*初始化源数组*/
cout << "Size of first: " << int(first.size()) << '\n';
for (int i = 0; i < first.size(); i++)
cout << first[i] << " ";
cout << endl;
first.assign(10, 5);
cout << "Size of first: " << int(first.size()) << '\n';
for (int i = 0; i < first.size(); i++)
cout << first[i] << " ";
cout << endl;
return 0;
}
输出:
Size of second: 5
1.9 2.9 3.9 4.9 5.9
Size of second: 10
5 5 5 5 5 5 5 5 5 5
void assign( std::initializer_list<T> ilist );//(C++11 起) (C++20 前)
constexpr void assign( std::initializer_list<T> ilist );//(C++20 起)
功能:将当前vector内容替换成ilist中内容
// vector assign
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
vector<double> first{ 1.9, 2.9, 3.9, 4.9, 5.9 }; /*初始化源数组*/
vector<double> second{ 1, 2, 3, 4, 5 }; /*初始化源数组*/
cout << "Size of first: " << int(first.size()) << '\n';
for (int i = 0; i < first.size(); i++)
cout << first[i] << " ";
cout << endl;
second.assign({1, 2, 3});
cout << "Size of second: " << int(second.size()) << '\n';
for (int i = 0; i < second.size(); i++)
cout << second[i] << " ";
cout << endl;
return 0;
}
输出:
Size of first: 5
1.9 2.9 3.9 4.9 5.9
Size of second: 3
1 2 3
2.reserve和resize
vector 的reserve增加了vector的capacity,但是它的size没有改变!而resize改变了vector的capacity同时也增加了它的size!
原因如下:
reserve是容器预留空间,但在空间内不真正创建元素对象,所以在没有添加新的对象之前,不能引用容器内的元素。加入新的元素时,要调用push_back()/insert()函数。
resize是改变容器的大小,且在创建对象,因此,调用这个函数之后,就可以引用容器内的对象了,因此当加入新的元素时,用operator[]操作符,或者用迭代器来引用元素对象。此时再调用push_back()函数,是加在这个新的空间后面的。
两个函数的参数形式也有区别的,reserve函数之后一个参数,即需要预留的容器的空间;resize函数可以有两个参数,第一个参数是容器新的大小, 第二个参数是要加入容器中的新元素,如果这个参数被省略,那么就调用元素对象的默认构造函数。
(1)resize用法:
void resize(size_type new_size);
void resize(size_type new_size, const value_type& value);
void resize(size_type new_size);
①new_size>size()时,如果new_size>capacity(),那么size()和capacity()大小同时变为new_size,重新分配出来的空间执行默认初始化。如果size()<new_size<capacity(),size()大小变为new_size、capacity()不变,size()和new_size之间的数据执行默认初始化,new_size和capacity()之间的数据无法访问。
②new_size<size()时,size()大小变为new_size,capacity()不变,new_size和capacity()之间数据无法访问
例1:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
std::vector<int> vec(5); // 创建一个大小为5的向量
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << " ";
}
cout << endl;
for (int i = 0; i < vec.size(); i++)
{
vec[i] = 2*i;
cout << vec[i] << " ";
}
cout << endl;
cout << "vec.size():" << vec.size() << ", vec.capacity():" << vec.capacity() << endl;
cout << "===========================================================" << endl;
vec.resize(7); // 改变向量的大小为7
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << " ";
}
cout << endl;
cout << "vec.size():" << vec.size() << ", vec.capacity():" << vec.capacity() << endl;
cout << "===========================================================" << endl;
vec.resize(5); // 改变向量的大小为5
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << " ";
}
cout << endl;
cout << "vec.size():" << vec.size() << ", vec.capacity():" << vec.capacity() << endl;
cout << "===========================================================" << endl;
return 0;
}
输出:
0 0 0 0 0
0 2 4 6 8
vec.size():5, vec.capacity():5
===========================================================
0 2 4 6 8 0 0
vec.size():7, vec.capacity():7
===========================================================
0 2 4 6 8
vec.size():5, vec.capacity():7
===========================================================
void resize(size_type new_size, const value_type& value);
①new_size<size()时,结果是容器的size()减小到new_size,删除n之后的元素,capacity()不变
②size()<new_size<capacity()时,结果是容器的size()增加到new_size,增加的元素值初始化为value,capacity()不变
③new_size>capacity()时,结果是容器的size()和capacity()增加到new_size,增加的元素值初始化为value
例2:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
std::vector<int> vec(5); // 创建一个大小为5的向量
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << " ";
}
cout << endl;
for (int i = 0; i < vec.size(); i++)
{
vec[i] = 2*i;
cout << vec[i] << " ";
}
cout << endl;
cout << "vec.size():" << vec.size() << ", vec.capacity():" << vec.capacity() << endl;
cout << "===========================================================" << endl;
vec.resize(3,6); // 改变向量的大小为3
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << " ";
}
cout << endl;
cout << "vec.size():" << vec.size() << ", vec.capacity():" << vec.capacity() << endl;
cout << "===========================================================" << endl;
vec.resize(4, 6); // 改变向量的大小为3
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << " ";
}
cout << endl;
cout << "vec.size():" << vec.size() << ", vec.capacity():" << vec.capacity() << endl;
cout << "===========================================================" << endl;
vec.resize(7, 6); // 改变向量的大小为3
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << " ";
}
cout << endl;
cout << "vec.size():" << vec.size() << ", vec.capacity():" << vec.capacity() << endl;
cout << "===========================================================" << endl;
return 0;
}
输出:
0 0 0 0 0
0 2 4 6 8
vec.size():5, vec.capacity():5
===========================================================
0 2 4
vec.size():3, vec.capacity():5
===========================================================
0 2 4 6
vec.size():4, vec.capacity():5
===========================================================
0 2 4 6 6 6 6
vec.size():7, vec.capacity():7
===========================================================
(2)reserve用法:
void reserve( size_type new_cap );//(C++20 前)
constexpr void reserve( size_type new_cap );//(C++20 起)
①new_cap < capacity()时,size与capacity均不发生改变;
②new_cap > capacity()时,此时会重新分配一块空间,使得capacity扩容至n,size不发生改变。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
std::vector<int> vec(5); // 创建一个大小为5的向量
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << " ";
}
cout << endl;
for (int i = 0; i < vec.size(); i++)
{
vec[i] = 2*i;
cout << vec[i] << " ";
}
cout << endl;
cout << "vec.size():" << vec.size() << ", vec.capacity():" << vec.capacity() << endl;
cout << "===========================================================" << endl;
vec.reserve(3); // 改变向量的大小为3
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << " ";
}
cout << endl;
cout << "vec.size():" << vec.size() << ", vec.capacity():" << vec.capacity() << endl;
cout << "===========================================================" << endl;
vec.reserve(9);
for (int i = 0; i < vec.size(); i++)
{
cout << vec[i] << " ";
}
cout << endl;
cout << "vec.size():" << vec.size() << ", vec.capacity():" << vec.capacity() << endl;
cout << "===========================================================" << endl;
return 0;
}
输出:
0 0 0 0 0
0 2 4 6 8
vec.size():5, vec.capacity():5
===========================================================
0 2 4 6 8
vec.size():5, vec.capacity():5
===========================================================
0 2 4 6 8
vec.size():5, vec.capacity():9
===========================================================
例1:
#include<iostream>
#include<vector>
using namespace std;
void printVector(vector<int>& v)
{ //利用迭代器打印 v
int i = 0;
for (vector<int>::iterator it = v.begin(); it != v.end(); ++it)
{
cout << *it << " ";
i++;
if(i%10 == 0)
cout << endl;
}
cout << endl;
}
int main(int argc, char* argv[])
{
vector<int> myVec;
myVec.reserve(100); // 新元素还没有构造,
// 此时不能用[]访问元素
for (int i = 0; i < 100; i++)
{
myVec.push_back(i); //新元素这时才构造
}
myVec.resize(102); // 用元素的默认构造函数构造了两个新的元素
myVec[100] = 1; //直接操作新元素
myVec[101] = 2;
printVector(myVec);
return 0;
}
5.8综合示例
// vectorsample.cpp : 定义控制台应用程序的入口点。
//
//#include "stdafx.h"
#include<iostream>
#include<vector>
#include<string>
using namespace std;
class Student
{
public:
string m_strNO;
string m_strName;
string m_strSex;
string m_strDate;
public:
Student(string strNO, string strName, string strSex, string strDate)
{
m_strNO = strNO;
m_strName = strName;
m_strSex = strSex;
m_strDate = strDate;
}
void Display()
{
cout << m_strNO << "\t";
cout << m_strName << "\t";
cout << m_strSex << "\t";
cout << m_strDate << "\t";
}
};
class StudCollect
{
vector<Student> m_vStud;
public:
void Add(Student& s)
{
m_vStud.push_back(s);
}
Student* Find(string strNO)
{
bool bFind = false;
int i;
for (i = 0; i < m_vStud.size(); i++)
{
Student& s = m_vStud.at(i);
if (s.m_strNO == strNO)
{
bFind = true;
break;
}
}
Student* s = NULL;
if (bFind)
s = &m_vStud.at(i);
return s;
}
};
int main(int argc, char* argv[])
{
Student s1("1001", "zhangsan", "boy", "1988-10-10");
Student s2("1002", "lisi", "boy", "1988-8-25");
Student s3("1003", "wangwu", "boy", "1989-2-14");
StudCollect s;
s.Add(s1);
s.Add(s2);
s.Add(s3);
Student* ps = s.Find("1002");
if (ps)
ps->Display();
return 0;
}
代码运行实例如下:
1002 lisi boy 1988-8-25
参考:vector容器用法详解 - 蒲公英110 - 博客园 (cnblogs.com)
6.vector容器的底层实现机制
作者建议还是稍微看一下底层机制,vector主要有三个指针(迭代器) 来表示的:
●_Myfirst 和 _Mylast 可以用来表示 vector 容器中目前已被使用的内存空间;
●_Mylast 和 _Myend 可以用来表示 vector 容器目前空闲的内存空间;
●_Myfirst 和 _Myend 可以用表示 vector 容器的容量。
对于空的 vector 容器,由于没有任何元素的空间分配,因此 _Myfirst、_Mylast 和 _Myend 均为 null。
vector扩容本质
另外需要指明的是,当 vector 的大小和容量相等(size==capacity)也就是满载时,如果再向其添加元素,那么 vector 就需要扩容。vector 容器扩容的过程需要经历以下 3 步:
1.完全弃用现有的内存空间,重新申请更大的内存空间;
2.将旧内存空间中的数据,按原有顺序移动到新的内存空间中;
3.最后将旧的内存空间释放。
这也就解释了,为什么 vector 容器在进行扩容后,与其相关的指针、引用以及迭代器可能会失效的原因。
由此可见,vector 扩容是非常耗时的。为了降低再次分配内存空间时的成本,每次扩容时 vector 都会申请比用户需求量更多的内存空间(这也就是 vector 容量的由来,即 capacity>=size),以便后期使用。
vector 容器扩容时,不同的编译器申请更多内存空间的量是不同的。以 VS 为例,它会扩容现有容器容量的 50%。
参考:C++ STL vector容器(深入了解,一文学会)
11.动态数组
1.动态数组结构
上图所示,该动态数组有3个元素,空间容量是6,每个元素类型为void*,因为void*可以接受任何类型指针,可以用来存各种类型指针。第一个元素地址为pAddr,第一个元素为*pAddr。
用结构体表示动态数组为:
//动态数组结构体
struct dynamicArray
{
void** pAddr;//维护真实在堆区的数组的指针
int m_capacity;//数组容量
int m_size;//数组大小
};
对应上图m_size是3,m_capacity是6
2.动态数组的操作
2.1动态数组的初始化操作
动态数组每个元素类型为void*,void*可以保存任意元素地址,可以是内置类型,也可以是自定义类型
//初始化数组
struct dynamicArray * init_DynamicArray(int capacity);
struct dynamicArray* init_DynamicArray(int capacity)
{
if (capacity <= 0)
{
return NULL;
}
//给数组分配空间
//对是malloc()函数的返回值是否进行强制转换取决于程序的编译环境。在C中,强制转化是在画蛇添足;在C++中强制转换是必须的。如果说在C环境和C++环境之间进行移植,建议还是对其进行强制转换。
struct dynamicArray* array = (struct dynamicArray*)malloc(sizeof(struct dynamicArray));//array为数组名,是第一个元素的地址
if (array == NULL)//判断分配空间操作是否成功,不成功立即退出
{
return;
}
//给数组初始化
array->pAddr = malloc(sizeof(void*) * capacity);//动态数组容量
array->m_capacity = capacity;
array->m_size = 0;
return array;
}
2.2动态数组的插入操作
//插入数组
void insert_DynamicArray(struct dynamicArray * array, int pos, void * data);
//array为需要做插入操作的数组,所插数据的插入位置为pos、值为data,这里是插入一个数据
void insert_DynamicArray(struct dynamicArray * array, int pos, void * data)
{
if (array == NULL)//无法对未分配空间的数组做插入操作
{
return;
}
if (data == NULL)//无法插入一个无效值
{
return;
}
//无效位置 尾插
if (pos < 0 || pos > array->m_size)
{
pos = array->m_size;
}
//判断是否满了,如果满动态扩展
if (array->m_size == array->m_capacity)
{
//1、计算新空间大小 这里多分配空间,是防止多次分配空间,以空间换时间
int newCapacity = array->m_capacity * 2;//array->m_capacity等价于(*array).m_capacity
//2、创建新空间
void ** newSpace = malloc(sizeof(void *)* newCapacity);//sizeof(void *)为每个元素大小
//3、将原有数据拷贝到新空间下
memcpy(newSpace, array->pAddr, sizeof(void *) * array->m_capacity);
//4、释放原有内存空间
free(array->pAddr);
//5、更新新空间指向
array->pAddr = newSpace;
//6、更新新容量
array->m_capacity = newCapacity;
}
//插入新元素
//移动元素 进行插入新元素
for (int i = array->m_size - 1; i >= pos; i--)//将插入位置后的元素先后移,从尾元素开始
{
//数据向后移动
array->pAddr[i + 1] = array->pAddr[i];
}
//将新元素 插入到指定位置上
array->pAddr[pos] = data;
//更新大小
array->m_size++;
}
2.3动态数组的遍历操作
//打印数组
void foreach_DynamicArray(struct dynamicArray * array, void(*myPrint)(void*));
void foreach_DynamicArray(struct dynamicArray * array, void(*myPrint)(void*))
{
if (array == NULL)
{
return;
}
if (myPrint == NULL)
{
return;
}
for (int i = 0; i < array->m_size; i++)
{
myPrint(array->pAddr[i]);
}
}
2.4动态数组的删除操作
//删除数组 按位置删除
void removeByPos_DynamicArray(struct dynamicArray * array, int pos)
{
if (NULL == array)
{
return;
}
if (pos < 0 || pos > array->m_size - 1)
{
return;
}
//数据前移
for (int i = pos; i < array->m_size - 1; i++)
{
array->pAddr[i] = array->pAddr[i + 1];
}
//更新数组大小
array->m_size--;
}
//按值删除数据
void removeByValue_DynamicArray(struct dynamicArray * array, void * data, int(*myCompare)(void *, void *))
{
if (array == NULL)
{
return;
}
if (data == NULL)
{
return;
}
for (int i = 0; i < array->m_size; i++)
{
if (myCompare(array->pAddr[i], data))
{
//如果找到要删除的数据,i就是要删除的具体位置
removeByPos_DynamicArray(array, i);
break;
}
}
}
int myComparePerson(void * data1, void *data2)
{
struct Person * p1 = data1;
struct Person * p2 = data2;
return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
}
removeByValue_DynamicArray(array, &p, myComparePerson);
2.5销毁数组
//销毁数组
void destroy_DynamicArray(struct dynamicArray* array)
{
if (array == NULL)
{
return;
}
if (array->pAddr != NULL)
{
free(array->pAddr);
array->pAddr = NULL;
}
free(array);
array = NULL;
}
3.完整代码
头文件:dynamicArray.h
#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//动态数组结构体
struct dynamicArray
{
void ** pAddr; //维护真实在堆区创建的数组的指针
int m_capacity; // 数组容量
int m_size; //数组大小
};
//初始化数组
struct dynamicArray * init_DynamicArray(int capacity);
//插入数组
void insert_DynamicArray(struct dynamicArray * array, int pos, void * data);
//遍历数组
void foreach_DynamicArray(struct dynamicArray * array, void(*myPrint)(void*));
//删除数组 按位置删除
void removeByPos_DynamicArray(struct dynamicArray * array, int pos);
//按值删除数据
void removeByValue_DynamicArray(struct dynamicArray * array, void * data, int(*myCompare)(void *, void *));
//销毁数组
void destroy_DynamicArray(struct dynamicArray* array);
01 动态数组.c
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include "dynamicArray.h"
////动态数组结构体
//struct dynamicArray
//{
// void ** pAddr; //维护真实在堆区创建的数组的指针
//
// int m_capacity; // 数组容量
//
// int m_size; //数组大小
//};
//
//
////初始化数组
//struct dynamicArray * init_DynamicArray(int capacity)
//{
// if (capacity <= 0)
// {
// return NULL;
// }
//
// //给数组分配空间
//
// struct dynamicArray * array = malloc(sizeof(struct dynamicArray));
// if (array == NULL)
// {
// return NULL;
// }
//
// //给数组初始化
// array->pAddr = malloc(sizeof(void *)* capacity);
// array->m_capacity = capacity;
// array->m_size = 0;
//
// return array;
//}
//
////插入数组
//void insert_DynamicArray(struct dynamicArray * array , int pos , void * data)
//{
// if ( array == NULL)
// {
// return;
// }
// if ( data == NULL)
// {
// return;
// }
//
// //无效位置 尾插
// if (pos < 0 || pos > array->m_size)
// {
// pos = array->m_size;
// }
//
// //判断是否满了,如果满动态扩展
// if (array->m_size == array->m_capacity)
// {
// //1、计算新空间大小
// int newCapacity = array->m_capacity * 2;
//
// //2、创建新空间
// void ** newSpace = malloc(sizeof(void *)* newCapacity);
//
// //3、将原有数据拷贝到新空间下
// memcpy(newSpace, array->pAddr, sizeof(void *)* array->m_capacity);
//
// //4、释放原有内存空间
// free(array->pAddr);
//
// //5、更新新空间指向
// array->pAddr = newSpace;
//
// //6、更新新容量
// array->m_capacity = newCapacity;
// }
//
// //插入新元素
//
// //移动元素 进行插入新元素
// for (int i = array->m_size - 1; i >= pos; i--)
// {
// //数据向后移动
// array->pAddr[i + 1] = array->pAddr[i];
// }
//
// //将新元素 插入到指定位置上
// array->pAddr[pos] = data;
// //更新大小
// array->m_size++;
//}
//
//
////遍历数组
//void foreach_DynamicArray(struct dynamicArray * array, void(*myPrint)(void*))
//{
// if (array == NULL)
// {
// return;
// }
// if (myPrint == NULL)
// {
// return;
// }
//
// for (int i = 0; i < array->m_size;i++)
// {
// myPrint(array->pAddr[i]);
// }
//}
//
//
////删除数组 按位置删除
//void removeByPos_DynamicArray(struct dynamicArray * array , int pos)
//{
// if ( NULL == array)
// {
// return;
// }
//
// if (pos < 0 || pos > array->m_size - 1)
// {
// return;
// }
//
// //数据前移
// for (int i = pos; i < array->m_size - 1;i++)
// {
// array->pAddr[i] = array->pAddr[i + 1];
// }
//
// //更新数组大小
// array->m_size--;
//
//}
//
////按值删除数据
//void removeByValue_DynamicArray(struct dynamicArray * array , void * data , int (* myCompare)(void * ,void *))
//{
// if ( array == NULL)
// {
// return;
// }
// if ( data == NULL)
// {
// return;
// }
//
// for (int i = 0; i < array->m_size;i++)
// {
// if (myCompare(array->pAddr[i], data))
// {
// //如果找到要删除的数据,i就是要删除的具体位置
// removeByPos_DynamicArray(array, i);
// break;
// }
// }
//
//}
//
////销毁数组
//void destroy_DynamicArray(struct dynamicArray* array)
//{
// if (array == NULL)
// {
// return;
// }
//
// if (array->pAddr != NULL)
// {
// free(array->pAddr);
// array->pAddr = NULL;
// }
//
// free(array);
// array = NULL;
//}
//
//测试
struct Person
{
char name[64];
int age;
};
void myPrintPerson(void *data)
{
struct Person * p = data;
printf("姓名: %s 年龄:%d\n", p->name, p->age);
}
int myComparePerson(void * data1, void *data2)
{
struct Person * p1 = data1;
struct Person * p2 = data2;
return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
}
int main(){
//初始化动态数组
struct dynamicArray * array = init_DynamicArray(5);
//准备数据
struct Person p1 = { "亚瑟", 18 };
struct Person p2 = { "妲己", 20 };
struct Person p3 = { "安琪拉", 19 };
struct Person p4 = { "凯", 21 };
struct Person p5 = { "孙悟空", 999 };
struct Person p6 = { "李白", 999};
printf("插入数据前: 容量:%d 大小:%d\n", array->m_capacity, array->m_size);
//插入数据
insert_DynamicArray(array, 0, &p1);
insert_DynamicArray(array, 0, &p2);
insert_DynamicArray(array, 1, &p3);
insert_DynamicArray(array, 0, &p4);
insert_DynamicArray(array, -1, &p5);
insert_DynamicArray(array, 2, &p6);
// 凯 妲己 李白 安琪拉 亚瑟 孙悟空
//遍历数据
foreach_DynamicArray(array, myPrintPerson);
printf("插入数据后: 容量:%d 大小:%d\n", array->m_capacity, array->m_size);
//测试删除 按位置删除
removeByPos_DynamicArray(array, 2);
printf("---------------------\n");
foreach_DynamicArray(array, myPrintPerson);
printf("删除数据后: 容量:%d 大小:%d\n", array->m_capacity, array->m_size);
struct Person p = { "亚瑟", 18 };
removeByValue_DynamicArray(array, &p, myComparePerson);
printf("---------------------\n");
foreach_DynamicArray(array, myPrintPerson);
printf("删除数据后: 容量:%d 大小:%d\n", array->m_capacity, array->m_size);
//array->pAddr = NULL;
//array->m_capacity = 0;
//array->m_size = 0;
//销毁数组
destroy_DynamicArray(array);
array = NULL;
system("pause");
return EXIT_SUCCESS;
}
dynamicArray.c
#include "dynamicArray.h"
//初始化数组
struct dynamicArray * init_DynamicArray(int capacity)
{
if (capacity <= 0)
{
return NULL;
}
//给数组分配空间
struct dynamicArray * array = malloc(sizeof(struct dynamicArray));
if (array == NULL)
{
return NULL;
}
//给数组初始化
array->pAddr = malloc(sizeof(void *)* capacity);
array->m_capacity = capacity;
array->m_size = 0;
return array;
}
//插入数组
void insert_DynamicArray(struct dynamicArray * array, int pos, void * data)
{
if (array == NULL)
{
return;
}
if (data == NULL)
{
return;
}
//无效位置 尾插
if (pos < 0 || pos > array->m_size)
{
pos = array->m_size;
}
//判断是否满了,如果满动态扩展
if (array->m_size == array->m_capacity)
{
//1、计算新空间大小
int newCapacity = array->m_capacity * 2;
//2、创建新空间
void ** newSpace = malloc(sizeof(void *)* newCapacity);
//3、将原有数据拷贝到新空间下
memcpy(newSpace, array->pAddr, sizeof(void *)* array->m_capacity);
//4、释放原有内存空间
free(array->pAddr);
//5、更新新空间指向
array->pAddr = newSpace;
//6、更新新容量
array->m_capacity = newCapacity;
}
//插入新元素
//移动元素 进行插入新元素
for (int i = array->m_size - 1; i >= pos; i--)
{
//数据向后移动
array->pAddr[i + 1] = array->pAddr[i];
}
//将新元素 插入到指定位置上
array->pAddr[pos] = data;
//更新大小
array->m_size++;
}
//遍历数组
void foreach_DynamicArray(struct dynamicArray * array, void(*myPrint)(void*))
{
if (array == NULL)
{
return;
}
if (myPrint == NULL)
{
return;
}
for (int i = 0; i < array->m_size; i++)
{
myPrint(array->pAddr[i]);
}
}
//删除数组 按位置删除
void removeByPos_DynamicArray(struct dynamicArray * array, int pos)
{
if (NULL == array)
{
return;
}
if (pos < 0 || pos > array->m_size - 1)
{
return;
}
//数据前移
for (int i = pos; i < array->m_size - 1; i++)
{
array->pAddr[i] = array->pAddr[i + 1];
}
//更新数组大小
array->m_size--;
}
//按值删除数据
void removeByValue_DynamicArray(struct dynamicArray * array, void * data, int(*myCompare)(void *, void *))
{
if (array == NULL)
{
return;
}
if (data == NULL)
{
return;
}
for (int i = 0; i < array->m_size; i++)
{
if (myCompare(array->pAddr[i], data))
{
//如果找到要删除的数据,i就是要删除的具体位置
removeByPos_DynamicArray(array, i);
break;
}
}
}
//销毁数组
void destroy_DynamicArray(struct dynamicArray* array)
{
if (array == NULL)
{
return;
}
if (array->pAddr != NULL)
{
free(array->pAddr);
array->pAddr = NULL;
}
free(array);
array = NULL;
}
参考资料来源:
黑马程序员