☰
第二章 数据类型和运算符
2026/10/22 17:39:22 网站建设 项目流程

第二章 数据类型和运算符

┌────────────────────────────────────────────────────────────────┐ │ 这一章要讲的事,一张图看完 │ ├────────────────────────────────────────────────────────────────┤ │ 第二章 数据类型和运算符 │ │ │ │ │ ├── 1. C++ 基本数据类型 整型浮点字符布尔,各占多大地方 │ │ ├── 2. 常量和变量 认领内存的规矩,命名和初始化 │ │ ├── 3. 运算符 加减乘除到移位,外加优先级 │ │ ├── 4. 数据类型转换 隐式自动转,显式手动转,坑最多 │ │ └── 5. 小结 一张表收口,加练习和下一章预告 │ └────────────────────────────────────────────────────────────────┘

老规矩,例子都自己敲一遍。这一章尤其别偷懒,类型和运算符的细节,看的时候都懂,一写就错,不敲一遍发现不了。


一、C++ 基本数据类型

┌──────────────────────────────────────────────────┐ │ C++ 类型的家谱 │ ├──────────────────────────────────────────────────┤ │ 一套类型一套脾气,各管各的 │ │ │ │ 整型 │ │ ├─ short 2 字节,小整数 │ │ ├─ int 4 字节,日常主力 │ │ ├─ long 4 或 8 字节,看平台 │ │ └─ long long 8 字节,大数专用 │ │ 每家整型都有带符号和不带符号两版 │ │ 不带符号的只放 0 和正数,范围翻倍 │ │ │ │ 浮点型 float、double、long double,管小数 │ │ 字符型 char,一个字节,存的是编号 │ │ 布尔型 bool,一个字节,只放 true 和 false │ └──────────────────────────────────────────────────┘

1.1 类型是内存的图纸

内存是一条超长的字节带,一个字节 8 位,每个字节有自己的地址。同样的字节序列,按不同类型解释,读出来的东西完全不同。类型就是图纸,它告诉编译器这几字节按什么规矩摆、怎么读。

比如 0x01020304 这四字节,当成四个编号看是四个小数字,当成一个 int 看就是 16909060。声明类型,就是提前把图纸交给编译器,后面怎么摆怎么算全按它来。

┌────────────────────────────────────────────────────────┐ │ 一个 int 的四字节怎么摆 │ ├────────────────────────────────────────────────────────┤ │ 同一个数,字节顺序两样(0x01020304,就是 16909060) │ │ │ │ 小端,低字节放低地址 大端,高字节放低地址 │ │ │ │ 地址 0 04 地址 0 01 │ │ 地址 1 03 地址 1 02 │ │ 地址 2 02 地址 2 03 │ │ 地址 3 01 地址 3 04 │ │ │ │ x86 和主流 ARM 用这套 网络传输和部分老平台 │ │ │ │ 两种摆法读出来都是同一个数,自己电脑上不用操心 │ │ 跨机器传二进制才分大小端,那是网络编程的事 │ └────────────────────────────────────────────────────────┘

int 的四字节怎么摆有大小端两种规矩。普通开发者一辈子感知不到这个差别,除非去写网络协议或者跟别的机器交换二进制文件,所以这页图看过就算了,不用记。

1.2 整型一家

整型管整数,按能放多大的数分四档,每一档都有带符号和不带符号两个版本。带符号的能放负数,是默认版本;不带符号的只放 0 和正数,范围翻倍。

类型字节能放的范围
short2-32768 到 32767
unsigned short20 到 65535
int4-2147483648 到 2147483647
unsigned int40 到 4294967295
long4 或 8看平台,Windows 上 4,Linux 上 8
long long8-9223372036854775808 到 9223372036854775807
unsigned long long80 到 18446744073709551615

日常用 int 就够。数大的场合,销量、库存、金额乘个几百倍就可能过 21 亿,上 long long。计数、编号、位运算这种不会出现负数的场景,用 unsigned 就对了。

