概括
2.1. 基本内置类型、算术类型以及带(无)符号类型;类型转换,无符号类型的类型转换问题;字面值常量,转义序列;
2.2. 变量的定义;变量的初始化与默认初始化问题;变量的声明、定义以及作用域;标识符的命名规范:用户自定义的标识符中不能连续出现两个下划线,也不能以下划线紧连大写字母开头,定义在函数体外的标识符不能以下划线开头;
2.3. 引用和指针;复合类型的声明的理解;
2.4. 限定符const
对变量、引用和指针的作用;顶层const
和底层const
;限定符constexpr
;
2.5. 类型别名的理解;auto
类型说明符;decltype
类型指示符以及与引用的关系;
2.6. 类的定义,类内初始化;预处理;
基本内置类型的关系
基本内置类型包括:
无符号类型的类型转换问题
当赋给无符号类型一个超出它表示范围的值,或者表达式同时含有有符号类型和无符号类型时,都会出现无符号类型的类型转换问题。针对这两种情况,它们处理的目标是一致的:使得最终的结果为无符号类型的值,同时该介于0和该无符号类型表示数值的总数(我们用T表示)之间。因此,这两种情况处理的本质也是相同的:
首先将有符号类型的值转换为无符号类型。这个有符号的类型可以理解为同时含有带符号类型和无符号类型表达式中的带符号类型的值,也可以理解为赋给无符号类型的一个负值;其方法为:将该有符号类型的数值加上T的整数倍,并保证其最后的数值介于0和T之间;
然后再进行赋值操作和无符号类型的算术运算即可;
无关主题的说明:类型char
实际上表现为char
还是unsigned char
取决于实际使用的编译器,因此,变量定义为类型char
时要额外小心些。
变量的初始化与默认初始化问题
初始化和赋值是两个概念
初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。
初始化的方法
根据C++ Primer第2章以及第3章的内容,我们可以把初始化的方法分为4种:默认初始化、拷贝初始化、直接初始化和列表初始化(该初始化方法为C++11新标准的一部分)。
默认初始化:定义变量时并未指定初值,但是并不是所有变量都可以默认初始化,比如:引用类型、auto
类型都必须指定初值。示例:
int ival; //在任一函数外默认初始化为0;
string str; //默认初始化为空串;
拷贝初始化:使用等号(=
)初始化一个变量称为拷贝初始化。示例:
int ival = 1;
string str = "hiya";
直接初始化:与拷贝初始化相反,即不使用等号(=
)进行初始化。示例:
int ival(0);
string str("hiya");
列表初始化:使用花括号来初始化变量的方法,该方法为C++11新标准的一部分。示例:
int ival = {0};
int ival{0};
vector<string> vstr{"a","an","the"};
初始化方法的区别
在大多数情况下这些初始化方法可以相互等价地使用,不过也并非一直如此。目前已经介绍过的两种情况是:其一,使用拷贝初始化时只能提供一个初始值;其二,如果提供的是类内初始值,则只能使用拷贝初始化或使用花括号的形式初始化。第三种特殊的要求是,如果提供的是初始元素值的列表,则只能把初始值放在花括号里进行列表初始化,而不能放在圆括号里。
默认初始化的问题
如果定义变量时未指定初值,那么将执行默认初始化,默认值到底是什么由变量类型决定,同时定义变量的位置也会对此有影响。
内置类型的变量未被显示初始化,那么它的值由定义的位置决定:
内置类型变量{在任一函数外,默认初始化为0在任一函数(包括main函数)内,则不被初始化,此时不能拷贝和访问 属于每个类的对象则由类决定其初始化方式,绝大多数类都支持无须显式初始化而定义对象,这样的类提供了一个合适的默认值。
指针的4种状态
- 指向一个对象
- 指向紧邻对象所占空间的下一个位置
- 空指针,意味着指针没有指向任何对象
- 无效指针,也就是上述情况之外的其他值
复合类型的声明与复杂数组的声明的理解
&与*的多重含义
在声明语句中,&与*用于组成符合类型;
在表达式中,&与*分别表示取地址符和解引用操作;
复合类型的声明
复合类型的说明采用从右向左的方式进行理解,举例说明:
int *&r = p;
从变量r
往左阅读,首先紧邻的是&
,因此变量r
为引用类型;剩下的int *
则说明该是对指向int
指针的引用。
const是对给定类型的修饰(类型别名的理解)
在类型别名下,不能简单进行替换做以理解,举例说明:
typedef char *pstring;
const pstring cstr = 0; //cstr是指向char的常量指针
const pstring *ps; //ps是一个指针,它的对象是指向char的常量指针(修饰了紧邻的类型而不是ps)
在上述第二句中,不能将pstring
替换为char *
进行理解。
复杂数组的声明
复杂数组的声明采用由内到外的方式进行理解,举例说明:
int (*Parray)[10] = &arr; //Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr; //arrRef引用一个含有10个整数的数组
const对引用和指针的影响
const对引用的影响
引用类型必须初始化;同时引用的类型必须与其所引用的对象的类型一致;而且引用只能绑定到对象上,而不能与字面值或某个表达式的计算结果绑定在一起。但是当存在const
时,出现例外情况:在初始化常量引用时,允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。
同时,允许对常量的引用绑定到非常量上。
const对指针的影响
类似地,允许指向常量的指针指向一个非常量对象,举例如下:
const int ival = 1;
const int *iptr = &ival;
int val = 2;
iptr = &val; //允许这种情况存在
但是,和对const
的引用一样,均不允许通过对const
的引用或者指向常量的指针修改其绑定或指向的对象。
constexpr对指针的作用
限定符constexpr
仅对指针有效,与指针所指的对象无关,举例如下:
const int *p = nullptr; //p是一个指向整型常量的指针
constexpr int *q = nullptr; //q是一个指向整数的常量指针
decltype对引用的作用
引用从来都是作为其所指对象的同义词出现,但是在decltype
是个例外,举例说明:
const int ci = 0, &cj = ci;
decltype(cj) y = x; //y的类型是const int &且绑定在x上,而不是const int
如果表达式的内容是解引用操作,则decltype
将得到引用类型,举例说明:
int i = 42, *p = &i, &r = i;
decltype(*p) c; //错误:c是int &,必须初始化