C++高级编程:涵盖文件和流、异常处理等的小程序开发历程与解决方法

2024-08-04
来源:网络整理

前言

一周过去了,这篇博客比我承诺的每周发一篇的博客晚了一天,因为我记错了时间,哈哈哈哈,我以为我是上周周三发的博客。这篇博客讲的是C++高级编程,主要包括文件和流,异常处理,多线程,模板和STL。这周我学完这些内容后,做了一个涵盖这些知识的小程序,小程序涵盖了这些知识里比较经典的程序。所以在这篇博客里,我主要记录开发这个小程序的过程,遇到的困难,以及我的解决办法。

开始之前准备好项目设计文档

在设计程序之前,必须完成相应的设计文档。设计文档相当于一份蓝图,决定了程序的大致方向。设计文档主要包括设计需求、功能分析、模块图、流程图、各种API、函数参数和返回值、数据库设计等。我正在做的项目是一个涉及高级C++编程知识的小程序。下图是我做项目前的设计文档截图:

我把这个项目设计成模块式的,每个模块都有对应的知识内容,模块及对应内容如下:

异常处理

模板

多线程

STL

流程图

根据模块及设计文档绘制相应的流程图:

思维导图

思维导图主要包括各个模块功能的详细解释,以及各个模块的功能名称,思维导图如下:

导航栏详细设计

为了方便程序的运行和管理,我用–case和()循环做了一个小的菜单导航栏,用户输入相应的数字就可以运行相应的功能,非常方便。在程序中我设置了二级菜单栏,从二级菜单栏返回上一级菜单时遇到了困难,试了很多方法,后来发现用goto循环返回上一级菜单栏是个巧妙的办法。这里给出二级菜单代码,简洁易懂,很幼稚,看到后摇头。哈哈哈哈:

#include #include #include #include using namespace std; int main() { loop: while (1) { cout << "1.文件和流" << endl; cout << "2.异常处理" << endl; cout << "3.模板" << endl; cout << "4.多线程" << endl; cout << "5.STL " << endl; cout << "0.退出" << endl; int a; cin >> a; switch (a) { case 1: system("cls"); while (1) { cout << "1.流成员函数举例" << endl; cout << "2.文本文件读写" << endl; cout << "3.二进制文件读写" << endl; cout << "0.返回上一级" << endl; int b1; cin >> b1; switch (b1) { case 1: system("cls"); cout << "流成员函数举例" << endl; break; case 2: system("cls"); cout << "文本文件读写" << endl; break; case 3: system("cls"); cout << "二进制文件读写" << endl; break; case 0: goto loop; } } break; case 2: system("cls"); while (1) { cout << "1.多个catch块的异常处理" << endl; cout << "2.求三角形面积的异常处理" << endl; cout << "3.定义新的异常" << endl; cout << "0.返回上一级" << endl; int b2; cin >> b2; switch (b2) { case 1: system("cls"); cout << "多个catch块的异常处理" << endl; break; case 2: system("cls"); cout << "求三角形面积的异常处理" << endl; break; case 3: system("cls"); cout << "定义新的异常" << endl; break; case 0: goto loop; } } break; case 3: system("cls"); while (1) { cout << "1.在类模板外定义成员函数" << endl; cout << "2.函数模板和非函数模板的重载" << endl; cout << "0.返回上一级" << endl; int b3; cin >> b3; switch (b3) { case 1: system("cls"); cout << "在类模板外定义成员函数" << endl; break; case 2: system("cls"); cout << ".函数模板和非函数模板的重载" << endl; break; case 0: goto loop; } } break; case 4: system("cls"); while (1) { cout << "1.创建线程" << endl; cout << "2.互斥量" << endl; cout << "3.生产者消费者模型" << endl; cout << "0.返回上一级" << endl; int b4; cin >> b4; switch (b4) { case 1: system("cls"); cout << ".创建线程" << endl; break; case 2: system("cls"); cout << "互斥量" << endl; break; case 3: system("cls"); cout << ".生产者消费者模型" << endl; break; case 0: goto loop; } } break; case 5: system("cls"); while (1) { cout << "1.vector的插入和删除" << endl; cout << "2.使用栈进行进制转换" << endl; cout << "3.使用deque产生随机数" << endl; cout << "4.set关联式容器的使用" << endl; cout << "0.返回上一级" << endl; int b5; cin >> b5; switch (b5) { case 1: system("cls"); cout << "vector的插入和删除" << endl; break; case 2: system("cls"); cout << "使用栈进行进制转换" << endl; break; case 3: system("cls"); cout << "使用deque产生随机数" << endl; break; case 4: system("cls"); cout << "set关联式容器的使用" << endl; break; case 0: goto loop; } } break; case 0: exit(1); } } return 0; }

