【阅读提示】通过本文你将实际了解如何用gcc/g++编译多个c/cpp文件;如何实现将一行字符串分隔成单个单词的split函数;另外还将看到一些常见的错误处理办法以及如何将一个程序分成几个cpp文件的。
本文的主要目的是实现将一行字符串分隔成单个单词的split函数,就如我们熟知的java中的split一样。
好了,本文就来带大家实现一个自己写的c++的split函数,不过功能只能以空白符划分单词哦,其他功能需要自己再进行改写^_^。
在开始之前,介绍一下string,一般来说,我们可以把string当作一个特殊的容器,不过它包含的只是字符;它也支持一些容器操作,不过不是全部。它支持的操作包括索引,而且字符串类型提供一个迭代器,类似于vector的类型的迭代器。因此,很多适用于vector的处理办法都适用于字符串。
那么我们为什么要写这样一个split函数呢?因为我们希望能把一行输入分成单个单词,这些单词通过空白符来分隔(如空格,制表符,退格符,或者是行终止符)。这种操作很有用,所以我想单独把它写成一个函数,因此,我就需要把这个函数独立出来成为一个单独的文件,而不适合像以往一样跟main函数混在一起^_^。
好了,我们知道了我们的目标了,那就来看看如何具体实现这个功能。
首先,我们考虑一下这个函数的输入和输出,这样好判断使用什么样的数据结构来存相关的数据。 既然我们要处理一行字符串输入,那么输入肯定一系列的字符串(为了简化就定位一行由若干个单词组成的字符串好了),那么输出呢,自然就是若干个单独单词了。好了,想想具体用什么数据结构来存这个输入输出了,至于输入嘛,都说了是字符串,那就用字符串来存OK了,输出呢?这个本文采用vector容器来存。
这样一来整个函数的样子应该就设计好了,应该是这样的吧:
vector<string> split (const string& s);
这个样子的函数就能满足我们上面的分析,输入在这个函数中就是其形式参数表示的部分,当然调用函数后,其各个单独的单词就存在vector中了,也就是我们返回值表示的部分。
另外,为了理解这个函数,需要知道字符串支持索引操作的方式与vector相同。比如,如果s是一个字符串,并且包含至少一个字符,那么s中的第一个字符就是s[0],最后一个字符就是s[s.size()-1]。
好了,现在我们来考虑函数里面的实现,我们如何来判断一个单词的开始和结束呢?解决了这个问题,这个函数就好办了。在本文中我们采用两个索引i和j来对单词进行划分。i和j将对字符串中的每个单词依次划界。具体思路是通过
计算i和j的值来对每个单词进行定位,每个单词都是区间[ i, j )之间的字符。如图
每当我们计算出这两个索引,我们就用它们划定的界限中的所有字符来创建一个新的字符串,然后把这个字符串复制到vector中。所有字符都处理完后,就把这个vector返回给调用程序。
下面是一个单独的split.cpp文件的源代码:
#include <vector>
#include <cctype>
#include <string>
using std::vector;
using std::string;
vector<string> split (const string& s)
{
vector<string> ret;
typedef string::size_type string_size;
string_size i = 0;
while(i != s.size())
{
while(i!=s.size() && isspace(s[i])) //此处是跳过空白字符
i++;
string_size j = i;
while(j!=s.size() && !isspace(s[j])) //此处就是从非空白符开始到遇到
j++; //空白符结束,即[ i, j )之间的单词
//如果是非空字符就加入到vector中去
if(i!=j)
{
ret.push_back(s.substr(i, j-i));
i=j;
}
}
return ret;
}
这样split函数就完成了,可是没有main函数没法运行测试啊,那我们再写一个简单的测试程序来进行测试
这个程序位于文件main.cpp中
其程序代码如下:
#include <iostream>
#include "split.h"
#include <string>
#include <vector>
using std::cout;
using std::cin;
using std::string;
using std::endl;
using std::vector;
int main(void)
{
string s = "please enter strings (a line)";
cout << endl;
cout<< "当前句子是:" << endl;
cout << s << endl;
cout << "......" <<endl;
cout << "processing...."<< endl;
cout << endl;
vector<string> vs;
vs = split(s);
vector<string>::iterator iter = vs.begin();
while(iter != vs.end())
{
cout<< (*iter) << endl;
iter++;
}
return 0;
}
从上面的函数中可以看出main函数调用了split函数,可是他们不在一个文件中,怎么办呢?
于是我们为split函数写了一个头文件,位于split.h中,在上面的main函数中
#include "split.h"
包含了该头文件
split.h文件的代码如下:
#ifndef GUARD_split_h
#define GUARD_split_h
#include <vector>
#include <string>
//注意在这里的函数声明若不加“std::“将会出现”'vector' does not name a type“错误
//string前面不加”std::“会出现”error: 'string' was not declared in this scope“错误
std::vector<std::string> split(const std::string&);
#endif
好了,这个程序的三个文件都有了,现在可谓是万事俱备,只欠东风啊。可是我们怎么来编译运行这三个文件呢,具体关于这部分的介绍就请看文章: windows命令行(或linux)下用gcc/g++编译多个c/cpp文件
在这里我们就不分别编译运行了,直接一次搞定,在windows的dos命令行下面切换到程序所在的目录,输入下面命令:
g++ split.cpp main.cpp
即可,发现文件下多了一个a.exe的文件。
然后我们继续在命令行下输入a即可看到下面的结果
经测试我们的函数满足我们的要求,完工了 \(≧▽≦)/~啦啦啦
别看这写得这么顺利,在实际我做的过程中可遇到了好几个错误呢,在这里补充在下面
【常见错误处理】
1. 错误:undefined reference to `split(std::string const&)'
collect2.exe: error: ld returned 1 exit status
这个错误出现的原因我在编译运行的时候输入的命令是这样的,g++ main.cpp,是由于没有把split.cpp文件一起编译造成的;
更多关于这个错误的总结,请参考:
undefined reference to error解决方法
错误截图:
2.错误:'vector' does not name a type
这个错误由于在vector前面木有加std::,这个在上面的split.h里面有提到
3.错误:'string' was not declared in this scope
这错误也是由于在vector前面木有加std::,这个也在上面的split.h里面有提到
PS:关于如何将一个大的程序分解的详细讲解可参考《accelerated C++》第四章