1 運算符與表達式核心概念
1.1 什么是運算符
????????運算符是編程和數學中具有特定功能的符號,用于對數據進行運算、賦值、比較及邏輯處理等操作。它們能夠改變、組合或比較操作數的值,進而生成新值或觸發特定動作。
1.2 什么是表達式
????????表達式是編程和數學中用于表達計算或操作的結構,由運算數(如變量、常量)和運算符按特定規則組合而成。
????????表達式能夠計算或代表一個確定的值,形式可簡單(如單一變量或常量)也可復雜(包含多個運算符、運算數,甚至嵌套函數調用、條件表達式等)。
1.3 什么是操作數
????????在 C 語言的表達式中,操作數是指參與運算符運算的具體值或變量。操作數通常與運算符結合使用,以完成特定的計算或操作。根據運算符的類型,操作數可以分為左操作數和右操作數,尤其是在二元運算符(如 +、-、*、/ 等)及部分需要兩個操作數的操作(如賦值)的上下文中。
- 左操作數:位于運算符左側,在賦值操作中通常為變量,用于存儲運算結果。
- 右操作數:位于運算符右側,可以是變量、常量或表達式的結果。
????????例如,在表達式 a = b + 5; 中,a 為左操作數,b + 5 為右操作數(其中 b 和 5 分別為加法運算符 + 的左、右操作數)。
1.4 運算符分類
按操作數個數分類:
- 一元運算符(一目運算符):作用于單個操作數。
- 二元運算符(二目運算符):作用于兩個操作數。
- 三元運算符(三目運算符):作用于三個操作數(如 C 語言中的條件運算符 ?:)。
按功能分類:
- 算術運算符:執行基本數學運算,如 +、-、*、/、%(取模)。
- 關系運算符:比較兩個操作數的關系,返回布爾值,如 ==、!=、>、<、>=、<=。
- 邏輯運算符:對布爾值進行邏輯運算,如 &&(與)、||(或)、!(非)。
- 位運算符:對操作數的二進制位進行操作,如 &(按位與)、|(按位或)、^(按位異或)、~(按位取反)、<<(左移)、>>(右移)。
- 賦值運算符:用于給變量賦值,如 =、+=、-= 等。
1.5 如何掌握運算符
????????要全面掌握一個運算符,需關注以下幾個方面:
- 含義:明確運算符的具體功能(如 & 是按位與,&& 是邏輯與)。
- 操作數個數與類型:區分一元(如 !)、二元(如 +)、三元(如 ?:),并注意類型要求(如 %?需整數)。
- 表達式值的計算規則:理解運算符優先級(如 * 高于 +)、結合性(如 a - b - c 從左到右)及隱式轉換(如 int + float 轉為 float)。
- 副作用與狀態修改:判斷運算是否直接修改操作數(如 ++、--、賦值運算符 = 會修改,而 +、== 不會)。
2 算術運算符
????????算術運算符用于對數值類型變量進行運算,在 C 程序中廣泛使用。運算結果的類型和精度由操作數類型決定。
運算符 | 描述 | 操作數個數 | 組成的表達式的值 | 副作用 |
---|---|---|---|---|
+ | 正號 | 1 | 操作數本身 | 無 |
- | 負號 | 1 | 操作數符號取反 | 無 |
+ | 加法 | 2 | 兩數之和 | 無 |
- | 減法 | 2 | 兩數之差 | 無 |
* | 乘法 | 2 | 兩數之積 | 無 |
/ | 除法 | 2 | 兩數之商 | 無 |
% | 取模(取余) | 2 | 兩整型數相除的余數 | 無 |
++ | 自增 | 1 | 自增前 / 后的值 | 有 |
-- | 自減 | 1 | 自減前 / 后的值 | 有 |
2.1 正、負號
????????正號(+)和負號(-)是一元運算符,用于直接修改操作數的符號。它們可以獨立使用(如 +5、-3),也可在表達式中與其他運算符組合(如 -x + y)。
- 負號(-):將操作數的值取反(如 -12 變為 -12,-(-67) 變為 67)。
- 正號(+):通常不改變操作數的值,但可用于強調數值的正負性(如 +12 仍為 12)。
#include <stdio.h>int main()
{int x = 12;int x1 = -x, x2 = +x;printf("x=%d,x1=%d, x2=%d\n", x, x1, x2); // x=12,x1=-12, x2=12int y = -67;int y1 = -y, y2 = +y;printf("y=%d,y1=%d, y2=%d\n", y, y1, y2); // y=-67,y1=67, y2=-67return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
2.2?加、減、乘、除
????????算術運算符 +(加)、-(減)、*(乘)、/(除)是二元運算符,用于執行基本的數學運算。
- 加法(+):將兩個操作數相加(如 5 + 2.5 結果為 7.5,但整數類型會截斷小數部分)。
- 減法(-):計算兩數之差(如 7 - 2.5 結果為 4.5,整數類型截斷后為 4)。
- 乘法(*):計算兩數乘積(如 7 * 7 結果為 49)。
- 除法(/):
- 若操作數為整數,執行整數除法(如 6 / 4 結果為 1,丟棄小數部分)。
- 若至少一個操作數為浮點數,執行浮點除法(如 6.0 / 4 或 6 / 4.0 結果為 1.5)。
#include <stdio.h>int main()
{int a = 5 + 2.5;printf("a 的值為:%d\n", a); // 輸出:7(7.5 被截斷)printf("%d * %d = %d\n\n", a, a, a * a); // 輸出:49a = a - 2.5;printf("a 的值為:%d\n", a); // 輸出:4(4.5 被截斷)printf("%d * %d = %d\n\n", a, a, a * a); // 輸出:16double b = 6 / 4; // 整數除法:6/4=1,再轉為 double → 1.00double c = 6.0 / 4; // 浮點除法:1.50double d = 6 / 4.0; // 浮點除法:1.50double e = (double)6 / 4; // 強制類型轉換后除法:1.50double f = 6 / (double)4; // 強制類型轉換后除法:1.50printf("b = %.2f c = %.2f d = %.2f e = %.2f f = %.2f\n", b, c, d, e, f);return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
2.3 取模(取余)
????????取模運算符 % 用于計算兩個整數相除后的余數,要求操作數必須為整數類型(如 int、long)。
- 規則:結果的符號與左操作數一致(如 -10 % 3 結果為 -1,10 % -3 結果為 1)。
- 浮點數限制:% 不可用于浮點數(如 5.0 % 2 會報錯),因浮點除法不產生整數商和余數。
#include <stdio.h>int main()
{// 基礎取模運算int res1 = 10 % 3;printf("10 %% 3 = %d\n", res1); // 輸出: 1(商 3 余 1)// 負數取模:結果符號與左操作數一致int res2 = -10 % 3;printf("-10 %% 3 = %d\n", res2); // 輸出: -1(商 -3 余 -1)int res3 = 10 % -3;printf("10 %% -3 = %d\n", res3); // 輸出: 1(商 -3 余 1)int res4 = -10 % -3;printf("-10 %% -3 = %d\n", res4); // 輸出: -1(商 3 余 -1)// 不可對浮點數進行取模運算,編譯器會報錯!!!// float res5 = 10.5 % 3.2;return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
????????在 C 語言中,直接對浮點數使用取模運算符(%)會引發編譯錯誤 ,如下所示:
2.4 自增與自減
????????自增(++)和自減(--)運算符 是 C 語言中用于對變量進行 +1 或 -1 操作的單目運算符,分為前綴形式和后綴形式。它們的核心區別在于表達式返回值和變量修改順序:
- 前綴形式(++i、--i):
- 先修改值,再返回新值。
- 示例:i = ++a 會先執行 a = a + 1,然后將新值 a 賦給 i。
- 后綴形式(i++、i--):
- 先返回原值,再修改值。
- 示例:i = a++ 會先將 a 的當前值賦給 i,再執行 a = a + 1。
- 副作用一致性:
- 無論前綴或后綴,變量最終值均會被修改(如 a++ 和 ++a 最終都會使 a = a + 1)。
- 表達式值差異:
- 前綴返回新值(如 ++a 直接返回 a+1)。
- 后綴返回原值(如 a++ 返回修改前的 a)。
- 常見應用場景:
- 循環計數器(如 for (int i=0; i<10; i++))。
- 簡化代碼(如 while (--n > 0))。
#include <stdio.h>int main()
{// 基礎用法:驗證變量最終值(無論前綴/后綴,變量均被修改)int a = 8;a++; // 后綴:a 變為 9printf("a = %d\n", a);++a; // 前綴:a 變為 10printf("a = %d\n", a);a--; // 后綴:a 變為 9printf("a = %d\n", a);--a; // 前綴:a 變為 8printf("a = %d\n", a);// 前綴 vs 后綴對表達式的影響int i1 = 10, i2 = 20;// 后綴:先賦值原值,再自增int i = i1++; // i = 10(原值),i1 = 11printf("i=%d, i1=%d\n", i, i1);// 前綴:先自增,再賦值新值i = ++i1; // i1 = 12,i = 12printf("i=%d, i1=%d\n", i, i1);// 后綴:先賦值原值,再自減i = i2--; // i = 20(原值),i2 = 19printf("i=%d, i2=%d\n", i, i2);// 前綴:先自減,再賦值新值i = --i2; // i2 = 18,i = 18printf("i=%d, i2=%d\n", i, i2);return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
????????在 C 語言中,自增(++)和自減(--)運算符只能用于可修改的左值(變量),不能用于常量或字面量。嘗試這樣做會導致編譯錯誤:
復雜自增/減運算
????????在 C 語言編程中,理解運算符的求值順序和避免未定義行為至關重要。某些看似簡單的表達式,如涉及多個自增(++)和自減(--)運算符的復合表達式,其實際行為可能因編譯器而異,導致不可預測的結果。下面通過一個具體示例,分析這類表達式的常見行為,并探討如何避免此類問題以及如何利用編譯器警告來輔助開發。
#include <stdio.h>int main()
{int num = 20;int sum = num++ - --num + ++num;printf("The sum is %d\n", sum); // 輸出:The sum is 21, 不同編譯器結果可能不一樣printf("The num is %d\n", num); // 輸出:The num is 21, 不同編譯器結果可能不一樣return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
程序運算過程分析(以常見編譯器行為【從左到右計算】為例):
- num++:先使用 num 的值 20 作為整個表達式(num++)的值,然后 num 自增為 21。
- --num:num 先從 21 自減為 20,然后整個表達式(--num)使用 20 作為其自身的值,這里 num 最后為 20。
- ++num:num 先從 20 自增為 21,然后整個表達式(++num)使用 21 作為其自身的值,這里 num 最后為 21。
- 最終計算:?sum = 20 - 20 + 21 = 21,num 最終為 21。
未定義行為警告:
????????C 標準未明確規定上述這種復雜表達式的求值順序,不同編譯器可能產生不同的結果。這種代碼依賴于編譯器的具體實現,屬于未定義行為(undefined behavior)。
編程建議:
- 避免編寫此類難以理解和維護的復雜表達式。
- 建議拆分為多個簡單語句以提高可讀性和可預測性。
VS Code 警告配置
????????在之前的學習中,我們了解到可以在 VS Code 的當前工作區的 tasks.json 文件中修改編譯指令。例如,我們之前添加了 -Wconversion 參數,現在我們加上 -Wall 和 -Wextra 這兩個參數。
- -Wconversion:
- 啟用對隱式類型轉換的警告。
- 幫助開發者發現由于類型轉換而可能導致的精度丟失或數據截斷問題。
- -Wall:
- 啟用 “所有” 警告信息。實際上,它并不是啟用所有的警告,而是一個常用的警告集合。
- 包括一些常見的警告,比如未使用的變量、未初始化的變量、類型不匹配等。
- -Wextra:
- 啟用一些額外的警告信息。
- 包含一些不在 -Wall中 的警告,比如函數返回類型不匹配、使用空指針解引用等。
????????加上這兩個參數之后,如果我們再重新編譯上面這個程序,編譯器就會發出警告信息提示我們,如下所示:
3 關系運算符
????????在 C 語言中,關系運算符(也稱比較運算符)用于比較兩個值,并返回一個布爾結果。這些運算符在條件判斷、循環控制等場景中非常常見。
????????關系運算符的操作數個數為 2,表達式的值為 0(假)或 1(真),且沒有副作用。
運算符 | 描述 | 操作數個數 | 返回值(表達式的值) | 說明 | 副作用 |
---|---|---|---|---|---|
== | 相等 | 2 | 0 或 1 | 判斷兩個操作數是否相等 | 無 |
!= | 不等 | 2 | 0 或 1 | 判斷兩個操作數是否不相等 | 無 |
< | 小于 | 2 | 0 或 1 | 判斷左操作數是否小于右操作數 | 無 |
> | 大于 | 2 | 0 或 1 | 判斷左操作數是否大于右操作數 | 無 |
<= | 小于等于 | 2 | 0 或 1 | 判斷左操作數是否小于或等于右操作數 | 無 |
>= | 大于等于 | 2 | 0 或 1 | 判斷左操作數是否大于或等于右操作數 | 無 |
- 在 C 語言中,關系運算符的返回值為 0(表示假)或 1(表示真)。
- 任何非零值在布爾上下文中都被視為真,但在關系運算符的上下文中,返回值始終為 0 或 1。
#include <stdio.h>int main()
{int a = 8;int b = 7;// 使用關系運算符進行比較,并打印結果printf("a > b 的值:%d\n", a > b); // 輸出 1(真),因為 8 > 7printf("a >= b 的值:%d\n", a >= b); // 輸出 1(真),因為 8 >= 7printf("a < b 的值:%d\n", a < b); // 輸出 0(假),因為 8 不小于 7printf("a <= b 的值:%d\n", a <= b); // 輸出 0(假),因為 8 不小于或等于 7printf("a == b 的值:%d\n", a == b); // 輸出 0(假),因為 8 不等于 7printf("a != b 的值:%d\n", a != b); // 輸出 1(真),因為 8 不等于 7return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
4?邏輯運算符
????????邏輯運算符用于對布爾值進行邏輯操作,通常用于條件判斷和控制程序流程。
運算符 | 描述 | 操作數個數 | 返回值(表達式的值) | 副作用 |
---|---|---|---|---|
&& | 邏輯與 | 2 | 0 或 1 | 無 |
|| | 邏輯或 | 2 | 0 或 1 | 無 |
! | 邏輯非 | 1 | 0 或 1 | 無 |
4.1 邏輯與 &&
- 運算規則:當且僅當兩個操作數均為真(非零)時,整個表達式結果為真;否則為假(遵循 “一假則假” 規則)。
- 短路現象:若第一個操作數為假,直接判定整個表達式為假,不再計算第二個操作數(避免不必要的執行)。例如,在邏輯與表達式 A && B 中,如果 A 為假(false),則無論 B 的值如何,整個表達式的結果都為假,因此 B 不會被計算。
#include <stdio.h>int main()
{double score = 70;// 使用邏輯與(&&)運算符檢查 score 是否在 60 到 80(包括 60 和 80)之間if (score >= 60 && score <= 80){// 當 score 在 60 到 80 之間時,打印 "ok1"printf("ok1\n");}else{// 如果 score 不在這個范圍內,則執行這個分支printf("ok2\n");}int a = 10, b = 99;// 展示邏輯與運算符的短路現象if (a < 2 && ++b > 99){// 這個分支不會執行,因為 a < 2 的結果為假,導致整個條件表達式為假printf("ok100");}printf("b=%d\n", b); // 由于短路現象,b 的值仍然是 99,沒有因為 ++b 而改變return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
注意:C 語言中的條件判斷寫法
????????在 C 語言中,邏輯與運算符?&&?允許我們以一種直觀的方式組合多個條件,例如?score >= 60 && score <= 80,這表示?score?必須在 60 和 80 之間(包括 60 和 80)。這種寫法是合法的,并且易于理解。
????????然而,需要注意的是,C 語言中不能像數學中那樣直接使用鏈式比較,例如?60 <= score <= 80。這種寫法在數學中是合法的,但在 C 語言中會被解釋為?(60 <= score) <= 80。這意味著?60 <= score?會被先計算為一個布爾值(0 或 1),然后再與 80 進行比較。這種解釋方式通常不符合我們的意圖,因此會導致邏輯錯誤。
????????因此,在 C 語言中,我們應該始終使用邏輯與運算符?&&?來組合多個條件,以確保我們的邏輯判斷是正確的。
4.2 邏輯或 ||
- 運算規則:若任意一個操作數為真(非零),整個表達式結果為真;僅當兩個操作數均為假時,結果為假(遵循 “一真則真” 規則)。
- 短路現象:若第一個操作數為真,直接判定整個表達式為真,不再計算第二個操作數(避免不必要的執行)。例如,在邏輯或表達式 A || B 中,如果 A 為真(true),則無論 B 的值如何,整個表達式的結果都為真,因此 B 不會被計算。
#include <stdio.h>int main()
{double score = 70;// 使用邏輯或(||)運算符檢查 score 是否滿足大于等于 70 或小于等于 80 的條件if (score >= 70 || score <= 80){printf("ok1\n"); // 由于條件表達式總是為真,所以這里總是打印 "ok1"}else{printf("ok2\n"); // 這個分支永遠不會執行,因為上面的條件表達式總是為真}int a = 10, b = 99;// 展示邏輯或運算符的短路現象if (a > 5 || ++b > 100){printf("ok100\n"); // 由于 a > 5 為真,所以這里會打印 "ok100"}printf("b=%d\n", b); // 打印 b 的值,由于短路現象,b 的值仍然是 99return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
4.3?邏輯非?!
- 描述:對操作數的布爾值進行取反。
#include <stdio.h>int main()
{int score = 100;int res = score > 99;printf("res = %d\n", res); // 100 > 99,為真,輸出:1printf("!res = %d\n", !res); // 取反,輸出:0// 檢查 res 的值,如果 res 為真(即 score > 99),則執行以下語句if (res){printf("yes, Thanks\n"); // 因為 score 確實大于 99,所以這里會打印 "yes, Thanks"}// 緊接著檢查 !res 的值,即 res 的否定。由于 res 為真(1),!res 為假(0)if (!res){printf("no,Thanks \n"); // 這個語句不會執行,因為 !res為假}return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
5 編程練習
5.1 購物折扣計算
????????你在超市購物,買了 3 盒牛奶(每盒 5 元)和 2 袋面包(每袋 8 元)。超市正在打折:如果購物總價滿 30 元,可以減 5 元。編寫一個程序,計算并輸出未打折前的總價和打折后的實際支付金額。
#include <stdio.h>int main()
{// 牛奶面包的單價int milk_price = 5, bread_price = 8;// 牛奶和面包的購買數量int milk_count = 3, bread_count = 2;// 計算總價int total = milk_price * milk_count + bread_price * bread_count;// 打折后的總價int discounted_total = total;// 判斷是否滿足打折條件if (total >= 30){// 滿足打折條件,減 5 元discounted_total = total - 5;}printf("未打折總價:%d 元\n", total);printf("實際支付:%d 元\n", discounted_total);return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
5.2?兒童過山車資格
????????游樂園規定:身高超過 120 厘米或年齡超過 12 歲的兒童可以單獨乘坐過山車。編寫一個程序,輸入兒童的身高(厘米)和年齡,判斷是否可以單獨乘坐過山車,并輸出結果。
#include <stdio.h>int main()
{float height; // 兒童的身高int age; // 兒童的年齡printf("請輸入兒童的身高(厘米):");scanf("%f", &height);printf("請輸入兒童的年齡:");scanf("%d", &age);// 判斷是否滿足乘坐條件// 身高大于 120 厘米 或 年齡大于 12 歲if (height > 120 || age > 12){printf("可以乘坐過山車\n");}else{printf("不可以乘坐過山車\n");}return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
5.3 周末公園計劃
????????今天是周末,你計劃去公園玩。如果天氣晴朗且溫度在 20°C 到 30°C 之間,就去公園;否則在家看書。編寫一個程序,輸入天氣和當前溫度,判斷是否去公園,并輸出結果。
#include <stdio.h>int main()
{int sunny; // 是否晴朗:1 表示是,0 表示否float temperature; // 當前溫度printf("今天天氣晴朗嗎?(1 表示是,0 表示否):");scanf("%d", &sunny);printf("當前溫度(°C)為:");scanf("%f", &temperature);// 判斷是否去公園:晴朗且溫度在 20 到 30 度之間if (sunny == 1 && temperature >= 20 && temperature <= 30)// 等價于 if (sunny && temperature >= 20 && temperature <= 30){printf("去公園\n");}else{printf("在家看書\n");}return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
5.4?電影票價格
????????電影院對學生打折:學生票 20 元,成人票 35 元。如果觀看的是 3D 電影,所有票價加 10 元。編寫一個程序,輸入是否是學生和是否是 3D 電影,計算并輸出票價。
#include <stdio.h>int main()
{int is_student; // 是否是學生:1 表示是,0 表示否int is_3d; // 是否是 3D 電影:1 表示是,0 表示否int ticket_price; // 票價printf("是否是學生?(1 表示是,0 表示否):");scanf("%d", &is_student);printf("是否是 3D 電影?(1 表示是,0 表示否):");scanf("%d", &is_3d);// 1. 計算基礎票價if (is_student == 1){ticket_price = 20; // 學生票價}else{ticket_price = 35; // 成人票價}// 2. 判斷是否加收 3D 費用if (is_3d == 1){ticket_price += 10; // 3D 電影加收費用}printf("票價:%d 元\n", ticket_price);return 0;
}
? ? ? ? 上面這個程序可以進行簡化操作,由于我們已經將 is_student 和 is_3d 定義為 int 類型,并且約定輸入值為 1 或 0 來表示真假,我們可以直接在條件中使用這些變量,而不需要顯式地與 1 進行比較。
#include <stdio.h>int main()
{int is_student; // 是否是學生:非零表示是,0 表示否int is_3d; // 是否是 3D 電影:非零表示是,0 表示否int ticket_price; // 票價printf("是否是學生?(1 表示是,0 表示否):");scanf("%d", &is_student);printf("是否是 3D 電影?(1 表示是,0 表示否):");scanf("%d", &is_3d);// 1. 計算基礎票價if (is_student) // 直接判斷是否非零{ticket_price = 20; // 學生票價}else{ticket_price = 35; // 成人票價}// 2. 判斷是否加收 3D 費用if (is_3d) // 直接判斷是否非零{ticket_price += 10; // 3D 電影加收費用}printf("票價:%d 元\n", ticket_price);return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
5.5?健康飲食建議
????????根據每日攝入的卡路里和運動量,給出健康建議:
- 如果卡路里攝入 > 2000 且運動量 < 30 分鐘,建議 "減少高熱量食物"。
- 如果卡路里攝入 < 1500 且運動量 > 60 分鐘,建議 "增加營養攝入"。
- 其他情況,建議 "保持現狀"。
????????編寫一個程序,輸入卡路里攝入和運動量(分鐘),輸出健康建議。
#include <stdio.h>int main()
{int calories, exercise_minutes; // 今日卡路里攝入量和運動量(分鐘)printf("請輸入今日卡路里攝入量:");scanf("%d", &calories);printf("請輸入今日運動量(分鐘):");scanf("%d", &exercise_minutes);// 根據條件給出建議if (calories > 2000 && exercise_minutes < 30){printf("建議:減少高熱量食物\n");}else if (calories < 1500 && exercise_minutes > 60){printf("建議:增加營養攝入\n");}else{printf("建議:保持現狀\n");}return 0;
}
????????程序在 VS Code 中的運行結果如下所示:
提示:
????????在上述編程練習中,我們使用了 if 分支語句和 if-else if 多分支語句來實現邏輯判斷。如果你現在還不太懂這些語句,沒關系!從 if(如果)、else(否則)這些單詞可以猜到它們的意思。后面我們會專門學條件判斷,學完后再回來做這些練習,你會覺得輕松很多!