Skip to content

C# 算术运算符:从加减乘除到自增自减 ​

前置认知:程序的「计算器」 ​

想象你正在使用一台多功能计算器。加减乘除是基本功能,但当你按下 1 ÷ 3 时,普通计算器显示 0.333...,而整数计算器直接显示 0——这就是「整数除法」与「浮点除法」的区别。

C# 的算术运算符就是这台计算器的按钮。但比实体计算器更强大的是:它处理的不只是数字,还有不同「精度等级」的数据类型,以及「先算哪一步」的优先级规则。理解这些规则,能避免「1 除以 3 等于 0」这类让人摸不着头脑的 Bug。

算术运算符全景图 ​

C# 算术运算符
│
├─ 基础二元运算符(两个操作数)
│  ├─ +  加法 / 字符串拼接
│  ├─ -  减法
│  ├─ *  乘法
│  ├─ /  除法(整数除法 vs 浮点除法)
│  └─ %  取余(模运算)
│
├─ 一元运算符(单个操作数)
│  ├─ +  正号(通常省略)
│  └─ -  负号(取相反数)
│
├─ 复合赋值运算符
│  ├─ +=  加后赋值
│  ├─ -=  减后赋值
│  ├─ *=  乘后赋值
│  ├─ /=  除后赋值
│  └─ %=  取余后赋值
│
└─ 自增自减运算符
   ├─ ++  自增(前置/后置)
   └─ --  自减(前置/后置)

术语小课堂:

  • 操作数:参与运算的数据,如 a + b 中的 a 和 b
  • 优先级:先算哪一步,如 1 + 2 * 3 先乘后加(像数学中的「先乘除后加减」)
  • 求值顺序:同优先级时从左到右,如 10 - 5 - 2 先算 10 - 5

一、基础算术运算符 ​

1.1 加法 +:数字求和与字符串拼接 ​

csharp
// 基础用法:数值加法
int a = 10, b = 20;
int sum = a + b;           // 30
Console.WriteLine(sum);

// 边界场景:字符串拼接
string firstName = "张";
string lastName = "三";
string fullName = firstName + lastName;  // "张三"
Console.WriteLine($"全名:{fullName}");

// 混合类型:字符串 + 数字 = 字符串拼接
string message = "年龄:" + 25;          // "年龄:25"
Console.WriteLine(message);

// 注意顺序:先算数字,再拼接
Console.WriteLine("结果:" + 1 + 2);     // "结果:12"(先拼接 "结果:1",再拼接 "2")
Console.WriteLine("结果:" + (1 + 2));   // "结果:3"(括号改变优先级)
Console.WriteLine(1 + 2 + "是结果");     // "3是结果"(先算 1+2=3,再拼接)

避坑提醒:当 + 的一边是字符串时,另一边会被自动转为字符串。1 + 2 + "abc" 得到 "3abc",而 "abc" + 1 + 2 得到 "abc12"。

1.2 减法 -:数值相减 ​

csharp
// 基础用法
int x = 15, y = 7;
int diff = x - y;          // 8

// 边界场景:负数结果
int small = 5, big = 10;
int negative = small - big;  // -5
Console.WriteLine(negative);

// 浮点数减法
double d1 = 10.5, d2 = 3.2;
double dResult = d1 - d2;    // 7.3(注意:浮点数可能有精度误差)

1.3 乘法 *:数值相乘 ​

csharp
// 基础用法
int length = 5, width = 3;
int area = length * width;   // 15

// 边界场景:溢出
int max = int.MaxValue;      // 2147483647
int overflow = max * 2;      // 默认不检查,结果错误!
Console.WriteLine(overflow); // 输出 -2(溢出后的错误值)

// 使用 checked 检查溢出
checked
{
    // int willThrow = max * 2; // 抛出 OverflowException
}

1.4 除法 /:整数除法 vs 浮点除法 ​

这是最容易踩坑的运算符!核心规则:两边都是整数时,结果也是整数(小数部分直接丢弃)。

csharp
// 基础用法:浮点除法
double a = 10, b = 3;
double result1 = a / b;      // 3.333...(正确)

// 陷阱:整数除法
int x = 10, y = 3;
int result2 = x / y;         // 3(不是 3.333!小数部分被截断)
Console.WriteLine(result2);  // 输出 3

// 混合运算:一边有小数,结果就是小数
int numerator = 10;
int denominator = 3;
double result3 = numerator / denominator;           // 3(整数除法,再转为 double)
double result4 = (double)numerator / denominator;   // 3.333...(先转 double,再除)
double result5 = numerator / (double)denominator;   // 同上

// 更隐蔽的陷阱
double wrong = 10 / 3;       // 3.0!整数除法后再转 double
double right = 10.0 / 3;     // 3.333...(至少一边是小数字面量)

