C++ STL 应用点滴

来源:互联网 发布:注册表修改mac地址 编辑:程序博客网 时间:2024/05/22 07:46

1. 字符串大小写转换

string没有直接提供to_upper或to_lower这样的方法,不过有更通用的方法:

std::string s("hello");transform(s.begin(), s.end(), s.begin(), toupper);transform(s.begin(), s.end(), s.begin(), tolower);std::vector<char> v(s.begin(), s.end());transform(v.begin(), v.end(), v.begin(), toupper);transform(v.begin(), v.end(), v.begin(), tolower);

string没有to_upper方法是因为,不是每个string里的成员都是有独立意义的。比如string里可以存放utf8编码的中文字符串,这样对单个的char元素进行大小写转换是有风险的,因为每个char元素之间在utf8编码含义上存在相互依赖关系。注意这个例子中使用了C++的ADL技术,下面的例子还将出现。

2. 字符串的trim方法

string没有直接提供trim方法,但要实现也是比较容易的;但是要trim一个vector<char>就不大容易了:

const std::string source(" \t hello \t\t ");// trim string, some what simplestd::string s(source.begin(), source.end());// trim lefts.erase(0, s.find_first_not_of(' ')); // "\t hello \t\t "s.erase(0, s.find_first_not_of(" \t")); // "hello \t\t "// trim rights.erase(s.find_last_not_of(' ') + 1); // "hello \t\t"s.erase(s.find_last_not_of(" \t") + 1); // "hello"// trim character vector, a bit complicatedstd::vector<char> v(source.begin(), source.end());const auto is_blank = bind2nd(std::equal_to<char>(), ' ');const auto is_space = bind1st(std::ptr_fun<const char*>(strchr), " \t");v.erase(v.begin(), find_if_not(v.begin(), v.end(), is_blank)); // "\t hello \t\t "v.erase(v.begin(), find_if_not(v.begin(), v.end(), is_space)); // "hello \t\t "v.erase(find_if_not(v.rbegin(), v.rend(), is_blank).base(), v.end()); // "hello \t\t"v.erase(find_if_not(v.rbegin(), v.rend(), is_space).base(), v.end()); // "hello"

这里把复杂的functor提取出一个变量,以免一行代码过长。如果不这样的话代码会很复杂,这大概就是string为什么要提供find_??_of函数的一个原因吧。最后注意下,is_blank/is_space与C语言库中定义的isblank/isspace含义不同,参见http://www.cplusplus.com/reference/cctype/。

3. erase/remove_if 惯用手法

这是C++标准库的一个经典手法之一,常用于序列容器的元素删除。

const auto source = std::string("http://Blog.CSDN.NET");// erase/remove_if for stringauto s = std::string(source.begin(), source.end());s.erase(remove_if(s.begin(), s.end(), isupper), s.end()); // "http://log.."// erase/remove_if for vectorauto v = std::vector<char>(source.begin(), source.end());v.erase(remove_if(v.begin(), v.end(), islower), v.end()); // "://B.CSDN.NET"

4. 字符串替换

通常有两种情况,一种是将字符串中的特定内容替换为另外的内容,另外一种是单个字符的替换。前者用string的replace方法即可,后者则要动用algorithm了。可惜的是,string的replace方法一次只能替换掉一次出现,不能一次性替换全部。如果要这样做,可以自己手写一个for循环。

auto s = std::string("http://blog.csdn.net");const auto src = std::string("csdn");const auto dst = std::string("chinaunix");s.replace(s.find(src), src.size(), dst); // "http://blog.chinaunix.net"replace(s.begin(), s.end(), '.', '&'); // "http://blog&chinaunix&net"const auto ampersand = std::string("&");const auto html_escape = std::string("&amp;");for (auto offset = std::string::size_type();    (offset = s.find(ampersand, offset)) != s.npos;    (offset += html_escape.size())){    s.replace(offset, ampersand.size(), html_escape);} // "http://blog&amp;chinaunix&amp;net"

5. string的begin_with和end_width方法

这是比较常用的功能,对于简单的要求只要用compare方法,就不必动用正则表达式了。特别注意的是,在判断end_with时要注意越界检查。

