最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

基于JavaScript實(shí)現(xiàn)字符串計(jì)算器的實(shí)戰(zhàn)指南

 更新時(shí)間:2026年03月09日 09:39:35   作者:顏醬  
這篇文章主要為大家詳細(xì)介紹了如何基于JavaScript實(shí)現(xiàn)字符串計(jì)算器,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下

用代碼實(shí)現(xiàn)一個(gè)計(jì)算器,是面試和刷題里很常見(jiàn)的一類(lèi)題。很多人一上來(lái)就想寫(xiě)「完整版」,容易在符號(hào)優(yōu)先級(jí)、括號(hào)、多位數(shù)上一起踩坑。更好的方式是:先解決最小子問(wèn)題,再一層層加能力。

這篇文章會(huì)按下面這條路線(xiàn),帶你從零搭出一個(gè)支持 + - * / 和括號(hào)的表達(dá)式計(jì)算器,并在最后做一輪優(yōu)化。

  • 字符串轉(zhuǎn)整數(shù):先把「數(shù)字字符串」正確轉(zhuǎn)成數(shù)字
  • 只做加減:引入運(yùn)算符和棧,處理 +、-
  • 加入乘除:在原有框架上加上 *、/ 與優(yōu)先級(jí)
  • 支持括號(hào):用遞歸或區(qū)間,處理嵌套括號(hào)
  • 優(yōu)化:避免爆棧、少做拷貝、寫(xiě)法更穩(wěn)

每一步都會(huì)在前一步可運(yùn)行代碼的基礎(chǔ)上做增量修改,方便你跟著寫(xiě)、跟著測(cè)。

第一步:字符串轉(zhuǎn)整數(shù)

表達(dá)式是由「數(shù)字」和「運(yùn)算符」組成的,而數(shù)字在字符串里可能是多位數(shù),比如 "123"。所以首先要會(huì):把一段連續(xù)的數(shù)字字符轉(zhuǎn)成一個(gè)整數(shù)

核心就一句話(huà):每多一位,相當(dāng)于原來(lái)的數(shù)左移一位(×10),再加上當(dāng)前這一位。

  • "1" → 先得到 1
  • "12" → 原來(lái)的 1 變成十位:1*10+2 = 12
  • "123" → 原來(lái)的 12 再左移一位:12*10+3 = 123

用代碼寫(xiě)出來(lái)就是這樣(只考慮正整數(shù)、無(wú)空格無(wú)符號(hào))。用 while 把連續(xù)數(shù)字一次性讀完,后面各步也都用這一套邏輯,更統(tǒng)一:

/**
 * 將純數(shù)字字符串轉(zhuǎn)為整數(shù)(如 "123" → 123)
 * @param {string} str - 僅包含數(shù)字字符的字符串
 * @returns {number} 轉(zhuǎn)換后的整數(shù)
 */
function stringToInt(str) {
  let num = 0; // 累加結(jié)果,初始為 0(乘法的單位元,不影響首次 num*10+cur)
  let i = 0; // 當(dāng)前掃描到的下標(biāo)
  // 只要未越界且當(dāng)前字符是數(shù)字,就繼續(xù)讀(多位數(shù)一次性讀完)
  while (i < str.length && /[0-9]/.test(str[i])) {
    num = num * 10 + (str[i] - '0'); // 左移一位(×10)+ 當(dāng)前位數(shù)字;'5'-'0' → 5
    i++;
  }
  return num;
}

這里用 str[i] - '0' 把字符轉(zhuǎn)成數(shù)字,比 parseInt 更直接,也避免一些隱式轉(zhuǎn)換問(wèn)題。

建議:自己寫(xiě)一遍,用 "0"、"9"、"123"、"1000" 測(cè)幾組,確保多位數(shù)和邊界都對(duì)。這是后面所有步驟的「數(shù)字解析」基礎(chǔ)。

第二步:只做加減法

假設(shè)輸入只有 +、-、數(shù)字和空格,例如:"1 - 12 + 3"。目標(biāo)是:一次遍歷,得到結(jié)果。