避坑提醒:计算平均分、折扣、比例时,务必确保至少一边是浮点类型,否则结果永远是整数。

1.5 取余 %:模运算的正负逻辑 ​

取余运算(模运算)返回除法后的余数,常用于「是否整除」「循环索引」「奇偶判断」。

csharp
// 基础用法:判断奇偶
int number = 7;
bool isEven = number % 2 == 0;  // false(7 是奇数)
bool isOdd = number % 2 != 0;   // true

// 基础用法:循环索引
string[] colors = { "红", "绿", "蓝" };
for (int i = 0; i < 10; i++)
{
    string color = colors[i % colors.Length];  // 0,1,2,0,1,2... 循环
    Console.WriteLine(color);
}

// 核心细节:余数的正负性与被除数一致
Console.WriteLine(10 % 3);    // 1(10 是正数,余数 1)
Console.WriteLine(-10 % 3);   // -1(-10 是负数,余数 -1)
Console.WriteLine(10 % -3);   // 1(10 是正数,余数 1)
Console.WriteLine(-10 % -3);  // -1(-10 是负数,余数 -1)

// 记忆口诀:余数符号跟「被除数」走(左边那个数)

避坑提醒:C# 的 % 是「取余」而非「数学取模」。数学取模结果总是非负的,但 C# 取余可能为负。若需严格数学取模,使用 Math.FloorMod(.NET 7+)或自行调整。


二、一元算术运算符 ​

2.1 正号 +:通常省略 ​

csharp
int a = +5;        // 等价于 5,正号通常省略
int b = +(-3);     // -3,正号不改变负数的符号

// 实际用途:强调数值的正数属性(少见)
int positive = +10; // 明确表示这是正数

2.2 负号 -:取相反数 ​

csharp
int x = 5;
int negX = -x;     // -5

double price = 19.99;
double negativePrice = -price;  // -19.99

// 边界场景:对最小值取负会溢出
int min = int.MinValue;  // -2147483648
// int willOverflow = -min; // 溢出!正数没有对应的 2147483648

三、复合赋值算术运算符 ​

复合赋值是「运算 + 赋值」的简写,让代码更简洁。

3.1 基础用法 ​

csharp
int score = 100;

score += 10;   // score = score + 10;  结果 110
score -= 20;   // score = score - 20;  结果 90
score *= 2;    // score = score * 2;   结果 180
score /= 3;    // score = score / 3;   结果 60(整数除法)
score %= 7;    // score = score % 7;   结果 4

Console.WriteLine(score); // 4

3.2 隐式类型转换规则 ​

复合赋值运算符会自动处理类型转换,这是与展开写法的关键差异。

csharp
byte b = 100;

// 展开写法:编译错误!byte + byte 结果是 int,不能自动转回 byte
// b = b + 50;

// 复合赋值:编译通过!编译器自动插入显式转换
b += 50;       // 等价于 b = (byte)(b + 50);
Console.WriteLine(b); // 150

// 但溢出仍会发生
byte max = 255;
max += 1;      // 溢出!结果为 0(255 + 1 = 256,byte 最大 255)

避坑提醒:复合赋值 += 会自动插入类型转换,可能掩盖溢出错误。关键计算建议用展开写法并配合 checked。


四、自增自减运算符 ​

4.1 前置与后置的核心差异 ​

这是面试常考、新手必错的知识点!核心规则:前置先变后用,后置先用后变。

csharp
int a = 5;
int b = 5;

// 前置 ++:先加 1,再参与运算
int result1 = ++a;  // a 先变为 6,然后赋值给 result1
// a = 6, result1 = 6

// 后置 ++:先参与运算,再加 1
int result2 = b++;  // b 的当前值 5 赋值给 result2,然后 b 变为 6
// b = 6, result2 = 5

Console.WriteLine($"前置:a={a}, result1={result1}"); // a=6, result1=6
Console.WriteLine($"后置:b={b}, result2={result2}"); // b=6, result2=5

生活化类比:

  • 前置 ++:先涨工资,再干活(先变后用)
  • 后置 ++:先干活,再涨工资(先用后变)

4.2 复杂表达式中的执行顺序 ​

csharp
int i = 5;

// 分析:i++ + ++i
// 步骤 1:i++ → 取 i 的当前值 5,然后 i 变为 6
// 步骤 2:++i → i 先变为 7,然后取 7
// 结果:5 + 7 = 12
int result = i++ + ++i;
Console.WriteLine(result); // 12
Console.WriteLine(i);      // 7

// 更复杂的例子
int x = 1;
int y = x++ + x++ + x++;
// 步骤:1(x变2)+ 2(x变3)+ 3(x变4)= 6
Console.WriteLine(y); // 6
Console.WriteLine(x); // 4

