前言

最近我们项目部的核心产品正在进行重构,然后又是年底了,除了开发工作之外项目并不紧急,加上加班时间混不够了....所以就忙里偷闲把整个项目的开发思路聊一下,以供参考。

鉴于接下来的一年我要操刀这个主框架的开发,本着精益求精的态度,加上之前维护前辈的产品代码确实给我这个刚毕业的社畜带来了不小的震撼,我决定在这个模块的开发中优化之前的开发模式,提升整个产品的健壮性和独立性。

开发一个大型软件最重要的问题有三个,一是如何保证每个模块开发的独立性 二是如何保证数据结构的一致性 三是如何保证程序的可维护性和健壮性。这几个文章的内容我会在几篇文章中分开聊聊我的做法,做个记录。

本篇文章我们暂时只谈开发准备,虽然不涉及核心问题,但我仍然认为准备阶段也是和开发、测试一样重要的阶段。和后者不同,准备阶段一旦做好了之后可以为后续的开发提供模板,可以大大减少在实际开发中走的弯路。

这一期简单聊聊开发准备,下一期浅谈怎么在开发中我们项目如何保证模块开发的独立性。

开发准备

在大学的软件工程这门课中我们可以知道,软件开发实际上占比时间最长的是前期策划和测试阶段。

实际上开发中这两个阶段占比是最长的也是最折磨的。但是在实际的开发流程中我们会发现:

在准备阶段做的事情越多,开发阶段受的折磨就越少;在测试阶段做的事情越多,在维护阶段受的折磨就越少。

在这里我就简单聊聊在我们当前这个大型软件进行开发之前做了哪些准备,给出一些能给的范例以供参考。

一、制定开发规范

我个人认为,在C++程序的联合开发中,除非完全不需要维护别人代码或产品(当然这个在实际情况下是不可能的),否则一份开发规范和定期的Code Review是必要的。一个好的规范是严肃且必要的,好的编码习惯是决定一个程序员产品健壮性和可读性的关键。

我在这里可以列出一份我给部门内部定义的简单开发规范:云网络智慧课堂-Qt程序代码开发规范,内容如下:

