龙空技术网

C++运算符

孙工精品 129

前言:

而今姐妹们对“c语言高精度计算参考代码”大致比较讲究,兄弟们都需要剖析一些“c语言高精度计算参考代码”的相关内容。那么小编同时在网络上搜集了一些对于“c语言高精度计算参考代码””的相关文章,希望看官们能喜欢,朋友们一起来了解一下吧!

1、表达式和运算符1.1 基本概念

在程序中,一个或多个运算对象的组合叫做“表达式”(expression),我们可以把它看成用来做计算的“式子”。对一个表达式进行计算,可以得到一个结果,有时也把它叫做表达式的值。

前面讲到的字面值常量和变量,就是最简单的表达式;表达式的结果就是字面值和变量的值。而多个字面值和变量,可以通过一些符号连接组合在一起,表示进行相应的计算,这就可以得到更加复杂的表达式,比如 a + 1。像“+”这些符号就被叫做“运算符”(operator)。

C++中定义的运算符,可以是像“+”这样连接两个对象,称为“二元运算符”;也可以只作用于一个对象,称为“一元运算符”。另外,还有一个比较特殊的运算符可以作用于三个对象,那就是三元运算符了。

1.2 运算优先级和结合律

如果在一个表达式中,使用多个运算符组合了多个运算对象,就构成了更加复杂的“复合表达式”,比如 a + 1 - b。对于复合表达式,很显然我们应该分步来做计算;而计算顺序,是由所谓的“优先级”和“结合律”确定的。

简单来说,就是对不同的运算符赋予不同的“优先级”,我们会优先执行高优先级的运算、再执行低优先级的运算。如果优先级相同,就按照“结合律”来决定执行顺序。这其实跟数学的综合算式是一样的,我们会定义乘除的优先级要高于加减,平级运算从左往右,所以对于算式:1 + 2 – 3 × 4,我们会先计算高优先级的 3×4,然后按照从左到右的结合顺序计算1+2,最后做减法。另外,如果有括号,那就要先把括起来的部分当成一个整体先做计算,然后再考虑括号外的结合顺序,这一点在C++表达式中同样适用。

2、算术运算

最简单的运算符,就是表示算术计算的加减乘除,这一类被称为“算术运算符”。C++支持的算术运算符如下:

这里需要注意的是,同一个运算符,在不同的场合可能表达不同的含义。比如“-”,可以是“减号”也可以是“负号”:如果直接放在一个表达式前面,就是对表达式的结果取负数,这是一元运算符;如果连接两个表达式,就是两者结果相减,是二元运算符。

算术运算符相关规则如下:

一元运算符(正负号)优先级最高;接下来是乘、除和取余;最后是加减;算术运算符满足左结合律,也就是说相同优先级的运算符,将从左到右按顺序进行组合;算术运算符可以用来处理任意算术类型的数据对象;不同类型的数据对象进行计算时,较小的整数类型会被“提升”为较大的类型,最终转换成同一类型进行计算;对于除法运算“/”,执行计算的结果跟操作数的类型有关。如果它的两个操作数(也就是被除数和除数)都是整数,那么得到的结果也只能是整数,小数部分会直接舍弃,这叫“整数除法”;当至少有一个操作数是浮点数时,结果就会是浮点数,保留小数部分;对于取余运算“%”(或者叫“取模”),两个操作数必须是整数类型;3、赋值

将一个表达式的结果,传递给某个数据对象保存起来,这个过程叫做“赋值”。

3.1 赋值运算符

在C++中,用等号“=”表示一个赋值操作,这里的“=”就是赋值运算符。需要注意的是,赋值运算符的左边,必须是一个可修改的数据对象,比如假设我们已经定义了一个int类型的变量a,那么a = 1;这样赋值是对的,但1 = a;就是错误的。因为a是一个变量,可以赋值;而 1只是一个字面值常量,不能再对它赋值

    // 2. 赋值运算符    a = 1;    //1 = a;    // 错误,1不是可修改的左值    a = b + 5;    //b + 5 = a;   // 错误    const int c = 10;    //c = b;    // 错误

所以像变量a这样的可以赋值的运算对象,在C++中被叫做“左值”(lvalue);对应的,放在赋值语句右面的表达式就是“右值”(rvalue)。赋值运算有以下一些规则:

