程序员修仙之路-数据结构之 CXO让我做一个计算器

640?wx_fmt=gif

菜菜呀,个税最近改革了,我得重新计算你的工资呀,我需要个计算器,你开发一个吧

640?wx_fmt=pngCEO,CTO,CFO于一身的CXO640?wx_fmt=gif

X总,咱不会买一个吗?

640?wx_fmt=png菜菜640?wx_fmt=jpeg

那不得花钱吗,一块钱也是钱呀··这个计算器支持加减乘除运算就行,很简单

640?wx_fmt=pngCEO,CTO,CFO于一身的CXO640?wx_fmt=gif

(尼玛)那能不能给我涨点工资呀?

640?wx_fmt=png菜菜640?wx_fmt=jpeg

公司现在很困难,你这个计算器关系到公司的存亡,你要注意呀!!

640?wx_fmt=pngCEO,CTO,CFO于一身的CXO640?wx_fmt=gif

(关于撇开话题佩服的五体投地)好吧X总,我尽快做

640?wx_fmt=png菜菜640?wx_fmt=jpeg

给你一天时间,我这里着急要用

640?wx_fmt=pngCEO,CTO,CFO于一身的CXO640?wx_fmt=gif

.........

640?wx_fmt=png菜菜640?wx_fmt=jpegCXO的需求果然还在继续,深呼吸,深呼吸 .......640?wx_fmt=png640?wx_fmt=png

有人说数据结构是为算法服务的,我还要在加一句:数据结构和算法都是为业务服务的!!


CXO的需求果然不同凡响,又让菜菜想到了新的数据结构:

640?wx_fmt=png


◆◆栈的特性◆◆


定义

栈(stack)又名堆栈,它是一种运算受限的线性表。其限制是仅允许在表的一端进行插入和删除运算。这一端被称为栈顶,相对的,把另一端称为栈底。向一个栈插入新元素又称作进栈、入栈或压栈,它是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

栈作为一种数据结构,其中有几个特性需要提起大家注意:


1.  操作受限:何为操作受限?在栈的操作中,一般语言中针对栈的操作只有两种:入栈和出栈。并且操作只发生在栈的顶部。 有的同学会问,我用其他数据结构也一样能实现栈的效果。不错,但是每种数据结构都有自己的使用场景,没有一种绝对无用的数据结构。

2.  栈在数据结构上属于一种线性表,满足后进先出的原则。这也是栈的最大特性,几乎大部分后进先出的场景都可以使用栈这个容器。比如一个函数的调用过程中,局部变量的存储就是栈原理。当执行一个函数结束的时候,局部变量其实最先释放的是最后的局部变量。


640?wx_fmt=png


◆◆实现◆◆


        在内存分布上栈是用什么实现的呢?既然栈是一种线性结构,也就说可以用线性的内存分布数据结构来实现。


1. 数组实现栈(顺序栈):数组是在内存分布上连续的一种数据结构。经过以前的学习,我们知道数组的容量是不变的。如果业务上可以知道一个栈的元素的最大数量,我们完全可以用数组来实现。为什么这么说?因为数组的扩容在某些时候性能是比较低的。因为需要开辟新空间,并发生复制过程。

class MyStack

    {

        //数组容器

        int[] container = new int[100];

        //栈顶元素的索引

        int TopIndex = -1;


        //入栈操作

        public void Push(int newValue)

        {

            if (TopIndex >= 99)

            {

                return ;

            }

            TopIndex++;

            container[TopIndex] = newValue;

        }

        //出栈操作

        public int Pop()

        {

            if (TopIndex < 0)

            {

                return 0;

            }

            var topValue = container[TopIndex];

            TopIndex--;

            return topValue;

        }

    }


2. 链表实现栈(链式栈):为了应对数组的扩容问题,我们可以用链表来实现栈。栈的顶部元素永远指向链表的头元素即可。具体代码有兴趣的同学可以实现一下


由以上可以看出,栈其实是基于基础数据结构之上的一个具体业务形式的封装。即:先进后出。


