前言:本篇文章介绍了一个基于各类文件操作函数的一个小项目“注释转换”,功能是将C风格的注释装换成C++风格的注释,当然对于C++风格的注释装换C风格的注释原理是一样的。本篇博文里所用到的一些文件操作函数在我的上一篇博文(输入/输出函数总结)里有详细介绍。
项目介绍
本项目基于一个状态机的思想,每次处理完成之后通过状态的装换继续处理后边的内容。另外,利用各类的文件操作函数,实现将将C风格的注释装换成C++风格的注释的一个简单功能。大概如下图:
什么是状态机?
一个极度确切的描述是它是一个有向图形,由一组节点和一组相应的转移函数组成。状态机通过响应一系列事件而“运行”。每个事件都在属于“当前” 节点的转移函数的控制范围内,其中函数的范围是节点的一个子集。函数返回“下一个”(也许是同一个)节点。这些节点中至少有一个必须是终态。当到达终态, 状态机停止。(百度摘抄)
在本项目中,会用到四种状态,既空状态(正常代码区)、c注释状态(c风格注释区)、c++注释状态(c++风格注释区)、文件结束状态(EOF),下面是四种状态的装换图:
因为c注释区里的内容不可能包括文件结束标志,所以c注释状态不可能存在与文件结束状态之间的转换。
一些测试用例
// 1.一般情况
int num = 0;
/* int i = 0; */
// 2.换行问题
/* int i = 0; */int j = 0;
/* int i = 0; */
int j = 0;
// 3.匹配问题
/*int i = 0;/*xxxxx*/
// 4.多行注释问题
/*
int i=0;
int j = 0;
int k = 0;
*/int k = 0;
// 5.连续注释问题
/**//**/
// 6.连续的**/问题
/***/
// 7.C++注释问题
// /*xxxxxxxxxxxx*/
参考代码
CommentCovert.h
#ifndef __COMMENTCOVERT_H__
#define __COMMENTCOVERT_H__
enum status
{
END_STATUS,
NUL_STATUS,
C_STATUS,
CPP_STAUS
};
#include<stdio.h>
#include<stdlib.h>
#pragma warning(disable:4996)
void CommentCovert(FILE *pfIn, FILE *pfOut);
void DoNulStaus(FILE *pfIn, FILE *pfOut, enum status *ps);
void DoCStaus(FILE *pfIn, FILE *pfOut, enum status *ps);
void DoCppStaus(FILE *pfIn, FILE *pfOut, enum status *ps);
#endif //__COMMENTCOVERT_H__
CommentCovert.c
//为了方便理解,下面的代码存在向原状态装换的过程,这是不必要的,已经注释掉。
#include"CommentCovert.h"
//正常代码处理
void DoNulStaus(FILE *pfIn, FILE *pfOut, enum status *ps)
{
int first = fgetc(pfIn);
switch (first)
{
case '/':
{
int second = fgetc(pfIn);
switch (second)
{
case'*':
{
fputc('/', pfOut);
fputc('/', pfOut);
*ps = C_STATUS;
}
break;
case'/':
{
fputc(first, pfOut);
fputc(second, pfOut);
*ps = CPP_STAUS;
}
break;
default:
{
fputc(first, pfOut);
fputc(second, pfOut);
/**ps = NUL_STATUS;*/
}
break;
}
}
break;
case EOF:
{
fputc(first, pfIn);
*ps = END_STATUS;
}
break;
default:
{
fputc(first, pfOut);
/**ps = NUL_STATUS;*/
}
break;
}
}
//c风格代码注释区处理
void DoCStaus(FILE *pfIn, FILE *pfOut, enum status *ps)
{
int first = fgetc(pfIn);
switch (first)
{
case'*':
{
int second = fgetc(pfIn);
switch (second)
{
case'/':
{
int third = fgetc(pfIn);
if (third != '\n')
{
fseek(pfIn, -1, SEEK_CUR);
//如果third不是'\n',那个字符已经被读走,应该把那个字符还回去在放一个'\n'
fputc('\n', pfOut);
}
else
{
fputc(third, pfOut);
}
*ps = NUL_STATUS;
}
break;
case'*':
{
fputc(first, pfOut);
fseek(pfIn, -1, SEEK_CUR);
/**ps = C_STATUS;*/
//把fp指针移动到离文件当前位置-1字节处,回退1个字符
//因为第二个字符如果是*要和/一起判断是否是C语言注释风格结束
}
break;
default:
{
fputc(first, pfOut);
fputc(second, pfOut);
/**ps = C_STATUS;*/
}
break;
}
}
break;
case'\n':
{
fputc(first, pfOut);
fputc('/', pfOut);
fputc('/', pfOut);
/**ps = C_STATUS;*/
}
break;
default:
{
fputc(first, pfOut);
/**ps = C_STATUS;*/
}
break;
}
}
//c++风格代码注释区处理
void DoCppStaus(FILE *pfIn, FILE *pfOut, enum status *ps)
{
int first = fgetc(pfIn);
switch (first)
{
case'\n':
{
fputc(first, pfOut);
*ps = NUL_STATUS;
}
break;
case EOF:
fputc(first, pfOut);
*ps = END_STATUS;
break;
default:
fputc(first,pfOut);
/**ps = CPP_STAUS;*/
break;
}
}
fseek的简单理解
SEEK_SET: 文件开头
SEEK_CUR: 当前位置
SEEK_END: 文件结尾
其中SEEK_SET,SEEK_CUR和SEEK_END依次为0,1和2
例如:
fseek(fp,100L,0);把stream指针移动到离文件开头100字节处;
fseek(fp,100L,1);把stream指针移动到离文件当前位置100字节处;
fseek(fp,-100L,2);把stream指针退回到离文件结尾100字节处。
test.c
#include"CommentCovert.h"
void CommentCovert(FILE *pfIn, FILE *pfOut)
{
enum status status = NUL_STATUS;
while (status != END_STATUS)
{
switch (status)
{
case NUL_STATUS:
DoNulStaus(pfIn, pfOut, &status);
break;
case C_STATUS:
DoCStaus(pfIn, pfOut, &status);
break;
case CPP_STAUS:
DoCppStaus(pfIn, pfOut, &status);
break;
default:
break;
}
}
}
int main()
{
FILE *pfIn = NULL;
FILE *pfOut = NULL;
pfIn = fopen("input.c", "r");
if (pfIn == NULL)
{
perror("use file to read");
exit(EXIT_FAILURE);
}
pfOut = fopen("output.c", "w");
if (pfOut == NULL)
{
perror("use file to write");
fclose(pfIn);
pfIn = NULL;
exit(EXIT_FAILURE);
}
//注释转换
CommentCovert(pfIn, pfOut);
fclose(pfIn);
pfIn = NULL;
fclose(pfOut);
pfOut = NULL;
return 0;
}
源代码:CommentCovert