C++中两种最重要的标准库类型是 string 和 vector。首先介绍一下迭代器。
迭代器
现代 C++ 程序更倾向于使用迭代器而不是下标操作访问容器元素,即使对支持下标操作的 vector 类型也是这样。
每种容器类型都定义了自己的迭代器类型,如 vector:
vector<int>::iterator iter;
该语句定义了一个名为 iter 的变量,它的数据类型是 vector 定义的 iterator 类型。
一些典型的迭代器操作
begin 函数:
把 iter 初始化为由名为 vector 操作返回的值。假设 vector 不空,初始化后,iter 即指该元素为 ivec[0]。
end函数:
由 end 操作返回的迭代器指向 vector 的“末端元素的下一个”,表明它指向了一个不存在的元素。如果 vector 为空,begin 返回的迭代器与 end 返回的迭代器相同。
解引用操作符(*):
解引用操作符返回迭代器当前所指向的元素。
自增操作符(++):
向前移动迭代器指向容器中下一个元素。
注:
由于 end 操作返回的迭代器不指向任何元素,因此不能对它进行解引用或自增操作。
比较:
用 == 或 != 操作符来比较两个迭代器,如果两个迭代器对象指向同一个元素,则它们相等,否则就不相等。
迭代器的使用
int i = 1;
for(vector<int>::iterator iter = ivec.begin(); iter != ivec.end(); iter++)
{
*iter = i++;
}
const_iterator
每种容器类型还定义了一种名为 const_iterator 的类型,当我们对普通 iterator 类型解引用时,得到对某个元素的非 const。而如果我们对 const_iterator 类型解引用时,则可以得到一个指向 const 对象的引用,如同任何常量一样,该对象只读不写。
使用迭代器的二分查找
STL中关于二分查找的函数有三个lower_bound 、upper_bound 、binary_search 。这三个函数都运用于有序区间(当然这也是运用二分查找的前提),下面记录一下这两个函数。
ForwardIter lower_bound(ForwardIter first, ForwardIter last,const _Tp& val)算法返回一个非递减序列[first, last)中的第一个大于等于值val的位置。
ForwardIter upper_bound(ForwardIter first, ForwardIter last, const _Tp& val)算法返回一个非递减序列[first, last)中的第一个大于值val的位置。
遍历元素的新方法:for(auto count : counts)
这是C++11中的语法,即:Range-based for loop。其中counts应满足:begin(counts), end(counts)是合法的。
它等价于
for(some_iterator p = begin(counts); p != end(counts); ++p)
//同时
some_type count = *p。
string 类型
与其他的标准库类型一样,用户程序要使用 string 类型对象,必须包含相关头文件。
#include <string>
string的初始化
string s1;//用默认构造函数创建s1,s1为空串
string s2=s1;//将 s2 初始化为 s1 的一个副本
string s3="value";//将 s3 初始化为一个字符串字面值副本
string s4(n,'c');//将 s4 初始化为字符'c'的n个副本
string的元素读取可以使用下标。
string的读取
cin>>s
• 读取并忽略开头所有的空白字符(如空格,换行符,制表符)。
• 读取字符直至再次遇到空白字符,读取终止。
如:输入“hello world”,那么会输出“hello”而没有空格。
getline(cin,s)
此时s读取的将是一行字符串,且不忽略空格,直到换行符出现。
用stringstream类实现字符串分割
假设s是一个以空格为分隔符的string变量,如何获得被分割的子string呢?可以使用stringstream。
C++引入了ostringstream、istringstream、stringstream这三个类,要使用它们创建对象就必须包含<sstream>
这个头文件。
- istringstream类用于执行串流的输入操作。
- ostringstream类用于执行串流的输出操作。
- strstream类同时可以支持串流的输入输出操作。
istringstream的构造函数原形如下:
istringstream::istringstream(string str);
它的作用是从string对象str中读取字符。
#include<iostream>
#include<sstream>
#include<string>
using namespace std;
int main() {
string str="i am a boy";
istringstream is(str);
string s;
while(is>>s) {
cout<<s<<endl;
}
}
输出是:
i
am
a
boy
以上就实现了按空格分割字符串的功能。同样的功能也可以由stringstream实现。stringstream的str( s)方法將字符串s传入stringstream中,然后可以像使用cin那样使用stringstream。
注意到,一般还会调用ss.clear()方法清空目前的标志位。
string str="i an a boy";
stringstream ss;
string as;
ss.clear();
ss.str(str);
while(ss>>as) {
cout<<as<<endl;
}
如果不知道具体被分割单元的个数,还可以使用ss.fail()
函数,该函数当ss流没有数据时返回true,可以据此停止读取string。在这里,ss起到和cin>>
类似的作用。
getline(cin, s);//从cin中读取了一个以回车结束的string
ss.clear();
ss.str(s);//将s转化成ss流
sum=0;
while (!ss.fail()) {
ss >> a;
sum+=a;
}
cout << sum << endl;
利用istringstream实现更通用的字符串分割
string本身没有提供切割的方法,但可以使用istringstream和getline方法实现分割。getline方法的第三个形参是分隔符。
PS:
这里getline的分隔符只能是字符,而不能是字符串。
对比Java,Java中的split方法是把字符串作为分隔符,所以相对来说更加方便。
如下:
string str="i@am@a@boy";
istringstream is(str);
vector<string> v;
string a;
while(getline(is,a,'@'))
v.push_back(a);
for(int i=0;i<v.size();i++)
cout<<v[i]<<endl;
输出为:
i
am
a
boy
常用的 string 方法
注:string::npos
的类型是string::size_type
,所以,一旦需要把一个索引与npos相比,这个索引值必须是string::size
类型的。大多情况下,我们可以直接把函数和npos进行比较,如: if(s.find(“jia”)== string::npos)
//string的大小
s.empty();//判断是否为空
s.size();
//在尾部添加字符
str1 += str2;
str.append();
str.push_back()
//赋值
s.assign(str);
s.assign(str,1,3);//如果str是”iamangel” 就是把”ama”赋给字符串
s.assign(str,2,string::npos);//把字符串str从索引值2开始到结尾赋给s
s.assign(“gaint”);
s.assign(“nico”,5);//把'n' ‘I' ‘c' ‘o' ‘/0'赋给字符串
s.assign(5,'x');//把五个x赋给字符串
//插入
//在字符串指定位置前面插入指定字符串
str1.insert(14,str2);
//在字符串指定位置前面插入指定字符串的子串(从指定索引开始的指定个数的字符)
str1.insert(14,str2,2,9);
//插入指定字符串的前n个字符
str1.insert(14,"test hello",5);
//插入n个相同字符到字符串中
str1.insert(14,6,'*');
//替换
//替换指定索引开始的指定长度的子串
str1.replace(3,3,"may");
//用给定字符串的指定子串来进行替换
//如下,实际上使用的是could来进行替换
str1.replace(3,3,"can could",4,5);
//使用给定字符串的前n个字符来进行替换
//用can来进行替换
str1.replace(3,5,"can could",3);
//使用指定个数的重复字符来进行替换
str1.replace(3,3,5,'*');
//交换
str1.swap(str2);//交换当前str1与str2的值
//删除
//删除从pos开始的n个字符,比如erase(0,1)就是删除第一个字符
str.erase(pos,n);
//删除iterator迭代器指向的一个字符
str.erase (iterator);
//删除从迭代器first到last之间的字符
str.erase (str.begin()+5, str.end()-7);
/***/
str.clear() //删除全部字符
//查找
str.find(const string& str, size_t pos = 0);//返回符合搜索条件的字符区间内的第一个字符的索引,没找到目标就返回npos
//返回子串
s.substr();//返回s的全部内容
s.substr(11);//返回从索引11往后的子串
s.substr(5,6);//返回从索引5开始6个字符
//将string转换成整型数
//返回值:1. 成功转换显示一个Int类型的值. 2. 不可转换的字符串返回0. 3.如果转换后缓冲区溢出,返回 INT_MAX or INT_MIN
int a=atoi(s.c_str());
//或者:(利用了stringstream)
string s = "17";
int i;
stringstream ss;
ss<<s;
ss>>i;
//将string转换成float
//返回值:1. 转换成功返回doublel类型的值 2.不能转换,返回0.0。 3.越界,返回HUGE_VAL
double a=atof(s.c_str());
//整型转换成string(利用了stringstream)
int aa = 30;
stringstream ss;
string s2;
ss<<aa;
ss>>s2;
//string转换成字符数组
char *p=str.c_str();//返回C的字符数组的指针
//比较
> < >= <= != //都被重载,用于string的比较
常用的字符(char)处理函数
isalnum(c);//如果 c 是字母或数字,则为 True。
isalpha(c);//如果 c 是字母,则为 true。
isdigit(c);//如果 c 是数字,则为 true。
isxdigit(c);//如果是 c 十六进制数,则为 true。
islower(c);//如果 c 是小写字母,则为 true。
isupper(c);//如果 c 是大写字母,则 true。
tolower(c);//如果 c 大写字母,返回其小写字母形式,否则直接返回 c。
toupper(c);//如果 c 是小写字母,则返回其大写字母形式,否则直接返回 c。
iscntrl(c);//如果 c 是控制字符,则为 true
isgraph(c);//如果 c 不是空格,但可打印,则为 true。
isprint(c);//如果 c 是可打印的字符,则为 true。
ispunct(c);//如果 c 是标点符号,则 true。
isspace(c);//如果 c 是空白字符,则为 true。
vector类型(容器向量)
vector的重要特性就是可以动态增长。
使用 vector 之前,必须包含相应的头文件。
#include <vector>
vector的声明
vector 是一个类模板,声明从类模板产生的某种类型的对象,需要提供附加信息。以 vector 为例,必须说明 vector 保存何种对象的类型,通过将类型放在类模板名称后面的尖括号中来指定类型:
vector<int> ivec; // ivec holds objects of type int
vector<Sales_item> Sales_vec; // holds Sales_items
二维vector的声明如下:
vector<vector<int> > ary(row_num, vector<int>(col_num, 0));
注意两个右尖括号之间有一个空格。
vector 的初始化
vector<T> v(n,i);//v包含n 个值为 i 的元素
vector<T> v(v1);//v是v1 的一个副本
vector<T> v(n);//v包含n 个值初始化的元素
//数组初始化vector
int iarray[]={1,2,3,4,5,6,7,8,9,0};
size_t count=sizeof(iarray)/sizeof(int);
//int数组初始化 ivec3
vector<int> ivec3(iarray,iarray+count);
vector 的常用操作函数
vector 标准库提供了许多类似于 string 对象的操作,表 3.5 列出了几种最重要的 vector 操作。
v.empty() | 如果 v 为空,则返回 true,否则返回 false。 |
---|---|
v.size() | 返回 v 中元素的个数。 |
v.resize(n) | 重新设置v的大小为n |
v.push_back(t) | 在 v 的末尾增加一个值为 t 的元素。 |
v.pop_back() | 删除v的尾端的元素 |
v.front() | 返回当前vector容器中起始元素的引用。 |
v.back() | 返回当前vector容器中末尾元素的引用。 |
v.insert(a,t) | 在迭代器a的前面增加一个值为t的元素。 |
v.erase(iter) | 删除指定位置的元素 |
v.erase(iterator first, iterator last) | 删除指定范围内的元素 |
v.clear() | 清空vector里的元素 |
v[n] | 返回 v 中位置为 n 的元素。 |
v1 = v2 | 把 v1 的元素替换为 v2 中元素的副本。 |
v1 == v2 | 如果 v1 与 v2 相等,则返回 true。 |
!=, <, <=,>,和>= | 保持这些操作符惯有的含义。 |
vector的添加
必须是已存在的元素才能用下标操作符进行索引,不能直接通过下标操作添加元素。
如果要添加新的元素,要用push_back函数:
ivec.push_back(ix);
或insert函数:
insert函数能在指定位置loc前插入值为val的元素,并返回指向这个元素的迭代器,如:
v.insert(v.begin(),8);//在最前面插入新元素。
v.insert(v.begin()+2,1);//在迭代器中下标为2的元素前插入新元素
v.insert(v.end(),3);//在向量末尾追加新元素。
insert也能在指定位置loc前插入num个值为val的元素
vector <char>::iterator theIterator = alphaVector.begin();
alphaVector.insert( theIterator, 4, 'C' );
//在vector的开头添加了4个"C"
vector的删除
vector中删除指定元素使用了std::vector::erase()方法。
函数原型:
iterator erase (iterator position); //删除指定元素
iterator erase (iterator first, iterator last); //删除指定范围内的元素
v.erase(v.begin());//删除第一个元素
v.erase(v.end()-1);//删除最后一个元素,注意是v.end()-1
//当然删除最后一个元素也可以直接用v.pop_back()
注意,每一次删除之后,后边的元素都会向前移动。所以删除后,当前迭代器实际已经指向下一个元素,若再+1,实际上是指向了下一个元素的下一个元素。
因此,如果想删除vector中所有值为1的元素,应该这样写:
vector<int>::iterator itr = v.begin();
while (itr!=v.end())
{
if (*v==1)
itr=v.erase(itr);
else
itr++;
}
vector的查找
vector的查找需要用到迭代器,以及find函数
注意这里的查找范围[ array.begin(),array.end() )
是一个左闭右开的。
vector<int>::iterator s=find(array.begin(),array.end(),50);//第一个参数是array的起始地址,第二个参数是array的结束地址,第三个参数是需要查找的值
if( s !=array.end())//找到
cout<<*s<<endl;
else
cout<<"not find!"<<endl;
list 类型
使用list前要包含相应的h文件:
#include <list>
list是非连续存储结构,具有双链表结构,每个元素维护一对前向和后向指针,因此支持前向/后向遍历。支持高效的随机插入/删除操作,但随机访问效率低下,且由于需要额外维护指针,开销也比较大。每一个结点都包括一个信息块Info、一个前驱指针Pre、一个后驱指针Post。可以不分配必须的内存大小方便的进行添加和删除操作。使用的是非连续的内存空间进行存储。
如果你需要大量的插入和删除,而不关心随机存取,则应使用list。
list的双向迭代器
list的迭代器是双向迭代器,只能++和–,而不能偏移(如:iter+5)。
list的常用方法
//链表的初始化是使用指针的,所以使用的是数组的地址
int num[10]={0,1,2,3,4,5,6,7,8,9};
list<int> link(&num[0],&num[9]+1);
//返回list中的元素个数
size()
//如果list是空的则返回true
empty()
//删除所有元素
clear()
//改变list的大小
resize()
//把list的元素倒转
reverse()
//合并两个list
merge()
//返回迭代器
begin() //返回指向第一个元素的迭代器
end() //返回末尾的迭代器
rbegin() //返回指向第一个元素的逆向迭代器
rend() //指向list末尾的逆向迭代器
//返回元素
front() //返回第一个元素
back() //返回最后一个元素
//将元素e插入到迭代器iter指向的位置,之后,iter自动后移一位,指向原来那个元素
insert(iter,e)
//在list的末尾添加一个元素
push_back()
//在list的头部添加一个元素
push_front()
//删除iter指向位置的节点
erase(iter)
//删除list中所有值为e的节点
remove(e)
//去除list中相邻的重复元素
unique()
//删除最后一个元素
pop_back()
//删除第一个元素
pop_front()
//list有自带的sort函数,不需要调用algorithm里的sort函数
sort()
bitset 类型
同样的,先包含标准库:
#include <bitset>
初始化bitset 对象的方法
bitset b | b 有 n 位,每位都 0 |
---|---|
bitset b(u) | b 是 unsigned long 型 u 的一个副本 |
bitset b(s) | b 是 string 对象 s 中含有的位串的副本 |
bitset b(s, pos, n) | b 是 s 中从位置 pos 开始的&nbps;n 个位的副本 |
如
bitset<16> bit1("1101");//bit1=0000 0000 0000 1101
bitset<16> bit1(1101);//bit1是十进制数1101的二进制表示
bitset<16> bit1(0x1101);//bit1=0001 0001 0000 0001
注:
string 对象和 bitsets 对象之间是反向转化的:string 对象的最右边字符 (即下标最大的那个字符)用来初始化 bitset 对象的低阶位(即下标为 0 的位)。
一些常用操作
b.any() | b 中是否存在置为 1 的二进制位? |
---|---|
b.none() | b 中不存在置为 1 的二进制位吗? |
b.count() | b 中置为 1 的二进制位的个数 |
b.size() | b 中二进制位的个数 |
b[pos] | 访问 b 中在 pos 处二进制位 |
b.test(pos) | b 中在 pos 处的二进制位置为 1 么? |
b.set() | 把 b 中所有二进制位都置为 1 |
b.set(pos) | 把 b 中在 pos 处的二进制位置为 1 |
b.reset() | 把 b 中所有二进制位都置为 0 |
b.reset(pos) | 把 b 中在 pos 处的二进制位置为 0 |
b.flip() | 把 b 中所有二进制位逐位取反 |
b.flip(pos) | 把 b 中在 pos 处的二进制位取反 |
b.to_ulong() | 用 b 中同样的二进制位返回一个 unsigned long 值 |
os << b | 把 b 中的位集输出到 os 流 |