C程序分析

写在前面

开学也有一周了,上了一个数据结构的小学期,让我们自助选题,我就选了如这篇题目所说的程序分析。废话不多说,开始正文。

需求分析

首先来看要求:

我们挨个分析需求:

首先,把C程序文件按字符顺序读入源程序。这个很好说,我们直接读取整个文件,按行读取即可。

第二个条件,统计代码行,注释行和空行,识别函数的开始和结束统计函数个数和平均行数。这就需要我们对读入的数据进行处理,识别出各种结果。

第三个条件,评分。这个在前面两个条件完成的基础上,就十分简单了。我们直接对读取的结果进行处理就好了。

第四个条件,报告平均长度,没什么好说的,前面求过了,直接输出即可。

第五个条件和第六个条件,一个是函数的调用关系,这就要求我们在统计的时候顺便搞一下函数的名称,识别出函数内调用的函数。

第六个条件,与第五个条件类似,我们直接在统计的过程中统计一下就好。

大概的需求看完后,就开始动手写代码吧。

开始前的准备

首先,我们要确定使用什么语言。看到第一个条件我的下意识反应就是使用Java,毕竟我就对Java熟悉了。而且Java里的很多API我都知道,敲起来也会很方便。之后就是创建项目,确定要使用的工具类了。这里我为了方便起见,创建了一个maven工程,然后导入了Hutools,这是一个工具类的整合包,里面有很多实用的工具类,直接导入坐标依赖就可:

    <dependencies>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.4.2</version>
        </dependency>
    </dependencies>

在这个项目里主要用到了字符串的处理工具。

编码

读取文件

首先就是读取文件了,我们先来看代码:

    /**
     * 读取文件流
     *
     * @return 读取完的文件流List
     */
    private static List<String> getFileContent() {
        // 将C程序作为流读入
        Scanner in = new Scanner(System.in);
        // 使用一个List结构来存储读取的代码
        List<String> fileContent = new ArrayList<>();
        System.out.println("请输入要分析的C程序文件路径:");
        String filePath = in.next();
        if (StrUtil.isEmpty(filePath)) {
            System.out.println("请输入正确的文件路径!");
            System.exit(0);
        }
        File file = new File(filePath);
        BufferedReader reader;
        String line;
        try {
            reader = FileUtil.getReader(file, "UTF-8");
            // 一次读入一行
            while ((line = reader.readLine()) != null) {
                fileContent.add(line);
            }
            // 读取完毕,关闭文件读入流和输入流
            reader.close();
            in.close();
        } catch (Exception e) {
            System.out.println("文件读写出错嘞~");
            System.exit(0);
        }
        return fileContent;
    }

这段代码不是很长,很好理解。我们首先使用了Scanner类来获取用户输入的文件路径,并且使用BufferReader进行文件的读取,每次读取一行,然后存储到一个List中供之后使用。这里要注意的是异常的处理,我们一定要处理好异常,不能再向上层抛出了。

统计代码行

    /**
     * 统计行数的方法
     *
     * @param fileContent
     */
    private static void getLines(List<String> fileContent) {
        boolean isInBlockNote = false;
        for (String line : fileContent) {
            // 位于段注释内,算段注释行
            if (isInBlockNote) {
                noteBlockLineCount++;
            }
            // 统计空行
            if (StrUtil.isEmpty(line) && !isInBlockNote) {
                emptyLineCount++;
            }

            // 统计行注释://
            if (StrUtil.contains(line, "//")) {
                noteLineCount++;
            }

            // 统计段注释:/* */
            if (StrUtil.contains(line, "/*")) {
                // 代表段注释开始了
                noteBlockLineCount++;
                isInBlockNote = true;
            }
            if (StrUtil.contains(line, "*/")) {
                // 代表段注释结束了
                isInBlockNote = false;
            }

            // 统计总行数
            sumLineCount++;
        }
        // 统计代码行:总行数-空行-行注释-段注释
        codeLineCount = sumLineCount - emptyLineCount - noteLineCount - noteBlockLineCount;

        // 输出各个行数
        System.out.println("总行数 = " + sumLineCount);
        System.out.println("空行数 = " + emptyLineCount);
        System.out.println("段注释行数 = " + noteBlockLineCount);
        System.out.println("行注释行数 = " + noteLineCount);
        System.out.println("代码行数 = " + codeLineCount);
    }

这段代码也比较好理解,这里最关键的就是段注释了,段注释是有多行的情况的,所以这里我定义了一个标志变量来判断是否处在段注释内,这样就可以统计出段注释的长度了。

