目录
1 运算符分类
2 算术运算符与算术表达式
2.1 算术运算符的用法
2.2 左操作数和右操作数
3 关系运算符与关系表达式
3.1 关系运算符的用法
3.2 常量左置防错
3.3 三数相等判断误区
4 逻辑运算符与逻辑表达式
4.1 逻辑运算符的用法
4.2 闰年的判断
4.3 短路运算
5 赋值运算符
5.1 左值与右值
5.2 赋值操作的限制
5.3 复合赋值运算符
5.4 复合赋值运算符的运算规则与示例解析
6 sizeof 运算符
7 运算符优先级表
8 小节判断题
9 OJ 练习
9.1 课时3作业1
9.2 课时3作业2
1 运算符分类
C 语言提供了 13 种类型的运算符,如下所示:
(1)算术运算符(+ - * / % ) 。
(2)关系运算符(> < == >= <= != ) 。
(3)逻辑运算符(! && || ) 。
(4)位运算符(<< >> ~ | ^ & ) 。
(5)赋值运算符(= 及其扩展赋值运算符) 。
(6)条件运算符(? : ) 。
(7)逗号运算符(, ) 。
(8)指针运算符(* 和 & )——讲指针时讲解
(9)求字节数运算符(sizeof ) 。
(10)强制类型转换运算符((类型)) 。
(11)分量运算符(. -> ) 。——讲结构体时讲解
(12)下标运算符([ ] ) 。——讲数组时讲解
(13)其他(如函数调用运算符()) 。——讲函数时讲解
部分运算符在高级阶段进行讲解。
2 算术运算符与算术表达式
2.1 算术运算符的用法
算术运算符包含 “+”、“-”、“”、“/” 和 “%”,当一个表达式中同时出现这 5 种运算符时,先进行乘(*)、除(/)、取余(%) ,取余也称取模,后进行加(+)、减(-),也就是乘、除、取余运算符的优先级高于加、减运算符。
除 “%” 运算符外,其余几种运算符既适用于浮点型数又适用于整型数。当操作符 “/” 的两个操作数都是整型数时,它执行整除运算,在其他情况下执行浮点型数除法。
“%” 为取模运算符,它接收两个整型操作数,将左操作数除以右操作数,但它的返回值是余数而不是商。
由算术运算符组成的式子称为算术表达式,表达式一定有一个值。
#include <stdio.h>int main() {int a = 10, b = 3;float c = 5.0, d = 2.0;// 单独使用各个算术运算符printf("加法: %d + %d = %d\n", a, b, a + b); // 13printf("减法: %d - %d = %d\n", a, b, a - b); // 7printf("乘法: %d * %d = %d\n", a, b, a * b); // 30printf("除法(整型): %d / %d = %d\n", a, b, a / b); // 3printf("除法(浮点型): %.2f / %.2f = %.2f\n", c, d, c / d); // 2.50printf("取余: %d %% %d = %d\n", a, b, a % b); // 1// 综合使用算术运算符int result = 4+5*2-6/3+11%4;// 4 + 10 - 2 + 3 = 15printf("综合表达式结果: %d\n", result);// 注意事项 // 1. 整数除法时,结果会向下取整(即舍弃小数部分)。// 2. 运算符的优先级:乘、除、取余 > 加、减。可以使用括号改变运算顺序。// 3. % 运算符只适用于整型操作数。// 4. 浮点数运算可能产生精度问题,注意结果可能不是完全精确。return 0;
}
注意事项:
整数除法:当使用 / 运算符进行整数除法时,结果会向下取整到最接近的整数,这意味着任何小数部分都会被舍弃。
运算符优先级:算术运算符的优先级是固定的,乘、除、取余的优先级高于加、减。如果需要改变默认的运算顺序,可以使用括号 ()。
取模运算符:% 取模运算符只能用于整型操作数。如果尝试对浮点数使用 %,编译器会报错。
浮点数精度:浮点数运算可能会遇到精度问题,因为计算机中的浮点数表示是基于二进制的,某些十进制小数在二进制中可能无法精确表示。因此,进行浮点数运算时,结果的精度可能不是完全精确的。
类型提升:在表达式中混合使用整型和浮点型时,整型操作数会被提升为浮点型,然后执行浮点型运算。这可能会影响结果的类型和精度。
2.2 左操作数和右操作数
在 C 语言(以及大多数编程语言中),操作数是指参与运算的数据项或表达式的值。当我们在C语言中执行一个算术运算(如加法、减法、乘法、除法和取模)时,我们通常会涉及到至少两个操作数:左操作数和右操作数。
- 左操作数:在二元运算符(即需要两个操作数的运算符)的左侧的操作数。
- 右操作数:在二元运算符的右侧的操作数。
假设我们有以下表达式:
int a = 5;
int b = 3;
int sum = a + b;
在这个例子中,+ 是一个二元运算符,它有两个操作数:
- 左操作数是 a(值为 5)。
- 右操作数是 b(值为 3)。
3 关系运算符与关系表达式
3.1 关系运算符的用法
关系运算符 “>”、“<”、“==”、“>=”、“<=”、“!=” 依次为大于、小于、是否等于、大于等于、小于等于和不等于。
由关系运算符组成的表达式称为关系表达式。关系表达式的值只有真和假,对应的值为 1 和 0 。由于 C 语言中没有布尔类型,所以在 C 语言中 0 值代表假,非 0 值即为真。例如,关系表达式 3 > 4 为假,因此整体值为 0 ,而关系表达式 5 > 2 为真,因此整体值为 1 。
【关系运算符的优先级】 低于 【算术运算符】,运算符的优先级的详细情况见本文最后。
#include <stdio.h>int main() {int a = 5, b = 3, c = 2, d;// 演示关系运算符printf("%d > %d is %d\n", a, b, a > b); // 5 > 3 是真,输出 1printf("%d < %d is %d\n", a, b, a < b); // 5 < 3 是假,输出 0printf("%d == %d is %d\n", a, a, a == a); // 5 == 5 是真,输出 1printf("%d >= %d is %d\n", a, b, a >= b); // 5 >= 3 是真,输出 1printf("%d <= %d is %d\n", a, b, a <= b); // 5 <= 3 是假,输出 0printf("%d != %d is %d\n", b, c, b != c); // 3 != 2 是真,输出 1// 演示关系运算符与算术运算符的优先级d = (a + b) > (c * 2); // 先进行算术运算,再进行关系比较printf("(%d + %d) > (%d * 2) is %d\n", a, b, c, d); // (5 + 3) > (2 * 2) 是真,输出 1// 如果不加括号,算术运算符会先于关系运算符执行// 但这里由于表达式的自然结构,加不加括号结果一样// 只是为了说明优先级的概念d = a + b > c * 2; // 这实际上和上面加了括号的表达式效果一样,因为这里运算符的自然结合顺序也是先进行算术运算printf("Without parentheses, (%d + %d) > (%d * 2) is %d\n", a, b, c, d); // 输出也是 1return 0;
}
3.2 常量左置防错
在工作中,很多程序员容易不小心将两个等号写成一个等号,因此当判断整型变量 i 是否等于 3 时,我们可以写为 3 == i ,即把常量(如字面量或已定义的常量)写在前面而把变量写在后面。这是因为当不小心将两个等号写为一个等号时,变量在前面就会导致编译不通,从而快速发现错误(这种写法属于华为公司内部的一条编程规范)。
当你错误地将 == 写成 = 时,如果常量在左侧,那么这行代码将会尝试将常量赋值给变量,这在大多数情况下会导致编译错误(因为常量通常是不可写的),从而立即引起开发者的注意。例如:
if (3 = i) { // 错误,尝试将常量3赋值给变量i,导致编译错误 // ...
}
而如果你将变量放在左侧,那么这行代码在语法上是合法的,但逻辑上是错误的,因为它实际上是在将常量赋值给变量,而不是在比较它们。这样的错误可能更难在编译时被发现,因为编译器不会报错,但程序的逻辑将不符合预期:
if (i = 3) { // 正确编译,但逻辑错误,因为i被赋值为3,然后表达式的结果(即3,非0)被视为真 // ...
}
因此,将常量放在比较操作符的左侧是一种防御性编程的技巧,有助于避免此类逻辑错误。
3.3 三数相等判断误区
在编写程序时,如果我们需要判断三个数是否相等,那么绝对不可以写为 if(5==5==5) ,这种写法的值无论何时都为假,为什么?因为首先 5==5 得到的结果为 1 ,然后 1==5 得到的结果为 0 。如果要判断三个变量 a、b、c 是否相等,那么不能写为 a==b==c ,而应写为 a==b && b==c 。下面来看一个例子:
#include <stdio.h>// 不能用数学上的连续判断大小来判断某个数
int main()
{int a;while(scanf("%d",&a)) // 只要 scanf 函数成功读取到一个整数,就会一直执行循环{// 正确的判断方式应该是使用逻辑运算符 if(a > 3 && a < 10)if(3 < a < 10) // 错误的判断方式,在 C 语言中这种连续比较是不正确的{printf("a 在 3 和 10 之间\n");fflush(stdout);// Clion中需要加上这句代码才会上面的输出语句}else{printf("a 不在 3 和 10 之间\n");}}
}
输出结果:
如果要判断变量 a 是否大于 3 且同时小于 10 ,那么不能写为 3 < a < 10 , 这种写法在数学上的确是正确的,但是在程序中是错误的。首先,无论 a 是大于 3 还是小于 3 , 对于 3<a 这个表达式只有 1 或 0 两种结果。由于 1 和 0 都是小于 10 的,所以无论 a 的值为多少, 这个表达式的值始终为真,因此在判断变量 a 是否大于 3 且同时小于 10 时,要写成 a>3 && a<10 ,这才是正确的写法。
4 逻辑运算符与逻辑表达式
逻辑运算符 “!”、“&&”、“||” 依次为逻辑非、逻辑与、逻辑或,这和数学上的与、或、非是一致的。
!
运算符用于取反,即将真变为假,假变为真。
!!
是一个常见的技巧,用于将任何非零值转换为1(真),将0转换为0(假)。这是因为!
运算符会将非零值视为真并返回0(假),然后再取反就得到了1(真)。
&&
运算符用于逻辑与操作,只有当两边的表达式都为真时,结果才为真。一假即假!
||
运算符用于逻辑或操作,只要两边的表达式中有一个为真,结果就为真。一真即真!
逻辑非的优先级高于算术运算符,逻辑与和逻辑或的优先级低于关系运算符。
简单记就是: ! > 算术运算符 > 关系运算符 > && > || > 赋值运算符
由逻辑运算符组成的表达式称为逻辑表达式,逻辑表达式的值只有真和假,对应的值为 1 和 0。
4.1 逻辑运算符的用法
下面是一个包含逻辑运算符以及多重否定(如!!
)的程序。这个程序将演示如何使用这些逻辑运算符,并解释它们的行为:
#include <stdio.h>int main() {int a = 1; // 真(在C中,非零值被视为真)int b = 0; // 假(在C中,0被视为假)// 逻辑非(!)printf("!a = %d\n", !a); // 预期输出:!a = 0,因为a是真,非真为假printf("!b = %d\n", !b); // 预期输出:!b = 1,因为b是假,非假为真// 双重否定(!!)printf("!!a = %d\n", !!a); // 预期输出:!!a = 1,因为!a是0(假),再取非就是真printf("!!b = %d\n", !!b); // 预期输出:!!b = 0,因为!b是1(真),再取非就是假// 逻辑与(&&)printf("a && b = %d\n", a && b); // 预期输出:a && b = 0,因为a是真,但b是假,真与假为假printf("a && a = %d\n", a && a); // 预期输出:a && a = 1,因为a是真,真与真为真// 逻辑或(||)printf("a || b = %d\n", a || b); // 预期输出:a || b = 1,因为a是真,真或任何值都为真printf("b || b = %d\n", b || b); // 预期输出:b || b = 0,因为b是假,假或假还是假// 混合使用printf("!!(a && b) = %d\n", !!(a && b)); // 预期输出:!!(a && b) = 0,因为a && b是0(假),再取双重非还是假printf("!!(a || b) = %d\n", !!(a || b)); // 预期输出:!!(a || b) = 1,因为a || b是1(真),再取双重非还是真return 0;
}
4.2 闰年的判断
判断一个年份是否为闰年通常有以下两种常见的规则:
-
能被 4 整除但不能被 100 整除的年份为闰年。例如,2008 年是闰年,因为 2008 能被 4 整除但不能被 100 整除;1900 年不是闰年,因为它能被 100 整除但不能被 400 整除。
-
能被 400 整除的年份也是闰年。例如,2000 年是闰年,因为它能被 400 整除。
下例中的代码是计算一年是否为闰年的例子,因为需要重复测试,所以我们用了一个 while 循环:
#include <stdio.h>int main() {int year; // 定义一个整型变量 `year` 用于存储输入的年份while (scanf("%d", &year) && year!= 0) { // 只要能成功读取一个整数到 `year` 并且 `year` 不等于 0,就会一直执行循环if (year % 4 == 0 && year % 100!= 0 || year % 400 == 0) // 判断年份是否为闰年的条件{ printf("%d is leap year\n", year); // 如果是闰年,输出相应信息fflush(stdout); // 立即刷新标准输出缓冲区,确保输出内容立即显示}else{printf("%d is not leap year\n", year); // 如果不是闰年,输出相应信息fflush(stdout); // 立即刷新标准输出缓冲区,确保输出内容立即显示}}printf("Program exited.\n"); // 当循环结束(即输入为 0 时),输出程序已退出的信息return 0;
}
输出结果:
if (year % 4 == 0 && year % 100!= 0 || year % 400 == 0)
和
if ((year % 4 == 0 && year % 100!= 0) || year % 400 == 0)
这两个表达式的逻辑是一样的,括号在这里主要是为了增强代码的可读性和明确运算的先后顺序,即使不加括号,其运算结果也是相同的。但是,在初始时,由于是人工阅卷,如果括号写得较多,不便于阅卷老师阅卷,避免可分,能不加括号就不加括号,如这句代码:if (((year % 4 == 0) && (year % 100!= 0)) || (year % 400 == 0)),括号很多,完全没必要!
所以,常见运算符的优先级还是得记住!
4.3 短路运算
在 C 语言中,逻辑运算符 &&(逻辑与)和 ||(逻辑或)确实实现了短路运算(short-circuit evaluation)。这意味着,如果逻辑表达式的第一部分已经足够确定整个表达式的值,那么第二部分将不会被评估。这常用于条件判断中,以避免潜在的错误或不必要的计算。
下面是一个使用 && 运算符实现短路运算的例子,其中在短路运算符后面跟了 printf 打印语句,但由于短路的原因,这些打印语句可能不会被执行。
printf 函数返回一个整数值,该值表示实际输出的字符数(不包括结尾的空字符'\0')。如果输出成功,这个返回值将大于或等于0;如果发生错误,返回值将是负值。
#include <stdio.h>int main() {int a = 0;int b = 1;// 使用 && 运算符的短路特性// if 括号里面的printf不会被打印if (a && (printf("This && will not print.\n") > 0)) {// 这个代码块不会执行,因为 a 是 0(假),所以 && 表达式的第一部分已经决定了整个表达式的值printf("我不会被打印");}//让 && 表达式的第一部分为假// if 括号里面的printf会被打印if (!a && (printf("This && will print.\n") > 0)) {// 这个代码块会执行,因为 !a 是 1(真),所以需要评估第二部分来确定整个表达式的值printf("我会被打印1\n");}// 使用 || 运算符的短路特性// if 括号里面的printf不会被打印if (b || (printf("This will not print either, because b is already true.\n") > 0)) {// 这个代码块不会执行,因为 b 是 1(真),所以 || 表达式的第一部分已经足够确定整个表达式的值printf("我会被打印2\n");}// 让 || 表达式的第一部分为假// if 括号里面的printf会被打印if (0 || (printf("This || will print, because the first part of || is false.\n") > 0)) {// 这个代码块会执行,且 printf 语句也会执行,因为 || 表达式的第一部分是 0(假),所以需要评估第二部分来确定整个表达式的值printf("我会被打印3\n");}return 0;
}
输出结果:
5 赋值运算符
赋值运算符 = 在编程中用于将一个表达式的值赋给另一个变量。然而,这个操作受到左值和右值概念的限制。
5.1 左值与右值
在计算机编程中,特别是 C 和 C++ 等语言中,左值(L-value)和右值(R-value)是两个重要的概念,它们源于赋值操作的上下文,但具有更广泛的含义。
左值(L-value):左值是指可以出现在赋值表达式左侧的表达式。简单来说,左值代表了一个明确的内存位置,即它可以被赋予一个新值。在赋值操作中,左值用于存储右侧表达式计算出的结果。例如,在表达式 a = b + 25; 中,a 是一个左值,因为它代表了一个可以被赋新值的变量。
右值(R-value):右值则是指可以出现在赋值表达式右侧的表达式。右值通常表示一个具体的值或者一个临时对象,它不能表示一个可以存储新值的内存位置。在上面的例子中,b + 25 是一个右值,因为它是一个表达式的结果,这个结果可以赋值给左值。但是,b + 25 不能作为左值,因为它并未标识一个特定的位置(并不对应特定的内存空间)。因此,b + 25 = a 这条赋值语句是非法的。
5.2 赋值操作的限制
左值必须可修改:赋值操作要求左侧必须是一个左值,即一个可以存储新值的内存位置。如果尝试将一个右值(如表达式的结果)放在赋值语句的左侧,编译器会报错,因为右值没有内存位置来存储新值。
右值提供值:赋值操作的右侧可以是一个右值,它提供了要赋给左值的具体值。右值可以是字面量、表达式的结果、函数调用返回的值等。
示例与错误用法:
- 正确用法:a = b + 25;(a是左值,b + 25 是右值)
- 错误用法:b + 25 = a;(尝试将右值 b + 25 用作左值,这是不允许的)
- 编译错误结果,如下图所示:
5.3 复合赋值运算符
在C语言中,复合赋值运算符是一种简化的赋值方式,它将算术运算符或位运算符与赋值运算符(=)结合使用,以便在单个操作中完成值的计算和赋值。这些运算符包括 +=、-=、*=、/=、%= 等。下面是一张简明的表格,列出了这些复合赋值运算符及其功能:
运算符 | 含义 | 示例(假设 a = 5, b = 2) |
---|---|---|
+= | 加法赋值 | a += b; 等同于 a = a + b; 结果:a = 7 |
-= | 减法赋值 | a -= b; 等同于 a = a - b; 结果:a = 3 |
*= | 乘法赋值 | a *= b; 等同于 a = a * b; 结果:a = 10 |
/= | 除法赋值(整数除法时向下取整) | a /= b; 等同于 a = a / b; 结果:a = 2 |
%= | 求余赋值(模运算) | a %= b; 等同于 a = a % b; 结果:a = 1 |
下面是一个简单的C语言程序示例,展示了如何使用这些复合赋值运算符:
#include <stdio.h>int main() {int a = 5;int b = 2;// 使用复合赋值运算符a += b; // a = a + bprintf("a += b: a = %d\n", a); // 7a -= b; // a = a - bprintf("a -= b: a = %d\n", a); // 5a *= b; // a = a * bprintf("a *= b: a = %d\n", a); // 10a /= b; // a = a / bprintf("a /= b: a = %d\n", a); // 5a = 10; // 重置a的值a %= b; // a = a % bprintf("a %= b: a = %d\n", a); // 0return 0;
}
5.4 复合赋值运算符的运算规则与示例解析
在复合赋值运算符(如 +=、-=、*=、/=、%= 等)中,等号(=)后面的表达式首先被计算为一个整体的值,然后将这个值与等号左边的变量进行相应的算术或位运算,并将结果赋值回该变量。
以 a += b + 2; 为例,这里的计算过程可以分为两步:
首先计算等号右边的表达式 b + 2。假设 b 的值是已知的(比如 b = 3),则 b + 2 的结果是 5。然后,将这个结果 5 与 a 的当前值进行加法运算,即 a 的当前值(假设为 10)加上 5,得到 15。最后,将计算结果 15 赋值回 a,更新 a 的值为 15。
这种运算方式简化了代码,使得在需要更新变量值时不必显式地写出变量名两次,但是不好看。例如,a = a + b + 2; 可以更简洁地写为 a += b + 2;。
6 sizeof 运算符
很多同学会认为 sizeof 是一个函数,这种理解是错误的,实际sizeof是一个运算符。
sizeof 在 C 和 C++ 中是一个编译时运算符,用于计算对象或类型在内存中所占用的字节数。由于它是编译时求值的,因此其参数可以是任何数据类型、数组、结构体、联合体或者指针,但不可以是函数类型或者未定义的类型(如未声明的变量类型)。
#include <stdio.h>int main() {int a = 10;double b = 3.14;char c = 'A';char str[] = "Hello, World!";// 使用 sizeof 运算符获取变量和数组的内存大小printf("Size of int: %zu bytes\n", sizeof(a)); // 4bytesprintf("Size of double: %zu bytes\n", sizeof(b)); // 8bytesprintf("Size of char: %zu bytes\n", sizeof(c)); // 1bytesprintf("Size of string (including null terminator): %zu bytes\n", sizeof(str)); // 14bytes// 也可以用于类型printf("Size of int type: %zu bytes\n", sizeof(int)); // 4bytesprintf("Size of double type: %zu bytes\n", sizeof(double)); // 8bytesprintf("Size of char type: %zu bytes\n", sizeof(char)); // 1bytes// 注意:sizeof 对于指针总是返回指针自身的大小,与指针指向的类型无关// 在大多数现代系统上,指针的大小是固定的(例如,在 32 位系统上通常是 4 字节,在 64 位系统上通常是 8 字节)int *ptr = &a;printf("Size of pointer to int: %zu bytes\n", sizeof(ptr)); // 8bytesreturn 0;
}
sizeof 对于指针总是返回指针自身的大小,与指针指向的类型无关。
在大多数现代系统上,指针的大小是固定的(例如,在 32 位系统上通常是 4 字节,在 64 位系统上通常是 8 字节)
7 运算符优先级表
同一优先级的运算符,运算次序由结合方向所决定。
简单记就是: ! > 算术运算符 > 关系运算符 > && > || > 赋值运算符
8 小节判断题
1、算术运算符包含 “+”、“-”、“*”、“/” 和 “%”,乘、除、取余运算符的优先级高于加、减运算符?
A. 正确 B. 错误
答案:A
解释:正确的,这个需要记住。
2、“%” 取余(也称为取模运算)可以用于浮点数?
A. 正确 B. 错误
答案:B
解释:取模运算只能用于整型数,不能用于浮点数。
3、关系运算符的优先级高于算术运算符?
A. 正确 B. 错误
答案:B
解释:关系运算符优先级是低于算术运算符的,记住这个对于初试大题编写是必须的。
4、代码编写 “int a = 5; if (3 < a < 10)” 这种编写方式是否正确?
A. 正确 B. 错误
答案:B
解释:在程序中是错误的。首先,无论 a 是大于 3 还是小于 3 ,对于 “3 < a” 这个表达式只有 1 或 0 两种结果。由于 1 和 0 都是小于 10 的,所以无论 a 的值为多少,这个表达式的值始终为真,因此在判断变量 a 是否大于 3 且同时小于 10 时,要写成 “a > 3 && a < 10”,这才是正确的写法。
5、C 语言中逻辑表达式的值是真或假,真的值是 1,假是 0 ?
A. 正确 B. 错误
答案:A
解释:正确的,这个需要记住。
6、逻辑非的优先级高于算术运算符,逻辑与和逻辑或的优先级低于关系运算符?
A. 正确 B. 错误
答案:A
解释:逻辑非是单目运算符,优先级高于算术运算符,逻辑与和逻辑或的优先级低于关系运算符。
7、int a = 1, b = 0; b + 2 = a; 这个运算可以正常编译通过 ?
A. 正确 B. 错误
答案:B
解释:b + 2 = a 无法编译通过,因为 b + 2 是表达式,不是变量,没有对应的内存空间,无法作为左值。
8、sizeof(char) 的值是 1?
A. 正确 B. 错误
答案:A
解释:sizeof 用来计算不同类型空间大小,char 类型占用一个字节空间大小。
9、int j = 1; j || printf("hello world\n"); 代码运行后,我们会看到“hello world”的打印输出?
A. 正确 B. 错误
答案:B
解释:由于 j 等于 1,为真。逻辑或的短路运算,当前一个表达式为真时,后面的表达式不会得到运行,因此 printf("hello world\n"); 不会得到运行,所以看不到其打印输出。
9 OJ 练习
9.1 课时3作业1
#include <stdio.h>int main() {int year;scanf("%d",&year);if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0){printf("yes\n");}else{printf("no\n");}return 0;
}
9.2 课时3作业2
#include <stdio.h>int main() {int i;char j;float k;scanf("%d %c%f",&i,&j,&k); // 注意%c前面的空格printf("%.2f\n",i+j+k); //保留两位小数return 0;
}