Part I: The Basics
Chapter 2. Variables and Basic Types
2.1 基本内置类型
注意:类型 char 和 类型 signed char 并不一样。类型 char 会表现出带符号形式或无符号形式,具体是哪种由编译器决定。
表2.1 C++:算术类型(Arithmetic Types)
类型 | 含义 | 最小尺寸 |
---|---|---|
bool | 布尔类型 | 未定义 |
char | 字符 | 8 位 |
wchar_t | 宽字符 | 16 位 |
char16_t | Unicode 字符 | 16 位 |
char32_t | Unicode 字符 | 32 位 |
short | 短整型 | 16 位 |
int | 整型 | 16 位 |
long | 长整型 | 32 位 |
long long | 长整型 | 64 位 |
float | 单精度浮点数 | 6 位有效数字 |
double | 双精度浮点数 | 10 位有效数字 |
long double | 扩展精度浮点数 | 10 位有效数字 |
建议:如何选择类型
- 当明确知道数字不可能为负时,选用无符号类型。
- 使用 int 执行整数运算。如果数值超过了 int 的表示范围,选用 long long。
- 在算术表达式中不要使用 char 和 bool,只有在存放字符或布尔值时使用它们。
- 执行浮点数运算使用 double。
类型转换:将对象从一种给定的类型转换(convert)为另一种相关类型。
类型所能表示的值决定了转换的过程:
当赋给带符号类型一个超出它表示范围的值时,结果是未定义的(undefined)。此时,程序可能继续运行,可能崩溃,或可能生成垃圾数据。
在表达式中使用无符号类型要注意!
当一个算术表达式中同时包含 unsigned 和 int 值时,一般情况下,int 值会转换为 unsigned。
unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl; // prints -84
std::cout << u + i << std::endl; // if 32-bit ints, prints 4294967264
无符号不会小于 0 这一事实关系到循环的写法。
// WRONG: u can never be less than 0; the condition will always succeed
for (unsigned u = 10; u >= 0; --u)
std::cout << u << std::endl;
字面值常量
字符串字面值类型实际上是由常量字符构成的数组。
编译器在每个字符串的结尾处添加一个空字符 '\0'
,因此,字符串字面值的实际长度要比它的内容多 1。
彼此相邻且仅由空格、制表符或换行符分隔的两个字符串字面值,可以连接成一个字面值。当书写的字符串字面值常量过长,不适合写入一行时,可以使用这种形式的字面值:
// multiline string literal
std::cout << "a really, really long string literal "
"that spans two lines" << std::endl;
转义序列
符号 | \n | \t | \v | \b | \r | \f | \a | \\ | \? | \' | \" |
---|---|---|---|---|---|---|---|---|---|---|---|
含义 | 换行 | 横向制表 | 纵向制表 | 退格 | 回车 | 换页 | 报警(响铃) | 反斜杠 | 问号 | 单引号 | 双引号 |
也可以使用泛化的转义序列,其形式是 \x
后紧跟 1 个或多个十六进制数字,或者 \
后紧跟 1 个、2 个或 3 个八进制数字,其中数字部分表示的字符对应的数值。假设使用的是 Latin-1 字符集,示例如下:
符号 | \7 | \12 | \40 | \0 | \115 | \x4d |
---|---|---|---|---|---|---|
含义 | 响铃 | 换行 | 空格 | 空字符 | 字符 M | 字符 M |
指定字符和字符串字面值
前缀 | 含义 | 类型 |
---|---|---|
u | Unicode 16 字符 | char16_t |
U | Unicode 32 字符 | char32_t |
L | 宽字符 | wchar_t |
u8 | UTF-8(仅用于字符串字面常量) | char |
2.2 变量
变量定义
int units_sold = 0;
int units_sold = {0};
int units_sold{0};
int units_sold(0);
long double ld = 3.1415926536;
int a{ld}, b = {ld}; // error: narrowing conversion required
int c(ld), d = ld; // ok: but value will be truncated
变量定义和声明
extern int i; // declares but does not define i
int j; // declares and defines j
extern double pi = 3.1416; // definition
关键概念:静态类型
C++是一种静态类型(statically typed)语言,其含义是在编译阶段检查类型。
检查类型的过程称为类型检查(type checking)。
C++操作符替代名
and, not, or, xor, compl, bitand, bitor, not_eq, or_eq, xor_eq, and_eq
2.3 复合类型
引用
int ival = 1024;
int &refVal = ival; // refVal refers to (is another name for) ival
int &refVal2; // error: a reference must be initialized
// ok: refVal3 is bound to the object to which refVal is bound, i.e., to ival
int &refVal3 = refVal;
// initializes i from the value in the object to which refVal is bound
int i = refVal; // ok: initializes i to the same value as ival
int i = 1024, i2 = 2048; // i and i2 are both ints
int &r = i, r2 = i2; // r is a reference bound to i; r2 is an int
int i3 = 1024, &ri = i3; // i3 is an int; ri is a reference bound to i3
int &r3 = i3, &r4 = i2; // both r3 and r4 are references
int &refVal4 = 10; // error: initializer must be an object
double dval = 3.14;
int &refVal5 = dval; // error: initializer must be an int object
指针
int *ip1, *ip2; // both ip1 and ip2 are pointers to int
double dp, *dp2; // dp2 is a pointer to double; dp is a double
获取对象的地址
int ival = 42;
int *p = &ival; // p holds the address of ival; p is a pointer to ival
double dval;
double *pd = &dval; // ok: initializer is the address of a double
double *pd2 = pd; // ok: initializer is a pointer to double
int *pi = pd; // error: types of pi and pd differ
pi = &dval; // error: assigning the address of a double to a pointer to int
利用指针访问对象
int ival = 42;
int *p = &ival; // p holds the address of ival; p is a pointer to ival
cout << *p; // * yields the object to which p points; prints 42
*p = 0; // * yields the object; we assign a new value to ival through p
cout << *p; // prints 0
空指针
int *p1 = nullptr; // equivalent to int *p1 = 0;
int *p2 = 0; // directly initializes p2 from the literal constant 0
// must #include cstdlib
int *p3 = NULL; // equivalent to int *p3 = 0;
int zero = 0;
pi = zero; // error: cannot assign an int to a pointer
void* 指针
double obj = 3.14, *pd = &obj;
// ok: void* can hold the address value of any data pointer type
void *pv = &obj; // obj can be an object of any type
pv = pd; // pv can hold a pointer to any type
理解复合类型的声明
// i is an int; p is a pointer to int; r is a reference to int
int i = 1024, *p = &i, &r = i;
int* p; // legal but might be misleading
int* p1, p2; // p1 is a pointer to int; p2 is an int
int *p1, *p2; // both p1 and p2 are pointers to int
int* p1; // p1 is a pointer to int
int* p2; // p2 is a pointer to int
指向指针的指针
int ival = 1024;
int *pi = &ival; // pi points to an int
int **ppi = π // ppi points to a pointer to an int
cout << "The value of ival\n"
<< "direct value: " << ival << "\n"
<< "indirect value: " << *pi << "\n"
<< "doubly indirect value: " << **ppi
<< endl;
指向指针的引用
int i = 42;
int *p; // p is a pointer to int
int *&r = p; // r is a reference to the pointer p
r = &i; // r refers to a pointer; assigning &i to r makes p point to i
*r = 0; // dereferencing r yields i, the object to which p points; changes i to 0
面对一条复杂的指针或引用的声明语句时,从右向左阅读有助于理解其含义。
2.4 const 限定符
const int bufSize = 512; // input buffer size
bufSize = 512; // error: attempt to write to const object
const int i = get_size(); // ok: initialized at run time
const int j = 42; // ok: initialized at compile time
const int k; // error: k is uninitialized const
初始化和 const
int i = 42;
const int ci = i; // ok: the value in i is copied into ci
int j = ci; // ok: the value in ci is copied into j
默认状态下,const 对象仅在文件内有效
如果想在多个文件之间共享 const 对象,必须在变量的定义之前添加 extern 关键字。
// file_1.cc defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_1.h extern
const int bufSize; // same bufSize as defined in file_1.cc
const 的引用
const int ci = 1024;
const int &r1 = ci; // ok: both reference and underlying object are const
r1 = 42; // error: r1 is a reference to const
int &r2 = ci; // error: non const reference to a const object
术语:常量引用是对 const 的引用。
初始化和对 const 的引用
“引用类型必须与其所引用的对象一致”的第一个例外:在初始化对 const 的引用时,可以使用能够将值转换为引用的类型的任意表达式作为初始值。
int i = 42;
const int &r1 = i; // we can bind a const int& to a plain int object
const int &r2 = 42; // ok: r1 is a reference to const
const int &r3 = r1 * 2; // ok: r3 is a reference to const
int &r4 = r1 * 2; // error: r4 is a plain, non const reference
double dval = 3.14;
const int &ri = dval;
编译器将上面的代码变为:
const int temp = dval; // create a temporary const int from the double
const int &ri = temp; // bind ri to that temporary
在这种情况下,ri
绑定了一个临时量(temporary)对象。
由于将引用绑定到临时对象几乎肯定不是程序员想要的,因此C++语言使其成为非法。
对 const 的引用可能引用一个并非 const 的对象
int i = 42;
int &r1 = i; // r1 bound to i
const int &r2 = i; // r2 also bound to i; but cannot be used to change i
r1 = 0; // r1 is not const; i is now 0
r2 = 0; // error: r2 is a reference to const
指针和 const
const double pi = 3.14; // pi is const; its value may not be changed
double *ptr = π // error: ptr is a plain pointer
const double *cptr = π // ok: cptr may point to a double that is const
*cptr = 42; // error: cannot assign to *cptr
“指针的类型必须与其所指向的对象一致”的第一个例外:指向 const 的指针可以指向一个非const 的对象。
double dval = 3.14; // dval is a double; its value can be changed
cptr = &dval; // ok: but can't change dval through cptr
const 指针必须初始化,它的值(即存放的地址)不能更改。
int errNumb = 0;
int *const curErr = &errNumb; // curErr will always point to errNumb
const double pi = 3.14159;
const double *const pip = π // pip is a const pointer to a const object
*pip = 2.72; // error: pip is a pointer to const
// if the object to which curErr points (i.e., errNumb) is nonzero
if (*curErr) {
errorHandler();
*curErr = 0; // ok: reset the value of the object to which curErr is bound
}
顶层 const
int i = 0;
int *const p1 = &i; // we can't change the value of p1; const is top-level
const int ci = 42; // we cannot change ci; const is top-level
const int *p2 = &ci; // we can change p2; const is low-level
const int *const p3 = p2; // right-most const is top-level, left-most is not
const int &r = ci; // const in reference types is always low-level
当执行对象的复制操作时,顶层 const 不受影响。
i = ci; // ok: copying the value of ci; top-level const in ci is ignored
p2 = p3; // ok: pointed-to type matches; top-level const in p3 is ignored
当复制一个对象时,两个对象必须具有相同的底层 const 限定条件,或者两个对象的类型之间必须能够转换。通常,可以将 非const 转换为 const,但不能反过来。
int *p = p3; // error: p3 has a low-level const but p doesn't
p2 = p3; // ok: p2 has the same low-level const qualification as p3
p2 = &i; // ok: we can convert int* to const int*
int &r = ci; // error: can't bind an ordinary int& to a const int object const
int &r2 = i; // ok: can bind const int& to plain int
constexpr 和常量表达式
一个对象(或表达式)是否是常量表达式取决于类型和初始值。
const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27; // staff_size is not a constant expression
const int sz = get_size(); // sz is not a constant expression
C++11 标准中,通过在 constexpr 声明中声明变量,要求编译器验证变量是否是常量表达式。
constexpr int mf = 20; // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression
constexpr int sz = size(); // ok only if size is a constexpr function
在 constexpr 声明中定义指针时,constexpr 说明符应用于该指针,而不是该指针所指向的类型。
const int *p = nullptr; // p is a pointer to a const int
constexpr int *q = nullptr; // q is a const pointer to int
constexpr 可以指向 const 类型或 非const 类型。
constexpr int *np = nullptr; // np is a constant pointer to int that is null
int j = 0;
constexpr int i = 42; // type of i is const int
// i and j must be defined outside any function
constexpr const int *p = &i; // p is a constant pointer to the const int i
constexpr int *p1 = &j; // p1 is a constant pointer to the int j
2.5 处理类型
类型别名(type alias)
传统方法:使用关键字 typedef
typedef double wages; // wages is a synonym for double
typedef wages base, *p; // base is a synonym for double, p for double*
C++11 标准:使用别名声明(alias declaration)
using SI = Sales_item; // SI is a synonym for Sales_item
wages hourly, weekly; // same as double hourly, weekly;
SI item; // same as Sales_item item
指针、const 和类型别名
typedef char *pstring;
const pstring cstr = 0; // cstr is a constant pointer to char
const pstring *ps; // ps is a pointer to a constant pointer to char
const char *cstr = 0; // wrong interpretation of const pstring cstr
auto 类型说明符
// the type of item is deduced from the type of the result of adding val1 and val2
auto item = val1 + val2; // item initialized to the result of val1 + val2
auto i = 0, *p = &i; // ok: i is int and p is a pointer to int
auto sz = 0, pi = 3.14; // error: inconsistent types for sz and pi
复合类型、const 和 auto
编译器以引用对象的类型作为 auto 的类型。
int i = 0, &r = i;
auto a = r; // a is an int (r is an alias for i, which has type int)
auto 一般会忽略掉顶层 const,底层 const 会保留下来。
const int ci = i, &cr = ci;
auto b = ci; // b is an int (top-level const in ci is dropped)
auto c = cr; // c is an int (cr is an alias for ci whose const is top-level)
auto d = &i; // d is an int*(& of an int object is int*)
auto e = &ci; // e is const int*(& of a const object is low-level const)
如果希望推断出的类型有顶层 const,必须明确指出:
const auto f = ci; // deduced type of ci is int; f has type const int
还可以明确指出想要的对 auto 推断类型的引用:
auto &g = ci; // g is a const int& that is bound to ci
auto &h = 42; // error: we can't bind a plain reference to a literal
const auto &j = 42; // ok: we can bind a const reference to a literal
引用或指针是特定声明程序的一部分,而不是声明的基本类型的一部分。
auto k = ci, &l = i; // k is int; l is int&
auto &m = ci, *p = &ci; // m is a const int&;p is a pointer to const int
// error: type deduced from i is int; type deduced from &ci is const int
auto &n = i, *p2 = &ci;
decltype 类型说明符
decltype(f()) sum = x; // sum has whatever type f returns
编译器并不调用 f
,但是它使用调用返回的类型作为 sum
的类型。
当应用于 decltype 的表达式是一个变量时,decltype 返回该变量的类型,包括顶层 const 和引用。
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x has type const int
decltype(cj) y = x; // y has type const int& and is bound to x
decltype(cj) z; // error: z is a reference and must be initialized
当应用于 decltype 的表达式不是一个变量时,decltype 返回表达式结果对应的类型。
// decltype of an expression can be a reference type
int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // ok: addition yields an int; b is an (uninitialized) int
decltype(*p) c; // error: c is int& and must be initialized
如果表达式是解引用操作,decltype 返回引用类型。
decltype 推断的类型取决于其表达式的形式。
特别情况:在变量名外加上一对括号,影响 decltype 返回的类型。
// decltype of a parenthesized variable is always a reference
decltype((i)) d; // error: d is int& and must be initialized
decltype(i) e; // ok: e is an (uninitialized) int
2.6 自定义数据结构
定义 Sales_data 类型
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
使用 struct 关键字定义类。
在类定义的最后加上分号!因为可以在类体后面定义变量。
struct Sales_data { /* ... */ } accum, trans, *salesptr;
// equivalent, but better way to define these objects
struct Sales_data { /* ... */ };
Sales_data accum, trans, *salesptr;
最好不要将对象定义为类定义的一部分!
使用 Sales_data 类
#include <iostream>
#include <string>
#include "Sales_data.h"
int main()
{
// Adding Two Sales_data Objects
Sales_data data1, data2;
// Reading Data into a Sales_data Object
double price = 0; // price per book, used to calculate total revenue
// read the first transactions: ISBN, number of books sold, price per book
std::cin >> data1.bookNo >> data1.units_sold >> price;
// calculate total revenue from price and units_sold
data1.revenue = data1.units_sold * price;
// read the second transaction
std::cin >> data2.bookNo >> data2.units_sold >> price;
data2.revenue = data2.units_sold * price;
// Printing the Sum of Two Sales_data Objects
// check whether data1 and data2 have the same ISBN, if so print the sum of data1 and data2
if (data1.bookNo == data2.bookNo) {
unsigned totalCnt = data1.units_sold + data2.units_sold;
double totalRevenue = data1.revenue + data2.revenue;
// print: ISBN, total sold, total revenue, average price per book
std::cout << data1.bookNo << " " << totalCnt << " " << totalRevenue << " ";
if (totalCnt != 0)
std::cout << totalRevenue/totalCnt << std::endl;
else
std::cout << "(no sales)" << std::endl;
return 0; // indicate success
} else { // transactions weren't for the same ISBN
std::cerr << "Data must refer to the same ISBN" << std::endl;
return -1; // indicate failure
}
}
编写自己的头文件
头文件通常包含任何给定文件中只定义一次的实体,比如类定义、const 和 constexpr 变量。
预处理器(preprocessor)概述
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
#endif
预处理变量无视C++语言中关于作用域的规则!
为避免与程序中的其他实体发生冲突,通常将预处理变量的名字全部大写。
学习目录:【C++ primer】目录