函数的读取

    /**
     * 统计函数的方法
     * C程序里一个函数的特点:
     * 返回值类型 函数名(参数){}
     * 常见返回值类型:void int double float char
     * 指针结构体等特殊函数不再统计
     * 函数以'{' 作为开始,'}' 作为结束
     * 需要排除if for while do 等的大括号
     *
     * @param fileContent
     */
    private static void getMethods(List<String> fileContent) {
        boolean isInMethod = false;
        boolean isInOther = false;
        String methodName = null;
        for (String line : fileContent) {
            if (isInMethod) {
                // 在函数内,如果同时出现'('和')' 且不是while,if,for,证明调用了其他函数
                if (StrUtil.contains(line, "(")
                        && StrUtil.contains(line, ")")
                        && !StrUtil.contains(line, "for")
                        && !StrUtil.contains(line, "while")
                        && !StrUtil.contains(line, "if")) {
                    String[] lineSplits = StrUtil.split(line, "(");
                    if (StrUtil.contains(lineSplits[0], "=")) {
                        // 包含等号,说明是有返回值的函数,再进行一次切割
                        String[] splits = StrUtil.split(line, "=");
                        String replaceWithFen = StrUtil.replace(splits[1], ";", "");
                        String replaceWithZuo = StrUtil.replace(replaceWithFen, "(", "");
                        System.out.println(methodName + "函数调用了名为" + StrUtil.replace(replaceWithZuo, ")", "") + "的函数");
                    } else {
                        // 不包含等号,说明是没有返回值的函数,直接输出
                        String replaceWithFen = StrUtil.replace(lineSplits[0], ";", "");
                        System.out.println(methodName + "函数调用了名为" + StrUtil.replace(replaceWithFen, " ", "") + "的函数");
                    }
                }
                // 在方法内,找到方法开始的标志行,开始统计函数行数
                if (StrUtil.startWith(line, "{")) {
                    methodLineCount++;
                    continue;
                }
                // 如果嵌套定义函数,增加嵌套层数
                if (StrUtil.contains(line, "void")
                        || StrUtil.contains(line, "int")
                        || StrUtil.contains(line, "float")
                        || StrUtil.contains(line, "double")
                        || StrUtil.contains(line, "char")) {
                    // 包含返回值,且有右括号或者{ 表示该行为函数嵌套定义
                    if (StrUtil.endWith(line, ")") || StrUtil.endWith(line, "{")) {
                        methodLoopCount++;
                    }
                }
                methodLineCount++;

            }
            if (StrUtil.startWith(line, "void")
                    || StrUtil.startWith(line, "int")
                    || StrUtil.startWith(line, "float")
                    || StrUtil.startWith(line, "double")
                    || StrUtil.startWith(line, "char")) {
                // 满足条件,说明这行是函数定义
                isInMethod = true;
                String[] splits = StrUtil.split(line, " ");
                String[] split = StrUtil.split(splits[1], "(");
                methodName = split[0];

                // 统计函数个数
                methodCount++;
                // 如果这行有{ 说明是函数开始了,计数
                if (StrUtil.contains(line, "{")) {
                    methodLineCount++;
                }
            }
            if (StrUtil.contains(line, "while")
                    || StrUtil.contains(line, "if")
                    || StrUtil.contains(line, "for")
                    || StrUtil.contains(line, "do")) {
                // 证明是在其他大括号的结构里 (嵌套结构)
                isInOther = true;
                // 统计嵌套个数
                loopCount++;
            }
            // 遇到了最后一个 } 代表该函数到结尾了 (排除while,if,for do的大括号)
            if (StrUtil.startWith(line, "}") && isInMethod && !isInOther) {
                isInMethod = false;
                methodLoopLayerCounts.add(methodLoopCount);
                methodLineCounts.add(methodLineCount);
                methodLineCount = 0;
                methodLoopCount = 0;
            }

            // 在其他嵌套结构里,且到了尾部
            if (isInOther) {
                if (StrUtil.contains(line, "}")) {
                    isInOther = false;
                    methodLineCounts.add(loopCount);
                    loopCount = 0;
                }
            }

        }
        System.out.println("函数个数 = " + methodCount);
        for (Integer lineCount : methodLineCounts) {
            sumMethodLineCount += lineCount;
        }
        avrMethodLineCount = sumMethodLineCount / methodCount;
        System.out.println("函数平均长度 = " + avrMethodLineCount);
        // 输出函数最大嵌套层数
        maxLayer = CollUtil.max(methodLoopLayerCounts);
        System.out.println("函数最大嵌套层数 = " + maxLayer);
    }

