• 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);
        }
    }
    
  • 相关阅读:
    react hook超实用的用法和技巧分析
    React.js事件处理的三种写法
    基于虚拟 DOM 库 (Snabbdom) 的迷你 React
    函数式的React
    Node.js特点和适用场景
    浅谈小程序运行机制
    NodeJS 和 C++ 之间的类型转换
    angular多语言配置
    react传递方法 警告
    2019第12周知识总结
  • 原文地址:https://www.cnblogs.com/wushenjiang/p/13677811.html
Copyright © 2020-2023  润新知