:::info Repo地址
https://github.com/IsshikiHugh/C-Newbie-Helper :::

C Newbie Helper

简介 | Brief Intro

这是一个为 C 语言学习新手提供的一个日志打印工具,整体功能比较简单,目的是培养刚接触编程的同学学习和养成 Debug 正确方法和好习惯。
为了方便没有接触过多模块编程的同学的使用,该工具将全部通过预编译指令和宏实现,以便使用者也能够直接复制进代码文件使用。

1.0 版本

  1. #ifndef __C_NEWBIE_HELPER__
  2. #define __C_NEWBIE_HELPER__
  3. /****************************************************
  4. * C Newbie Helper 1.0 *
  5. * ------------------------------------------------ *
  6. * Github Repository Address: *
  7. * - https://github.com/IsshikiHugh/C-Newbie-Helper *
  8. ****************************************************/
  9. /*** Config Part ************************************/
  10. // MODE 0 : Logs will be write to 'CNH_log.txt' file.
  11. // MODE 1 : Logs will be print to console (colorful for normal terminal).
  12. // MODE 2 : Logs will be print to console (colorless but fine for CMD).
  13. #define CNH_MODE 1
  14. /*** Source Code Part *******************************/
  15. #include <stdio.h>
  16. /*** Show Setting Part ******************************/
  17. #define SHOW_LOGS CNH_SHOW_LOGS
  18. static int CNH_SHOW_LOGS = 1;
  19. // Be used to set whether the CNH will show or not.
  20. // CNH will show by default.
  21. #define SET_CNH_SHOW(CNH_SHOW_FLAG) \
  22. do{ \
  23. CNH_SHOW_LOGS = CNH_SHOW_FLAG; \
  24. }while(0)
  25. /*** Mode Setting Part ******************************/
  26. #define CNH_BRIEF CNH_USE_BRIEF_MODE
  27. static int CNH_USE_BRIEF_MODE = 0;
  28. static int CNH_TMP_USE_BRIEF_MODE = 0;
  29. // Be used to set the default mode CNH will use while printing logs.
  30. // CNH will use normal mode by default.
  31. #define SET_CNH_BRIEF_MODE(CNH_BRIEF_FLAG) \
  32. do{ \
  33. CNH_USE_BRIEF_MODE = CNH_TMP_USE_BRIEF_MODE = CNH_BRIEF_FLAG; \
  34. }while(0)
  35. // Force to use brief mode.
  36. #define BRIEF(...) \
  37. do{ \
  38. CNH_USE_BRIEF_MODE = 1;\
  39. __VA_ARGS__ \
  40. CNH_USE_BRIEF_MODE = CNH_TMP_USE_BRIEF_MODE;\
  41. }while(0)
  42. // Force to use normal mode.
  43. #define NORMAL(...) \
  44. do{ \
  45. CNH_USE_BRIEF_MODE = 0; \
  46. __VA_ARGS__; \
  47. CNH_USE_BRIEF_MODE = CNH_TMP_USE_BRIEF_MODE; \
  48. }while(0)
  49. // This part is used to customize the 'OUTPUT'.
  50. #if CNH_MODE == 0
  51. #define CNH_BLACK ""
  52. #define CNH_RED ""
  53. #define CNH_GREEN ""
  54. #define CNH_YELLOW ""
  55. #define CNH_BLUE ""
  56. #define CNH_PURPLE ""
  57. #define CNH_CYAN ""
  58. #define CNH_WHITE ""
  59. #define CNH_DEFAULT_COLOR ""
  60. #define OUTPUT(...) \
  61. do{ \
  62. if(1){ \
  63. FILE * CNH_fp = fopen("CNH_log.txt", "a"); \
  64. fprintf(CNH_fp, __VA_ARGS__); \
  65. fclose(CNH_fp); \
  66. } \
  67. }while(0)
  68. #endif
  69. #if CNH_MODE == 1
  70. #define CNH_BLACK "\033[0;30m"
  71. #define CNH_RED "\033[0;31m"
  72. #define CNH_GREEN "\033[0;32m"
  73. #define CNH_YELLOW "\033[0;33m"
  74. #define CNH_BLUE "\033[0;34m"
  75. #define CNH_PURPLE "\033[0;35m"
  76. #define CNH_CYAN "\033[0;36m"
  77. #define CNH_WHITE "\033[0;37m"
  78. #define CNH_DEFAULT_COLOR "\033[0m"
  79. #define OUTPUT(...) \
  80. do{ \
  81. if(1){ \
  82. fprintf(stderr, __VA_ARGS__); \
  83. } \
  84. }while(0)
  85. #endif
  86. #if CNH_MODE == 2
  87. #define CNH_BLACK ""
  88. #define CNH_RED ""
  89. #define CNH_GREEN ""
  90. #define CNH_YELLOW ""
  91. #define CNH_BLUE ""
  92. #define CNH_PURPLE ""
  93. #define CNH_CYAN ""
  94. #define CNH_WHITE ""
  95. #define CNH_DEFAULT_COLOR ""
  96. #define OUTPUT(...) \
  97. do{ \
  98. if(1){ \
  99. fprintf(stderr, __VA_ARGS__); \
  100. } \
  101. }while(0)
  102. #endif
  103. /*** Functional Part ********************************/
  104. // This part is used to print debug messages with a format like 'OUTPUT' needs.
  105. #define PRINT_LINE() \
  106. do{ \
  107. if(!CNH_BRIEF){ \
  108. OUTPUT(CNH_YELLOW "--------------------------------------------------------\n" CNH_DEFAULT_COLOR); \
  109. } \
  110. }while(0)
  111. // This part is used to print the head of the logs message.
  112. #define PRINT_BEGAIN() \
  113. do{ \
  114. if(!CNH_BRIEF){ \
  115. OUTPUT(CNH_YELLOW "\n=== >>" CNH_BLUE " [:CNH:] " CNH_YELLOW ">> ======================================\n" CNH_DEFAULT_COLOR); \
  116. } else { \
  117. OUTPUT(CNH_YELLOW "[CNH]"); \
  118. } \
  119. }while(0)
  120. // This part is used to print the location of the code printing the logs.
  121. #define PRINT_LOCATION() \
  122. do{ \
  123. if(!CNH_BRIEF){ \
  124. OUTPUT(CNH_YELLOW "+ +" CNH_BLUE " File @ [ " CNH_CYAN "%s" CNH_BLUE " ]\n" CNH_DEFAULT_COLOR, __FILE__); \
  125. OUTPUT(CNH_YELLOW "+ +" CNH_BLUE " Func @ [ " CNH_CYAN "%s()" CNH_BLUE " ] & Line @ [ " CNH_CYAN "%d" CNH_BLUE " ]\n" CNH_DEFAULT_COLOR, __FUNCTION__, __LINE__); \
  126. PRINT_LINE(); \
  127. } else { \
  128. OUTPUT(CNH_YELLOW " @ " CNH_BLUE "{" CNH_CYAN "%s" CNH_BLUE "}[" CNH_CYAN "%d" CNH_BLUE "]/" CNH_CYAN "%s()" CNH_BLUE " : ", __FILE__, __LINE__, __FUNCTION__); \
  129. } \
  130. }while(0)
  131. // This part is used to print the content of the logs.
  132. #define PRINT_CONTENT(...) \
  133. do{ \
  134. if(1){ \
  135. OUTPUT(CNH_GREEN __VA_ARGS__); \
  136. OUTPUT("\n" CNH_DEFAULT_COLOR); \
  137. } \
  138. }while(0)
  139. // This part is used to print the tail of the logs.
  140. #define PRINT_END() \
  141. do{ \
  142. if(!CNH_BRIEF){ \
  143. OUTPUT(CNH_YELLOW "====================================== << " CNH_BLUE "[:CNH:]" CNH_YELLOW " << ===\n\n" CNH_DEFAULT_COLOR); \
  144. } \
  145. }while(0)
  146. // The normal LOG macro function.
  147. #define LOG(...) \
  148. do{ \
  149. if(SHOW_LOGS){ \
  150. PRINT_BEGAIN(); \
  151. PRINT_LOCATION(); \
  152. PRINT_CONTENT(__VA_ARGS__); \
  153. PRINT_END(); \
  154. } \
  155. }while (0)
  156. // The LOG macro function specially for variable.
  157. #define SHOW_VAR(CNH_TYPE, CNH_VAR) \
  158. do{ \
  159. if(SHOW_LOGS){ \
  160. PRINT_BEGAIN(); \
  161. PRINT_LOCATION(); \
  162. if(!CNH_BRIEF) PRINT_CONTENT(CNH_BLUE "< Variable Monitor >" CNH_DEFAULT_COLOR); \
  163. PRINT_CONTENT("%s" CNH_BLUE " = " CNH_GREEN CNH_TYPE, #CNH_VAR, CNH_VAR); \
  164. PRINT_END(); \
  165. } \
  166. }while(0)
  167. // This part is uesd to print 1-d array.
  168. #define PRINT_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_BEGIN, CNH_ARR_END) \
  169. do{ \
  170. if(1){ \
  171. int CNH_IT; \
  172. OUTPUT(CNH_GREEN "%s" CNH_BLUE " = ", #CNH_ARR_NAME); \
  173. OUTPUT(CNH_RED "[");\
  174. for(CNH_IT = CNH_ARR_BEGIN; CNH_IT < CNH_ARR_END; ++CNH_IT){ \
  175. if(CNH_IT == CNH_ARR_END -1 ){ \
  176. OUTPUT(CNH_GREEN CNH_TYPE, CNH_ARR_NAME[CNH_IT]); \
  177. break; \
  178. } \
  179. OUTPUT(CNH_GREEN CNH_TYPE ", ", CNH_ARR_NAME[CNH_IT]); \
  180. } \
  181. OUTPUT(CNH_RED "]\n"); \
  182. } \
  183. }while(0)
  184. // This part is used to print N-d array.
  185. #define PRINT_N_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ELEMENT_SIZE, CNH_SIZE) \
  186. do{ \
  187. if(1){ \
  188. int CNH_IT; \
  189. char* CNH_BEGIN = (char*)CNH_ARR_NAME; \
  190. OUTPUT(CNH_GREEN "%s" CNH_BLUE " = ", #CNH_ARR_NAME); \
  191. OUTPUT(CNH_RED "["); \
  192. for(CNH_IT = 0; CNH_IT < CNH_SIZE; CNH_IT+=CNH_ELEMENT_SIZE){ \
  193. if(CNH_IT == CNH_SIZE - CNH_ELEMENT_SIZE ) {OUTPUT(CNH_GREEN CNH_TYPE, CNH_BEGIN[CNH_IT]); break;} \
  194. OUTPUT(CNH_GREEN CNH_TYPE ", ", CNH_BEGIN[CNH_IT]);} \
  195. OUTPUT(CNH_RED "]\n"); \
  196. } \
  197. }while(0)
  198. // This part is used to print 2-d array/
  199. #define PRINT_2_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_ROW_NUM, CNH_ARR_COL_NUM) \
  200. do{ \
  201. if(1){ \
  202. int CNH_IT_ROW; \
  203. OUTPUT(CNH_GREEN "%s" CNH_BLUE " = \n", #CNH_ARR_NAME); \
  204. OUTPUT(CNH_RED "[");\
  205. for(CNH_IT_ROW = 0; CNH_IT_ROW < CNH_ARR_ROW_NUM; ++CNH_IT_ROW){ \
  206. int CNH_IT_COL; \
  207. if(CNH_IT_ROW == 0 ) OUTPUT(CNH_RED "["); \
  208. else OUTPUT(CNH_RED " ["); \
  209. for(CNH_IT_COL = 0; CNH_IT_COL < CNH_ARR_COL_NUM; ++CNH_IT_COL){ \
  210. if(CNH_IT_COL == CNH_ARR_COL_NUM - 1 ) {OUTPUT(CNH_GREEN CNH_TYPE, CNH_ARR_NAME[CNH_IT_ROW][CNH_IT_COL]); break;} \
  211. OUTPUT(CNH_GREEN CNH_TYPE ", ", CNH_ARR_NAME[CNH_IT_ROW][CNH_IT_COL]); \
  212. } \
  213. if(CNH_IT_ROW == CNH_ARR_ROW_NUM - 1 ) {OUTPUT(CNH_RED "]");break;} \
  214. OUTPUT(CNH_RED "],\n"); \
  215. } \
  216. OUTPUT(CNH_RED "]\n"); \
  217. } \
  218. }while(0)
  219. // The LOG macro function specially for 1-d array.
  220. #define SHOW_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_BEGIN, CNH_ARR_END) \
  221. do{ \
  222. if(SHOW_LOGS){ \
  223. PRINT_BEGAIN(); \
  224. PRINT_LOCATION(); \
  225. if(!CNH_BRIEF) PRINT_CONTENT(CNH_BLUE "< Array Monitor >" CNH_DEFAULT_COLOR); \
  226. PRINT_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_BEGIN, CNH_ARR_END); \
  227. PRINT_END(); \
  228. } \
  229. }while(0)
  230. // The LOG macro function specially for N-d array.
  231. #define SHOW_N_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ELEMENT_SIZE, CNH_SIZE) \
  232. do{ \
  233. if(SHOW_LOGS){ \
  234. PRINT_BEGAIN(); \
  235. PRINT_LOCATION(); \
  236. if(!CNH_BRIEF) PRINT_CONTENT(CNH_BLUE "< Array Monitor >" CNH_DEFAULT_COLOR); \
  237. PRINT_N_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ELEMENT_SIZE, CNH_SIZE); \
  238. PRINT_END(); \
  239. } \
  240. }while(0)
  241. // The LOG macro function specially for 2-d array.
  242. #define SHOW_2_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_ROW_NUM, CNH_ARR_COL_NUM) \
  243. do{ \
  244. if(SHOW_LOGS){ \
  245. PRINT_BEGAIN(); \
  246. PRINT_LOCATION(); \
  247. if(!CNH_BRIEF) PRINT_CONTENT(CNH_BLUE "< Array Monitor >" CNH_DEFAULT_COLOR); \
  248. PRINT_2_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_ROW_NUM, CNH_ARR_COL_NUM); \
  249. PRINT_END(); \
  250. } \
  251. }while(0)
  252. #endif

C Newbie Helper

注意事项 | FYA

  • 如果您将日志输出到文件中,请注意本工具只会不断追加,不会主动清空日志文件。所以请您做好日志的手动清空或备份管理工作。
    • 换句话说,如果你不希望几次运行的日志全都在同一个文件里无法区分,那么请在每次运行完以后删除日志文件或者重命名它。
  • 该工具中的宏函数都需要在函数内使用,也就是说你不能把它写在函数外,当然我相信几乎没有人会这么做。

    使用之前的设置 | Config

    说明

  • 该项内容需要您手动修改源代码中的对应部分,请放心,它被放在来代码的头部,很好找。

  • 如果您希望日志打印在屏幕上但没有颜色高亮,设置为2
  • 如果您希望日志打印在屏幕上并且有颜色(CMD无法使用),设置为1
  • 如果希望日志打印在文件中,设置为0:

设置为1时效果最佳,但对于不熟悉命令行的一般同学我们推荐使用2,在有特殊需求时可以使用0

  1. // MODE 0 : Logs will be write to 'CNH_log.txt' file.
  2. // MODE 1 : Logs will be print to console (colorful for normal terminal).
  3. // MODE 2 : Logs will be print to console (colorless but fine for CMD).
  4. #define CNH_MODE 2

此外,CNH 默认输出到 stderr 中,如果希望它在和 printf 一起使用且顺序不变时,请改为 stdout

使用 | Usage

导入 | Import

  • 对于尚在学习单文件编程的同学,我们建议您直接复制CNewbieHelper.h中的所有内容至你的代码的最前。
  • 如果您觉得这太过累赘,那么请在本地将CNewbieHelper.h文件与您正在编写的.c文件放在同一目录,并在代码的开头写上#include "CNewbieHelper.h",但请注意,此时您无法直接将代码内容提交至在线评测工具(比如PTA)。解决方法请参考上一条。
  • 对于已经知道如何进行多模块编程的同学,我们建议您直接#include "CNewbieHelper.h"

    显示 | Show

    说明:在保留调试代码的情况下,控制是否显示日志内容。

  • 默认情况下,该工具都会默认输出所有日志内容;

  • 您也可以通过调用SET_CNH_SHOW(0);来关闭显示;
  • 在关闭之后您也可以通过SET_CNH_SHOW(1);来启用显示;

    1. // Run 'demo1.c' to see more!
    2. int main(){
    3. SET_CNH_SHOW(1);
    4. LOG("(QWQ) I can be seen!");
    5. SET_CNH_SHOW(0);
    6. LOG("(QAQ) I can't be seen!");
    7. }

    简化 | Simplify

    说明:如果您认为正常的输出过于花哨,CNH提供了两套方案来调整输出的格式。
    使用如下方法可以全局性地更改输出格式:

  • 使用SET_CNH_BRIEF_MODE(1);来启用简化模式;

  • 使用SET_CNH_BRIEF_MODE(0);来关闭简化模式;

使用BRIEF();NORMAL();可以局部性地修改输出格式:

  1. // Run 'demo2.c' to see more!
  2. int main(){
  3. SET_CNH_BRIEF_MODE(0);
  4. LOG("Normal mode here!");
  5. BRIEF(
  6. LOG("Brief mode here!");
  7. LOG("Multiline is ok!");
  8. );
  9. LOG("Normal mode here!");
  10. SET_CNH_BRIEF_MODE(1);
  11. LOG("Brief mode here!");
  12. NORMAL(
  13. LOG("Normal mode here!");
  14. LOG("Multiline is ok!");
  15. );
  16. LOG("Brief mode here!");
  17. }

预览
正常输出模式:
🗂 C 语言初学者辅助工具 | C-Newbie-Helper - 图1
简化输出模式:
🗂 C 语言初学者辅助工具 | C-Newbie-Helper - 图2

一般日志 | Log

使用LOG(...)来输出一般日志。

  • LOG()的使用方法和printf()基本一致,只需要把printf()语句中的函数名换成LOG即可。

举个例子:
如果您想在某一处输出一条日志语句:

  1. char myCharArr[] = "It's OK!";
  2. LOG("This is a log message. %s", myCharArr);

监控变量 | Variable Monitor

使用SHOW_VAR(CNH_TYPE, CNH_VAR)来监控变量。

  • 其中CNH_TYPE指对应变量在printf()的 format 中所使用的占位符,例如int对应的CNH_TYPE"%d"double对应的CNH_TYPE"%lf"
    • 当然,由于该宏函数的底层使用的是printf(),所以实际上相关的扩展语法都是支持的。比如"%.2lf"
  • CNH_VAR指对应变量;

举个例子:
如果您想在某一处监控int类型变量x,则在该位置写上:

  1. SHOW_VAR("%d", x);

监控数组 | Array Monitor

一维数组

使用SHOW_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_BEGIN, CNH_ARR_END)来监控数组。

  • 其中CNH_TYPE指对应变量在printf()的 format 中所使用的占位符,同上;
  • CNH_ARR_NAME指数组名,例如int a[10];中的a
  • CNH_ARR_BEGINCNH_ARR_END分别为需要监控的数组的始末下表,且左闭右开
    • 具体来说,比如为想监控a[0] a[1] a[2] a[3]a[9],那么对应的CNH_ARR_BEGIN0CNH_ARR_END10
    • 换句话来说,这个宏函数只能用来监控一维数组的连续段;