这段是全文的核心代码了,这里的难点有很多。这里要注意的是我没有统计类似struct,*等特殊的类型,毕竟要考虑的话确实比较多。

这里要注意的是,统计函数的长度的时候,会遇到类似while,do,for等结构,他们也有大括号,所以要确定好判断用的方法,这样才不会判断错误。

确定代码风格

这应该是所有代码中最简单的了,不再赘述,直接上代码:

    /**
     * 确定代码风格等级
     */
    private static void getCodeStyle() {
        // 代码等级 判断函数平均长度
        if (avrMethodLineCount >= 10 && avrMethodLineCount <= 15) {
            System.out.println("代码等级为A");
        } else if ((avrMethodLineCount >= 8 && avrMethodLineCount <= 9) || (avrMethodLineCount >= 16 && avrMethodLineCount <= 20)) {
            System.out.println("代码等级为B");
        } else if ((avrMethodLineCount >= 5 && avrMethodLineCount <= 7) || (avrMethodLineCount >= 21 && avrMethodLineCount <= 26)) {
            System.out.println("代码等级为C");
        } else {
            System.out.println("代码等级为D");
        }

        // 注释等级 看注释行数占总行数的比例
        int notePercent = (int) ((((float) (noteBlockLineCount + noteLineCount)) / sumLineCount) * 100);
        if (notePercent >= 15 && notePercent <= 25) {
            System.out.println("注释等级为A");
        } else if ((notePercent >= 10 && notePercent <= 14) || (notePercent >= 26 && notePercent <= 30)) {
            System.out.println("注释等级为B");
        } else if ((notePercent >= 5 && notePercent <= 9) || (notePercent >= 31 && notePercent <= 35)) {
            System.out.println("注释等级为C");
        } else {
            System.out.println("注释等级为D");
        }

        // 空行等级 看空行行数占总行数的比例
        int emptyPercent = (int) (((float) emptyLineCount / sumLineCount) * 100);
        if (emptyPercent >= 15 && emptyPercent <= 25) {
            System.out.println("空行等级为A");
        } else if ((emptyPercent >= 10 && emptyPercent <= 14) || (emptyPercent >= 26 && emptyPercent <= 30)) {
            System.out.println("空行等级为B");
        } else if ((emptyPercent >= 5 && notePercent <= 9) || (emptyPercent >= 31 && emptyPercent <= 35)) {
            System.out.println("空行等级为C");
        } else {
            System.out.println("空行等级为D");
        }
    }

总结

总的来说,这次的小学期算是验证了我自己的水平,自己也是这么长时间以来第一次自己敲长一点的代码。这个程序的问题还是蛮多的,毕竟程序分析这个题目实在是太大了。如果读者有兴趣的话,可以自己去完善。

附录

这里附上全部代码:

/**
 * @ClassName: AnalyzeMain
 * @Description: 程序分析主函数
 * 没有解决的问题:1.没有做到读取特殊返回类型的函数,如struct,指针等
 * 2.代码如果不够规范的话,该程序就可能会失效 可适性不够强
 * @author: LiuGe
 * @date: 2020/9/10  9:07
 */
public class AnalyzeMain {

    public static int emptyLineCount = 0;
    public static int noteLineCount = 0;
    public static int noteBlockLineCount = 0;
    public static int sumLineCount = 0;
    public static int codeLineCount = 0;
    public static int methodCount = 0;
    public static int loopCount = 0;
    public static List<Integer> methodLineCounts = new ArrayList<>();
    // 只存储 没有使用
    public static List<Integer> methodLoopCounts = new ArrayList<>();
    public static int methodLineCount = 0;
    public static int sumMethodLineCount = 0;
    public static int avrMethodLineCount = 0;
    public static int methodLoopCount = 0;
    public static List<Integer> methodLoopLayerCounts = new ArrayList<>();
    public static int maxLayer = 0;

    public static void main(String[] args) {
        // 获取读取的文件流
        List<String> fileContent = getFileContent();
        // 行数统计
        getLines(fileContent);
        // 函数统计
        getMethods(fileContent);
        // 确定代码风格等级
        getCodeStyle();
    }