◆◆性能◆◆


        基于数组的栈我们暂且只讨论未发生数组重建的场景下。无论是数组实现还是链表实现,我们发现栈的内部其实是有一个指向栈顶元素的指针,不会发生遍历数组或者链表的情形,所以栈的出栈操作时间复杂度为O(1)。

        至于入栈,如果你看过我以前介绍数组和链表的文章,你可以知道,给一个数组下标元素赋值的操作时间复杂度为O(1),在链表头部添加一个元素的操作时间复杂度也是O(1)。所以无论是数组还是链表实现栈,入栈操作时间复杂度也是O(1)。并且栈只有入栈出栈两种操作,比其他数据结构有N个操作方法要简单很多,也不容易出错。

        至于发生数组重建,copy全部数据的过程其实是一个顺序栈最坏的时间复杂度,因为和原数组的元素个数n有关,所以时间复杂度为O(n)


◆◆设计要点◆◆


        那一个计算器怎么用栈来实现呢?其实很多计算器就是通过两个栈来实现的,其中一个栈保存操作的数,另一个栈保存运算符。

        我们从左到右遍历表达式,当遇到数字,我们直接压入操作数栈;当遇到操作符的时候,当前操作符与操作符栈顶的元素比较优先级(先乘除后加减的原则)。如果当前运算符比栈顶运算符优先级高,那说明不需要执行栈顶运算符运算,我们直接将当前运算符也入栈;

        如果当前运算符比栈顶运算符优先级低,那说明该执行栈顶运算符的运算了。然后出栈运算符栈顶元素,数据栈顶两个元素,然后进行相关运算,然后把运算结果再次压入数据栈。


◆◆来一发吧◆◆

golang版本