一個(gè)很好用的思路是:把減法變成「加負(fù)數(shù)」,這樣最后只需要做「一堆數(shù)求和」。

  • 第一個(gè)數(shù)字前面補(bǔ)一個(gè)默認(rèn)的 +,整體看成:+1 -12 +3
  • 一個(gè)符號(hào) + 一個(gè)數(shù)字綁在一起,得到幾組:(+1)(-12)、(+3)。
  • 把這些數(shù)全部加在一起:1 + (-12) + 3 = -8

實(shí)現(xiàn)時(shí):

  • 用一個(gè)變量 sign 表示「當(dāng)前正在拼的這個(gè)數(shù)字前面是 + 還是 -」,初始為 '+'。
  • 遇到數(shù)字就按「第一步」的方式拼成多位數(shù),拼在 curNum 里,然后根據(jù)數(shù)字前面的sign,進(jìn)棧
  • 遍歷結(jié)束后,棧里全是「帶符號(hào)的數(shù)」,求和即可。
/**
 * 只支持 +、-、數(shù)字、空格的表達(dá)式(減法轉(zhuǎn)為加負(fù)數(shù),最后統(tǒng)一求和)
 * @param {string} s - 表達(dá)式字符串,如 "1 - 12 + 3"
 * @returns {number} 計(jì)算結(jié)果
 */
function calculateAddSub(s) {
  const stack = []; // 存「帶符號(hào)的加數(shù)」,最后全部相加即可
  let sign = '+'; // 當(dāng)前數(shù)字前的符號(hào),第一個(gè)數(shù)字前默認(rèn)為 +
  const n = s.length;

  for (let i = 0; i < n; i++) {
    const c = s[i];
    if (c === ' ') continue; // 空格跳過(guò),避免被誤判為數(shù)字

    // 遇到數(shù)字:用 while 一次性讀完多位數(shù)
    if (/[0-9]/.test(c)) {
      let num = 0;
      while (i < n && /[0-9]/.test(s[i])) {
        num = num * 10 + (s[i] - '0');
        i++;
      }
      stack.push(sign === '+' ? num : -num); // 按當(dāng)前符號(hào)入棧(減法即存負(fù)數(shù))
      i--; // while 結(jié)束時(shí) i 已指向非數(shù)字,for 會(huì)再 i++,故回退一步避免跳過(guò)
      continue;
    }

    // 遇到 + 或 -:只更新符號(hào),下一個(gè)數(shù)字將使用該符號(hào)入棧
    if (c === '+' || c === '-') {
      sign = c;
    }
  }

  // 棧內(nèi)全是帶符號(hào)的加數(shù),直接求和
  let sum = 0;
  while (stack.length) sum += stack.pop();
  return sum;
}

要點(diǎn):

  • 空格直接 continue,避免被誤當(dāng)數(shù)字。
  • 遇到數(shù)字就用 while 一次性讀完,入棧后記得 i--,因?yàn)?for 會(huì)再 i++,否則會(huì)跳過(guò)一個(gè)字符。
  • 遇到 +、- 只更新 sign,數(shù)字在讀完時(shí)按當(dāng)前 sign 入棧;棧里存的已經(jīng)是「帶符號(hào)的加數(shù)」,最后只做加法。

到這里,你已經(jīng)有了「數(shù)字解析 + 運(yùn)算符 + ?!沟目蚣埽乱徊街皇窃谶@個(gè)框架上擴(kuò)展運(yùn)算符種類(lèi)和優(yōu)先級(jí)。

第三步:加入乘除

在「加減 + ?!沟幕A(chǔ)上,再支持 *、/,并且滿(mǎn)足:乘除優(yōu)先于加減。

做法可以這樣理解:

  • 加減:像第二步一樣,把數(shù)字帶上符號(hào)壓棧(減法即壓負(fù)值),最后統(tǒng)一求和。
  • 乘除:需要「立刻算掉」,因?yàn)槌顺膬?yōu)先級(jí)高。做法是:遇到 */ 時(shí),用當(dāng)前剛拼好的數(shù)棧頂?shù)臄?shù)做運(yùn)算,把結(jié)果再壓回棧頂。這樣棧里存的仍然是「已經(jīng)按優(yōu)先級(jí)算好的、待求和的項(xiàng)」。

