Chapter 2. Variables and Basic Types
2.1 Primitive Built-in Types
2.1.1 Arithmetic Types
基本类型大小下限(节选):
int | 16 |
---|---|
long | 32 |
long long | 64 |
多种字符类型:char, wchar_t, char16_t, char32_t
long double的大小为96或128bits
与其他基本类型不同,char并不等同于signed char,char具体是signed还是unsigned依编译器而定
适用类型的一些规则:
- 不可能为负的值使用unsigned整数类型
- 用char表示小整数时,加上signed/unsigned来指定它的符号
2.1.2 Type Conversions
赋值时类型转换规则:
- 用非bool类型值给bool类型赋值时,如果为0,则布尔值为false,否则为true
- 将布尔值赋值给其他类型值时,如果为true,则值为1,否则值为0
- 将浮点数值赋给整数类型时,小数被丢弃
- 给unsigned类型赋值,超出范围,则对该值取模
- 给signed类型赋值,超出范围,undefined
如果一个表达式中同时出现了同样长度类型的unsigned类型值和signed类型值,signed类型值会被转化为unsigned类型值
for循环中不要使用unsigned类型值作为循环变量,否则可能无限循环
不要混合signed和unsigned类型值
2.1.3 Literals
literal: 字面量,常量
十进制常量大小匹配:int, long, long long
八进制和十六进制常量大小匹配:int, unsigned int, long, unsigned long, long long, unsigned long long
如果使用的常量超出了最大表示范围,则会报错
字符常量的前缀:
- u: Unicode 16 characters, char16_t
- U: Unicode 32 characters, char32_t
- L: Wide characters, wchar_t
- u8: utf-8(string literals only, char
整数常量不会是负值,因为负数前面的负号并不算作数的一部分,而是作为一个运算符
string literal: 字符串常量,就是引号中的字符串
两个用spaces/tabs/newlines分隔的字符串常量会被连接成同一个字符串常量
用8进制数表示字符,反斜杠后面最多跟3位数字,且只会解析反斜杠后的前3位数字
用16进制数表示字符,如果反斜杠后跟多位数字,则会解析成宽度更大的字符
nullptr是一个指针类型字面值
2.2 Variables
2.2.1 Variable Definitions
string类型:变长的字符序列
变量定义时,可以在同一条定义语句中,用左边的变量的值定义右边变量的值,如:
double a = 1.0, b = a;
C++中,初始化与赋值操作是有区别的
初始化:在变量被创建时给它一个初始值
赋值:抹掉变量的现有值并用一个新的值代替
C++中基本类型的初始化方法示例:
int a = 0;
int a = {0};
int a(0);
int a{0};
当可能发生精度损失时不能使用大括号进行初始化,数组也不可以
如果一个变量被定义但没有初始化,则它会被以默认的方式初始化
在函数体内的变量的默认初始化方式为undefined
一个未初始化的string变量,会被默认初始化为一个empty string
2.2.2 Variable Declarations and Definitions
std::表示使用的是定义在standard library中的对象
declaration(声明):当一个文件使用一个定义在其他地方的名字,需要声明
definition(定义):创建一个实体
声明确定变量的类型和名字
定义是一个声明,并且会为变量分配空间,有时会初始化
当一个声明不是一个定义时,需要在变量前加上extern关键字
所有带有显示初始化的声明都是定义。如果一个变量声明时带有extern,并且被初始化,则这是一个定义
在函数中,初始化一个extern变量时错误的
变量只能定义一次,可以声明很多次
2.2.3 Identifiers
identifiers: 名字
尽量不要使用以两条下划线开头,或者以一条下划线接一个大写字母开头的名字
在函数外部的名字尽量不要以一条下划线开头
2.2.4 Scope of a Name
最好在第一次使用一个变量的时候定义它
在outer scope中定义的变量,可以在inner scope中定义同名变量,两者为不同变量
"::"双冒号是scope operator,globe scope没有名字,当globe scope和函数中出现了重名变量,可以在变量名前加::来指定使用的是globe scope中的变量
2.3 Compound Types
引用和指针是compound types
2.3.1 References
引用变量必须初始化
引用变量不能更改引用对象
引用就是别名
不能用字面量初始化引用,也不能用表达范围更大的类型的表达式值初始化引用
2.3.2 Pointers
指针的类型和它指向的对象的类型必须匹配
指针值的几种情况:
- 指向一个对象
- 指向一个对象的结束位置
- NULL,表示没有指向对象
- 除以上几种以外,都为invalid pointers
invalid pointers会导致错误
三种将指针值设置为null的方式:
int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL;
nullptr是一个指针类型的字面值,它可以转换为任何指针类型
NULL的定义在cstdlib中,是一个预处理器变量,预处理器会将它替换成0
现代C++程序应当避免使用NULL而使用nullptr
将一个int的值赋给一个指针是非法的,但可以用0进行赋值
两个同类型的指针是可以用"==. !="相互比较的
void* 可以指向任意类型的对象的地址
不能使用void*来操作它指向的对象
2.3.3 Understanding Compound Type Declarations
因为引用不是一个对象,因此引用没有指针
对一个变量的声明,从右向左读,如int *&r
,表示r是 引用,引用的对象是一个int*
2.4 Const Qualifier
const变量必须被初始化,不能被赋值
编译器会自动将代码中的const变量换成它的值
const变量的范围是定义它的文件,除非是extern const变量,extern const变量在定义和声明时都要有extern
2.4.1 References to Const
const类型的引用在声明时前面也要加上const,如:const int &r = c;
const引用不能用来改变它引用的对象值
const引用,引用的对象可以是:普通变量/字面值/表达式,他们的类型可以和引用相同,也可以是能够自动转化成引用类型的类型(因为对于可以自动转化的类型,编译器会先创建一个临时const对象存放右边表达式的值,之后再让const引用引用这个临时对象)
如果右边变量的类型不是const引用的类型,且右边的变量不是常量,则改变右边变量的值,const引用的值不会改变,理由见上句括号
普通的引用不能用字面值初始化
2.4.2 Pointers and Const
pointer to const:
不能通过这种指针修改它指向的对象,它指向的对象可以是一个非const变量
声明方式:
const double pi = 3.14;
const double *cptr = π
指针本身的值可以改变
const pointer:
必须初始化,并且初始化过后它的值(即指向的地址)不能被改变
声明方式:
int errNumb = 0;
int *const curErr = &errNumb;
可以通过这个指针改变指向对象的值
2.4.3 Top-Level Const
对于指针:
top-level const: const pointer
low-level const: pointer to const
top-level const: 一个对象本身是const
low-level const: 一个compound type指向的类型为const
low level的compound type不能用来给普通的compound type赋值
如const int*
不能用来给int*
赋值
2.4.4 Constexpr and Constant Expressions
constant expression: 常量表达式,即在编译期可以求值的表达式
字面值是一个constant expression
一个用constant expression初始化的const对象也是一个constant expression
constexpr:用于告知编译器,其所修饰的表达式是常量表达式,以便编译器进行优化;编译器会对它是否真的是常量表达式进行检查,如果不是,则将运算留到运行时
constexpr隐含有const
literal和literal type有区别,literal指字面值,literal type指可在编译期求值
literal type: 算术类型、引用、指针和一些类
指针和引用是literal type,但constexpr中的指针只能以nullptr/某个地址固定的变量的地址初始化,引用也只能用地址固定的变量的地址初始化
当一个指针变量被声明为constexpr,则这个指针是一个const pointer
constexpr强制top-level const
2.5 Dealing with Types
2.5.1 Type Aliases
aliases: 别名
可以使用typedef关键字来为类型创建别名,如下:
typedef double wages;
typedef wages base, *p; //base is a synonym for double, p for double*
另一种定义别名的方式是:
using SI = Sales_item;
使用别名时注意const的位置,如果是一个指针的别名,则指针本身为一个对象,在用别名声明时句首加const,即表示这个对象不能修改。因此得到的是constant pointer而不是pointer to constant
2.5.2 The Auto Type Specifier
auto关键字让编译器来推断变量的类型
用auto关键字声明的变量必须显式初始化
可以在同一条auto声明语句中定义多个变量,但这些变量的基本类型必须相同
auto忽略top-level const,保留low-level const
如果我们希望推断出来的type拥有top-level const,则需要:
const auto f = ci;
也可以在变量前加"&"来声明引用
2.5.3 The Decltype Type Specifier
当我们希望用一个表达式的类型声明变量,却不想将变量初始化成表达式的值时,可以用decltype
decltype能够返回一个表达式的类型
decltype(f()) sum = x;
decltype与auto不同,它能够区分base type和它的引用类型
decltype(*p) c = &i;
*p的类型为引用类型,而不是普通类型
如果在decltype中给变量加括号,则会得到它的引用类型,即
decltype((type))
得到的总是引用类型
赋值表达式的类型:
赋值表达式的类型是左操作数的类型的引用类型
2.6 Defining Our Own Data Structures
2.6.1 Defining the Sales_data Type
在新标准中,我们可以为结构体的成员提供初始化,没有初始化的成员会被用默认方式初始化
不可以用圆括号初始化
2.6.2 Using the Sales_data Class
2.6.3 Writing Our Own Header Files
每当头文件被更改,使用这个头文件的代码都需要重新编译
preprocessors: 预处理器
当预处理器遇到"#include"时,它会用相应头文件的内容替换#include语句
preprocessor variables: 只有两种状态,defined和not defined
#define语句定义了preprocessor variables
#ifdef和#ifnedf用于判断一个preprocessor variable是否被定义
如果判断的结果为true,那么从#ifxxx到#endif中间的语句会被执行
header guard:
#ifnedf SALES_DATA_H
#define SALES_DATA_H
/* ... */
#endif
Chapter Summery
type的作用:定义了变量的存储空间大小,以及它能够进行的操作