本身就一定基础的读者我想变量常量这些概念应该已经不是问题了。但是本章还是有几个重点,需要特别留意一下的:
1.初始化和赋值是不同的操作
2.任何非0值都是true
3.使用新标准列表初始化,在有丢失精度的可能时,会报错。
4.引用一旦绑定对象就不可再绑定到其他对象,因此也必须初始化
5.const引用初始化时可以赋予字面常量值,只要可以转换为相应的类型
6.顶层const意味着指针本身是个常量,底层const意味着指针所指向的对象是常量
7.声明成constexpr的变量是一个常量。
到了第二章,其实才算是真正的开始。也就是本书的第一部分C++基础。
第一章可以说就是第一部分的一个简单的梗概。
从第二章开始,对C++的学习步入正轨,从最基础的开始学起,也就是 变量。
而说到变量就不得不说类型。
第二章讨论的主要就是如下几点:
1.什么是基本类型,什么是复合类型?
2.什么是变量,什么是常量?
3.什么是类型别名
4.定义数据结构(struct)
我认为本章讨论常量的部分,对后面的学习影响很大,如果没有理解清楚const的复合类型的一些细节问题,会对后面函数和类的部分的学习造成影响。
基本内置类型
C++中定义了一套 包括 算术类型 和 空类型 在内的 基础类型。
而算术类型分为 整型 和 浮点型
这些类型的尺寸不同,也就是所占bit数不同。
根据现在机器的性能,整型中基本用int可以解决大多数需求。
无符号和有符号
这里没什么好说的,值得注意的是字符型被分为三种,char,signed char , unsigned char。
另外unsigned int 可以缩写为 unsigned
类型转换
需要记住的是,整型转布尔型。只有0才是false。不管是负数还是正数都是true。只有当值为0的时候才是false。
字面值常量
字面的意思,但是字面值常量的形式和值决定了它的数据类型。
如,我们可以将字面值写成十进制,八进制或十六进制。
若字面值是八进制则加前缀:0
若字面值是十六进制则加前缀:0x
也可以通过加后缀来表示类型,在书中表2.2可以看到说明。
变量
当对象创建的时候获得一个特定的值,我们说这个对象被 初始化 了。
在C++中初始化和赋值是两个不同的操作。
初始化:创建变量的时候赋予一个初始值。
赋值:将当前值擦除,并以一个新值代替。
列表初始化
这个是C++11的新标准。
对于使用内置类型的变量时,如果使用列表初始化,在有可能损失精度的情况下,编译器会报错。
默认初始化
如果定义变量的时候没有初始化,那么这个变量会被默认初始化。
但是这个值是什么是不一定的。
内置类型:由定义的位置而定,全局变量将初始化为0。
未被初始化的变量的值,是未定义的。拷贝或访问一个未定义的变量会产生可怕的后果。
每个类各自决定初始化的方式。如果一个类要求每个对象都要初始化,则不初始化会产生错误。
变量声明和定义的关系
声明:让这个名字被程序所知,如果一个文件想使用别的文件定义的名字就必须包含对那个名字的生命;
定义:书中说,定义负责创建与名字关联的实体,也就是声明之后还为变量申请了空间和赋值。
如果是要声明而非定义,则可加关键字 extern 并且不要显式的初始化变量。
如果初始化了则会变成定义。
作用域
简单来说
越外面的影响范围越大,内层作用域可以使用外层作用域的变量,也可覆盖外层变量。
复合类型
可以说是最重要的内容了,也就是指针和引用。
复合类型之所以叫做复合的,是因为它是由一个基本类型加一个说明符组成的(说明符可以是多个)
引用
引用,其实就是一个对象的别名。并且需要注意的是,引用一旦绑定到一个对象身上就不可以再绑定到其他对象上。也由于这个原因, 引用必须初始化。
指针
类似于引用可以对对象进行间接的访问,但是指针本身是一个对象而引用不是,并且指针的生命周期内可以指向不同的对象不像引用终身绑定。最后指针并不强制要求初始化。
指针本身存放的是对象的地址,所以在对指针本身赋值的时候需要使用 取地址符&。
如果想访问指针所指向的对象则应该使用 解引用* 符。
void*指针
这种类型的指针可以存放任意对象的地址。
const限定符
我认为是个重点,里面的细节如果没有理顺的话对后面的章节会造成很大的影响。
const限定符实际上就是声明常量用的,代替了预处理。
被加上const关键字的定义就会变为常量,任何企图修改的行为都会引发错误。
初始化和const
实际上const也能完成许多非const对象所能完成的操作,限制是只能在const对象上执行不改变其内容的操作。
比如:const int 也能想 普通 int 一样参与运算。
使用const对象去初始化也是没问题的,利用一个对象去初始化另一个对象,它们是不是const都无关紧要。
因为,拷贝一个对象的值并不会改变它。
默认情况下,const对象只在当前文件内有效。
如果想在多个文件下共享,则无论是声明还是定义都加上extern就行了。
const的引用
“对常量的引用”
在初始化常量引用的时候,允许用任意表达式作为初始值,只要改表达式的结果能够转换成引用的类型即可。并且虽然常量对象只能被对常量的引用所引用,但是非常量对象也可以被对常量的引用所引用。
虽然对const的引用有可能是一个非const对象,但依然不能通过const引用修改非const对象的值。
指针和const
与引用类似,对const的指针可以指向常量或非常量。并且无论指向的是常量还是非常量,都不可以通过对const的指针来修改其值。
const指针
由于指针是个对象,而引用不是对象。
所以允许把指针本身定位常量。
常量指针必须初始化,并且初始化后将不能改变(因为指针本身即是常量)
将*放在const前面即可声明常量指针。
***需要注意的是,常量指针仅仅意味着指针本身是常量,但并不意味着指针所指向的对象是个常量。
所以能不能改变所指向的对象的值,完全依赖于所指对象的类型。
顶层const
由于指针本身是一个对象,并且可以指向另一个对象。
所以指针本身是不是常量以及所指的对象是不是常量是两个相互独立的问题。
顶层const表示 指针本身是个常量
底层const表示 指针所指的对象是个常量
顶层const可以表示任意的对象是常量,对任何数据类型都适用。
而底层const,则与指针和引用等复合类型的基本类型部分有关。
指针既可以是顶层const也可以是底层const。
int i = 0
int *const p1 = &i; 顶层
const int ci = 24 ; 顶层
const int *p2 = &ci ; 底层
const int *const p3 = p2 ; 既有底层,又有顶层
i = ci 正确,ci是顶层const 对初始化没有影响
p2 = p3 正确,
p2 是一个指向const int型的指针,而p3是一个指向const int 型的 常量指针。
p2 和 P3 所指向的类型相同,而p3的顶层部分对赋值没有影响。
int *p = p3 错误,P3包含底层const的定义,p没有。意味着P3不可以改变所指向对象的值。P是可以的。因此错误
p2 = p3 ; 正确, p2 和 P3 都是底层const,这两个变量都不能改变所指对象的值,然而p3本身是个常量指针对赋值没有影响。
p2 = &i ; 正确, 尽管P2是一个指向const的指针,但仍然可以指向非const对象。只不过不可以通过P2修改i的值罢了。
int &r = ci; 错误, ci 是一个常量。普通的引用不能绑定到常量上,除非是一个对const的引用。
const int &r2 = i; 正确,对常量的引用可以绑定到普通对象上。
constexpr 和 常量表达式
常量表达式指的是,值不会改变,在编译过程就已经得到计算结果的表达式。
constexpr变量
由于很难分辨一个初始值到底是不是常量表达式,所以新的C++标准允许将变量声明为constexpr。
这样做会由编译器来验证变量的值是不是常量表达式。
声明为constexpr的变量一定是一个常量。
字面值类型
算术类型,引用,指针,都属于字面值类型。
自定义的类,IO库,string类型则不属于自勉之类型。意味着不能被定义为constepxr。
constexpr指针初始值必须是nullptr或0.或是存储于某个固定地址中的对象(比如全局变量)(一般来说函数体内定义的变量都不是存在固定地址中的,因此constexpr指针不能指向这样的变量)
指针和constexpr
constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。
const int *p = nullptr; p是一个指向整型常量的指针
constexpr int *q = nullptr; q是一个指向整数的常量指针
关键在于constexpr把它所定义的对象置于顶层const
与常量指针类似,constexpr既可以指向常量也可以指向非常量
处理类型
到这里基本没有什么细节问题了,只是介绍一些语法和关键字。
类型别名
typedef double wages ; wages是double的同义词
using sages = double ; 同上
typedef char *pstring;
char pstring cstr = 0; cstr是一个指向char的常量指针
const pstring *ps; ps是一个指针,对象时指向char的常量指针
语句中用到pstring时,其基本数据类型是指针。
auto类型说明符
使用auto类型说明符,让编译器替我们分析表达式所属的类型。
也因此auto定义的变量必须初始化,
复合类型,常量和auto
编译器推断出来的auto类型并不一定和出初始值的类型完全一样。
比如使用引用其实是使用引用所指的对象,在初始化时,真正参与初始化的是引用对象的值。此时编译器以引用对象的类型作为auto的类型。
其次,auto一般会忽略顶层const,const则会保留。
如果希望auto类型是一个顶层const则需要明确指出:
const auto f = ci;
decltype类型指示符
decltype(f()) sum = x; sum的类型就是f()的返回类型
与auto不同,如果decltpye使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用)
引用在哪都是作为所指对象的同义词出现,只有在decltype这里是例外。