所以我們需要一個(gè)「數(shù)字前的運(yùn)算符」signBeforeNum

  • 遇到 +-:把當(dāng)前數(shù)字按正/負(fù)入棧,只改 signBeforeNum,不入棧時(shí)做運(yùn)算。
  • 遇到 */:先按 signBeforeNum 把當(dāng)前數(shù)字和棧頂結(jié)合(若是 *// 就彈出棧頂算完再壓回),再更新 signBeforeNum

為了代碼清晰,可以把「根據(jù)符號(hào)把 num 入棧」抽成一個(gè)函數(shù):

/**
 * 根據(jù)「數(shù)字前的運(yùn)算符」把 num 入棧(加減存帶符號(hào)數(shù),乘除與棧頂算完再入棧)
 * @param {number[]} stack - 數(shù)字棧
 * @param {string} signBeforeNum - 該數(shù)字前的運(yùn)算符(+、-、*、/)
 * @param {number} num - 剛讀完的數(shù)字
 */
function addNumToStack(stack, signBeforeNum, num) {
  switch (signBeforeNum) {
    case '+':
      stack.push(num); // 加:直接入棧
      break;
    case '-':
      stack.push(-num); // 減:轉(zhuǎn)成加負(fù)數(shù)入棧
      break;
    case '*':
      stack.push(stack.pop() * num); // 乘:彈出棧頂 × 當(dāng)前數(shù),結(jié)果入棧(乘除優(yōu)先)
      break;
    case '/':
      const top = stack.pop();
      stack.push(Math.trunc(top / num)); // 除:向零取整,如 -5/2 → -2
      break;
  }
}

/**
 * 支持 +、-、*、/、數(shù)字、空格,乘除優(yōu)先于加減
 * @param {string} s - 表達(dá)式,如 "10 - 2*3 + 8/2"
 * @returns {number} 計(jì)算結(jié)果
 */
function calculateMulDiv(s) {
  const stack = []; // 存待累加的項(xiàng)(加減已轉(zhuǎn)符號(hào),乘除已算完)
  let signBeforeNum = '+'; // 「下一個(gè)數(shù)字」前的運(yùn)算符,第一個(gè)數(shù)字前默認(rèn)為 +
  const n = s.length;

  for (let i = 0; i < n; i++) {
    const c = s[i];
    if (c === ' ') continue;

    // 遇到數(shù)字:while 讀完多位數(shù),再按 signBeforeNum 入棧
    if (/[0-9]/.test(c)) {
      let num = 0;
      while (i < n && /[0-9]/.test(s[i])) {
        num = num * 10 + (s[i] - '0');
        i++;
      }
      addNumToStack(stack, signBeforeNum, num);
      i--;
      continue;
    }

    // 遇到運(yùn)算符:只更新「下一個(gè)數(shù)字前的符號(hào)」,數(shù)字入棧在讀完數(shù)字時(shí)由 addNumToStack 完成
    if (['+', '-', '*', '/'].includes(c)) {
      signBeforeNum = c;
    }
  }

  return stack.reduce((a, b) => a + b, 0);
}

注意:

  • 除法用 Math.trunc,這樣 -5/2 === -2,符合「向零取整」的慣例。
  • 數(shù)字用 while 一次性讀完,入棧后 i--,與第二步一致。

到這一步,不帶括號(hào)的四則運(yùn)算已經(jīng)完成,邏輯也是后面「帶括號(hào)版」的基礎(chǔ)。

第四步:支持括號(hào)

括號(hào)的語(yǔ)義是:括號(hào)內(nèi)的表達(dá)式先算,結(jié)果當(dāng)作一個(gè)數(shù)。所以可以很自然想到:

  • 遇到 (:找到與之匹配的 ),對(duì)中間這一段遞歸計(jì)算,得到的結(jié)果當(dāng)作「一個(gè)數(shù)字」,再按當(dāng)前的 signBeforeNum 入棧。
  • 遇到 ):不需要在本層處理,因?yàn)槭怯伞钙ヅ涞?( 那一層」通過(guò)遞歸邊界自然結(jié)束的。

因此需要解決兩件事:

  • 括號(hào)匹配:給定一個(gè) ( 的位置,找到對(duì)應(yīng)的 )。嵌套括號(hào)用棧即可:從左到右掃,( 壓棧,) 彈棧,彈出來(lái)的就是匹配的左括號(hào),記錄下右括號(hào)位置即可得到「左 → 右」的映射。
  • 遞歸范圍:遇到 ( 時(shí),用映射找到配對(duì)的 ),對(duì)子串 s.slice(left+1, right) 遞歸計(jì)算(不包含括號(hào)本身),結(jié)果當(dāng)作一個(gè)數(shù)入棧,然后把下標(biāo) i 直接跳到 ),避免重復(fù)處理。