这是运行后的截图:

二级接口:

goto循环一般不推荐使用,goto循环功能太强大,会破坏程序的模块化,因为现在的程序都是模块化编程,所以以后大家还是慎用goto循环吧,当然在我的程序里是没问题的,我的程序代码量不大,用goto循环也无妨。

文件和流

在写完文件和流模块内容后,老师觉得我写的太复杂了,建议我把各种文件操作封装成一个类,比如文本文件的读写、二进制文件的读写、文件的删除和重命名等。今天经过一上午的努力,终于把文件相关的操作封装成一个“类”,文件的读写、删除、重命名都实现了,用户可以自定义相关操作,逻辑比较完整。

#include #include #include #include using namespace std; class CFile { public: CFile(); // 类的构造函数 ~CFile(); // 类的析构函数 void tFout();//文本文件写入 void tFin();//文本文件读出 void bFout();//二进制文件写入 void bFin();//二进制文件读出 void Remove();//删除文件 void Rename();//重命名文件 }; CFile::CFile() // 类的构造函数 { } CFile::~CFile() {} // 类的析构函数 void CFile::tFout() { string s,str1; cout << "以文本文件写入" << endl; cout << "请输入要写入的文件名:"; cin >> s; ofstream fout(s, ios::out);//定义输出文件流对象fout,打开输出文件f2.dat if (!fout) { cout << "Cannot open output file.\n"; exit(1); } cout << "要写入的信息:"; cin >> str1; fout << str1 << endl; fout.close(); } void CFile::tFin() { string s1, str2; cout << "以文本文件读出" << endl; cout << "请输入要读出的文件名:"; cin >> s1; ifstream fin(s1, ios::in); if (!fin) { cout << "Cannot open input file.\n"; exit(1); } char str[50]; while (fin) { fin.getline(str, 50); cout << str << endl; } fin.close(); } void CFile::bFout() { string s, str1; cout << "以二进制文件写入" << endl; cout << "请输入要写入的文件名:"; cin >> s; ofstream outf(s, ios::binary); if (!outf) { cout << "Cannot open output file\n,"; exit(1); } cout << "要写入的信息:"; cin >> str1; for (int i = 0; i <str1.length(); i++) { outf.put(str1[i]); } outf.close(); } void CFile::bFin() { string s1; cout << "以二进制文件读出" << endl; cout << "请输入要读出的文件名:"; cin >> s1; ifstream inf(s1, ios::binary); if (!inf) { cout << "Cannot open input file\n,"; exit(1); } char ch; while (inf.get(ch)) cout << ch; cout << endl; inf.close(); } void CFile::Remove() { char FileName[256] = { 0 }; cout << "请输入要删除的文件:"; cin>> FileName; if (0 == remove(FileName)) { cout << "删除成功" << endl; } /*cout << "请输入删除的文件名:"; string path; cin >> path; remove(path);*/ } void CFile::Rename() { char source[256];//文件路径 char newname[256]; cout << "请输入要重命名的文件路径:" << endl; cin >> source; cout << "请输入文件的新名称:" << endl; cin >> newname; if (!_access(source, 0))//如果文件存在: { if (!rename(source, newname))//改名成功 { cout << source << " 成功重命名为: " << newname << endl; } else//无法重命名:文件打开或无权限执行重命名 { cout << "文件无法重命名(可能原因如下):" << endl; cout << "\t" << "1. " << newname << " 已存在" << endl << "\t" << "2. " << newname << " 正在使用,未关闭." << endl << "\t" << "3. " << "你没有权限重命名此文件." << endl; } } else//文件不存在 { cout << source << " 不存在,无法重命名." << endl; } cin.get(); } int main() { CFile file; while (1) { cout << "1.写入文本文件" << endl; cout << "2.读出文本文件" << endl; cout << "3.写入二进制文件" << endl; cout << "4.读出文本文件" << endl; cout << "5.删除文件" << endl; cout << "6.重命名文件" << endl; cout << "0.退出" << endl; int a; cin >> a; switch (a) { case 1: system("cls"); file.tFout(); break; case 2: system("cls"); file.tFin(); break; case 3: system("cls"); file.bFout(); break; case 4: system("cls"); file.bFin(); break; case 5: system("cls"); file.Remove(); break; case 6: system("cls"); file.Rename(); break; case 0: exit(1); } } return 0; }