序言:

  1. 编程规范可以提升代码可读性,提高可维护性。
  2. 目录:
  3. 一、命名规范
  4. 二、内存管理规范
  5. 三、函数方法规范
  6. 四、控制语句规范
  7. 五、注释规范
  8. 六、排版规范
  9. 七、版本管理规范
  10. 八、界面编程
  11. 词义解释:强制,推荐,参考分别表示规范的三个等级。
  12. 一、命名规范:
  13. 【强制】1.类、函数、变量及参数采用[谷歌式命名约定](https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/naming)。
  14. 【强制】2.常量(包含对话框ID)命名所有字母大写。
  15. 【强制】3.接口类在前+字符(纯虚类)I。基类则在前+Base
  16. 【强制】4.函数命名规范:获取,查询用Get,设置用Set,增加用Add,插入用Insert,删除用Delete。保存用Save
  17. 【强制】5.槽函数的命名:每个函数前缀是slot_,如:slot_SendData();
  18. 【强制】6.信号的命名:每个函数前缀是sig_,如:sig_SendData();
  19. 【强制】7.如果是全局变量,请在前面加上this->以示区分。
  20. 二、内存管理规范
  21. 【强制】1.内存谁申请,谁释放
  22. 【强制】2.不允许使用CPP自带智能指针shared_ptrshare_ptrqobject搭配使用会出现一些意料之外的问题。
  23. 【强制】3.使用指针请一定要使用Qt自带的智能指针QPointerQSharedPointer,不允许出现裸露在外的普通指针。
  24. 【强制】4.new申请内存之后。使用try catch捕获申请内存是否成功。原因:new申请内存可能失败
  25. 【强制】5.变量(普通变量和指针)必须初始化。
  26. 【强制】6.使用指针前必须检查指针是否为空。
  27. 【强制】7.指针new后必须delete且将指针赋值为nullptr
  28. 【强制】8.函数中分配的内存,函数退出之前要释放。
  29. 【强制】9.多线程读写共用变量要加锁。
  30. 【推荐】10.对可能的跨线程信号槽函数需要在connect函数中加入Qt::QueuedConnection参数
  31. 【强制】11.程序内部的所有数据流动,除了自定义的类型,系统类型比如String,int等全部使用Qt内部类型QString,qint32等。
  32. 【推荐】12.在编写类的时候最好保留调用方参数,以方便使用Qt自带GC
  33. 三、函数方法规范
  34. 【强制】1.函数参数必须在使用前校验(建议放在函数第一行)。包括数据范围校验,数据越界校验,异常指针校验。
  35. 【推荐】2.增加函数错误处理流程,try catchasset
  36. 【参考】3.函数参数比较多时,应考虑用结构体代替
  37. 【推荐】4.函数体长度应在80行内,且保证函数功能的单一性。
  38. 【推荐】5.函数内代码层次应保持一致。
  39. 四、控制语句
  40. 【强制】1.尽量上的使用if else 语句,多采用卫语句。
  41. 【强制】2.不要在条件推断中运行其他复杂的语句。将复 杂逻辑推断的结果赋值给一个有意义的布尔变量名。以提高可读性。
  42. 五、注释规范
  43. 【强制】1.模块注释包含信息:作者,日期,功能,依赖模块,调用流程
  44. 【强制】2.类注释包含信息:作者,日期,功能,依赖类,调用流程
  45. 【强制】3.函数注释包含信息:作者,日期,功能,参数含义,返回值,其他。
  46. 【强制】4.变量注释:注解内容要清楚准确不能有歧义。
  47. 六、排版规范
  48. 【推荐】1.左大括号前不换行,左大括号后换行;右大括号前换行,右大括号后还有 else 等代码则不换行;表示终止右大括号后必须换行。
  49. 【推荐】 2.左括号和后一个字符之间不出现空格。相同,右括号和前一个字符之间也不出现空格。
  50. 【推荐】 3.if/for/while/switch/do 等保留字与左右括号之间都必须加空格
  51. 【推荐】4.不论什么运算符左右必须加一个空格。
  52. 【强制】 5.单行字符数限制不超过 120 个,超出须要换行,换行时遵循例如以下原则:
  53. 运算符与下文一起换行,方法调用的点符号与下文一起换行,在括号前不要换行。
  54. 【强制】6.使用空格进行对齐,禁止使用tab对齐。
  55. 七、版本管理规范
  56. 1.VXX.XX.XX.XXXXXX.XXXXXX使用四位数进行版本管理,1-2位为主版本号,3-4位为分支版本号,5-6为次版本号,7-10为修订号1,11-15
  57. 【强制】主版本号:从1开始,产品更新换代时+1。之后版本号清零。
  58. 【强制】分支版本号:从0开始,新建分支时+1,之后版本号清零。
  59. 【强制】次版本号:从0开始,新增功能时+1,之后版本号清零
  60. 【推荐】修订号1:年月日
  61. 【推荐】修订号2 小时分

注:实际开发规范和这篇文件中会有所出入,这篇文件会在实际的开发中动态修改,请以实际情况为准。

二、绘制功能流程图

这个功能流程图指的是当前开发的软件整体的功能,你也可以理解为从程序加载开始,到程序结束,中间可能会经过哪些事,可能会保存哪些数据,可能会调用哪些接口。需要提前把这些东西规划清楚。

这一步并不需要你精确到每一个方法或者属性,而只需要确定步骤内容即可。即你可以不需要知道每个类的内容,只需要知道每个类要做什么,能做什么,为什么要这么做即可。这么做的目的是为了给开发指明一条道路,接下来的开发就可以根据这个流程图从初始化开始一步步向下开发下去。也可以在绘制这个流程图的时候划分模块,进行分工,指定开发计划。

