string 类
构造字符串
#include <iostream>
#include<string>
using namespace std;
int main()
{
string str("hello"); //1
cout << str << endl;
string s2(10, 'w'); //2
cout << s2 << endl;
string s3(s2); //3
cout << s3 << endl;
s3 += "add";
cout << s3 << endl;
s3[0] = 'p';
cout << s3 << endl;
string s4; //4
s4 = s2 + s3;
cout << s4 << endl;
char cs[] = "this is a test";
string s6(cs,18); //5
cout << s6 << endl;
string s7(cs + 4, cs + 8); //6
cout << s7 << endl;
string s8(&s6[4], &s6[8]); //6
cout << s8 << endl;
string s9(str, 1, 3); //7
cout << s9 << endl;
return 0;
}
输出:
hello
wwwwwwwwww
wwwwwwwwww
wwwwwwwwwwadd
pwwwwwwwwwadd
wwwwwwwwwwpwwwwwwwwwadd
this is a test
is
is
ell
- C++11新增
string str = {‘1’,‘2’};
string类输入
1.c风格的字符串,有三种方式输入
char info[100];
cin>>info;
cin.getline(info,100);
cin.get(info,100);
2.string对象有两种
string str;
cin>>str;
getline(cin,str);
两个版本的getline()都有一个可选参数,用于指定使用哪个字符来确定输入边界
cin.getline(info,100,’:’);读到:并丢弃 cin为调用对象
getline(str,’:’); 函数,cin为参数 自动调整string的大小
限制:
1.string对象的最大允许长度,由常量 string::npos指定。通常是最大的unsigned int 值。(试图将整个文件读入string中,可能有问题)
2.程序可以使用的内存量
string 的getline()从输入中读取字符,并存储在string中,直到发生以下情况。
1.到达文件尾,此时输入流的eofbit将被设置,意味着方法fail()和eof()都返回true;
2.遇到分界字符,默认(\n),这种情况,将把分界字符从输入流中删除,但不存储它。
3.读取的字符达到最大允许值(string::npos和可供分配的内存字节数中较小的一个),这种情况下,将设置输入流的failbit意味着方法fail()将返回true。
string的operator>>()函数的行为与此类似,只是它不断读取,直到遇到空白字符,并将其留在输入队列中,而不是不断读取,直到遇到分解字符并将其丢弃。空白字符指空格,换行和制表符,更简单的说是调用isspace()是,返回true的字符。
#include <iostream>
#include<string>
#include<fstream>
#include<cstdlib>
using namespace std;
int main()
{
ifstream fin;
fin.open("123.txt");
if (fin.is_open() == false)
{
cout << "open error";
exit(EXIT_FAILURE);
}
string str;
int count = 0;
getline(fin, str,':');
while (fin)
{
++count;
cout <<count<<" : "<< str << endl;
getline(fin, str, ':');
}
fin.close();
return 0;
}
默认\n为分界符,指定:为分界符后,\n将被视为常规字符,因此5输出空 和safa。
使用字符串
string比较,如果在机器排列序列中,一个对象位于另一个对象前面,则前者被视为小于后者。如果机器排列为ASCII码,则数字将小于大写字符,大写字符小于 小写字符。可以使用如下的比较
string s1(“aaa”);
string s2(“bbb”);
char a[30] = “sssss”;
s1<s2;s2<a;s2 != a;
rfind(),find_first_of(),find_last_of(),find_last_not_of(),find_first_not_of(),它们重载的特征标与find方法相同。rfind()查找子字符串或字符最后一次出现的位置。
find_first_of()在字符串中查找参数中任何一个字符首次出现的位置。s1= cobra; pos =find_first_of(“hark”) = 3;因为hark中各个字符在 cobra中首次出现的位置3.
find_first_of()在字符串中查找参数中任何一个字符最后出现的位置 pos =find_last_of(“hark”) = 4,a最后一次
find_first_not_of()在字符串中查找第一个不包含在参数中的字符。pos =find_first_not_of(“hark”) = 0 , 即c的位置。
int main()
{
string tmp = "1234567890";
int index = 0;
string str = "345"; //从0开始
index = tmp.find(str); //2
index = tmp.find(str,1); //2
index = tmp.find("56"); //4
index = tmp.find("5678",1,2); //4
index = tmp.find("0"); //9
}
capacity()返回当前分配给字符串的内存块的大小。
reverse()设置能够请求内存块的最小长度。
string类实行动态内存分配,若不够,则在原来的基础上翻倍,并重新开辟内存。
字符串种类
智能指针
使用智能指针
auto_ptr,unique_ptr,shared_ptr都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当智能指针对象过期时,其析构函数将使用delete 来释放内存。
创建使用智能指针,包含头文件
throw()意味着构造函数不会引发异常,与auto_ptr一样,throw也被抛弃。
auto_ptr pd (new double);
#include <iostream>
#include<string>
#include<memory>
using namespace std;
class Report {
private:
string str;
public:
Report(const string s) :str(s) { cout << str << " created \n"; }
~Report()
{
cout << str << " delete \n";
}
void comment() const { cout << str << endl; }
};
int main()
{
std::cout << "Hello World!\n";
{
auto_ptr<Report> ps(new Report("use auto"));
ps->comment();
}
{
shared_ptr<Report> ps(new Report("use share"));
ps->comment();
}
{
unique_ptr<Report> ps(new Report("use unique"));
ps->comment();
}
}
- 所有智能指针类都有一个explicit构造函数,该构造函数将指针作为参数。因此不需要自动将指针转为智能指针对象。
shared_ptr pd;
double* p_reg = new double;
pd = p_reg; // not allowed
pd = shared_ptr(p_reg); //ok
shared_ptr ps = p_reg; // not allowed
shared_ptr ps (p_reg); //ok - 智能指针与常规指针类似,可以 ps-> 或者*ps
- 智能指针应该避免
string s(“safsgag”);
shared_ptr ps(&s);
ps过期时,程序将把delete运算符用于非堆的内存
智能指针注意事项
实际上有四种,还有weak_ptr,为何摒弃auto_prt;
auto_ptr ps(new string(“sssss”));
auto_ptr pd ;
ps = pd;
如果ps和pd是常规指针,则两个指针将指向同一个string对象。这样程序将试图删除同一个对象两次,一次是ps过期,一次是pd过期。避免这种问题方法有很多。
1.定义赋值运算,使之执行深度赋值。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本
2.建立所有权概念,对于特定的对象,只有一个智能指针能拥有它。这样只有拥有对象的智能指针的析构函数会删除该对象。然后让赋值操作转让所有权,这就是用于auto和unique的策略,但后者更严格。
3.创建更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。赋值时,计数+1,对象过期时-1。仅当最后一个对象过期时,才调用delete。(shared_ptr)
4. 不适用auto_ptr的例子
int main()
{
auto_ptr<string> a[4] =
{
auto_ptr<string>(new string("088")),
auto_ptr<string>(new string("134")),
auto_ptr<string>(new string("5667")),
auto_ptr<string>(new string("145125")),
};
auto_ptr<string> pa;
pa = a[2]; //若使用unique_ptr 编译不通过,此处报错
//此处将所有权从a【2】给pa,a【2】不再引用该字符串。在 auto_ptr放弃对象所有权后,便可能使用它来访问该对象,此时*a【2】为空指针
cout << "________________________\n";
for (auto p : a)
cout << *p << endl; //打印5667异常退出 ,若改用shared_ptr则不会;
cout << "the is " << *pa << endl;
}
unique_ptr优于auto_ptr
auto_ptr<string>p1(new string("088"));
auto_ptr<string>p2;
p2 = p1; //p2接管string对象所有权后,p1的所有权将被剥夺。可以防止析构同一个对象两次,但是此时p1指向无效的数据,使用p1会出现错误
unique_ptr<string>p1(new string("088"));
unique_ptr<string>p2;
p2 = p1; //此时编译器将报错
- 函数中可以
unique_ptr<string> demo(const char* s)
{
unique_ptr<string> tmp(new string(s));
return tmp;
}
int main()
{
unique_ptr<string> ps;
ps = demo("okl");
//允许demo返回临时的,然后ps接管了,ps拥有对象的所有权,临时的将很快销毁,没有机会使用无效的数据
}
总之,程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是一个临时右值,编译器允许这样;但源unique_ptr存在很长一段时间,则编译器禁止。
unique_ptr p1 (new string(“hello”));
unique_ptr ps;
ps = p1; // no 此时p1悬挂,危险。非要这样 ,不要使用智能指针
unique_ptr p2;
p2 = unique_ptr (new string(“hello”)); //ok 调用构造,临时变量很快被销毁
2. c++有一个标准库函数std::move(),能够将一个unique_ptr赋给另一个。
unique_ptr<string> demo(const char* s)
{
unique_ptr<string> tmp(new string(s));
return tmp;
}
int main()
{
unique_ptr<string> ps1,ps2;
ps1 = demo("wttkk");
ps2 = move(ps1);
ps1 = demo("wt");
cout << *ps1 << endl;
cout << *ps2 << endl;
}
- unique_ptr还有另一个优点,它有一个可用于数组的变体。
auto_ptr只能和new一起使用
unique_ptr<double []>pad(new double(5));
注意:智能指针只适用于用new或new【】分配的内存。
选择智能指针
1.如果程序使用多个指向同一对象的指针,应选择shared_ptr.这样的情况包括:有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大和最小;两个对象包含都指向第三个元素的指针;STL容器包含指针。若编译器没有shared_ptr.使用Boost库提供的shared_ptr.
2.如果程序不需要多个指向同一个对象的指针,则可以使用 unique_ptr。如果函数使用new分配内存,并返回指向该内存的地址,将其声明为unique_ptr是不错的选择。这样所有权将转让给接受返回值的 unique_ptr,而该智能指针将负责调用delete。可将 unique_ptr存到容器中,只要不调用将一个 unique_ptr复制或赋给另一个的方法或算法(如sort())。
标准模板库
STL提供了一组表示容器,迭代器,函数对象和算法的模板。容器是一个与数组类似的单元,可以存储若干值。STL容器是同质的,即存储的值的类型相同。算法是完成特定内容的处方(排序)。
迭代器能够用来遍历容器的对象,与能够遍历数组的指针类似,是广义指针
函数对象是类似于函数的对象,可以是类对象或函数指针(包括函数名,因为函数名被用作指针)。STL使得能够构造各种容器(包括数组,链表,队列)和执行各种操作(搜索,排序)。
STL不是面向对象的编程,而是一种不同的编程模式——泛型编程
模板类Vector
提供【】,将对象赋给另一个对象
对矢量可执行的操作
1.所有STL都提供了一些基本的方法,size(),swap(),begin() ,end()返回一个表示超过容器尾的迭代器
2.迭代器是一个广义指针,让STL能够为各种不同的容器类提供统一的接口。每个容器类都定义了一个合适的迭代器,该迭代器的类型是一个名为iterator的typedef,其作用域为整个类。
3.超过结尾:是一种迭代器,指向容器最后一个元素的后面那个元素。与C风格字符串最后一个空字符类似,空字符是一个值,而它是一个指向元素的指针。超尾元素使得插入到最后一个元素前面非常方便。
4. erase()删除矢量中给定区间的元素。接受两个迭代器参数,参数定义了要删除的区间【);
5. insert()插入,第一个参数指定了新元素的插入位置,第二三个定义了插入的区间。
#include <iostream>
#include<string>
#include<vector>
using namespace std;
struct Review
{
string title;
int rating;
};
bool FillReview(Review& rr)
{
cout << "enter book title (q to quit): ";
getline(cin, rr.title);
if (rr.title == "quit")
{
return false;
}
cout << "enter book rate : ";
cin >> rr.rating;
if (!cin)
return false;
while (cin.get()!= '\n')
{
continue;
}
return true;
}
void ShowReview(const Review& rr)
{
cout << rr.rating << "\t" << rr.title << endl;
}
int main()
{
vector<Review> books;
Review temp;
while (FillReview(temp))
{
books.push_back(temp);
}
int num = books.size();
if (num > 0)
{
cout << "your enter the following:\n " << "rating\tBook\n";
for (auto x : books)
ShowReview(x);
cout << "iterator \n";
vector<Review>::iterator pr; //可以使用auto
for (pr = books.begin(); pr != books.end(); pr++)
ShowReview(*pr);
vector<Review> oldist(books); //复制构造函数
if (num > 3)
{
//移除两个
books.erase(books.begin() + 1, books.begin() + 3);
cout << "afer erase \n";
for (pr = books.begin(); pr != books.end(); pr++)
ShowReview(*pr);
//插入一个
books.insert(books.begin(), oldist.begin() + 1, oldist.begin() + 2);
cout << "after insert \n";
for (pr = books.begin(); pr != books.end(); pr++)
ShowReview(*pr);
}
}
}
对矢量可执行的其它操作
程序员通常要对数组执行很多操作,如搜索,排序,随机排序,矢量模板类并没有执行这些的方法,STL从更广泛的角度定义可非成员函数来执行这些操作。即不是为每个容器类定义find()函数而是定义一个适用于所有容器类的非成员函数find()。
for_each(),random_shuffle()和sort()都接受三个参数,前两个是定义容器中区间的迭代器,最后一个是指向函数的指针。(#include)
1.for_each() 可用于任何容器类
for_each(books.begin(), books.end(), ShowReview);
2.random_shuffle() 随机排列区间中的元素 ,需要容器支持随机访问,sort()也是
random_shuffle(books.begin(), books.end());
3. sort版本 两种,一种
sort(books.begin(), books.end()),按升序排列,使用内置的<运算符。如果用户自己定义的类型,需要提供operator<函数
另一种,接受三个参数
sort(books.begin(), books.end(),f);
#include <iostream>
#include<string>
#include<vector>
#include<algorithm>
using namespace std;
struct Review
{
string title;
int rating;
};
bool FillReview(Review& rr)
{
cout << "enter book title (q to quit): ";
getline(cin, rr.title);
if (rr.title == "quit")
{
return false;
}
cout << "enter book rate : ";
cin >> rr.rating;
if (!cin)
return false;
while (cin.get()!= '\n')
{
continue;
}
return true;
}
void ShowReview(const Review& rr)
{
cout << rr.rating << "\t" << rr.title << endl;
}
bool operator<(const Review& r1, const Review& r2)
{
if (r1.title < r2.title)
return true;
else if (r1.title == r2.title && r1.rating < r2.rating)
return true;
else
return false;
}
bool worseThan(const Review& r1, const Review& r2)
{
if (r1.rating < r2.rating)
return true;
else
return false;
}
int main()
{
vector<Review> books;
Review temp;
while (FillReview(temp))
{
books.push_back(temp);
}
int num = books.size();
if (num > 0)
{
cout << "your enter the following:\n " << "rating\tBook\n";
for_each(books.begin(), books.end(), ShowReview);
sort(books.begin(), books.end());
cout << "after sort by title \n" << "rating\tBook\n";
for_each(books.begin(), books.end(), ShowReview);
sort(books.begin(), books.end(),worseThan);
cout << "after sort by rating \n" << "rating\tBook\n";
for_each(books.begin(), books.end(), ShowReview);
random_shuffle(books.begin(), books.end());
cout << "after random \n" << "rating\tBook\n";
for_each(books.begin(), books.end(), ShowReview);
}
}
基于范围的for循环(C++11)
int a[5] = { 1,2,3,4,5 };
for (int t : a)
cout << t << endl;
// for_each(books.begin(), books.end(), ShowReview);
for (auto book : books)
ShowReview(book);
//若要修改内容
for (auto & book : books)
ShowReview(book);
泛型编程
STL是一种泛型编程。面向对象编程关注的是编程的数据方面,而泛型编程关注的是算法。它们之间的共同点是抽象和创建可重用的代码。
泛型编程旨在编写独立于数据类型的代码,C++完成通用程序的工具是模板。模板使得能够按泛型定义函数或类,而STL通过通用算法更进一步。模板让这一切成为可能,但必须对元素进行详细设计。
为何使用迭代器
理解迭代器是理解STL关键所在,模板使得算法独立于存储的数据类型,而迭代器使算法独立于使用的容器类型。
//1. 在double数组中搜索特定的数值函数
//可以用模板将这种算法推广到包含==运算符的,任意类型的数组,尽管如此仍与特定的数据结构(此处数组)关联在一起
double* find_ar(double* ar, double n, const double& val)
{
for (int i = 0; i < n; i++)
{
if (ar[i] == val)
return &ar[i];
}
return nullptr;
}
struct Node
{
double item;
Node* p_next;
};
//2. 链表 可以用模板将这种算法推广到包含==运算符的,任意类型的链表,尽管如此仍与特定的数据结构(此处链表)关联在一起
Node* find_ll(Node* head, const double& val)
{
Node* start;
for (start = head; start != 0; start = start->p_next)
if (start->item == val)
return start;
return nullptr;
}
从实现细节看这两种算法不同,但广义上看,相同的:将值与容器中的每个值进行比较,直到找到匹配的值为止。
泛型编程旨在用同一个find函数处理数组,链表或者其它容器类型。即函数不仅独立于容器中的数据类型,而且独立于容器本身的数据结构。模板提供了存储在容器中的数据类型的通用表示,因此还需要遍历容器中的值的通用表示,迭代器正是这种通用的表示。
要实现find函数,迭代器应该具备下面的特征
1.应能够对迭代器解除引用操作,以便访问它的引用的值。若p是一个迭代器,应对*p进行定义
2.能够将一个迭代器赋给另一个迭代器,对表达式p=q定义。
3.将一个迭代器与另一个进行比较,看他们是否相等,对!= 和== 进行定义。
4.使用迭代器遍历数组中的所有元素,定义++p 和p++。
//重写后的find_ar函数
typedef double* iterator;
iterator find_ar(iterator begin, iterator end, const double& val)
{
iterator ar;
for (ar = begin; ar!=end; ar++)
{
if (*ar == val)
return ar;
}
return end;
}
struct Node
{
double item;
Node* p_next;
};
//重写个类
class iterator {
Node* pt;
public:
iterator() :pt(0) {}
iterator(Node* pn) :pt(pn) {}
double operator*() { return pt->item; }
iterator& operator++() //++int 前增
{
pt = pt->p_next;
return *this;
}
iterator& operator++(int ) //int++ 后增
{
iterator tmp = *this;
pt = pt->p_next;
return tmp;
}
// operator == operator !=
};
//重写后的函数
iterator find_ll(iterator head, const double& val)
{
iterator start;
for (start = head; start != 0; ++start)
if (*start == val)
return start;
return nullptr;
}
重写后,两个函数基本相同。差别在于如何确定已经达到最后一个值,find_ar使用超尾迭代器,find_ll使用存储在最后一个节点中的空值。
可以要求链表的最后一个元素后面还有一个额外的元素,即让数组和链表都有超尾元素。(增加超尾元素后,对迭代器类的要求变成了对容器类的要求)
STL遵循上面的方法,每个容器类(vector,list,deque等)定义了相应的迭代器类型。对于其中的某个类,迭代器可能是指针,也可能是对象。但不管实现方式如何,都提供了 ++ * 等操作。其次每个迭代器都有一个超尾标记,当迭代器递增到超越容器的最后一个元素后,这个值将被赋给迭代器。每个容器类都有start和end方法,指向第一个元素和超尾位置的迭代器。
总结:首先处理容器类的算法,应尽可能用通用的术语来表示算法,使之独立于数据类型和容器类型。为使通用算法能够 适用于具体的情况,应定义能够满足算法需求的迭代器,并把要求加到容器设计上。即基于算法的要求,设计基本迭代器的特征和容器特征。
迭代器类型
不同算法对迭代器的要求也不同。查找算法需要定义++,要求读取数据。排序算法要求能随机访问,读写数据。
STL定义了五种迭代器类型。
迭代器层次结构
正向迭代器具有输入和输出迭代器的全部功能,同时还有自己的功能;
双向迭代器具有正向迭代器的全部功能,同时还有自己的功能;
随机访问迭代器具有双向迭代器的全部功能,同时还有自己的功能;
概念 改进和模型
- 将指针用作迭代器
迭代器是广义指针,而指针满足所有迭代器要求,迭代器是STL算法的接口,而指针是迭代器,因此STL算法可以使用指针来对基于指针的非STL容器进行操作。STL算法用于数组。
(1)sort
double a[5] = { 4,6,12,2,8};
sort(a, a + 5); //也可以 sort(&a[0], &a[5]);
vector va(5); // 此处最好加上大小,否则会崩溃
copy(a, a + 5, va.begin());
也可以将STL用于自己设计的数组形式,只要提供适当的迭代器(可以是指针,也可以是对象)和超尾指示器即可。
(2)copy()前两个迭代器参数表示要复制的范围,最后一个迭代器参数表示要将第一个元素复制到什么位置。
前两个参数必须是(最好是)输入迭代器,后一个最好是,必须是输出迭代器。Copy将覆盖目标容器中已有的数据,同时目标容器必须足够大,以便能够容纳被复制的元素。
STL提供了输出流迭代器:ostream_iterator模板,头文件iterator
double a[5] = { 4,6,12,2,8};
sort(a, a + 5); //也可以 sort(&a[0], &a[5]);
vector<double> va(5);
copy(a, a + 5, va.begin());
ostream_iterator<double, char> out_iter(cout, ": ");
//第一个参数double指出了被发送给输出流的数据类型,
//第二个参数char指出了输出流使用的字符类型(也可能是wchar_t)
//构造函数,第一个参数cout指出要使用的输出流,第二个字符串是在发送给输出流的每个数据项后显示的分隔符
// *out_iter++ = 15; // ==>cout<<15<<""
//将copy用于迭代器
copy(va.begin(), va.end(), out_iter);
cout << "+++++++++++++++++\n";
copy(va.begin(), va.end(), ostream_iterator<double, char>(cout, "|"));
头文件iterator还定义了一个istream_iterator模板,可以使用两个istream_iterator对象来定义copy的输入范围
copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(), va.begin());
//第一个参数指出了要读取的数据类型,
//第二个参数char指出了输入流使用的字符类型(也可能是wchar_t)
//构造函数,cin意味着使用由cin管理的输入流,省略构造函数参数,表示输入失败。
//因此上述代码从输入流中读取,直到文件尾,类型不匹配或出现其他输入故障为止
- 其它有用途的迭代器
#include <iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<iterator>
using namespace std;
int main()
{
double a[5] = { 4,6,12,2,8};
vector<double> va(5);
copy(a, a + 5, va.begin());
ostream_iterator<double, char> out_iter(cout, ": ");
copy(va.begin(), va.end(), out_iter);
cout << "\n+++++++++++++++++\n";
copy(va.rbegin(), va.rend(), out_iter); //反向输出
cout << "\n+++++++++++++++++\n";
vector<double>::reverse_iterator ri; //反向输出
for (ri = va.rbegin(); ri != va.crend(); ++ri)
cout << *ri << endl;
}
#include <iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<iterator>
using namespace std;
void output(const std::string& s) { cout << s << " "; }
int main()
{
string s1[4] = { "012","321", "956", "753" };
string s2[2] = { "123","hello" };
string s3[2] = { "456","789"};
vector<string> words(4);
copy(s1, s1 + 4, words.begin());
for_each(words.begin(),words.end(), output);
cout << "\n+++++++++++++++++++++++++++\n";
copy(s2, s2 + 2, back_insert_iterator<vector<string> >(words));
for_each(words.begin(), words.end(), output);
cout << "\n+++++++++++++++++++++++++++\n";
copy(s3, s3 + 2, insert_iterator<vector<string> >(words,words.begin()));
for_each(words.begin(), words.end(), output);
}
输出:
012 321 956 753
+++++++++++++++++++++++++++
012 321 956 753 123 hello
+++++++++++++++++++++++++++
456 789 012 321 956 753 123 hello
容器种类
#include <iostream>
#include<string>
#include<list>
#include<algorithm>
using namespace std;
void outint(int n) { cout << n << " "; }
int main()
{
list<int> l1(5, 2); //5个2
int stuff[5] = { 1,2,3,4,5 };
list<int> l2;
l2.insert(l2.begin(), stuff, stuff + 5);
int more[6] = { 6,2,4,4,5,6 };
list<int> l3(l2);
l3.insert(l3.begin(), more, more + 6);
cout << " list 1\n";
for_each(l1.begin(), l1.end(), outint); //2 2 2 2 2
cout << "\n list 2\n";
for_each(l2.begin(), l2.end(), outint); // 1 2 3 4 5
cout << "\n list 3\n";
for_each(l3.begin(), l3.end(), outint); // 6,2,4,4,5,6 1 2 3 4 5
l3.remove(2); //移除所有2
cout << "\n list 3 remove\n";
for_each(l3.begin(), l3.end(), outint); //6 4 4 5 6 1 3 4 5
l3.splice(l3.begin(), l1);
cout << "\n list 3 splice\n";
for_each(l3.begin(), l3.end(), outint); //2 2 2 2 2 6 4 4 5 6 1 3 4 5
cout << "\n list 1\n";
for_each(l1.begin(), l1.end(), outint); //为空
//insert 和splice的区别,insert将原始区间的副本插入目标地址。
//splice将原始区间移到目标地址,之后原始区间为空
//splice方法执行后,迭代器仍然有效。也就是说,如果将迭代器设置为指向l1中的元素
//则在splice将它重新定位到元素l3后,该迭代器仍指向相同的元素
l3.unique(); //unique只能将相邻的相同值压缩为单个值。但在sort之后在使用unique便可以
cout << "\n list 3 unique\n";
for_each(l3.begin(), l3.end(), outint); //2 6 4 5 6 1 3 4 5
l3.sort();
l3.unique();
cout << "\n list 3 unique and sort\n";
for_each(l3.begin(), l3.end(), outint); //1 2 3 4 5 6
l2.sort(); //12345
l3.merge(l2);
cout << "\n sort two merage into three\n";
for_each(l3.begin(), l3.end(), outint); //1 1 2 2 3 3 4 4 5 5 6
}
关联容器
关联容器是对容器概念的另一个改进。关联容器将值与键关联在一起,并使用键来查找值。(采用树来实现)
优点:提供了对元素的快速访问(比链表还快)。允许插入新元素,但不允许指定位置插入。原因是关联容器通常有用于确定数据放置位置的算法,以便能够快速检索信息
STL提供了四种关联容器set multiset map multimap 头文件set和map
1.set 值类型与键相同,键是唯一。multiset类似set但可以有多个值的键相同
2.map值和键类型不同,键是唯一的,每个键只能对应一个值。multimap可以一键多值
- set 示例
// pr.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include<string>
#include<list>
#include<set>
#include<iterator>
#include<algorithm>
using namespace std;
int main()
{
const int N = 6;
string s1[N] = { "for","heelo","for","hello", "for","world" };
string s2[N] = { "12r","for","123","456","678","89" };
set<string> A(s1, s1 + N);
set<string> B(s2, s2 + N);
ostream_iterator<string, char>out(cout, " ");
cout << "Set A:";
copy(A.begin(), A.end(), out); //for heelo hello world
cout << endl;
cout << "Set B:";
copy(B.begin(), B.end(), out); //123 12r 456 678 89 for
cout << endl;
cout << "union of A and B \n"; //并集
set_union(A.begin(), A.end(), B.begin(), B.end(), out); //123 12r 456 678 89 for heelo hello world
cout << endl;
cout << "interface of A and B \n"; //交集
set_intersection(A.begin(), A.end(), B.begin(), B.end(), out); //for
cout << endl;
cout << "difference of A and B \n"; //集合的差
set_difference(A.begin(), A.end(), B.begin(), B.end(), out); //heelo hello world
cout << endl;
set<string> C;
cout << "Set C: \n";
set_union(A.begin(), A.end(), B.begin(), B.end(), insert_iterator<set<string> >(C,C.begin()));
copy(C.begin(), C.end(), out); //123 12r 456 678 89 for heelo hello world
cout << endl;
string s3("grungy");
C.insert(s3);
cout << "Set C after insert: \n";
copy(C.begin(), C.end(), out); //123 12r 456 678 89 for grungy heelo hello world
cout << endl;
cout << "show a range:\n";
copy(C.lower_bound("ghost"), C.upper_bound("speak"), out);//grungy heelo hello
cout << endl;
}
- map示例
#include <iostream>
#include<string>
#include<list>
#include<map>
#include<iterator>
#include<algorithm>
using namespace std;
typedef int KeyType;
typedef std::pair<const KeyType, string> Pair;
typedef multimap<KeyType, string>MapCode;
int main()
{
MapCode codes;
codes.insert(Pair(415, "San"));
codes.insert(Pair(510, "Tan"));
codes.insert(Pair(718, "Aan"));
codes.insert(Pair(718, "gan"));
codes.insert(Pair(415, "tan"));
codes.insert(Pair(510, "ban"));
cout << " Number of citys with area code 415 :" << codes.count(415) << endl;
cout << " Number of citys with area code 510 :" << codes.count(510) << endl;
cout << " Number of citys with area code 718 :" << codes.count(718) << endl;
cout << "Area Code city\n";
MapCode::iterator it;
for (it = codes.begin(); it != codes.end(); ++it)
cout << " " << (*it).first << " " << (*it).second << endl;
cout << "==================================\n";
pair<MapCode::iterator, MapCode::iterator> range = codes.equal_range(718);
cout << "code is 718\n";
for (it = range.first; it != range.second; ++it)
cout << (*it).second << endl;
}
无序列关联容器C++11
函数对象
很多STL算法都使用函数对象,也叫函数符。函数符可以以函数方式与()结合使用的任何对象。这包括函数名,指向函数的指针和重载了()运算符的对象(即定义了函数operator()() )
class Liner {
private:
double slope;
double y0;
public:
Liner(double s = 1, double y = 0) :slope(s), y0(y) {}
double operator()(double x) { return y0 + slope * x; }
};
//重载的()运算符使得能够像函数那样使用Liner对象
Liner f1;
Liner f2(2.5, 10.0);
double y1 = f1(12.5); //0+1*12.5
double y2 = f2(0.4); //10+ 0.4 * 2.5
函数符概念
正如STL定义了容器和迭代器的概念一样,也定义了函数符的概念
1.生成器是不用参数就可以调用的函数符
2.一元函数是用一个参数可以调用的函数符
3.二元函数是用两个参数可以调用的函数符
```cpp
#include <iostream>
#include<list>
#include<iterator>
#include<algorithm>
using namespace std;
template<class T>
class TooBig
{
private:
T cutoff;
public:
TooBig(const T& t) :cutoff(t) {}
bool operator()(const T& v) { return v > cutoff; }
};
void outint(int n) { cout << n << " "; }
int main()
{
std::cout << "Hello World!\n";
TooBig<int> f100(100);
int vals[10] = { 50,100,90,180,60,210,415,88,188,201 };
list<int>yadayada(vals, vals + 10);
list<int>etcetera(vals, vals + 10);
//c++11 list<int> a{1,2,3,5};
cout << "orign list\n";
for_each(yadayada.begin(), yadayada.end(), outint);
cout << endl;
for_each(etcetera.begin(), etcetera.end(), outint);
cout << endl;
yadayada.remove_if(f100); // 返回true则删除 移除大于100的
etcetera.remove_if(TooBig<int>(200));//移除大于200的
cout << "after trimm\n";
for_each(yadayada.begin(), yadayada.end(), outint);
cout << endl;
for_each(etcetera.begin(), etcetera.end(), outint);
cout << endl;
}
预定义函数符
自适应函数符和函数适配器
#include <iostream>
#include<vector>
#include<iterator>
#include<algorithm>
#include<functional>
using namespace std;
const int LIM = 6;
void Show(double v)
{
cout.width(6);
cout << v << ' ';
}
int main()
{
double a1[LIM] = { 28,29,30,35,38,59 };
double a2[LIM] = { 63,39,36,38,37,69 };
vector<double> gr8(a1, a1 + LIM);
vector<double> m8(a1, a1 + LIM);
cout.setf(ios_base::fixed);
cout.precision(1);
cout << "g8 \t";
for_each(gr8.begin(), gr8.end(), Show);
cout << endl;
cout << "m8 \t";
for_each(m8.begin(), m8.end(), Show);
cout << endl;
vector<double> sum(LIM);
transform(gr8.begin(), gr8.end(), m8.begin(), sum.begin(), plus<double>());
cout << "sum \t";
for_each(sum.begin(), sum.end(), Show);
cout << endl;
vector<double> prod(LIM);
transform(gr8.begin(), gr8.end(), prod.begin(), bind1st(multiplies<double>(), 2.5));
cout << "Prd \t";
for_each(prod.begin(), prod.end(), Show);
cout << endl;
}
注意:C++11提供了函数指针和函数符的替代品----lambda表达式
算法
算法组
算法的通用特征
STL和string类
string类虽然不是STL的组成部分,但设计它时考虑到了STL(包含begin,end等方法)。next_permutation()算法将区间内容转换为下一种排序方式。对于字符串排列按照字母递增的顺序进行。如果成功返回true。如果区间已经处于最后的序列中,则返回false。(因此使用之前应先将串排序)
string str("asda");
sort(str.begin(), str.end());
cout << str << endl;
while (next_permutation(str.begin(), str.end()))
{
cout << str << endl;
}
函数和容器方法
有时可以选择使用容器方法或者STL函数,通常使用容器方法是最好的选择。首先它更适用于特定的容器;其次作为成员函数,它可以使用模板类的内存管理工具,在需要时调整容器的长度。
#include <iostream>
#include<list>
#include<iterator>
#include<algorithm>
using namespace std;
const int LIM = 10;
void Show(int v)
{
cout << v << ' ';
}
int main()
{
int ar[LIM] = { 28,29,30,35,38,59,28,30,28,40};
list<int>la(ar, ar + LIM);
list<int>lb(la);
cout << "original list \n";
for_each(la.begin(), la.end(), Show);
cout << endl;
la.remove(28);
cout << "la after remove 28 \n";
for_each(la.begin(), la.end(), Show); //包含7个元素
cout << endl;
list<int>::iterator last;
last = remove(lb.begin(), lb.end(), 28); //包含10个元素,前面7个非28
cout << "lb after remove 28 \n";
for_each(lb.begin(), lb.end(), Show);
cout << endl;
lb.erase(last, lb.end());
cout << "lb after erase \n";
for_each(lb.begin(), lb.end(), Show); //与调用容器类的remove后结果相同
cout << endl;
}
使用STL
使用STL时应尽可能减少编写的代码。STL通用,灵活的设计将节省大量工作。
#include <iostream>
#include<list>
#include<map>
#include<set>
#include<cctype>
#include<iterator>
#include<algorithm>
#include<vector>
using namespace std;
char toLower(char ch) { return tolower(ch); }
string& ToLower(string& st)
{
transform(st.begin(), st.end(), st.begin(), toLower);
return st;
}
void diplay(const string &s)
{
cout << s << " ";
}
int main()
{
vector<string> words;
cout << "enter words (enter quit to quit):\n";
string input;
while (cin>>input&&input!="quit")
{
words.push_back(input);
}
cout << "your enter is :\n";
for_each(words.begin(), words.end(), diplay);
cout << endl;
//转小写 使用sort-》unique但这么做会覆盖原有的数据
set<string>wordset;
transform(words.begin(), words.end(), insert_iterator<set<string> >(wordset, wordset.begin()), ToLower);
cout << "after low is :\n";
for_each(words.begin(), words.end(), diplay);
cout << endl;
map<string, int>wordmap;
set<string>::iterator si;
for (si = wordset.begin(); si != wordset.end(); si++)
wordmap[*si] = count(words.begin(), words.end(), *si);
cout << "Result\n ";
for (si = wordset.begin(); si != wordset.end(); si++)
cout << *si << ":" << wordmap[*si] << endl;
}
其它库
C++还提供了一些其它库。头文件complex为复数提供了类模板complex,包含用于float,long和long double 的具体化,这个类提供了标准的复数运算以及能够处理复数的标准函数
C++11新增的头文件random提供了更多的随机数功能。
vector valarray array
vector valarray array三个数组模板,由不同的小组开发的用于不同的目的
1.vector模板类是一个容器类和算法系统的一部分,它支持面向容器的操作。如排序,插入,重新排列,搜索,将数据转移到其它容器中等。
2.valarray类模板是面向数值计算的,不是STL的一部分,没有push_back()和insert()等方法,但为数学运算提供了一个简单,直观的接口。
3.array是为替代内置数组而设计的,它通过提供更好,更安全的接口,让数组更加紧凑,效率更高。Array表示长度固定的数组,因此不支持push_back()和insert(),但提供了多个STL方法,如begin(),end(),rbegin(),rend(),这使得很容易将STL算法用于array对象。
4.若要将两个数组中第一个元素的和赋给第三个数组中的第一个元素
#include <iostream>
#include<list>
#include<map>
#include<set>
#include<cctype>
#include<iterator>
#include<algorithm>
#include<vector>
#include<valarray>
#include<functional>
using namespace std;
void diplay(double s)
{
cout << s << " ";
}
int main()
{
vector<double> vd1{ 1,2,3 };
vector<double> vd2{ 3,4,5 };
vector<double> vd3(3);
//使用vector
transform(vd1.begin(), vd1.end(), vd2.begin(), vd3.begin(), plus<double>());
for_each(vd3.begin(), vd3.end(), diplay);
//将数组元素扩大2.5
transform(vd3.begin(), vd3.end(), vd3.begin(),bind1st(multiplies<double>(),2.5));
for_each(vd3.begin(), vd3.end(), diplay);
//计算自然数对数,存在另一个数组相应的元素中
//transform(vd1.begin(), vd1.end(), vd3.begin(), log);
//array同上
valarray<double> v1{ 1,2,3 };
valarray<double> v2{ 3,4,5 };
valarray<double> v3 = v1 + v2; //valarray类重载了 + 和*
valarray<double> v4 = v3*2.5;
v3 = log(v1);//valarray类重载 log函数
v3 = v1.apply(log); //也可以,不修改调用对象,而是返回一个包含结果的新对象
cout << "\n++++++++++++++++++++\n";
for (double x : v3)
cout << x << " ";
//可以
sort(&v1[0], &v1[3]); // 但危险,越界,valarray类没有超尾元素
//c++11提供接受valarray对象作为参数的模板函数,begin和end
sort(begin(v1), end(v1));
}
#include <iostream>
#include<list>
#include<map>
#include<set>
#include<cctype>
#include<iterator>
#include<algorithm>
#include<vector>
#include<valarray>
#include<functional>
using namespace std;
int main()
{
vector<double> data;
double temp;
cout << "Enter numbers (<=0 to quit)\n";
while (cin >> temp && temp > 0)
{
data.push_back(temp);
}
sort(data.begin(),data.end());
int size = data.size();
valarray<double>numbers(size);
int i;
for (i = 0; i < size; i++)
numbers[i] = data[i];
valarray<double>sq_rts(size);
sq_rts = sqrt(numbers);
valarray<double> results(size);
results = numbers + 2.0 * sq_rts;
cout.setf(ios_base::fixed);
cout.precision(4);
for (int i = 0; i < size; i++)
{
cout.width(8);
cout << numbers[i] << ":";
cout.width(8);
cout << results[i] << endl;
}
}
#include <iostream>
#include<cstdlib>
#include<valarray>
using namespace std;
const int SIZE = 12;
typedef valarray<int> vint;
void Show(const vint& v, int cols)
{
int lim = v.size();
for (int i = 0; i < lim; ++i)
{
cout.width(3);
cout << v[i];
if (i % cols == cols - 1)
cout << endl;
else
cout << " ";
}
if (lim % cols != 0)
cout << endl;
}
int main()
{
vint valint(SIZE); // 4*3
int i;
for (i = 0; i < SIZE; i++)
valint[i] = rand() % 10;
cout << "orign \n";
Show(valint, 3);
vint vcol(valint[slice(1, 4, 3)]);
cout << "seconde col\n";
Show(vcol, 1);
vint vRow(valint[slice(3, 3, 1)]);
cout << "seconde vRow\n";
Show(vRow, 3);
valint[slice(2, 4, 3)] = 10;
cout << "Set last col\n";
Show(valint, 3);
valint[slice(0, 4, 3)] = vint{ valint[slice(1, 4, 3)] } +
vint{ valint[slice(2, 4, 3)] };
cout << "addd \n";
Show(valint, 3);
}
模板initializer_list(C++11)
模板initializer_list C++11新增的,可使用初始化列表语法将STL容器初始化为一系列值。
使用initializer_list
要在代码中使用initializer_list,需要头文件initializer_list,这个模板类包含成员函数begin和end。可以使用这些函数来访问列表元素。它还包含成员函数size()。
#include <iostream>
#include<initializer_list>
using namespace std;
//可以按值传递,也可以按引用传递。这种对象本身很小,
//通常是两个指针,一个指向开头,一个指向结尾,传递方式不会影响
//函数参数可以是initializer_list 变量,也可以是initializer_list 字面量{2,3,4}
double sum(initializer_list<double> il)
{
double tot = 0;
for (auto p = il.begin(); p != il.end(); p++)
tot += *p;
return tot;
}
double average(const initializer_list<double>& ril)
{
double total = 0;
int n = ril.size();
double ave = 0;
if (n > 0)
{
for (auto p = ril.begin(); p != ril.end(); p++)
total += *p;
ave = total / n;
};
return ave;
}
int main()
{
cout << "list 1:sum = " << sum({ 2,3,4 }) << ",ave = " << average({ 2,3,4 }) << endl;
initializer_list<double> d1 = { 1.1,1.2,1.3,1.4,1.5 };
cout << "list 2:sum = " << sum(d1) << ",ave = " << average(d1) << endl;
}
提供initializer_list让能够将一系列值传递给构造函数或者其它函数