有一条要注意,C++ 标准只规定了每种类型的最小范围,没规定精确字节数。long 在 Windows 上是 4 字节,在 Linux 上是 8 字节,同一份代码两边行为不一致。要精确控制宽度,后面章节有固定宽度类型,现在先知道这个坑。

1.3 浮点型

浮点型管小数,三兄弟按精度排。

类型字节有效数字大致范围
float4约 7 位1.2e-38 到 3.4e38
double8约 15 到 16 位2.2e-308 到 1.8e308
long double看平台不低于 double看平台

写代码时 3.14 默认是 double,要 float 得加后缀,3.14f。多数场合用 double,精度够,速度也不慢。

浮点数有个著名陷阱。0.1 这个数,二进制里存不住精确值,存进去的是近似值,两个近似值加起来,误差就露出来了。

0.1 + 0.2 的结果是 0.30000000000000004

所以比较浮点数别用 ==,要看两个数的差距够不够小。差值的绝对值小于 1e-6,就当它们相等,这是惯用写法。涉及钱的计算更别碰浮点,用整数存分,显示的时候再除以 100,一分钱都不许它飘。

1.4 字符型和布尔型

char 一个字节,存的是编号不是字本身。字符按 ASCII 编号入库,'A' 是 65,'a' 是 97,大小写相差 32,'0' 是 48。char 参与运算时按整数算,'A' + 1 等于 66,正好是 'B'。

常用的转义字符有这么几个。

转义符含义
\n换行
\t制表符,按一下 Tab 那么宽
\反斜杠自己
"双引号
'单引号
\0空字符,字符串的结尾标记

bool 一个字节,只放 true 和 false。C++ 里非零即真,0 为假,这个习惯后面到处都用。

1.5 一张表记住常见占用

每种类型占几个字节,不用背,写几行代码问出来。

#include <iostream> using namespace std; int main() { cout << "bool " << sizeof(bool) << " 字节" << endl; cout << "char " << sizeof(char) << " 字节" << endl; cout << "short " << sizeof(short) << " 字节" << endl; cout << "int " << sizeof(int) << " 字节" << endl; cout << "long " << sizeof(long) << " 字节" << endl; cout << "long long " << sizeof(long long) << " 字节" << endl; cout << "float " << sizeof(float) << " 字节" << endl; cout << "double " << sizeof(double) << " 字节" << endl; return 0; }

64 位机器上跑出来是这样。

bool 1 字节 char 1 字节 short 2 字节 int 4 字节 long 4 字节 long long 8 字节 float 4 字节 double 8 字节

64 位 Windows 和 64 位 Linux 的差别在 long,其他一样。自己的机器什么结果,敲一遍就知道,比信任何教程都强。


二、常量和变量

2.1 变量是认领下来的一块内存

变量就是在内存里认领一块地,认领的时候要说清三样,这块地多大、叫什么、先放什么。

┌─────────────────────────────────────────────────────────┐ │ 变量是认领下来的一块内存 │ ├─────────────────────────────────────────────────────────┤ │ 一条声明,三样东西 │ │ │ │ +----------+------------+------------+--------------+ │ │ |成分 |代码里 |例子 |管什么 | │ │ +----------+------------+------------+--------------+ │ │ |类型 |int |4 字节 |格子多大 | │ │ |名字 |score |贴标签 |代码怎么称呼它| │ │ |值 |90 |装内容 |随时能改 | │ │ +----------+------------+------------+--------------+ │ │ │ │ int score = 90; 这一行,三样全占齐 │ │ 没初始化的变量里是内存残留,读到什么全看运气 │ └─────────────────────────────────────────────────────────┘

声明的时候不给初值,这块地里的数据是上一位房客留下的。同一个程序跑两遍,读到的数可能都不一样,这种 bug 查起来最费劲。好习惯只有一条,声明的时候就初始化。

初始化的写法有四种,效果等价。