    /**
     * 读取文件流
     *
     * @return 读取完的文件流List
     */
    private static List<String> getFileContent() {
        // 将C程序作为流读入
        Scanner in = new Scanner(System.in);
        // 使用一个List结构来存储读取的代码
        List<String> fileContent = new ArrayList<>();
        System.out.println("请输入要分析的C程序文件路径:");
        String filePath = in.next();
        if (StrUtil.isEmpty(filePath)) {
            System.out.println("请输入正确的文件路径!");
            System.exit(0);
        }
        File file = new File(filePath);
        BufferedReader reader;
        String line;
        try {
            reader = FileUtil.getReader(file, "UTF-8");
            // 一次读入一行
            while ((line = reader.readLine()) != null) {
                fileContent.add(line);
            }
            // 读取完毕,关闭文件读入流和输入流
            reader.close();
            in.close();
        } catch (Exception e) {
            System.out.println("文件读写出错嘞~");
            System.exit(0);
        }
        return fileContent;
    }


    /**
     * 确定代码风格等级
     */
    private static void getCodeStyle() {
        // 代码等级 判断函数平均长度
        if (avrMethodLineCount >= 10 && avrMethodLineCount <= 15) {
            System.out.println("代码等级为A");
        } else if ((avrMethodLineCount >= 8 && avrMethodLineCount <= 9) || (avrMethodLineCount >= 16 && avrMethodLineCount <= 20)) {
            System.out.println("代码等级为B");
        } else if ((avrMethodLineCount >= 5 && avrMethodLineCount <= 7) || (avrMethodLineCount >= 21 && avrMethodLineCount <= 26)) {
            System.out.println("代码等级为C");
        } else {
            System.out.println("代码等级为D");
        }

        // 注释等级 看注释行数占总行数的比例
        int notePercent = (int) ((((float) (noteBlockLineCount + noteLineCount)) / sumLineCount) * 100);
        if (notePercent >= 15 && notePercent <= 25) {
            System.out.println("注释等级为A");
        } else if ((notePercent >= 10 && notePercent <= 14) || (notePercent >= 26 && notePercent <= 30)) {
            System.out.println("注释等级为B");
        } else if ((notePercent >= 5 && notePercent <= 9) || (notePercent >= 31 && notePercent <= 35)) {
            System.out.println("注释等级为C");
        } else {
            System.out.println("注释等级为D");
        }

        // 空行等级 看空行行数占总行数的比例
        int emptyPercent = (int) (((float) emptyLineCount / sumLineCount) * 100);
        if (emptyPercent >= 15 && emptyPercent <= 25) {
            System.out.println("空行等级为A");
        } else if ((emptyPercent >= 10 && emptyPercent <= 14) || (emptyPercent >= 26 && emptyPercent <= 30)) {
            System.out.println("空行等级为B");
        } else if ((emptyPercent >= 5 && notePercent <= 9) || (emptyPercent >= 31 && emptyPercent <= 35)) {
            System.out.println("空行等级为C");
        } else {
            System.out.println("空行等级为D");
        }
    }