const auto s = std::string("http://blog.csdn.net");const auto head = std::string("http://");const auto tail = std::string(".net");const bool is_begin_with = s.compare(0, head.size(), head) == 0;const bool is_end_with = s.size() >= tail.size()    && s.compare(s.size() - tail.size(), tail.size(), tail) == 0;
6. 字符串和数字之间的转换
这个功能实在是太常用了,不过标准库已经提供了相当完整的支持。如strtod/strtoul, stod/stoul, atoi/atof, std::to_string, sscanf/sprintf等,这里要指出的是,strto?/sto? 函数在进制填写为0时会自动识别字符串的进制信息,这在应用时比较方便;顺便提醒一下,这里sto?可能会抛异常!确切的说,sscanf/sprintf能实现所有数字和字符串之间的转换,但是其他函数在命名上则更为直观。

strtoul("10", nullptr, 0); // return 10strtoul("0x10", nullptr, 0); // return 16
7. 强大的assign方法

标准库中string, vector, deque, list, forward_list都提供了assign方法,并且有相应构造函数对应。这些方法都有(InputIterator first, InputIterator last)的形式,初看不怎么诱人,但如果结合标准库中的iostream, fstream, stringstream等流时,其功能非常强大、灵活。

比如大家都熟悉的从一个文件中读取以空白符分割的数字字符串,假设文件内容为 "30\v 43\n 50\t 64 71"。比较土的方法是将文件的内容一次性读取到一个buffer中,然后一个一个提取;或者直接用fscanf之类的函数一个一个提取;或者逐个调用用ifstream的>>(int)方法;等等。这些方法的缺点是要自己写循环。有了assign方法后,事情就容易多了。

const auto s = std::string("30\v 43\n 50\t 64 71");std::vector<std::string> vs;auto ss1 = std::istrstream(s.data(), s.size());vs.assign(std::istream_iterator<std::string>(ss1),           std::istream_iterator<std::string>());std::vector<int> vi;auto ss2 = std::istrstream(s.data(), s.size());vi.assign(std::istream_iterator<int>(ss2), std::istream_iterator<int>());// file input.txt has the same content as $sstd::forward_list<int> fli1;fli1.assign(std::istream_iterator<int>(std::ifstream("input.txt")),            std::istream_iterator<int>());// another way: use constructor, be care of syntax! more parenthesis required!std::forward_list<int> fli2((std::istream_iterator<int>(std::ifstream("input.txt"))),                             std::istream_iterator<int>());
注意最后一句中,构造函数的第1个参数必须加上额外的一对圆括号,否则fli2就不是一个变量,而是一个函数声明了。比如int f(char());表示的是函数声明,其函数名为f,参数有1个,参数类型为char(*)()即函数指针,返回类型为int;而int g((char()))则表示定义一个int类型的变量g,其初始值为char()即0。

以上例子是字符串的不同split方法(按字符串split,按转换值split),但不如s.split(vs, isspace)直观。也许你要问了,如果输入是逗号分隔的内容即CSV格式,那怎么拆分呢?亲,自己写一个有点复杂哦,还是动用强大的boost::algorithm::split 吧!如果您想不开,或者感觉生活无味,也可以用 boost::spirit 这把牛刀试试!

8. 字符串的split和join

学过PHP的人都知道有一组函数叫做explode/split和implode函数,比如将vector<string>拼接成CSV格式的字符串。这里就不费口舌了,想偷懒就用boost的split和join函数吧!

9. 文件后缀名提取

这实在是太简单了,甚至都可以不用C++了,直接上代码吧!

const auto s = std::string("readme.txt");const auto extension = strrchr(s.c_str(), '.');
这里输出为".txt";当没有后缀名的时候,extension为nullptr,这样检查文件是否有后缀名就容易了。

10. 小心掉进erase的陷阱

几乎所有标准库中的序列容器和关联容器都有erase方法。但一些容器在erase掉元素时,会使相关迭代器失效。C++11对erase方法进行了改进,使erase能返回后续的迭代器。

11. 快速输出容器中的内容

如果有一个vector<int>,如何用简单快捷的方法打印出其内容到屏幕上呢?

const auto vi = std::vector<int>{11, 22, 33, 44, 55};copy(vi.begin(), vi.end(), std::ostream_iterator<int>(std::cout, "\n"));


未完待续



原创粉丝点击