int a = 5; // 传统写法 int b(5); // 构造函数写法 int c{5}; // 列表初始化,C++11 起 int d{}; // 列表初始化,值为 0

推荐列表初始化那一对花括号。它多一道检查,窄化转换直接拦下。

int x{3.7}; // 编译报错,小数变整数不许悄悄发生 int y = 3.7; // 能过,y 变成 3,小数部分丢了

同一行声明多个同类型变量也合法,int a = 1, b = 2;。别滥用,一行一个看得清楚。

2.2 命名规则

名字只能由字母、数字、下划线组成,数字不能打头,大小写敏感,不能撞关键字。

名字行不行原因
score行纯字母
score2行数字不在开头
_total行下划线可以开头
2score不行数字打头
my-score不行短横线不是下划线
int不行撞了关键字
Score 和 score两个名字大小写敏感

风格上建议两条。名字见名知意,totalPrice 比 tp 强一百倍,三个月后回来看,tp 是谁你根本想不起来。多个单词之间用下划线或者驼峰,选一种用到底,别混着来。

2.3 常量,认领了就不许改

常量的值定下来不许改,写法有两种。

#define MAX_SIZE 100 // 宏常量 const int maxSize = 100; // const 常量

推荐 const。宏常量是预处理阶段的文本替换,编译器只看见 100,不知道它原来叫什么,出错了报错信息里连名字都不留。const 常量有类型、有名字,受编译器检查,还能进调试器看。

#defineconst
干活阶段预处理,文本替换编译期
有类型吗没有有
出错了只看见数值,看不见名字名字和类型都在报错里
能进调试器吗不能能

const 后面还有花样,const 指针、const 引用、const 成员函数,到指针和类那两章再展开。现在记住一句,新代码用 const,别用宏。

2.4 关键字,这些名字被占了

C++ 留了一批名字自己用,变量不能跟它们重名。常用的有这么些。

类别关键字
类型相关int、double、float、char、bool、void、short、long
类型修饰const、unsigned、signed、static、auto
流程相关if、else、switch、case、default、for、while、do、break、continue、return
真值与内存true、false、sizeof、new、delete、this
结构和类struct、class、public、private、protected、enum、union
其他using、namespace、typedef、try、catch、throw

完整名单有九十多个,不用背。用到的时候编辑器会给它上个色,变色了就别当变量名。


三、运算符

3.1 算术运算符

运算符作用例子
+加3 + 2 得 5
-减3 - 2 得 1
*乘3 * 2 得 6
/除看两边类型,见下
%取余7 % 3 得 1,只认整数

除号是重灾区。两边都是整数,除出来还是整数,小数部分直接丢,不是四舍五入。

┌─────────────────────────────────────────────────────┐ │ 整数除法不要小数 │ ├─────────────────────────────────────────────────────┤ │ 同样的两个数,不同的下场 │ │ │ │ 7 / 2,两边都是整数 7.0 / 2,有个小数 │ │ │ │ │ │ v v │ │ 3,小数部分被丢掉 3.5,小数保住了 │ │ │ │ 算平均数、算比例、算百分比,先把一边转成 double │ └─────────────────────────────────────────────────────┘

把开篇那个平均分程序写全,看得更清楚。

#include <iostream> using namespace std; int main() { int total = 285; int count = 3; cout << total / count << endl; // 95,碰巧整除了 cout << total / 4 << endl; // 71,71.25 被丢成 71 return 0; }

取余号只认整数,3.5 % 2 直接编不过。除零更狠,整数除以 0 程序当场崩溃,浮点除以 0 得 inf,不报错但结果没法用。

3.2 赋值和复合赋值

等号是赋值,把右边的值装进左边的变量,不是数学里的相等。等号左边只能放一个变量,a = b = 3能写,从右边开始算,b 先得 3,再把 b 的值给 a。

复合赋值是懒人写法,效果和拆开一样。