下面先給出未優(yōu)化版:每次進(jìn)入 calculate(s) 都會(huì)對(duì)當(dāng)前 s 算一遍 getBracketMap(s),遞歸時(shí)用 s.slice(i+1, right) 復(fù)制出一段子串再遞歸。邏輯清晰,但長(zhǎng)表達(dá)式、深括號(hào)時(shí)會(huì)有重復(fù)計(jì)算和大量拷貝,第五步再優(yōu)化。

先寫(xiě)括號(hào)匹配(只掃一遍字符串):

/**
 * 建立括號(hào)配對(duì):左括號(hào)下標(biāo) → 對(duì)應(yīng)的右括號(hào)下標(biāo)(嵌套括號(hào)用棧就近匹配)
 * @param {string} s - 表達(dá)式字符串
 * @returns {Map<number, number>} 左括號(hào)索引 → 右括號(hào)索引
 */
function getBracketMap(s) {
  const map = new Map();
  const stack = []; // 存左括號(hào)的下標(biāo),遇到 ) 時(shí)彈棧即得到與之匹配的 (
  for (let i = 0; i < s.length; i++) {
    if (s[i] === '(') {
      stack.push(i); // 左括號(hào):記錄其下標(biāo),等待匹配
    }
    if (s[i] === ')') {
      const left = stack.pop(); // 彈出最近的左括號(hào)下標(biāo),即與當(dāng)前 ) 配對(duì)
      map.set(left, i); // 記錄配對(duì)關(guān)系
    }
  }
  return map;
}