赋值运算的结果,就是它左侧的运算对象;结果的类型就是左侧运算对象的类型;如果赋值运算符两侧对象类型不同,就把右侧的对象转换成左侧对象的类型;C++ 11新标准提供了一种新的语法:用花括号{}括起来的数值列表,可以作为赋值右侧对象。这样就可以非常方便地对一个数组赋值了;赋值运算满足右结合律。也就是说可以在一条语句中连续赋值,结合顺序是从右到左;赋值运算符优先级较低,一般都会先执行其它运算符,最后做赋值;

    a = { 10 };    int arr[] = {1,2,3,4,5};    a = b = 5;
3.2 复合赋值运算符

实际应用中,我们经常需要把一次计算的结果,再赋值给参与运算的某一个变量。最简单的例子就是多个数求和,比如我们要计算a、b、c的和,那么可以专门定义一个变量sum,用来保存求和结果:

// 复合赋值运算符int sum = a;//sum = sum + b;//sum = sum + c;sum += b;sum += c;cout << "a + b + c = " << sum << endl;

要注意赋值运算符“=”完全不是数学上“等于”的意思,所以上面的赋值语句sum = sum + b; 说的是“计算sum + b的结果,然后把它再赋值给sum”。

为了更加简洁,C++提供了一类特殊的赋值运算符,可以把要执行的算术运算“+”跟赋值“=”结合在一起,用一个运算符“+=”来表示;这就是“复合赋值运算符”。

复合赋值一般结合的是算术运算符或者位运算符。每种运算符都有对应的组合形式:

3.3 递增递减运算符

C++为数据对象的“加一”“减一”操作,提供了更加简洁的表达方式,这就是递增和递减运算符(也叫“自增”“自减”运算符)。“递增”用两个加号“++”表示,表示“对象值加一,再赋值给原对象”;“递减”则用两个减号“--”表示。

// 递增递减运算符cout << "++a = " << ++a << endl;int i = 0, j;j = ++i;//j = i++;cout << "i = " << i << endl;cout << "j = " << j << endl;

递增递减运算符各自有两种形式:“前置”和“后置”,也就是说写成“++a”和“a++”都是可以的。它们都表示“a = a + 1”,区别在于表达式返回的结果不同:

前置时,对象先加1,再将更新之后的对象值作为结果返回;

后置时,对象先将原始值作为结果返回,再加1;

这要特别注意:如果我们单独使用递增递减运算符,那前置后置效果都一样;但如果运算结果还要进一步做计算,两者就有明显不同了。

    int i = 0, j;    j = ++i;    // i = 1,j = 1    j = i--;        // i = 0, j = 1

在实际应用中,一般都是希望用改变之后的对象值;所以为了避免混淆,我们通常会统一使用前置的写法。

4、关系和逻辑运算

在程序中,不可缺少的一类运算就是逻辑和关系运算,因为我们往往需要定义“在某种条件发生时,执行某种操作”。判断条件是否发生,这就是一个典型的逻辑判断;得到的结果或者为“真”(true),或者为“假”。很显然,这类运算的结果应该是布尔类型。

4.1、关系运算符

最简单的一种条件,就是判断两个算术对象的大小关系,对应的运算符称为“关系运算符”。包括:大于“>”、小于“<”、等于“==”、不等于“!=”、大于等于“>=”、小于等于“<=”。

这里要注意区分的是,在C++语法中一个等号“=”表示的是赋值,两个等号“==”才是真正的“等于”。

关系运算符的相关规则:

算术运算符的优先级高于关系运算符,而如果加上括号就可以调整计算顺序;关系运算符的返回值为布尔类型,如果参与算术计算,true的值为1,false的值为0;4.2 逻辑运算符

一个关系运算符的结果是一个布尔类型(ture或者false),就可以表示一个条件的判断;如果需要多个条件的叠加,就可以用逻辑“与或非”将这些布尔类型组合起来。这样的运算符叫做“逻辑运算符”。

逻辑非(!):一元运算符,将运算对象的值取反后返回,真值反转;逻辑与(&&):二元运算符,两个运算对象都为true时结果为true,否则结果为false;逻辑或(||):二元运算符,两个运算对象只要有一个为true结果就为true,都为false则结果为false;

    1 < 2 && 3 >= 5;    // false    1 < 2 || 3 >= 5;    // true    !(1 < 2 || 3 >= 5);    // false

我们可以把逻辑运算符和关系运算符的用法、优先级和结合律总结如下(从上到下优先级递减):

这里需要注意的规则有:

