JAVA 转 C++ 必记 PartB
来源:互联网 发布:小米推送php demo 编辑:程序博客网 时间:2024/06/06 00:18
JAVA 转型C++必记 partB
顺序容器
有5个顺序容器和其特性:
vector : 可变的数组,支持快速随机访问。在尾部以外插入元素和删除元素可能会很慢。
deque:双端列表。支持快速随机访问,在头部和尾部以外插入和删除元素可能会很慢。
list:双向链表,支持双向顺序访问,在任意插入或者删除速度好快。
forward_list :单向链表,支持单向顺序访问。在任意插入或者删除速度好快。
string:类似于vector容器,随机访问快,在尾部插入和添加速度快。
顺序容器比较像JAVA的List,也像JAVA一样指定容器的存储类型,但是需要注意的是C++的容器是必须要在<>中指定类型的,而且他们叫做模板类不是JAVA所称为的泛型。
deque<double> deque_for_double;
容器的构造函数,有默认构造函数,还有以下两个构造函数
vector<noDefualt> v1(10); //如果有默认构造函数则成功通过编译,使用默认构造函数初始化10个元素
vector<NoDefault> v1(10,{10,”adsf"}); //使用指定的构造函数初始化10个成员。
vector<NoDefault> v2(v1) //复制v1所有元素为v2进行初始化,注意v1必须要和v2一样的容器类型以及元素类型一致,否则报错。
vector<NoDefault> v1 = {NoDefault(11, "11"), NoDefault(13, “13")}; //通过初始化列表进行初始化。
vector<NoDefault> v1 = {NoDefault(11, "11"), NoDefault(13, "13")};
deque<NoDefault> d1(v1.begin(),v1.end()); //通过迭代器进行初始化,不限制容器类型。
vector<NoDefault> v2(v1.begin(),v1.end());
迭代器
迭代器基本和内置数组的迭代器一致,其中end是指向最后一个元素的后一位。[begin,end)开闭区间。
vector<NoDefault> v1(10, {10, "adsf"});
vector<NoDefault>::iterator it = v1.begin();
while (it != v1.end()) {
static int index = 0;
cout << it->toString() << " ---- index : " << index++ << endl;
it++;
}
容器的迭代器函数:
v1.begin(); //这个是获得一个带const或者不带const的正向迭代器,做了begin做了const的重载,当v1是const的时候返回const_iterator。
v1.rbegin(); //这个获得一个反向的迭代器,同样做了const和非const的重载。
v1.cbegin(); //这个获得个正向的迭代器const版本。
v2.crbegin(); //这个获得一个反向的迭代器同样返回的是const版本。
const vector<NoDefault> v1(10, {10, "adsf"});
vector<NoDefault>::const_iterator iter = v1.begin(); //这里主要返回的是一个const版本。
const_iterator 不能修改容器的内容,即容器中元素也是不能修改元素中的成员数据的。
iter->setNumberAndText(0,”test"); //报错的是无法再到定义了const的成员函数。因为iter解引用之后是一个const的对象。
array 是类似于内置数组的容器,这个容器和内置数组一样都是固定了容器的长度无法自动扩展的。
array<int,10> ai1; //定义了一个array容器,int类型一共有10个元素。
array<int,10> ai1 = {10} //ai1数组第一个元素是10 其他9个为0。
array<int,10> newA1 = ai1 //array可以支持复制
assign 不同容器的元素替换
vector<string> v1 = {"A","B","C","D","F"};
deque<string> d1 = {"1","2","3","4","5","6","7","8"};
d1.assign(v1.begin(),v1.end()); //d1目前结果:ABCDF完全替换迭代器中的内容
需要注意的是array是不能使用assign的
使用swap讲两个相同容器的元素掉转,这个速度相当快,因为是直接掉转变量的指向。
vector<string> letter = {"A","B","C","D","F"};
vector<string> number = {“1","2","3","4","5"};
swap(letter,number); // 所有元素交换,与letter.swap(number);等价
获得容器大小,forward_list不能获得容器大小
vector<string> letter = {"A","B","C","D","F"};
cout << letter.size() << endl; //获得容器大小
cout << letter.max_size() << endl; //获得容器最大容量
cout << letter.empty() << endl; //获得容器是否为空
关于容器的运算符
1、如果两个容器的元素不一致,看首个不一致的元素进行对比。
2、如果两个容器的元素都一样,但是元素的数量不一样,数量多的大。
3、如果两个容器的元素和数量都一样则两个容器一样。
vector<int> v1 = {1,3,5,7,8,9};
vector<int> v2 = {1,3,6,7};
vector<int> v3 = {1,3,5,7,8};
vector<int> v4 = {1,3,5,7,8,9};
v2 > v1 // true 因为index 2的元素相对比,发现v2[2] 比 v1[2]要大。
v1 > v3 // true 因为v1和v3的元素基本一致,发现v1的元素大于v3。
v1 == v4 // true 因为v1无论元素和size都和v4一致。
注意:如果vector中的元素类型是自定义类就必须定义><=运算符,否则会产生错误。因为最终他们会使用元素中的对象的运算符。userA > userB 如果无定义>运算符则非法。
元素的增加和删除
push_back 添加元素到尾部,array和 forward_list 不能使用。
vector<string> letter = {"A","B","C","D","F"};
letter.push_back(“G”);
push_front 添加元素到头部,list、forward_list、deque之外不能使用push_front
deque<string> number = {"0","1","2","3","4","5"};
number.push_front(“-1”);
insert插入元素,list、deque、vector、string都支持 insert 函数,forward_list有自己特殊的insert的函数。
deque<string> number = {“0","1","2","3","4","5"};
number.insert(number.begin() + 2 , “1.5”); //开始的迭代器移动向后一定两位(即下标为2)的前面插入字符串“1.5”元素。
使用迭代器插入一组数据
deque<string> number = {"0","1","2","3","4","5"};
vector<string> number2 = {"-2","-1"};
number.insert(number.begin(),number2.begin(),number2.end());
number.insert(number.end(),{“6","7"}); //使用初始化列表插入
insert函数调用后返回新插入元素的迭代器
上面函数改成:
deque<string>::iterator iter = number.insert(number.end(),{“6","7"}); // *iter == “6”
使用emplace函数插入元素,使用emplace函数只需要传入需要添加的元素的构造方法元素即可。
deque<NoDefault> d1 = {NoDefault(0,"first_one")};
d1.emplace_front(-1,”-1"); //对应push_front
d1.emplace_back(1,”1"); //对应push_back
d1.emplace(d1.begin() + 2 ,1,”0.5"); // 对应insert
使用back 和front访问成员
deque<string> d1 = {"1","2","3"};
cout << d1.front() << endl; //每个容器都有front成员
cout << d1.back() << endl; // 除了forward_list容器之外都有back成员
访问成员函数都是返回相应元素的引用
deque<string> d1 = {"1","2","3"};
auto &first = d1.front();
first = “10"; //d1[0] == 10
auto last = d1.back();
last = “30"; //d1[2] == 3 因为这里不是引用,没有打上&
string &second = d1.at(1); //我们可以使用at函数获得元素的应用
second = “20"; // d1[1] == 20
cout << d1[0] << "," << d1[1] << "," << d1[d1.size() - 1] << endl; //同样我们可以使用下标的方式获得元素的引用。
注意:不支持随机访问的容器强制使用会产生异常,forward_list , list不支持随机访问。
容器中删除元素
deque<string> d1 = {“1","2","3","4","5","6"};
d1.pop_back(); //删除最后的一个元素,返回void
d1.pop_front(); //删除最前面的一个元素,返回void
pop_back(); 同样是在forward_list、array中不支持的
pop_front(); 同样是在vector、array中不支持的
同时可以使用erase指定删除元素。
auto iter = d1.erase(d1.begin() + 3); //传入需要删除的迭代器,并返回被删除元素的后个迭代器
cout << *iter << endl; //输出字符串5
可以使用clear 和 erase删除多个元素
auto iter = d1.erase(d1.begin() + 2, d1.end()); //删除除下标为2到 容器的尾部,并返回容器的新尾部。
d1.clear(); //清空所有容器中的元素。
forward_list 有很多特殊的函数API下面就介绍一下:
由于forward_list 单向链表的原因,所以在删除或者插入元素都是需要获得插入或者删除的目标的前一个元素,如果我们想在最前面的元素中插入一个元素,那我们就需要获得想end迭代器一样的before_begin迭代器。
forward_list<string> flist = {"1", "2", "3", "4", "5", "6", "7", "8"};
auto prev = flist.before_begin(); //由于删除元素需要获得目标元素的前一个元素
auto curr = flist.begin();
while (curr != flist.end()) {
int itemVal = stoi(*curr);
if (itemVal % 2 == 0) {
curr = flist.erase_after(prev);
} else {
prev = curr;
curr++;
}
}
同样我们可以使用独特的erase函数
forward_list<string> flist = {"1", "2", "3", "4", "5", "6", "7", "8"};
auto iter = flist.erase_after(++flist.begin()); //此处删除的是字符串3下标为2的元素,但是传入的迭代器是下标为1的迭代器。
cout << *iter << endl; //此处返回4则删除的目标元素后一个位迭代器。
flist.insert_after(++flist.begin(),”2.5”); //添加一个元素到下标为2的后一位。
forward_list<string> flist = {"1", "2", "3", "4", "5", "6", "7", "8"};
flist.erase_after(++flist.begin(),flist.end()); //删除一组范围的元素,剩下1和 2 两个元素。
关于顺序容器的resize函数
vector<string> v1 = {"0","1","2","3","4","5"};
v1.resize(10,”undefine"); //由于函数中有6个元素,v1扩容到10个元素,新增的元素为”undefine”,如果后面的参数不传则使用string的默认构造。
v1.resize(3); //如果resize的元素比原来容器中的元素还要少,则删除后面的元素。剩下 0 1 2 三个元素。
关于对容器操作后,会使得迭代器失效
1、vector、string容器在存储控制重新分配之后迭代器无效,如果在插入元素,在插入元素之前的迭代器仍然有效,如果在插入之后的迭代器则无效。
2、deque在除了首尾插入元素迭代器仍然有效之外,其余插入操作到会导致迭代器失效
3、如果是链表插入数据后,迭代器仍然有效。list和 forward_list 就是链表。
4、删除链表中的元素之后,不是指向被删除的迭代器仍然有效。
5、deque 如果删除除了首尾之外的元素,都会导致迭代器失效,如果删除尾部元素,end迭代器也会失效。
6、vector 被删除的元素前面的迭代器仍然有效,但是注意的是删除元素后end迭代器会失效。
注意:所以如果判读是否为结尾,最好都是本次都调用end函数获得尾部迭代器。
关于vector 的容量管理
vector<string> v1 = {"0", "1", "2", "3", "4", “5"};
cout << v1.capacity() << endl; // 获得在不从新分配空间的情况下可存储个数
v1.reserve(30); // 分配至少可以容纳30个元素的空间
cout << v1.capacity() << endl;
v1.shrink_to_fit(); // 将capacity和 size的容量一致,只要添加元素就会重新分配空间。
cout << v1.capacity() << endl;
String 的实用方法
char *cp = "what the fuck!";
char noNull[] = {'F', 'U', 'C', 'K'};
string s1(cp); //从char指针初始化字符串
string s2(cp + 9, 4); //从char指针移动9位然后截取4个字符 结果:fuck
string s3(noNull); //错误因为noNull 最后一个元素没有一个空字符,作为结尾符
char haveNull[] = {'F', 'U', 'C', 'K', ' ‘}; //这个比noNull多了一个空字符做结尾符。
string s4(haveNull); //从字符数组初始化字符串
string s5(noNull, 4); //从字符数组中获得4个字符并初始化
string s6(s1,9); //从字符串中从下标为9到结尾获得字符寸进行初始化
string s7(s1,9,4); //从字符串下标为9开始获取4个字符进行初始化
同时string支持insert erase assign函数。
而且还支持append 和 replace函数进行字符串修改
string s1 = "what the ...";
s1.replace(9,3,”fuck"); //在下标为9开始删除3个字符然后删除的位置添加fuck字符串
cout << s1 << endl; //输出结果 what the fuck
s1.append(“!!!"); //在s1结尾添加 !!!字符串
cout << s1 << endl; //输出结果 what the fuck!!!
string name("TonyYan");
auto pos = name.find(“Tony"); //这个等同于 java的 indexOf
string something("skdkl56akk21dsdf");
string number(“0123456789");
auto index = something.find_first_of(number); //在something字符串中获得number的任意字符位置就是有数字的第一个下标,这里返回5
string something("56dd23232");
string number("0123456789");
auto index = something.find_first_not_of(number); //返回不存在number中的任意字符的下标,结果为2
逆向搜索有:
string sentence = "what the fuck! what going on?";
sentence.rfind(“what");
auto pos1 = sentence.find_last_of(“!,.?");
auto pos1 = sentence.find_last_not_of(“?!,.");
除了使用关系运算符还可以使用类似JAVA的字符串对比方法
string a = "abd";
string b = "abc";
auto result = a.compare(b); //返回1 因为a大于b 如果等于返回0 大于返回大约0的值如果小于 返回负数
字符串转换其他类型
int i = 88;
string i_string = to_string(i);
int i = 88;
string i_string = to_string(i);
string snumber_int = "1";
int nunber_int = stoi(snumber_int);
string snumber_double = "23.66";
double number_double = stod(snumber_double);
相应的转型函数都是以sto开头然后结合类型的缩写,例如stold转成 long double。
容器适配器
所谓的容器适配器就是对顺序容器进行进一步的封装
一共有三种容器适配器
stack、queue、priority_queue
同样函数:
empty、size、swap、size_type[size的类型]、value_type[元素类型]、container_type容器实现
栈适配器
stack可以接受底层容器有list vector deque,默认情况下的底层是使用deque
stack<int> s1; //如果需要指定相应的底层容器可以写成 stack<int,vector<int>> s1;
for (size_t ix = 0; ix != 10; ++ix) {
s1.push(ix);
}
while(!s1.empty()){
cout << s1.top() << endl; //获得最后一个元素
s1.pop(); //弹出最后一个元素
}
queue 和 priority_queue
queue 是采用了标准的前进先出策略,而priority_queue使用优先级进行顺序安排的。
priority_queue 默认使用vector实现。
queue相应的比stack多出一下函数可以使用
front 获得首个元素或者 尾部元素
back 获得尾部元素,只能在queue使用。
而priority_queue是采用<操作符来判断优先级的。如果是int的话肯定数字越大优先级就越高了。
priority_queue<int> pq;
pq.push(6);
pq.push(2);
pq.push(5);
pq.push(8);
pq.push(1);
while(!pq.empty()){
cout << pq.top() << ends;
pq.pop();
} //输出结果为:86521 数字越大越优先
关于一些不限制容器类型的算法函数,包括查找、排序、唯一排除等。
这些算法函数都是来自两个头文件 :
#include <algorithm>
#include <numeric>
find函数,传入开始和结尾迭代器、需要查找的数据,返回结果的迭代器,如果没有查找到相应的元素返回一个结尾迭代器。
vector<int> vector_number = {566, 56, 4, 2487, 615, 3, 16, 5, 67, 11, 23, 662};
auto target_iterator = find(vector_number.begin(), vector_number.end(), 999);
cout << (target_iterator != vector_number.end() ? to_string(*target_iterator) : "Didn't find it out!") << endl;
accumulate函数,将迭代器范围中的元素相加,并在第三个元素基础上进行添加
vector<int> vector_number = {566, 56, 4, 2487, 615, 3, 16, 5, 67, 11, 23, 662};
int sum = accumulate(vector_number.begin(),vector_number.end(),0);
equal函数判断两个容器是否相等,第一、二个参数是需要对比的容器迭代范围,第三个参数是另外一个需要对比的容器起始迭代器(注意:equal函数就算两个容器的元素个数不一致也不会判断为false的只要存在的元素一一对应就返回true)
vector<int> vector_number = {566, 56, 4, 2487, 615, 3, 16, 5, 67, 11, 23, 662};
list<int> list_number = {2487, 615, 3, 16, 5, 67, 11};
bool isEqual = equal(vector_number.begin() + 3, vector_number.end() - 2, list_number.begin());
fill函数将迭代器范围内的元素重置为第三个参数的值,这里将下标为2的元素到倒数第三个元素重置为666
vector<int> vector_number = {566, 56, 4, 2487, 615, 3, 16, 5, 67, 11, 23, 662};
fill(vector_number.begin() + 2, vector_number.end() - 2, 666);
fill_n函数从第一个参数作为起始,重置8个元素为666,运行结果同上
vector<int> vector_number = {566, 56, 4, 2487, 615, 3, 16, 5, 67, 11, 23, 662};
fill_n(vector_number.begin() + 2, 8, 666);
back_inserter函数返回一个插入迭代器
vector<int> v1 = {99, 99, 99};
auto insert_iterator = back_inserter(v1);
for (int i = 0; i < 10; i++) {
*insert_iterator = 66;
}
v1内容:99 99 9966 66 66 66 66 66 66 66 66 66 红色部分为通过插入迭代器添加的元素
下面代码结合fill_n效果一致:
vector<int> v1 = {99, 99, 99};
auto insert_iterator = back_inserter(v1);
fill_n(insert_iterator, 10, 66);
关于内置数组使用copy函数实现复制
int arrayA[] = {251, 54, 3, 45, 6, 8, 4, 3, 46};
int arrayB[end(arrayA) - begin(arrayA)];
auto last_p = copy(begin(arrayA), end(arrayA), arrayB); //将数组A复制到数组B并返回数组B的结尾迭代器
auto *start_p = arrayB;
while (start_p != last_p) {
cout << *start_p++ << " " << flush;
}
replace函数将容器中的4的元素全部改成3
vector<int> v1 = {645, 5878, 13, 4, 4, 63, 4, 46, 53, 4, 6, 56};
replace(v1.begin(), v1.end(), 4, 3);
输出结果:645 5878 13 3 3 63 3 46 53 3 6 56
replace_copy函数原来容器不变的情况下将修改结果添加到其他容器当中。
vector<int> v1 = {645, 5878, 13, 4, 4, 63, 4, 46, 53, 4, 6, 56};
vector<int> v2;
auto insert_iterator = back_inserter(v2);
replace_copy(v1.begin(), v1.end(), insert_iterator, 4, 3);
输出结果:
v1 —> 645 5878 13 4 4 63 4 46 53 4 6 56
v2 —> 645 5878 13 3 3 63 3 46 53 3 6 56
坑爹的unique函数,主要作用删除重复元素
vector<string> v1 = {"tony","egg","TT","alice","fang","TT","tony","jose","chao","egg","TT","jose"};
sort(v1.begin(),v1.end()); //首先需要进行排序否则unique无法获得重复元素
print(v1); // TT TT TT alice chao egg egg fang jose jose tony tony
auto end_unique = unique(v1.begin(),v1.end()); //将重复元素往后移动,然后返回需要删除的元素的迭代器
print(v1); // TT alice chao egg fang jose tony jose tony
v1.erase(end_unique,v1.end()); // 删除重复元素,TMD就不能一气呵成吗,屌多狗余~
print(v1); //TT alice chao egg fang jose tony
向算法函数传入函数
一般如果传入函数作为参数称为谓词(predicate)
谓词又分一元谓词和二元谓词,所谓的一元而二元就是说有函数一个参数和两个参数的区别,下面就是一个通过二元谓词改变sort的排序方式。
定义方法:
bool isShorter(const string &name_a,const string &name_b){
return name_a.size() < name_b.size();
}
在sort函数中使用二元谓词
vector<string> names = {"alice","tony","teddy","andy","egg","john","david"};
sort(names.begin(),names.end(),isShorter);
排序结果:egg andy tony john alice teddy david
我们可以通过stable_sort 排序函数,相同长度的名字,继续通过原来的方式进行排序
vector<string> names = {"alice","tony","teddy","andy","egg","john","david"};
stable_sort(names.begin(),names.end(),isShorter);
但是我在实验中并没有奏效。
其实谓词就是可调用的对象,换句话老说我们可以使用函数指针
vector<string> names = {"alice","tony","teddy","andy","egg","john","david"};
bool (*method_p)(const string&,const string&) = isShorter;
// auto method_p = isShorter; 等价于上面
// decltype(isShorter) *method_p = isShorter; 等价于上面
sort(names.begin(),names.end(),method_p);
print(names);
除此之外我们还可以使用lambda,lambda可以理解为一种匿名函数,同时这个函数还可以使用参数以外的变量
以下是lambda表达式的基本格式:
[需要在lambda函数中使用的变量](参数) ->返回类型 {函数体}
英文:
[capture list](parameter list) -> return type {function body}
我们可以忽略 parameter list 和 return type但是绝对不能缺失 capture list 和 function body如果我们没有需要捕获的值 可以以[]来定义 capture list
下面使用find_if函数查找checkName变量中的名字,并返回相应的iterator,如果没有查找到相应条件的元素则返回end iterator.
vector<string> names = {"alice", "tony", "teddy", "andy", "egg", "john", "david"};
string checkName = "andy";
auto result = find_if(names.begin(), names.end(), [checkName] (const string &nameItem) {
return nameItem == checkName;
}); //注意如果checkName在 lambda 的[]没有定义,则无法使用checkName,需要注意的是这里的checkName是需要拷贝的,而且在lambda的function body 中无法修改checkName 的值,如果想修改checkName的值可以在parameter list的)后面添加 mutable所以可以将上述代码改成以下的形式,这个时候使用checkName就可以进行修改了!
auto result = find_if(names.begin(), names.end(), [checkName] (const string &nameItem)mutable {
checkName = "tony";
return nameItem == checkName;
});
使用for_each方法,没有JAVA PHP那些那么屌,可以使用for( : ) 的方式调用,下面是C++的for_each 函数的使用:
vector<string> names = {"alice", "tony", "teddy", "andy", "egg", "john", "david"};
for_each(names.begin(),names.end(),[](const string &nameItem){
cout << nameItem << " " << flush;
}); //注意cout可以使用的原因是因为他不是定义在函数的局部对象,cout是属于全局对象所以可以不通过capture list进行捕获。
下面是关于值捕获和引用捕获的不同
int numberA = 10;
auto fun = [numberA] { return numberA + 3; }; //等于函数指针
auto numberB = fun(); //调用lambda由于我们是值捕获所以修改不会让numberA改变
cout << numberA << " " << flush; //10
cout << numberB << " " << flush; //13
下面是引用捕获
int numberA = 10;
auto fun_p = [&numberA](int num) { return numberA += num; };
auto numberB = fun_p(3); //这里numberB也是返回一个引用(int &numberB)
cout << numberA << " " << flush; //13
cout << numberB << " " << flush; //13
C++ 最叻就搞埋晒咩隐性之类的,同样在lambda会有一个叫隐性捕获。
在capture list 中 [=]这种形式代表,捕获所有调用函数的局部变量,同时而拷贝的方式进行捕获。如果是[&]就是以引用的形式捕获。
当然还有更加灵活的写法:[
1、[&,c] 全部以引用方式捕获,其中c使用拷贝捕获。
2、[=,&os] 全部以拷贝方式捕获,其中os使用引用捕获。
一下就是使用案例:
vector<string> names = {"alice", "tony", "teddy", "andy", "egg", "john", "david"};
fstream fs("/Users/Tony/Desktop/log", fstream::app);
int index = 1;
for_each(names.begin(), names.end(), [=, &fs](string &name) mutable {
fs << index++ << " - > " << name << endl;
});
fs.close();
关于lambda的返回值,以上我们都没有写过一次返回类型,因为我们所有lambda当中只有一个return语句可以忽略编写return type但是如果是多个return语句就需要显性写上return type
auto fun_p = [](const string &a, const string &b) -> int { //由于这里有多个return语句如果无指定就会报错。
if (a == b) { return 0; } else {
if (a > b) { return 1; } else { return -1; }
}
};
cout << fun_p("a","b") << endl;
使用bind函数
bind函数比较简单就是将函数进行修改成一个合适调用的指针。
void addIndexStr(int &index, string &name);
using namespace std::placeholders; //_1 _2 等占位符位置
int main() {
vector<string> names = {"alice", "tony", "teddy", "andy", "egg", "john", "david"};
int index = 1;
auto new_addIndexStr = bind(addIndexStr, index,_1); //生成一个一元谓词
for_each(names.begin(),names.end(),new_addIndexStr);
print(names);
}
void addIndexStr(int &index, string &name) {
name = to_string(index++) + " -> " + name;
}
下面这个例子就更加凸显bind的9唔搭8的功底
int test(int a,int b,int c,int d);
int main() {
auto new_test = bind(test,_2,3,_1,9);
cout << new_test(5,10) << endl; //这个调用test的参数顺序是test(10,3,5,9)
}
int test(int a,int b,int c,int d){
return (a - b) + (d - c);
}
但是bind对于绑定引用参数还需要使用ref进行混合使用。
int main() {
vector<string> names = {"alice", "tony", "teddy", "andy", "egg", "john", "david"};
int index = 1;
fstream fs("/Users/Tony/Desktop/log", fstream::trunc);
auto new_save = bind(save, _1, ref(fs), ref(index)); //bind是的参数是使用其拷贝的,所以需要使用ref函数。
for_each(names.begin(), names.end(), new_save);
fs.close();
}
void save(string &name, fstream &fs, int &index) {
name = to_string(index++) + " -> " + name;
fs << name << endl;
}
再探插入迭代器:
front_inserter迭代器:
deque<int> numbers = {6,6,6,6,6};
auto f_iter = front_inserter(numbers); //注意front_inserter需要容器支持push_front函数
*f_iter = 1;
*f_iter = 2;
*f_iter = 3;
numbers :3 2 1 6 6 6 6 6
back_inserter迭代器:
deque<int> numbers = {6,6,6,6,6};
auto f_iter = back_inserter(numbers); //需要容器自身支持push_back函数
*f_iter = 1;
*f_iter = 2;
*f_iter = 3;
numbers :6 6 6 6 6 1 2 3
inserter迭代器:
deque<int> numbers = {6,6,6,6,6};
auto f_iter = inserter(numbers,numbers.begin() + 2); //一个参数为容器第二个参数为相应位置的迭代器
*f_iter = 1;
*f_iter = 2;
*f_iter = 3;
numbers :6 6 1 2 3 6 6 6
还有关于流的迭代器:
deque<int> numbers;
istream_iterator<int> int_it(cin),end_it;
// istream_iterator 默认构造函数是创建一个结尾符的迭代,传入流对象的构造函数返回一个读取流的迭代器。
while(int_it != end_it){
numbers.push_back(*int_it++); // 读取完移位
}
利用算法函数快速使用流:
istream_iterator<int> int_it(cin),eof;
deque<int> numbers(int_it,eof);
——————————————————————-
istream_iterator<int> int_it(cin),eof;
cout << accumulate(int_it,eof,0) << endl;
输出流迭代器:
vector<int> v1 = {12,9,6,13,6,97,23,29,28,18,13,28,23};
//第二个参数是每次迭代负责后添加一个字符串作为后缀
ostream_iterator<string> oit(cout," “);
for_each(v1.begin(),v1.end(),[&oit](const int &item){
*oit++ = to_string(item);
});
反向指针:
vector<int> v1 = {1,2,3,4,5,6,7,8,9};
auto iter = v1.rbegin();
while(iter != v1.rend()){
cout << *iter++ << " " << flush;
} //输出9 8 7 6 5 4 3 2 1
关联容器
关联容器有:set 和 map
其中又可以有可重复的、无序的。所以有以下的实质性容器:
map : 有序、关键字唯一
set:有序、关键字唯一
multimap:有序、关键字可重复
multiset:有序、关键字可重复
unordered_map:无序【哈希函数组织】、关键字唯一
unordered_set:无序【哈希函数组织】、关键字唯一
unordered_multimap:无序【哈希函数组织】、关键字可重复
unordered_multiset:无序【哈希函数组织】、关键字可重复
其实基本上与JAVA的MAP概念一样,下面来点实例:
map<string, string> contacts = {
{"13656545523", "TONY"},
{"13662386142", "HOME"},
{"13561254578", "MOTHER"},
{"13953421023", "FATHER"},
{"13955612643", "FRIEND_ANDY"},
{"13955612643", "FRIEND_VIVI"},
};
map<string, int> call_recode;
set<string> blackList = {"FRIEND_ANDY", "FRIEND_VIVI"};
string someone_call;
while (cin >> someone_call) {
if(someone_call == "stop"){
break;
}
call_recode[someone_call]++; //如果元素不存在,则马上添加并初始化
string contact = contacts[someone_call]; //获得来电显示
if (contact.empty()) { //没有联系人记录,由于会自动创建,所以返回控制字符串
cout << "phone : " << someone_call << " calling !" << endl;
} else {
if (blackList.find(contact) != blackList.end()) {
cout << " calling reject ! " << endl;
} else {
cout << contact << " calling !" << endl;
}
}
}
关于multiset 和 set
vector<int> v1;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
v1.push_back(i);
}
multiset<int> ms(v1.begin(),v1.end());
set<int> s(v1.begin(),v1.end());
cout << ms.size() << endl; // 20
cout << s.size() << endl; //10
注意:关联容器正常的排序行为是根据<运算符进行的。
pair 类型 这个类似与 JAVA 当中的 Map.Entry类似。
里面有两个属性分别是 first 和 second对应 entry的 key和 value
pair<string,string> pairA = {"key","value"};
cout << pairA.first << endl; //显示 key
cout << pairA.second << endl; //显示 value
pair还有一个生成函数 makr_pair(v1,v2)
int key = 10;
string value = "content";
auto pairA = make_pair(key,value); //返回一个pair<int,string>
关联容器的类型:
key_type : 容器关键字类型
mapped_type : 容器对应的value类型,只适用于map
value_type:对于map会返回一个对应的pair<key,value>类型,对于set会返回与key_type一样的类型。
关于容器迭代器
Set 迭代器解引用会返回对应的value_type类型
set<int> setA = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
set<int>::iterator iter = setA.begin();
while (iter != setA.end()) {
cout << *iter++ << " " << flush;
}
Map 迭代器解引用会返回一个map相应的pair类型
map<string, string> mapA = {
{"TONY", "YAN"},
{"EGG", "LI"},
{"JOHN", "WONG"}
};
map<string, string>::iterator iter = mapA.begin();
while (iter != mapA.end()) {
cout << "key : " << iter->first << " , value : " << iter->second << endl;
iter++;
}
注意set 迭代器都是 const所以不能修改,而 map迭代器关键字部分是const,value部分可以修改
SET插入数据的两种方式:
set<int> s1 = {1,2,3,4,5,6,7,8,9};
vector<int> v1 = {20,21,22,23,24,25};
s1.insert({10,11,96,65,18});
s1.insert(v1.begin(),v1.end());
Map插入数据
map<string, int> call_recode = {
{"TONY", 1},
{"EGG", 1},
{"JOHN", 1}
};
//返回一个pair first指向map的pair second是否成功
auto result = call_recode.insert({"TONY", 1});
cout << "insert key: " << result.first->first //map的key
<< " , value : " << result.first->second //map的value
<< "[" << result.second //插入是否成功
<< "]" << endl;
// 输出:insert key: TONY , value : 1[0]
auto result2 = call_recode.insert({"ALICE", 1});
cout << "insert key: " << result2.first->first
<< " , value : " << result2.first->second
<< "[" << result2.second
<< "]" << endl;
// 输出:insert key: ALICE , value : 1[1]
需要注意的是如果向一个multimap 插入数据是直接返回一个map对应的pair因为multimap不存在插入不成功的情况:
multimap<string, int> call_recode = {
{"TONY", 1},
{"EGG", 1},
{"JOHN", 1}
};
auto iter = call_recode.insert({“TONY",2}); //返回插入的位置的迭代器
cout << iter->first << "," << iter->second << endl; //显示TONY,2
—iter;
cout << iter->first << "," << iter->second << endl; //显示TONY,1
删除元素的三种方法
map<string, int> call_recode = {
{"TONY", 1},
{"EGG", 1},
{"JOHN", 1}
};
call_recode.erase(“JOHN");
call_recode.erase(call_recode.begin());
call_recode.erase(call_recode.begin(),call_recode.end());
map的访问,map支持下标访问以及at函数访问。注意:multimap是不能使用下标访问。
使用下标访问,如果元素不存在会自动创建,如果不希望在查找的过程当中自动创建的话,可以使用at函数当at函数传入的关键字无法搜索到结果的情况下,不会自动创建会报std::out_of_range的错误。
map<string, int> call_recode = {
{"TONY", 1},
{"EGG", 1},
{"JOHN", 1}
};
auto &value = call_recode.at(“TONY"); //返回一个value的引用,如元素不存在即报错
cout << value << endl;
value = 10;
cout << call_recode["TONY"] << endl; //通过下标访问,如果元素不存在则创建一个
Set使用find函数和count函数
multiset<int> ms1 = {1,1,1,1,1,5,5,5,5,5};
//返回迭代器,如果不存在则返回尾后迭代器
multiset<int>::iterator iter = ms1.find(1);
//统计1在set中的数量,如果是set则只有一,因为set始终唯一
multiset<int>::size_type size = ms1.count(1);
Map中的find函数和count函数
multimap<string,string> authors = {
{"TONY","C++"},
{"TONY","JAVA"},
{"TONY","C#"},
{"JOHN","OC"}
};
int count = authors.count(“TONY"); //返回三
auto first_iter = authors.find(“TONY"); //返回key为TONY的第一个迭代器
while(count--){
cout << first_iter->first << "," << first_iter->second << endl;
first_iter++;
}
对于multimap除了上面一种遍历方法还有以下两种:
第一种:
multimap<string, string> authors = {
{"TONY", "C++"},
{"TONY", "JAVA"},
{"TONY", "C#"},
{"JOHN", "OC"}
};
string target_key = "TONY";
auto first_target = authors.lower_bound(target_key); //获得目标关键字首个迭代器
auto end_target = authors.upper_bound(target_key); //获得目标关键字尾后迭代器
for ( ; first_target != end_target; first_target++) {
cout << first_target->first << "," << first_target->second << endl;
}
第二种:
multimap<string, string> authors = {
{"TONY", "C++"},
{"TONY", "JAVA"},
{"TONY", "C#"},
{"JOHN", "OC"}
};
for (auto pair_pos = authors.equal_range("TONY");
pair_pos.first != pair_pos.second;
pair_pos.first++){
cout << pair_pos.first->first << "," << pair_pos.first->second << endl;
} // 返回的pair_pos first为目标关键字的第一个迭代器 second目标关键字的尾后迭代器。
关于无序容器后面再详细讲解
动态内存
shared_ptr :共享智能指针,每次赋值或者销毁,计数器会加一或者 减一
unique_prt:独享指针,只能有一个指针,指向该空间。
weak_prt:弱引用指针,每次赋值或者销毁不会影响计数器。
关于shared_prt 的基本用法
int main() {
shared_ptr<User> user_p = make_shared<User>("TONY","TONYPWD");
//通过make_shared函数在堆中构造User对象
cout << user_p->getUsername() << endl;
//和普通指针的调用方式一致
cout << (*user_p).getPassword() << endl;
shared_ptr<User> user_p2 = user_p;
// 通过赋值到另外一个只能user_p2,引用计数器+1
shared_ptr<User> user_p3(user_p);
// 通过构造函数 将user_p 拷贝到 user_p3, reference count +1
cout << user_p3.use_count() << endl;
// user_count 获得当前内存位置,有多少个智能指针指向。这里返回3
shared_ptr<User> user_from_method = getUserInstance("YAN","pwd");
//这个解决了 局部 栈普通指针不能返回的问题 这里使用了堆指针。
cout << user_from_method.unique() << endl;
//获得该内存位置是否只有一个指针引用,这里返回true
user_from_method.swap(user_p);
// 交换两个指针的内存指向。
}
shared_ptr<User> getUserInstance(string username,string password){
shared_ptr<User> userp = make_shared<User>(username,password);
//创建局部指针userp
return userp;
//指针返回后,由于userp是局部变量,则函数执行完毕就销毁,计数器-1
}
直接管理内存
//创建了一个int类型的内存空间,但是这个内存空间没有值
int *number_p = new int;
//创建了一个string类型的内存空间,并初始化为控制符。
string *text_p = new string;
//创建一个string类型的内存空间,并初始化为aaaaa;
string *number_string = new string(5,'a');
//通过正常的解引用使用动态内存空间
*number_p = 10;
//内置类型在开辟空间的时候是不会初始化的,所以需要手动初始化。
int *number_p2 = new int(50);
//通过内置指针创建一个底层const指针
const int *cnumber_p = new const int(10);
//通过内置指针创建User
User *user_a = new User("TONY","PWD");
//如果内存不够,会返回空指针,不会报错
User *user_b = new (nothrow) User("YAN","PWD");
//使用完之后需要手动释放相关内存,不然会出现内存泄露
delete number_p;
delete text_p;
delete number_string,cnumber_p;
delete user_a,user_b;
//释放后的内存无论是再使用或者是再删除,操作都是未定义的。会出错
delete number_p;
//如果想再使用释放了的变量,释放后手动赋值nullptr,标明不指向任何位置
number_p = nullptr;
关于shared_ptr 和内置指针的结合
//通过内置指针创建一个智能指针
shared_ptr<int> number_p(new int(10));
string *text_p = new string("TONY");
//智能指针会完全托管text_p的生命周期,也就是说,
// 如果这个智能指针释放或者销毁,text_p指向的就会无效。
// 因为内置指针是没有加入引用计数器当中的
shared_ptr<string> stext_p(text_p);
//智能指针重置为null_ptr,
// 原指向释放,text_p将无效
stext_p.reset();
关于shared_ptr 的get函数。
get函数返回的是一个内置指针,这里有个问题,就是get返回的内置指针没有引用计数器,如果这个内置指针让另外一个shared_ptr指向的话,他们是因为大家的引用计数器都唯一:
shared_ptr<string> str_pA = make_shared<string>("TONY");
//这里返回一个内置指针,这个内存空间仍然是由str_pA进行生命周期管理
string *nstr_p = str_pA.get();
//使用内置指针创建了一个新的智能指针
shared_ptr<string> str_pB(nstr_p);
cout << str_pA.use_count() << endl; //这里都返回1
cout << str_pB.use_count() << endl; //这里都返回1
由于两个智能指针都认为自己是该内存空间的唯一指向,如果其中一个智能指针释放该内存空间的话,则另外一个会出现异常情况。
危险情况:
shared_ptr<string> str_pA = make_shared<string>("TONY");
//这里返回一个内置指针,这个内存空间仍然是由str_pA进行生命周期管理
string *nstr_p = str_pA.get();
//使用内置指针创建了一个新的智能指针
shared_ptr<string> str_pB(nstr_p);
//str_pA 使用reset指向了一个新的内存空间。
// 释放原来指向,导致str_pB无效,而且nstr_p一样无效
str_pA.reset(new string(“YAN”)); // reset 参数是内置指针。
更加危险的是:
str_pA = str_pB; //导致两个都无效,指向前释放原内存,导致str_pB无效,然而str_pA在被str_pB赋值,导致两个智能指针都是无效的。
关于智能指针的第二种用法,定义释放的函数:
如果程序在执行函数的时候出错的时候,栈内存会自动释放,释放了shared_ptr对象的时候会释放指向的对象,如果修改释放的方式,我们可以利用这一特性去安全关闭资源,不需要担心程序异常所导致的内存泄露
int main() {
string conn = connection();
shared_ptr<string> conn_ptr(&conn,disconnect);
cout << "A->" << *conn_ptr << endl; //输出:A->open connection
//释放的时候调用 disconnect函数,不是直接调用delete
// 由于这里是栈内存,所以在删除栈内存的时候会报错
conn_ptr.reset();
cout << "B->" << conn << endl; //输出:B->disconnected
}
string connection(){
return "open connection";
}
void disconnect(string *target){
*target = "disconnected";
}
关于unique_ptr的使用
unique指针支持使用内置指针的方式初始化
unique_ptr<string> u_str_pA(new string(“TONY"));
使用release函数可以获得一个内置指针,但是同时unique本身就失去该空间的指向,release返回的内置指针没有删除 或者没有用内置指针接收就会出现内存泄露
unique_ptr<string> u_str_pA(new string("TONY"));
string *sp = u_str_pA.release(); //u_str_pA 目前没有指向。
注意直接调用:u_str_pA.release() 会直接失去指向,而且出现内存泄露。
同样和shared_ptr 一样都可以使用reset来重新接受一个内存指向和释放原指向内存。
unique_ptr<string> u_str_pA(new string("TONY"));
unique_ptr<string> u_str_pB(new string("Yan"));
u_str_pA.reset(u_str_pB.release());
由于unique共享指向相同内存位置的,所以如果要赋值到其他unique或者是shared必须使用release函数放弃自身引用。
但是有一种例外情况,如果例如unique_ptr 作为返回值是可以接收赋值的:
int main() {
unique_ptr<string> sptr = getStringP();
cout << *sptr << endl;
}
unique_ptr<string> getStringP(){
unique_ptr<string> strp(new string("TONY"));
return strp;
}
关于自定义释放内存的方法与shared_ptr指针有所区别,需要特别指定释放内存的函数类型:
int main() {
string conn = connection();
unique_ptr<string,void (*)(string*)> sptr(&conn,disconnect);
sptr = nullptr; //指向并空指针,并释放原内存空间,调用自定义释放函数
cout << conn << endl;
}
string connection(){
return string("connected!");
}
void disconnect(string *target){
*target = "disconnected!";
}
最后一个weak_ptr 弱引用,弱引用注意是不能直接引用使用的。
shared_ptr<string> str_p = make_shared<string>("TONY");
weak_ptr<string> w_str_pA = str_p; //此时计数器不会加1
// weak_ptr<string> w_str_pA(str_p); 另外一种写法
cout << w_str_pA.use_count() << endl; //返回的是shared_ptr的引用数
if(shared_ptr<string> temp_p = w_str_pA.lock()){
//weak_ptr只能通过lock返回一个shared_ptr进行使用
//如果返回的是一个空指针则是失效的引用。也无法进行if分支
cout << temp_p.use_count() << endl; //此时计数器是2
}
str_p = nullptr; //shared_ptr 指向空,并释放原引用
if(!w_str_pA.expired()){ //判断是否失效,此时属于失效状态。
w_str_pA.reset(); //重置指针。
}
动态数组
动态数组的创建方式如下:
//这里开辟了10个int的内存空间,且未初始化
int *array_pA = new int[10];
//和内置指针一样,内置类型加括号会自动初始化
int *array_pB = new int[10]();
//开辟10个int类型的内存空间,下标为7之后的元素初始化为0
int *array_pC = new int[10]{0,1,2,3,4,5,6};
由于动态数组返回的是一个内存空间起始元素的指针,所以无法使用begin和end函数获得迭代器。所以非常不幸的是你只能够记住自己申请了多少个空间进行迭代了。
int *array_pC = new int[10]{0, 1, 2, 3, 4, 5, 6};
//用一个新变量接array_pC不然搞乱就妈蛋了,或者可以使用下标运算符
for (int *p = array_pC; p != array_pC + 10; p++) {
cout << *p << " " << flush;
}
释放动态数组,和内置指针基本一致不过需要使用[]
delete []array_pC;
动态数据结合unique_ptr指针使用。
unique_ptr<int[]> dynamicArray(new int[10]);
for (int i = 0; i < 10; i++) {
dynamicArray[i] = i; //动态数组是可以使用下标运算符的
}
dynamicArray = nullptr; //unique_ptr 释放内存会自动使用delete [] 进行释放。
动态数组结合shared_ptr 就没有这么简单了,因为shared_ptr需要自定义释放内存的函数,<>当中也只能定义int的指针,能定义成[]数组形式:
shared_ptr<int> sp(new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
[](int *p) {
cout << "executed delete []" << endl;
delete[] p; //自定义释放内存方式
}); //没错我偷鸡使用了lambda表达式
for(int i=0;i<10;i++){
cout << *(sp.get() + i) << endl; //都可以使用下标方式sp.get()[i]
}
sp.reset();
allocator 类是一个可以建立一篇连续存储空间的工具。创建的内存空间是未被初始化的所以使用之前需要先初始化你想使用的元素。
allocator<string> string_allocator; //先获得一个allocator对象
string *start_p = string_allocator.allocate(10); //申请了10个string的内存空间
auto target = start_p; //获得起始的位置指针
for (int i = 0; i < 5; i++) { //需要通过construct函数进行构造对象,否则无法使用
string_allocator.construct(target++, string("TONY") + to_string(i));
} //只构造了5个元素,目前start_p这片内存已经被使用了一半。
如果这边内存某些元素希望销毁,供有需求的时候重新使用,我们可以使用destroy函数
string_allocator.destroy(—target); //结合上面代码,这里销毁了这片内存的第五个string
这个位置又可以供其他有需要的地方构造使用:
string_allocator.construct(target,string(“YAN")); //重新构造使用;
我们可以进行遍历获得这片内存的使用情况:
for (int i = 0; i <5; i++) {
cout << *(start_p + i) << " " << flush;
}
当然如果这批内存空间不想再次使用了,可以使用deallocate函数将这片内存释放:
string_allocator.deallocate(start_p, 10);
关于allocator的算法函数:
vector<int> v1 = {0,1,2,3,4,5};
allocator<int> number_allocator;
int *start = number_allocator.allocate(10);
int *target = uninitialized_copy(v1.begin(),v1.end(),start); //从v1的迭代器返回复制到从start指针开始迭代,返回一个末尾迭代器。
for (int i = 1; i <= v1.size(); i++) {
number_allocator.destroy(target - i); //销毁之前构造的元素
}
//从v1.begin的开始复制3个元素,到start为起始的位置返回末尾迭代器
target = uninitialized_copy_n(v1.begin(),3,start);
//从原始内存位置开始到start + 5的元素,初始化为10
uninitialized_fill(start, start + 5, 10);
////从原始内存位置开始到第五位的元素,初始化为10
uninitialized_fill_n(start,5,10);
- JAVA 转 C++ 必记 PartB
- 成为c#、java调试高手必记的重要快捷键
- JAVA 转 C++ 必记 PartA
- JAVA 转 C++ 必记 PartC
- JAVA 转 C++ 必记 PartD
- java必记的知识点
- UDP课本例题partB
- 3.8-7.25 partB
- MIT6.828 LAB4 PartB
- c/c++必学
- C语言必看!
- c编程必知
- java初学者必看
- Java入门必学
- 学习Java必看
- 学习java必看
- 学习java必看
- 初学JAVA必看
- LeetCode 338. Counting Bits
- linux下mysql开启远程访问权限及防火墙开放3306端口
- Vue从入门到精通(1)--前言、基础知识
- 常见的设计模式
- 字串的连接最长路径查找(使用TreeSet集合的比较器new Comparator(){ }:匿名内部类方法)
- JAVA 转 C++ 必记 PartB
- POJ 1458 Common Subsequence(LCS)
- 文件传输命令scp使用——linux主机传文件到windows主机
- 类似智能购票的demo--进入页面后默认焦点在第一个输入框,输入内容、回车、right时焦点自动跳到下一个,当跳到select时,下拉选项自动弹出,并且可以按上下键选择,选择完成后再跳到下一个。
- Javascript函数式编程要掌握的知识点
- 全面的android webview开发使用详解
- 关于语义化的js的总结,DOM、element、text等节点的操作
- 我的第一个Python小程序爬出网站图片
- java8 Stream类