今天是C++重新学习的第二天,因为是周末加上第一章内容比较少,两天就看完了前两章。
第二章的重点是指针、引用和const等,这些都是很令人头秃的,下面是第二章的学习笔记。
第二章 变量和基本类型
2.1 基本内置类型
C++定义了一套包括算术类型(arithmetic type)和空类型(void)在内的基本数据类型
2.1.1 算术类型
算术类型包括整型(包括字符型和布尔型)和浮点型
类型 | 含义 | 最小尺寸(bit) |
---|---|---|
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位有效数字 |
C++语言规定一个int至少和一个short一样大,一个long至少和一个int一样大,一个long long至少和一个long一样大。其中,数据类型long long是在C++11中新定义的
2.1.2 类型转换
程序应该尽量避免依赖于实现环境的行为。如果我们把int的尺寸看成是一个确定不变的已知值,那么这样的程序就称作不可移植的(nonportable)。当程序移植到别的机器上后,依赖于实现环境的程序就可能发送错误
当一个算术表达式中既有无符号数又有int值时,那个int值就会转换成无符号数
2.1.3 字面值常量
一个形如42的值被称作字面值常量(literal),这样的值一望而知。每个字面值常量都对应一种数据类型,字面值常量的形式和值决定了它的数据类型
默认情况下,十进制字面值是带符号数,八进制和十六进制字面值既可能是带符号的也可能是无符号的。十进制字面值的类型是int、long、long long中尺寸最小的那个(例如,三者当中最小的是int),当然前提是这种类型要能容纳下当前值。八进制和十六进制字面值的类型是能容纳其数值的int、unsigned int、long、unsigned long 、long long和unsigned long long中的尺寸最小者。如果一个字面值连与之关联的最大的数据类型都放不下,将产生错误。类型short没有对应的字面值。
尽管整型字面值可以存储在带符号数据类型中,但严格来说,十进制字面值不会是负数。如果我们使用了一个形如-42的负十进制字面值,那个符号并不在字面值之内,它的作用仅仅是对字面值取负值而已
指定字面值的类型
前缀 | 含义 | 类型 |
---|---|---|
u | Unicode 16字符 | char16_t |
U | Unicode 32字符 | char32_t |
L | 宽字符 | wchar_t |
u8 | UTF-8(仅用于字符串字面常量) | char |
后缀 | 最小匹配类型 |
---|---|
u or U | unsigned |
l or L | long |
ll or LL | long long |
后缀 | 类型 |
---|---|
f or F | float |
l or L | long double |
2.2 变量
2.2.1 变量定义
在C++中,变量(variable)和对象(object)一般可以互换使用
对象是指一块能存储数据并具有某种类型的内存空间
列表初始化
int units_sold{0};
int units_sold(0);
// 当用内置类型的变量时,这种初始化形式有一个重要特点:如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器会报错
默认初始化
- 定义于任何函数体之外的变量初始化为0
- 定义在函数体内部的内置类型变量将不被初始化
2.2.2 变量声明和定义的关系
声明(declaration)使得名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。而定义(definition)负责创建于名字相关的实体
如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显示地初始化变量
C++是一种静态类型(statically typed)语言,其含义是在编译阶段检查类型。其中,检查类型的过程称为类型检查(type checking)
2.2.3 标识符
用户自定义的标识符不能连续出现两个下划线,也不能以下划线紧连大写字母开头。此外,定义在函数体外的标识符不能以下划线开头。
2.2.4 作用域
可以使用::
加变量名访问全局变量
2.3 复合类型
2.3.1 引用(reference)
一般在初始化对象时,初始值会被拷贝到新建的对象中,然而定义引用时,程序把引用和它的初始值绑定(bind)在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。
引用并非对象,相反的,它至是为一个已经存在的对象所起的另外一个名字
引用类型在初始化是必须与初始化对象类型相同,而引用在初始化后的赋值则不再与引用相关,只是简单的变量赋值(习题2.16)
2.3.2 指针(pointer)
指针与引用的不同
- 指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。
- 指针无须在定义时赋初值。和其它内置类型一样,在块作用域内定义的指针如果没有被初始化,它将拥有一个不确定的值。
在新标准下。现在的C++程序最好使用nullptr,同时尽量避免使用NULL
void是一种特殊的指针类型,可用于存放任意对象的地址,以void的视角来看内存空间也就仅仅是内存空间,没办法访问内存空间中所存的对象。
2.3.3 理解复合类型的声明
引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用
2.4 const限定符
- 因为const对象一旦创建后其值就不能改变,所以const对象必须初始化(extern可以不用)。
- 默认状态下,const对象仅在文件中有效
- 编译器在编译过程中把用到该变量的地方都替换成对应的值。
extern
和const
某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下,我们不希望编译器为每个文件分别生成独立的变量。相反,我们想让这类const对象像其它(非常量)对象一样工作,也就是说,只在一个文件中定义const,而在其他多个文件中声明并使用它。
解决的方法是,对于const变量不管声明还是定义都添加extern关键字,这样只需定义一次就可以了。
// file_1.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
// file_1.h头文件
extern const int bufSize; // 与file_1.cc中定义的是同一个
2.4.1 const的引用
在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量对象、字面值,甚至是一个一般表达式
允许将const int &绑定到一个普通int对象上,不允许将int&绑定到一个const int上
“一种例外”
double dval = 3.14;
const int &ri == dval;
// ||
// 编译器转换
// \/
double dval = 3.14;
const int temp = dval;
const int &ri = temp;
ri并没有绑定在dval上,而是绑定在temp上,temp只是一个临时变量,因为大家基本上不会想把引用绑定到临时量上,因此C++语言也就把这种行为归为非法。
对const的引用可能引用一个并非const的对象
int i = 42;
int &r1 = i;
const int &r2 = i;
r1 = 0; // 合法
r2 = 0; // 非法
2.4.2 指针和const
pointer to const & const pointer
常量指针:不能改变其所指对象的值
指针常量:固定指向,无法更改指向的对象
2.4.3 顶层const
顶层const(top-level const) 表示指针本身是个常量,而用名词 底层const(low-level const) 表示指的对象是常量
更一般的,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等。底层const则与指针和引用等复合类型的基本类型部分有关。
当执行对象的拷贝操作时,拷入和拷出的对象必须有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行。
2.4.4 constexpr和常量表达式
**常量表达式(const expression)**是指值不会改变并且在编译过程就能得到计算结果的表达式。
const int max_files = 20; // 是常量表达式
const int limit = max_files + 1; // 是常量表达式
int staff_size = 27; // 不是,因为可变
const int sz = get_size(); // 不是,因为编译过程无法直接计算结果
C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,且必须用常量表达式初始化。
constexpr int mf = 20;
constexpr int limit = mf + 1;
constexpr int sz = size(); // 只有当size是一个constexpr函数时才是一条正确的声明语句
到现在为止接触的数据类型中,算术类型、引用和指针都属于字面值类型(literal type)
常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须是字面值类型
尽管指针和引用都能定义为constexpr,但它们的初始值却受到严格限制。一个constexpr指针的初始值必须是nullptr或者0,或者存储于某个固定地址中的对象。
函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量。相反的,定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针。
指针和constexpr
在constexpr声明中定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。
const int *p = nullptr; // pointer to const
constexpr int *p = nullptr; // const pointer
2.5 处理类型
2.5.1 类型别名
// typedef
typedef double wages; // wages是double的同义词
typedef wages base, *p; // base是double的同义词,p是double*的同义词
// using
using SI = Sales_item; // SI是sales_item的同义词
指针、常量和类型别名
typedef char *pstring;
const pstring cstr = 0; // cstr是指向char的常量指针
const pstring *ps; // ps是一个指针,它的对象是指向char的常量指针
2.5.2 auto类型说明符
auto 让编译器通过初始值来推算变量的类型
使用auto也能在一条语句中声明多个变量。因为一条声明语句只能有一个数据类型,所以该语句中所有变量的初始基本数据类型必须都一样。
auto 一般会忽略掉顶层const,同时底层const则会保留下来
2.5.3 decltype 类型指示符
decltype的作用是返回操作数的数据类型
decltype(f()) sum = x; // sum的类型就是f()的返回类型
decltype和引用
如果表达式内容是解引用操作,则decltype将得到解引用类型。
int i = 42, *p = &i;
decltype(*p) c; // 错误,c是int&,必须初始化
decltype((variable))的结果永远是引用,而decltype(variable)结果只要当variable本身就是一个引用时才是引用
2.6 其它
预处理器概述
预处理器变量无视C++语言中关于作用域的规则。
新手发博,恳请各位不吝赐教,不胜感激!
系列链接:
第一章 开始