在设计这部分程序的时候,文件读写没问题,但卡在文件删除和重命名两个函数上。在学习这部分的时候,我主要关注文件读写,对文件删除和重命名感到很困惑,对相关函数完全没有概念。后来上网了解了()函数和()函数,两个函数的函数原型如下:

了解了相关功能之后,设计相关内容就很容易了。

异常处理

异常处理是一套处理预期的运行时错误的实现机制。抛出异常使用语句,检查和捕获异常使用try语句和语句。在异常处理部分,最经典的就是求三角形面积的异常处理。当用户输入三角形的三条边时,程序首先要检查输入的数据是否为负数,然后检查三角形两边之和是否大于第三边,当输入的数据异常时,要输出相应的异常。注意检查的顺序不能乱,否则会出现逻辑错误。我这里犯了一个错误,当我输入负数时,程序总是抛出两边之和小于第三边的异常。相关代码如下:

#include using namespace std; //此程序是为了处理求三角形面积时出现的异常。 //异常包括边不能为负和两边之和大于第三边。处理异常时应先处理边不能为负然后再处理两边之和小于第三边的异常。 double triarea(int a, int b, int c) { double s = (a + b + c) / 2; if (a + b <= c || a + c <= b || b + c <= a) { throw 1; } return sqrt(s * (s - a) * (s - b)); } int main() { int x; int y; int z; double area; try { cin >> x >> y >> z; if (x <= 0 || y <= 0 || z <= 0) { throw 1.0; } if (x > 0 && y > 0 && z > 0) { area = triarea(x, y, z); } cout << "三角形的面积为: " << area << endl; } catch(int) { cout << "两边之和小于第三边,不能构成三角形" << endl; } catch (double) { cout << "边不能为负" << endl; } return 0; }

模板

模板是实现代码复用机制的重要工具,模板主要分为函数模板和类模板,学习这部分只需要记住函数模板和类模板的声明,以及类模板外定义成员函数的一般形式即可。关于函数模板的使用,可以通过函数模板和非函数模板的重载来加深理解:

/* 开发者:Wang 功能:函数模板和非函数模板的重载 版本:V1.0.0 时间:10:12 2022/7/15 */ #include using namespace std; template <typename T> T Min(T a, T b) { cout << "调用模板函数: "; return (a < b) ? a : b; } int Min(int a, int b) { cout << "调用非模板函数:"; return (a < b) ? a : b; } int main() { int x = 1, y = 2; cout << "最小的数为:" << Min(x, y) << endl; double x1 = 1.0, y1 = 2.0; cout << "最小的数为:" << Min(x1, y1) << endl; return 0; }

多线程

小程序开发模式_开发类小程序在那设置_程序开发模式

