C++基础(4) - 运算符

本文介绍了C++中的基本输入输出操作,包括使用cout进行输出和cin获取键盘输入。详细讨论了算术运算符如除法、求模和自增自减,以及关系运算符、逻辑运算符和三元运算符的用法。此外,还阐述了位运算符的概念,原码、反码和补码的原理,并解释了类型转换中的自动转换和强制转换。最后,提到了C++11中的auto关键字用于自动类型推断。
摘要由CSDN通过智能技术生成

输入输出浅谈

 

1、cout 进行 C++ 输出

看一个例子:

cout << "welcome to c++!";

这里的运算符 << 将字符串 "welcome to c++!" 发送给 coutcout 是一个预定义的对象,字符串会通过它,被插入到输出流中。

1.1 控制符 endl

诸如 endl 等对于 cout 有特殊含义的特殊符号被称为控制符(manupulator)endl 也被定义在头文件 iostream 中,且位于名称空间 std 中。

endl 有什么用呢?从这个符号的名称可以略知一二,end line,结束一行,所以它其实就是一个换行符。因为 cout 输出语句不会默认换行,所以当需要换行的时候,一般会在后面加上 endl。它跟旧有的换行符 \n 的作用是一样的。

1.2 使用 cout 进行拼接

当需要输出多条语句的时候,可以直接使用运算符 << 来进行拼接输出。例如:

int num = 5;
cout << "you have " << num << " carrots." << endl;

C++ 通过 << 将多条语句拼接输出,而 cout 更是智能,它可以自动识别输出变量的类型,而不需要像 C 中的 printf 来指定输出变量的类型。

 

2、cin 获取键盘输入

看一个例子:

string name;
cout << "请输入你的姓名:"
cin >> name;

从运算符 >> 可以看出信息的流向,从 cinnamecin 也是一个智能对象,它能够将从键盘输入的一系列字符转换为接收变量能接受的形式,是一个不需要像 C 中的 scanf 一样需要指明类型的输入对象。

 

 

常用运算符分类

运算符类型作用
算术运算符用于处理算术运算
赋值运算符用于将值或者表达式的值赋值给变量
关系运算符用于值或者表达式的关系比较,返回 bool 类型的值
逻辑运算符用于测试真假值,返回 bool 类型的值
三元运算符用于调用数据时逐级筛选
位运算符用于处理数据的位运算
sizeof 运算符用于获取变量或者类型的字节数

 

 

算术运算符

运算符术语示例
+正号+5
-负号-5
+1+5
-1-5
*1*5
/1/5
%1%5
++自增++i; i++
自减–i; i–

运算符中前面几个很好理解,跟数学中的操作一样。这里需要进一步解释的,有:/%++--

 