如果将一个算术类型的对象作为逻辑运算符的操作数,那么值为0表示false,非0值表示true;逻辑与和逻辑或有两个运算对象,在计算时都是先求左侧对象的值,再求右侧对象的值;如果左侧对象的值已经能决定最终结果,那么右侧就不会执行计算:这种策略叫做“短路求值”;4.3 条件运算符

C++还从C语言继承了一个特殊的运算符,叫做“条件运算符”。它由“?”和“:”两个符号组成,需要三个运算表达式,形式如下:

条件判断表达式 ? 表达式1 : 表达式2

它的含义是:计算条件判断表达式的值,如果为true就执行表达式1,返回求值结果;如果为false则跳过表达式1,执行表达式2,返回求值结果。这也是C++中唯一的一个三元运算符。

int i = 0;cout << ((1 < 2 && ++i) ? "true" : "false") << endl;
条件运算符的优先级比较低,所以输出的时候需要加上括号条件运算符满足右结合律

事实上,条件运算符等同于流程控制中的分支语句if...else...,只用一条语句就可以实现按条件分支处理,这就让代码更加简洁。

5、位运算符

之前介绍的所有运算符,主要都是针对算术类型的数据对象进行操作的;所有的算术类型,占用的空间都是以字节(byte,8位)作为单位来衡量的。在C++中,还有一类非常底层的运算符,可以直接操作到具体的每一位(bit)数据,这就是“位运算符”。

位运算符可以分为两大类:移位运算符,和位逻辑运算符。下面列出了所有位运算符的优先级和用法。

5.1、移位运算符

算术类型的数据对象,都可以看做是一组“位”的集合。那么利用“移位运算符”,就可以让运算对象的所有位,整体移动指定的位数。

移位运算符有两种:左移运算符“<<”和右移运算符“>>”。这个符号我们并不陌生,之前做输入输出操作的时候用的就是它,不过那是标准IO库里定义的运算符重载版本。下面是移位运算符的一个具体案例:

较小的整数类型(char、short以及bool)会自动提升成int类型再做移位,得到的结果也是int类型左移运算符“<<”将操作数左移之后,在右侧补0;右移运算符“>>”将操作数右移之后,对于无符号数就在左侧补0;对于有符号数的操作则要看运行的机器环境,有可能补符号位,也有可能直接补0;由于有符号数右移结果不确定,一般只对无符号数执行位移操作;

    unsigned char bits = 0xb5;    // 181    cout << hex;    // 以十六进制显示    cout << "0xb5 左移2位:" << (bits << 2) << endl;    // 0x 0000 02d4    cout << "0xb5 左移8位:" << (bits << 8) << endl;    // 0x 0000 b500    cout << "0xb5 左移31位:" << (bits << 31) << endl;    // 0x 8000 0000    cout << "0xb5 右移3位:" << (bits >> 3) << endl;    // 0x 0000 0016    cout << dec;    cout << (200 << 3) << endl;    // 乘8操作    cout << (-100 >> 2) << endl;    // 除4操作,一般右移是补符号位
5.2 位逻辑运算符

计算机存储的每一个“位”(bit)都是二进制的,有0和1两种取值,这跟布尔类型的真值表达非常类似。于是自然可以想到,两个位上的“0”或“1”都可以执行类似逻辑运算的操作。

位逻辑运算符有:按位取反“~”,位与“&”,位或“|”和位异或“^”。

按位取反“~”:一元运算符,类似逻辑非。对每个位取反值,也就是把1置为0、0置为1;位与“&”:二元运算符,类似逻辑与。两个数对应位上都为1,结果对应位为1;否则结果对应位为0;位或“|”:二元运算符,类似逻辑或。两个数对应位上只要有1,结果对应位就为1;如果全为0则结果对应位为0;位异或“^”:两个数对应位相同,则结果对应位为0;不同则结果对应位为0;

下面是位逻辑运算符的一个具体案例:

    // 位逻辑运算    cout << (~5) << endl;    // ~ (0... 0000 0101) = 1... 1111 1010,  -6    cout << (5 & 12) << endl;   // 0101 & 1100 = 0100, 4    cout << (5 | 12) << endl;   // 0101 | 1100 = 1101, 13    cout << (5 ^ 12) << endl;    // 0101 & 1100 = 1001, 9
5、类型转换

在C++中,不同类型的数据对象,是可以放在一起做计算的。这就要求必须有一个机制,能让有关联的两种类型可以互相转换。在上一章已经介绍过变量赋值时的自动类型转换,接下来我们会对类型转换做更详细的展开。

