C++ 基础知识回顾(string基础、智能指针、迭代器、容器类)
[1] string基础
[1.1] string 的构造
#include <iostream>
#include <string> int main()
{
using namespace std; cout << "1 --- string(const char* s):将string对象初始化为s指向的C风格字符串" << endl;
string one("benxintuzi_1");
cout << "one = " << one << endl; // 重载了 << cout << "2 --- string(size_type n, char c):以字符c初始化包含n个字符的string对象" << endl;
string two(, '$');
cout << "two = " << two << endl; cout << "3 --- string(const string& str):复制构造函数初始化" << endl;
string three(one);
cout << "three = " << three << endl; cout << "4 --- string():默认构造函数" << endl;
string four;
four += one; // 重载了 +=
four = two + three; // 重载了 +
cout << "four = " << four << endl; cout << "5 --- string(const char* s, size_type n):用s指向的C风格字符串的前n个字符初始化string对象" << endl;
char s[] = "benxintuzi";
string five(s, );
cout << "five = " << five << endl; cout << "6 --- string(Iter begin, Iter end):用[begin, end)区间内的字符初始化string对象" << endl;
string six(s + , s + );
cout << "six = " << six << endl; cout << "7 --- string(const string& str, string size_type pos = 0, size_type n = npos):将string对象初始化为str中从pos开始的n个字符" << endl;
string seven(four, , );
cout << "seven = " << seven << endl; /*
cout << "8 --- string(string&& str) noexcept:将string对象初始化为str,并可能修改str,移动构造函数[C++11新特性]" << endl; cout << "9 --- string(initializer_list<char>il:将string对象初始化为初始化列表il中的字符[C++11新特性]" << endl;
string nine = {'t', 'u', 'z', 'i'}; // or string nine{'t', 'u', 'z', 'i'};
cout << "nine = " << nine << endl;
*/
}
[1.2] string 的输入
对于 C 风格字符串,有3种输入方式 |
char info[100]; cin >> info; // 从流中读一个单词存放到info中 cin.getline(info, 100); // 从流中读入一行存放到info,删除流中的\n cin.get(info, 100); // 从流中读入一行存放到info,保留流中的\n |
对于 string 对象,有2种输入方式 |
string stuff; cin >> stuff; // 从流中读取一个单词 getline(cin, stuff); // 从流中读取一行,删除流中的\n |
getline可以指定用于分割单词的分隔符,将其放到第三个参数中,如:
cin.getline(info, 100, ‘:’); // C 风格字符串使用这种形式
getline(stuff, ‘:’); // string 类使用这种形式
string与传统 C 风格字符串的比较:
使用string类输入时,不需要具体指定输入的字符个数,其可以自动匹配字符串大小,使用C风格字符串必须显示指定,因为C风格字符串使用的是istream类的方法,而string版本使用的是独立的函数,因此形式上有所区别,这种区别在其他运算符上也有体现,如>>操作符:
C 风格字符串使用:cin.operator>>(fname),而string风格为:operator(cin, fname);
说明:
string版本的getline()在如下三种情况下结束读取:
1 到达文件末尾:此时输入流中的eofbit置位;
2 遇到分割符:默认为\n,此时,删除流中的\n;
3 读取的字符个数达到最大值[min(string::npos, 空闲内存)],此时将输入流的failbit置位。
在输入流中有一个统计系统,用于跟踪流的状态:
1 检测到文件末尾,设置eofbit寄存器;
2 检测到错误,设置failbit寄存器;
3 检测到无法识别的故障,设置badbit寄存器;
4 检测到一切顺利,设置goodbit寄存器。
string 类对全部 6 个关系运算符进行了重载,对于每个关系运算符,又进行了3种重载,分别是:
string 对象与 string 对象;
string 对象 C 风格字符串;
C 风格字符串与 string 对象。
[2] 智能指针(smart pointer)
智能指针是行为类似于指针,但却是类。其可以帮助管理动态分配的内存,有三种可选:分别为auto_ptr\unique_ptr\shared_ptr。auto_ptr是C++ 98提供的,C++ 11已经抛弃了(虽然已经被抛弃,但是仍然被大量使用)。
小知识: 为何新标准要放弃auto_ptr?理由如下: 假设如下赋值: auto_ptr<string> ps1(new string(“benxintuzi”)); auto_ptr<string> ps2; ps2 = ps1; 如果ps2和ps1是普通指针,那么他们将同时指向一块堆内存,那么auto_ptr类型的指针若同时指向一块堆内存,那么就会调用两次delete,这个太可怕了,因此,auto_ptr采用的策略是如果发生赋值,那么就会令ps1为0,同样unique_ptr也是采用这种策略,但是更加严格。 如下,当ps2 = ps1,即ps1置空时,如果发生调用*ps1,那么相当于调用了一个空指针指向的对象,在auto_ptr情况下,可以编译通过,在运行时会出错;但在unique_ptr情况下,不会让你通过编译的。 结论就是unique_ptr比auto_ptr更安全一些。
shared_ptr对于每个堆对象,其维护一个指向同一对象的引用计数,只有当引用计数为0时才调用delete,因此可以很好地支持智能指针赋值操作。 |
要创建智能指针,必须包含头文件memory,然后使用模板语法实例化所需类型的指针,例如auto_ptr包含如下构造函数:
template<class X> class auto_ptr
{
public:
// throw()意味着不引发异常【C++ 11也将throw()抛弃了】
explicit auto_ptr(X* p = 0) throw();
...
}
因此,智能指针的使用非常简单,只需要用特定类型的指针初始化即可,如:
auto_ptr<double> pd(new double);
说明:
智能指针都放在名称空间std中(注意shared_ptr和unique_ptr都是C++ 11新增的,旧时的编译器可能不支持)。
#include <iostream>
#include <string>
#include <memory> class Report
{
public:
Report(const std::string s) : str(s)
{
std::cout << "Object created!" << std::endl;
}
~Report()
{
std::cout << "Object deleted!" << std::endl;
}
void comment() const
{
std::cout << "str = " << str << std::endl;
} private:
std::string str;
}; int main()
{
std::auto_ptr<Report> pa(new Report("using auto_ptr"));
28 pa->comment();
29
30 std::shared_ptr<Report> ps(new Report("using shared_ptr"));
31 ps->comment();
32
33 std::unique_ptr<Report> pu(new Report("using unique_ptr"));
34 pu->comment();
return ;
} /** output */
Object created!
str = using auto_ptr
Object created!
str = using shared_ptr
Object created!
str = using unique_ptr
Object deleted!
Object deleted!
Object deleted!
注意:
绝对不要将非堆内存指针赋予智能指针,否则虽然可以通过编译,但是情况并非总是乐观的,这就相当于delete了非堆内存,容易引发难以发现的问题。
关于如何选择合适的智能指针,如下给出参考建议:
如果需要多个指向同一堆对象的有效指针,那么选择使用shared_ptr。这种情况包括:
1 有一个指针数组,并且使用一些辅助指针标识特殊元素,如标识最值元素;
2 两个对象中都包含指向第三个对象的指针;
3 STL容器中的指针等,很多STL算法都支持复制和赋值操作。
如果程序不需要多个有效指针同时指向同一堆对象,那么使用unique_ptr。如果需要将unique_ptr作为右值时,可将其赋值给shared_ptr;在满足unique_ptr条件时,也可使用auto_ptr,但unique_ptr似乎是更好的选择(如果编译器不提供unique_ptr,可以使用boost库提供的scoped_ptr,其功能与unique_ptr类似)。
[3] 迭代器
[3.1] 迭代器基础
迭代器就是广义的指针,可对其进行递增操作和解引用操作等的对象。每个容器类都定义了一个与之相关的迭代器,该迭代器是一个名为iterator的typedef定义,作用域为整个类。模板使算法独立于数据类型,而迭代器使算法独立于容器类型。
迭代器主要用于遍历容器中的元素,其应该具有如下基本功能:
1 支持解引用操作
2 赋值操作
3 比较操作,如 ==、!=
4 自增操作
具备以上能力就差不多了。其实STL按照迭代器功能强弱定义了多种级别(5种)的迭代器,说明如下:
迭代器类型 |
说明 |
输入迭代器 |
【读容器】可来读取容器中的元素,但可能不会修改容器中的元素。输入迭代器必须可以访问容器中的所有元素,因此,支持++操作。 |
输出迭代器 |
【写容器】程序可以修改容器中的元素值,但不能读取容器中的元素。 |
正向迭代器 |
【读或者写容器】单向读写。 |
双向迭代器 |
【双向读或者写容器】双向读写。支持自减运算符。 |
随机访问迭代器 |
【双向读或者写容器】提供了附加的“跳步”访问能力。 |
迭代器支持的操作含义:
表达式 |
说明 |
a + n、n + a |
指向a所指元素后的第n个元素 |
a - n |
指向a所指元素前的第n个元素 |
r += n、r -= n |
r = r + n、r = r – n |
a[n] |
*(a + n) |
b - a |
a ~ b区间的元素个数 |
a < b、a > b、a >= b、a <= b |
逻辑判断 |
STL迭代器功能汇总:
总结:
迭代器支持的通常操作为解引用读或者写;所有类型的迭代器都支持自增操作;自减操作只有双向迭代器和随机访问迭代器支持;随机访问迭代器为“跳步”访问提供了可能。因此迭代器能力大小可以表示如下:
随机访问迭代器 > 双向迭代器 > 正向迭代器 > 输入迭代器 == 输出迭代器
[3.2] 迭代器进阶
迭代器是广义的指针,而指针满足所有迭代器的要求。因此STL算法可以使用指针来对基于指针的非STL容器进行操作。例如,可将STL算法用于数组:
如果要将一个double Receipts[100]进行排序,可以使用STL的算法sort,但传入的确是指针,如:
sort(Receipts, Receipts + 100);
STL提供了一些预定义的迭代器:copy()、ostream_iterator、istream_iterator。
假设有如下定义:
int casts[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
vector<int> dice(10);
copy(casts, casts + 10, dice.begin()); // 将[casts, casts + 10)范围内的数据复制到dice.begin()开始的目标空间中
输出流迭代器:
假设现在需要将数据复制到显示器上,那么需要一个表示输出流的迭代器,STL为我们提供了ostream_iterator模板。该迭代器是一个适配器,可将所有的接口转换为STL使用的接口。我们使用时必须包含头文件iterator,并且做出如下声明来创建该迭代器:
#include <iterator>
...
ostream_iterator<int, char> out_iter(cout, “ ”);
说明:
int: 表示发送到输出流中的数据类型;
char: 表示输出流使用的字符类型;
构造函数中,cout: 表示要使用的输出流;“ ”:表示所发送数据的分隔符;
可以这样使用迭代器:
*out_iter++ = 15; // 等价于cout << 15 << “ ”;
这条语句表明将”15 ”赋予指针指向的位置,然后指针向前移动一个单位。
实现如上的copy动作为:
copy(dice.begin(), dice.end(), out_iter)即可将dice容器的整个区间复制到显示器中显示。
当然也可以创建匿名的迭代器,如下:
copy(dice.begin(), dice.end(), ostream_iterator<int, char>(cout, “ ”));
输入流迭代器:
对于的输入流迭代器为istream_iter模板:
copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(), ostream_iterator<int, char>(cout, “ ”));
说明:
int:要读取的数据类型;
char:输入流使用的数据类型;
构造函数中,第一个参数cin表示读取由cin管理的输入流;省略构造函数意味着输入失败,因此上述代码从输入流中读取int型数据,直到文件末尾、类型不匹配或者出现其他输入故障为止才输出到屏幕上。
#include <iostream>
#include <iterator>
using namespace std; int main()
{
copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(),
ostream_iterator<int, char>(cout, " "));
return ;
} /** output */ __
除了istream_iterator和ostream_iterator外,头文件iterator中还提供了一些专用的迭代器,如:reverse_iterator、back_insert_iterator、front_insert_iterator和insert_iterator。
reverse_iterator执行递增时,指针实际上发生递减操作。例如:vector类中的rbegin()和rend()分别返回指向最后一个元素下一位置的指针、指向第一个元素的指针。
强调:
虽然rbegin()和end()返回的指针虽然指向同一个位置,但是类型不同,前者类型是reverse_iterator,后者类型是iterator。
同样要注意的是:对于反向迭代的解引用的具体实现实则是先递减,再解引用。否则会出错。试想一下,如果一个rp是rbegin()返回的,直接解引用将导致操作一个空指针。
#include <iostream>
#include <iterator>
#include <vector>
using namespace std; /*
int main()
{
copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(),
ostream_iterator<int, char>(cout, " "));
return 0;
}
*/ int main()
{
int casts[] = {, , , , , , , , , };
vector<int> dice();
copy(casts, casts + , dice.begin()); // 将casts的前5个数据复制到dice中 ostream_iterator<int, char> out_iter(cout, " "); // 创建输出流迭代器
copy(dice.begin(), dice.end(), out_iter); // 将dice中的全部元素复制到屏幕上输出 cout << endl; /* 隐式使用reverse_iterator */
copy(dice.rbegin(), dice.rend(), out_iter); // 将dice中的全部元素反向输出到屏幕上 cout << endl; /* 显式使用reverse_iterator */
vector<int>::reverse_iterator ri;
for(ri = dice.rbegin(); ri != dice.rend(); ++ri)
cout << *ri << " : ";
cout << endl; return ;
} /** output */ : : : : : : : : : :
在形式上,STL中的很多算法都和copy函数类似,如下为三种插入迭代器操作:
back_insert_iterator将元素插入容器尾部;
front_insert_iterator将元素插入容器头部;
insert_iterator将元素插入指定位置。
插入操作相比于copy操作而言,可以自动增加空间,而copy确是静态分配好的空间,即使不够了也不会向操作系统去申请,默认情况下会截断后边的数据,只复制容量允许的数据。使用插入迭代器时,使用容器类型作为模板参数,使用容器变量作为构造参数,如下所述:
back_insert_iterator<vector<int>> back_iter(dice);
对于insert_iterator,还需要一个指定插入位置的构造参数:
insert_iterator<vector<int>> insert_iter(dice, dice.begin());
[4]容器类
容器用于存储对象,其要求所存储的对象必须具有相同的类型,而且该类型必须是可复制构造和可赋值的。一般基本类型和类类型都满足要求(当然除非你把这两种函数声明为私有或保护类型的)。C++ 11中又添加了可复制插入和可移动插入要求。
容器类型是用于创建具体容器的模板。之前共有11个容器类型:deque/list/queue/priority_queue/stack/vector/map/multimap/set/multiset/biset,C++ 11新增了5个:forward_list/unordered_map/unordered_multimap/unordered_set/unordered_multiset。
何为序列?
序列保证了元素将按特定的顺序排列,不会在两次迭代之间发生变化。序列还要求其元素严格按线性顺序排列,即存在第一个/第二个/.../等。比如数组和链表都是序列,但分支结构就不是序列。
由于序列中的元素具有特定的顺序,因此可以执行像插入元素到特定位置、删除特定区间等操作。如下容器都为序列容器:
vector |
vector是数组的一种类的表示,它提供了自动管理内存的功能,可以动态改变vector的长度,增大或减小。vector是可以反转的,主要是通过rbegin()和rend()实现的,并且其返回的迭代器类型都是reverse_iterator。 |
deque |
双端队列。其实现类似于vector,支持随机访问。主要区别在于可以从deque中开头和结尾处插入和删除元素,而且其时间复杂度固定。 |
list |
双向链表。与vector类似,list也可以反转。主要区别是:list不支持随机访问。除此之外,list模板类还包括了链表专用的成员函数,如下所示: void merge(list<T, Alloc>& x): 将链表x与调用链表合并,并且已然有序。合并后的链表保存在调用链表中,x为空。 void remove(const T& val): 从链表中删除val的所有实例。 void sort(): 使用<运算符对链表进行排序,时间复杂度为nlgn。 void splice(iterator pos, list<T, Alloc>x): 将链表x的内容插入到pos前边,插入后x为空。 void unique(): 将链表中连续的相同元素压缩为单个元素。 说明: insert()和splice()之间的主要区别在于:insert是插入原始区间的副本到目标地址,而splice是将原始区间转移到目标地址。 C++ 11新增了容器类forward_list,实现了单链表,与之关联的迭代器是正向迭代器而非双向迭代器。 |
queue |
queue模板类是一个适配器类,如前所述,ostream_iterator模板也是一个适配器,可以让输出流使用迭代器接口,同样,queue模板类让底层类(默认为deque)展示出队列接口。 queue模板的限制比deque更多。他不仅不允许随机访问,而且不允许遍历操作。其把使用限制在队列基本操作上,如入队、出队、取队首、判队空等,具体如下: bool empty() const: 如果队列为空则返回true;否则返回false。 size_type size() const: 返回队列中元素的数目。 T& front(): 返回指向队首元素的引用。 T& back(): 返回指向队尾元素的引用。 void push(const T& x): 在队尾插入x。 void pop(): 删除队首元素。 说明: priority_queue与queue的主要区别在于:最大元素被移到队首。其内部实现为vector,可以指定内部元素排序规则。 |
stack |
适配器类,使用vector实现,支持操作如下: bool empty() const: 如果栈为空则返回true;否则返回false。 size_type size() const: 返回栈中元素的数目。 T& top(): 返回指向栈顶元素的引用。 void push(const T& x): 在栈顶插入x。 void pop(): 删除栈顶元素。 |
array(C++ 11 新增) |
模板类array并非STL容器,因为其长度是固定的。因此不能使用动态调整容器大小的函数如push_back()和insert()等,但是其定义了很有用的成员函数,如operator[]()和at(),正如之前所述,许多标准的STL算法也可用于array对象,如:copy()和for_each()。 |
操作示例:
#include <iostream>
#include <list>
#include <iterator>
#include <algorithm>
using namespace std; void PrintList(int n){ cout << n << " "; } int main()
{
list<int> one(, ); // 5个2
cout << "list one: ";
for_each(one.begin(), one.end(), PrintList);
cout << endl; list<int> two;
int stuff[] = {, , };
two.insert(two.begin(), stuff, stuff + );
cout << "list two: ";
for_each(two.begin(), two.end(), PrintList);
cout << endl; list<int> three(two);
three.insert(three.end(), one.begin(), one.end());
cout << "list three: ";
for_each(three.begin(), three.end(), PrintList);
cout << endl; cout << "merge one and three: ";
three.splice(three.begin(), one);
for_each(three.begin(), three.end(), PrintList);
cout << endl; cout << "delete the duplicate elements: ";
three.unique();
for_each(three.begin(), three.end(), PrintList);
cout << endl; cout << "merge with another: four = ";
list<int> four(, );
four.merge(three);
for_each(four.begin(), four.end(), PrintList);
cout << endl; cout << "remove the value 8 in four: ";
four.remove(8);
for_each(four.begin(), four.end(), PrintList);
cout << endl; return ;
} /** output */
list one:
list two:
list three:
merge one and three:
delete the duplicate elements:
merge with another: four =
remove the value in four:
C++ 基础知识回顾(string基础、智能指针、迭代器、容器类)的更多相关文章
- java基础知识回顾之---java String final类普通方法
辞职了,最近一段时间在找工作,把在大二的时候学习java基础知识回顾下,拿出来跟大家分享,如果有问题,欢迎大家的指正. /* * 按照面向对象的思想对字符串进行功能分类. * ...
- C#学习笔记(基础知识回顾)之值传递和引用传递
一:要了解值传递和引用传递,先要知道这两种类型含义,可以参考上一篇 C#学习笔记(基础知识回顾)之值类型和引用类型 二:给方法传递参数分为值传递和引用传递. 2.1在变量通过引用传递给方法时,被调用的 ...
- C#基础知识回顾-- 反射(3)
C#基础知识回顾-- 反射(3) 获取Type对象的构造函数: 前一篇因为篇幅问题因为篇幅太短被移除首页,反射这一块还有一篇“怎样在程序集中使用反射”, 其他没有什么可以写的了,前两篇主要是铺垫, ...
- C#基础知识回顾--线程传参
C#基础知识回顾--线程传参 在不传递参数情况下,一般大家都使用ThreadStart代理来连接执行函数,ThreadStart委托接收的函数不能有参数, 也不能有返回值.如果希望传递参数给执行函数, ...
- Java基础知识回顾之七 ----- 总结篇
前言 在之前Java基础知识回顾中,我们回顾了基础数据类型.修饰符和String.三大特性.集合.多线程和IO.本篇文章则对之前学过的知识进行总结.除了简单的复习之外,还会增加一些相应的理解. 基础数 ...
- C++ 基础知识回顾总结
一.前言 为啥要写这篇博客?答:之前学习的C和C++相关的知识,早就被自己忘到一边去了.但是,随着音视频的学习的不断深入,和C/C++打交道的次数越来越多,看代码是没问题的,但是真到自己操刀去写一些代 ...
- C#学习笔记(基础知识回顾)之值类型与引用类型转换(装箱和拆箱)
一:值类型和引用类型的含义参考前一篇文章 C#学习笔记(基础知识回顾)之值类型和引用类型 1.1,C#数据类型分为在栈上分配内存的值类型和在托管堆上分配内存的引用类型.如果int只不过是栈上的一个4字 ...
- C#学习笔记(基础知识回顾)之值类型和引用类型
一:C#把数据类型分为值类型和引用类型 1.1:从概念上来看,其区别是值类型直接存储值,而引用类型存储对值的引用. 1.2:这两种类型在内存的不同地方,值类型存储在堆栈中,而引用类型存储在托管对上.存 ...
- Java基础知识回顾(一):字符串小结
Java的基础知识回顾之字符串 一.引言 很多人喜欢在前面加入赘述,事实上去技术网站找相关的内容的一般都应当已经对相应知识有一定了解,因此我不再过多赘述字符串到底是什么东西,在官网中已经写得很明确了, ...
- C#基础知识回顾-- 反射(1)
C#基础知识回顾-- 反射(1) 反射(reflection)是一种允许用户获得类型信息的C#特性.术语“反射”源自于它的工作方式: Type对象映射它所代表的底层对象.对Type对象进行查询可以 ...
随机推荐
- git学习——分支
分支 创建分支:git branch 如:git branch testing Git通过HEAD指针知道用户是在哪一个分支上工作. 切换分支用git checkout命令,注意:可以用git sta ...
- Java Executor框架
java.util.concurrent 包中包含灵活的线程池实现,但是更重要的是,它包含用于管理实现 Runnable 的任务的执行的整个框架,该框架称为 Executor 框架.该框架基于生产者- ...
- 如何使用FlashgameMaster修改游戏
1 打开任意一款游戏,注意最好是AS2.0版的,我们以黄金矿工为例. 2 使用方法: 点击编辑按钮的打开编辑面板出现编辑面板后在对话框中输入要查找的数值,点击查找按钮,会列表出所以等于该值的变量. 如 ...
- Windows利用命令行快速清除以及建立密码
我们Win10一般是没有管理员权限的!这就要求我们获取管理员权限了,一般有两种方法获取,我就介绍下面一种最简单的 老操作:WIn+R打开本窗口,输入:taskmgr 建立密码(administrato ...
- Python趣味实用小工具
代码地址如下:http://www.demodashi.com/demo/12918.html python 趣味实用小工具 概述 用python实现的三个趣味实用小工具: 图片转Execl工具 , ...
- PL/SQL 异常错误处理
异常错误处理 一个优秀的程序都应该可以正确处理各种出错情况,并尽可能从错误中恢复.ORACLE 提供异常情况(EXCEPTION)和异常处理(EXCEPTION HANDLER)来实现错误处理 ...
- Eclipse3.4以上使用dropins的插件安装方式
Eclipse3.4以上版本支持使用dropins的插件安装方式,使用方便,共有四种使用方法: 1. 最简单的,直接将jar包放到dropins目录下eclipse/ dropins/ 2. 传 ...
- chrome浏览器FQ插件,可以上Facebook等。。。
不需要复杂的配置,看里面的介绍就知道了,目前很稳定. 插件下载地址:http://honx.in/_VGQLtYIaA10BIEKV
- filebeat 选项
Filebeat Options input_type: log|stdin 指定输入类型 paths 支持基本的正则,所有golang glob都支持,支持/var/log/*/*.log enco ...
- docker run
1.登录Docker Hub 账户 2.输入命令:构建一个web应用. docker run -d -P training/webapp python app.py 参数说明: -d:让容器在后台运行 ...