Skip to content

Latest commit

 

History

History
159 lines (106 loc) · 8.74 KB

2-1.md

File metadata and controls

159 lines (106 loc) · 8.74 KB

融合态 · 复合类型

还记得之前的篮子与瓶子吗?它们作为不同类型物质的容器,被针对性地以不同的材料与形状设计出来,以适应人们的日常需求。

想一想,瓶子可以用塑料、陶瓷或是蜡纸这类隔水的材料制作,而篮子得用麻绳、布料或是橡胶这类易于变形的材料制作。它们都是装载物质的容器,但它们所承载的物质类型不同,也提醒了人们如何选择正确的容器。

在 C++ 中亦是如此——我们所持有的数据决定了承载该数据的容器类型;同样地,容器的类型亦反过来决定我们应向其装载的数据类型。

下面,我们就来探寻 C++ 中最基本的一批容器吧。


0

这是什么?对,还是它,数字零!

零是什么?首先它肯定是个数。再细分以下,它还是个整数,是一个既不可算作正数、亦不可算作负数的整数。

我们可以使用类型为int的容器存储数字零,int是英语单词 integer (整数) 的缩写。在这里,我们先随便取个容器名a

int a = 0;

类型必须写在开头,其后是容器的名字,而等于号之后便是我们想要装载的内容物。

在 C++ 中,我们称这样的容器叫作变量。变量本质上就是具名的数据,它有具体的可承载数据的类型,并且也有一片用于放置此类型数据的空间。就拿刚刚的例子来讲,int类型的每个变量都最少占据 4 字节的空间。

如同刚刚的程序所作的操作,是声明一个变量。在声明的同时,我们还可以通过在气候紧跟一个等于号与欲赋予的值,使得这个变量被初始化。在这个例子中,我们便将数字0作为变量a的初始值。在变量受到初始化的同时,它也就相当于被定义为了某一个


难道 C++ 只提供了一个int类型么?当然不是。光是表达整数这个事情,C++ 就给了我们一大批的整数类型供使用,不过就是其所能表达的数字范围有所区别罢了:

类型 最小表达范围 通常表达范围
char $[2^{-7}, 2^7)$ $[2^{-7}, 2^7)$
shortshort int $[2^{-15}, 2^{15})$ $[2^{-15}, 2^{15})$
int $[2^{-15}, 2^{15})$ $[2^{-31}, 2^{31})$
longlong int $[2^{-31}, 2^{31})$ $[2^{-31}, 2^{31})$
long longlong long int $[2^{-31}, 2^{31})$ $[2^{-63}, 2^{63})$

一般来讲,我们使用intlong类型便足以满足日常需求了。更广的数字表达范围也许能方便我们表达更大的数字,但由此也将会带来更大的空间占用;同样的,更窄的数字表达范围虽然能够很好地减少空间占用,但它也仅适用于对空间占用要求严格、需表达的整数又较小的场合。


回忆先前有关二进制数在计算机中如何表达的知识,我们知道整数的最开头以为用于表达数字的正负,气候才是数字本身的内容。

有没有办法让最开头那一位也作为数字呢?有!在类型前加unsinged即可:

unsigned int a = 0;

如果我们为某个整数类型加上unsigned前缀,它将不能表达负数,并且正数的表达范围也会变为原来的 2 倍。它通常适用于保证用不到负数并且也不使用负数的场合,例如计算物体间的距离——再怎么调整,我们都找不到距离为负数的两个物体。

这样不带正负号的类型称作无符号类型,由其承载的正数也可称作无符号数。无符号是整数类型的特权,除此之外的其它任何类型都不能也无法添加unsigned前缀。与之相反,带符号的类型被称作有符号类型,可通过在类型前加signed来声明。

默认情况下,除了char类型以外的整数类型均为有符号类型,无需额外在类型前加signed前缀。char类型默认情况下是否有符号,是根据具体所在的运行环境而定的。

无符号类型的数字表达范围其实与其对应的有符号类型的一样宽,形象地讲其实也就是把表示负数的那部分范围“挪”到了右边:

类型 最小表达范围 通常表达范围
unsigned char $[0, 2^8)$ $[0, 2^8)$
unsigned shortunsigned short int $[0, 2^{16})$ $[0, 2^{16})$
unsigned int $[0, 2^{16})$ $[0, 2^{32})$
unsigned longunsigned long int $[0, 2^{32})$ $[0, 2^{32})$
unsigned long longunsigned long long int $[0, 2^{32})$ $[0, 2^{64})$

除了整数之外,我们有时也需要表达更为复杂的小数,而这种数字无法完整储存在整数类型的变量中。

C++ 提供了另一批类型,即浮点数类型,可使我们得以储存一定精度的小数:

类型 表达范围
float 最低 6 位有效数字,小数位数 $[-38, 38]$
double 最低 15 位有效数字,小数位数 $[-308. 308]$

我们一般会选择double作为首选的浮点数类型,不仅仅是因为其足够高的精度,也因为现代大多数的设备都对double类型有硬件上的效率优化——甚至要比较小float类型还快。

精度一词,实际上指的是该类型可表达的最大有效位数,而非数字小到多少位,或是数字大到多少位。例如,以下数字虽然有着悬殊的大小差距,但其精度其实是相等的:

$1.23168809 × 10^{91}$

$3.14159263 × 10^0$

$2.19857822 × 10^{-82}$


还记得最初我们向屏幕打印的话么?

"Hi, C++ World!"

这看起来更像是一系列字符,而不是什么数字。

那么,字符使用什么类型的变量储存呢?答案是char类型:

char a = 'a';
char b = 'b';
char abc[] = "abc";

变量ab分别仅存储了一个字符,而变量abc却因为后面跟了个[],结果它却可以接受以双引号包裹的 C 风格字符串

不必惊讶,我们将在后续的章节学习有关数组的内容,而刚刚的变量abc便是将要介绍的数组的一种特殊形式——字符数组,也就是刚刚所提到的 C 风格字符串。

除了char类型,C++ 也提供了一批其它的字符类型,便于我们使用其它的字符集表达文字:

类型 占用空间大小 说明
char 1 字节 普通字符类型
wchar_t 2 字节 宽字符类型
char8_t(C++20) 1 字节 Unicode 字符类型,8 位版
char16_t 2 字节 Unicode 字符类型,16 位版
char32_t 4 字节 Unicode 字符类型,32 位版

这些类型与先前介绍的一批字符字面量是相互匹配的,亦因此这些字符字面量便也是有对应类型的变量容纳的。


总结以下,我们认识了以下的变量类型:

  • 用于表达整数的charshortintlonglong long
  • 用于修饰整数类型、使之成为无符号数的unsigned
  • 用于表达浮点数的floatdouble
  • 用于表达字符的charwchar_tchar8_tchar16_tchar32_t

类型是 C++ 的重要组成部分,它构成了 C++ 的大部分内容。接下来,我们将学习更深层次的类型系统。


习题

  • 录入下面的程序,尝试修改程序中的变量名与初始值并允许,体验一下定义变量的感觉。
#include <iostream>
int main()
{
    int apple = 1;
    int pen = 1;
    char pineapple = 'a';
    std::cout << "I have " << apple << " apple" << std::endl
        	  << "I have " << pen << " pen" << std::endl
              << "Uh! Apple-pen!" << std::endl;
    		  << " I have " << pen << " pen" << std::endl
              << " I have " << pineapple << " pineapple" << std::endl
              << "Uh! Pineapple-pen!" << std::endl;
    return 0;
}