:::info
Repo地址
https://github.com/IsshikiHugh/C-Newbie-Helper
:::
C Newbie Helper
简介 | Brief Intro
这是一个为 C 语言学习新手提供的一个日志打印工具,整体功能比较简单,目的是培养刚接触编程的同学学习和养成 Debug 正确方法和好习惯。
为了方便没有接触过多模块编程的同学的使用,该工具将全部通过预编译指令和宏实现,以便使用者也能够直接复制进代码文件使用。
1.0 版本
#ifndef __C_NEWBIE_HELPER__
#define __C_NEWBIE_HELPER__
/****************************************************
* C Newbie Helper 1.0 *
* ------------------------------------------------ *
* Github Repository Address: *
* - https://github.com/IsshikiHugh/C-Newbie-Helper *
****************************************************/
/*** Config Part ************************************/
// MODE 0 : Logs will be write to 'CNH_log.txt' file.
// MODE 1 : Logs will be print to console (colorful for normal terminal).
// MODE 2 : Logs will be print to console (colorless but fine for CMD).
#define CNH_MODE 1
/*** Source Code Part *******************************/
#include <stdio.h>
/*** Show Setting Part ******************************/
#define SHOW_LOGS CNH_SHOW_LOGS
static int CNH_SHOW_LOGS = 1;
// Be used to set whether the CNH will show or not.
// CNH will show by default.
#define SET_CNH_SHOW(CNH_SHOW_FLAG) \
do{ \
CNH_SHOW_LOGS = CNH_SHOW_FLAG; \
}while(0)
/*** Mode Setting Part ******************************/
#define CNH_BRIEF CNH_USE_BRIEF_MODE
static int CNH_USE_BRIEF_MODE = 0;
static int CNH_TMP_USE_BRIEF_MODE = 0;
// Be used to set the default mode CNH will use while printing logs.
// CNH will use normal mode by default.
#define SET_CNH_BRIEF_MODE(CNH_BRIEF_FLAG) \
do{ \
CNH_USE_BRIEF_MODE = CNH_TMP_USE_BRIEF_MODE = CNH_BRIEF_FLAG; \
}while(0)
// Force to use brief mode.
#define BRIEF(...) \
do{ \
CNH_USE_BRIEF_MODE = 1;\
__VA_ARGS__ \
CNH_USE_BRIEF_MODE = CNH_TMP_USE_BRIEF_MODE;\
}while(0)
// Force to use normal mode.
#define NORMAL(...) \
do{ \
CNH_USE_BRIEF_MODE = 0; \
__VA_ARGS__; \
CNH_USE_BRIEF_MODE = CNH_TMP_USE_BRIEF_MODE; \
}while(0)
// This part is used to customize the 'OUTPUT'.
#if CNH_MODE == 0
#define CNH_BLACK ""
#define CNH_RED ""
#define CNH_GREEN ""
#define CNH_YELLOW ""
#define CNH_BLUE ""
#define CNH_PURPLE ""
#define CNH_CYAN ""
#define CNH_WHITE ""
#define CNH_DEFAULT_COLOR ""
#define OUTPUT(...) \
do{ \
if(1){ \
FILE * CNH_fp = fopen("CNH_log.txt", "a"); \
fprintf(CNH_fp, __VA_ARGS__); \
fclose(CNH_fp); \
} \
}while(0)
#endif
#if CNH_MODE == 1
#define CNH_BLACK "\033[0;30m"
#define CNH_RED "\033[0;31m"
#define CNH_GREEN "\033[0;32m"
#define CNH_YELLOW "\033[0;33m"
#define CNH_BLUE "\033[0;34m"
#define CNH_PURPLE "\033[0;35m"
#define CNH_CYAN "\033[0;36m"
#define CNH_WHITE "\033[0;37m"
#define CNH_DEFAULT_COLOR "\033[0m"
#define OUTPUT(...) \
do{ \
if(1){ \
fprintf(stderr, __VA_ARGS__); \
} \
}while(0)
#endif
#if CNH_MODE == 2
#define CNH_BLACK ""
#define CNH_RED ""
#define CNH_GREEN ""
#define CNH_YELLOW ""
#define CNH_BLUE ""
#define CNH_PURPLE ""
#define CNH_CYAN ""
#define CNH_WHITE ""
#define CNH_DEFAULT_COLOR ""
#define OUTPUT(...) \
do{ \
if(1){ \
fprintf(stderr, __VA_ARGS__); \
} \
}while(0)
#endif
/*** Functional Part ********************************/
// This part is used to print debug messages with a format like 'OUTPUT' needs.
#define PRINT_LINE() \
do{ \
if(!CNH_BRIEF){ \
OUTPUT(CNH_YELLOW "--------------------------------------------------------\n" CNH_DEFAULT_COLOR); \
} \
}while(0)
// This part is used to print the head of the logs message.
#define PRINT_BEGAIN() \
do{ \
if(!CNH_BRIEF){ \
OUTPUT(CNH_YELLOW "\n=== >>" CNH_BLUE " [:CNH:] " CNH_YELLOW ">> ======================================\n" CNH_DEFAULT_COLOR); \
} else { \
OUTPUT(CNH_YELLOW "[CNH]"); \
} \
}while(0)
// This part is used to print the location of the code printing the logs.
#define PRINT_LOCATION() \
do{ \
if(!CNH_BRIEF){ \
OUTPUT(CNH_YELLOW "+ +" CNH_BLUE " File @ [ " CNH_CYAN "%s" CNH_BLUE " ]\n" CNH_DEFAULT_COLOR, __FILE__); \
OUTPUT(CNH_YELLOW "+ +" CNH_BLUE " Func @ [ " CNH_CYAN "%s()" CNH_BLUE " ] & Line @ [ " CNH_CYAN "%d" CNH_BLUE " ]\n" CNH_DEFAULT_COLOR, __FUNCTION__, __LINE__); \
PRINT_LINE(); \
} else { \
OUTPUT(CNH_YELLOW " @ " CNH_BLUE "{" CNH_CYAN "%s" CNH_BLUE "}[" CNH_CYAN "%d" CNH_BLUE "]/" CNH_CYAN "%s()" CNH_BLUE " : ", __FILE__, __LINE__, __FUNCTION__); \
} \
}while(0)
// This part is used to print the content of the logs.
#define PRINT_CONTENT(...) \
do{ \
if(1){ \
OUTPUT(CNH_GREEN __VA_ARGS__); \
OUTPUT("\n" CNH_DEFAULT_COLOR); \
} \
}while(0)
// This part is used to print the tail of the logs.
#define PRINT_END() \
do{ \
if(!CNH_BRIEF){ \
OUTPUT(CNH_YELLOW "====================================== << " CNH_BLUE "[:CNH:]" CNH_YELLOW " << ===\n\n" CNH_DEFAULT_COLOR); \
} \
}while(0)
// The normal LOG macro function.
#define LOG(...) \
do{ \
if(SHOW_LOGS){ \
PRINT_BEGAIN(); \
PRINT_LOCATION(); \
PRINT_CONTENT(__VA_ARGS__); \
PRINT_END(); \
} \
}while (0)
// The LOG macro function specially for variable.
#define SHOW_VAR(CNH_TYPE, CNH_VAR) \
do{ \
if(SHOW_LOGS){ \
PRINT_BEGAIN(); \
PRINT_LOCATION(); \
if(!CNH_BRIEF) PRINT_CONTENT(CNH_BLUE "< Variable Monitor >" CNH_DEFAULT_COLOR); \
PRINT_CONTENT("%s" CNH_BLUE " = " CNH_GREEN CNH_TYPE, #CNH_VAR, CNH_VAR); \
PRINT_END(); \
} \
}while(0)
// This part is uesd to print 1-d array.
#define PRINT_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_BEGIN, CNH_ARR_END) \
do{ \
if(1){ \
int CNH_IT; \
OUTPUT(CNH_GREEN "%s" CNH_BLUE " = ", #CNH_ARR_NAME); \
OUTPUT(CNH_RED "[");\
for(CNH_IT = CNH_ARR_BEGIN; CNH_IT < CNH_ARR_END; ++CNH_IT){ \
if(CNH_IT == CNH_ARR_END -1 ){ \
OUTPUT(CNH_GREEN CNH_TYPE, CNH_ARR_NAME[CNH_IT]); \
break; \
} \
OUTPUT(CNH_GREEN CNH_TYPE ", ", CNH_ARR_NAME[CNH_IT]); \
} \
OUTPUT(CNH_RED "]\n"); \
} \
}while(0)
// This part is used to print N-d array.
#define PRINT_N_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ELEMENT_SIZE, CNH_SIZE) \
do{ \
if(1){ \
int CNH_IT; \
char* CNH_BEGIN = (char*)CNH_ARR_NAME; \
OUTPUT(CNH_GREEN "%s" CNH_BLUE " = ", #CNH_ARR_NAME); \
OUTPUT(CNH_RED "["); \
for(CNH_IT = 0; CNH_IT < CNH_SIZE; CNH_IT+=CNH_ELEMENT_SIZE){ \
if(CNH_IT == CNH_SIZE - CNH_ELEMENT_SIZE ) {OUTPUT(CNH_GREEN CNH_TYPE, CNH_BEGIN[CNH_IT]); break;} \
OUTPUT(CNH_GREEN CNH_TYPE ", ", CNH_BEGIN[CNH_IT]);} \
OUTPUT(CNH_RED "]\n"); \
} \
}while(0)
// This part is used to print 2-d array/
#define PRINT_2_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_ROW_NUM, CNH_ARR_COL_NUM) \
do{ \
if(1){ \
int CNH_IT_ROW; \
OUTPUT(CNH_GREEN "%s" CNH_BLUE " = \n", #CNH_ARR_NAME); \
OUTPUT(CNH_RED "[");\
for(CNH_IT_ROW = 0; CNH_IT_ROW < CNH_ARR_ROW_NUM; ++CNH_IT_ROW){ \
int CNH_IT_COL; \
if(CNH_IT_ROW == 0 ) OUTPUT(CNH_RED "["); \
else OUTPUT(CNH_RED " ["); \
for(CNH_IT_COL = 0; CNH_IT_COL < CNH_ARR_COL_NUM; ++CNH_IT_COL){ \
if(CNH_IT_COL == CNH_ARR_COL_NUM - 1 ) {OUTPUT(CNH_GREEN CNH_TYPE, CNH_ARR_NAME[CNH_IT_ROW][CNH_IT_COL]); break;} \
OUTPUT(CNH_GREEN CNH_TYPE ", ", CNH_ARR_NAME[CNH_IT_ROW][CNH_IT_COL]); \
} \
if(CNH_IT_ROW == CNH_ARR_ROW_NUM - 1 ) {OUTPUT(CNH_RED "]");break;} \
OUTPUT(CNH_RED "],\n"); \
} \
OUTPUT(CNH_RED "]\n"); \
} \
}while(0)
// The LOG macro function specially for 1-d array.
#define SHOW_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_BEGIN, CNH_ARR_END) \
do{ \
if(SHOW_LOGS){ \
PRINT_BEGAIN(); \
PRINT_LOCATION(); \
if(!CNH_BRIEF) PRINT_CONTENT(CNH_BLUE "< Array Monitor >" CNH_DEFAULT_COLOR); \
PRINT_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_BEGIN, CNH_ARR_END); \
PRINT_END(); \
} \
}while(0)
// The LOG macro function specially for N-d array.
#define SHOW_N_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ELEMENT_SIZE, CNH_SIZE) \
do{ \
if(SHOW_LOGS){ \
PRINT_BEGAIN(); \
PRINT_LOCATION(); \
if(!CNH_BRIEF) PRINT_CONTENT(CNH_BLUE "< Array Monitor >" CNH_DEFAULT_COLOR); \
PRINT_N_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ELEMENT_SIZE, CNH_SIZE); \
PRINT_END(); \
} \
}while(0)
// The LOG macro function specially for 2-d array.
#define SHOW_2_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_ROW_NUM, CNH_ARR_COL_NUM) \
do{ \
if(SHOW_LOGS){ \
PRINT_BEGAIN(); \
PRINT_LOCATION(); \
if(!CNH_BRIEF) PRINT_CONTENT(CNH_BLUE "< Array Monitor >" CNH_DEFAULT_COLOR); \
PRINT_2_ARR(CNH_TYPE, CNH_ARR_NAME, CNH_ARR_ROW_NUM, CNH_ARR_COL_NUM); \
PRINT_END(); \
} \
}while(0)
#endif
注意事项 | FYA
- 如果您将日志输出到文件中,请注意本工具只会不断追加,不会主动清空日志文件。所以请您做好日志的手动清空或备份管理工作。
- 换句话说,如果你不希望几次运行的日志全都在同一个文件里无法区分,那么请在每次运行完以后删除日志文件或者重命名它。
该工具中的宏函数都需要在函数内使用,也就是说你不能把它写在函数外,当然我相信几乎没有人会这么做。
使用之前的设置 | Config
说明:
该项内容需要您手动修改源代码中的对应部分,请放心,它被放在来代码的头部,很好找。
- 如果您希望日志打印在屏幕上但没有颜色高亮,设置为
2
; - 如果您希望日志打印在屏幕上并且有颜色(CMD无法使用),设置为
1
; - 如果希望日志打印在文件中,设置为
0
:
设置为1
时效果最佳,但对于不熟悉命令行的一般同学我们推荐使用2
,在有特殊需求时可以使用0
// MODE 0 : Logs will be write to 'CNH_log.txt' file.
// MODE 1 : Logs will be print to console (colorful for normal terminal).
// MODE 2 : Logs will be print to console (colorless but fine for CMD).
#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);
来启用显示;// Run 'demo1.c' to see more!
int main(){
SET_CNH_SHOW(1);
LOG("(QWQ) I can be seen!");
SET_CNH_SHOW(0);
LOG("(QAQ) I can't be seen!");
}
简化 | Simplify
说明:如果您认为正常的输出过于花哨,
CNH
提供了两套方案来调整输出的格式。
使用如下方法可以全局性地更改输出格式:使用
SET_CNH_BRIEF_MODE(1);
来启用简化模式;- 使用
SET_CNH_BRIEF_MODE(0);
来关闭简化模式;
使用BRIEF();
和NORMAL();
可以局部性地修改输出格式:
// Run 'demo2.c' to see more!
int main(){
SET_CNH_BRIEF_MODE(0);
LOG("Normal mode here!");
BRIEF(
LOG("Brief mode here!");
LOG("Multiline is ok!");
);
LOG("Normal mode here!");
SET_CNH_BRIEF_MODE(1);
LOG("Brief mode here!");
NORMAL(
LOG("Normal mode here!");
LOG("Multiline is ok!");
);
LOG("Brief mode here!");
}
一般日志 | Log
使用LOG(...)
来输出一般日志。
LOG()
的使用方法和printf()
基本一致,只需要把printf()
语句中的函数名换成LOG
即可。
举个例子:
如果您想在某一处输出一条日志语句:
char myCharArr[] = "It's OK!";
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
,则在该位置写上:
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_BEGIN
和CNH_ARR_END
分别为需要监控的数组的始末下表,且左闭右开;- 具体来说,比如为想监控
a[0]
a[1]
a[2]
a[3]
…a[9]
,那么对应的CNH_ARR_BEGIN
为0
,CNH_ARR_END
为10
; - 换句话来说,这个宏函数只能用来监控一维数组的连续段;
- 具体来说,比如为想监控
举个例子:
如果您想在某一处监控int
类型数组a[]
,范围是a[0]
到a[10]
,则在该位置写上:
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_NUM
和CNH_ARR_COL_NUM
分别为需要监控的二维数组的行数和列数;- 具体来说,比如为想监控这样一个二维数组:
int a[3][4] = {...};
- 具体来说,比如为想监控这样一个二维数组:
那么对应的
CNH_ARR_ROW_NUM
为3
,CNH_ARR_COL_NUM
为4
;- 换句话来说,这个宏函数只能用来监控二维数组从
( 0 , 0 )
到( CNH_ARR_ROW_NUM-1 , CNH_ARR_COL_NUM-1 )
的矩阵。
举个例子:
如果您想在某一处监控上面那个二维数组a[][]
,则在该位置写上:
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_SIZE
和CNH_SIZE
分别为需要监控的数组的每一个元素的大小和整个数组的大小,一般推荐直接写成sizeof(<EleType>)
和sizeof(<ArrName>)
;- 具体来说,比如有一个三维
double
类数组a[11][45][14]
,则: CNH_ELEMENT_SIZE
为sizeof(double)
;CNH_SIZE
为sizeof(a)
;
举个例子:
- 具体来说,比如有一个三维
如果您想在某一处监控上面那个三维数组a[][]
,则在该位置写上:
SHOW_2_ARR("%lf", a, sizeof(double), sizeof(a));