当前位置: 首页 > news >正文

【献给过去的自己】栈实现计算器(C语言)

背景

        记得在刚学C语言时,写了一篇栈实现计算器-CSDN博客文章。偶然间看到了文章的阅读量以及评论,居然有1.7w的展现和多条博友的点评,反馈。

        现在回过头来看,的确有许多不严谨的地方,毕竟当时分享文章时,还未毕业。理论和思维还不够严谨。但是我还依稀记得,班级上当时写出这个程序的同学,稀疏可数。所以在当时,还是有骄傲的资本的。本着对技术精益求精的态度,再通过本篇文章希望能够帮助刚接触C语言的朋友,也是给过去的自己一个满意的答复~

规则

        对于一个表达式,我们应该如何去识别它呢?当时,老师和我们说,按照如下规则进行解析即可。

        当时我们并不懂这个规则的由来,只知道按照这个规则去编程即可。再后来的工作中,因为考《软件设计师》资格证,了解到上述的规则,其实就是后缀表达式。同理还有前缀表达式中缀表达式

中缀表达式

        中缀表达式就是我们常用的一种算数表示方式。它的特点是操作符以中缀的方式处于操作数中间。但是中缀表达比较适合人类计算,对于计算机而言过于复杂。前缀表达式和后缀表达式对于计算机而言,更加友好。

        因此,我们想用程序实现计算器功能,有两种方式:

中缀表达式--> 前缀表达式-->计算

中缀表达式--> 后缀表达式-->计算

前缀表达式

        前缀表达式的运算符位于两个操作数之前,又称为前缀记法或波兰式。比如表达式(中缀)5+4,前缀表达式+ 5 4。因此使用前缀表达式进行计算,需要两个步骤。

  1. 如何将中缀表达式转换为前缀表达式

  2. 计算机如何识别前缀表达式并计算

