原题
要求:设计一个保留字的统计程序
建立保留字文件;
从源数据文件(C或C++语言程序)中,读取字符或字符串,与保留字文件中的保留字进行匹配比较,并统计计数。输出两张表文件:保留字计数,扫描程序的次数,非保留字计数。
想法
我先说说我是第一天是怎么弄的,第一天。。。什么也没写出来,我连 保留字 是什么都不知道,后来百度后才知道 保留字就是c/c++关键字比如 int short while for,然后再看看题目,要求已经很明确了,建一个保留字文件以及一个c/cpp的源代码(任意的),通过程序筛选出保留字与非保留字,并且计数,最后分别用两个文件输出。所以,这个程序可以分成3部分:
- 文件读取
- 筛选以及计数
- 文件输出
先说文件输入输出,这个我是有点头疼的,之前c里面是用fread,fwrite两个函数,但这里显然不合适,你无法预测文档的大小。无奈,拿起上学期的c++书,翻到了文件输入输出流,书上的太简略,没有合适的代码,于是就先放在了这里。再来想一想第二个步骤,第二个步骤也是最难的部分,首先看筛选,如何将保留字与非保留字从源码里区分开来,和周围同学讨论后,他们是这样想的:通过文件操作将源码读到一个大的字符数组里,然后再把保留表再读到一个字符数组里,然后通过模式匹配(比如KMP)完成计数。可是这只是保留字啊,剩下的非保留字怎么办呢?
继续讨论后决定通过字符串分割,将源码按 空格 分割下来,这样除保留字以外的所有字符都是非保留字。可是我又有了疑问:如何保证分割的精确性呢,要知道源码里还有大量的 括号,引号……
而且,我觉得这个肯定不是上佳的方案,先不考虑可行性,这个算法的时间成本太高了,保留字差不多快50个,光统计这个就得把源码扫50遍???
所以,要想尽量时间成本的话,就得边读边统计。
不过,关于计数我倒有个想法,我想用stl里的map,据说map内部里是由 红黑树维护的(一种非严格意义上的平衡二叉树),用了map就非常方便,这样就可以直接将 字符串映射成 整型来计数了。
最后来重新总结一下需要解决的问题:
- 找到合适的方式使得将源码边读边计数
- 确保分割的精确性
- 文件输出
好了,第一天就这样结束了,虽然代码一行没写,但是有了目标。
下面简单介绍一下map。
使用map首先要加头文件
#include <map>
map就是从键(key)到值(value)的映射。因为重载了 []运算符,map像数组的“高级版”。例如我们可以用一个 map< string,int>month_name来表示“月份名字到月份编号”的映射,然后用 month_name[“July”]=7这样的方式来赋值。
本程序的具体操作下面再讲。
第二天,是取得重大突破的一天,因为我想到了以前做的一道Acm竞赛题,完整题目如下:安迪的第一个字典 UVa 10815大意就是输入一个文本,找出所有不同的单词,按字典序从小到大输出,单词不区分大小写。
代码如下:
#include <iostream>
#include <cstdio>
#include <string>
#include <set>
#include <sstream>
using namespace std;
set<string> dict;
int main(){
// freopen("input.txt","r",stdin);
string s,buf;
while(cin >> s){
for(int i = 0; i < s.length(); i++){
if(isalpha(s[i])) s[i] = tolower(s[i]);
else s[i] = ' ';
}
//注意下面两行
stringstream ss(s);
while(ss >> buf) dict.insert(buf);
}
for(set<string>::iterator it = dict.begin(); it != dict.end(); it++){
cout << *it <<"\n";
}
return 0;
}
其中用到了 stringstream(需要头文件 #include< sstream>),这个stringstream是字符串输入输出流,我先给这个流取了个名字叫 ss然后用这个流读一篇文章,这个流式读取有个好处,就是自动按空格给你读,也就是说能以空格来一个一个的读单词。
这样的话,我联想到本次任务, 我可以先用文件流读取源码,然后再将所有非字母以及‘_’(保留字仅有的类型)用空格覆盖,再用这个字符串流读取这样,就实现了边读边筛选的功能了。
而且,切割相对精确。想到这里,我的思路已经很明确了,再去百度了一下文件流的操作,算法就出来了。
算法
用两个map< string,int>,分别存储保留字与非保留字。
将保留字的表保存至一个txt文档,再保存一个源代码文档,用文件流先读保留字文档,读一个string,就放到第一个map里,并且赋值为0,然后再用文件输入流读源码,每读一次将其他干扰字符用空格覆盖,再用字符串流再读一次,若在保留的map里找到了匹配的,该字符串对应值就自加一次,若没有找到,再放到非保留字的map里匹配,若匹配成功,该字符串对应的值自加一次,否则将该字符串当作新的键值加入到非保留字的map里并赋值为0。
代码程序
#include "stdafx.h"
#include <iostream>
#include <map>
#include <string>
#include <fstream>
#include <sstream>
using namespace std;
map<string, int> reserved_word_dict; //用map保存保留字和非保留字最终统计结果
map<string, int> unreserved_word_dict;
//将数字转化成字符
string ChangeIntIntoString(int num) {
if (num == 0) return "0";
string str;
while (num != 0) {
int t = num % 10;
str += (t + '0');
num /= 10;
}
for (int i = 0; i < str.length() / 2; i++)
{
char c = str[i];
str[i] = str[str.length() - i - 1];
str[str.length() - i - 1] = c;
}
return str;
}
int main(){
string reserved_word;
ifstream Reserved_word_dict_file("保留字表.txt"); //文件流读入保留字表并记录到map中
while (Reserved_word_dict_file >> reserved_word) {
reserved_word_dict[reserved_word] = 0; //读一个保留字就赋值为0
}
Reserved_word_dict_file.close();
ifstream source_code("测试源程序代码.cpp");
string temp, buf;
while (source_code >> temp) {
int i;
for ( i = 0; temp[i] != '\0'; i++)
//将干扰的字符全变为空格
if (temp[i] == '/' && temp[i + 1] == '/') {
for (; temp[i] != '\0'; i++)
temp[i] = ' ';
break;
}
else if (temp[i] != '_' && !isalpha(temp[i]))
temp[i] = ' ';
stringstream ss(temp); //字符串流读写
while (ss >> buf) {
cout << buf << endl;
if (reserved_word_dict.count(buf)) reserved_word_dict[buf]++;
else {
//非保留字的计数
if (!unreserved_word_dict.count(buf)) unreserved_word_dict[buf] = 0;
unreserved_word_dict[buf]++;
}
}
}
source_code.close();
//文件写操作
ofstream reserved_word_calculation("保留字统计表.txt");
//遍历map
for (map<string, int>::reverse_iterator it = reserved_word_dict.rbegin(); it != reserved_word_dict.rend(); it++) {
string t, p;
t = it->first;
t += ':';
p = ChangeIntIntoString(it->second);
t += p;
reserved_word_calculation << t << endl;
}
reserved_word_calculation.close();
ofstream unreserved_word_calculation("非保留字统计表.txt");
for (map<string, int>::reverse_iterator it = unreserved_word_dict.rbegin(); it != unreserved_word_dict.rend(); it++) {
string t, p;
t = it->first;
t += ':';
p = ChangeIntIntoString(it->second);
t += p;
unreserved_word_calculation << t << endl;
}
unreserved_word_calculation.close();
return 0;
}
补充说明
string为stl库里的一个类,非常方便,需要头文件 #include< string>,string类里重载了‘+’,可以实现字符串的拼接,同时也重载了‘[]’,可以以下标访问,string里的length()用来求字符串的长度。
文件输入输出的流文件需要加头文件 #include< fstream>,具体使用时要注意输入流类型是 ifstream,输出流类型是 ofstream,在这里ifstream/ostream等同于 int,定义了变量名后可以用 “变量名>>”这种方式读文件,写文件也是同样的道理,如:
string s; ifstream myfile("input.cpp"); while(myfile >> s){……}
我在map里用了count()函数,这个函数可以用来找键(key),如果找到了就返回1,否则返回0。
另外看一下这段代码:
for (map< string, int>::reverse_iterator it = unreserved_word_dict.rbegin(); it != unreserved_word_dict.rend(); it++)
这里的 iterator是迭代器,这句话的意思就是遍历map里的值,如果取key值的话就用 it->first 取value值的话就用 it->second,若要输出要在前面加上“*”。
好了,这就是这个程序的设计过程了,如果大家有意见或者问题欢迎留言。