写法等价于
a += 2a = a + 2
a -= 2a = a - 2
a *= 2a = a * 2
a /= 2a = a / 2
a %= 2a = a % 2

3.3 自增自减,++ 放前放后不一样

++ 让变量加一,-- 让变量减一,各有两种放法,差别只在嵌进表达式的时候露出来。

┌────────────────────────────────────────────────────┐ │ ++ 放前面和放后面 │ ├────────────────────────────────────────────────────┤ │ i 从 3 起步,两条路不一样 │ │ │ │ j = i++ j = ++i │ │ 先用旧值,再加一 先加一新,再用新值 │ │ │ │ │ │ v v │ │ j 得 3,i 变 4 j 得 4,i 变 4 │ │ │ │ 单独一行用没区别,嵌进表达式就差在这里 │ └────────────────────────────────────────────────────┘

代码走一遍。

#include <iostream> using namespace std; int main() { int i = 3; int j = i++; // 先把 3 给 j,i 再加成 4 cout << j << " " << i << endl; // 3 4 int x = 3; int y = ++x; // x 先加成 4,再把 4 给 y cout << y << " " << x << endl; // 4 4 return 0; }

建议只有一条,别把 ++ 塞进复杂表达式,cout << i++ + ++i这种代码除了炫技没有任何好处,行为还容易看错。

3.4 关系运算符

比大小和比相等,结果都是 bool,成立是 true,不成立是 false。

运算符作用
>大于
<小于
>=大于等于
<=小于等于
==相等,两个等号
!=不相等

经典事故是把 == 写成 =。if (x = 5)是赋值,5 非零,条件恒为真,if 里永远执行。现代编译器会给警告,警告别当没看见。

浮点数之间别用 == 比,回 1.3 节,用差值小于一个小数来判断。

3.5 逻辑运算符和短路

运算符作用
&&两边都真才真
两个竖线有一边真就真
!取反,真变假,假变真

&& 和 两个竖线会抄近路。左边已经能定输赢,右边根本不看。

┌──────────────────────────────────────────────────────────┐ │ 逻辑运算会抄近路 │ ├──────────────────────────────────────────────────────────┤ │ && 碰到假就收工,|| 碰到真就收工 │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ if (x != 0 && 10 / x > 1) │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ 左边先算,x 是 0 的话整个式子直接假 │ │ 右边的除法压根不发生,顺手把除零也挡了 │ │ │ │ 判空的表达式要写在左边,越界访问放右边 │ │ 顺序写反了,右边先炸,左边白写 │ └──────────────────────────────────────────────────────────┘

短路是标准规定的行为,不是哪个编译器的好心,可以放心用。写复合条件有个讲究,便宜又安全的放左边,贵又有风险的放右边。

3.6 位运算符

位运算直接在二进制位上动手,只认整型。

运算符叫什么例子结果和规律
&按位与5 & 31,两位都是 1 才留 1
单竖线按位或5 单竖线 37,有一位是 1 就留 1
^按位异或5 ^ 36,相同为 0,不同为 1
~按位取反~5-6,0 变 1,1 变 0
<<左移5 << 220,后面补 0
>>右移5 >> 12,前面补 0

左移就是往高位搬,每搬一位数值翻倍。

┌──────────────────────────────────────────────────────────┐ │ 左移右移在干什么 │ ├──────────────────────────────────────────────────────────┤ │ 1 往左搬三位,每搬一位翻一倍 │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 起步 00000001 十进制 1 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 左移一位 00000010 十进制 2 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 左移两位 00000100 十进制 4 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ 左移三位 00001000 十进制 8 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ 右移是反着搬,每搬一位除以 2,零头丢掉 │ │ 1 << 10 就是 1024,乘除 2 的幂用它最快 │ └──────────────────────────────────────────────────────────┘

位运算的实用场合有两个。权限标志,一个 int 的 32 位当 32 个开关用,第 n 位是 1 表示有这个权限。判奇偶,x & 1是 1 就是奇数,是 0 就是偶数,比取余快。乘除 2 的幂图上画了,记住位移比乘法快就行。