然后在主邏輯里加上對(duì) ( 的分支(遞歸時(shí)傳入子串,即未優(yōu)化版):

/**
 * 表達(dá)式計(jì)算函數(shù)(支持 +-*\/、數(shù)字、空格、嵌套括號(hào),遵循 括號(hào)>乘除>加減 優(yōu)先級(jí))
 * @param {string} s - 輸入表達(dá)式,示例:"1+2+1*3+(3*6+12*(3+3))-5"
 * @returns {number} - 計(jì)算結(jié)果
 *
 * 核心設(shè)計(jì)思路:
 * 1. 棧的作用:存儲(chǔ)待累加的數(shù)字(根據(jù)符號(hào)決定數(shù)字進(jìn)棧的值,加減轉(zhuǎn)符號(hào)存儲(chǔ),乘除優(yōu)先計(jì)算后存儲(chǔ),括號(hào)結(jié)果作為單個(gè)數(shù)字存儲(chǔ))
 * 2. 符號(hào)規(guī)則:signBeforeNum 記錄「當(dāng)前數(shù)字的前一個(gè)運(yùn)算符」,決定數(shù)字如何入棧
 * 3. 字符處理優(yōu)先級(jí):空格跳過(guò) → 數(shù)字拼接 → 左括號(hào)遞歸 → 運(yùn)算符更新符號(hào)
 */
function calculate(s) {
  const stack = []; // 存儲(chǔ)待累加的數(shù)字棧(核心數(shù)據(jù)結(jié)構(gòu))
  const bracketMap = getBracketMap(s); // 預(yù)生成「左括號(hào)索引→右括號(hào)索引」映射,解決嵌套括號(hào)匹配
  let signBeforeNum = '+'; // 【易錯(cuò)點(diǎn)1】初始值必須為+,對(duì)應(yīng)第一個(gè)數(shù)字前默認(rèn)是加號(hào)

  // 遍歷表達(dá)式的每個(gè)字符
  for (let i = 0; i < s.length; i++) {
    let curChar = s[i];

    // 1. 跳過(guò)空格(唯一需要主動(dòng)跳過(guò)的特殊字符)
    if (curChar === ' ') continue;

    // 2. 處理連續(xù)數(shù)字拼接(核心邏輯:多位數(shù)拆解)
    if (/[0-9]/.test(curChar)) {
      let num = 0;
      // 循環(huán)拼接所有連續(xù)數(shù)字,直到遇到非數(shù)字字符
      while (i < s.length && /[0-9]/.test(s[i])) {
        // 【易錯(cuò)點(diǎn)2】必須加i < s.length,避免越界
        // 【易錯(cuò)點(diǎn)3】必須用s[i]而非curChar,curChar是循環(huán)外的初始值,不會(huì)隨i更新
        const singleNum = s[i] - '0';
        num = num * 10 + singleNum; // 位權(quán)原理拼接多位數(shù)(如1→12→123)
        i++; // 移動(dòng)到下一個(gè)字符
      }
      // 數(shù)字拼接完成,按符號(hào)入棧
      addNumToStack(stack, num, signBeforeNum);
      // 【易錯(cuò)點(diǎn)4】核心細(xì)節(jié):循環(huán)后i指向非數(shù)字,外層i++會(huì)跳過(guò)運(yùn)算符,必須回退一步
      i--;
      continue; // 跳過(guò)后續(xù)邏輯,避免數(shù)字被誤判為運(yùn)算符
    }

    // 3. 處理左括號(hào)(遞歸+索引跳轉(zhuǎn),無(wú)需處理右括號(hào))
    if (curChar === '(') {
      const rightBracketIdx = bracketMap.get(i); // 獲取匹配的右括號(hào)索引
      // 【易錯(cuò)點(diǎn)5】遞歸截取范圍是[i+1, rightBracketIdx),不包含右括號(hào)
      const bracketRes = calculate(s.slice(i + 1, rightBracketIdx));
      // 括號(hào)結(jié)果作為單個(gè)數(shù)字,按當(dāng)前符號(hào)入棧
      addNumToStack(stack, bracketRes, signBeforeNum);
      // 【易錯(cuò)點(diǎn)6】跳到右括號(hào)索引,外層i++會(huì)自然跳過(guò)右括號(hào),無(wú)需額外處理)
      i = rightBracketIdx;
      continue; // 本次循環(huán)結(jié)束,避免后續(xù)邏輯處理右括號(hào)
    }

    // 4. 處理運(yùn)算符(僅更新符號(hào),數(shù)字入棧邏輯在數(shù)字拼接時(shí)觸發(fā))
    if (['+', '-', '*', '/'].includes(curChar)) {
      // 【易錯(cuò)點(diǎn)7】符號(hào)僅記錄,不立即計(jì)算,作用于下一個(gè)數(shù)字
      signBeforeNum = curChar;
    }
  }

  // 5. 棧內(nèi)所有數(shù)字累加(加減已轉(zhuǎn)符號(hào),乘除/括號(hào)已計(jì)算,直接求和)
  let sum = 0;
  while (stack.length) {
    sum += stack.pop();
  }
  return sum;
}

注意:

  • 遞歸區(qū)間用 s.slice(i+1, right),不包含左右括號(hào);每一層遞歸收到的都是「當(dāng)前層」的字符串,所以本層算出的 bracketMap 就是當(dāng)前 s 的映射。
  • i = right 之后,外層 for 會(huì)執(zhí)行 i++,所以不會(huì)重復(fù)處理 )。
  • 未優(yōu)化點(diǎn):每次遞歸都復(fù)制一次子串,且每次進(jìn)入 calculate(s) 都會(huì)執(zhí)行一次 getBracketMap(s),表達(dá)式長(zhǎng)、括號(hào)深時(shí)容易爆棧或變慢。下一步在第五步里改成「只算一次 bracketMap、只傳區(qū)間不復(fù)制」。(主要是跑leetCode224題目的時(shí)候,用例沒(méi)過(guò))