避坑提醒:同一表达式中多次使用 ++/-- 会降低代码可读性,且不同编译器的求值顺序可能不同(C# 保证从左到右,但其他语言未必)。实际开发中应拆分简单语句。

4.3 自减 --:同理 ​

csharp
int c = 10;
int d = 10;

int r1 = --c;  // c 先变为 9,r1 = 9
int r2 = d--;  // d 的 10 赋给 r2,然后 d 变为 9

Console.WriteLine($"c={c}, r1={r1}"); // c=9, r1=9
Console.WriteLine($"d={d}, r2={r2}"); // d=9, r2=10

五、混合类型运算:不同数值类型的碰撞 ​

当 int、long、float、double、decimal 混合运算时,C# 会自动向「更高精度」的类型转换。

5.1 类型提升规则 ​

精度从低到高:int → long → float → double → decimal
csharp
int i = 10;
long l = 20;
float f = 1.5f;
double d = 2.5;
decimal m = 3.5m;

// int + long → long
long r1 = i + l;

// int + float → float
float r2 = i + f;

// int + double → double
double r3 = i + d;

// float + double → double(float 精度低于 double)
double r4 = f + d;

// double + decimal → 编译错误!double 和 decimal 不自动转换
// decimal r5 = d + m; // 错误!
decimal r5 = (decimal)d + m; // 正确:显式转换

// decimal 与任何类型混合 → decimal
decimal r6 = i + m; // int 自动转 decimal

避坑提醒:double 和 decimal 不能隐式转换。金融计算中若混用,必须显式转换,否则编译报错。

5.2 精度丢失的隐蔽场景 ​

csharp
// float 精度只有 6-7 位有效数字
float bigFloat = 123456789f;
Console.WriteLine(bigFloat); // 123456800(精度丢失!)

// double 精度 15-16 位
double bigDouble = 123456789.123456789;
Console.WriteLine(bigDouble); // 123456789.123457(四舍五入)

// decimal 精度 28-29 位,金融专用
decimal bigDecimal = 123456789.123456789m;
Console.WriteLine(bigDecimal); // 123456789.123456789(完整保留)

六、溢出检查:checked 与 unchecked ​

6.1 默认行为与显式控制 ​

csharp
int max = int.MaxValue; // 2147483647

// 默认(unchecked):溢出静默发生,结果错误但不报错
int silent = max + 1;
Console.WriteLine(silent); // -2147483648(绕回最小值)

// checked:溢出时抛出异常,暴露问题
try
{
    int loud = checked(max + 1); // 抛出 OverflowException
}
catch (OverflowException ex)
{
    Console.WriteLine($"溢出:{ex.Message}");
}

// 项目级设置:在 .csproj 中启用全局检查
// <PropertyGroup>
//     <CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
// </PropertyGroup>

6.2 实际应用场景 ​

csharp
// 金融计算:必须严格检查溢出
decimal CalculateInterest(decimal principal, decimal rate, int years)
{
    checked
    {
        // 若溢出立即抛异常,而非返回错误结果
        return principal * (decimal)Math.Pow(1 + (double)rate, years);
    }
}

// 高性能场景:确认安全后关闭检查提升性能
int FastSum(int[] numbers)
{
    unchecked
    {
        int sum = 0;
        foreach (int n in numbers)
            sum += n; // 若确认不会溢出,用 unchecked 提升性能
        return sum;
    }
}

七、null 值参与算术运算 ​

csharp
int? nullableA = 10;
int? nullableB = null;

// 可空类型与值类型运算:结果自动转为可空类型
int? result1 = nullableA + 5;     // 15
int? result2 = nullableA + nullableB; // null(任何数与 null 运算都得 null)

// 空合并运算符提供默认值
int safe = (nullableA ?? 0) + (nullableB ?? 0); // 10 + 0 = 10

// 除法中的 null
int? dividend = 10;
int? divisor = null;
int? quotient = dividend / divisor; // null(不会抛异常,因为可空类型)

// 但值类型除零会抛异常
int zero = 0;
// int crash = 10 / zero; // DivideByZeroException!

避坑提醒:可空类型的运算结果总是可空类型。使用 ?? 提供默认值,避免 null 传播导致后续计算全部失效。


八、常见坑点总结 ​

坑点错误示例正确做法原因
整数除法截断double d = 10 / 3; → 3.0double d = 10.0 / 3;先整数除法,再转 double
取余正负混淆x % 2 == 1 判断奇数x % 2 != 0负数取余得 -1
前置后置混淆int i = 5; Console.Write(i++ + ++i);拆分为两行可读性差,易出错
复合赋值溢出byte b = 255; b += 1; → 0用 checked 或展开写法隐式转换掩盖溢出
浮点精度丢失float f = 123456789f;用 double 或 decimalfloat 仅 7 位有效数字
double/decimal 混算decimal m = 1.0 + 1.0m;(decimal)1.0 + 1.0m无隐式转换
null 传播int? a = null; int b = a + 1;(a ?? 0) + 1null + 任何数 = null

九、实战场景应用 ​

场景 1:电商折扣计算 ​

csharp
// 计算商品折后价(保留两位小数)
decimal originalPrice = 199.99m;
int discountPercent = 15; // 85 折

// 错误:整数除法导致折扣计算错误
// decimal wrong = originalPrice * (discountPercent / 100); // discountPercent/100 = 0!

// 正确:显式转为 decimal 再除
decimal correct = originalPrice * (discountPercent / 100m); // 或 / 100.0m
Console.WriteLine($"折后价:{correct:F2}"); // 169.99

// 更专业的写法:避免中间精度损失
decimal finalPrice = originalPrice * discountPercent / 100m;
Console.WriteLine($"最终价:{finalPrice:F2}");

场景 2:分页计算总页数 ​

csharp
int totalItems = 47;  // 总数据量
int pageSize = 10;    // 每页条数

// 计算总页数:向上取整
int totalPages = totalItems / pageSize;           // 4(错误!还差 7 条)
int correctPages = (totalItems + pageSize - 1) / pageSize; // 5(正确)

// 或使用 Math.Ceiling
int ceilPages = (int)Math.Ceiling(totalItems / (double)pageSize); // 5

Console.WriteLine($"总页数:{correctPages}");

场景 3:循环队列索引 ​

csharp
string[] weekDays = { "周一", "周二", "周三", "周四", "周五", "周六", "周日" };
int todayIndex = 3; // 周四

// 计算 n 天后的星期
int n = 10;
int futureIndex = (todayIndex + n) % weekDays.Length;
Console.WriteLine($"{n}天后是:{weekDays[futureIndex]}"); // 周日

// 计算 n 天前的星期(处理负数)
int m = 5;
int pastIndex = (todayIndex - m + weekDays.Length) % weekDays.Length; // 加长度防负数
Console.WriteLine($"{m}天前是:{weekDays[pastIndex]}"); // 周六

十、练习题 ​

题目 1:整数除法陷阱 ​

以下代码输出什么?为什么?

csharp
int a = 7, b = 2;
double result = a / b;
Console.WriteLine(result);
查看答案提示

答案:3(不是 3.5)

解析:a / b 是整数除法,结果为 3(int 类型)。赋值给 double 时发生隐式转换,3 变为 3.0,但小数部分已在整数除法中被截断。修正:double result = (double)a / b;

题目 2:前置后置混合运算 ​

以下代码输出什么?

csharp
int x = 5;
int y = x++ + ++x * 2;
Console.WriteLine($"x={x}, y={y}");
查看答案提示

答案:x=7, y=19

解析:

  1. x++:取 5,x 变为 6
  2. ++x:x 先变为 7,取 7
  3. 7 * 2 = 14
  4. 5 + 14 = 19

题目 3:复合赋值与类型转换 ​

以下代码能否编译?若可以,输出什么?

csharp
byte b = 200;
b += 100;
Console.WriteLine(b);
查看答案提示

答案:编译通过,输出 44

解析:b += 100 等价于 b = (byte)(b + 100)。200 + 100 = 300,byte 最大 255,溢出后 300 - 256 = 44。若需检测溢出,使用 checked { b += 100; }。


知识点思维导图 ​

C# 算术运算符
│
├─ 基础二元运算符
│  ├─ + 加法/字符串拼接(注意拼接顺序)
│  ├─ - 减法
│  ├─ * 乘法(注意溢出)
│  ├─ / 除法
│  │  ├─ 整数/整数 = 整数(截断)
│  │  └─ 含小数 = 浮点除法
│  └─ % 取余
│     ├─ 余数符号与被除数一致
│     └─ 判断奇偶用 != 0
│
├─ 一元运算符
│  ├─ + 正号(省略)
│  └─ - 负号(取相反数)
│
├─ 复合赋值
│  ├─ += -= *= /= %=
│  └─ 隐式类型转换(byte += int 合法)
│
├─ 自增自减
│  ├─ 前置 ++/--:先变后用
│  ├─ 后置 ++/--:先用后变
│  └─ 避免在同一表达式中多次使用
│
├─ 混合类型运算
│  ├─ 自动向高精度提升:int→long→float→double→decimal
│  └─ double 与 decimal 需显式转换
│
├─ 溢出检查
│  ├─ checked:溢出抛异常
│  └─ unchecked:默认,溢出静默
│
└─ null 值运算
   ├─ 可空类型参与运算 → 结果可空
   ├─ null + 任何 = null
   └─ 用 ?? 提供默认值