为了更加具象化这部分内容,我可以拿我之前绘制的功能流程图来作为参考

这部分绘图可读性不强,主要原因是为了打印好看,两张纸贴在一起可以展示给领导看哈哈。就意思意思就行。

这样一幅图就把整个框架内部提供的类、功能模块划分清楚了,并提供了一个大概的开发方向。后续的开发就按图索骥即可。这样不仅对流程更加清除,同时也能好地指定计划。至于每个模块类内提供的方法和属性是可以在开发中慢慢商榷的。

不足的是,我并不是软件工程科班出生的学生,对于UML图和功能流程图的绘制并不清楚,只能大致的画一下我想要表达的内容。之后的时间里我会抽空好好学习一下如何做项目管理。

三、进行合理分工

这个要根据实际模块开发进行分工,这里不做讨论。实际在进度管理中要善用甘特图。

四、自定义开发原则

在实际的开发中,我们可能需要根据这个产品的实际应用场景或者开发背景,来决定这个产品的开发原则。如果我现在开发的这个产品是一个需要保证保密性的产品,那这个产品就需要以稳定、可靠为第一要义;如果是以长期开发、长期维护为第一要义,那么产品的健壮性、可维护性就必须要在设计之初就考虑清楚了。

我们这个项目维护了差不多十年,到我手上重新开发,那么这个产品很显然就是一个需要长期维护的代码。那么这个产品中模块的独立开发性、代码的可读性、接口调用的简便性就是我们程序开发中主要关注的地方。

除了开发的原则,我们还需要确定数据的流动原则。我们这个产品现在是多模块开发,原来的框架中数据流动比较自由(乱),导致维护的时候非常难找数据的流向,有可能这个数据一下子就走到百八十里外的模块里面去了,但是去找是非常困难的。

所以我们需要在开发前规定好数据的原则:

一:与其他终端的信息交流中,可以不必表明来源,但必须标明终点

二、内部数据统一采用Qt自带的系统数据类型,比如QString qint32等,数组统一用QList。对外接口的数据统一使用标准系统类型,比如int string bool等

三、参数命名的一致性,详情参考前面开发规范

除了基本原则以外,我们还可能会遇到对外发布结构体等情况。但是我们的项目实际上为了保证接口的便捷性,采取了COM组件对外暴露接口工具数据的方式,这么做的话就会涉及到一个类对象数据的封装和解析,这里我造了个轮子如下【QtJson】用Qt自带的QJson,直接一步到位封装和解析一个类的实例对象!

内容大致如下:

我们现在的要求就是直接在不知道类成员的情况下,把一个类丢进去就能生成一个Json字符串,也可以把一个字符串和一个类成员丢进去就能根据成员变量名匹配到元素并赋值,大概就这样

中心思想就是Q_PROPERTY宏提供了一个property类型,可以直接通过变量名称获得一个变量名称对应的字符串,比如int a;可以直接获得一个"a"的字符串,而且还可以知道这个a 的类型。并据此来进行字符串的封装和解析。