多线程是C++高级编程中比较难学、理解的一个知识点,目前我还处于学习多线程的摸索阶段,因为多线程涉及的知识面比较广,而且跟其他知识结合紧密,比如线程安全、C++下的多线程等,以后会涉及到,这里就简单说一下多线程。

首先,创建线程如下:

S , //线程安全相关属性,通常设置为NULL

, //新线程的初始化堆栈大小可以设置为0

NE, //线程执行的回调函数,也叫线程函数

,//传入线程函数的参数,不需要参数时为NULL

, // 控制线程创建的标志

//输出参数,用于获取线程ID,如果为NULL则不返回线程ID);

阐明:

互斥锁

确保共享数据操作的完整性

S , //线程安全相关属性,通常设置为NULL

BOOL, //创建时当前线程是否拥有所有权

//名字);

3.生产者消费者模型

生产者消费者模型采用互斥锁实现,具体代码如下:

/* 生产者消费者问题 */ #include #include #include #include #include using namespace std; deque<int> q; mutex mu; condition_variable cond; int c = 0;//缓冲区的产品个数 void producer() { int data1; while (1)//通过外层循环,保证生成永不停止 { if (c < 3)//限流 { data1 = rand(); unique_lock<mutex>locker(mu);//锁 q.push_front(data1); cout << "存了" << data1 << endl; cond.notify_one();//通知取 ++c; } Sleep(500); } } void consumer() { int data2;//data用来覆盖存放取的数据 while (1) { { unique_lock<mutex> locker(mu); while (q.empty()) cond.wait(locker); data2 = q.back(); q.pop_back(); cout << "取了" << data2 << endl; --c; } Sleep(1500); } } int main() { thread t1(producer); thread t2(consumer); t1.join(); t2.join(); return 0; }

具体知识可以转移到:

多线程 1

博客园

STL

标准模板库提供了很多组件可供使用,组件分为算法、容器、函数、迭代器等,常见的容器有list、set、map等。

容器提供了()插入函数、()删除函数、()向末尾添加函数、()删除当前最后一个元素。

以下分别是使用 ()、() 和 () 函数读取容器的方法:

/* 开发者:Wang 功能:vector容器 版本:V1.0.0 时间:15:07 2022/7/17 */ #include #include using namespace std; int main() { unsigned i; vector <int> number; number.insert(number.begin(), 90); number.insert(number.begin(), 80); number.insert(number.end(), 20); number.insert(number.end(), 30); for (i = 0; i < number.size(); i++) { cout << number[i] << endl; } number.erase(number.begin()); number.erase(number.end()-1);//不能为number.erase(number.end());会超出范围 for (i = 0; i < number.size(); i++) { cout << number[i] << endl; } return 0; }

/* 开发者:Wang 功能:vector容器中iterator用法 版本:V1.0.0 时间:15:25 2022/7/17 */ #include #include #include #include using namespace std; int main() { vector <int> intVector; for (int i = 0; i < 10; i++) { intVector.push_back(i + 10); } vector <int>::iterator theIterator = intVector.begin(); intVector.insert(theIterator, 4, 5); for (theIterator = intVector.begin(); theIterator != intVector.end(); ++theIterator) cout << *theIterator ; return 0; }

集合容器可用于存储相同数据类型的数据,集合中的每个元素都是唯一的,并且自动排序。

/* 开发者:Wang 功能:set关联式容器的使用 版本:V1.0.0 时间:11:35 2022/7/19 */ #include #include #include using namespace std; void setTest() { set<string> s; s.insert("linxiaocha"); s.insert("chenweixing"); s.insert("gaoying"); s.insert("chenweixing"); set<string>::iterator myit; for (myit = s.begin(); myit != s.end(); ++myit) { cout << *myit << endl; } cout << endl; }

程序运行结果为:

总结

由于涉及的知识面比较广,所以设计这个小程序还是有点难度的。不过程序完成的那一刻,还是挺有成就感的。相关设计文档和代码会在文章最后附上链接。谢谢阅读,下周见!

关联:

提取码:or01

分享