第五步:優(yōu)化

在第四步未優(yōu)化版的基礎(chǔ)上做兩處改動(dòng):不復(fù)制字符串、只算一次括號(hào)映射。

1. 不拷貝字符串,只傳區(qū)間

遞歸時(shí)不再用 calculate(s.slice(i + 1, right)),而是始終在原串 s 上操作,用內(nèi)部函數(shù) calc(low, high) 表示「只處理 s[low..high] 這一段」。遞歸時(shí)調(diào)用 calc(i + 1, right - 1),不產(chǎn)生任何子串拷貝。

2. 只算一次括號(hào)映射

getBracketMap(s) 只依賴(lài)整串 s,和遞歸層數(shù)無(wú)關(guān)。在最外層入口算一次,得到整串的「左括號(hào)下標(biāo) → 右括號(hào)下標(biāo)」映射,然后在整個(gè)遞歸過(guò)程中通過(guò)閉包共用這一個(gè) bracketMap。這樣每一層不再重新掃一遍當(dāng)前子串,避免重復(fù)計(jì)算。

優(yōu)化后的主邏輯示意(與文末完整實(shí)現(xiàn)一致):入口處 const bracketMap = getBracketMap(s) 只執(zhí)行一次,然后 return calc(0, s.length - 1);內(nèi)部 calc(low, high) 遇到 ( 時(shí)用 bracketMap.get(i) 取到匹配的右括號(hào)下標(biāo)(因?yàn)?i 始終是原串下標(biāo)),再遞歸 calc(i + 1, right - 1),并令 i = right 跳過(guò)整段括號(hào)。

3. 數(shù)字拼接時(shí)的小細(xì)節(jié)

  • while 把連續(xù)數(shù)字一次性拼完,避免用 curChar 而忘記用 s[i] 導(dǎo)致死循環(huán)或錯(cuò)位;拼完后記得 i--,因?yàn)?for 會(huì)再 i++,否則會(huì)跳過(guò)一個(gè)字符。
  • 數(shù)字判斷用 /[0-9]/.test(s[i]),循環(huán)條件里務(wù)必寫(xiě) i < s.length,防止越界。

4. 除法取整

堅(jiān)持用 Math.trunc(top / num),這樣負(fù)數(shù)的除法行為一致(向零取整),和常見(jiàn)數(shù)學(xué)期望一致。

/**
 * 建立括號(hào)配對(duì):左括號(hào)下標(biāo) → 右括號(hào)下標(biāo)(僅掃一遍,嵌套用棧匹配)
 */
function getBracketMap(s) {
  const map = new Map();
  const stack = []; // 存左括號(hào)下標(biāo)
  for (let i = 0; i < s.length; i++) {
    if (s[i] === '(') stack.push(i);
    if (s[i] === ')') map.set(stack.pop(), i);
  }
  return map;
}

/**
 * 按「數(shù)字前的運(yùn)算符」將 num 入棧:加減存帶符號(hào)數(shù),乘除與棧頂算完再入棧
 */
function addNumToStack(stack, signBeforeNum, num) {
  switch (signBeforeNum) {
    case '+':
      stack.push(num);
      break;
    case '-':
      stack.push(-num);
      break;
    case '*':
      stack.push(stack.pop() * num);
      break;
    case '/':
      stack.push(Math.trunc(stack.pop() / num));
      break;
  }
}

/**
 * 表達(dá)式計(jì)算(優(yōu)化版:不復(fù)制 s,只傳區(qū)間;bracketMap 只算一次)
 * @param {string} s - 整段表達(dá)式
 * @returns {number} 計(jì)算結(jié)果
 */