5.1 隐式类型转换

大多数情况,C++编译器可以自动对类型进行转换,不需要我们干涉,这种方式叫做“隐式类型转换”。

隐式类型转换主要发生在算术类型之间,基本思路就是将长度较小的类型转换成较大的类型,这样可以避免丢失精度。隐式类型转换不仅可以在变量赋值时发生,也可以在运算表达式中出现。例如:

short s = 15.2 + 20;cout << " s = " << s << endl;    // s = 35cout << " 15.2 + 20 结果长度为:" << sizeof(15.2 + 20) << endl;// 8cout << " s 长度为:" << sizeof(s) << endl; // 2

对于这条赋值语句,右侧是两个字面值常量相加,而且类型不同:15.2是double类型,20是int类型。当它们相加时,会将int类型的20转换为double类型,然后执行double的加法操作,得到35.2。

这个结果用来初始化变量s,由于s是short类型,所以还会把double类型的结果35.2再去掉小数部分,转换成short类型的35。所以s最终的值为35。隐式类型转换的一般规则可以总结如下:

在大多数算术运算中,较小的整数类型(如bool、char、short)都会转换成int类型。这叫做“整数提升”;(而对于wchar_t等较大的扩展字符类型,则根据需要转换成int、unsigned int、long、unsigned long、long long、unsigned long long中能容纳它的最小类型)当表达式中有整型也有浮点型时,整数值会转换成相应的浮点类型;在条件判断语句中,其它整数类型会转换成布尔类型,即0为false、非0为true;初始化变量时,初始值转换成变量的类型;在赋值语句中,右侧对象的值会转换成左侧对象的类型;

此外,要尽量避免将较大类型的值赋给较小类型的变量,这样很容易出现精度丢失或者数据溢出。

s = 32767;cout << " s + 1 = " << s + 1 << endl;short s2 = s + 1;cout << " s2 = " << s2 << endl;

另外还要注意,如果希望判断一个整型变量a是否在某个范围(0, 100)内,不能直接写:0 < a < 100;

由于小于运算符“<”满足左结合律,要先计算0 < a,得到一个布尔类型的结果,再跟后面的100进行比较。此时布尔类型做整数提升,不管值是真(1)还是假(0),都会满足 < 100 的判断,因此最终结果一定是true。

要想得到正确的结果,需要将两次关系判断拆开,写成逻辑与的关系。

    a = -1;                  0 < a < 100;         // 不论a取什么值,总是true    0 < a && a < 100;    // false
5.2 、强制类型转换

除去自动进行的隐式类型转换,我们也可以显式地要求编译器对数据对象的类型进行更改。这种转换叫做“强制类型转换”(cast)。

比如对于除法运算,我们知道整数除法和浮点数除法是不同的。如果希望对一组整数求一个平均数,直接相加后除以个数是无法得到想要的结果的:

    // 求平均数    int total = 20, num = 6;    double avg = total / num;    cout << " avg = " << avg << endl;    // avg = 3

因为两个int类型的数相除,执行的是整数除法,得到3;再转换成double类型对avg做初始化,得到是3.0。如果想要更准确的结果,就必须将int类型强制转换成double,做浮点数除法。

C++中可以使用不同的方式进行强制类型转换。

(1)C语言风格

最经典的强转方式来自C语言,格式如下:

cout << " avg = " << (double) total / num << endl;  

把要强制转成的类型,用一个小括号括起来,放到要转换的对象值前面就可以了。

(2)C++函数调用风格

这种方式跟C语言的强转类似,只不过看起来更像是调用了一个函数:

// C++函数风格  cout << " avg = " << double (total) / num << endl;   

要转成的类型名就像是一个函数,调用的时候,后面小括号里是传递给它的参数。

(3)C++强制类型转换运算符

C++还引入了4个强制类型转换运算符,这种新的转换方式比前两种传统方式要求更为严格。通常在类型转换中用到的运算符是static_cast,用法如下:

// C++强转运算符cout << " avg = " << static_cast<double>(total) / num << endl; 

static_cast运算符后要跟一个尖括号,里面是要转换成的类型。

有了这些强转的方式,就可以解决之前求平均数的问题了:强制类型转换会干扰正常的类型检查,带来很多风险,所以通常要尽量避免使用强制类型转换。

标签: #c语言高精度计算参考代码