大多数编程语言通过两种方式来进一步补充其基本特征:
- 1、赋予程序员自定义数据类型的权利,从而实现对语言的扩展
- 2、将一些有用的功能封装成库函数提供给程序员
C++是一种静态数据类型语言
,它的类型检查发生在编译时
数据类型是程序的基础:它告诉我们数据的意义以及我们能在数据上执行的操作
一、基本内置类型
基本数据类型:字符型、整型、浮点型、布尔型、空类型
算术类型的尺寸在不同的机器上有所差别
,C++标准规定各个算术类型的最小尺寸,同时允许编译器赋予这些类型更大的尺寸
char
的空间应确保可以保存机器基本字符集
中任意字符对应的数字值
wchar_t
可以存放机器最大扩展字符集
中的任意一个字符对应的数字值
char16_t
和char32_t
为Unicode字符集服务
类型之间存在大小关系,但没有固定的大小(为了程序的可移植性)
C++标准指定了一个浮点数有效位数
的最小值,然而大多数编译器都实现了更高的精度
除了布尔型和扩展的字符型之外,其他整型可以划分为带符号的signed
和无符号的unsigned
char会表现为带符号char或不带符号的char,具体是由编译器决定的
如果表达式里既有带符号类型又有无符号类型
,当带符号类型取负时会出现异常结果,这是因为带符号数会自动转换成无符号数(没事找事)
每个字面值常量
都对应一种数据类型。
整型字面值常量
具体的数据类型由它的值和符号决定。默认情况下,十进制字面值是带符号数,八进制和十六进制字面值既可能是带符号也可能是无符号。前提是要放的下。类型short没有对应的字面值。我们可以通过后缀显式指定类型
字符串字面值常量
的类型实际上是由字符常量构成的数组(C风格字符串,const char *
)
转义字符:有好几种表示方式
指定字面值
的类型p37
二、变量
初始化不是赋值
,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代
要多多使用列表初始化
,C++11才支持(使用大括号来进行初始化)
如果内置类型变量
未被显式初始化,那么定义在任何函数体之外的变量被初始化为0。函数体内部的不会被初始化,这样的值是未定义的
类的对象如果没有显式初始化
,则其值由类的定义确定(可由类内初始值
或默认构造函数
来决定)
建议显式初始化
每个内置类型的变量
一个文件如果想要使用别的文件定义的名字则必须包含对那个名字的声明(extern
)。
定义负责创建与名字关联的实体
变量声明
只规定了变量的类型和名字;变量定义
同时还申请了存储空间,也可能会为变量赋一个初始值
如果仅仅是想声明一个变量,就使用extern关键字(不会为该变量分配内存)
在函数内部试图初始化(其实就是赋值)一个由extern标记的变量,会引发错误
变量只能定义一次,可以被多次声明
(为了文件间共享代码)
C++的标识符由字母、数字和下划线
组成,其中必须以字母或下划线开头。标识符的长度没有限制(其实是有限制的),但是对大小写字母敏感。
C++为标准库保留一些名字。用户自定义的标识符中不能连续出现两个下划线,也不能以下划线紧连大写字母开头。此外,定义在函数体外的标识符不能以下划线开头。
标识符要体现实际含义,变量名一般用小写字母(采用匈牙利命名法
)。用户自定义的类名一般以大写字母开头
C++中大多数作用域都以花括号分隔。名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束
当第一次使用变量时再定义它,C++是支持的
作用域中一旦声明了某个名字,内层作用域中都能访问该名字。同时,允许在内层作用域重新定义外层作用域已有的名字
三、复合类型
引用:
- 1、引用必须初始化
- 2、为引用赋值,实际上是把值赋给了引用绑定的对象
- 3、引用本身不是一个对象,所以不能定义引用的引用
- 4、引用的类型都要和与之绑定的对象严格匹配(对常量的引用不一定)。而且,引用只能绑定在对象上,而不能绑定于字面值常量或某个表达式的计算结果之上(由两个例外p55、p534)
指针:
- 1、指针和内置类型一样,在块作用域内定义的指针如果没有被初始化,将拥有一个不确定的值
- 2、&取地址符
- 3、指针类型需要和它指向的类型严格匹配(由例外p56、p534)
- 4、*解引用运算符,
解引用运算符的运算结果是一个引用
- 5、解引用操作仅适用于那些确实指向了某个对象的有效指针
- 6、在声明语句中,&和*用于组成复合类型
- 7、nullptr是一种特殊类型的字面值,它可以被转换成任意其他的指针类型,C++11
- 8、
NULL
定义在cstdlib头文件中,值为0。使用%p
来输出,显示的是nil
- 9、预处理变量不属于命名空间std,它由预处理器负责管理???
- 10、不能把int变量直接赋给指针
- 11、建议初始化所有指针
- 12、赋值改变的是等号左侧对象
- 13、指针可以用在条件表达式中,只要内容非空,就为真
- 14、void是一种特殊的指针类型,可以存放任意对象的地址,不能直接操作void指针所指向的对象,因为我们并不知道这个对象到底是什么类型,也就无法判断可以进行什么操作,可以将其中的数据取出来,放入合适类型的变量中后,再进行相关的处理
复合类型的声明:一个声明语句中最前面的是基础数据类型
,然后是声明符(声明符包括类型修饰符
和标识符),注意理解
指针的引用
:注意声明的格式,离变量名最近的符号对变量的类型有最直接的影响(从右向左阅读)
四、const限定符
const对象
一旦创建后其值就不能再改变,所以const对象必须初始化
只能在const类型的对象上执行不改变其内容的操作(只读操作)
const对象在编译时有类似于宏替换的操作,为了更好用,默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量(这里所说的const变量应该是定义的全局const变量)
有一种const变量,它的初始值不是一个常量表达式(一定要不是一个常量表达式吗?),但又确实有必要在文件之间共享。解决办法是,对于const变量不管是声明还是定义都添加extern关键字。
???以上这句话不是很理解???
并不存在常量引用,由于C++语言并不允许随意改动引用所绑定的对象,所以从这层意义上理解所有的引用其实就是常量
引用类型必须与所绑定的对象类型保持一致,第一种例外情况是在初始化一个对常量的引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。所以允许一个对常量的引用绑定非常量对象、字面值,甚至是个一般表达式
对常量的引用可以绑定一个非常量对象,对常量的引用只是规定了程序不能通过该引用改变其绑定的对象(即使该对象是非常量),并没有规定程序不能通过其他途径改变其绑定的对象(如果该对象是非常量的话)
要想存放常量对象的指针,只能使用指向常量的指针
指针的类型必须与其所指对象的类型一致,但是有两个例外。第一种例外情况是允许令一个指向常量的指针指向一个非常量对象
和对常量的引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。所谓指向常量的指针仅仅是要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变
常量指针必须初始化(注意const关键字放在哪?????)
用顶层const表示指针本身是个常量,而用底层const表示指针所指的对象是一个常量(不一定)
底层const与指针和引用等复合类型的基本类型部分有关
当执行对象的拷贝操作时,常量是顶层const还是底层const区别明显。其中,顶层const不受什么影响。当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换为常量,反之则不行
以上这句话需要结合实际的例子来理解*
常量表达式(const expression)是指值不会改变并且在编译过程中就能得到计算结果的表达式。字面值属于常量表达式。用常量表达式初始化的const对象也是常量表达式
一个对象或表达式是不是常量表达式由它的“数据类型”和“初始值”共同决定
在复杂的程序中,很难分辨一个初始值到底是不是常量表达式,所以在C++11新标准中,允许将变量声明为constexpr类型以便由编译器来验证变量和其初始值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化(如果不满足要求,编译器会报错)。
一般来说,如果认定变量是一个常量表达式,那就把它声明为constexpr类型
可以用constexpr函数来初始化constexpr类型的变量,constexpr函数是一种足够简单使得在编译时就可以计算出结果
能够被声明为constexpr的类型是字面值类型。因为常量表达式的值需要在编译时就得到计算,所以对constexpr声明时用到的类型必须比较简单、显而易见。到目前为止,算术类型、引用和指针都属于字面值类型
constexpr的引用和指针的初始值受到严格限制。一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象的地址(一般是定义在函数体外的对象或者定义在函数体内的static对象)。函数体内定义的非static变量一般来说并非存放在固定地址中。constexpr引用对初始值的要求与constexpr指针类似
对于指针和引用来说,限定符constexpr仅对指针有效,与指针所指的对象无关。constexpr相当于直接为变量加上了顶层const
五、处理类型
声明类型别名有两种方法:一种是使用typedef(注意声明符中可以含有类型修饰符和标识符)、另一种是using语句
指针、常量和类型别名:注意不要进行“还原改写”来理解。要注意把变量的定义语句分为数据类型部分和声明符两个部分来理解p51
??????????上述所说的很重要,仔细思考??????????
在声明变量时,要清楚知道表达式的类型不是很容易,在C++11中可以使用auto关键字让编译器根据变量的初始值来推断变量的类型。所以auto定义的变量必须要有初始值
使用auto也能在一条语句中声明多个变量。因为一条语句只能有一个“基本数据类型”,所以该语句中所有变量的“初始基本数据类型”都必须一样。(在这样的语句中声明多个类型不相同的变量不现实的,因为你需要根据基本数据类型来增加类型修饰符,但是之所以用auto就是因为我不知道基础数据类型。所以说一般都是用来声明一个变量或者是多个相同类型的变量)
在使用auto时,推断出来的类型与初始值的类型并不完全一样,编译器会适当的改变结果类型使其更符合初始化规则(例如初始值是引用时,你懂我意思)。auto一般会忽略掉顶层const保留底层const。在auto语句中也可以使用类型修饰符
有时程序希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式初始化。那么我们可以用C++11提供的decltype关键字。它的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值。
decltype处理顶层const和引用的方式与auto有些许不同(decltype会保留顶层const和引用。如果是引用,注意一定要初始化)
引用从来都是作为所指对象的同义词出现的,只有在decltype处是一个例外
decltype和引用:decltype的运算对象可以是变量或者表达式。如果r是一个引用,decltype(r)得到的也是引用类型,使用decltype(r + 0)得到的是非引用类型。如果表达式的内容是解引用操作,那么decltype将得到引用类型。还有注意decltype操作数是否加括号有很大的区别,加了括号结果永远是引用,没加括号只有自身是引用时,结果才是引用
六、自定义数据结构
类内部定义的名字必须唯一,但是可以与类外部定义的名字重复
C++11规定,可以为数据成员提供类内初始值。创建对象时,类内初始值将用于初始化数据成员。在创建对象时,没有提供初始值的成员将被默认初始化
什么是默认初始化?
默认初始化就是使用类内初始值来初始化数据成员
类内初始值不能用圆括号提供,可以使用花括号和等号
可以使用struct和class关键字,区别在于默认权限不同
为了确保各个文件中类的定义一致,类通常被定义在头文件中,而且类所在头文件的名字应与类的名字一样
头文件通常包含那些只能被定义一次的实体。头文件也经常用到其他头文件的功能
有必要在书写头文件时作适当处理,使其遇到多次包含的情况也能够安全和正常的工作
头文件一旦改变,相关的源文件必须重新编译以获取更新过的声明
一旦#ifdef或#ifndef检查结果为真,则执行后续操作直至遇到#endif指令为止
使用#define定义预处理变量,预处理变量一般都是大写,取有意义的名字
预处理变量
预处理变量无视C++中关于作用域的规则,因为它们是预处理器管理的。建议只要是头文件就应该设置保护符,这是一种好习惯