C、C++ 运算符优先级及常用数制转换详解
1. C 和 C++ 运算符优先级
在 C 和 C++ 编程中,运算符优先级至关重要,它决定了表达式中运算符的执行顺序。运算符优先级从高到低排列,下面为你详细介绍。
1.1 C 运算符优先级
| 运算符 | 类型 | 结合性 |
|---|---|---|
()
、
[]
、
.
、
->
、
++
(后缀)、
--
(后缀)
| 括号(函数调用运算符)、数组下标、通过对象选择成员、通过指针选择成员、一元后缀自增、一元后缀自减 | 从左到右 |
++
(前缀)、
--
(前缀)、
+
(一元正)、
-
(一元负)、
!
、
~
、
(type)
、
*
、
&
、
sizeof
| 一元前缀自增、一元前缀自减、一元正、一元负、一元逻辑非、一元按位取反、C 风格一元类型转换、解引用、取地址、确定字节大小 | 从右到左 |
*
、
/
、
%
| 乘法、除法、取模 | 从左到右 |
+
、
-
| 加法、减法 | 从左到右 |
<<
、
>>
| 按位左移、按位右移 | 从左到右 |
<
、
<=
、
>
、
>=
| 关系小于、关系小于等于、关系大于、关系大于等于 | 从左到右 |
==
、
!=
| 关系等于、关系不等于 | 从左到右 |
&
| 按位与 | 从左到右 |
^
| 按位异或 | 从左到右 |
|
| 按位或 | 从左到右 |
&&
| 逻辑与 | 从左到右 |
||
| 逻辑或 | 从左到右 |
?:
| 三元条件运算符 | 从右到左 |
=
、
+=
、
-=
、
*=
、
/=
、
%=
、
&=
、
^=
、
|=
、
<<=
、
>>=
| 赋值、加法赋值、减法赋值、乘法赋值、除法赋值、取模赋值、按位与赋值、按位异或赋值、按位或赋值、按位左移赋值、按位右移赋值 | 从右到左 |
,
| 逗号运算符 | 从左到右 |
下面是一个简单的 C 代码示例,展示运算符优先级的影响:
#include <stdio.h>
int main() {
int a = 3 + 4 * 2; // 先计算乘法,再计算加法
printf("a 的值为: %d\n", a);
return 0;
}
1.2 C++ 运算符优先级
C++ 运算符优先级在 C 的基础上有所扩展,新增了一些运算符。
| 运算符 | 类型 | 结合性 |
| — | — | — |
|
::
(二元作用域解析)、
::
(一元作用域解析) | 二元作用域解析、一元作用域解析 | 从左到右 |
|
()
、
[]
、
.
、
->
、
++
(后缀)、
--
(后缀)、
typeid
、
dynamic_cast<type>
、
static_cast<type>
、
reinterpret_cast<type>
、
const_cast<type>
| 括号(函数调用运算符)、数组下标、通过对象选择成员、通过指针选择成员、一元后缀自增、一元后缀自减、运行时类型信息、运行时类型检查转换、编译时类型检查转换、非标准转换的强制类型转换、去除常量性的强制类型转换 | 从左到右 |
|
++
(前缀)、
--
(前缀)、
+
(一元正)、
-
(一元负)、
!
、
~
、
(type)
、
sizeof
、
&
、
*
、
new
、
new[]
、
delete
、
delete[]
| 一元前缀自增、一元前缀自减、一元正、一元负、一元逻辑非、一元按位取反、C 风格一元类型转换、确定字节大小、取地址、解引用、动态内存分配、动态数组分配、动态内存释放、动态数组释放 | 从右到左 |
|
.*
、
->*
| 通过对象的成员指针、通过指针的成员指针 | 从左到右 |
|
*
、
/
、
%
| 乘法、除法、取模 | 从左到右 |
|
+
、
-
| 加法、减法 | 从左到右 |
|
<<
、
>>
| 按位左移、按位右移 | 从左到右 |
|
<
、
<=
、
>
、
>=
| 关系小于、关系小于等于、关系大于、关系大于等于 | 从左到右 |
|
==
、
!=
| 关系等于、关系不等于 | 从左到右 |
|
&
| 按位与 | 从左到右 |
|
^
| 按位异或 | 从左到右 |
|
|
| 按位或 | 从左到右 |
|
&&
| 逻辑与 | 从左到右 |
|
||
| 逻辑或 | 从左到右 |
|
?:
| 三元条件运算符 | 从右到左 |
|
=
、
+=
、
-=
、
*=
、
/=
、
%=
、
&=
、
^=
、
|=
、
<<=
、
>>=
| 赋值、加法赋值、减法赋值、乘法赋值、除法赋值、取模赋值、按位与赋值、按位异或赋值、按位或赋值、按位左移赋值、按位右移赋值 | 从右到左 |
|
,
| 逗号运算符 | 从左到右 |
以下是一个 C++ 代码示例,展示 C++ 特有的运算符:
#include <iostream>
#include <typeinfo>
class Base { virtual void func() {} };
class Derived : public Base {};
int main() {
Base* basePtr = new Derived();
Derived* derivedPtr = dynamic_cast<Derived*>(basePtr);
if (derivedPtr) {
std::cout << "类型转换成功" << std::endl;
}
delete basePtr;
return 0;
}
2. ASCII 字符集
ASCII(美国信息交换标准代码)字符集是计算机中常用的字符编码标准,它使用 7 位二进制数表示 128 个字符,包括数字、字母、标点符号和控制字符等。
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 |
nul
|
soh
|
stx
|
etx
|
eot
|
enq
|
ack
|
bel
|
bs
|
ht
|
| 1 |
lf
|
vt
|
ff
|
cr
|
so
|
si
|
dle
|
dc1
|
dc2
|
dc3
|
| 2 |
dc4
|
nak
|
syn
|
etb
|
can
|
em
|
sub
|
esc
|
fs
|
gs
|
| 3 |
rs
|
us
|
sp
|
!
|
"
|
#
|
$
|
%
|
&
|
‘
|
| 4 |
(
|
)
|
*
|
+
|
,
|
-
|
.
|
/
|
0
|
1
|
| 5 |
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
:
|
;
|
| 6 |
<
|
=
|
>
|
?
|
@
|
A
|
B
|
C
|
D
|
E
|
| 7 |
F
|
G
|
H
|
I
|
J
|
K
|
L
|
M
|
N
|
O
|
| 8 |
P
|
Q
|
R
|
S
|
T
|
U
|
V
|
W
|
X
|
Y
|
| 9 |
Z
|
[
|
\
|
]
|
^
|
_
|
’
|
a
|
b
|
c
|
| 10 |
d
|
e
|
f
|
g
|
h
|
i
|
j
|
k
|
l
|
m
|
| 11 |
n
|
o
|
p
|
q
|
r
|
s
|
t
|
u
|
v
|
w
|
| 12 |
x
|
y
|
z
|
{
|
|
|
}
|
~
|
del
|
例如,字符 “F” 的 ASCII 码是 70,字符 “&” 的 ASCII 码是 38。在编程中,我们可以通过 ASCII 码来处理字符,以下是一个简单的 C 代码示例:
#include <stdio.h>
int main() {
char ch = 'F';
printf("字符 %c 的 ASCII 码是 %d\n", ch, ch);
return 0;
}
3. 数制系统
在编程中,常用的数制系统有二进制(Base 2)、八进制(Base 8)、十进制(Base 10)和十六进制(Base 16)。理解这些数制系统的基本概念和相互转换方法对于程序员来说非常重要。
3.1 基本概念
- 基数(Base) :数制系统中使用的数字个数。例如,二进制的基数是 2,使用数字 0 和 1;十进制的基数是 10,使用数字 0 - 9。
- 位值(Positional Value) :每个数位的权重,它是基数的幂。例如,在十进制数 937 中,7 的位值是 $10^0$,3 的位值是 $10^1$,9 的位值是 $10^2$。
- 符号值(Symbol Value) :每个数位上的数字。
不同数制系统的比较如下:
| 属性 | 二进制 | 八进制 | 十进制 | 十六进制 |
| — | — | — | — | — |
| 基数 | 2 | 8 | 10 | 16 |
| 最低数字 | 0 | 0 | 0 | 0 |
| 最高数字 | 1 | 7 | 9 | F |
各数制系统的数字表示如下:
| 二进制数字 | 八进制数字 | 十进制数字 | 十六进制数字 |
| — | — | — | — |
| 0 | 0 | 0 | 0 |
| 1 | 1 | 1 | 1 |
| | 2 | 2 | 2 |
| | 3 | 3 | 3 |
| | 4 | 4 | 4 |
| | 5 | 5 | 5 |
| | 6 | 6 | 6 |
| | 7 | 7 | 7 |
| | | 8 | 8 |
| | | 9 | 9 |
| | | | A(十进制值 10) |
| | | | B(十进制值 11) |
| | | | C(十进制值 12) |
| | | | D(十进制值 13) |
| | | | E(十进制值 14) |
| | | | F(十进制值 15) |
下面是各数制系统中位值的示例:
-
十进制
:以 937 为例,7 在个位,位值是 $10^0 = 1$;3 在十位,位值是 $10^1 = 10$;9 在百位,位值是 $10^2 = 100$。
-
二进制
:以 101 为例,最右边的 1 在个位,位值是 $2^0 = 1$;0 在二位,位值是 $2^1 = 2$;最左边的 1 在四位,位值是 $2^2 = 4$。所以 $101_{(2)} = 1\times2^2 + 0\times2^1 + 1\times2^0 = 4 + 0 + 1 = 5$。
-
八进制
:以 425 为例,5 在个位,位值是 $8^0 = 1$;2 在八位,位值是 $8^1 = 8$;4 在六十四位,位值是 $8^2 = 64$。
-
十六进制
:以 3DA 为例,A 在个位,位值是 $16^0 = 1$;D 在十六位,位值是 $16^1 = 16$;3 在二百五十六位,位值是 $16^2 = 256$。
以下是一个简单的 C 代码示例,展示如何计算二进制数对应的十进制值:
#include <stdio.h>
int main() {
int binary = 101;
int decimal = 0;
int power = 1;
while (binary > 0) {
int digit = binary % 10;
decimal += digit * power;
power *= 2;
binary /= 10;
}
printf("二进制数 101 对应的十进制数是 %d\n", decimal);
return 0;
}
3.2 二进制数的缩写
八进制和十六进制常用于缩写冗长的二进制表示。因为八进制的基数 8 和十六进制的基数 16 都是二进制基数 2 的幂,所以可以方便地进行转换。
例如,一个 12 位二进制数
100011010001
,转换为八进制时,将其每三位一组划分:
100
、
011
、
010
、
001
,对应的八进制数分别是 4、3、2、1,所以八进制表示为
4321
。转换为十六进制时,将其每四位一组划分:
1000
、
1101
、
0001
,对应的十六进制数分别是 8、D、1,所以十六进制表示为
8D1
。
graph LR
A[二进制 100011010001] --> B[分组: 100 011 010 001]
B --> C[八进制: 4321]
A --> D[分组: 1000 1101 0001]
D --> E[十六进制: 8D1]
在编程中,我们可以编写函数来实现这种转换,以下是一个简单的 Python 代码示例:
binary_num = '100011010001'
octal_num = oct(int(binary_num, 2))[2:]
hex_num = hex(int(binary_num, 2))[2:]
print(f"二进制 {binary_num} 对应的八进制是 {octal_num},十六进制是 {hex_num}")
3.3 八进制和十六进制转换为二进制
将八进制或十六进制数转换为二进制数,只需将每个八进制或十六进制数字转换为对应的二进制数字即可。
-
八进制数
653转换为二进制:6 对应的二进制是110,5 对应的二进制是101,3 对应的二进制是011,所以653对应的二进制是110101011。 -
十六进制数
FAD5转换为二进制:F 对应的二进制是1111,A 对应的二进制是1010,D 对应的二进制是1101,5 对应的二进制是0101,所以FAD5对应的二进制是1111101011010101。
以下是一个简单的 Python 代码示例,实现八进制和十六进制到二进制的转换:
octal_num = '653'
hex_num = 'FAD5'
binary_octal = bin(int(octal_num, 8))[2:]
binary_hex = bin(int(hex_num, 16))[2:]
print(f"八进制 {octal_num} 对应的二进制是 {binary_octal}")
print(f"十六进制 {hex_num} 对应的二进制是 {binary_hex}")
3.4 二进制、八进制、十六进制转换为十进制
将二进制、八进制或十六进制数转换为十进制数,可将每个数字的十进制等效值乘以其位值,然后求和。
-
二进制数
110101转换为十进制:$1\times2^5 + 1\times2^4 + 0\times2^3 + 1\times2^2 + 0\times2^1 + 1\times2^0 = 32 + 16 + 0 + 4 + 0 + 1 = 53$。 -
八进制数
7614转换为十进制:$7\times8^3 + 6\times8^2 + 1\times8^1 + 4\times8^0 = 3584 + 384 + 8 + 4 = 3980$。 -
十六进制数
AD3B转换为十进制:$A\times16^3 + D\times16^2 + 3\times16^1 + B\times16^0 = 40960 + 3328 + 48 + 11 = 44347$。
以下是一个简单的 Python 代码示例,实现二进制、八进制、十六进制到十进制的转换:
binary_num = '110101'
octal_num = '7614'
hex_num = 'AD3B'
decimal_binary = int(binary_num, 2)
decimal_octal = int(octal_num, 8)
decimal_hex = int(hex_num, 16)
print(f"二进制 {binary_num} 对应的十进制是 {decimal_binary}")
print(f"八进制 {octal_num} 对应的十进制是 {decimal_octal}")
print(f"十六进制 {hex_num} 对应的十进制是 {decimal_hex}")
3.5 十进制转换为二进制、八进制、十六进制
将十进制数转换为二进制、八进制或十六进制数,可通过不断除以目标基数并记录余数的方法实现。
以将十进制数 57 转换为二进制为例:
1. 写出二进制位值:64、32、16、8、4、2、1,因为 64 大于 57,所以舍去 64 这一列。
2. 从左到右处理:
- 32 能整除 57 一次,余数为 25,所以 32 列写 1。
- 16 能整除 25 一次,余数为 9,所以 16 列写 1。
- 8 能整除 9 一次,余数为 1,所以 8 列写 1。
- 4 和 2 不能整除 1,所以 4 和 2 列写 0。
- 1 能整除 1 一次,所以 1 列写 1。
最终得到二进制数
111001
。
同理,将十进制数 103 转换为八进制:
1. 写出八进制位值:512、64、8、1,舍去 512 这一列。
2. 从左到右处理:
- 64 能整除 103 一次,余数为 39,所以 64 列写 1。
- 8 能整除 39 四次,余数为 7,所以 8 列写 4。
- 1 能整除 7 七次,所以 1 列写 7。
最终得到八进制数
147
。
将十进制数 375 转换为十六进制:
1. 写出十六进制位值:4096、256、16、1,舍去 4096 这一列。
2. 从左到右处理:
- 256 能整除 375 一次,余数为 119,所以 256 列写 1。
- 16 能整除 119 七次,余数为 7,所以 16 列写 7。
- 1 能整除 7 七次,所以 1 列写 7。
最终得到十六进制数
177
。
以下是一个简单的 Python 代码示例,实现十进制到二进制、八进制、十六进制的转换:
decimal_num = 57
binary_num = bin(decimal_num)[2:]
octal_num = oct(decimal_num)[2:]
hex_num = hex(decimal_num)[2:]
print(f"十进制数 {decimal_num} 对应的二进制是 {binary_num},八进制是 {octal_num},十六进制是 {hex_num}")
通过以上介绍,你应该对 C 和 C++ 运算符优先级、ASCII 字符集以及常用数制系统有了更深入的理解。在实际编程中,合理运用这些知识可以提高代码的正确性和效率。
3.6 负二进制数:补码表示法
在计算机中,负数通常使用补码表示法来存储。补码表示法可以简化计算机的算术运算,特别是减法运算。
3.6.1 补码的基本原理
对于一个二进制数,求其补码的步骤如下:
1. 对原二进制数的每一位取反(0 变 1,1 变 0),得到反码。
2. 在反码的基础上加 1,得到补码。
例如,对于一个 8 位二进制数表示的十进制数 5,其原码为
00000101
。要求 -5 的补码:
1. 先求 5 的反码:
11111010
。
2. 再在反码上加 1,得到补码:
11111011
。
3.6.2 补码的运算
使用补码表示负数后,计算机可以使用相同的加法电路来进行加法和减法运算。例如,计算 5 - 3 可以转换为 5 + (-3)。
假设使用 8 位二进制表示:
- 5 的原码:
00000101
- 3 的原码:
00000011
- -3 的补码:
1. 3 的反码:
11111100
2. 加 1 得到补码:
11111101
进行加法运算:
00000101
+ 11111101
-----------
00000010
结果
00000010
对应的十进制数是 2,与 5 - 3 的结果一致。
以下是一个简单的 Python 代码示例,展示补码的计算:
def get_complement(num, bits):
if num >= 0:
return bin(num & (2**bits - 1))[2:].zfill(bits)
else:
return bin((abs(num) ^ (2**bits - 1)) + 1)[2:].zfill(bits)
num = -3
bits = 8
complement = get_complement(num, bits)
print(f"十进制数 {num} 的 {bits} 位补码是 {complement}")
4. 总结
本文详细介绍了 C 和 C++ 运算符优先级、ASCII 字符集以及常用数制系统的相关知识。
- 运算符优先级 :C 和 C++ 运算符优先级决定了表达式中运算符的执行顺序,不同的运算符具有不同的优先级和结合性。在编写复杂表达式时,需要注意运算符优先级,避免出现意外的结果。
- ASCII 字符集 :ASCII 字符集是计算机中常用的字符编码标准,使用 7 位二进制数表示 128 个字符。通过 ASCII 码可以方便地处理字符。
- 数制系统 :常用的数制系统包括二进制、八进制、十进制和十六进制。理解这些数制系统的基本概念(基数、位值、符号值)和相互转换方法(二进制与八进制、十六进制的缩写转换,二进制、八进制、十六进制与十进制的相互转换)对于程序员来说非常重要。同时,了解负二进制数的补码表示法可以帮助我们更好地理解计算机中负数的存储和运算。
在实际编程中,合理运用这些知识可以提高代码的正确性和效率。例如,在处理位运算时,需要清楚运算符优先级;在处理字符时,可以使用 ASCII 码进行比较和转换;在进行底层编程时,数制系统的转换和补码表示法是必不可少的知识。
以下是一个总结表格,对比不同数制系统的特点和转换方法:
| 数制系统 | 基数 | 数字范围 | 转换方法 |
| — | — | — | — |
| 二进制 | 2 | 0, 1 | 与八进制、十六进制可按位分组转换;与十进制通过位值相乘求和转换 |
| 八进制 | 8 | 0 - 7 | 与二进制按三位一组转换;与十进制通过位值相乘求和转换 |
| 十进制 | 10 | 0 - 9 | 与二进制、八进制、十六进制通过位值运算相互转换 |
| 十六进制 | 16 | 0 - 9, A - F | 与二进制按四位一组转换;与十进制通过位值相乘求和转换 |
graph LR
A[二进制] <--> B[八进制]
A <--> C[十进制]
A <--> D[十六进制]
B <--> C
B <--> D
C <--> D
希望通过本文的介绍,你能对这些知识有更深入的理解,并在编程实践中灵活运用。
超级会员免费看
9万+

被折叠的 条评论
为什么被折叠?