两个注意。有符号数左移丢了高位,是未定义行为,别赌编译器。右移负数,填 0 还是填 1 看编译器,想要确定行为先转无符号。

3.7 三元运算符

三元运算符是压缩版的 if else,问号前面是条件,冒号两边是两个结果。

int max = (a > b) ? a : b; // a 大取 a,b 大取 b

简单场合用着顺手,嵌套三元是灾难。a ? b : c ? d : e这种,看得人想砸键盘,老老实实写 if。

3.8 优先级,不确定就加括号

运算符有先后,全表在这儿,从高到低。

档位运算符一句话
第一档括号想先算的套起来
第二档单目运算++ -- ! ~ 正负号、强制转换、sizeof
第三档乘除取余* / %
第四档加减+ -
第五档位移<< >>
第六档比大小< <= > >=
第七档比相等== !=
第八档按位与&
第九档按位异或^
第十档按位或单竖线
第十一档逻辑与&&
第十二档逻辑或两个竖线
第十三档三元? :
第十四档赋值= 和 += 那一族
第十五档逗号最低

这张表不用背,背了也会忘。记两条就够,不确定的地方加括号,括号永远最先算。位运算的优先级比普通人直觉低,a & b == c先算相等再按位与,想要按位与先算就套括号。


四、数据类型转换

4.1 隐式转换,编译器悄悄干活

类型之间会互相转,不用你开口的那种叫隐式转换。方向只有一个,往高了转。

┌──────────────────────────────────────────────────────────┐ │ 隐式转换的阶梯 │ ├──────────────────────────────────────────────────────────┤ │ 自动转换的方向,一路往高爬 │ │ │ │ ┌────────────────────────────────────────────────────┐ │ │ │ bool 只有真假两个值 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ char 一个字节,编号 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ short 两个字节 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ int 四个字节,日常主力 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ long 四到八个字节 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ long long 八个字节,大数 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ float 小数起步 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ │ v │ │ ┌────────────────────────────────────────────────────┐ │ │ │ double 小数主力 │ │ │ └────────────────────────────────────────────────────┘ │ │ │ │ 两边类型打架,低的自动升级成高的那个再算 │ │ int 和 double 相加,int 先变成 double │ └──────────────────────────────────────────────────────────┘

规矩有两条。小的参与运算先提升,char 和 short 不管是加减还是打印,先升成 int 再上场。两边类型不一样,低的自动爬成高的,算完的结果按高的那个类型存。赋值的时候也转,double d = 3;里的 3 悄悄变成 3.0。

编译器做这些事不吭声,好处是省事,坏处是错了你不知道。

4.2 显式转换,自己开口要清楚

自己要求的转换叫显式转换,两种写法。

int total = 285; int count = 4; double avg1 = (double)total / count; // C 风格 double avg2 = static_cast<double>(total) / count; // C++ 风格

两种效果一样,都得到 71.25。推荐 static_cast,理由和 2.3 节推荐 const 一样,它在代码里长得显眼,搜索能搜到,编译器能检查,出错了报错信息带类型名。C 风格那个括号混在代码里,肉眼难找。

C++ 还有 const_cast、reinterpret_cast、dynamic_cast 三种,各有各的场合,到指针和类那两章再见。现在记住,日常转换 static_cast 一把够用。

4.3 转换的坑,挨个排雷

转换是 bug 密度最高的地方,常见事故有这么几款。

事故现场原因解法
平均数丢小数285 除以 4 得 71两个 int 相除一边转 double
int 溢出大数乘大数得负数结果超过 21 亿中间量用 long long
无符号回绕unsigned 减到 0 再减回绕是标准行为,不报错边界处加判断
混合比较翻车-1 大于 1有符号转无符号后成了巨大正数别混着比
浮点丢精度float 存 16777217 变 16777216有效数字不够换 double
char 溢出char 装 200超出一个字节换类型