function calculate(s) {
  const bracketMap = getBracketMap(s); // 整串只算一次,遞歸過(guò)程中通過(guò)閉包共用
  return calc(0, s.length - 1);

  /** 只處理 s[low..high] 這一段,不復(fù)制字符串 */
  function calc(low, high) {
    const stack = [];
    let signBeforeNum = '+';

    for (let i = low; i <= high; i++) {
      const c = s[i];
      if (c === ' ') continue;

      // 數(shù)字:while 讀完多位數(shù)(注意邊界 i <= high),入棧后 i-- 避免 for 多跳一格
      if (/[0-9]/.test(c)) {
        let num = 0;
        while (i <= high && /[0-9]/.test(s[i])) {
          num = num * 10 + (s[i] - '0');
          i++;
        }
        addNumToStack(stack, signBeforeNum, num);
        i--;
        continue;
      }

      // 左括號(hào):用預(yù)算好的 bracketMap 取匹配的右括號(hào),遞歸區(qū)間 [i+1, right-1],結(jié)果入棧后跳過(guò)整段
      if (c === '(') {
        const right = bracketMap.get(i);
        const sub = calc(i + 1, right - 1);
        addNumToStack(stack, signBeforeNum, sub);
        i = right;
        continue;
      }

      // 運(yùn)算符:更新「下一個(gè)數(shù)字前的符號(hào)」
      if (['+', '-', '*', '/'].includes(c)) {
        signBeforeNum = c;
      }
    }

    return stack.reduce((a, b) => a + b, 0);
  }
}

測(cè)試示例:

  • calculate('1 + 1') → 2
  • calculate(' 2-1 + 2 ') → 3
  • calculate('10 - 2*3 + 8/2') → 8
  • calculate('(1+(4+5+2)-3)+(6+8)') → 23
  • calculate('3*(2-6/(3-7))') → 12

小結(jié)

整體路線(xiàn)可以概括成:

步驟能力核心點(diǎn)
1字符串轉(zhuǎn)整數(shù)while 讀連續(xù)數(shù)字,num = num * 10 + (c - '0'),多位數(shù)左移再累加
2加減同上 while 讀數(shù)字,讀完按 sign 入棧;減法當(dāng)加負(fù)數(shù),最后求和
3乘除同上 while 讀數(shù)字,讀完用 addNumToStack;乘除與棧頂算完再入棧
4括號(hào)(未優(yōu)化)同上 while 讀數(shù)字;每層 getBracketMap(s),遞歸用 s.slice 傳子串
5優(yōu)化不 slice、只傳區(qū)間 calc(low,high);只算一次 bracketMap;注意 i-- 與越界

按這個(gè)順序?qū)崿F(xiàn),每一步都能單獨(dú)測(cè),最后再接到一起,就是一個(gè)清晰、可維護(hù)的字符串計(jì)算器實(shí)現(xiàn)。如果你愿意,還可以在此基礎(chǔ)上再擴(kuò)展:負(fù)號(hào)、空格預(yù)處理、更多運(yùn)算符等,思路都是在這一套「棧 + 符號(hào) + 區(qū)間」上做延伸。

如果你按「字符串轉(zhuǎn)整數(shù) → 加減 → 乘除 → 括號(hào) → 優(yōu)化」的順序自己實(shí)現(xiàn)一遍,再和這份代碼對(duì)照,會(huì)更容易抓住每一步的意圖和易錯(cuò)點(diǎn)。

