之所以会有这篇文章, 是因为C++开发在笔试的时候, 需要手动处理数据, 但是C++又没有像是java, python那样自带的split函数
或者re包
或者eval函数
可以直接提取输入的数据变成想要的格式, 因此经过了一大轮的洗礼, 我总结了下面的输入处理方法以及常用的std容器使用方法, 作为C++入门的教程
1. C++的输入输出 #include <iostream>
1.1. 输入输出的定义
cin | 标准输入流 | 键盘读取数据 | 可被重定向(文件读取) | freopen("input.dat", "r", stdin); |
---|
cout | 标准输出流 | 屏幕输出(缓存) | 可被重定向(文件写入) | freopen("test.txt", "w", stdout); |
clog | 标准错误流 | 缓存输出 | | |
cerr | 标准错误流 | 不缓存输出 | 用于迅速输出出错信息 | |
cerr和clog的区别在于:cerr不适用缓冲区,直接向显示器输出信息;而输出到clog中的信息会先被存放到缓冲区,缓冲区满或者刷新时才输出到屏幕。
ostream类的无参构造函数和复制构造函数都是私有的,因此在程序中一般无法定义ostream类的对象,唯一能用的ostream类的对象就是cout。
cout可以被重定向,而cerr不能。所谓重定向,就是将输入的源或输出的目的地改变。例如,cout本来是输出到屏幕的,但是经过重定向,本该输出到屏幕上的东西就可以被输出到文件中。
1.2. 输入输出重定向
1.2.1. a 输出重定向
freopen(file, mode, stdout)
是个标准库函数,第二个参数mode=w写/a追加/r读
,第三个参数代表标准输出。该语句的作用是将标准输出重定向为test.txt文件。重定向之后,所有对cout的输出都不再出现在屏幕上,而是在test.txt文件中。
#include <iostream>
using namespace std;
int main()
{
int x,y;
cin >> x >> y;
freopen("test.txt", "w", stdout);
if( y == 0 )
cerr << "error 分母不能为0" << endl;
else
cout << x /y ;
return 0;
}
1.2.2. b 输入重定向
在文件test.py中输入23 24回车
运行下面代码直接就可以输入23 24
int main(int argc, char const *argv[]){
freopen("test.py", "r", stdin);
int a, b;
cin >> a >>b;
cout << a << " "<< b <<endl;
return 0;
}
1.2.3. c 流操作算子
比如将十进制的整形转成8进制数等操作
void test(){
int a = 125;
cout<< hex << a <<endl;
cout<< oct << a <<endl;
double b = 3.1415926;
cout<< fixed << b <<endl;
cout<< scientific << b <<endl;
}
1.3. getline()读取一行的问题
cin>>str
读字符串由于遇到空格会终止,有时候为了读取一行不得不使用getline(cin, s)
但getline(cin, s)
只能读取一行,但在getline(cin, s)
前调用了cin>>str
的情况下,读取会跳过一行。
为了解决上述问题, 可以在getline(cin, s)
上面加入一行 cin.ignore(numeric_limits <streamsize> ::max(), '\n');
int main(int argc, char const *argv[]){
freopen("test.py", "r", stdin);
int a, b;
cin >> a >>b;
cout << a << " "<< b <<endl;
string s;
cin.ignore(numeric_limits <streamsize> ::max(), '\n');
getline(cin, s);
cout<< s <<endl;
getline(cin, s);
cout<< s <<endl;
return 0;
}
1.4. 输入字符串解析各种数据
由于输入的数据基本都是按照字符串来进行解析的,
比如输入一个数组: [12,32,43,5,6]
其实输入的应该是vector<int> a = {12,32,43,5,6}
有两种方式解决上面的input->vector
- 方法1: 如果输入规范, 则将上面字符串
str = str[1:-1]
去掉左右两个括号, 在通过,
分割, 在利用atoi(s.c_str())
将s
转成int - 方法2: 如果上述输出不规范, 将上面非[0-9, +, -, .]转化为空格,
"[12,32,43,5,6]"
→ "12 32 43 5 6"
; 然后在利用空格分割, 在转int型 - 方法3: 得到
"12 32 43 5 6"
可以利用sstream
流直接转换
下面是三种方式的具体实现方法:
1.4.1. a 字符串分割方法
vector<string> split(string s,char ch){
int start=0;
int len=0;
vector<string> ret;
for(int i=0;i<s.size();i++){
if(s[i]==ch){
ret.push_back(s.substr(start,len));
start=i+1;
len=0;
}
else len++;
}
if(start<s.size()) ret.push_back(s.substr(start,len));
return ret;
}
1.4.2. b. 字符串转int类型
int main(int argc, char const *argv[]){
string s="123";
int v=atoi(s.c_str());
printf("result v=%d\n",v);
return 0;
}
1.4.3. c. 方法2,3的实现 字符串直接提取数组
vector<int> getV(string str1){
for(int i=0; i<str1.size(); i++){
if(str1[i]-'0' > 9 || str1[i]-'0' < 0){
if(str1[i] != '-' && str1[i] != '.')
str1[i] = ' ';
}
}
istringstream s1(str1);
int num;
vector<int> nums;
while(s1 >> num){
nums.push_back(num);
}
print_v(nums);
return nums;
}
1.4.4. d. 方法1 字符串提取二维数组
vector<vector<int>> getVV(string s="[[2,3], [23,43.5], [2,3,432]]"){
vector<string> vS = split(s.substr(0, s.size()-1), ']');
vector<vector<int>> ans;
for(int i=0; i<vS.size(); i++){
ans.push_back(getV(vS[i]));
}
print_vv(ans);
return ans;
}
2. fstream 文件←→输入输出流
在c++中我们易知的是cout和cin俩个标准输出输入,而在真实的状况中。我们则是需要对文件的内容进行读取和重新写入,这样我们只有cin和cout这俩个标准输入和输出就明显的不够满足条件了。所以有一个fstream类中的ifstream和ofstream则解决了这个对文件操作的问题。
#include <fstream>
2.1. 从数据写入到文件
#include <iostream>
#include <fstream>
using namespace std;
int main(){
string s = "hsdjkgjdksrh";
ofstream fout;
fout.open("data.txt", ios_base::app|ios_base::out);
fout << s << endl;
fout.close();
return 0;
}
其中fout.open
的文件打开方式如下, 默认是 ios_base::trunc
常量 | 含义 |
---|
ios_base::in | 打开文件,读取 |
ios_base::out | 打开文件,写入 |
ios_base::ate | 打开文件,移到文件尾 |
ios_base::app | 追加到文件尾 |
ios_base::trunc | 如果文件存在,则截断文件 |
ios_base::binary | 二进制文件 |
2.2. 从文件中读取数据到输入
ifstream fin("data.txt");
if (!fin.is_open()) {
cout<< "open error" << endl;
}
while(fin.good()){
string s;
fin >> s;
cout<< s << endl;
}
if(fin.eof()){
cout<< "end..." << endl;
} else if(fin.fail()){
printf("判断最后一次读取数据的时候是否遇到了类型不配的情况");
} else {
printf("出现意外的问题,如文件受损或硬件故障,最后一次读取数据的时候发生了这样的问题\n");
}
fin.close();
2.3. 最后读写的区别在于
c++模式 | c模式 | 含义 |
---|
ios_base::in | r | 打开文件,读取 |
ios_base::out | w | 打开文件,写入 |
ios_base::out-ios_base::trunc | w | 打开文件,写入,如果存在文件,则截短文件 |
ios_base::out-ios_base::app | a | 打开文件,追加到文件尾 |
ios_base::in-ios_base::out | r+ | 打开文件读写,在文件允许的位置写入 |
ios_base::in-ios_base::out-ios_base::trunc | w+ | 打开并写入,如果已存在,则截短文件 |
c++mode-ios_base::binary | cmodeb | c++mode和二进制模式打开 |
c++mode-ios_base::ate | cmode | 已指定模式打开,并且移动到文件尾 |
2.4. sstream
字符串←→输入输出流
#include <sstream>
功能是将字符串专户为流或者流转化为字符串
string Token::toString(){
ostringstream oss;
oss << "type:" << _type << " value:" << _value;
cout<< oss.str() << endl;
return oss.str();
}
vector<int> getV(string str1){
for(int i=0; i<str1.size(); i++){
if(str1[i]-'0' > 9 || str1[i]-'0' < 0){
if(str1[i] != '-' && str1[i] != '.')
str1[i] = ' ';
}
}
istringstream s1(str1);
int num;
vector<int> nums;
while(s1 >> num){
nums.push_back(num);
}
print_v(nums);
return nums;
}
3. 基础容器 vector和list
3.1. vector和list底层逻辑以及区别
Vector | List |
---|
连续存储的容器,动态数组,在堆上分配空间, 两倍容量增长, 顺序内存 | 动态双向链表, 堆上空间, 每删除一个元素会释放一个空间 |
访问:O(1)(随机访问);插入:后插快, 中间需要内存拷贝, 内存申请和释放; 删除: 后删快, 中间需要内存拷贝 | 访问: 随机访问差, 只能开头和结尾; 插入和删除快, 常数开销 |
适用场景:经常随机访问,且不经常对非尾节点进行插入删除 | 适用于经常插入和删除 |
下面是区别 | |
数组 | 双向链表 |
支持随机访问 | 不支持随机访问 |
顺序内存 | 离散内存 |
中间节点插入删除会导致拷贝 | 不会 |
一次性分配好内存, 二倍扩容 | list每次在新节点插入会进行内存申请 |
随机访问性能好,插入性能差 | 相反 |
注意: vector在保存对象时, 建议使用指针保存, 这是因为无论指针还是对象在内存中开辟的空间都一样, 但是使用指针时, 就不会内存中开辟连续的巨大空间, 并且随着对象的插入, vector内存如果不够, 需要新开辟空间, 拷贝对象到新的空间, 并且调用对象的析构函数回收上一个空间, 因此会有创建, 拷贝, 和析构的时间浪费;
3.2. vector基础语法 #include <vector>
3.2.1. a. vector增删改查
#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;
void print_vector1(vector<int> b){
for(vector<int>::iterator it=b.begin();it!=b.end();it++)
cout<<*it<<" ";
cout<<endl;
}
void print_vector2(vector<int> &b){
for(const auto&a : b){
cout<< a <<" ";
}cout<<endl;
}
void add_item(){
vector<int> a;
for(int i=0;i<10;i++)
a.push_back(i);
int a2[6]={1,2,3,4,5,6};
vector<int> a3;
vector<int> a4(a2,a2+4);
for(vector<int>::iterator it=a4.begin(); it<a4.end(); it++)
a3.push_back(*it);
}
int main(int argc, char const *argv[])
{
vector<int> a0;
vector<int> a1(10);
vector<int> a2(10, 1) ;
vector<int> a3(a2);
vector<int> a4 = {1,2,3,4,5,6,7,8,9};
vector<int> a5(a4.begin(), a4.begin()+3);
int i1[7]={1,2,3,4,5,9,8};
vector<int> a6(i1,i1+7);
vector<int> a7;
a7.assign(a6.begin(), a6.begin()+3);
vector<int> a8;
a8.assign(4,2);
a0.push_back(1);
a0.insert(a0.begin(), 2);
a0.insert(a0.begin()+1,5);
a0.insert(a0.begin()+1,3,7);
a0.insert(a0.begin()+1,a1.begin()+3,a1.begin()+6);
sort(a6.begin(), a6.end());
reverse(a6.begin(),a6.end());
copy(a6.begin(),a6.end(), a1.begin()+2);
a6.push_back(4);
if(find(a6.begin(), a6.end(), 4) != a6.end()){
for(vector<int>::iterator it = find(a6.begin(),a6.end(),4); it!=a6.end(); it++){
cout<< "a6中4存在" << *it <<endl;
}
}
if(count(a6.begin(), a6.end(), 2) != 0){
cout<< "a6 存在2" <<endl;
}
a4.back();
a4.front();
a4.at(1);
a4.clear();
if(a4.empty()){
cout<< "a4空了" <<endl;
}
a4.push_back(4);
a4.pop_back();
a6.erase(a6.begin()+1,a6.begin()+3);
a7.size();
a7.capacity();
a7.resize(6);
a7.resize(8,2);
a7.reserve(100);
a7.swap(a0);
a7==a0;
print_vector1(a1);
print_vector2(a2);
return 0;
}
3.2.2. b. 二维vector的相关方法
#include <stdio.h>
#include <algorithm>
#include <vector>
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
vector<vector<int>> v1(5,vector<int>(10,6));
vector<vector<int>> v2 = {{1,2,3,4}, {2,3,4,5}};
int a1[5]={1,2,3,5,6};
vector<int> i(a1, a1+4);
printf("打印向量:\n");
for(auto c : i) cout<< c <<" ";
vector<vector<int>> v3;
v3.push_back(i);
i[0]=5;
i[1]=4;
i[2]=4;
i[3]=4;
i[4]=4;
v3.push_back(i);
cout<<"\n打印二维数组"<<endl;
for (int i = 0; i < v1.size(); i++)
{
for (int j = 0; j < v1[0].size(); j++)
{
cout<<v1[i][j]<<" ";
}
cout<<endl;
}
cout<<endl;
cout<<"hello world"<<endl;
return 0;
}
3.3. list的基础语法
跟vector一样
#include <list>
void test(){
list<int> list1 = {1,2,3,3 ,3, 3, 3, 4,5,5,5,6,7,8, 2, 2, 2};
for(list<int>::iterator it=list1.begin(); it!=list1.end(); it++){
cout<< *it << " ";
} cout << endl;
list1.remove(2);
for(auto &a : list1){ cout<< a <<" " ;}cout<< "\n";
list1.remove_if([](int &a){return a%2==0;});
for(auto &a : list1){ cout<< a <<" " ;}cout<< "\n";
list1.unique();
for(auto &a : list1){ cout<< a <<" " ;}cout<< "\n";
list1.erase(++list1.begin(), list1.end());
for(auto &a : list1){ cout<< a <<" " ;}cout<< "\n";
}
4. 基础容器set和map以及unordered_set和unordered_map
#include<unordered_map>
#include<map>
#include<unordered_set>
#include<set>
4.1. map/set
和unordered_map/unordered_set
的底层逻辑以及区别
| map/set | unordered_map/unordered_set |
---|
底层 | 红黑树 | 哈希表 |
有序性 | 自动排序 | 无序, key映射 |
查找时间 | O(logn) | O(1) |
| 空间占用率高(保存父子节点关系) | 空间占用率低 |
为何不能修改key?
由于map
是key-value
键值对和set
是key
, 因此都无法修改key
, 因为红黑树底层按照key
排列,必须保证有序, 如果可以修改key
, 则需要删除key
, 调节树平衡, 在插入修改后的key
, 调节平衡, 将会破坏map
和set
的结构;
4.2. map的重要方法
#include<iostream>
#include<map>
#include<algorithm>
#include<vector>
#include <unordered_map>
using namespace std;
void map_test(){
typedef map<int, string> myMap;
myMap test;
test.insert(pair<int, string>(3, "a"));
test.insert(pair<int, string>(4, "b"));
test.insert(pair<int, string>(5, "c"));
test.insert(pair<int, string>(8, "d"));
test.insert(pair<int, string>(50, "e"));
cout << "遍历" << endl;
for(const auto &i : test){
cout << i.second << endl;
cout << endl;
auto iter = test.rbegin();
for (int i = 0; i < 3; i++)
cout << iter++->second << endl;
cout << "查找" << endl;
auto it = test.find(50);
if (it != test.end())
cout << it->second << endl;
cout << test.count(3) << endl;
cout << "删除" << endl;
if (test.erase(3))
cout << "delete success" << endl;
for (auto &i : test)
cout << i.second << endl;
}
void map_test2(){
map<int, string> myMap;
myMap.insert(pair<int, string>(3, "a"));
myMap.insert(pair<int, string>(5, "b"));
myMap.insert(pair<int, string>(50, "d"));
for (auto &i : myMap) cout <<i.first <<"value="<< i.second<<"; "; cout<<endl;
map<int, string>::reverse_iterator iter = myMap.rbegin();
if (iter != myMap.rend()) cout<<"最后一个值是"<<iter->first << "-" << iter->second <<endl;
auto iter1 = myMap.rbegin();
for (int i = 0; i < 2; i++)
cout << iter1++->second << endl;
auto it = myMap.find(50);
if (it != myMap.end())
cout <<it->first << "-"<<it->second << endl;
cout << "3有" << myMap.count(3) << endl;
}
int main()
{
unordered_map<int, string> map1{{1, "hel"}, {2, "ahskg"}, {3, "world"}};
cout<<map1.at(1)<<endl;
return 0;
}
5. 基础容器queue和stack
5.1. stack
-
stack是一种容器适配器,专门用在具有后进先出(LIFO)操作的上下文环境中,其删除只能从容器的一端进行元素的插入与提取操作。
-
stack是作为容器适配器被实现的,容器适配器即是对特定类封装作为其底层的容器,并提供一组特定的成员函数来访问其元素,将特定类作为其底层的,元素特定容器的尾部(即栈顶)被压入和弹出。
-
stack的底层容器可以是任何标准的容器类模板或者一些其他特定的容器类,这些容器类应该支持以下 操作: empty:判空操作 back:获取尾部元素操作 push_back:尾部插入元素操作 pop_back:尾部删除元素操作
-
标准容器vector、deque、list均符合这些需求,默认情况下,如果没有为stack指定特定的底层容器, 默认情况下使用deque。
-
stack的所有元素进出都必须符合“先进后出”的条件,只有stack顶端的元素,才有机会被外界取用。stack不提供随机访问功能,也不提供迭代器。
函数接口 | 接口说明 |
---|
stack() | 构造空栈 |
empty() | 检测栈是否为空 |
top() | 返回栈顶元素的引用 |
size() | 返回栈中元素个数 |
push() | 将元素val压入stack中 |
pop() | 将stack中尾部的元素弹出 |
class MyQueue{
private:
stack<int> in, out;
int q_size = 0;
public:
void push(int x){
in.push(x);
q_size++;
}
int pop(){
in2out();
int r = out.top();
out.pop();
q_size--;
return r;
}
int peek(){
in2out();
int r = out.top();
return r;
}
bool empty(){
return q_size==0;
}
void in2out(){
if(out.empty()){
while(!in.empty()){
out.push(in.top());
in.pop();
}
}
}
};
class MyStack{
private:
queue<int> temp;
public:
MyStack() {}
void push(int x){
temp.push(x);
for(int i=0; i<temp.size()-1; i++){
temp.push(temp.front());
temp.pop();
}
}
int pop(){
int r=temp.front();
temp.pop();
return r;
}
int top(){
return temp.front();
}
bool empty(){
return temp.empty();
}
};
6. 基础容器 string
#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <vector>
#include <sstream>
using namespace std;
string String_test(){
string str="hello world";
cout<<"str="<<str<<endl;
const char *p = str.c_str();
printf("str=%s\n", p);
cout<<"字符串长度等于: "<<str.length()<<endl;
cout<<"字符串最后一位"<<str[str.length()-1]<<endl;
cout<<"字符串最后一位"<<str.back()<<endl;
cout<<"切片"<<str.substr(0, 5)<<endl;
if (0 == str.compare("hello world")){
printf("字符串等于hello world\n");
}
if(!str.empty()){
printf("字符串不为空\n");
}
const char* pszName = "liitdar";
char pszCamp[] = "alliance";
string strName = pszName;
string strCamp = pszCamp;
cout << "strName is: " << strName << endl;
cout << "strCamp is: " << strCamp << endl;
string str2 = "world";
size_t son_location = str.find(str2);
if (son_location != string::npos){
cout<<"找到子串str2, 在str位置是: "<<son_location<<endl;
}
str.insert(6, "zjq's ");
str.insert(5, 4, 'a');
cout<<str<<endl;
int n1 = 1234;
stringstream str3;
str3 << n1;
string str4 = str3.str();
cout<<"将int类型转化为string类型: "<<str4<<endl;
string str5;
str3 >> str5;
cout<<str5<<endl;
int numb2 = 456;
string str6;
str6 = to_string(numb2);
cout << "str6 is: " << str6 << endl;
return str6;
}
int main(int argc, char const *argv[])
{
string str = String_test();
cout<<str<<endl;
return 0;
}
7. 本文涉及到的头文件
#include <algorithm>
#include <vector>
#include <list>
#include <iostream>
#include <map>
#include <set>
#include <unordered_set>
#include <unordered_map>
#include <numeric>
#include <string.h>
#include <queue>
#include <stack>