中缀表达式转换前缀表达式

        根据文中描述,中缀表达式转换为前缀表达式的规则如下:

  1. 初始化两个栈:运算符栈S1和存储中间结果的栈S2;

  2. 从右至左扫描中缀表达式;

  3. 遇到操作数时,将其压入S2;

  4. 遇到运算符时,比较其与S1栈顶运算符的优先级;

    1. 如果S1为空,或栈顶运算符为右括号),则将此运算符入栈;

    2. 否则,若优先级比栈顶运算符的较高或相等,也将运算符压入S1;

    3. 否则,将S1栈顶的运算符弹出并压入到S2,再次转到4.1与S1中新的栈顶运算符相比较;

  5. 遇到括号时:

    1. 如果是右括号),则直接压入S1;

    2. 如果是左括号(,则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止,此时将这一对括号丢弃;

  6. 重复步骤25,直到表达式的最左边;

  7. 将S1中剩余的运算符依次弹出并压入S2;

  8. 依次弹出S2中的元素并输出,结果即为中缀表达式对应的前缀表达式。

        虽然规则很复杂,但是编码难度并不是很大,大家可以按照自己的技术能力尝试一下。

分析思路

        我们以表达式1+(2+3)*4-5举例。

        1. 因为输入表达式是字符串,后续我们需要从右往左扫描表达式,因此首先需要将字符串表达式中的运算符和操作数进行区分,可以用整型数组如下图:

        2. 根据25规则,进行分析。

       

        3. 弹出S2中的数据元素:- + 1 * + 2 3 4 5;

代码示例

我的代码示例如下:

#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<stdint.h>
#include<stdbool.h>#define STACK_LEN 1024/** 中缀表达式栈*/
static int32_t g_infix_expression[1024] = {0};/** 前缀表达式栈*/
static int32_t g_prefix_expression[1024] = {0};/** 后缀表达式栈*/
static int32_t g_suffix_expression[1024] = {0};/*** @brief 将输入的字符串表达式转换为中缀表达式** @param [in] expression 字符串表达式* @return int 0 成功 non-0 失败* */
int expression2infix(const char* expression)
{if(expression == NULL){printf("input error\n");return -1;}int dataTmp = 0; //表达式中的操作数bool dataFlag = false; // 操作数标识,表示当前是否有数据需要入栈const char* ptr = expression;int32_t* infix_index = g_infix_expression;printf("expression = %s\n",expression);while(*ptr != '\0'){/** 字符为数字*/if('0' <= *ptr  && *ptr <= '9'){dataTmp = dataTmp*10 +(*ptr - '0');dataFlag = true;}/**字符为操作符或括号*/else if(*ptr == '+' || *ptr == '-' || *ptr == '*' || *ptr == '/' || *ptr == '(' || *ptr == ')'){if(dataFlag == true){*(infix_index++) = dataTmp;dataFlag = false;dataTmp = 0;}*(infix_index++) = *ptr;}else{printf("wrong exptrssion\n");return -1;}ptr++;}/**将最后一个操作数,入栈*/if(dataFlag == true){*(infix_index++) = dataTmp;dataFlag = false;dataTmp = 0;}return 0;
}/*** @brief 将中缀表达式转换为前缀表达式** @return int 0 成功 non-0 失败* */
int infix2prefixExpression()
{/**初始化运算符栈和中间结果栈*/int32_t stack_s1[STACK_LEN] = {0};int32_t stack_s1_top = 0;int32_t stack_s2[STACK_LEN] = {0};int32_t stack_s2_top = 0; int32_t * index = g_infix_expression;/**获取中缀表达式最右侧操作数*/while(*(index+1) != 0){index++;}while(index != g_infix_expression){/** 操作符*/if(*index == '+' || *index == '-' || *index == '*' || *index == '/'){while(true){/**S1为空,或栈顶运算符为右括号),则将此运算符入栈*/if(stack_s1_top == 0 || stack_s1[stack_s1_top-1] == ')' || stack_s1[stack_s1_top-1] == '-'|| stack_s1[stack_s1_top-1] == '+'){stack_s1[stack_s1_top++] = *index;break;}stack_s2[stack_s2_top++] = stack_s1[stack_s1_top-1];stack_s1[stack_s1_top-1] = 0;stack_s1_top = stack_s1_top -1;}}/**左括号* 则依次弹出S1栈顶的运算符,并压入S2,直到遇到右括号为止*/else if(*index == '('){while(true){/**异常*/if(stack_s1_top == 0){printf("infix experssion worong\n");return -1;}/**遇到右括号,丢弃括号*/if(stack_s1[stack_s1_top-1] == ')'){stack_s1[stack_s1_top-1] = 0;stack_s1_top = stack_s1_top -1;break;}/**其它符号需要入栈S2*/else{stack_s2[stack_s2_top++] = stack_s1[stack_s1_top-1];stack_s1[stack_s1_top-1] = 0;stack_s1_top--;}}}/**右括号* 直接入运算符栈s1*/else if(*index == ')'){stack_s1[stack_s1_top++] = *index;}/** 操作数* 直接加入栈s2*/else{stack_s2[stack_s2_top++] = *index;}index--;
#if 0printf("==============\n");printf("stack_s1=");for(int i = 0 ; i < stack_s1_top; i++){(stack_s1[i] > 9) ? (printf("%c ",stack_s1[i])):(printf("%d ",stack_s1[i]));}printf("\n");printf("stack_s2=");for(int i = 0 ; i < stack_s2_top; i++){(stack_s2[i] > 9) ? (printf("%c ",stack_s2[i])):(printf("%d ",stack_s2[i]));}printf("\n");
#endif  }/**将最左侧操作数压入s2*/stack_s2[stack_s2_top++] = *index;/**将s1中的符号压入s2*/for(int i = stack_s1_top - 1; i >= 0; i-- ){stack_s2[stack_s2_top++] = stack_s1[i];stack_s1[i] = 0;    }/**将s2中的数据弹出,放入前缀表达式栈中*/for(int i = 0 ; stack_s2_top > 0; i++,stack_s2_top--){g_prefix_expression[i] = stack_s2[stack_s2_top-1];}return 0;
}int main(int argc,char* argv[])
{if(argc != 2){printf("please input experssion\n");return -1;}int32_t iRet = 0;iRet = expression2infix(argv[1]);if(iRet == 0){for(int i = 0 ; i < STACK_LEN && g_infix_expression[i] != 0; i++){if(g_infix_expression[i] == '+' || g_infix_expression[i] == '-' || g_infix_expression[i] == '*' || g_infix_expression[i] == '/'){printf("%c ",g_infix_expression[i]);}else{printf("%d ",g_infix_expression[i]);}}printf("\n");}iRet = infix2prefixExpression();if(iRet == 0){for(int i = 0 ; i < STACK_LEN && g_prefix_expression[i] != 0; i++){if(g_infix_expression[i] == '+' || g_infix_expression[i] == '-' || g_infix_expression[i] == '*' || g_infix_expression[i] == '/'){printf("%c ",g_infix_expression[i]);}else{printf("%d ",g_infix_expression[i]);}}printf("\n");}prefixExpressionCaculate();return 0;
}

前缀表达式计算

前缀表达式的计算规则如下:

  1. 从右至左扫描表达式;

  2. 遇到数字,压入栈中;

  3. 遇到运算符,弹出栈顶的两个数,并用运算符对这两个数做相应的计算,并将结果入栈;

  4. 重复上述2,3步骤,直到表达式最左端,最后的值为表达式的结果。

分析思路

        以上述后缀表达式举例:- + 1 * + 2 3 4 5

        得出结果为16。

代码示例

新增prefixExpressionCaculate接口。代码如下:

/*** @brief 将前缀表达式进行计算** @return int 0 成功 non-0 失败* */
int prefixExpressionCaculate()
{/**结果栈*/int32_t stack[1024] = {0};int32_t stack_len = 0;/**临时结果*/int32_t tmpResult = 0;int32_t data1 = 0;int32_t data2 = 0;/**获取后缀表达式的最右侧操作数*/int32_t* index = g_prefix_expression;while(*(index+1) != 0){index++;}while(index >= g_prefix_expression){/**弹出栈顶的两个数,并用运算符对这两个数做相应的计算,并将结果入栈*/if(*index == '+' || *index == '-' || *index == '*' || *index == '/'){data1 = stack[stack_len-1];data2 = stack[stack_len-2];if(*index == '+'){tmpResult = data1 + data2;}else if(*index == '-'){tmpResult = data1 - data2;}else if(*index == '*'){tmpResult = data1 * data2;}else if(*index == '/'){tmpResult = data1 / data2;}else{printf("worng prefixExperssion\n");return -1;}stack[stack_len-1] = 0;stack[stack_len-2] = tmpResult;stack_len --;}/**遇到数字,压栈*/else{stack[stack_len] = *index;stack_len ++;}index --;}printf("result = %d\n",stack[0]);return 0;
}

演示

后缀表达式

        后缀表达式与前缀表达式类似,只是运算符位于两个相应操作数之后,后缀表达式也称为后缀记法或逆波兰式。同样,我们需要解决两个问题。

  1. 如何将中缀表达式转换为后缀表达式

  2. 后缀表达式的计算规则

中缀表达式转后缀表达式

根据文中描述,中缀表达式转换为后缀表达式的规则如下:

  1. 初始化两个栈:运算符栈S1和存储中间结果的栈S2;

  2. 从左至右扫描中缀表达式

  3. 遇到操作数时,将其压入S2;

  4. 遇到运算符时,比较其与S1栈顶运算符的优先级;

    1. 如果S1为空,或栈顶运算符为左括号(,则将此运算符入栈;

    2. 否则,若优先级比栈顶运算符的高,也将运算符压入S1;(注意是必须为高,相同或低于都不行)

    3. 否则,将S1栈顶的运算符弹出并压入到S2,再次转到4.2与S1中新的栈顶运算符相比较;

  5. 遇到括号时:

    1. 如果是左括号(,则直接压入S1;

    2. 如果是右括号),则依次弹出S1栈顶的运算符,并压入S2,直到遇到左括号为止,此时将这一对括号丢弃;

  6. 重复步骤25,直到表达式的最右边;

  7. 将S1中剩余的运算符依次弹出并压入S2;

  8. 依次弹出S2中的元素并输出,结果即为中缀表达式对应的后缀表达式。

后缀表达式计算规则

后缀表达式的计算规则如下:

  1. 从左至右扫描表达式;

  2. 遇到数字,压入栈中;

  3. 遇到运算符,弹出栈顶的两个数,并用运算符对这两个数做相应的计算,并将结果入栈;

  4. 重复上述2,3步骤,直到表达式最右端,最后的值为表达式的结果。

        后缀表达式的代码示例可以参考前缀表达式的分析思路和代码,大家可以尝试编写。

总结

        时间流逝,在竞争激烈的社会背景下,我们的身处IT行业,不断逼迫自己去学习,去成长。但是总会觉得自己做的还不够。为什么总是赶不上别人的脚步,陷入怀疑自我的处境。

        朋友们,偶尔回头看看来时路上的自己,你会发现,你一直在成长,你的努力一直是正向反馈着你,不要轻视自己的努力。感谢csdn给予记录成长的平台,也感谢一直努力的自己。共勉~

参考文档

前缀表达式、中缀表达式和后缀表达式 - 乘月归 - 博客园

数据结构和算法(六):前缀、中缀、后缀表达式

栈实现计算器-CSDN博客

相关文章:

【献给过去的自己】栈实现计算器(C语言)

背景 记得在刚学C语言时&#xff0c;写了一篇栈实现计算器-CSDN博客文章。偶然间看到了文章的阅读量以及评论&#xff0c;居然有1.7w的展现和多条博友的点评&#xff0c;反馈。 现在回过头来看&#xff0c;的确有许多不严谨的地方&#xff0c;毕竟当时分享文章时&#xff0c;还…...

如何利用ChatGPT撰写学术论文?

在阅读全文前请注意&#xff0c;本文是利用ChatGPT“辅助完成”而不是“帮写”学术论文&#xff0c;请一定要注意学术规范&#xff01; 本文我将介绍如何使用清晰准确的“指令”让ChatGPT帮助我们在论文写作上提高效率&#xff0c;希望通过本文的指导&#xff0c;读者能够充分…...

【PG】PostgreSQL高可用方案repmgr管理之配置文件

1 配置文件 1.1 配置文件格式 repmgr.conf是一个纯文本文件&#xff0c;每行包含一个参数/值组合。 空格是无关紧要的&#xff08;除了在带引号的参数值内&#xff09;&#xff0c;并且空行将被忽略。#将该行的其余部分指定为注释。不是简单标识符或数字的参数值应该用单引号…...

labelme自动标注工具

可以实现多图中相同目标的追踪&#xff0c;自动标注目标位置&#xff0c;速度极快&#xff0c;有需要评论...

【C++学习手札】模拟实现vector

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;くちなしの言葉—みゆな 0:37━━━━━━️&#x1f49f;──────── 5:28 &#x1f504; ◀️ ⏸ ▶️ ☰…...

Python将图片按照表格形式排列

图片按照表格的形式排列&#xff0c;可以使用图像处理库Pillow来实现 事例代码 from PIL import Image, ImageDraw# 创建一个画布&#xff0c;用来存放排列后的图片 canvas Image.new(RGB, (800, 600), white)# 读取图片 im1 Image.open(image1.jpg) im2 Image.open(image…...

Linux 简要命令记录

1、设置时区&#xff1a; #设为上海&#xff1a; timedatectl set-timezone Asia/Shanghai #搜索特定时区 timedatectl list-timezone2、修改时间&#xff1a; #设定系统时间 date -s "2023-11-16 22:30:00" #同步写入BIOS hwclock -w3、fdisk分区 rootheihei:~# …...

深度学习与深度强化学习

1. 深度学习中卷积层的作用是什么&#xff1f;全连接层的作用是什么&#xff1f;二者有什么联系和区别&#xff1f; 在深度学习中&#xff0c;卷积层&#xff08;Convolutional Layer&#xff09;和全连接层&#xff08;Fully Connected Layer&#xff09;是神经网络中常见的两…...

C++函数重载中形参是引用类型和常量引用类型的调用方法

void fun(int &a) {cout<<"调用func(int &a)<<endl; }void fun(const int &a) {cout<<"调用func(const int &a)<<endl; }int main() {// 1.调用引用类型的函数int a10;func(a);// 2.调用常量引用类型的函数&#xff0c;因为…...

Quest 3期间Sui上游戏处理了数百万笔交易

Sui固有的可扩展性和低且可预测的gas费使其成为Web3游戏的理想平台。在Quest 3中&#xff0c;参与的游戏项目处理了数百万笔交易&#xff0c;这毫无疑问地展示了Sui卓越的能力。 Quest 3的主题是游戏&#xff0c;让开发者有机会向潜在玩家介绍他们激动人心的创作。鼓励这些玩家…...

Python中如何定义类、基类、函数和变量?

在Python中&#xff0c;定义类、基类、函数和变量是非常常见的操作。以下是简单的示例&#xff1a; 定义类&#xff1a; class Animal:def __init__(self, name):self.name namedef make_sound(self):passclass Dog(Animal):def make_sound(self):return "Woof!"上…...

打开文件 和 文件系统的文件产生关联

补充1&#xff1a;硬件级别磁盘和内存之间数据交互的基本单位 OS的内存管理 内存的本质是对数据临时存/取&#xff0c;把内存看成很大的缓冲区 物理内存和磁盘交互的单位是4KB&#xff0c;磁盘中未被打开的文件数据块也是4KB&#xff0c;所以磁盘中页帧也是4KB&#xff0c;内存…...

【Rust】快速教程——模块mod与跨文件

前言 道尊&#xff1a;没有办法&#xff0c;你的法力已经消失&#xff0c;我的法力所剩无几&#xff0c;除非咱们重新修行&#xff0c;在这个世界里取得更多法力之后&#xff0c;或许有办法下降。——《拔魔》 \;\\\;\\\; 目录 前言跨文件mod多文件mod 跨文件mod //my_mod.rs…...

crontab定时任务是否执行

centos查看 crontab 是否启动 systemctl status crond.service 查看cron服务的启动状态 systemctl start crond.service 启动cron服务[命令没有提示] systemctl stop crond.service 停止cron服务[命令没有提示] systemctl restart crond.service 重启cron服务[命令没有提示] s…...

MATLAB程序设计:牛顿迭代法

function xnewton(x0,e,N,fx) %输入x0,误差限e,迭代次数N和函数Fx k1; while k<Nif subs(diff(fx),x0)0disp("输出奇异标志");break;endx1x0-subs(fx,x0)/subs(diff(fx),x0);if abs(x1-x0)<ebreak;endx0x1;kk1; end if k<Ndisp(x1); elsedisp("迭代失败…...

B031-网络编程 Socket Http TomCat

目录 计算机网络网络编程相关术语IP地址ip的概念InerAdress的了解与测试 端口URLTCP、UDP和7层架构TCPUDPTCP与UDP的区别和联系TCP的3次握手七层架构 Socket编程服务端代码客户端代码 http协议概念Http报文 Tomcat模拟 计算机网络 见文档 网络编程相关术语 见文档 IP地址 …...

gRPC之metadata

1、metadata 服务间使用 Http 相互调用时&#xff0c;经常会设置一些业务自定义 header 如时间戳、trace信息等&#xff0c;gRPC使用 HTTP/2 协议自然也是支持的&#xff0c;gRPC 通过 google.golang.org/grpc/metadata 包内的 MD 类型提供相关的功能接口。 1.1 类型定义 /…...

【OpenCV实现图像:OpenCV进行OCR字符分割】

文章目录 概要基本概念读入图像图像二值化小结 概要 在处理OCR&#xff08;Optical Character Recognition&#xff0c;光学字符识别&#xff09;时&#xff0c;利用传统的图像处理方法进行字符切分仍然是一种有效的途径。即便当前计算机视觉领域主导的是卷积神经网络&#xf…...

景联文科技入选量子位智库《中国AIGC数据标注产业全景报告》数据标注行业代表机构

量子位智库《中国AIGC数据标注产业全景报告》中指出&#xff0c;数据标注处于重新洗牌时期&#xff0c;更高质量、专业化的数据标注成为刚需。未来五年&#xff0c;国内AI基础数据服务将达到百亿规模&#xff0c;年复合增长率在27%左右。 基于数据基础设施建设、大模型/AI技术理…...

ClickHouse SQL操作

基本上来说传统关系型数据库&#xff08;以MySQL为例&#xff09;的SQL语句&#xff0c;ClickHouse基本都支持&#xff0c;这里不会从头讲解SQL语法只介绍ClickHouse与标准SQL&#xff08;MySQL&#xff09;不一致的地方。 1 Insert 基本与标准SQL&#xff08;MySQL&#xff09…...

GitHub开源项目分享:SenseVoice-Small模型微调与领域适配工具链

GitHub开源项目分享&#xff1a;SenseVoice-Small模型微调与领域适配工具链 最近在语音识别领域&#xff0c;一个挺有意思的现象是&#xff0c;很多通用模型虽然能力很强&#xff0c;但一遇到专业领域的对话&#xff0c;比如医生讨论病例、律师分析法条&#xff0c;准确率就容…...

基于cv_unet_image-colorization的Python爬虫实战:自动化图像数据集着色

基于cv_unet_image-colorization的Python爬虫实战&#xff1a;自动化图像数据集着色 为计算机视觉项目快速构建高质量的彩色图像数据集 在计算机视觉项目中&#xff0c;获取高质量的标注数据集往往是最耗时耗力的环节。特别是当我们需要大量彩色图像数据时&#xff0c;手动收集…...

【Python并发革命】:GIL解除后首个生产级无锁插件生态正式开放下载(限时72小时)

第一章&#xff1a;Python并发革命的里程碑意义 Python 并发模型的演进并非渐进式改良&#xff0c;而是一场深刻重塑编程范式的革命。从早期依赖线程与锁的阻塞式模型&#xff0c;到 asyncio 的异步 I/O 抽象、async/await 语法糖的引入&#xff0c;再到结构化并发&#xff08;…...

RWKV7-1.5B-g1a轻量部署方案:中小企业AI落地首选,年省GPU成本超40%

RWKV7-1.5B-g1a轻量部署方案&#xff1a;中小企业AI落地首选&#xff0c;年省GPU成本超40% 1. 为什么选择RWKV7-1.5B-g1a 在当今AI技术快速发展的背景下&#xff0c;中小企业往往面临高昂的GPU计算成本和技术门槛。rwkv7-1.5B-g1a作为一款基于RWKV-7架构的多语言文本生成模型…...

FPGA网络加速入门:拆解Xilinx 7系列GTP与1G/2.5G Ethernet PCS/PMA IP核,搞懂SGMII接口那些事

FPGA网络加速实战&#xff1a;从Xilinx GTP架构到SGMII接口的深度解析 在FPGA高速通信领域&#xff0c;以太网接口设计一直是工程师面临的核心挑战之一。当我们需要在Xilinx 7系列FPGA上实现1G/2.5G以太网功能时&#xff0c;GTP收发器与PCS/PMA IP核的配置往往成为项目成败的关…...

智能影像雅鉴系统:丹青识画在美术馆导览中的落地实操

智能影像雅鉴系统&#xff1a;丹青识画在美术馆导览中的落地实操 1. 艺术与科技的完美融合 1.1 传统导览的痛点与革新 在美术馆参观时&#xff0c;我们常常面临这样的困境&#xff1a;站在一幅名画前&#xff0c;却无法真正理解其深层意境&#xff1b;面对珍贵文物&#xff…...

内容营销对 SEO 有什么影响

<h3 id"seo">内容营销对 SEO 有什么影响</h3> <h4 id"">引言</h4> <p>在当今数字化时代&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;和内容营销被广泛认为是网站流量和业务增长的关键驱动因素。许多企业在网站建设…...

RexUniNLU异常检测能力:识别虚假评论与垃圾内容

RexUniNLU异常检测能力&#xff1a;识别虚假评论与垃圾内容 1. 效果惊艳开场 打开任何一个内容平台&#xff0c;评论区总是最热闹的地方。但你可能不知道&#xff0c;每10条评论里&#xff0c;就有2-3条是机器生成的广告、水军刷的好评&#xff0c;或者是纯粹的垃圾信息。这些…...

别再为PDF表格头疼了!用Nougat+LangChain搞定RAG系统里的表格问答(附完整代码)

突破PDF表格解析瓶颈&#xff1a;Nougat与LangChain构建智能问答系统实战 每次打开满是表格的学术论文PDF时&#xff0c;你是否也经历过这样的挫败感&#xff1f;传统OCR工具要么把跨页表格拆得七零八落&#xff0c;要么将复杂的LaTeX公式识别成乱码&#xff0c;更别提准确关联…...

dexcount-gradle-plugin最佳实践:提升Android应用性能的10个技巧

dexcount-gradle-plugin最佳实践&#xff1a;提升Android应用性能的10个技巧 【免费下载链接】dexcount-gradle-plugin A Gradle plugin to report the number of method references in your APK on every build. 项目地址: https://gitcode.com/gh_mirrors/de/dexcount-grad…...