C++笔记 —— vector常用方法
本篇中使用的调试函数如下
1 template<typename T> 2 void print_vector(vector<T> a){ 3 if(a.size() == 0) 4 cout << "empty" << endl; 5 else{ 6 for(int i = 0; i < a.size(); i++) 7 cout << a[i] << " "; 8 cout << endl; 9 } 10 }
1.vector的初始化
1 vector<double> a; // 生成空向量 2 vector<double> b(5); // 指定大小,每个元素都是初始值0 3 vector<double> c{1,2,3,4,5}; // 指定每一个元素的值 4 vector<double> d(5,3); // 指定大小和统一初始化值 5 vector<double> e(c); // 复制构造函数 6 vector<double> f(begin(e), begin(e) + 3); // 部分复制构造 7 8 print_vector(a); // empty 9 print_vector(b); // 0 0 0 0 0 10 print_vector(c); // 1 2 3 4 5 11 print_vector(d); // 3 3 3 3 3 12 print_vector(e); // 1 2 3 4 5 13 print_vector(f); // 1 2 3
2.向vector中增加(或者是插入)元素
1 vector<int> a; 2 vector<int> b{4,5,6}; 3 4 a.push_back(1); //在末端增加1个元素 5 print_vector(a); // 1 6 7 a.emplace_back(2);//同在末端增加1个元素,比push_back()效率更高 8 print_vector(a); // 1 2 9 10 a.insert(begin(a) + 1, 3);//在迭代器指定的位置插入单个元素 11 print_vector(a); // 1 3 2 12 13 a.insert(end(a),begin(b),end(b));//三个参数都是迭代器,向第一个参数指定的位置中插入从第二个参数到第三个参数中的元素 14 print_vector(a); // 1 3 2 4 5 6 15 16 a.insert(end(a) - 1, 2, 7);//往第一个参数(迭代器)指定的位置插入以第二个参数为个数的第三个参数 17 print_vector(a); // 1 3 2 4 5 7 7 6 18 19 a.insert(end(a) - 2, {8,9});//第一个参数是插入位置,第二个参数是插入的参数(初始化列表形式) 20 print_vector(a); // 1 3 2 4 5 7 8 9 7 6
3.向vector中删除某些元素
在这里暂时先不介绍remove,对于单纯想要删除元素,remove是不被建议的做法。还有pop_back()用于删除最后一个元素
1 vector<int> a{1,2,3,4,5,6,7,8,9}; 2 3 a.pop_back(); // 删除vector中最后的单个元素 4 print_vector(a); // 1 2 3 4 5 6 7 8 5 6 a.erase(a.begin() + 1); // 删除由迭代器指定的位置中的单个元素 7 print_vector(a); // 1 3 4 5 6 7 8 8 9 a.erase(a.begin() + 2, a.begin() + 4); // 删除两个迭代器之间的元素 10 print_vector(a); // 1 3 6 7 8
4.查找
vector支持随机访问,可以用方括号运算符实现按下标查找。
按值查找主要用到algorithm中的find()和find_if()两个算法:
find()函数有三个参数,第一二个参数都是迭代器,分别指向要查找的区间的起始点和终止点,第三个参数是一个特定的值,说明要查找的元素。如果找到则返回一个指向第一个符合条件的元素的迭代器,如果没有找到则返回指向查找区间终点的迭代器(也就是和第二个参数一样)。find()函数不仅可以查找基本数据类型的vector,也可以查找自定义的类的vector,要求是自定义的类中重载了==运算符。
find_if()可以实现更广义的查找。find_if()前两个参数和find()一样,第三个参数是一个函数func,func只有一个参数,参数类型是被查找的元素的类型,返回值是bool类型,find_if()会返回使得func返回值为真的元素的迭代器。如果找不到则返回指向查找区间末端的迭代器。
最后是一个小技巧:使用find()或find_if()查找到的指向某个元素的迭代器,减去指向该vector的头部的迭代器(也就是begin()),可以得到该元素的下标,也就是按值查找下标功能。
1 class Student{ 2 public: 3 int year; 4 int grade; 5 Student(int y, int g):year(y), grade(g){} 6 bool operator==(const Student & Other){ 7 return (year == Other.year); 8 } 9 }; 10 11 bool excellent(Student s){ 12 return s.grade > 85; 13 } 14 15 int main(){ 16 vector<Student> a{Student(17,80), Student(16,95), Student(18,70)}; 17 18 cout << "year: " << a[2].year << " grade: " << a[2].grade << endl; // 根据下标查找值 19 20 vector<Student>::iterator b = find(begin(a), end(a), Student(18,100)); 21 cout << b - begin(a) << endl;/*利用find的结果可以做到根据值查找下标*/ 22 23 auto d = find_if(begin(a), end(a), excellent); 24 cout << d - begin(a)<< endl; 25 }
在这个例子中,find()函数查找的判断依据是Student类中的==运算符(例子中只要求找到第一个年龄等于18岁的学生),而find_if()中查找判断的依据是用户自己定义的excellent函数(例子中是要求找到第一个成绩大于85分的学生)
最后,由于find_if()的判断是自定义的函数,实际上有更多更灵活的写法,比如重载类的()操作符成为成员函数,或者使用lambda表达式等。
5.修改
C++似乎并没有python那种直接指把一个指定区间的元素整个替换成另一个区间的元素的操作。由于vector的实现是动态数组,往数组中插入或者删除元素的时间复杂度都是O(n),如无必要不要经常反复增删vector元素.assign有两种使用方式,但是这两种使用方式都会直接清除这个向量之前内部的所有数据。随机访问还可以用at,作用一样。
1 vector<int> a{1,2,3}; 2 vector<int> b{4,5,6,7}; 3 a[1] = 0; 4 print_vector(a); // 1 0 3 5 6 a.assign(begin(b), end(b)); 7 print_vector(a); // 4 5 6 7 8 9 a.assign(5,2); 10 print_vector(a); // 2 2 2 2 2
6.排序
使用vector内自带的排序功能排序,对两个迭代器内指定的区间按照自定义的方式进行排序,cmp参数是两个待比较的值类型,返回值是bool类型
1 class A{ 2 public: 3 int n, m; 4 A(int N, int M):n(N), m(M){} 5 }; 6 7 bool cmp(A p1, A p2){ 8 return (p1.m > p2.m); 9 } 10 11 int main(){ 12 vector<A> a{A(1,4),A(3,3),A(2,5),A(5,1),A(4,2)}; 13 sort(a.begin(), a.end(), cmp); // 按照m从大到小排序 14 }
7.迭代器
迭代器本质上就是指向元素的指针,跟指针的用法差不多,对于指向同一个容器的两个迭代器iter1和iter2,可以使用自加/自减或者加减某一个整数的操作使得迭代器左右移动。也可以用比较运算符==和!=(只有vector和queue的迭代器才可以使用<和>比较)。同时还可以相减得出下标的差值。由于迭代器相当于指针,因此可以使用*解引用或者如果是类可以使用->取其成员变量。
特殊的迭代器有begin和end,以及rbegin和rend可以用来初始化迭代器。
注意区分front,back和being,end,前者的两个分别返回的是第一个元素和最后一个元素的引用。
1 vector<double> a{1.1, 2.2, 3.3, 4.4, 5.5}; 2 vector<double>::iterator iter1 = a.begin(); 3 auto mid = iter1 + a.size() / 2; 4 cout << *mid << endl;
8.其他
size()获取大小,reverse()翻转,clear()清除所有元素,swap()交换元素,resize()改变大小(如果大于现大小则往后补默认元素,小于现大小则在尾端删除至要求的大小),capacity()获取容量(当vector大小达到容量上限时会启动自动扩容,但是自动扩容是一件很耗时间的事情,而且自动扩容期间会导致现有的迭代器和指针失效。所以实现估计大小并提前申请好容量大小是必要的。),reserve()手动扩容,count()统计某个元素出现次数。
最后重点说明两个:
1.remove()可以实现类似于“删除”某个元素的功能,以数组b[2,3,4,3,5]为例,如果想remove(begin(b),end(b),3),将会执行以下几步
申请两个指针i,j都指向b[0]
*i 不等于3(也就是b[0]),把i指向元素的值赋给j指向元素的值(b[0] <- b[0])++i,++j
*i 等于3(也就是b[1]),++i,j不动(j相当于标记了一个坑的位置)
*i 不等于3(也即是b[2]),把i指向元素的值赋给j指向元素的值(b[1] <- b[3])++i,++j
*i 等于3(也就是b[3]),++i,j不动
*i 不等于3(也即是b[4]),把i指向元素的值赋给j指向元素的值(b[2] <- b[5])++i,++j
最后的结果就是[2,4,5,3,5],从过程可以看出,是吧所有非3的元素都往前移,把原来是3的位置补上,而后面多出来的位置保持不变。remove返回值是就是上文中的j。
因此,想要用remove真正删除元素3,应该写成:
1 b.erase(remove(begin(b), end(b),3), end(b));
2.去重:想要去除vector中的重复元素有两种思路:
一是把vector转换成set再转换成vector
二是sort+unique+erase(unique的作用是把所有相邻且重复的元素放到列表末尾)
1 vector<int> a{1,2,3,4,5,6,7,8,9}; 2 cout << a.size() << endl; // 输出9 3 4 reverse(begin(a),end(a)); // reverse是algorithm中的函数,用于翻转 5 print_vector(a); // 9 8 7 6 5 4 3 2 1 6 7 remove(begin(a), end(a), 5); 8 print_vector(a); // 9 8 7 6 4 3 2 1 1 9 10 reverse(begin(a), end(a)); 11 print_vector(a); // 1 1 2 3 4 6 7 8 9 12 13 swap(a[0], a[2]); 14 print_vector(a); // 2 1 1 3 4 6 7 8 9 15 16 a.resize(12); 17 print_vector(a); // 2 1 1 3 4 6 7 8 9 0 0 0 18 19 a.resize(5); 20 print_vector(a); // 2 1 1 3 4 21 22 cout << "Capacity: " << a.capacity() << endl; // 18 23 a.reserve(30); 24 cout << "Capacity: " << a.capacity() << endl; // 30 25 26 cout << "Num of 1: " << count(begin(a), end(a), 1) << endl; // 2 27 28 a.clear(); 29 cout << a.empty() << endl; // 输出1,即true