1、除法(/

跟数学中的除法不同,/ 的行为取决于操作数的类型。如果两个操作数都是整数,则 C++ 将执行整数除法,即执行除法操作之后,只保留整数部分;如果操作数中有一个(或者两个)是浮点数,则小数部分会保留。

int a = 21;
int b = 10;
cout << a / b << endl;	// 2

int c = -21;
int b = 10;
cout << a / b << endl;	// -2

float e = 2.1;
double f = 1.3;
cout << e / f << endl;	// 1.61538
// 使用typeid查看数据类型需要引入头文件“typeinfo”
cout << typeid(e / f).name() << endl;	// d(如果出现f就表示float,如果出现d就表示double)

注意:如果两个浮点数相除,操作数都是 double 类型,则结果为 double 类型;操作数都是 float 类型,则结果为 float 类型;两个浮点型操作数的类型不一致,则结果为 double 类型。

 

2、求模(%

返回整数除法的余数。

其中可能碰到正负号或浮点数的求模运算,都大同小异。例如求 x % y,可以看成 x = n*y + f,要保证 n 为整数(正负都可), fx 有相同的符号,而且 f 的绝对值小于 y 的绝对值。

int a = -21;
int b = 10;
cout << a % b << endl;	// -1

注意:运算符 % 只支持整型的计算,如果想进行浮点数的求模运算,需要导入头文件 cmath(即 math.h),使用函数 fmod(x, y)

double a = 2.1;
double b = 1.3;
cout << fmod(a, b) << endl;	// 0.8

 

3、自增和自减

自增和自减都有一个特性。当运算符在变量之前的时候,说明不管接下来变量需要做什么操作,先自加1再说;若运算符在变量之后,则先使用当前值进行表达式计算,之后变量再自加1。

int a = 10;
int b = 20;
cout << "a++: " << a++ << ", ++b: " << ++b << endl;	// a++: 10, ++b: 21
cout << "a: " << a << ", b: " << b << endl;	// a: 11, b: 21

其实这种操作对变量的影响是一样的,但是影响时间不同。我先吃一碗粉再给钱,还是先给钱再吃一碗粉,对于我来说都是吃了一碗粉,唯一不同在于给钱的时机而已。如果这钱在我吃粉的时间里,能给我不停的赚钱,那我先给钱不就很亏吗?

除了简单数据类型之外,自增和自减还会应用到指针当中,也是一个经常考察的知识点,等介绍到指针时再来讨论。

 

 

赋值运算符

运算符示例等价
=x = 1
x = y
= 右边的数据赋值给左边
x = y = 1y = 1; x = y
+=x += 3x = x + 3
-=x -= 3x = x - 3
*=x *= 3x = x * 3
/=x /= 3x = x / 3
%=x %= 3x = x % 3

赋值运算符没有太多好说的,只需要注意两点:

  1. 赋值运算符左边必须是一个变量;【常量不能修改,当然也就不能赋值】

  2. 组合赋值运算符(也就是 += 这种),必须把右边看成一个整体;

    例如:x += y + 3 ==> x = x + (y + 3)

 

 

关系运算符

比较两者关系,返回值是 bool 值。

运算符含义
<小于
<=小于等于
==等于
>大于
>=大于等于
!=不等于

 

 

逻辑运算符

通常情况下,例如关系运算,可能不止一个条件。当我们需要判断多个条件的时候,必须有一种符号将它们连接起来一起判断。为了满足这种需要,C++ 提供了逻辑运算来组合或修改已有的表达式,它们的返回值也是 bool 类型。

运算符含义示例结果
&&逻辑与true && truetrue
true && falsefalse
false && truefalse
false && falsefalse
||逻辑或true || truetrue
true || falsetrue
false || truetrue
false || falsefalse
!逻辑非!truefalse
!falsetrue

注意:对于逻辑运算符来说,当一侧的结果就能决定最终的结果的时候,另一侧的表达式就不会计算。例如逻辑或,当左边的表达式为 true 的时候,右边的表达式就不会计算,而是直接返回 true;逻辑与的化,当左边的表达式结果为 false 的时候,右边的表达式也不会计算,而是直接返回 false

 

 

三元运算符

三元运算符又叫条件表达式或者三元表达式。格式为:关系表达式 ? 表达式1 : 表达式2

三元运算符通常被用来代替 if else 语句的运算符,它是 C++ 中唯一一个需要3个操作数的运算符。三元运算符先执行关系表达式,当结果为 true 时,则整个条件表达式的值为表达式1的值,否则为表达式2的值。

示例:int max = num1 > num2 ? num1 : num2;,如果 num1 大于 num2 ,则 max 的值为 num1,否则 max 的值为 num2

 

 

位运算符

先学习位运算符之前,首先得了解一下计算机中的原码、反码和补码。

 

1、原码、反码、补码

  • 原码:最高位存储符号(0表示正数,1表示负数),其他位存储数据的绝对值;

    如用8位表示一个数: [ + 1 ] 原 = 0000   0001 [+1]_{原} = 0000\ 0001 [+1]=0000 0001 [ − 1 ] 原 = 1000   0001 [-1]_{原} = 1000\ 0001 [1]=1000 0001

  • 反码: [ 正数 ] 反 = [ 正数 ] 原 [正数]_{反} = [正数]_{原} [正数]=[正数],而负数的反码则等于将原码除符号位外按位取反;

    如用8位表示一个数: [ + 1 ] 反 = [ + 1 ] 原 = 0000   0001 [+1]_{反} = [+1]_{原} = 0000\ 0001 [+1]=[+1]=0000 0001 [ − 1 ] 反 = 1111   1110 [-1]_{反} = 1111\ 1110 [1]=1111 1110

  • 补码: [ 正数 ] 补 = [ 正数 ] 原 [正数]_{补} = [正数]_{原} [正数]=[正数] [ 负数 ] 补 = [ 负数 ] 反 + 1 [负数]_{补} = [负数]_{反} + 1 [负数]=[负数]+1

    如用8位表示一个数: [ + 1 ] 补 = 0000   0001 [+1]_{补} = 0000\ 0001 [+1]=0000 0001 [ − 1 ] 补 = 1111   1111 [-1]_{补} = 1111\ 1111 [1]=1111 1111

注意:计算机系统中,数值一律用**补码**来表示和存储。

1.1 为什么采用补码

原因:希望采用加法器电路来实现减法运算!

例如 1 - 1 = 1 + (-1) = 0

  • 采用原码: 0000   0001 + 1000   0001 = 1000   0010 = − 2 0000\ 0001 + 1000\ 0001 = 1000\ 0010 = -2 0000 0001+1000 0001=1000 0010=2
  • 采用反码: 0000   0001 + 1111   1110 = 1111   1111 = 转为原码 1000   0000 = − 0 0000\ 0001 + 1111\ 1110 = 1111\ 1111 \stackrel{转为原码}{=} 1000\ 0000 = -0 0000 0001+1111 1110=1111 1111=转为原码1000 0000=0
  • 采用补码: 0000   0001 + 1111   1111 = 0000   0000 = 0 0000\ 0001 + 1111\ 1111 = 0000\ 0000 = 0 0000 0001+1111 1111=0000 0000=0

1.2 补码的原理

比如我们采用8位二进制表示一个数,那么表示的最大十进制数不超过256( 2 8 2^{8} 28),模也就是256。

看一个例子:252 = 255 + (-3) = (255 + 253) mod 256。这里的 -3253 关于模 256 同余(也就是余数相等)。这里的求余使用的是数学中的方法: a m o d    b = a − ⌊ a b ⌋ ∗ b a \mod b = a - \lfloor\frac{a}{b} \rfloor * b amodb=abab。简单地说,就是一正一负两个数的绝对值之后为模,它们同余。

若是用二进制观察就可以知道,-3 的补码 1111 1101 如果看成无符号数,就是 253!最后也是经过更加科学化的演变才有了如今的补码加法。

 

2、位运算符

运算符含义说明
逻辑按位运算符&按位与只要有一位为0则为0
|按位或只要有一位为1则为1
~按位非将操作数的每个位都取反
^按位异或两位相同返回0,不同返回1
移位运算符<<左移整体左移指定位数,空位补0,被移除的高位丢弃
>>右移整体右移指定位数,无符号数空位补0,有符号数空位用原来最左边的位数值来补,被移除的低位丢弃

例如:

  • 3(0000 0011) & 5(0000 0101) = 1(0000 0001)
  • 3(0000 0011) | 5(0000 0101) = 7(0000 0111)
  • -3(1111 1101) ^ 5(0000 0101) = -8(1111 1000);由于计算机中存储的是补码,所以这里 -3-8 括号后面的是他们的补码,结果中的补码 1111 1000 转换成原码 1000 1000 就是 -8
  • ~-5(1111 1011) = 4(0000 0100);显然,结果 4 就是将 -5 的补码逐位取反的结果;
  • -5(1111 1011) << 2 = -20(1110 1100)
  • -5(1111 1011) >> 2 = -2(1111 1110)

以上括号中的二进制都是补码形式,将它们转换成原码,对应的数值就是括号外的数值了。

 

 

运算符的优先级和结合性

  • 运算符的优先级决定了各个运算符执行的先后顺序,优先级高的运算符要先于优先级低的运算符进行运算,我们数学中学到的“先乘除后加减”就是一种优先级的定义。
  • 如果运算符的优先级相同,则 C++ 使用结合性规则来决定运算的顺序。从左到右的结合性(L-R)表示首先应用最左边的运算符,而从右到左的结合性(R-L)表示首先应用最右边的运算符。
  • 有一个操作数的运算符被称为一元运算符;
  • 有两个操作数的运算符被称为二元运算符;当然,我们还学过 C++ 中唯一一个三元运算符?:

运算符和结合性

 

 

类型转换

C++ 允许使用不同的数据类型,但是这些数据之间的运算设计到的硬件编译指令可能完全不同,为了处理这种混乱情况,C++ 提供了类型转换的方法。

C++ 转换方式:

  • 自动类型转换(隐式转换):遵循一定的规则,有编译系统自动完成;
  • 强制类型转换:把表达式的运算结果强制转换成所需的数据类型;

 

1、自动类型转换

  • 将一种算术类型的值赋值给另一个算术类型的变量时;
  • 表达式中包含不同的类型时;
  • 将参数传递给函数时;

1.1 初始化和赋值进行的转换

  1. 值赋给取值范围更大的类型;
  2. 值赋给取值范围更小的类型;
  3. 0 赋值给 bool 变量时,将被转换为 false,非零值转换为 true;

潜在的数值转换问题:

转换潜在的问题
将较大的浮点类型转换为较小的浮点类型,如将 double 转换为 float精度(有效数位)降低,值可能超出目标类型的取值范围,在这种情况下,结果将是不确定的
将浮点型转换为整型小数部分丢失,原来的值可能超出目标类型的取值范围,在这种情况下,结果将是不确定的
将较大的整型转换为较小的整型,如将 long 转换为 short原来的值可能超出目标类型的取值范围,通常只复制右边的字节

1.2 表达式中的转换

当同一个表达式中包含多种不同的算术类型时,C++ 将执行两种自动转换:

  1. 首先,一些类型在出现时便会自动转换;
    1. 在计算表达式时,C++ 将 bool、char、unsigned char、signed char、short 值转换为 int,其中 bool 值中的 true 转换为1,false 转换为 0,这些被称为整型提升(integral promotion)
    2. 还有,如果 short 比 int 短,则 unsigned short 转换为 int。如果两种类型长度相同,则 unsigned short 转换为 unsigned int,从而确保在对 unsigned short 提升时不会丢失数据;
    3. 同时,wchar_t 被提升为下列类型中第一个宽度足够存储 wchar_t 取值范围的类型:int、unsigned int、long、unsigned long;
  2. 其次,有些类型在与其他类型同时出现在表达式中时将被转换;
    1. 当运算涉及两种类型时,较小的类型将被转换为较大的类型。(如 int 类型和 float 相加时,将 int 转换为 float);
    2. 编译器通过校验表来确定在表达式中执行的转换,C++11 校验表顺序如下:
      1. 如果有一个操作数的类型是 long double,则将另一个操作数转换为 long double;
      2. 否则,如果有一个操作数的类型是 double,则将另一个操作数转换为 double;
      3. 否则,如果有一个操作数的类型是 float,则将另一个操作数转换为 float;
      4. 否则,说明操作数都是整型,因此执行整型提升。
      5. 这种情况下,如果两个操作数都是有符号或无符号数,且其中一个操作数的级别比另一个低,则转换为级别高的类型;
      6. 如果一个有符号,另一个无符号,且无符号操作数的级别比有符号操作数高,则将有符号操作数转换为无符号操作数所属的类型;
      7. 否则,如果有符号类型可表示无符号类型的所有可能取值,则将无符号转换为有符号所属的类型;
      8. 否则,将两个操作数都转换为有符号类型的无符号版本。

1.3 传递参数时的转换

传递参数时的类型转换通常由 C++ 函数原型控制,然而,也可以取消原型对参数传递的控制。在这种情况下,C++ 将对 char 和 short 类型(signed 和 unsigned)应用整型提升。另外,在将参数传递给取消原型对参数传递控制的函数时,C++ 将 float 参数提升为 double。

 

2、强制类型转换

C++ 允许通过强制类型转换机制显式进行类型转换。格式为:(typename)valuetypename(value)

强制类型转换不会修改转换的变量本身,而是创建一个新的、指定类型的值。

优先级问题:

  • (int)a + b:将 a 转为 int 类型之后与 b 相加;
  • (int)(a + b):将 a 和 b 的和转为 int 类型;

C++ 还引入了 4 个强制类型转换运算符,对它们的使用要求更为严格:

  • static_cast<typename>(value):可用于将值从一个数值类型转换为另一种数值类型;
  • const_cast<typename>(value):用于执行只有一种用途的类型转换,即改变值为 const 或 volatile;
  • dynamic_cast<typename>(value):在类层次结构中进行向上转换,而不允许其他转换;
  • reinterpret_cast<typename>(value):用于天生危险的类型转换;

以上仅是介绍了一下,因为涉及到许多没有介绍的内容,后面再进行详细介绍。

 

3、C++11 中的 auto 声明

auto 让编译器能够根据初始值的类型推断变量的类型。

处理复杂类型时,自动类型推断的优势才能显现出来,所以 auto 一般常用在这种情况:

std::vector<double> scores;
auto pv = scores.begin();
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值