混合比较这款最反直觉,光看代码不信,跑一遍就服了。

#include <iostream> using namespace std; int main() { unsigned int u = 1; int i = -1; cout << (u > i) << endl; // 1,true,-1 居然大于 1 return 0; }

有符号和无符号比大小,有符号的先被转成无符号,-1 转过去变成 4294967295,当然大于 1。规矩只有一条,比较的两边类型一致,不一致就先转成一样的。

4.4 转换不报错,这才最危险

语法错误编译器会拦,类型转换它基本不拦。该丢的小数丢了,该报的溢出没报,程序跑得欢,结果离谱,这种 bug 调试起来最耗人。

应对办法也简单,每一处转换心里有数。写除法前问一句,两边是不是都想要整数。比较大小时问一句,两边类型一样吗。存大数前问一句,这个类型装得下吗。三个问题问完,这一章的坑就躲掉大半。


五、小结

5.1 本章知识收口表

模块你现在应该能做对的事自测
基本类型说清四大家庭和各自字节数□ 能说出 int 和 double 的字节数
sizeof会用它验字节数□ 跑一遍 1.5 节的程序
变量常量声明即初始化,常量用 const□ 能写出列表初始化
运算符算术关系逻辑位移都会用□ 能心算 5 & 3 和 5 << 2
优先级知道括号最优先□ 能排出 1 + 2 * 3 的顺序
类型转换隐式往高转,显式用 static_cast□ 能修好平均分程序
┌─────────────────────────────────────────────────────────┐ │ 一章走完该带走的东西 │ ├─────────────────────────────────────────────────────────┤ │ 学完这一章,兜里该有这些 │ │ │ │ +--------------+------------------------------------+ │ │ |模块 |带走什么 | │ │ +--------------+------------------------------------+ │ │ |类型四家 |整型浮点字符布尔,字节数用 sizeof 验| │ │ |变量常量 |声明即初始化,const 胜过 #define | │ │ |运算符 |整数除法丢小数,位运算只认整数 | │ │ |类型转换 |隐式一路往高转,显式用 static_cast | │ │ +--------------+------------------------------------+ │ │ │ │ 哪一块心里没底,回去找对应小节,别急着往下翻 │ └─────────────────────────────────────────────────────────┘

5.2 五句话记住本章

  1. 类型是图纸,字节数用 sizeof 现算,别背。
  2. 整数除法丢小数,要小数先把一边转成 double。
  3. 变量声明就初始化,常量用 const,别用宏。
  4. ++ 放前放后不一样,别塞进复杂表达式。
  5. 有符号和无符号别混着比,比就先转成一路的。

5.3 课后练习

  1. 打印 1.5 节那张 sizeof 表,在自己机器上跑一遍,和书上的数对一遍,long 是几字节。
  2. 算班级平均分。先用两个 int 相除,再把除数转成 double,对比两次结果,说清差别在哪。
  3. 输入一个大写字母,输出它的小写形式,提示,大小写编号相差 32。
  4. 用位运算判断输入的整数是奇数还是偶数,提示,看二进制最低那一位。
  5. 有 a、b 两个变量,不用第三个变量把两个值换过来。做不出来正常,第三章学完再回头看。
  6. 挑战题,输入一个 0 到 255 的整数,输出它的二进制写法,提示,从第 7 位开始一位位问过去。

附录|运算符速查卡

类别成员一句话提醒
算术+ - * / %整数除法丢小数,% 只认整数
复合赋值+= -= *= /= %=懒人写法,左右类型不变
自增自减++ --放前面先加,放后面先用
关系> < >= <= == !=== 是两个等号,写成一个就完蛋
逻辑&& 两个竖线 !会短路,判空的写左边
位运算& 单竖线 ^ ~ << >>只认整型,判奇偶用 x & 1
三元? :简单场合用,嵌套是灾难
优先级括号最高,逗号最低不确定就加括号

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询