软件体系结构作业:C++实现KWIC
KWIC(Keywords in context)上下文关键字。按照我的理解就是,给出一些文本即字符串:
- 首先每行中的字符串按照第一个字母的ASCII大小升序排序
- 每行排序好之后,文本按照每行中第一个单词的首字母的ASCII大小进行每行的升序排序
输入和输出,既可以在终端(cmd命令行)中实现,也可以从文件中读取文本,转换后输出到文件中。
注释写的很细,因为老师要求的。。。。。看下来应该没有障碍,
主程序子程序实现KWIC
主程序子程序方法,是按照具体实现的功能对构件进行组装,构件以子程序/过程/函数的形式出现。我使用的是子程序+函数,也就是类+函数。每个实现功能的接口(子程序)必须清晰。如果是比较复杂的实现要求,那么首先要进行功能分解,将系统分为高内聚,低耦合的功能模块,进行适当修改后把功能分别用子程序实现。可以使用构造法和子类法实现。
源代码
#include <iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<fstream>
using namespace std;
//因为是子程序主程序实现,所以就不用非得放到类里面
//虽然这样不放到类里面有点乱,但是没办法,毕竟是水博客嘛哈哈哈哈哈哈
//这里的宏定义可以改成自己想创建或者想输出文件的地方
#define FROM "D:/DEV/in.txt"
#define TO "D:/DEV/out.txt"
//全局变量,储存读入和转换后输出的字符串数组
vector<vector<string> > inputarr;
//翻转每一行的字符串数组
bool static sortstring(const vector<string>& s1, const vector<string>& s2)
{
return s1[0][0] < s2[0][0];
}
//这个是测试用的
//bool static sortstr(const string& s1, const string& s2)
//{
// return s1[0] < s2[0];
//}
//反转每一行的字符串
void reverseword(vector<string>& s)
{
for (int i = 0, j = s.size() - 1; i < j; i++, j--)
{
swap(s[i], s[j]);
}
//测试代码
//sort(s.begin(), s.end(), sortstr);
//sort(s.begin(), s.end());
}
class Input
{
public:
int m, n;
void input(int m, int n)//设置输入m行每行n列的字符串二维数组
{//这个地方输入的时候不用非要到输出n个就换行,只要保证能输出m*n个字符串之后回车就可以
cout << "请输入字符串:" << endl;
for (int i = 0; i < m; i++)
{
vector<string> ans;
for (int j = 0; j < n; j++)
{
string s;
cin >> s;
ans.push_back(s);
}
inputarr.push_back(ans);
}
}
};
class shift
{
public:
//反转二维数组中每一行的字符串数组
void shiftstring()
{
for (int i = 0; i < inputarr.size(); i++)
{
reverseword(inputarr[i]);
}
}
};
class alphabetizer
{
public:
//在每行排好序之后,按照数组中每行的第一个单词的第一个字母给二维数组排序
void alphastring()
{
sort(inputarr.begin(), inputarr.end(), sortstring);
}
};
class output
{
public:
//将转换好的数组循环输出到命令行
void outputarr()
{
for (int i = 0; i < inputarr.size(); i++)
{
for (int j = 0; j < inputarr[i].size(); j++)
{
cout << inputarr[i][j] << " ";//此处要将输出每个单词后加入空行
}
cout << endl;//每行字符串输出后换行
}
}
};
//下面是读入写出到文件的
//trimstr是将从文件中读入的数据按照空格进行切割。切割后放入储存每行字符串的数组
void trimstr(string& s, vector<string>& target)
{
string res;
for (int i = 0; i < s.size(); i++)
{
if (s[i] != ' ')res += s[i];
else
{
target.push_back(res);
res.clear();
}
}
target.push_back(res);
}
//mergestr是用来将处理好的字符串转换成每行的形式,便于之后可以通过一次循环重定向到目标文件
void mergestr(string& s, vector<string>& target)
{
for (int i = 0; i < target.size(); i++)
{
s += target[i];
s += ' ';
}
s.pop_back();
}
//从文件中读入数组到二维数组中
void Readfile(vector < vector<string>>& ans)
{
ifstream Input;
Input.open(FROM);//打开已经宏定义的文件路路径
string s;
vector<string>res;
//储存文件中的字符串数组
if (Input.is_open())
{
while (getline(Input, s))
{
trimstr(s, res);//分割每行数组
ans.push_back(res);//分割好的数组放到整个储存文本的数组中
res.clear();
s = "";//清空s数据,重新记录,s.clear()也可以
}
}
//测试输出成功
/*for (auto a : ans)
{
for (auto ii : a)
{
cout << ii << " ";
}
cout << endl;
}*/
Input.close();
}
//将转换好的字符串重组后重定向到目标文件中
void Writefile(vector < vector<string>>& ans)
{
ofstream Myfile;
Myfile.open(TO);
string s;
vector<string>res;
//储存文件中的字符串数组
if (Myfile.is_open())
{
for (int i = 0; i < ans.size(); i++)
{
mergestr(s, ans[i]);
Myfile << s;
Myfile << "\n";
s.clear();
}
}
//测试输出成功
Myfile.close();
}
//实现文件层面的输出输出的KWIC
void testforfile()
{
Readfile(inputarr);
shift shiftt;
shiftt.shiftstring();
alphabetizer Alpha;
Alpha.alphastring();
Writefile(inputarr);
}
//实现命令行上的输入输出KWIC,从终端输入的字符串文本作文输入,经过转换之后输出到终端
void testforcommand()
{
int m, n;
cout << "请输入行数:";
cin >> m;
cout << "请输入每行字符串的个数:";
cin >> n;
Input num;
num.m = m;
num.n = n;
num.input(num.m, num.n);
cout << "您输入的字符串数组为:" << endl;
for (int i = 0; i < inputarr.size(); i++)
{
for (int j = 0; j < inputarr[i].size(); j++)
{
cout << inputarr[i][j] << " ";
}
cout << endl;
}
cout << "--------------------------------" << endl;
shift Shift;
Shift.shiftstring();
alphabetizer alpha;
alpha.alphastring();
output outputstring;
outputstring.outputarr();
}
int main(int argc, char** argv) {
testforcommand();
//testforfile();
return 0;
}
终端方法运行结果
管道过滤器实现KWIC
听到管道过滤器首先想到的就是Linux里面的管道pipe函数和“|”,犹记得当初学Linux的时候经常ps aux|grep pid.管道过滤器实现是把运行程序比作一个管道,管道的两个口相当于是输入输出。构件输入数据到管道,管道把输入的数据流进行处理,这里称为“过滤”。而后传输到管道出口。管道过滤器值得注意的是管道过滤器必须是实体,不能与其他过滤器共享数据。一个管道输出的正确性并不依赖于过滤器的多少以及计算顺序
源代码
#include <iostream>
using namespace std;
#include<vector>
#include<string>
#include<algorithm>
#include<fstream>
//这里的宏定义可以改成自己想创建或者想输出文件的地方
#define FROM "D:/DEV/in.txt"
#define TO "D:/DEV/out.txt"
//全局变量,储存读入和转换后输出的字符串数组
vector<vector<string> > inputarr;
//sortstring是反转矩阵中一行字符串的规则,回调函数
bool static sortstring(const vector<string>& s1, const vector<string>& s2)
{
return s1[0][0] < s2[0][0];
}
//这个是测试用的
//bool static sortstr(const string& s1, const string& s2)
//{
// return s1[0] < s2[0];
//}
class Input
{
public:
int m, n;
//trimstr是将从文件中读入的数据按照空格进行切割。切割后放入储存每行字符串的数组
void trimstr(string& s, vector<string>& target)
{
string res;
for (int i = 0; i < s.size(); i++)
{
if (s[i] != ' ')res += s[i];
else//遇到空格就将之前放好的字符串加入数组
{
target.push_back(res);
res.clear();
}
}
target.push_back(res);
}
//设置从命令行输入m行每行n列的字符串储存到二维数组
void input(int m, int n)
{
cout << "请输入" << m <<"行" << n <<"列的字符串"<< endl;//如果测试的时候这个乱码就用下面这一行
//cout << "start input " << m << "X" << n << "string array please" << endl;
for (int i = 0; i < m; i++)
{
vector<string> ans;
for (int j = 0; j < n; j++)
{
string s;
cin >> s;
ans.push_back(s);
}
inputarr.push_back(ans);
}
}
//从文件中读入数组到二维数组中
void Readfile(vector < vector<string>>& ans)
{
ifstream Input;
Input.open(FROM);//打开已经宏定义的文件路路径
string s;
vector<string>res;
//储存文件中的字符串数组
if (Input.is_open())
{
while (getline(Input, s))
{
trimstr(s, res);//分割每行数组
ans.push_back(res);//分割好的数组放到整个储存文本的数组中
res.clear();
s = "";//清空s数据,重新记录,s.clear()也可以
}
}
//测试输出成功
/*for (auto a : ans)
{
for (auto ii : a)
{
cout << ii << " ";
}
cout << endl;
}*/
Input.close();
}
};
class Output {
public:
//mergestr是用来将处理好的字符串转换成每行的形式,便于之后可以通过一次循环重定向到目标文件
void mergestr(string& s, vector<string>& target)
{
for (int i = 0; i < target.size(); i++)
{
s += target[i];
s += ' ';
}
s.pop_back();
}
//将转换好的数组循环输出到命令行
void outputarr()
{
cout << "转换后的数组为:" << endl;
for (int i = 0; i < inputarr.size(); i++)
{
for (int j = 0; j < inputarr[i].size(); j++)
{
cout << inputarr[i][j] << " ";//此处要将输出每个单词后加入空行
}
cout << endl;
}
}
//将转换好的字符串重组后重定向到目标文件中
void Writefile(vector < vector<string>>& ans)
{
ofstream Myfile;
Myfile.open(TO);
string s;
vector<string>res;
//储存文件中的字符串数组
if (Myfile.is_open())
{
for (int i = 0; i < ans.size(); i++)
{
mergestr(s, ans[i]);
Myfile << s;//每行重定向到文件
Myfile << "\n";//每行输出到文件之后要换行
s.clear();
}
}
//测试输出成功
Myfile.close();
}
};
class Move
{
public:
//反转每一行的字符串
void reverseword(vector<string>& s)
{
for (int i = 0, j = s.size() - 1; i < j; i++, j--)
{
swap(s[i], s[j]);
}
//测试代码
//sort(s.begin(), s.end(), sortstr);
//sort(s.begin(), s.end());
}
//反转二维数组中每一行的字符串数组
void move()
{
for (int i = 0; i < inputarr.size(); i++)
{
reverseword(inputarr[i]);
}
}
};
class Order
{
public:
//在每行排好序之后,按照数组中每行的第一个单词的第一个字母给二维数组排序
void order()
{
sort(inputarr.begin(), inputarr.end(), sortstring);
}
};
//控制输出端和输出端,实现KWIC从输入端文件到输出端文件,类似Linux中的pipe管道
class Pipe
{
public:
//实现命令行上的输入输出KWIC,从终端输入的字符串文本作文输入,经过转换之后输出到终端
void testforcommand()
{
int m, n;
cout << "请输入行数";
cin >> m;
cout << "请输入每行字符串的数量";
cin >> n;
Input num;
num.m = m;
num.n = n;
num.input(num.m, num.n);
//测试输入是否成功
/*cout << "您输入的字符串数组为:" << endl;
for (int i = 0; i < inputarr.size(); i++)
{
for (int j = 0; j < inputarr[i].size(); j++)
{
cout << inputarr[i][j] << " ";
}
cout << endl;
}
cout << "--------------------------------" << endl;*/
Move mo;
mo.move();
Order alpha;
alpha.order();
Output outputstring;
outputstring.outputarr();
}
//实现文件层面的输出输出的KWIC
void testforfile()
{
Input inn;
inn.Readfile(inputarr);
Move mo;
mo.move();
Order alpha;
alpha.order();
Output outputstring;
outputstring.outputarr();
Output outt;
outt.Writefile(inputarr);
}
};
int main(int argc, char** argv) {
Pipe pip;
//pip.testforcommand();
//pip.testforfile();
return 0;
}
终端方法运行结果
面向对象风格实现KWIC
抽象数据类型概念对软件系统有着重要作用,目前软件界已普遍转向使用面向对象系统。这种风格建立在数据抽象和面向对象的基础上,数据的表示方法和它们的相应操作封装在一个抽象数据类型或对象中。这种风格的构件是对象,或者说是抽象数据类型的实例。对象是一种被称作管理者的构件,因为它负责保持资源的完整性。对象是通过函数和过程的调用来交互的。
面向对象风格的优缺点:
- 因为对象对其它对象隐藏它的表示,所以可以改变一个对象的表示,而不影响其它的对
象 - 设计师可将一些数据存取操作的问题分解成一些交互的代理程序的集合
但是,面向对象的系统也存在着某些问题: - 为了使一个对象和另一个对象通过过程调用等进行交互,必须知道对象的标识。只要一个对象的标识改变了,就必须修改所有其他明确调用它的对象
- 必须修改所有显式调用它的其它对象,并消除由此带来的一些副作用。例如,如果A使用了对象B,C也使用了对象B,那么,C对B的使用所造成的对A的影响可能是料想不到的
因为不知道能不能用全局变量存储执行结果,在这里我是觉得不能使用全局变量的,所以在每个类中创建一个成员变量存储每个类成员函数执行之后的结果
源代码
#include <iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<fstream>
using namespace std;
//这里的宏定义可以改成自己想创建或者想输出文件的地方
#define FROM "D:/DEV/in.txt"
#define TO "D:/DEV/out.txt"
//这个是测试用的
//bool static sortstr(const string& s1, const string& s2)
//{
// return s1[0] < s2[0];
//}
class Input
{
public:
int m, n;
vector<vector<string> > inputarr;
void inputdata()//设置输入m行每行n列的字符串二维数组
{
cout << "请输入行数:";
cin >> m;
cout << "请输入每行字符串的个数:";
cin >> n;
//这个地方输入的时候不用非要到输出n个就换行,只要保证能输出m*n个字符串之后回车就可以
cout << "请输入字符串:" << endl;
for (int i = 0; i < m; i++)
{
vector<string> ans;
for (int j = 0; j < n; j++)
{
string s;
cin >> s;
ans.push_back(s);
}
inputarr.push_back(ans);
}
}
};
class Shift
{
public:
vector<vector<string> > inputarr;
Shift(vector<vector<string> > inputarr):inputarr(inputarr){}
Shift(){}
//翻转每一行的字符串数组
void reverseword(vector<string>& s)
{
for (int i = 0, j = s.size() - 1; i < j; i++, j--)
{
swap(s[i], s[j]);
}
//测试代码
//sort(s.begin(), s.end(), sortstr);
//sort(s.begin(), s.end());
}
//反转二维数组中每一行的字符串数组
void shiftstring()
{
for (int i = 0; i < inputarr.size(); i++)
{
reverseword(inputarr[i]);
}
}
};
class Alphabetizer
{
public:
vector<vector<string> > inputarr;
Alphabetizer(vector<vector<string> > inputarr) :inputarr(inputarr) {}
Alphabetizer(){}
//给二维数组进行行排序
bool static sortstring(const vector<string>& s1, const vector<string>& s2)
{
return s1[0][0] < s2[0][0];
}
//在每行排好序之后,按照数组中每行的第一个单词的第一个字母给二维数组排序
void alphastring()
{
sort(inputarr.begin(), inputarr.end(), sortstring);
}
};
class Output
{
public:
vector<vector<string> > inputarr;
Output(vector<vector<string> > inputarr) :inputarr(inputarr) {}
Output(){}
//将转换好的数组循环输出到命令行
void outputarr()
{
for (int i = 0; i < inputarr.size(); i++)
{
for (int j = 0; j < inputarr[i].size(); j++)
{
cout << inputarr[i][j] << " ";//此处要将输出每个单词后加入空行
}
cout << endl;//每行字符串输出后换行
}
}
};
//下面是读入写出到文件的
class FileInput
{
public:
vector<vector<string> > inputarr;
//trimstr是将从文件中读入的数据按照空格进行切割。切割后放入储存每行字符串的数组
void trimstr(string& s, vector<string>& target)
{
string res;
for (int i = 0; i < s.size(); i++)
{
if (s[i] != ' ')res += s[i];
else
{
target.push_back(res);
res.clear();
}
}
target.push_back(res);
}
//从文件中读入数组到二维数组中
void Readfile()
{
ifstream Input;
Input.open(FROM);//打开已经宏定义的文件路路径
string s;
vector<string>res;
//储存文件中的字符串数组
if (Input.is_open())
{
while (getline(Input, s))
{
trimstr(s, res);//分割每行数组
inputarr.push_back(res);//分割好的数组放到整个储存文本的数组中
res.clear();
s = "";//清空s数据,重新记录,s.clear()也可以
}
}
//测试输出成功
/*for (auto a : ans)
{
for (auto ii : a)
{
cout << ii << " ";
}
cout << endl;
}*/
Input.close();
}
};
class FileOutput
{
public:
vector<vector<string> > inputarr;
FileOutput(vector<vector<string> > inputarr):inputarr(inputarr){}
//mergestr是用来将处理好的字符串转换成每行的形式,便于之后可以通过一次循环重定向到目标文件
void mergestr(string& s, vector<string>& target)
{
for (int i = 0; i < target.size(); i++)
{
s += target[i];
s += ' ';
}
s.pop_back();
}
//将转换好的字符串重组后重定向到目标文件中
void Writefile()
{
ofstream Myfile;
Myfile.open(TO);
string s;
vector<string>res;
//储存文件中的字符串数组
if (Myfile.is_open())
{
for (int i = 0; i < inputarr.size(); i++)
{
mergestr(s, inputarr[i]);
Myfile << s;
Myfile << "\n";
s.clear();
}
}
//测试输出成功
Myfile.close();
}
};
//实现文件层面的输出输出的KWIC
void testforfile()
{
FileInput input;
input.Readfile();
Shift shift(input.inputarr);
shift.shiftstring();
Alphabetizer alpha(shift.inputarr);
alpha.alphastring();
FileOutput output(alpha.inputarr);
output.Writefile();
}
//实现命令行上的输入输出KWIC,从终端输入的字符串文本作文输入,经过转换之后输出到终端
void testforcommand()
{
/*int m, n;
cout << "请输入行数:";
cin >> m;
cout << "请输入每行字符串的个数:";
cin >> n;*/
Input inputstring;
inputstring.inputdata();
/*cout << "您输入的字符串数组为:" << endl;
for (int i = 0; i < num.inputarr.size(); i++)
{
for (int j = 0; j < num.inputarr[i].size(); j++)
{
cout << num.inputarr[i][j] << " ";
}
cout << endl;
}
cout << "--------------------------------" << endl;*/
Shift shift(inputstring.inputarr);
shift.shiftstring();
Alphabetizer alpha(shift.inputarr);
alpha.alphastring();
Output outputstring(alpha.inputarr);
outputstring.outputarr();
}
int main(int argc, char** argv) {
//testforcommand();
testforfile();
return 0;
}
运行结果
个人感想
实际上,不同结构实现的KWIC只是结构不同而已,核心代码没有变。老师给我们的这个案例只是让我们更加理解这些软件体系结构的。不过之前了解KWIC的时候发现KWIC有8种实现方式之多!另外网上基本上都是Java写的代码,虽然能看懂,但是有些费劲。用C++写的KWIC,我个人觉得也没有那么好,正好老师布置了这个作业,那正好水一下博客hhhhhhh
说实话我没感觉面向对象和主程序子程序有什么太大的区别,可能面向对象就是把操作/方法(子程序)封装成类了,所有的操作都是通过类对象的方法传递信息的。