到此這篇關(guān)于基于JavaScript實(shí)現(xiàn)字符串計(jì)算器的實(shí)戰(zhàn)指南的文章就介紹到這了,更多相關(guān)JavaScript字符串計(jì)算器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • javascript間隔刷新的簡(jiǎn)單實(shí)例

    javascript間隔刷新的簡(jiǎn)單實(shí)例

    這篇文章介紹了javascript間隔刷新的簡(jiǎn)單實(shí)例,有需要的朋友可以參考一下
    2013-11-11
  • 解決window.opener=null;window.close(),只支持IE6不支持IE7,IE8的問(wèn)題

    解決window.opener=null;window.close(),只支持IE6不支持IE7,IE8的問(wèn)題

    本篇文章主要是對(duì)window.opener=null;window.close(),只支持IE6不支持IE7,IE8的解決方法進(jìn)行了介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助
    2014-01-01
  • PyQt中QLabel標(biāo)簽組件使用指南

    PyQt中QLabel標(biāo)簽組件使用指南

    QLabel是PyQt中用于顯示文本和圖像的組件,本文詳細(xì)介紹了其基本用法和高級(jí)特性,首先,介紹如何創(chuàng)建QLabel,以及如何用它來(lái)顯示圖像,接著,探討了如何使用HTML標(biāo)簽格式化文本,設(shè)置文本對(duì)齊方式,以及如何實(shí)現(xiàn)自動(dòng)換行
    2024-10-10
  • JavaScript實(shí)現(xiàn)彩虹文字效果的方法

    JavaScript實(shí)現(xiàn)彩虹文字效果的方法

    這篇文章主要介紹了JavaScript實(shí)現(xiàn)彩虹文字效果的方法,涉及javascript操作文字樣式的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-04-04
  • JS利用正則表達(dá)式實(shí)現(xiàn)簡(jiǎn)單的密碼強(qiáng)弱判斷實(shí)例

    JS利用正則表達(dá)式實(shí)現(xiàn)簡(jiǎn)單的密碼強(qiáng)弱判斷實(shí)例

    這篇文章主要給大家介紹了關(guān)于JS利用正則表達(dá)式實(shí)現(xiàn)簡(jiǎn)單的密碼強(qiáng)弱判斷的相關(guān)資料,實(shí)現(xiàn)后的效果非常簡(jiǎn)單,但也挺實(shí)用的,文中給出了詳細(xì)的示例代碼供大家參考學(xué)習(xí),需要的朋友們下面來(lái)一起看看吧。
    2017-06-06
  • layui use 定義js外部引用函數(shù)的方法

    layui use 定義js外部引用函數(shù)的方法

    今天小編就為大家分享一篇layui use 定義js外部引用函數(shù)的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2019-09-09
  • Vue formData實(shí)現(xiàn)圖片上傳

    Vue formData實(shí)現(xiàn)圖片上傳

    這篇文章主要為大家詳細(xì)介紹了Vue formData實(shí)現(xiàn)圖片上傳,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • javascript模擬鼠標(biāo)點(diǎn)擊事件原理和實(shí)現(xiàn)方法

    javascript模擬鼠標(biāo)點(diǎn)擊事件原理和實(shí)現(xiàn)方法

    本文詳細(xì)介紹了JS模擬鼠標(biāo)點(diǎn)擊事件的原理以及應(yīng)用場(chǎng)景,并提供了模擬鼠標(biāo)左鍵點(diǎn)擊事件、右鍵點(diǎn)擊事件、滾輪事件和移動(dòng)事件的代碼實(shí)現(xiàn),了解JS模擬鼠標(biāo)點(diǎn)擊事件的原理和實(shí)現(xiàn)方法對(duì)于開(kāi)發(fā)人員非常重要,這對(duì)于許多面向用戶(hù)的web應(yīng)用程序的開(kāi)發(fā)和測(cè)試都具有很重要的意義
    2023-09-09
  • 奇妙的js

    奇妙的js

    奇妙的js...
    2007-09-09
  • p5.js入門(mén)教程之圖片加載

    p5.js入門(mén)教程之圖片加載

    這篇文章主要介紹了p5.js入門(mén)教程之圖片加載,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-03-03

最新評(píng)論

内乡县| 平武县| 镇远县| 本溪市| 福清市| 贞丰县| 宜章县| 襄垣县| 广饶县| 定结县| 镇安县| 谢通门县| 双流县| 灌云县| 屏东县| 佛山市| 循化| 仁化县| 成安县| 建瓯市| 永川市| 德令哈市| 华安县| 高青县| 义马市| 革吉县| 新河县| 玉山县| 绵阳市| 阿巴嘎旗| 安仁县| 玉田县| 罗源县| 镇江市| 东阳市| 平南县| 邵阳市| 敦化市| 黄石市| 三亚市| 浮梁县|