举个例子:
如果您想在某一处监控int类型数组a[],范围是a[0]a[10],则在该位置写上:

  1. SHOW_ARR("%d", a, 0, 10);

二维数组

使用SHOW_2_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_ROW_NUM, CNH_ARR_COL_NUM)来监控数组。

  • 其中CNH_TYPE指对应变量在printf()的 format 中所使用的占位符,同上;
  • CNH_ARR_NAME指数组名,例如int a[10][10];中的a
  • CNH_ARR_ROW_NUMCNH_ARR_COL_NUM分别为需要监控的二维数组的行数和列数;

    • 具体来说,比如为想监控这样一个二维数组:
      1. int a[3][4] = {...};
  • 那么对应的CNH_ARR_ROW_NUM3CNH_ARR_COL_NUM4

  • 换句话来说,这个宏函数只能用来监控二维数组从( 0 , 0 )( CNH_ARR_ROW_NUM-1 , CNH_ARR_COL_NUM-1 )的矩阵。

举个例子:
如果您想在某一处监控上面那个二维数组a[][],则在该位置写上:

  1. SHOW_2_ARR("%d", a, 3, 4);

任意维数组

使用SHOW_N_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ELEMENT_SIZE, CNH_SIZE)来监控任意维数组。

  • 其中CNH_TYPE指对应变量在printf()的 format 中所使用的占位符,同上;
  • CNH_ARR_NAME指数组名,例如int a[10][10];中的a
  • CNH_ELEMENT_SIZECNH_SIZE分别为需要监控的数组的每一个元素的大小和整个数组的大小,一般推荐直接写成sizeof(<EleType>)sizeof(<ArrName>)
    • 具体来说,比如有一个三维double类数组a[11][45][14],则:
    • CNH_ELEMENT_SIZEsizeof(double)
    • CNH_SIZEsizeof(a)
      举个例子:

如果您想在某一处监控上面那个三维数组a[][],则在该位置写上:

  1. SHOW_2_ARR("%lf", a, sizeof(double), sizeof(a));