zoukankan      html  css  js  c++  java
  • JS实现简易的计算器

    JS可以做的事多了,那就用来实现一个计算器吧

    看看手机中的计算器,分为普通计算器和科学计算器

       

    自认脑袋不够大,就实现一个普通版本的吧(支持正负数加减乘除等基本连续的运算,未提供括号功能)

    看看图示效果, 或 在线演示

    一、知识准备

    1+1 = ?

    正常来说,我们看到这个表达式都知道怎么运算,知道运算结果

    但计算机不一样,计算机无法识别出这串表达式,它只能识别特定的规则:前缀表达式+ 1 1 或后缀表达式1 1 +

    举个栗子

    (3 + 4) × 5 - 6 就是中缀表达式
    - × + 3 4 5 6 前缀表达式
    3 4 + 5 × 6 - 后缀表达式

    所以为了实现程序的自动运算,我们需要将输入的数据转化为前缀或后缀表达式

    前缀、中缀、后缀表达式的概念以及相互转换方法在这里就不多说了,这篇博文 说得比较清楚了 

    所以,在这个计算器的实现中,采用了后缀表达式的实现方式,参考以上文章,重点关注这两个算法:

    与转换为前缀表达式相似,遵循以下步骤:
    (1) 初始化两个栈:运算符栈S1和储存中间结果的栈S2;
    (2) 从左至右扫描中缀表达式;
    (3) 遇到操作数时,将其压入S2;
    (4) 遇到运算符时,比较其与S1栈顶运算符的优先级:
    (4-1) 如果S1为空,或栈顶运算符为左括号“(”,则直接将此运算符入栈;
    (4-2) 否则,若优先级比栈顶运算符的高,也将运算符压入S1(注意转换为前缀表达式时是优先级较高或相同,而这里则不包括相同的情况);
    (4-3) 否则,将S1栈顶的运算符弹出并压入到S2中,再次转到(4-1)与S1中新的栈顶运算符相比较;
    (5) 遇到括号时:
    (5-1) 如果是左括号“(”,则直接压入S1;
    (5-2) 如果是右括号“)”,则依次弹出S1栈顶的运算符,并压入S2,直到遇到左括号为止,此时将这一对括号丢弃;
    (6) 重复步骤(2)至(5),直到表达式的最右边;
    (7) 将S1中剩余的运算符依次弹出并压入S2;
    (8) 依次弹出S2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式(转换为前缀表达式时不用逆序)。
    将中缀表达式转换为后缀表达式:
    与前缀表达式类似,只是顺序是从左至右:
    从左至右扫描表达式,遇到数字时,将数字压入堆栈,遇到运算符时,弹出栈顶的两个数,用运算符对它们做相应的计算(次顶元素 op 栈顶元素),并将结果入栈;重复上述过程直到表达式最右端,最后运算得出的值即为表达式的结果。
    例如后缀表达式“3 4 + 5 × 6 -”:
    (1) 从左至右扫描,将3和4压入堆栈;
    (2) 遇到+运算符,因此弹出4和3(4为栈顶元素,3为次顶元素,注意与前缀表达式做比较),计算出3+4的值,得7,再将7入栈;
    (3) 将5入栈;
    (4) 接下来是×运算符,因此弹出5和7,计算出7×5=35,将35入栈;
    (5) 将6入栈;
    (6) 最后是-运算符,计算出35-6的值,即29,由此得出最终结果。
    后缀表达式的计算机求值:

    二、实现过程

    第一步当然是搭建计算器的页面结构,不是科学计算器,只提供了基本的运算功能,但也能即时地进行运算,显示出完整的中缀表达式,运算后保存上一条运算记录。

    要先说一下:本来想实现小数点功能的,但小数点的存在让数据存储与数据显示的实现有了压力,实现过程实在脑大,索性先取消这个功能。

    1. 页面结构:

        <h5>计算计算</h5>
        <!-- 计算器 -->
        <div class="calc-wrap">
            <div class="calc-in-out">
                <!-- 上一条运算记录 -->
                <p class="calc-history" title=""></p>
                <!-- 输入的数据 -->
                <p class="calc-in"></p>
                <!-- 输出的运算结果 -->
                <p class="calc-out active"></p>
            </div>
            <table class="calc-operation">
                <thead></thead>
                <tbody>
                    <tr>
                        <td data-ac="cls" class="cls">C</td>
                        <td data-ac="del">&larr;</td>
                        <td data-ac="sq">x<sup>2</sup></td>
                        <td data-ac="mul">&times;</td>
                    </tr>
                    <tr>
                        <td data-val="7">7</td>
                        <td data-val="8">8</td>
                        <td data-val="9">9</td>
                        <td data-ac="div">&divide;</td>
                    </tr>
                    <tr>
                        <td data-val="4">4</td>
                        <td data-val="5">5</td>
                        <td data-val="6">6</td>
                        <td data-ac="plus">+</td>
                    </tr>
                    <tr>
                        <td data-val="1">1</td>
                        <td data-val="2">2</td>
                        <td data-val="3">3</td>
                        <td data-ac="minus">-</td>
                    </tr>
                        <td data-ac="per">%</td>
                        <td data-val="0">0</td>
                        <td data-ac="dot">.</td>
                        <td data-ac="eq" class="eq">=</td>
                </tbody>
            </table>
        </div>

    2. 结合一点样式:

    body {
        padding: 20px;
        font-family: Arial;
    }
    
    .calc-wrap {
        width: 300px;
        border: 1px solid #ddd;
        border-radius: 3px;
    }
    
    
    .calc-operation {
        width: 100%;
        border-collapse: collapse;
    }
    
    .calc-in-out {
        width: 100%;
        padding: 10px 20px;
        text-align: right;
        box-sizing: border-box;
        background-color: rgba(250, 250, 250, .9);
    }
    .calc-in-out p {
        overflow: hidden;
        margin: 5px;
        width: 100%;
    }
    .calc-history {
        margin-left: -20px;
        font-size: 18px;
        color: #bbb;
        border-bottom: 1px dotted #ddf;
        min-height: 23px;
    }
    
    .calc-in,
    .calc-out {
        font-size: 20px;
        color: #888;
        line-height: 39px;
        min-height: 39px;
    }
    
    .calc-in {
        color: #888;
    }
    .calc-out {
        color: #ccc;
    }
    
    .calc-in.active,
    .calc-out.active {
        font-size: 34px;
        color: #666;
    }
    
    .calc-operation td {
        padding: 10px;
        width: 25%;
        text-align: center;
        border: 1px solid #ddd;
        font-size: 26px;
        color: #888;
        cursor: pointer;
    }
    
    .calc-operation td:active {
        background-color: #ddd;
    }
    
    .calc-operation .cls {
        color: #ee8956;
    }
    CSS样式

    这样静态的计算器就粗来了~~

    3. JS逻辑

    这部分就是重点了,一步步来说

    首先是对计算器的监听吧,也就是这个表格,可以使用事件委托的方式,在父级节点上监听处理

            // 绑定事件
            bindEvent: function() {
                var that = this;
    
                that.$operation.on('click', function(e) {
                    e = e || window.event;
                    var elem = e.target || e.srcElement,
                        val,
                        action;
    
                    if (elem.tagName === 'TD') {
                        val = elem.getAttribute('data-val') || elem.getAttribute('data-ac');    
    
                ...

    监听数据,获取到的只是页面上的某个值/操作符,所以需要将数据存储起来形成中缀,再由中缀转换成后缀,最后通过后缀进行计算

            // 中缀表达式
            this.infix = [];
            // 后缀表达式
            this.suffix = [];
            // 后缀表达式运算结果集
            this.result = [];

    按照算法步骤,实现出来,这里没有使用到括号,如果实际需要,可在相应位置修改判断条件即可~

            // 中缀表达式转后缀
            infix2Suffix: function() {
                var temp = [];
                this.suffix = [];
    
                for (var i = 0; i < this.infix.length; i++) {
                    // 数值,直接压入
                    if (!this.isOp(this.infix[i])) {
                        this.suffix.push(this.infix[i]);
                    }
                    else {
                        if (!temp.length) {
                            temp.push(this.infix[i]);
                        }
                        else {
                            var opTop = temp[temp.length - 1];
                            // 循环判断运算符优先级,将运算符较高的压入后缀表达式
                            if (!this.priorHigher(opTop, this.infix[i])) {
                                while (temp.length && !this.priorHigher(opTop, this.infix[i])) {
                                    this.suffix.push(temp.pop());
                                    opTop = temp[temp.length - 1];
                                }
                            }
                               // 将当前运算符也压入后缀表达式
                            temp.push(this.infix[i]);
                        }
                    }
                }
                // 将剩余运算符号压入
                while (temp.length) {
                    this.suffix.push(temp.pop());
                }
            },
            // 后缀表达式计算
            calcSuffix: function() {
                this.result = [];
    
                for (var i = 0; i < this.suffix.length; i++) {
                    // 数值,直接压入结果集
                    if (!this.isOp(this.suffix[i])) {
                        this.result.push(this.suffix[i]);
                    }
                    // 运算符,从结果集中取出两项进行运算,并将运算结果置入结果集合
                    else {
                        this.result.push(this.opCalc(this.result.pop(), this.suffix[i], this.result.pop()));
                    }
                }
                // 此时结果集中只有一个值,即为结果
                  return this.result[0];
            }

    其实,在实现的时候会发现,中缀、后缀只是一个难点,更复杂的地方是整个计算器的状态变化(或者说是数据变化)

    在这个简单的计算器中,就有数字(0-9)、运算符(+ - * /)、操作(清除 删除)、预运算(百分号 平方)、小数点、即时运算等数据及操作

    如果是科学计算器那就更复杂了,所以理清如何控制这些东西很关键,而其中最重要的就是中缀表达式的构建与存储

    当连续点击+号时,是不符合实际操作的,所以需要一个变量 lastVal 来记录上一个值,随着操作而更新,再通过判断,防止程序出错

    在点击=号之后,我们可以继续使用这个结果进行运算,或者重新开始运算

        // 构建中缀表达式
            buildInfix: function(val, type) {
                // 直接的点击等于运算之后,
                if (this.calcDone) {
                    this.calcDone = false;
                    // 再点击数字,则进行新的运算
                    if (!this.isOp(val)) {
                        this.resetData();
                    }
                    // 再点击运算符,则使用当前的结果值继续进行运算
                    else {
                        var re = this.result[0];
                        this.resetData();
                        this.infix.push(re);
                    }
    
                }
    
                var newVal;
                 ...

    点击删除,是删除一位数,不是直接地删除一个数,然后更新中缀表达式的值

                // 删除操作
                if (type === 'del') {
                    newVal = this.infix.pop();
                    // 删除末尾一位数
                    newVal = Math.floor(newVal / 10);
                    if (newVal) {
                        this.infix.push(newVal);
                    }
    
                    this.lastVal = this.infix[this.infix.length - 1];
                    return this.infix;
                }    

    而添加操作,要考虑的就更多了,比如连续的连续运算符、连续的数字、运算符+ - 接上数字表示正负数,小数点的连接存取等

                // 添加操作,首先得判断运算符是否重复
                else if (type === 'add') {
                    // 两个连续的运算符
                    if (this.isOp(val) && this.isOp(this.lastVal)) {
                        return this.infix;
                    }
                    // 两个连续的数字
                    else if (!this.isOp(val) && !this.isOp(this.lastVal)) {
                        newVal = this.lastVal * 10 + val;
                        this.infix.pop();
                        this.infix.push(this.lastVal = newVal);
    
                        return this.infix;
                    }
                    // 首个数字正负数
                    if (!this.isOp(val) && this.infix.length === 1 && (this.lastVal === '+' || this.lastVal === '-')) {
                        newVal = this.lastVal === '+' ? val : 0 - val;
                        this.infix.pop();
                        this.infix.push(this.lastVal = newVal);
    
                        return this.infix;
                    }
    
    
                    this.infix.push(this.lastVal = val);
                    return this.infix;
                }

    在很多次操作的时候,计算器都需要即时地进行运算,为简化代码,可以封装成一个方法,在相应的位置调用即可

            // 即时得进行运算
            calculate: function(type) {
                this.infix2Suffix();
                var suffixRe = this.calcSuffix();
    
                if (suffixRe) {
                    this.$out.text('=' + suffixRe)
                        .attr('title', suffixRe)
                        .removeClass('active');
    
                    // 如果是直接显示地进行等于运算
                    if (type === 'eq') {
                        this.$in.removeClass('active');
                        this.$out.addClass('active');
                        // 设置标记:当前已经显示地进行计算
                        this.calcDone = true;
                        this.lastVal = suffixRe;
                        // 设置历史记录
                        var history = this.infix.join('') + ' = ' + suffixRe;
                        this.$history.text(history).attr('title', history);
                    }
    
                }
            },

    剩下的就是点击之后的处理过程了,也就是各种调用处理 传递数据->构建中缀处理数据->中缀转后缀->后缀运算显示

    比如点击了数字

                  // 数字:0-9
                        if (!isNaN(parseInt(val, 10))) {
                            // 构建中缀表达式并显示
                            var infixRe = that.buildInfix(parseInt(val, 10), 'add');
                            that.$in.text(infixRe.join('')).addClass('active');
    
                            that.calculate();
    
                            return;
                        }

    又比如几个预运算,其实长得也差不多

                    // 预运算:百分比、小数点、平方
                        else if (['per', 'dot', 'sq'].indexOf(action) !== -1) {
                            if (!that.infix.length || that.isOp(that.lastVal)) {
                                return;
                            }
    
                            if (action === 'per') {
                                that.lastVal /= 100;
                            } else if (action === 'sq') {
                                that.lastVal *= that.lastVal;
                            } else if (action === 'dot') {
                                // that.curDot = true;
                            }
    
                            // 重新构建中缀表达式
                            var infixRe = that.buildInfix(that.lastVal, 'change');
                            that.$in.text(infixRe.join('')).addClass('active');
    
                            that.calculate();
                        }

    以上就是这个简单计算器的实现步骤了,变化太多还不敢保证不会出错

    基本逻辑如此,如果要加上小数点运算、括号运算、正余弦等科学计算器的功能,还是自己去实现吧。。脑大啊。。

      1 $(function() {
      2 
      3     function Calculator($dom) {
      4         this.$dom = $($dom);
      5         // 历史运算
      6         this.$history = this.$dom.find('.calc-history');
      7         // 输入区
      8         this.$in = this.$dom.find('.calc-in');
      9         // 输出区
     10         this.$out = this.$dom.find('.calc-out');
     11         this.$operation = this.$dom.find('.calc-operation');
     12 
     13         // 运算符映射
     14         this.op = {
     15             'plus': '+',
     16             'minus': '-',
     17             'mul': '*',
     18             'div': '/'
     19         };
     20         this.opArr = ['+', '-', '*', '/'];
     21 
     22         // 中缀表达式
     23         this.infix = [];
     24         // 后缀表达式
     25         this.suffix = [];
     26         // 后缀表达式运算结果集
     27         this.result = [];
     28         // 存储最近的值
     29         this.lastVal = 0;
     30         // 当前已经计算等于完成
     31         this.calcDone = false;
     32         // 当前正在进行小数点点(.)相关值的修正
     33         this.curDot = false;
     34 
     35         this.init();
     36     }
     37 
     38     Calculator.prototype = {
     39         constructor: Calculator,
     40         // 初始化
     41         init: function() {
     42             this.bindEvent();
     43         },
     44         // 绑定事件
     45         bindEvent: function() {
     46             var that = this;
     47 
     48             that.$operation.on('click', function(e) {
     49                 e = e || window.event;
     50                 var elem = e.target || e.srcElement,
     51                     val,
     52                     action;
     53 
     54                 if (elem.tagName === 'TD') {
     55                     val = elem.getAttribute('data-val') || elem.getAttribute('data-ac');
     56                     // 数字:0-9
     57                     if (!isNaN(parseInt(val, 10))) {
     58                         // 构建中缀表达式并显示
     59                         var infixRe = that.buildInfix(parseInt(val, 10), 'add');
     60                         that.$in.text(infixRe.join('')).addClass('active');
     61 
     62                         that.calculate();
     63 
     64                         return;
     65                     }
     66 
     67                     action = val;
     68 
     69                     // 操作:清除、删除、计算等于
     70                     if (['cls', 'del', 'eq'].indexOf(action) !== -1) {
     71                         if (!that.infix.length) {
     72                             return;
     73                         }
     74 
     75                         // 清空数据
     76                         if (action === 'cls' || (action === 'del' && that.calcDone)) {
     77                             that.$in.text('');
     78                             that.$out.text('');
     79 
     80                             that.resetData();
     81                         }
     82                         // 清除
     83                         else if (action === 'del') {
     84                             // 重新构建中缀表达式
     85                             var infixRe = that.buildInfix(that.op[action], 'del');
     86                             that.$in.text(infixRe.join('')).addClass('active');
     87 
     88                             that.calculate();
     89 
     90                         }
     91                         // 等于
     92                         else if (action === 'eq') {
     93                             that.calculate('eq');
     94 
     95                         }
     96                     }
     97                     // 预运算:百分比、小数点、平方
     98                     else if (['per', 'dot', 'sq'].indexOf(action) !== -1) {
     99                         if (!that.infix.length || that.isOp(that.lastVal)) {
    100                             return;
    101                         }
    102 
    103                         if (action === 'per') {
    104                             that.lastVal /= 100;
    105                         } else if (action === 'sq') {
    106                             that.lastVal *= that.lastVal;
    107                         } else if (action === 'dot') {
    108                             // that.curDot = true;
    109                         }
    110 
    111                         // 重新构建中缀表达式
    112                         var infixRe = that.buildInfix(that.lastVal, 'change');
    113                         that.$in.text(infixRe.join('')).addClass('active');
    114 
    115                         that.calculate();
    116                     }
    117                     // 运算符:+ - * /
    118                     else if (that.isOp(that.op[action])) {
    119                         if (!that.infix.length && (that.op[action] === '*' || that.op[action] === '/')) {
    120                             return;
    121                         }
    122 
    123                         var infixRe = that.buildInfix(that.op[action], 'add');
    124                         that.$in.text(infixRe.join('')).addClass('active');
    125                     }
    126                 }
    127             });
    128         },
    129 
    130         resetData: function() {
    131             this.infix = [];
    132             this.suffix = [];
    133             this.result = [];
    134             this.lastVal = 0;
    135             this.curDot = false;
    136         },
    137 
    138         // 构建中缀表达式
    139         buildInfix: function(val, type) {
    140             // 直接的点击等于运算之后,
    141             if (this.calcDone) {
    142                 this.calcDone = false;
    143                 // 再点击数字,则进行新的运算
    144                 if (!this.isOp(val)) {
    145                     this.resetData();
    146                 }
    147                 // 再点击运算符,则使用当前的结果值继续进行运算
    148                 else {
    149                     var re = this.result[0];
    150                     this.resetData();
    151                     this.infix.push(re);
    152                 }
    153 
    154             }
    155 
    156             var newVal;
    157 
    158             // 删除操作
    159             if (type === 'del') {
    160                 newVal = this.infix.pop();
    161                 // 删除末尾一位数
    162                 newVal = Math.floor(newVal / 10);
    163                 if (newVal) {
    164                     this.infix.push(newVal);
    165                 }
    166 
    167                 this.lastVal = this.infix[this.infix.length - 1];
    168                 return this.infix;
    169             }
    170             // 添加操作,首先得判断运算符是否重复
    171             else if (type === 'add') {
    172                 // 两个连续的运算符
    173                 if (this.isOp(val) && this.isOp(this.lastVal)) {
    174                     return this.infix;
    175                 }
    176                 // 两个连续的数字
    177                 else if (!this.isOp(val) && !this.isOp(this.lastVal)) {
    178                     newVal = this.lastVal * 10 + val;
    179                     this.infix.pop();
    180                     this.infix.push(this.lastVal = newVal);
    181 
    182                     return this.infix;
    183                 }
    184                 // 首个数字正负数
    185                 if (!this.isOp(val) && this.infix.length === 1 && (this.lastVal === '+' || this.lastVal === '-')) {
    186                     newVal = this.lastVal === '+' ? val : 0 - val;
    187                     this.infix.pop();
    188                     this.infix.push(this.lastVal = newVal);
    189 
    190                     return this.infix;
    191                 }
    192 
    193             // TODO: 小数点运算
    194             //     if (this.isOp(val)) {
    195             //         this.curDot = false;
    196             //     }
    197 
    198             //     // 小数点
    199             //     if (this.curDot) {
    200             //         var dotLen = 0;
    201             //         newVal = this.infix.pop();
    202             //         dotLen = newVal.toString().split('.');
    203             //         dotLen = dotLen[1] ? dotLen[1].length : 0;
    204 
    205             //         newVal +=  val / Math.pow(10, dotLen + 1);
    206             //         // 修正小数点运算精确值
    207             //         newVal = parseFloat(newVal.toFixed(dotLen + 1));
    208 
    209             //         this.infix.push(this.lastVal = newVal);
    210             //         return this.infix;
    211             //     }
    212 
    213                 this.infix.push(this.lastVal = val);
    214                 return this.infix;
    215             }
    216 
    217             // 更改操作,比如%的预运算
    218             else if (type === 'change') {
    219                 this.infix.pop();
    220                 this.infix.push(this.lastVal = val);
    221 
    222                 return this.infix;
    223             }
    224 
    225         },
    226         // 判断是否为运算符
    227         isOp: function(op) {
    228             return op && this.opArr.indexOf(op) !== -1;
    229         },
    230         // 判断运算符优先级
    231         priorHigher: function(a, b) {
    232             return (a === '+' || a === '-') && (b === '*' || b === '/');
    233         },
    234         // 进行运算符的运算
    235         opCalc: function(b, op, a) {
    236             return op === '+'
    237                 ? a + b
    238                 : op === '-'
    239                 ? a - b
    240                 : op === '*'
    241                 ? a * b
    242                 : op === '/'
    243                 ? a / b
    244                 : 0;
    245         },
    246         // 即时得进行运算
    247         calculate: function(type) {
    248             this.infix2Suffix();
    249             var suffixRe = this.calcSuffix();
    250 
    251             if (suffixRe) {
    252                 this.$out.text('=' + suffixRe)
    253                     .attr('title', suffixRe)
    254                     .removeClass('active');
    255 
    256                 // 如果是直接显示地进行等于运算
    257                 if (type === 'eq') {
    258                     this.$in.removeClass('active');
    259                     this.$out.addClass('active');
    260                     // 设置标记:当前已经显示地进行计算
    261                     this.calcDone = true;
    262                     this.lastVal = suffixRe;
    263                     // 设置历史记录
    264                     var history = this.infix.join('') + ' = ' + suffixRe;
    265                     this.$history.text(history).attr('title', history);
    266                 }
    267 
    268             }
    269         },
    270 
    271         // 中缀表达式转后缀
    272         infix2Suffix: function() {
    273             var temp = [];
    274             this.suffix = [];
    275 
    276             for (var i = 0; i < this.infix.length; i++) {
    277                 // 数值,直接压入
    278                 if (!this.isOp(this.infix[i])) {
    279                     this.suffix.push(this.infix[i]);
    280                 }
    281                 else {
    282                     if (!temp.length) {
    283                         temp.push(this.infix[i]);
    284                     }
    285                     else {
    286                         var opTop = temp[temp.length - 1];
    287                         // 循环判断运算符优先级,将运算符较高的压入后缀表达式
    288                         if (!this.priorHigher(opTop, this.infix[i])) {
    289                             while (temp.length && !this.priorHigher(opTop, this.infix[i])) {
    290                                 this.suffix.push(temp.pop());
    291                                 opTop = temp[temp.length - 1];
    292                             }
    293                         }
    294                            // 将当前运算符也压入后缀表达式
    295                         temp.push(this.infix[i]);
    296                     }
    297                 }
    298             }
    299             // 将剩余运算符号压入
    300             while (temp.length) {
    301                 this.suffix.push(temp.pop());
    302             }
    303         },
    304 
    305         // 后缀表达式计算
    306         calcSuffix: function() {
    307             this.result = [];
    308 
    309             for (var i = 0; i < this.suffix.length; i++) {
    310                 // 数值,直接压入结果集
    311                 if (!this.isOp(this.suffix[i])) {
    312                     this.result.push(this.suffix[i]);
    313                 }
    314                 // 运算符,从结果集中取出两项进行运算,并将运算结果置入结果集合
    315                 else {
    316                     this.result.push(this.opCalc(this.result.pop(), this.suffix[i], this.result.pop()));
    317                 }
    318             }
    319             // 此时结果集中只有一个值,即为结果
    320               return this.result[0];
    321         }
    322     };
    323 
    324     new Calculator('.calc-wrap');
    325 });
    完整JS
  • 相关阅读:
    (一)js概述
    (八)js函数二
    (七)js函数一
    (十)js获取日期
    Java 定时器 Timer 的使用.
    多线程编程学习四(Lock 的使用)
    多线程编程学习三(线程间通信).
    wait/notify 实现多线程交叉备份
    多线程编程学习二(对象及变量的并发访问).
    浅析多线程的对象锁和Class锁
  • 原文地址:https://www.cnblogs.com/imwtr/p/5970865.html
Copyright © 2011-2022 走看看