基本内置类型
C++包括算数类型和空类型(void)两种基本数据类型。其中算数类型包含字符、整形数、浮点数和布尔值。
一个char的大小和一个机器字节一样。
不要在算数表达式中使用char或bool,因为根据机器的不同,char可能为带符号或不带符号。当需要使用不大的整数时明确指定signed char或unsigned char。
执行浮点数运算时选用double,因为通常float精度不够。
当把非布尔类型的值赋值给布尔型时,为0则转换为false否则为true。当把布尔类型的值赋值给非布尔时,false转换为0,true转换为1。
当把浮点数赋值给整数时,将进行近似处理。
禁止混用带符号类型和无符号类型。
每个字面值常量都对应一种数据类型。
20,024,0x14分别为十进制,八进制和十六进制的整数字面值。3.14,3.14e0位浮点数的字面值,其中e表示科学计数法的指数。默认浮点型字面值是一个double。
单引号括起来的字符称为char型字面值。双引号括起来的一个或多个字符构成字符串型字面值。编译器或自动在字符串末尾添加空字符' '。多个字符串字面值紧由空格分割时会进行自动连接。
当程序中需要使用保留字或不可打印的字符时需要使用转义序列,可以通过x后跟多个十六进制或后跟多个八进制表示转义序列。可以像使用普通字符那样使用转义序列,如“Hello ”或cout<<' '。八进制时反斜杠后面只有3个数字属于转义序列,十六进制时表示反斜杠后面的所有数字都属于转移序列。
通过添加字面值前缀和后缀,可以指定字面值的类型。前缀u=Unicode16,U=Unicode32,L表示宽字符,u8,表示utf8编码,只用于字符串字面值。
true和false是布尔类型的字面值。nullptr是指针字面值。
变量
变量提供一个具体名称可供程序操作的存储空间,变量的数据类型决定内存空间的大小和布局方式。
类型说明符后跟一个或多个变量名列表,定义变量时可以赋初始值。通过字面值初始化std::string类型对象 std::string book("Primer c++")。用于初始化变量的值可以是任意复杂的表达式,变量的名字随着定义马上就可以使用了,因此可以使用先定义的变量值去初始化后定义的变量值,如double price=10,discount=price*0.16。
初始化与赋值是不同的操作,初始化是创建变量时赋予初始值。
初始化的多种形式:int i=0,i={0},i{0},i(0)。如果使用列表初始化编译器会自动进行溢出检查。
定义变量时如果没有指定初始值,则变量被默认初始化。如果是内置类型变量,定义在函数之外的初始化为0,但是定义在函数体内部的内置类型变量将不被初始化。类类型的初始化方式由类本身决定。
建议确保初始化每个内置类型的变量。
C++语言支持分离式编译机制,如果某个文件想使用其他文件定义的变量,必须先声明变量,变量可以在多处声明但只能在一处使用。如果想声明而不定义变量,只需在变量前添加extern关键字而不要初始化,在函数体内部如果尝试初始化一个由extern关键字声明的变量将引发错误。如果要在多个文件中使用同一个变量,则一定要将声明与定义分开。
自定义标识符不能连续出现两个下划线,也不能以下划线紧邻大写字母开头,函数体外的标识符不能以下划线开头。
大多数作用域以花括号分割,名字的有效区域从声明语句开始到语句所在作用域结束。作用域可以嵌套,并且内部作用域中可以声明外层作用域中同名变量,这时会覆盖外层变量,可以使用域操作符引用外层作用域变量,如::globalVar。
复合类型
复合类型是指基于其他类型定义的类型,引用和指针属于复合类型。
一条声明语句由一个基本数据类型后跟声明符列表组成,每个声明符命名一个变量并指定该变量与基本数据类型的关系。
通过将声明符写成&var的形式来定义引用类型,定义引用时必须进行初始化,并且初始化完成后无法把引用重新绑定到另外一个对象。int var=8;int &refVar=var;对引用进行的操作都是对其绑定的对象的操作。引用类型必须与要绑定的对象类型匹配,并且引用类型不能绑定到字面值和某个表达式。实际上引用是某个对象的别名,引用不是对象。
指针是指向另外一种类型的复合类型,通过将声明符写成*var的形式来定义指针,指针保存某个对象的地址,给指针赋值时可以使用取地址符&。指针类型要与它指向的对象严格匹配。指针的值应属于下列四种状态之一,指向一个对象,指向紧邻对象所占空间的下一个位置,空指针,无效指针。试图拷贝或以其他方式访问无效指针都将引发错误。如果指针指向了某个对象,则允许使用解引用符(*)来访问该对象。
定义空指针的方式,int *p1=nullptr, *p2=0, nullptr是一种特殊的字面值,可以转换成任何其他的指针类型。尽量避免使用NULL初始化空指针。初始化指针或使用0初始化。
任何非0指针对应的条件值都是true。对于两个类型相同的合法指针,如果他们指向同一个对象,则应用相等操作符时返回true,这实际上比较的是指针的内容,即如果都为空指针则也为true。
void*是一种特殊的指针类型,可用于存放任意类型的地址。void*指针能做的事比较有限,和别的指针比较,作为函数的输入与输出,赋值给另外一个void*指针。
从右向左阅读一个复杂的类型声明,离变量最近的那个符号说明变量的类型,如int *&var,var为引用,引用的是int指针类型。
const限定符
用const关键字对变量类型加以修饰,可使变量不能被修改,如 const int bufferSize=1024。const对象一但创建后不可修改,所以const必须在创建时初始化,一如既往的可以使用任何表达式初始化。
编译器编译时把用到const对象的地方全部使用其值替换,因此编译器必须知道const变量的初始值,所以默认情况下const对象被设定为仅在文件内有效。如果想在文件间共享const变量,在变量定义和声明语句中都添加extern关键字。
可以把引用绑定到const对象上,称之为对常量的引用。不能通过引用改变被引用的常量,也不能让一个非常量引用对象指向一个常量引用。const int i=1024; const int &r=i;r=42;//错误,不能改变常量,int &r2=r;//错误,非常量引用绑定到常量。
初始化常量引用时允许用任意表达式初始化作为初始值,在转换过程中或引用一个字面值时,编译器会自动生成一个临时量,这时引用引用的是临时量。
const引用可能并非引用一个const对象,如int i=2;const int &r=i;这时通过引用不能修改i,但通过其他途径可以。
要想存放常量对象的地址,只能使用指向常量的指针,但是允许指向常量的指针引用非常量对象。理所当然不能通过指向常量的指针修改引用的对象。
const指针,指针是对象,因此可以把指针本身声明为常量,常量指针必须初始化,初始化完成后其值不能被修改。把*放到const关键字之前说明指针是一个常量。如int *const prt=&i;常量指针不变的是指针本身而不是指针指向的那个对象。
弄清楚声明的含义最行之有效的方法是从右向左阅读,离变量最近的符号首先说明变量的类型。
用名词顶层const表示指针本身是一个常量,用名词底层const表示指针指向的对象是一个常量。顶层const可以表示任意的对象是常量,底层const则与指针和引用等复合类型相关。一般来说非常量可以转换成常量,反之则不行。
常量表达式是指值不会改变并且在编译过程中就能计算结果的表达式,一个表达式是不是常量表达式由它的类型和初始值共同决定。C++11标准规定允许将变量声明为constexpr类型以便由编译器来验证变量是不是一个常量表达式,如果不是将会报错。声明为constexpr的变量一定是一个常量,并且只能由常量表达式进行初始化。
算数类型,引用和指针属于字面值类型。自定义类,io库string等类型不属于字面值类型。只有字面值类型能定义成constexpr。
在constexpr声明中如果定义了指针,constexpr仅对指针有效,与指针指向的对象无关。
处理类型
类型别名是一个名字,它是某种类型的同义词。传统的方法是使用关键字typedef double wages;wages是double的同义词。typedef wages base,*p;base是wages的同义词,p是double*的同义词。新标准规定了一种新的声明别名的方式,using SI=Sales_item;
如果某个类型别名指代的是复合类型或常量,那么把它用到声明语句中就会产生意想不到的后果。在理解带有别名的声明语句时,不要尝试把别名替换成它本来的样子。
auto定义的变量必须有初始值,编译器自动根据赋值表达式推断变量的类型。编译器会根据引用的对象的类型来推荐类型,auto一般会忽略掉顶层const,同时保留底层const,如果期望auto推断出顶层const,则必须明确指出,如const auto i=0;
C++ 11新标准引入另一个类型说明符,delctype,它的作用是返回操作数的类型,只分析表达式的类型而不实际计算。使用delctype分析表达式时返回的类型会包括顶层const和引用。如果delctype使用的表达式不是一个变量,则delctype返回表达式结果的类型。
自定义数据结构
struct Sales_Data { std::string bookNo; unsigned units_solds = 0; double revenue = 0.0; };
类的定义由struct开始,后面紧跟类名和类体,类体由花括号包围,并形成新的作用域,类体可以为空。可以在花括号后面紧跟变量名,后面的分好必不可少。可以通过点操作符引用对象的成员。
类通常被定义在头文件中,并且类的名字应该与文件的名字一致。头文件通常包含那些只能被定义一次的实体。有必要在书写头文件时做必要的处理以防止头文件被多次包含时也能正确编译。头文件保护符依赖于预处理变量,预处理变量有两种状态,未定义和已定义。#define把一个名字设置为预处理变量,#ifdef当且仅当预处理变量已经定义时为true,#ifndef相反。一旦检查结果为真则执行后续代码知道遇到#endif。防止重复包含的代码示例:
#ifndef SALES_DATA_H #define SALES_DATA_H #include<string> struct Sales_Data { std::string bookNo; unsigned units_solds = 0; double revenue = 0.0; }; #endif
整个程序中的预处理变量包括头文件保护符必须唯一。一般做法是基于头文件名称构建预处理变量。