1. 字面量
字面量用于在代码中编写数字或字符串。C++支持大量的字面量,如下所示。
类别 | 示例 |
---|---|
十进制字面量 | 123 |
八进制字面量 | 0173 |
十六进制字面量 | 0x7B |
二进制字面量 | 0b1111011 |
浮点数 | 3.14f |
双精度浮点数 | 3.14 |
十六进制浮点字面量 | 0x3.ABCp-10、0Xb.cp121 |
单个字符 | ‘a’ |
以零结尾的字符数组 | “character array" |
还可以在数值字面量中使用数字分隔符,数字分隔符是一个单引号,示例如下。
23'456'789
0.123'456f
2. 变量
2.1 变量初始化
在C++中,可在任何位置声明变量,并且可在声明一个变量所在行之后的任意位置使用该变量。声明变量时可不指定值,这些未初始化的变量通常会被赋予一个半随机值,这个值取决于当时内存中的内容。在C++中,也可在声明变量时为变量指定初始值。下面的代码给出了两种风格的变量声明方式,使用的都是代表整型值的int类型。
int uninitializedInt;
int initializedInt { 7 };
std::cout << std::format("{} is a random value\n", uninitializedInt);
std::cout << std::format("{} was assigned an initial value", initializedInt) << std::endl;
运行结果如下:
注意:当代码中使用了未初始化的变量时,多数编译器会给出警告或报错信息。当访问未初始化的变量时,某些C++环境可能会报告运行时错误。
initializedInt变量使用统一初始化语法进行初始化。也可以使用下面的赋值语句进行初始化:
initializedInt = 7;
统一初始化在2011年的C++11标准中引入。建议使用统一初始化代替旧的赋值语法。
2.2 变量类型
C++中的变量是强类型的,它们总是有一个特定的类型。C++自带一个可以开箱即用 的内置类型集合,如下表所示。
类型 | 说明 | 用法 |
(signed) int
signed | 正整数或负整数,范围取决于编译器(通常是四字节) |
int i {-7};
signed int i {-6}; signed i {-5}; |
(signed) short (int) | 短整型整数(通常是2字节) |
short s {13}; short int s {14}; signed short s {15}; signed short int s {16}; |
(signed) long (int) | 长整型整数(通常是4字节) | long l {-7L}; |
(signed) long long (int) | 超长整型整数,范围取决于编译器,但不低于长整型(通常是8字节) | long long ll {14LL}: |
unsigned (int) unsigned short (int) unsigned long (int) unsigned long long (int) | 对前面的类型加以限制,使其值大于等于0 |
unsigned int i {2U}; unsigned j {5U}; unsigned short s {23U}; unsigned long l {5400UL}; unsigned long long ll {140ULL}; |
float | 浮点型数字 | float f {7.2f}; |
double | 双精度数字,精度不低于float | double d {7.2}; |
long double | 长双精度数字,精度不低于double | long double d {16.89L}; |
char unsigned char signed char | 单个字符 | char ch {'m'}; |
char8_t(从C++20开始) char16_t char32_t | 单个n位UTF-n编码的Unicode字符,n可以是8,16,32 |
char8_t c8 {u8'm'}; char16_t c16 {u'm'}; char32_t c32 {U'm'}; |
wchar_t | 单个宽字符,大小取决于编译器 | wchar_t w {L'm'}; |
bool | 布尔类型,取值为true或false | bool b {true}; |
char类型与signed char和unsigned char类型是不同的类型,它只应该用来表示字符。根据你的编译器不同,它既可能是有符号的,也可能是无符号的,所以不因该用它来表示有符号或者无符号字符。
与char相关的是,<cstddef>中提供了std::byte类型来表示单个字节。在C++17之前,char或unsigned char用来表示一个字节,但是那些类型使得像在处理字符。std::byte却能指明意图,即内存中的单个字节。一个byte可以用如下方式初始化:
std::byte b { 42 };
注意:C++并没有提供基本的字符串类型。但是作为标准库的一部分提供了字符串的标准实现。
2.3 数值极限
C++提供了一种获取数值极限信息的标准方式,例如在当前的平台上一个整数能表示的最大值。在C中,你可以使用各种宏定义,例如INT_MAX。尽管这些方法在C++中仍然可以使用,但推荐的做法是使用定义在<limits>中的类模板std::numeric_limits,在使用类模板时需要在一对尖括号内指定需要的类型。
std::cout << "int:\n";
std::cout << std::format("Max int value: {}\n", std::numeric_limits<int>::max());
std::cout << std::format("Min int value: {}\n", std::numeric_limits<int>::min());
std::cout << std::format("Lowest int value: {}\n", std::numeric_limits<int>::lowest());
std::cout << "\ndouble:\n";
std::cout << std::format("Max double value: {}\n", std::numeric_limits<double>::max());
std::cout << std::format("Min double value: {}\n", std::numeric_limits<double>::min());
std::cout << std::format("Lowest double value: {}\n", std::numeric_limits<double>::lowest());
运行结果:
注意min()和lowest()的区别。对于一个整数,最小值等于最低值。对于浮点类型来说,最小值表示该类型能表示的最小正数,最低值表示该类型能表示的最小负数,即-max()。
2.4 零初始化
可以用一个{0}的统一初始化器将变量初始化为0,0在这里是可选的。一对空的花括号组成的统一初始化器,{},称为零初始化器。零初始化会将原始的整数类型初始化为0,将原始的浮点类型初始化为0.0,将指针类型初始化为nullptr,将对象用默认构造器初始化。示例如下:
float myFloat {};
int myInt {};
2.5 类型转换
可使用类型转换方式将变量转换为其他类型。C++提供了3种方法显式地转换变量类型。第一种方法来自C,并且仍然被广泛使用,但是实际上已经不建议使用这种方法;第二种方法初看上去更自然,但很少使用;第三种方法最复杂,却最整洁,是推荐方法。
float myFloat { 3.14f };
int i1 { (int)myFloat }; // method1
int i2 { int(myFloat) }; // method2
int i3 { static_cast<int>(myFloat) }; // method3
得到的整数是去掉小数部分的浮点数。在某些环境中,可自动执行类型转换或强制执行类型转换。例如short可自动转换为long,因为long代表精度更高的相同数据类型。
long someLong { someShort }; // no explicit cast needed
当自动转换变量的类型时,应该了解潜在的数据丢失情况。例如将float转换为int会丢掉一些信息,并且如果浮点数表示的数字超过了整数可表示的最大值,转换结果可能是完全错误的。如果将一个float赋给一个int而不显式执行类型转换,多数编译器会给出警告甚至错误信息。如果确信左边的类型与右边的类型完全兼容,那么隐式转换也完全没问题。
2.6 浮点型数字
处理浮点数数字可能比处理整型数字复杂。使用数量级不同的浮点值计算可能导致错误;计算两个几乎相同的浮点数的差时会导致精度丢失;很多十进制数不能精确地表示为浮点数。
特殊的浮点数:
1. +/-infinity:表示正无穷和负无穷。
2. NaN:非数字的缩写。
可以用std::isnan()判断一个给定的浮点数是否为非数字,用std::isinf()判断是否为无穷,这两个函数都定义在<cmath>中。此外,还可以通过numeric_limits获取这些特殊的浮点数。
3. 运算符
下表中给出了C++中最常用的运算符以及使用这些运算符的示例代码。注意:在C++中,运算符可以是二元的、一元的甚至是三元的。在C++中只有一个条件运算符。
运算符 | 说明 | 用法 |
= | 二元运算符,将右边的值赋给左边的变量 |
int i; i = 3; int j; j = i; |
! | 一元运算符,改变变量的真/假(非零/零)状态 |
bool b { !true }; bool b2 { !b }; |
+ | 执行加法的二元运算符 |
int i { 3 + 2 }; int j { i + 5 }; int k { i + j }; |
- */ | 执行减法、乘法以及除法的二元运算符 |
int i { 5 - 1 }; int j { 5 * 2 }: int k { j / i }; |
% | 二元运算符,求除法操作的余数,也称为mod运算符 | int rem { 5 % 2 }; |
++ | 一元运算符,使变量自增1 如果运算符在变量之后(后增量),表达式的结果是没有增加的值 如果运算符在变量之前(前增量),表达式的结果是增加1后的新值 |
i++; ++i; |
-- | 一元运算符,使变量值减1 |
i--; --i; |
+= -= *= /= %= |
i=i+j;的简写
i=i-j;的简写 i=i*j;的简写 i=i/j;的简写 i=i%j;的简写 |
i += j; i -= j; i *= j; i /= j; i %= j; |
& &= | 将一个变量的原始位与另一个变量执行按位与运算 |
i = j & k; j &= k; |
| |= | 将一个变量的原始位与另一个变量执行按位或运算 |
i= j | k; j |= k; |
<< >> <<= >>= | 对一个变量的原始位执行移位运算,每一位左移或右移指定的位数 |
i = i << 1; i = i >> 4; i <<= 1; i >>= 4; |
^ ^= | 执行两个变量之间的按位异或运算 |
i = i ^ j; i ^= j; |
下面的程序展示了最常见的变量类型以及运算符的用法。
int someInteger { 256 };
short someShort;
long someLong;
float someFloat;
double someDouble;
someInteger++;
someInteger *= 2;
someShort = std::static_cast<short>(someInteger);
someLong = someShort * 10000;
someFloat = someLong + 0.785f;
someDouble = static_cast<double>(someFloat) / 100000;
std::cout << std::format("someInteger: {}, someShort: {}, someLong: {}, someFloat: {}, someDouble: {}\n", someInteger, someShort, someLong, someFloat, someDouble) << std::endl;
运行结果:
关于表达式求值顺序,C++编译器有一套准则。如果某行代码非常复杂,包含多个运算符,其执行顺序可能不会一目了然。因此,最好将复杂表达式分成若干短小的表达式,或使用括号明确地将子表达式分组。示例如下:
//int i { 34 + 8 * 2 + 21 / 7 % 2 };
int i { 34 + ( 8 * 2 ) + ( ( 21 / 7 ) % 2) }; // add round brakets to make computation clear
如果测试不加圆括号和加圆括号的代码,会发现这两种方法是等价的,其结果都是i等于51。如果你认为C++按从左到右的顺序对表达式求值,答案将是1。实际上,C++会首先对/、*以及%求值(从左向右),然后才执行加减运算,最后是位运算。通过使用圆括号,可明确告诉编译器某个运算应该单独求值。
运算符的计算顺序是由所谓的优先级决定的,优先级高的运算符要先于优先级低的运算符执行。下面给出了部分运算符的优先级,越靠上的运算符优先级越高,它们也将先执行。
++ --(后置)
! ++ --(前置)
* / %
+ -
<< >>
&
^
|
= += -= *= /= %= &= |= ^= <<= >>=
参考
[比] 马克·格雷戈勒著 程序喵大人 惠惠 墨梵 译 C++20高级编程(第五版)