主要是为了开发方便,就可以直接把一个QObject对象扔进去返回一个字符串,也可以把一个Json字符串和指定类的对象扔进去就直接自动把类中对应的属性修改了,总的来说随拿随用。

  1. #pragma region JsonMaker
  2. //JsonMaker类使用方法:
  3. //Json相关
  4. //给定任意模板类,将其公开属性打包成一个Json字符串,使用此方法需要所有的公开属性均为Q_PROPERTY宏声明,该类提供单例。
  5. //序列化类Q_PROPERTY宏声明的属性 set/get函数命名规则:get/set+属性名 如getBirthday setList,大小写不限,如果是set方法需要在set方法前面加上Q_INVOKABLE 宏
  6. //如果需要反序列化数组,请保证数组中的所有数据结构是同一个类型,否则可能会出错
  7. //注:请尽量使用int不要使用qint32,使用double不要使用float
  8. class JsonMaker :public QObject {
  9. JsonMaker();
  10. //提供单例
  11. public:
  12. static JsonMaker& JsonMaker::Singleton() {
  13. static JsonMaker Instance;
  14. return Instance;
  15. // TODO: 在此处插入 return 语句
  16. }
  17. //序列化类Q_PROPERTY宏声明的属性,如果有数组类型,请使用QList
  18. template<class T1>
  19. QString JsonSerialization(T1& T_Class_1) {
  20. auto T_Class = dynamic_cast<QObject*>(&T_Class_1);
  21. QJsonObject jsonObject;
  22. //通过元对象定义成员
  23. const QMetaObject* metaObject = T_Class->metaObject();
  24. for (int i = 0; i < metaObject->propertyCount(); ++i) {
  25. QMetaProperty property = metaObject->property(i);
  26. if (!property.isReadable()) {
  27. continue;
  28. }
  29. //这个不知道是什么,暂时需要先屏蔽掉
  30. if (QString(property.name()) == "objectName") {
  31. continue;
  32. }
  33. //如果是QList
  34. if (QString(property.typeName()).contains("QList")) {
  35. //这里可能要根据常见类型进行一下分类
  36. QJsonArray jsonListArray;
  37. //输入一个模板类类型,输出一个jsonObject
  38. if (QString(property.typeName()) == "QList<QString>") {
  39. QList<QString> str_message = property.read(T_Class).value<QList<QString>>();
  40. jsonListArray = QListToJsonArray(str_message);
  41. }
  42. else if (QString(property.typeName()) == "QList<qint32>" || QString(property.typeName()) == "QList<int>") {
  43. QList<qint32> str_message = property.read(T_Class).value<QList<qint32>>();
  44. jsonListArray = QListToJsonArray(str_message);
  45. }
  46. else if (QString(property.typeName()) == "QList<qint64>") {
  47. QList<qint64> str_message = property.read(T_Class).value<QList<qint64>>();
  48. jsonListArray = QListToJsonArray(str_message);
  49. }
  50. else if (QString(property.typeName()) == "QList<int>") {
  51. QList<int> str_message = property.read(T_Class).value<QList<int>>();
  52. jsonListArray = QListToJsonArray(str_message);
  53. }
  54. else if (QString(property.typeName()) == "QList<bool>") {
  55. QList<bool> str_message = property.read(T_Class).value<QList<bool>>();
  56. jsonListArray = QListToJsonArray(str_message);
  57. }
  58. else if (QString(property.typeName()) == "QList<double>") {
  59. QList<double> str_message = property.read(T_Class).value<QList<double>>();
  60. jsonListArray = QListToJsonArray(str_message);
  61. }
  62. else if (QString(property.typeName()) == "QList<Float>") {
  63. QList<float> str_message = property.read(T_Class).value<QList<float>>();
  64. jsonListArray = QListToJsonArray(str_message);
  65. }
  66. else if (QString(property.typeName()) == "QList<QByteArray>") {
  67. QList<QByteArray> str_message = property.read(T_Class).value<QList<QByteArray>>();
  68. jsonListArray = QListToJsonArray(str_message);
  69. }
  70. jsonObject.insert(property.name(), QJsonValue(jsonListArray));
  71. }
  72. //如果不是QList
  73. else {
  74. QVariant result = property.read(T_Class);
  75. jsonObject[property.name()] = QJsonValue::fromVariant(property.read(T_Class));
  76. }
  77. qDebug() << property.name();
  78. }
  79. QJsonDocument doc(jsonObject);
  80. return doc.toJson(QJsonDocument::Compact);
  81. }
  82. //反序列化类Q_PROPERTY宏声明的属性,如果有数组类型,请使用QList
  83. template<class T>
  84. void JsonDeserialization(T& T_Class, const QString& jsonString)
  85. {
  86. auto qobject = dynamic_cast<QObject*>(&T_Class);
  87. QJsonDocument doc = QJsonDocument::fromJson(jsonString.toUtf8());
  88. QJsonObject jsonObject = doc.object();
  89. // 使用QMetaObject的invokeMethod()函数来调用模板类T的setter函数
  90. const QMetaObject* metaObject = qobject->metaObject();
  91. for (int i = 0; i < metaObject->propertyCount(); ++i) {
  92. QMetaProperty property = metaObject->property(i);
  93. if (property.isReadable() && property.isWritable()) {
  94. QString propertyName = property.name();
  95. QString str_functinoname = QString("set" + propertyName);
  96. //为了转换成const char*类型必须的一个中间步骤
  97. QByteArray temp_qba_functinoname = str_functinoname.toLocal8Bit();
  98. const char* func_name = temp_qba_functinoname.data();
  99. if (jsonObject.contains(propertyName)) {
  100. QJsonValue value = jsonObject[propertyName];
  101. JsonMaker temp;
  102. qDebug() << value;
  103. switch (value.type()) {
  104. case QJsonValue::Type::Bool:
  105. QMetaObject::invokeMethod(qobject, func_name, Q_ARG(bool, value.toBool()));
  106. break;
  107. case QJsonValue::Type::Double:
  108. QMetaObject::invokeMethod(qobject, func_name, Q_ARG(double, value.toDouble()));
  109. break;
  110. case QJsonValue::Type::String:
  111. QMetaObject::invokeMethod(qobject, func_name, Q_ARG(QString, value.toString()));
  112. break;
  113. case QJsonValue::Type::Array: {
  114. //如果是数组则需要根据情况进行解析
  115. if (!value.isArray()) {
  116. break;
  117. }
  118. QJsonArray arr = value.toArray();
  119. //下面确定数组类型
  120. this->JsonArrayDeserialization(qobject, func_name, arr);
  121. }
  122. break;
  123. case QJsonValue::Type::Object:
  124. QMetaObject::invokeMethod(qobject, func_name, Q_ARG(QJsonValue, value));
  125. break;
  126. default:
  127. break;
  128. }
  129. }
  130. }
  131. }
  132. }
  133. private:
  134. //将模板类QList转换成JsonObject
  135. template<class T>
  136. QJsonArray QListToJsonArray(QList<T> list) {
  137. QJsonArray jsonArray;
  138. for each (T temp_T in list)
  139. {
  140. jsonArray.append(QJsonValue::fromVariant(temp_T));
  141. }
  142. return jsonArray;
  143. }
  144. //解析数组并注入QObject对象
  145. void JsonArrayDeserialization(QObject* qobject, const char* func_name, QJsonArray arr) {
  146. try {
  147. //判断类型
  148. //QString
  149. if (arr[0].type() == QJsonValue::String) {
  150. QList<QString> list_result;
  151. QJsonValue value;
  152. for each (QJsonValue temp_value in arr)
  153. {
  154. list_result.append(temp_value.toString());
  155. }
  156. QMetaObject::invokeMethod(qobject, func_name, Q_ARG(QList<QString>, list_result));
  157. }
  158. else if (arr[0].isDouble()) {
  159. //若为为整形
  160. if (arr[0].toDouble() == arr[0].toInt()) {
  161. qDebug() << arr[0].toDouble() << arr[0].toInt();
  162. QList<qint32> list_result;
  163. QList<int> list_result_2;
  164. QJsonValue value;
  165. for each (QJsonValue temp_value in arr)
  166. {
  167. //int 和 qint32都需要尝试,但请尽量尝试使用qint32,这段代码占用了两倍的内存,将来可能考虑删除
  168. list_result.append(temp_value.toInt());
  169. list_result_2.append(temp_value.toInt());
  170. }
  171. if (!QMetaObject::invokeMethod(qobject, func_name, Q_ARG(QList<qint32>, list_result))) {
  172. QMetaObject::invokeMethod(qobject, func_name, Q_ARG(QList<int>, list_result_2));
  173. }
  174. }
  175. //若为双精度
  176. else {
  177. QList<double> list_result;
  178. QList<float> list_result_2;
  179. QJsonValue value;
  180. for each (QJsonValue temp_value in arr)
  181. {
  182. list_result.append(temp_value.toDouble());
  183. }
  184. //double和float都会尝试,请尽量使用double
  185. if (!QMetaObject::invokeMethod(qobject, func_name, Q_ARG(QList<double>, list_result))) {
  186. QMetaObject::invokeMethod(qobject, func_name, Q_ARG(QList<float>, list_result_2));
  187. }
  188. }
  189. }if (arr[0].type() == QJsonValue::Bool) {
  190. QList<bool> list_result;
  191. QJsonValue value;
  192. for each (QJsonValue temp_value in arr)
  193. {
  194. list_result.append(temp_value.toBool());
  195. }
  196. QMetaObject::invokeMethod(qobject, func_name, Q_ARG(QList<bool>, list_result));
  197. }
  198. }
  199. catch (const QException& e) {
  200. WriteErrorMessage("JsonArrayDeserialization", "JsonArrayDeserialization", e.what());
  201. }
  202. }
  203. };
  204. #pragma endregion
  205. //Json相关方法调用实例:
  206. //
  207. // 如果想要调用JsonMaker类来把你的类成员元素,假设是A a,其中包含元素qint32 a1,QString a2,bool a3进行封装,那么你需要使用Q_PROPERTY来
  208. // 声明封装a1,a2,a3元素和其set/get方法(如果需要解析就需要set方法,如果需要封装就需要get方法),set/get方法命名规则为set/get+元素名称
  209. // 比如seta1,geta2,其中不对大小写做规定,也可以写成setA1,getA2
  210. //
  211. // 调用方法如下:
  212. // 1.封装字符串
  213. // A a;
  214. // QString result = JsonMaker::Singleton().JsonSerialization<Tester1>(tester);
  215. // 2.解析字符串
  216. // A a
  217. // JsonMaker::Singleton().JsonDeserialization<Tester1>(a, Json);
  218. // 调用完毕后a中的对应数据都会被Json字符串中的数据覆盖
  219. //