特别鸣谢公司朋友亮亮提供golang代码


  1package stack
 2
 3import (
 4    "errors"
 5    "fmt"
 6)
 7
 8type Stack struct {
 9    Element []interface{} //Element
10}
11
12func NewStack() *Stack {
13    return &Stack{}
14}
15
16func (stack *Stack) Push(value ...interface{}) {
17    stack.Element = append(stack.Element, value...)
18}
19
20//返回下一个元素
21func (stack *Stack) Top() (value interface{}) {
22    if stack.Size() > 0 {
23        return stack.Element[stack.Size()-1]
24    }
25    return nil //read empty stack
26}
27
28//返回下一个元素,并从Stack移除元素
29func (stack *Stack) Pop() (value interface{}) {
30    if stack.Size() > 0 {
31        d := stack.Element[stack.Size()-1]
32        stack.Element = stack.Element[:stack.Size()-1]
33        return d
34    }
35    return nil
36}
37
38//交换值
39func (stack *Stack) Swap(other *Stack) {
40    switch {
41    case stack.Size() == 0 && other.Size() == 0:
42        return
43    case other.Size() == 0:
44        other.Element = stack.Element[:stack.Size()]
45        stack.Element = nil
46    case stack.Size() == 0:
47        stack.Element = other.Element
48        other.Element = nil
49    default:
50        stack.Element, other.Element = other.Element, stack.Element
51    }
52    return
53}
54
55//修改指定索引的元素
56func (stack *Stack) Set(idx int, value interface{}) (err error) {
57    if idx >= 0 && stack.Size() > 0 && stack.Size() > idx {
58        stack.Element[idx] = value
59        return nil
60    }
61    return errors.New("Set失败!")
62}
63
64//返回指定索引的元素
65func (stack *Stack) Get(idx int) (value interface{}) {
66    if idx >= 0 && stack.Size() > 0 && stack.Size() > idx {
67        return stack.Element[idx]
68    }
69    return nil //read empty stack
70}
71
72//Stack的size
73func (stack *Stack) Size() int {
74    return len(stack.Element)
75}
76
77//是否为空
78func (stack *Stack) Empty() bool {
79    if stack.Element == nil || stack.Size() == 0 {
80        return true
81    }
82    return false
83}
84
85//打印
86func (stack *Stack) Print() {
87    for i := len(stack.Element) - 1; i >= 0; i-- {
88        fmt.Println(i, "=>", stack.Element[i])
89    }
90}
91//========================分割线==============================//
92package calculator
93
94import (
95    "calculator/stack"
96    "strconv"
97)
98
99type Calculator struct{}
100
101var DataStack *stack.Stack
102var OperatorStack *stack.Stack
103
104func NewCalculator() *Calculator {
105    DataStack = stack.NewStack()
106    OperatorStack = stack.NewStack()
107    return &Calculator{}
108}
109
110func (c *Calculator) Cal(dataOrOperator string) int {
111
112    if data, ok := strconv.ParseInt(dataOrOperator, 1064); ok == nil {
113        //如果是数据直接入数据栈
114        // fmt.Println(dataOrOperator)
115        DataStack.Push(data)
116    } else {
117
118        //如果是操作符,和栈顶操作符比较优先级,如果大于栈顶,则直接入栈,否则栈顶元素出栈 进行操作
119        if OperatorStack.Size() <= 0 {
120            OperatorStack.Push(dataOrOperator)
121        } else {
122            //当前运算符的优先级
123            currentOpePrecedence := operatorPrecedence(dataOrOperator)
124            //当前运算符栈顶元素的优先级
125            stackTopOpePrecedence := operatorPrecedence(OperatorStack.Top().(string))
126            if currentOpePrecedence > stackTopOpePrecedence {
127                //如果当前运算符的优先级大于栈顶元素的优先级,则入栈
128                OperatorStack.Push(dataOrOperator)
129            } else {
130                //运算符栈顶元素出栈,数据栈出栈两个元素,然后进行运算
131                stackOpe := OperatorStack.Pop()
132                data2 := DataStack.Pop()
133                data1 := DataStack.Pop()
134
135                ret := calculateData(stackOpe.(string), data1.(int64), data2.(int64))
136                DataStack.Push(ret)
137                OperatorStack.Push(dataOrOperator)
138            }
139        }
140    }
141    return 0
142}
143
144func (c *Calculator) GetResult() int64 {
145    var ret int64
146    for {
147
148        if OperatorStack.Size() > 0 {
149            stackOpe := OperatorStack.Pop()
150            data2 := DataStack.Pop()
151            data1 := DataStack.Pop()
152
153            ret = calculateData(stackOpe.(string), data1.(int64), data2.(int64))
154
155            DataStack.Push(ret)
156        } else {
157            break
158        }
159    }
160
161    return ret
162}
163
164func calculateData(operatorString string, data1, data2 int64) int64 {
165    switch operatorString {
166    case "+":
167        return data1 + data2
168    case "-":
169        return data1 - data2
170    case "*":
171        return data1 * data2
172    case "/":
173        return data1 + data2
174    default:
175        return 0
176    }
177}
178
179func operatorPrecedence(a string) int {
180    i := 0
181    switch a {
182    case "+":
183        i = 1
184    case "-":
185        i = 1
186    case "*":
187        i = 2
188    case "/":
189        i = 2
190    }
191    return i
192}
193//========================分割线==============================//
194package main
195
196import (
197    "calculator/calculator"
198    "flag"
199    "fmt"
200)
201
202var (
203    inputStr = flag.String("input""""请输入...")
204)
205
206func main() {
207    flag.Parse()
208
209    var lstAllData []string
210    var tempData string
211
212    rs := []rune(*inputStr)
213    for i := 0; i < len(rs); i++ {
214        if string(rs[i]) == "+" || string(rs[i]) == "-" || string(rs[i]) == "*" || string(rs[i]) == "/" {
215            lstAllData = append(lstAllData, tempData)
216            lstAllData = append(lstAllData, string(rs[i]))
217            tempData = ""
218        } else {
219            tempData += string(rs[i])
220        }
221        if i == len(rs)-1 {
222            lstAllData = append(lstAllData, tempData)
223        }
224    }
225
226    ca := calculator.NewCalculator()
227    for _, v := range lstAllData {
228        ca.Cal(v)
229    }
230    ret := ca.GetResult()
231    fmt.Println(ret)
232}



c#版本

  1class Program
 2    {
 3        static void Main(string[] args)
 4        
{
 5            List<string> lstAllData = new List<string>();
 6            //读取输入的表达式,并整理
 7            string inputStr = Console.ReadLine();
 8            string tempData = "";
 9            for (int i = 0; i < inputStr.Length; i++)
10            {
11                if (inputStr[i] == '+' || inputStr[i] == '-' || inputStr[i] == '*' || inputStr[i] == '/')
12                {
13                    lstAllData.Add(tempData);
14                    lstAllData.Add(inputStr[i].ToString());
15                    tempData = "";
16                }
17                else
18                {
19                    tempData += inputStr[i];
20                }
21                if(i== inputStr.Length - 1)
22                {
23                    lstAllData.Add(tempData);
24                }
25            }
26            foreach (var item in lstAllData)
27            {
28                Calculator.Cal(item.ToString());
29            }
30            var ret = Calculator.GetResult();
31            Console.WriteLine(ret);
32            Console.Read();
33        }
34
35    }
36    //计算器
37    class Calculator
38    {
39        //存放计算数据的栈
40        static Stack<int> DataStack = new Stack<int>();
41        //存放操作符的栈
42        static Stack<string> OperatorStack = new Stack<string>();
43        public static int Cal(string dataOrOperator)
44        
{
45            int data;
46            bool isData = int.TryParse(dataOrOperator, out data);
47            if (isData)
48            {
49                //如果是数据直接入数据栈
50                DataStack.Push(data);
51            }
52            else
53            {
54                //如果是操作符,和栈顶操作符比较优先级,如果大于栈顶,则直接入栈,否则栈顶元素出栈 进行操作
55                if (OperatorStack.Count <= 0)
56                {
57                    OperatorStack.Push(dataOrOperator);
58                }
59                else
60                {
61                    //当前运算符的优先级
62                    var currentOpePrecedence = OperatorPrecedence(dataOrOperator);
63                    //当前运算符栈顶元素的优先级
64                    var stackTopOpePrecedence = OperatorPrecedence(OperatorStack.Peek());
65                    if (currentOpePrecedence > stackTopOpePrecedence)
66                    {
67                        //如果当前运算符的优先级大于栈顶元素的优先级,则入栈
68                        OperatorStack.Push(dataOrOperator);
69                    }
70                    else
71                    {
72                        //运算符栈顶元素出栈,数据栈出栈两个元素,然后进行运算
73                        var stackOpe = OperatorStack.Pop();
74                        var data2 = DataStack.Pop();
75                        var data1 = DataStack.Pop();
76                        var ret = CalculateData(stackOpe, data1, data2);
77                        DataStack.Push(ret);
78                        OperatorStack.Push(dataOrOperator);
79                    }
80                }
81            }
82            return 0;
83        }
84        //获取表达式最后的计算结果
85        public static int GetResult()
86        
{
87            var ret = 0;
88            while (OperatorStack.Count > 0)
89            {
90                var stackOpe = OperatorStack.Pop();
91                var data2 = DataStack.Pop();
92                var data1 = DataStack.Pop();
93                ret = CalculateData(stackOpe, data1, data2);
94                DataStack.Push(ret);
95            }
96            return ret;
97        }
98        //根据操作符进行运算,这里可以抽象出接口,请自行实现
99        static int CalculateData(string operatorString, int data1, int data2)
100        
{
101            switch (operatorString)
102            {
103                case "+":
104                    return data1 + data2;
105                case "-":
106                    return data1 - data2;
107                case "*":
108                    return data1 * data2;
109                case "/":
110                    return data1 + data2;
111                default:
112                    return 0;
113            }
114        }
115        //获取运算符优先级
116        public static int OperatorPrecedence(string a)    //操作符优先级
117        
{
118            int i = 0;
119            switch (a)
120            {
121                case "+": i = 1break;
122                case "-": i = 1break;
123                case "*": i = 2break;
124                case "/": i = 2break;
125            }
126            return i;
127
128        }
129    }


640?wx_fmt=png


640?wx_fmt=gif程序猿修仙之路--数据结构之设计高性能访客记录系统●程序猿修仙之路--算法之快速排序到底有多快程序猿修仙之路--数据结构之你是否真的懂数组?

●程序猿修仙之路--算法之希尔排序!

●程序员修仙之路--算法之插入排序!

●程序员修仙之路--算法之选择排序!

640?wx_fmt=jpeg


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/317813.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

P5518-[MtOI2019]幽灵乐团【莫比乌斯反演,欧拉反演】

正题 题目链接:https://www.luogu.com.cn/problem/P5518 题目大意 TTT次给出A,B,CA,B,CA,B,C求以下三个式子 ∏i1A∏j1B∏k1Clcm(i,j)gcd(i,k)\prod_{i1}^A\prod_{j1}^B\prod_{k1}^{C}\frac{lcm(i,j)}{gcd(i,k)}i1∏A​j1∏B​k1∏C​gcd(i,k)lcm(i,j)​ ∏i1A∏j1B∏k1C(lcm(…

【2020牛客NOIP赛前集训营-提高组(第二场)】题解(GCD,包含,前缀,移动)

文章目录T1&#xff1a;GCDtitlesolutioncodeT2&#xff1a;包含titlesolutioncode(正解code补充在上面了)T3&#xff1a;前缀titlesolutioncodeT4&#xff1a;移动titlesolutioncodeT1&#xff1a;GCD title solution 非常水&#xff0c;看一眼就知道了 首先我们知道每一个…

.NET Core实战项目之CMS 第十五章 各层联动工作实现增删改查业务

连着两天更新叙述性的文章大家可别以为我转行了&#xff01;哈哈&#xff01;今天就继续讲讲我们的.NET Core实战项目之CMS系统的教程吧&#xff01;这个系列教程拖得太久了&#xff0c;所以今天我就以菜单部分的增删改查为例来讲述下我的项目分层之间的协同工作吧&#xff01;…

[2020-11-23 contest]图(dfs剪枝),劫富济贫(字典树),小A的树(树形DP),游戏(贪心/斜率优化)

文章目录T1&#xff1a;图solutioncodeT2&#xff1a;劫富济贫solutioncodeT3&#xff1a;小A的树solutioncodeT4&#xff1a;游戏solutioncodeT1&#xff1a;图 【问题描述】 给你一个n个点&#xff0c;m条边的无向图&#xff0c;每个点有一个非负的权值ci&#xff0c;现在你…

P1742 最小圆覆盖

P1742 最小圆覆盖 题意&#xff1a; 给出N个点&#xff0c;让你画一个最小的包含所有点的圆。 题解&#xff1a; 先说结论&#xff1a; 最优解的圆一定是在以某两个点连线为直径的圆 或者 某三个点组成的三角形的外接圆 初始化将某个圆心定为第一个点&#xff0c;R0 枚举第…

Java实现非对称加密【详解】

Java实现非对称加密 1. 简介2. 非对称加密算法--DH&#xff08;密钥交换&#xff09;3. 非对称加密算法--RSA非对称加密算法--EIGamal5. 总结6 案例6.1 案例16.2 案例26.3 案例3 1. 简介 公开密钥密码学&#xff08;英语&#xff1a;Public-key cryptography&#xff09;也称非…

轻量级.Net Core服务注册工具CodeDi发布啦

为什么做这么一个工具因为我们的系统往往时面向接口编程的,所以在开发Asp .net core项目的时候,一定会有大量大接口及其对应的实现要在ConfigureService注册到ServiceCollection中,传统的做法是加了一个服务,我们就要注册一次(service.AddService()),又比如,当一个接口有多个实…

2020 CSP-S 游记

迟到的游记总述T1&#xff1a;儒略日T2&#xff1a;动物园T3&#xff1a;函数调用T4&#xff1a;贪吃蛇总结总述 可能是有了去年第一次的狂炸经历&#xff0c;很明显的就是在考试策略上的提升 头不铁了&#xff0c;手不残了&#xff0c;心态稳了&#xff0c;分也多了 T1&…

P7516-[省选联考2021A/B卷]图函数【bfs】

正题 题目链接:https://www.luogu.com.cn/problem/P7516 题目大意 懒了&#xff0c;直接抄题意了 对于一张 nnn 个点 mmm 条边的有向图 GGG&#xff08;顶点从 1∼n1 \sim n1∼n 编号&#xff09;&#xff0c;定义函数 f(u,G)f(u, G)f(u,G)&#xff1a; 初始化返回值 cnt0cn…

【.NET Core项目实战-统一认证平台】第十三章 授权篇-如何强制有效令牌过期

上一篇我介绍了JWT的生成验证及流程内容&#xff0c;相信大家也对JWT非常熟悉了&#xff0c;今天将从一个小众的需求出发&#xff0c;介绍如何强制令牌过期的思路和实现过程。.netcore项目实战交流群&#xff08;637326624&#xff09;&#xff0c;有兴趣的朋友可以在群里交流讨…

[2020-11-24 contest]糖果机器(二维偏序),手套(状压dp),甲虫(区间dp),选举(线段树 最大子段和)

文章目录T1&#xff1a;糖果机器solutioncodeT2&#xff1a;手套solutioncodeT3&#xff1a;甲虫solutioncodeT4&#xff1a;选举solutioncodeT1&#xff1a;糖果机器 solution 考虑从第iii个糖果出发能到达第jjj个&#xff0c;则有Tj−Ti≥∣Sj−Si∣T_j-T_i≥|S_j-S_i|Tj​…

ASP.NET Core 数据加解密的一些坑

点击蓝字关注我ASP.NET Core 给我们提供了自带的Data Protection机制&#xff0c;用于敏感数据加解密&#xff0c;带来方便的同时也有一些限制可能引发问题&#xff0c;这几天我就被狠狠爆了一把我的场景我的博客系统有个发送邮件通知的功能&#xff0c;因此需要配置一个邮箱账…

[2020-11-28 contest]素数(数学),精灵(区间dp),农夫约的假期(结论),观察(树链剖分lca+set)

文章目录素数solutioncode精灵solutioncode农夫约的假期solutioncode观察solutionsolutioncode素数 solution 通过观察可得一个结论 对于两个相邻的质数p1,p2(p1<p2)p_1,p_2\ (p_1<p_2)p1​,p2​ (p1​<p2​) 对于x∈[p1,p2)x∈[p_1,p_2)x∈[p1​,p2​)&#xff0c;都…

B. Box Fitting

B. Box Fitting 题意&#xff1a; 现在有n个长方形&#xff0c;宽均为1&#xff0c;现在有一个底为m的容器&#xff0c;问将长方形放入其中&#xff0c;所用容器的最小宽度是多少 &#xff08;长方形必须长朝下放置详细如图&#xff09; 题解&#xff1a; 比赛时脑子抽了。…

人工智能第六课:如何做研究

这是我学习 Data Science Research Methods 这门课程的笔记。这门课程的讲师是一名教授和数据科学家&#xff0c;可能因为他既有理论背景&#xff0c;又有实践经验&#xff0c;所以整个课程听下来还比较舒服&#xff0c;学到了一些不错的理论知识。这门课比较系统地介绍了什么…

[2020-11-30 contest]数列(矩阵加速),秘密通道(dijkstra最短路)小X游世界树(换根dp),划分(数学)

文章目录数列solutioncode秘密通道solutioncode小X游世界树solutioncode划分solutioncode数列 a[1]a[2]a[3]1 a[x]a[x-3]a[x-1] (x>3) 求 a 数列的第 n 项对 1000000007&#xff08;10^97&#xff09;取余的值。 输入格式 第一行一个整数 T&#xff0c;表示询问个数。 以下…

Docker最全教程——数据库容器化之持久保存数据(十二)

上一节我们讲述了SQL Server容器化实践&#xff08;注意&#xff0c;SQL Server现在也支持跨平台&#xff09;&#xff0c;本节将讲述如何持久保存数据&#xff0c;并且接下来将逐步讲解其他数据库&#xff08;MySql、Redis、Mongodb等等&#xff09;的容器化实践&#xff0c;中…

【李超树】李超线段树维护凸包(凸壳) (例题:blue mary开公司+线段游戏+ZZH的旅行)

文章目录前言李超树引入(斜率优化)什么是李超树&#xff1f;李超树活着能干点什么&#xff1f;算法思想(使用手册&#xff1f;)插入查询模板判断是否覆盖(优不优)插入查询例题板题&#xff1a;BlueMary开公司分析code线段游戏分析code拓展——(动态开点李超树维护凸包)ZZH的旅行…

老牌开源Office操作组件NPOI现已支持.NET Core

昨天在微信群里听到老牌Excel开发利器NPOI的作者瞿总说4.6.1版本的NPOI已经支持.NET Standard 2.0了&#xff0c;这也就意味着你可以在.NET Core中使用NPOI了。作者&#xff1a;依乐祝原文地址 &#xff1a;https://www.cnblogs.com/yilezhu/p/10269281.html写在前面曾经的.NET…

.NET西安社区 [拥抱开源,又见 .NET] 第二次活动简报

「拥抱开源, 又见 .NET」随着 .NET Core的发布和开源&#xff0c;.NET又重新回到人们的视野。 .NET Core的下个3.0即将release&#xff0c;加入非常多的新功能&#xff0c;越来越拥抱变化&#xff0c;DevOps和Microservice的最佳实践已经在 .NET Core落地&#xff0c;比如 Ocel…