C++复习总结,仅供笔者复习使用,参考教材:
- 《零基础C++——从入门到精通》
- 《C++ Primer Plus》
本文主要内容为:C++ 指针、函数和STL容器
C++ 基本语法、输入输出、流程控制、字符串和数组部分 见 C++复习总结1;
C++ 结构体、面向对象和泛型编程部分 见 C++复习总结3.
目录
一. 指针与引用
C++ 提供指针对内存进行操作,本质就是一个记录地址的特殊变量。如下示例:
#include <iostream>
using namespace std;
int main(){
int arr[5]={1,5,3,6,7};
int *ptr1=arr; //指向数组(或者说是int元素)的指针
int num=4;
int *ptr2=# //将num地址赋给int型指针
float *ptr3=NULL; //声明时必须初始化
return 0;
}
1. 指针的基本操作
- 指针声明时必须初始化,若无初值则赋值为 NULL;
- 指针解引用前一般会判断非空:
if(ptr != NULL) cout<<*ptr
; - 与指针相关的操作:解引用
*ptr
和 取地址&num
; - 通过指针修改数据与修改变量值等效,如上面的例子中
*ptr2=5
与num=5
等效; - 指针可以进行运算,但算术后不一定是有效地址,若不注意有可能导致程序崩溃;
- 指针的加减运算与指针类型绑定,如上面例子中
ptr1+2
的实际地址加了8,因为 int 大小为 4 字节; - 指针之间也可以进行加减运算(如:
ptr1 - ptr2
),结果的单位是所指向数据类型的个数;
2. 指向 const 对象的指针和 const 指针
- const 变量需要 指向 const 对象的指针 来指向,指针指向的值不能改变 但 指针的指向可以改变;
const int num1 = 3;
const int num2 = 4;
const int *ptr = &num1;
//*ptr = 4; //禁止修改const元素
ptr = &num2; //可以修改指针的指向
- const 指针 的指向不可修改,但指向的值可以改变;
int num1 = 3;
int num2 = 4;
int *const ptr = &num1;
*ptr = 4; //允许修改指向元素的值
//ptr = &num2; //禁止修改const指针的指向
- 指向 const 对象的 const 指针 既不能修改指向,也不能修改指向的值;
const int num = 5;
const int *const ptr = #
3. 指针的数组与数组的指针
指针的数组,顾名思义,就是一个装有指针的数组;而数组的指针是一个指针,它指向某个数组整体,并且可以通过索引访问到每一个元素。
有一点难以理解,就是 数组的指针与其解引用的值相同,都是数组的首地址。这是因为数组名既代表数组,又代表数组的首地址,所以 arr 和 &arr 相同。因此对应的 arrptr 和 *arrptr 也得相同。
但是要注意,数组的指针并不能用数组的首地址代替,因为前者可以通过索引访问数组的所有元素,是实实在在对数组的映射,而后者只指向了第一个元素:
#include <iostream>
using namespace std;
int main(){
int arr[5]={1,0,5,9,6};
int *ptr1=arr; //数组的首地址
int (*ptr2)[5]=&arr; //数组的指针
int *ptr3[5]={&arr[0],&arr[1],&arr[2],&arr[3],&arr[4]}; //指针的数组
cout<<ptr1<<endl; //0x6ffdf0
cout<<ptr2<<" "<<*ptr2<<endl; //0x6ffdf0 0x6ffdf0
for(int i=0;i<5;i++){
cout<<ptr3[i]<<" "; //0x6ffdf0 0x6ffdf4 0x6ffdf8 0x6ffdfc 0x6ffe00
}
return 0;
}
当数组的指针想要修改指向时,修改后所指向的数组必须和修改前的数组大小相同:
#include <iostream>
using namespace std;
int main(){
int arr1[5]={1,0,5,9,6};
int arr2[6]={3,2,6,8,4,7};
int *ptr1=arr1;
int (*ptr2)[5]=&arr1;
ptr1=arr2; //int指针可以随意更改
// ptr2=&arr2; //数组的指针不能指向大小不同的数组
return 0;
}
4. 指针的指针
指针可以指向任何变量或对象,当然也可以指向指针:
#include <iostream>
using namespace std;
int main(){
int num=3;
int *numptr=# //指针
int **numptrptr=&numptr; //指针的指针
return 0;
}
5. 引用
引用是 C++ 在指针的基础上更新改进的一个概念,使用起来也更加方便,并且不需要考虑空指针、解引用或者取地址。引用得到的变量用起来和普通变量一样直观。
- 引用的本质就是绑定某个变量或对象,通过引用对变量值的更改在原变量名上也可见。引用在声明时就必须赋值,也就是原变量名:
#include <iostream>
using namespace std;
int main(){
int num=5;
int &numRef=num; //原变量名初始化别名
numRef=4;
cout<<num<<endl;
return 0;
}
- 对于 const 变量,也有特定的 const 引用,普通引用 const 变量会造成编译器出错:
#include <iostream>
using namespace std;
int main(){
const int num=5;
const int &numRef=num; //const引用
//int &numRef2=num;
return 0;
}
其实引用的功能指针也可以提供,只是会带来一些错误的风险。因此一般情况下引用只用于函数传参,在 复制传参比较费时 或者 参数在函数中的变化需要同步给主函数 的情况下使用。
二. 函数
1. 参数传递
C++ 主要提供了 3 种传参方式,分别是:按值传递、指针传递、引用传递:
- 按值传递:本质上就是将参数值 复制拷贝,并初始化函数的形参。因此函数对于参数的改变并不会同步到函数外;
- 指针传递:当函数参数是数组或自定义对象时,拷贝复制会消耗相当的时间。于是可以采用指针传递,即传递数组或对象的地址。正因为传递的是地址,因此函数对于参数的改变会同步到原变量;
#include <iostream>
using namespace std;
void swap(int *ptra,int *ptrb){
int tmp=*ptra;
*ptra=*ptrb;
*ptrb=tmp;
return;
}
int main(){
int a=3;
int b=5;
swap(&a,&b);
cout<<a<<" "<<b<<endl;
return 0;
}
- 引用传递:引用传递既不需要复制拷贝,也不需要声明指针,它只是声明一个变量的别名,相当简洁高效。并且函数对于参数的改变也会同步到原变量:
#include <iostream>
using namespace std;
void swap(int &a,int &b){
int tmp=a;
a=b;
b=tmp;
return;
}
int main(){
int a=3;
int b=5;
swap(a,b);
cout<<a<<" "<<b<<endl;
return 0;
}
注意,有一种特殊的参数可能需要传递:数组
#include <iostream>
#include <vector>
using namespace std;
void printArr(int arr[],int n){
for(int i=0;i<n;i++){
cout<<arr[i]<<" ";
}
cout<<endl;
}
void printVec(vector<int>& v){
for(int i=0;i<v.size();i++){
cout<<v[i]<<" ";
}
cout<<endl;
}
int main(){
int arr[10]={5,0,3,6,8,7};
vector<int> v(arr,arr+6);
printArr(arr,6);
printVec(v);
return 0;
}
2. 返回值
C++ 中返回的方法也有 3 种:返回值或对象、返回指针、返回引用。但是一般不会采用返回引用的方式,因为对于 int a = fun()
,即使 fun() 返回的是引用值,也是先将其放在寄存器中,然后复制给 a,达不到引用的效果。
注意,函数的返回值不能超过 1 个,若想 返回多个返回值,可以使用指针传递或引用传递:
#include <iostream>
using namespace std;
void getMaxMin(int a,int b,int *max,int &min){
*max=a>b?a:b;
min=a<b?a:b;
return;
}
int main(){
int a=3;
int b=5;
int max=0;
int min=0;
getMaxMin(a,b,&max,min);
cout<<max<<" "<<min<<endl;
return 0;
}
3. 函数重载
重定义参数或返回值类型不同的同名函数,称为函数重载。并且函数重载必须发生在相同作用域中,否则内层作用域中函数定义会屏蔽外层函数定义。
在重写参数类型不同的函数时容易出现多个重载函数与参数列表匹配的情况,即 二义性问题。需要显式转换参数以准确匹配参数列表:
#include <iostream>
using namespace std;
int add(int a,int b) {return a+b;}
int add(int a,int b,int c) {return a+b+c;}
float add(float a,float b) {return a+b;}
int main(){
int a=3;
float b=4.5;
// cout<<add(a,b); //有多个重载函数 "add" 实例与参数列表匹配
cout<<add(a,(int)b)<<endl;
return 0;
}
4. 函数指针
函数指针指向函数的入口,与具体函数名无关,可以指向任意 返回值类型和参数类表 与声明匹配的函数。因此函数指针在声明时就需要指定 返回值和参数列表的数据类型:
#include <iostream>
using namespace std;
int add(int a,int b) {return a+b;}
int substract(int a,int b) {return a-b;}
int main(){
//函数指针可以指向不同函数
int (*funcptr)(int,int);
funcptr=add;
cout<<funcptr(3,5)<<endl;
funcptr=substract;
cout<<funcptr(3,5)<<endl;
//借助typedef简化定义
typedef int (*fp)(int,int);
fp fpadd=add;
fp fpsub=substract;
cout<<fpadd(1,2)<<endl<<fpsub(8,2)<<endl;
return 0;
}
函数指针一般用于判断执行哪一条指令:
#include <iostream>
using namespace std;
int add(int a,int b) {return a+b;}
int substract(int a,int b) {return a-b;}
int main(){
int (*fp)(int,int);
string s;
cin>>s;
if(s=="add"){
fp=add;
}else if(s=="sub"){
fp=substract;
)else{
cout<<"wrong command!"<<endl;
return 0;
}
cout<<fp(8,2)<<endl;
return 0;
}
5. 静态局部对象
函数定义时可以通过定义静态变量,这种对象不会随着函数返回而销毁,而是从程序开始到结束一直与函数绑定,可以用于函数调用次数的计数:
#include <iostream>
using namespace std;
int Cnt(){
static int cnt=0;
cnt++;
return cnt;
}
int main(){
for(int i=0;i<10;i++){
cout<<"Number of calls: "<<Cnt()<<endl;
}
/* Number of calls: 1
Number of calls: 2
Number of calls: 3
Number of calls: 4
Number of calls: 5
Number of calls: 6
Number of calls: 7
Number of calls: 8
Number of calls: 9
Number of calls: 10
*/
return 0;
}
6. const 修饰函数
前面讲到过 const 修饰变量和指针,其实在函数中,它还可以修饰函数的参数、返回值,甚至函数的定义体:
- const 修饰函数的参数:const 修饰函数参数时只修饰 输入参数,可以防止意外改动该变量,起到保护作用。一般用于指针传递和引用传递(因为按值传递是复制过去的参数,不会改变原变量);
- const 修饰函数的返回值:一般用在指针传递和引用传递,前者必须赋给 const 对象的指针,后者可以提高效率;
- const 成员函数:一般用于面向对象的函数定义,const 修饰的成员函数可以防止修改成员变量;
class A{
public:
int num;
string NO;
int getnum() const{
// num=3; //const成员函数不可以修改成员变量
return num;
}
};
void func1(const int* ptr,const A &a){
// *ptr=3; //const修饰的输入参数不可修改
int num=5;
int* tmpptr=#
ptr=tmpptr; //但该指针可以指向其他地址
// a.num=3; //const修饰的对象也不可修改
}
const A& func2(){
A a{3,"test"};
return a;
}
三. STL容器
C++ 标准库中,容器和算法所在的标准子库称为标准模板库,简称STL。STL 容器可以存放任意一种类型的元素,但该类型必须支持 赋值 和 复制,因为容器初始化、添加元素等操作都会涉及复制控制。
1. 迭代器
- 如果想要遍历 STL 容器,就需要使用迭代器,因为不是所有容器都支持下标索引。使用迭代器遍历时只需使用 for 循环即可,分为正向迭代和反向迭代:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> vec(5);
for(int i=0;i<5;i++){
vec[i]=i;
}
cout<<"正向遍历vector:"<<endl;
for(vector<int>::iterator it1=vec.begin();it1!=vec.end();it1++){
cout<<*it1<<" "; //迭代器可以视为指针,需要解引用得到结果
}
cout<<endl<<"反向遍历vector:"<<endl;
// 直接反向遍历的话首尾不符合
// for(vector<int>::iterator it2=vec.end();it2!=vec.begin();it2--){
// cout<<*it2<<" ";
// }
for(vector<int>::reverse_iterator it2=vec.rbegin();it2!=vec.rend();it2++){
cout<<*it2<<" ";
}
return 0;
}
反向迭代不能写成正向迭代的逆形式是因为首尾值对不上:vec.begin() 指向第一个元素,vec.end() 指向最后一个元素之后的元素;vec.rbegin() 指向最后一个元素,vec.rend() 指向第一个元素之前的元素。
- 迭代器不仅用于遍历容器,还可用于添加或删除元素:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main() {
int a[5]={6,12,13,9,4};
vector<int> vec(a,a+5);
for(vector<int>::iterator it=vec.begin();it!=vec.end();it++){
if(*it>8){
it=vec.erase(it); //返回删除元素的后一个元素的位置
it--; //返回值相当于it++,因此需要前推一个位置
}else if(*it<7){
it++; //先后移再插入,否则it一直卡在6的前面陷入死循环
it=vec.insert(it,7);//insert在it之前插入元素并返回当前位置
}
}
cout<<endl<<vec.size()<<endl;
return 0;
}
需要注意的是,vec.erase(it) 返回的是删除元素的后一个元素的位置,vec.insert(it) 返回的是插入的第一个元素的位置,一般情况下需要对迭代器赋返回值,否则容易出错。
2. 链表 list
C++ STL 中封装了链表 list,通过指针相连,可以在任意地方删除或插入,但不支持下标索引。
- 初始化:list 初始化可以通过 指定初始值、其他list复制 或 数组复制:
#include <iostream>
#include <list>
using namespace std;
int main(){
int arr[5]={1,5,3,6,7};
list<int> l1; //空链表
list<int> l2(10); //10个元素,保持默认值
list<int> l3(10,1); //10个元素,初值为1
list<int> l4(l2); //用l2初始化
list<int> l5(l2.begin(),l2.end()); //用l2初始化
// list<int> l5(l2.begin()+1,l2.end()-2); //因为list内存不连续,所以不支持加减法截取
list<int> l6(arr,arr+sizeof(arr)/sizeof(arr[0])); //用数组初始化
return 0;
}
lst.assign(l2.begin(), l2.end())
/lst.assign(n,0)
:对 list 重新赋值;lst.resize(10,2)
:调整 list 大小并指定默认值(默认值可以不指定);lst.front()
/lst.back()
:返回 list 首/尾元素(使用前先判断是否为空);lst.pop_front(a)
/lst.pop_back()
:删除首/尾元素(使用前先判断是否为空);lst.push_front(a)
/lst.push_back(s)
:添加首/尾元素;lst.clear()
:清空 list;lst.empty()
:返回 list 是否为空;lst.size()
:返回 list 大小;lst.swap(l2)
:链表交换;lst.merge(l2)
:合并链表至 lst 中并清空 l2;lst.insert(iter,a)
:在 iter 处插入元素 a 并返回该位置;lst.insert(iter,n,a)
:在 iter 处插入 n 个 a ,无返回值;lst.insert(iter,l2.begin(),l2.end())
:在 iter 处插入迭代器间的元素;lst.erase(iter)
:删除 iter 处元素 a 并返回后一个元素的位置 ;lst.erase(iter1,iter2)
:删除 iter1 到 iter 间元素;lst.sort()
:对链表排序;lst.reverse()
:将链表反转;lst.remove(a)
:删除 lst 中元素 a.
3. 栈 stack
栈的实现是基于数组的,因此操作较为简单。
- 初始化:stack 初始化时为空栈:
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> st;
cout<<st.size()<<endl; //0
return 0;
}
st.empty()
:返回 stack 是否为空;st.size()
:返回 stack 大小;st.top()
:返回栈顶的值;st.pop()
:删除栈顶元素;st.push(a)
:压入元素a至栈顶.
4. 队列 queue
队列的实现也是基于数组的,操作较为简单。
- 初始化:queue 初始化时为空队列:
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<int> q;
cout<<q.size()<<endl; //0
return 0;
}
q.empty()
:返回 queue 是否为空;q.size()
:返回 queue 大小;q.front()
/q.back()
:返回队首/尾的值;q.pop()
:删除队首元素;q.push(a)
:压入元素a至队尾.
5. 双端队列 deque
双队列的操作如下:
- 初始化:deque 初始化时为空队列:
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> dq;
cout<<dq.size()<<endl; //0
return 0;
}
dq.empty()
:返回 deque 是否为空;dq.size()
:返回 deque 大小;dq.front()
/dq.back()
:返回队首/尾的值;dq.pop_front()
/dq.pop_back()
:删除队首/尾元素;dq.push_front(a)
/dq.push_back(a)
:压入元素a至队首/尾;dq.insert(iter,a)
:在 iter 处插入元素 a 并返回该位置;dq.erase(iter)
:删除 iter 处元素并返回后一个元素位置.
6. 优先队列 priority_queue
priority_queue 是一种可以自动排序的队列,即最大堆 / 最小堆,可以自定义容器和排序方式:
- 初始化:
#include <iostream>
#include <queue>
#include <functional> //greater 和 less 的头文件
#include <string>
using namespace std;
struct cmp{
bool operator() (string s1, string s2){
return s1.length()>s2.length();
}
};
int main(){
priority_queue<int> p1;
priority_queue<int,vector<int>,less<int> > p2;
priority_queue<int,vector<int>,greater<int> > p3;
priority_queue<int,vector<int>,cmp > p4;
return 0;
}
q.empty()
:返回 priority_queue 是否为空;q.size()
:返回 priority_queue 大小;q.top()
:返回堆顶的值;q.pop()
:删除堆顶元素;q.push(a)
:压入元素a.
7. 键值对 pair
pair 是关联容器的基本元素,操作较为简单:
- 初始化:pair 初始化可以直接构造或者采用函数:
#include <iostream>
#include <utility>
#include <string>
using namespace std;
int main() {
pair<int,string> p1;
pair<int,char> p2(1,'a');
pair<int,string> p3=make_pair(3,"three");
pair<int,string> p4(3,"three");
cout<<p3==p4<<endl; //1
return 0;
}
p.first
:键;p.second
:值.
8. 字典 map
map 是一种关联容器,是由许多个 pair 对象组成的集合,不允许有相同的键,但可以有不同的键拥有相同的值。
- 初始化:map 初始化直接构造,其元素可以直接赋值或者使用 insert 函数:
#include <iostream>
#include <string>
#include <utility>
#include <map>
using namespace std;
int main() {
map<string,int> mp;
mp["我"]=1;
mp.insert(pair<string,int>("的",1));
mp["家"]++; //不在map中的键值对的值默认值为该类型的默认值
cout<<mp.size()<<endl;
return 0;
}
iter->first
:迭代器遍历 map 返回指向元素的键;iter->second
:迭代器遍历 map 返回指向元素的值;mp.count(a)
:返回 map 中键为 a 的元素个数;mp.insert(p)
:向 map 中插入 pair;mp.find(p)
:返回 map 中 p 的位置(迭代器),若不存在则返回 mp.end()。通常使用mp.find(p) != mp.end()
判断 map 中是否存在某元素。
其实 map 中元素默认按键的顺序升序排列,也支持 自定义排序(必须基于键):
#include <iostream>
#include <string>
#include <map>
using namespace std;
struct cmp{
bool operator()(string s1,string s2){
return s1.length()>s2.length();
}
};
int main() {
map<string,int,cmp> mp;
mp["abc"]++;
mp["fghjk"]=5;
mp["te"]=3;
for(map<string,int,cmp>::iterator it=mp.begin();it!=mp.end();it++){
cout<<it->first<<" "<<it->second<<endl;
}
/*fghjk 5
abc 1
te 3
*/
return 0;
}
需要注意的是,C++ 标准库中的关联容器(map、set 等)的比较函数要求比较操作是可传递的,并且函数只接受 常量引用参数、比较函数对象内部也必须声明为 const 成员函数:
struct cmp{
bool operator()(const vector<int>& v1, const vector<int>& v2) const {
if(v1[1]!=v2[1]) return v1[1]<v2[1];
else return v1[0]<v2[0];
}
};
如果想要基于值排序,只能对 pair 数组排序:
#include <iostream>
#include <vector>
#include <unordered_map>
#include <utility>
#include <algorithm>
using namespace std;
struct ValueComparator{ // 自定义按值排序
bool operator()(pair<int,int>& lhs, pair<int,int>& rhs){
return lhs.second<rhs.second;
}
};
int main(){
vector<int> nums={1,3,2,2,5,3,4,1};
unordered_map<int,int> mp;
for(int i=0;i<nums.size();i++){
mp[nums[i]]++;
}
vector<pair<int,int>> v(mp.begin(),mp.end());
sort(v.begin(),v.end(),ValueComparator());
for(int i=0;i<v.size();i++){
cout<<v[i].first<<" "<<v[i].second<<endl;
}
return 0;
}
上面介绍的 map 底层使用红黑树实现,保证了 键值对的有序性。因此在迭代时,键值对会按照键的顺序访问。若使用中不需要元素按序排列,可以使用 unordered_map,底层使用哈希表实现,键值对没有特定的顺序。哈希表在查找操作上通常比红黑树更快,但是不会维护元素的顺序,且哈希桶会占用更多的内存:
#include <iostream>
#include <unordered_map>
#include <string>
using namespace std;
int main() {
unordered_map<std::string, int> mp;
mp["apple"] = 5;
mp["banana"] = 3;
mp["orange"] = 8;
string fruit = "banana";
if (mp.find(fruit) != mp.end()) {
std::cout << fruit << " 的数量是 " << mp[fruit] << std::endl;
}
else {
std::cout << fruit << " 不在映射中" << std::endl;
}
for (const auto& pair : mp) {
std::cout << pair.first << ": " << pair.second << std::endl;
}
return 0;
}
9. 集合 set
set 是键的集合,不允许有相同的键,操作与 map 类似。
- 初始化:set 初始化时为空集合:
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> s;
cout<<s.size()<<endl; //0
return 0;
}
s.count(a)
:返回 set 中元素 a 的个数;s.insert(a)
:向 set 中插入 a;s.erase(a)
:从 set 中删除 a,返回结果(删除成功为1,否则为0);s.find(a)
:返回 set 中 a 的位置,若不存在则返回 s.end();s.empty()
:返回 set 是否为空;s.size()
:返回 set 的大小;s.max_size()
:返回 set 的最大容量,即 461168601842738790;s.clear()
:清空集合;s1.swap(s2)
:键类型相同的 s1 与 s2 交换.
set 有一个重要性质 —— 对数组去重:
#include <iostream>
#include <vector>
#include <set>
using namespace std;
int main(){
vector<int> v={1,3,2,2,5,3,4,1};
set<int> st(v.begin(), v.end());
vector<int> uniqueVector(st.begin(), st.end());
for(int i=0;i<uniqueVector.size();i++) cout<<uniqueVector[i]<<" ";
return 0;
}
STL 还支持集合的交并差集运算,但结果只能返回到 vector 中:
#include <iostream>
#include <set>
#include <vector>
#include <algorithm>
using namespace std;
void display(vector<int> &v){
for(int i=0;i<v.size();i++){
cout<<v[i]<<" ";
}
cout<<endl;
}
int main() {
set<int> s1;
s1.insert(1);s1.insert(3);s1.insert(4);s1.insert(6);s1.insert(9);
set<int> s2;
s2.insert(1);s2.insert(2);s2.insert(5);s2.insert(6);s2.insert(7);
//求交集
vector<int> itsv(10); //不能为空
vector<int>::iterator it1;
it1=std::set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(),itsv.begin());
itsv.resize(it1-itsv.begin());
display(itsv); //1 6
//求并集
vector<int> unv(10);
vector<int>::iterator it2;
it2=std::set_union(s1.begin(),s1.end(),s2.begin(),s2.end(),unv.begin());
unv.resize(it2-unv.begin());
display(unv); //1 2 3 4 5 6 7 9
//求差集s1-s2
vector<int> difv1(10);
vector<int>::iterator it3;
it3=std::set_difference(s1.begin(),s1.end(),s2.begin(),s2.end(),difv1.begin());
difv1.resize(it3-difv1.begin());
display(difv1); //3 4 9
//求差集s2-s1
vector<int> difv2(10);
vector<int>::iterator it4;
it4=std::set_difference(s2.begin(),s2.end(),s1.begin(),s1.end(),difv2.begin());
difv2.resize(it4-difv2.begin());
display(difv2); //2 5 7
return 0;
}
上面这种写法必须保证存储集合运算结果的 vector 非空(即 itsv、unv、difv1、difv2 有元素),因为函数的最后一个参数(即迭代器)表示集合运算结果的插入位置,必须确保迭代器指向有效的内存位置,以便进行正确的插入操作。而空向量中没有任何元素,因此没有有效的位置来插入结果。为了避免这种问题,可以使用 std::back_inserter 迭代器作为插入位置,它会将新元素插入到容器的末尾,确保正确的插入操作,并自动调整容器的大小:
#include <iostream>
#include <set>
#include <vector>
#include <algorithm>
using namespace std;
void display(vector<int> &v){
for(int i=0;i<v.size();i++){
cout<<v[i]<<" ";
}
cout<<endl;
}
int main() {
set<int> s1;
s1.insert(1);s1.insert(3);s1.insert(4);s1.insert(6);s1.insert(9);
set<int> s2;
s2.insert(1);s2.insert(2);s2.insert(5);s2.insert(6);s2.insert(7);
//求交集
vector<int> itsv;
std::set_intersection(s1.begin(),s1.end(),s2.begin(),s2.end(),std::back_inserter(itsv));
display(itsv); //1 6
//求并集
vector<int> unv;
std::set_union(s1.begin(),s1.end(),s2.begin(),s2.end(),std::back_inserter(unv));
display(unv); //1 2 3 4 5 6 7 9
//求差集s1-s2
vector<int> difv1;
std::set_difference(s1.begin(),s1.end(),s2.begin(),s2.end(),std::back_inserter(difv1));
display(difv1); //3 4 9
//求差集s2-s1
vector<int> difv2;
std::set_difference(s2.begin(),s2.end(),s1.begin(),s1.end(),std::back_inserter(difv2));
display(difv2); //2 5 7
return 0;
}
上面介绍的 set 底层使用红黑树实现,保证了 元素的有序性。因此在迭代时,元素会按顺序访问。若使用中不需要元素按序排列,可以使用 unordered_set,底层使用哈希表实现,元素没有特定的顺序。哈希表在查找操作上通常比红黑树更快,但是不会维护元素的顺序,且哈希桶会占用更多的内存:
#include <iostream>
#include <unordered_set>
using namespace std;
int main() {
unordered_set<std::string> st;
st.insert("apple");
st.insert("banana");
st.insert("orange");
string fruit = "banana";
if (st.find(fruit) != st.end()) {
std::cout << fruit << " 存在于集合中" << std::endl;
} else {
std::cout << fruit << " 不在集合中" << std::endl;
}
for (const auto& item : st) {
std::cout << item << " ";
}
std::cout << std::endl;
return 0;
}