【大型软件开发】浅谈大型Qt软件开发(一)开发前的准备——在着手开发之前,我们要做些什么?的更多相关文章

  1. [IC]浅谈嵌入式MCU软件开发之中断优先级与中断嵌套

    转自:https://mp.weixin.qq.com/s?__biz=MzI0MDk0ODcxMw==&mid=2247483680&idx=1&sn=c5fd069ab3f ...

  2. 浅谈大型web系统架构

    动态应用,是相对于网站静态内容而言,是指以c/c++.php.Java.perl..net等服务器端语言开发的网络应用软件,比如论坛.网络相册.交友.BLOG等常见应用.动态应用系统通常与数据库系统. ...

  3. 转:浅谈大型web系统架构

    浅谈大型web系统架构 动态应用,是相对于网站静态内容而言,是指以c/c++.php.Java.perl..net等服务器端语言开发的网络应用软件,比如论坛.网络相册.交友.BLOG等常见应用.动态应 ...

  4. 浅谈OA办公软件市场行情

    3.原文:http://www.jiusi.net/detail/472__776__3999__1.html 关键词:oa系统,OA办公软件 浅谈OA办公软件市场行情 中国的OA办公软件市场历经20 ...

  5. 【ZZ】浅谈大型web系统架构 | 菜鸟教程

    浅谈大型web系统架构 http://www.runoob.com/w3cnote/large-scale-web-system-architecture.html

  6. 浅谈关于QT中Webkit内核浏览器

    关于QT中Webkit内核浏览器是本文要介绍的内容,主要是来学习QT中webkit中浏览器的使用.提起WebKit,大家自然而然地想到浏览器.作为浏览器内部的主要构件,WebKit的主要工作是渲染.给 ...

  7. springboot开发浅谈 2021/05/11

    学习了这么久,本人希望有时间能分享一下,这才写下这篇浅谈,谈谈软件,散散心情. 这是本人的博客园账号,欢迎关注,一起学习. 一开始学习springboot,看了好多网站,搜了好多课程.零零落落学了一些 ...

  8. Android开发-浅谈架构(二)

    写在前面的话 我记得有一期罗胖的<罗辑思维>中他提到 我们在这个碎片化 充满焦虑的时代该怎么学习--用30%的时间 了解70%该领域的知识然后迅速转移芳草鲜美的地方 像游牧民族那样.原话应 ...

  9. Python测试开发-浅谈如何自动化生成测试脚本

    Python测试开发-浅谈如何自动化生成测试脚本 原创: fin  测试开发社区  前天 阅读文本大概需要 6.66 分钟. 一 .接口列表展示,并选择 在右边,点击选择要关联的接口,区分是否要登录, ...

  10. J1001.Java原生桌面及Web开发浅谈

    自从Java问世以来,在服务端开发方面取得了巨大的发展.但是在桌面/Web开发方面,一直没有得到大的发展.从最初的AWT,到Swing,再到JavaFX,Java从来没有在桌面/Web解决方案中取得重 ...

