文章目录
- 输入输出浅谈
- 1、cout 进行 C++ 输出
- 1.1 控制符 `endl`
- 1.2 使用 `cout` 进行拼接
- 2、cin 获取键盘输入
- 常用运算符分类
- 算术运算符
- 1、除法(`/`)
- 2、求模(`%`)
- 3、自增和自减
- 赋值运算符
- 关系运算符
- 逻辑运算符
- 三元运算符
- 位运算符
- 1、原码、反码、补码
- 1.1 为什么采用补码
- 1.2 补码的原理
- 2、位运算符
- 运算符的优先级和结合性
- 类型转换
- 1、自动类型转换
- 1.1 初始化和赋值进行的转换
- 1.2 表达式中的转换
- 1.3 传递参数时的转换
- 2、强制类型转换
- 3、C++11 中的 `auto` 声明
输入输出浅谈
1、cout 进行 C++ 输出
看一个例子:
cout << "welcome to c++!";
这里的运算符 <<
将字符串 "welcome to c++!"
发送给 cout
。cout
是一个预定义的对象,字符串会通过它,被插入到输出流中。
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;
从运算符 >>
可以看出信息的流向,从 cin
到 name
。cin
也是一个智能对象,它能够将从键盘输入的一系列字符转换为接收变量能接受的形式,是一个不需要像 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
为整数(正负都可), f
和 x
有相同的符号,而且 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 = 1 | y = 1; x = y | |
+= | x += 3 | x = x + 3 |
-= | x -= 3 | x = x - 3 |
*= | x *= 3 | x = x * 3 |
/= | x /= 3 | x = x / 3 |
%= | x %= 3 | x = x % 3 |
赋值运算符没有太多好说的,只需要注意两点:
-
赋值运算符左边必须是一个变量;【常量不能修改,当然也就不能赋值】
-
组合赋值运算符(也就是
+=
这种),必须把右边看成一个整体;例如:
x += y + 3
==>x = x + (y + 3)
。
关系运算符
比较两者关系,返回值是 bool
值。
运算符 | 含义 |
---|---|
< | 小于 |
<= | 小于等于 |
== | 等于 |
> | 大于 |
>= | 大于等于 |
!= | 不等于 |
逻辑运算符
通常情况下,例如关系运算,可能不止一个条件。当我们需要判断多个条件的时候,必须有一种符号将它们连接起来一起判断。为了满足这种需要,C++ 提供了逻辑运算来组合或修改已有的表达式,它们的返回值也是 bool
类型。
运算符 | 含义 | 示例 | 结果 |
---|---|---|---|
&& | 逻辑与 | true && true | true |
true && false | false | ||
false && true | false | ||
false && false | false | ||
|| | 逻辑或 | true || true | true |
true || false | true | ||
false || true | true | ||
false || false | false | ||
! | 逻辑非 | !true | false |
!false | true |
注意:对于逻辑运算符来说,当一侧的结果就能决定最终的结果的时候,另一侧的表达式就不会计算。例如逻辑或,当左边的表达式为 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
。这里的 -3
和 253
关于模 256
同余(也就是余数相等)。这里的求余使用的是数学中的方法:
a
m
o
d
b
=
a
−
⌊
a
b
⌋
∗
b
a \mod b = a - \lfloor\frac{a}{b} \rfloor * b
amodb=a−⌊ba⌋∗b。简单地说,就是一正一负两个数的绝对值之后为模,它们同余。
若是用二进制观察就可以知道,-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 初始化和赋值进行的转换
- 值赋给取值范围更大的类型;
- 值赋给取值范围更小的类型;
- 0 赋值给 bool 变量时,将被转换为 false,非零值转换为 true;
潜在的数值转换问题:
转换 | 潜在的问题 |
---|---|
将较大的浮点类型转换为较小的浮点类型,如将 double 转换为 float | 精度(有效数位)降低,值可能超出目标类型的取值范围,在这种情况下,结果将是不确定的 |
将浮点型转换为整型 | 小数部分丢失,原来的值可能超出目标类型的取值范围,在这种情况下,结果将是不确定的 |
将较大的整型转换为较小的整型,如将 long 转换为 short | 原来的值可能超出目标类型的取值范围,通常只复制右边的字节 |
1.2 表达式中的转换
当同一个表达式中包含多种不同的算术类型时,C++ 将执行两种自动转换:
- 首先,一些类型在出现时便会自动转换;
- 在计算表达式时,C++ 将 bool、char、unsigned char、signed char、short 值转换为 int,其中 bool 值中的 true 转换为1,false 转换为 0,这些被称为整型提升(integral promotion);
- 还有,如果 short 比 int 短,则 unsigned short 转换为 int。如果两种类型长度相同,则 unsigned short 转换为 unsigned int,从而确保在对 unsigned short 提升时不会丢失数据;
- 同时,wchar_t 被提升为下列类型中第一个宽度足够存储 wchar_t 取值范围的类型:int、unsigned int、long、unsigned long;
- 其次,有些类型在与其他类型同时出现在表达式中时将被转换;
- 当运算涉及两种类型时,较小的类型将被转换为较大的类型。(如 int 类型和 float 相加时,将 int 转换为 float);
- 编译器通过校验表来确定在表达式中执行的转换,C++11 校验表顺序如下:
- 如果有一个操作数的类型是 long double,则将另一个操作数转换为 long double;
- 否则,如果有一个操作数的类型是 double,则将另一个操作数转换为 double;
- 否则,如果有一个操作数的类型是 float,则将另一个操作数转换为 float;
- 否则,说明操作数都是整型,因此执行整型提升。
- 这种情况下,如果两个操作数都是有符号或无符号数,且其中一个操作数的级别比另一个低,则转换为级别高的类型;
- 如果一个有符号,另一个无符号,且无符号操作数的级别比有符号操作数高,则将有符号操作数转换为无符号操作数所属的类型;
- 否则,如果有符号类型可表示无符号类型的所有可能取值,则将无符号转换为有符号所属的类型;
- 否则,将两个操作数都转换为有符号类型的无符号版本。
1.3 传递参数时的转换
传递参数时的类型转换通常由 C++ 函数原型控制,然而,也可以取消原型对参数传递的控制。在这种情况下,C++ 将对 char 和 short 类型(signed 和 unsigned)应用整型提升。另外,在将参数传递给取消原型对参数传递控制的函数时,C++ 将 float 参数提升为 double。
2、强制类型转换
C++ 允许通过强制类型转换机制显式进行类型转换。格式为:(typename)value
或 typename(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();