    /**
     * 统计函数的方法
     * C程序里一个函数的特点:
     * 返回值类型 函数名(参数){}
     * 常见返回值类型:void int double float char
     * 指针结构体等特殊函数不再统计
     * 函数以'{' 作为开始,'}' 作为结束
     * 需要排除if for while do 等的大括号
     *
     * @param fileContent
     */
    private static void getMethods(List<String> fileContent) {
        boolean isInMethod = false;
        boolean isInOther = false;
        String methodName = null;
        for (String line : fileContent) {
            if (isInMethod) {
                // 在函数内,如果同时出现'('和')' 且不是while,if,for,证明调用了其他函数
                if (StrUtil.contains(line, "(")
                        && StrUtil.contains(line, ")")
                        && !StrUtil.contains(line, "for")
                        && !StrUtil.contains(line, "while")
                        && !StrUtil.contains(line, "if")) {
                    String[] lineSplits = StrUtil.split(line, "(");
                    if (StrUtil.contains(lineSplits[0], "=")) {
                        // 包含等号,说明是有返回值的函数,再进行一次切割
                        String[] splits = StrUtil.split(line, "=");
                        String replaceWithFen = StrUtil.replace(splits[1], ";", "");
                        String replaceWithZuo = StrUtil.replace(replaceWithFen, "(", "");
                        System.out.println(methodName + "函数调用了名为" + StrUtil.replace(replaceWithZuo, ")", "") + "的函数");
                    } else {
                        // 不包含等号,说明是没有返回值的函数,直接输出
                        String replaceWithFen = StrUtil.replace(lineSplits[0], ";", "");
                        System.out.println(methodName + "函数调用了名为" + StrUtil.replace(replaceWithFen, " ", "") + "的函数");
                    }
                }
                // 在方法内,找到方法开始的标志行,开始统计函数行数
                if (StrUtil.startWith(line, "{")) {
                    methodLineCount++;
                    continue;
                }
                // 如果嵌套定义函数,增加嵌套层数
                if (StrUtil.contains(line, "void")
                        || StrUtil.contains(line, "int")
                        || StrUtil.contains(line, "float")
                        || StrUtil.contains(line, "double")
                        || StrUtil.contains(line, "char")) {
                    // 包含返回值,且有右括号或者{ 表示该行为函数嵌套定义
                    if (StrUtil.endWith(line, ")") || StrUtil.endWith(line, "{")) {
                        methodLoopCount++;
                    }
                }
                methodLineCount++;

            }
            if (StrUtil.startWith(line, "void")
                    || StrUtil.startWith(line, "int")
                    || StrUtil.startWith(line, "float")
                    || StrUtil.startWith(line, "double")
                    || StrUtil.startWith(line, "char")) {
                // 满足条件,说明这行是函数定义
                isInMethod = true;
                String[] splits = StrUtil.split(line, " ");
                String[] split = StrUtil.split(splits[1], "(");
                methodName = split[0];

                // 统计函数个数
                methodCount++;
                // 如果这行有{ 说明是函数开始了,计数
                if (StrUtil.contains(line, "{")) {
                    methodLineCount++;
                }
            }
            if (StrUtil.contains(line, "while")
                    || StrUtil.contains(line, "if")
                    || StrUtil.contains(line, "for")
                    || StrUtil.contains(line, "do")) {
                // 证明是在其他大括号的结构里 (嵌套结构)
                isInOther = true;
                // 统计嵌套个数
                loopCount++;
            }
            // 遇到了最后一个 } 代表该函数到结尾了 (排除while,if,for do的大括号)
            if (StrUtil.startWith(line, "}") && isInMethod && !isInOther) {
                isInMethod = false;
                methodLoopLayerCounts.add(methodLoopCount);
                methodLineCounts.add(methodLineCount);
                methodLineCount = 0;
                methodLoopCount = 0;
            }

            // 在其他嵌套结构里,且到了尾部
            if (isInOther) {
                if (StrUtil.contains(line, "}")) {
                    isInOther = false;
                    methodLineCounts.add(loopCount);
                    loopCount = 0;
                }
            }

        }
        System.out.println("函数个数 = " + methodCount);
        for (Integer lineCount : methodLineCounts) {
            sumMethodLineCount += lineCount;
        }
        avrMethodLineCount = sumMethodLineCount / methodCount;
        System.out.println("函数平均长度 = " + avrMethodLineCount);
        // 输出函数最大嵌套层数
        maxLayer = CollUtil.max(methodLoopLayerCounts);
        System.out.println("函数最大嵌套层数 = " + maxLayer);
    }

    /**
     * 统计行数的方法
     *
     * @param fileContent
     */
    private static void getLines(List<String> fileContent) {
        boolean isInBlockNote = false;
        for (String line : fileContent) {
            // 位于段注释内,算段注释行
            if (isInBlockNote) {
                noteBlockLineCount++;
            }
            // 统计空行
            if (StrUtil.isEmpty(line) && !isInBlockNote) {
                emptyLineCount++;
            }

            // 统计行注释://
            if (StrUtil.contains(line, "//")) {
                noteLineCount++;
            }

            // 统计段注释:/* */
            if (StrUtil.contains(line, "/*")) {
                // 代表段注释开始了
                noteBlockLineCount++;
                isInBlockNote = true;
            }
            if (StrUtil.contains(line, "*/")) {
                // 代表段注释结束了
                isInBlockNote = false;
            }

            // 统计总行数
            sumLineCount++;
        }
        // 统计代码行:总行数-空行-行注释-段注释
        codeLineCount = sumLineCount - emptyLineCount - noteLineCount - noteBlockLineCount;

        // 输出各个行数
        System.out.println("总行数 = " + sumLineCount);
        System.out.println("空行数 = " + emptyLineCount);
        System.out.println("段注释行数 = " + noteBlockLineCount);
        System.out.println("行注释行数 = " + noteLineCount);
        System.out.println("代码行数 = " + codeLineCount);
    }
}
原文地址:https://www.cnblogs.com/wushenjiang/p/13677811.html