随机推荐

  1. Vue中组件化编码使用(实战练习一)

    Vue中组件化编码的大致流程(初接触).组件之间的参数传递(最基础的形式).组件之间的配合完成一个需求 1.在Vue中进行组件化编码 1.1.组件化编码流程: (1).拆分静态组件:组件要按照功能点拆 ...

  2. Magnet: Push-based Shuffle Service for Large-scale Data Processing

    本文是阅读 LinkedIn 公司2020年发表的论文 Magnet: Push-based Shuffle Service for Large-scale Data Processing 一点笔记. ...

  3. 1.pygame快速入门-创建游戏窗口

      简介 pygame是python一个包,专为电子游戏设计#安装 pip3 install pygame #验证安装 # aliens 是pygame内置的一个小游戏,可以启动成功说明pygame安 ...

  4. 洛P8109题解

    摘自本人洛谷博客,原文章地址:https://www.luogu.com.cn/blog/cjtb666anran/solution-p8109 本题原题目摘录: 本场比赛共有 \(n\) 道题,Ci ...

  5. Python3.11正式版,它来了!

    转载请注明出处️ 作者:测试蔡坨坨 原文链接:caituotuo.top/b055fbf2.html 你好,我是测试蔡坨坨. 就在前几天,2022年10月24日,Python3.11正式版发布了! P ...

  6. 基于PCIe的多路视频采集与显示子系统

    基于PCIe的多路视频采集与显示子系统 1        概述 视频采集与显示子系统可以实时采集多路视频信号,并存储到视频采集队列中,借助高效的硬实时视频帧出入队列管理和PCIe C2H DMA引擎, ...

  7. 文盘Rust -- 把程序作为守护进程启动

    当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服.今天我们就来聊聊这个事儿. 最早大家部署应用的通常操作是 "nohu ...

  8. 重新整理 .net core 实践篇 ———— linux上排查问题实用工具 [外篇]

    前言 介绍下面几个工具: Lldb createdump dotnet-dump dotnet-gcdump dotnet-symbol Procdump 该文的前置篇为: https://www.c ...

  9. 《吐血整理》高级系列教程-吃透Fiddler抓包教程(34)-Fiddler如何抓取微信小程序的包-上篇

    1.简介 有些小伙伴或者是童鞋们说小程序抓不到包,该怎么办了???其实苹果手机如果按照宏哥前边的抓取APP包的设置方式设置好了,应该可以轻松就抓到包了.那么安卓手机小程序就比较困难,不是那么友好了.所 ...

  10. KVC原理与数据筛选

    作者:宋宏帅 1 前言 在技术论坛中看到一则很有意思的KVC案例: @interface Person : NSObject @property (nonatomic, copy) NSString ...