2.1 基本内置类型
C++定义了一套包括算数类型(arithmetic type)和空类型(void)在内的基本数据类型,其中算数类型包括: 字符、整型、布尔值和浮点。
2.1.1 算数类型
算数类型分为2类整形(包括字符和布尔型)和浮点型。
算数类型的尺寸(该类型所占的比特数)在不同的机器上有所差别。C++标准列出了最小的尺寸最小值。
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位有效数字
一个char的空间应该确保可以存放机器基本字符集中的任意字符对应的数字值。一个char的大小和一个机器字节一样。
wchar_t用于确保可以存放机器最大扩展字符集中的任意一个字符。
字节:可寻址的最小内存块
字:存储的基本单元,计算机依次能直接处理的二进制数据,取决于CPU内部数据总线的位宽。
带符号类型和无符号类型
除去布尔和扩展的字符型,其他的整形都分为带符号的(signed)和无符号的(unsigned)两种。带符号的为正数、0、负数。类型 unsigned int 可以缩写为 unsigned。char可以分为 signed char char 和 unsigned char 。char 和signed char 不一样,具体表现为哪种由编译器决定。
2.1.2 类型转换
当程序在某处我们使用一种类型而其实对象为另外一种类型,程序会自动进行转换。
bool b = 42 //b为真
int i = b; //i 为1
i = 3.14 ; //i 为3
double pi = i ; //pi 为3.0,虽然cout 输出为3,但实际上为3.0
unsigned char c= -1 // 假设char占8位,那么c 为255
signed char c2 = 256; //假设char占8位,那么c为未定义
(1)非布尔型赋值给布尔型时,初始值0则结果为false,否则true;
(2)把布尔类型赋值给非布尔型,初始值为false则结果为0,true 为1;
(3)浮点数赋值给整数类型,结果将仅保留小数点之前部分。
(4)整数赋值给浮点类型,小数部分计为0.如果整数超过了浮点型的范围,精度损失。
(5)赋值无符号类型超过表示范围数,结果为初始值对无符号类型表示总数的取余。
例如 8比特 unsigned char 表示0-255,所以可以表示的范围为256,
unsigned char aa = -1 , 则aa 为255
unsigned char aa = 257,则aa 为1;
(6)给带符号类型赋值一个超出表示范围的数,结果为未定义。
2.含有无符号类型的表达式
当一个算数表达式中既有无符号数又有int值时,那个int值就会转换为无符号数。
unsigned u = 10;
int i = -42;
cout << i+i <<endl; //结果-84
cout<< i+u <<endl; //结果 如果int占32位,输出4294967264
当一个无符号数减去一个数时,需要确保结果不能是一个负值。
unsigned u1 = 42, u2 =10;
cout<< u2-u1<< endl; //结果为取模后的值
int a = -1; unsigned b = 1;
a*b = 4294967295 // 带符号数会自动转换为无符号数。
2.1.3 字面值常量
形如42的值被称为字面值常量(literal)。
整形和浮点型字面值
整形字面值可以写为十进制、八进制或十六进制。0开头八进制、以0x或者0X开头十六进制。
20/*十进制*/ 024/*八进制*/ 0x14 /*十六进制*/
十进制字面值类型是int ,long ,long中尺寸最小的那个。
八进制和十六进制是int,unsigned int ,long ,unsigend long ,long long 和unsigned long long
中尺寸最小的那个。
short没有对应的字面值。负数不在字面值之内,仅表示对字面值取负数。
浮点数的字面值表示为小数或者科学计数法表示。指数部分用E或者e表示。默认字面值为一个double
字符和字符串字面值
由单引号括起来的字符为char型字面值。双引号括起来的零个或者多个字符构成字符串型字面值。字符串型字面值实际上是由字符常量构成的数组(array)。编译器在每个字符串的结尾处添加一个空字符(‘\0’)"A"表示一个字符A和一个空字符。
转义序列
泛化的转义序列形式是\x后面跟着1个或者多个十六进制数字,或者\后面跟着1个、2个或者3个八进制数字。
\1234 八进制数字超过3个,只有前三个与\构成转义
\x12345 十六进制包括\x后面的所有数字。
指定字面值类型
前缀 含义 类型
u Unicode16字符 char16_t
U Unicode32字符 char32_t
L 宽字符 wchar_t
u8 UTF-8(仅用于字符串字面值) char
整形字面值
u或者U unsigned
l 或者L long
ll或者LL long long
浮点型字面值
f或者F float
l或者L long double
L'a' // wchar_t
u8"hi!" //utf8字面值用8位编码一个unicode字符
42ULL // unsigned long long
1E-3F //float
3.14159L //long double
字符在前面,数字的在后面,不能变
布尔字面值和指针字面值。true和false 是布尔字面值 nullptr是指针字面值。
2.2 变量
变量提供一个具体的、可供程序操作的存储空间,每个变量都有其数据类型,数据类型决定着变量所占空间的。大小和布局方式、该空间能存储的值的范围,以及可以参与的运算。
2.2.1变量的定义
形式:类型说明符后面跟着一个或者多个变量名组成的列表,变量名以逗号分隔,以分号结束。定义时还可以为变量赋值。
初始值
对象在创建时获得一个特定值,这个对象被初始化了。在同一条定义语句中,可以用先定义的变量去初始化后定义的其他变量。
初始化和赋值概念不一样。初始化时创建变量时赋予一个初始值。赋值是把对象当前的值擦除,然后用一个新值替代。
列表初始化
定义个int 型u,并初始化为0,以下四种形式都可以
int u = 0;
int u = {0} //列表初始化
int u{0}; //列表初始化
int u(0);
使用列表初始化且初始值存在丢失信息的风险,编译器报错。warning不影响运行。
long double aa = 3.14;
int b{aa};
int c(aa);
默认初始化
定义变量没有指定初始值,则变量被默认初始化。内置类型的变量未被显示初始化,它的值由定义位置决定。定义于任何函数体外的变量被初始化为0,定义于函数体内部的内置类型变量将不被初始化。包括main函数内也算函数内。
2.2.2变量声明和定义的关系
C++支持分离式编译机制,允许将程序分割为若干个文件,每个文件可被单独编译。为支持分离式编译,C++将声明和定义区分开来。声明使得名字为程序所知,一个文件如果想使用别处定义的名字则必须对那个名字声明。而定义负责创建与名字关联的实体。
声明规定了变量的类型和名字,定义还创建了空间,或者赋值一个默认值。
声明一个变量非定义需要在前面加extern,而且不能显式的初始化。
extern语句如果包含一个初始值就不在是声明,而是定义。
函数体内部,试图初始化一个由extern关键字标记的变量,引发错误。
多个文件使用同一个变量,必须声明和定义分开,定义的变量只能出现在一个文件中,用到该变量的文件必须对其进行声明,不能重复定义。
2.2.3标识符
标识符由字母、数字和下划线组成,标识符长度没有限制,但对大小写敏感。
1.用户自定义标识符不能连续出现两个下划线。
2.不能以下划线紧靠大写字母开头
3.定义在函数体外的不能以下划线开头。
2.2.4名字的作用域
每个名字都会指向一个特定的实体:变量、函数、类型。
名字的有效区域始于名字的声明,以声明语句所在的作用域末端作为结束。
作用域中声明了某个名字,嵌套的所有作用域都能访问该名字,同时允许在内层作用域中重新定义外层作用域已有的名字。
2.3 复核类型
一条声明语句由一个基本数据类型和紧随其后的一个声明符列表组成。每个声明符命名了一个变量并制定该变量与基本数据类型有关的某种类型。
2.3.1引用
引用为对象起了另外一个名字。&d的形式来定义引用类型。
int &ref2; //引用必须初始化。
引用将和他的初始值绑定在一起,所以引用必须初始化。引用并非对象,它只是为一个已经存在的对象所起的另外一个名字。
定义了一个引用后,对其进行的所有操作,都在与之绑定的对象上进行。为引用赋值,就是把值赋给引用绑定对象上。同理,以引用为初始值,就是以引用绑定的对象作为初始值。
因为引用不是对象,不能定义引用的引用。
引用的类型都必须与绑定的类型严格匹配。引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定在一起。
int &ref=10;//错误,引用对象不能为字面值
2.3.2 指针
指针(pointer)是指向另外一种类型的复核类型。
指针与引用不同。一是指针本身是一个对象,允许对指针进行赋值和拷贝,而且可以指向几个不同的对象。
二是指针无须在定义时赋值。在块作用域内,指针没有被初始化,也将拥有一个不确定的值。
定义指针的形式*d,在一条语句中定义几个指针每个指针都需要*
int* p1, p2; //p1是指针,p2不是
引用不是对象,不能定义指向引用的指针。
所有指针的类型,需要和他指向的对象严格匹配。
对解引用的结果赋值,就是对指针指向的对象赋值。
空指针
int *p = nullptr;
int *p2 = 0;
int *p3 = NULL;//需要引入cstdlib
把int变量直接赋值给指针是错误的操作,即使int变量的值恰好为0也不行。
int zero =0;
int *ptr = zero;//错误
两个类型相同的指针可以用 == 和!=进行判断,类型不同会报错。
void*是可以存放任意对象的地址。
别的任意类型的指针可以放在void*中,反之不行。
只能进行地址间的比较,作为函数的输入输出或者赋值给另外一个void*.
2.3.3理解复核类型的声明
int* p1,p2;// p1指向int的指针,p2是int
指向指针的引用
int i = 42;
int *p;
int *&r =p;//r是一个对指针p的引用
r = &i; //给r赋值就是令p指向i
*r=0; //解引用r得到i,也就是将i的值改为0
//从右往左阅读
2.4 const限定符
初始化和const
const对象创建后就不能改变,所以const对象必须初始化。初始值可以是任意复杂的表达式。
const int i = get_size();//正确运行时初始化。
const int j = 42; //正确编译时初始化
只能在const类型上执行不改变其内容的操作。如果利用一个对象去初始化另外一个对象,则他们是不是const对象都无关紧要。
int i = 42;
const int j = i;
int l = j;
默认状态下,const对象仅在文件内有效,如果想在多个文件间共享,就不管声明还是定义都添加extern关键字。
//file1中
extern const int buff= fcn();
//file2中
extern const int bufff;
2.4.1 const引用
把引用绑定到const对象上,称为对常量对象的引用。
const int ci=1024;
const int &b = ci;
int &r2 = ci;//错误不能让一个非常量引用,指向一个常量对象
初始化和对const的引用
引用的类型必须和其所引用对象类型一致。有2种例外,第一种就是初始化常量引用时候,允许表达式作为初始值,有其允许一个常量引用绑定非常量对象、字面值甚至是一个表达式。
int i = 42;
const int &r1 = i; //正确
const int &r2 = 42; //正确
const int &r3 = r1*2; //正确
int &r4 = r1*2; //错误,r4是一个非常量引用
2.4.2 指针和const
指向常量的指针不能用于改变其所指向对象的值,想要存放常量对象的地址,只能使用指向常量的指针。
const double pi = 3.14;
double *ptr = &pi //错误
const double *cptr=π //正确
*cptr = 42;//错误
指针的类型必须与其所指向的对象的类型一致,但有2种特殊情况,第一种是允许一个指向常量的指针指向一个非常量对象。
double dval = 3.14;
cptr = &dval; //正确,但是不能通过cptr改变dval的值,指针可以改变,但是指针指向的内容不能变。
指向常量的指针仅仅要求,不能通过该指针改变对象的值。
const指针
常量指针必须初始化,且一旦完成初始化,它的值就不能改变。
int err= 0;
int *const err1 =&err; //err1将一直指向err不发生变化
const double p=0.0;
const double *const pip = &p;//双重const
2.4.3顶层const
顶层const 表示指针本身是一个常量,顶层const拷入拷出都没有影响。
底层const 表示指针指向对象是一个常量。底层const两个必须都具有底层const资格或者两个对象能够相互转化,非常量可以转化为常量。
2.4.4 constexpr和常量表达式
常量表达式是指值不会改变并且在编译过程就能得到结果的表达式,
字面值是常量表达式,用常量表达式初始化const对象也是常量表达式。
const int max_file = 20; //常量表达式
const int limit = max_file+1 //limit常量表达式
int staff_size= 27;//不是常量表达式
const int sz = get_size();//sz不是常量表达式,因为直到运行才得到
constexpr变量
将变量声明为constexpr由编译器来验证变量的值是否是常量表达式。
constexpr int mf = 20;
constexpr int limit = mf +1;
constexpr int sz = size();//只有当size()是constexpr函数是,才是一条正确的
字面值类型
字面值类型:比较简单,值也显而易见、容易得到。
算数类型、引用、指针都属于字面值类型。constexpr指针必须初始为nullptr或者0或者某个存储固定对象的地址。不能是函数体内变量的地址。constexpr声明指针仅仅对指针有效,对指向对象无效。
const int *p = nullptr; /p是一个指向整形常量的指针
constexpr int *ll = nullptr; //ll是指向整数的常量指针
2.5 处理类型
2.5.1类型别名
类型别名是一个名字,它是某种类型的同义词。
两种定义方法:
一是 typedef
typedef double wages;
typedef wages base,*p;//base 是double的同义词,p是double*的同义词
二是别名声明using
using SI= sales_item;
指针、常量和类型别名
typedef char *pstring;
const pstring cstr=0; //cstr 是一个常量指针,const修饰指针,不是替换为const char * cstr,
const pstring *ps; //ps 是一个指向 char常量指针的指针。
const pstring ;// const 是修饰pstring 这个指针的
2.5.2 auto类型说明符
auto 是编译器通过分析表达式初始值来推算变量类型,auto定义的变量必须有初始值
auto item= val1+val2;//item的初始化为val1+val2相加的结果。
使用auto 也能在一条语句中声明多个变量。
auto i = 0,*p =&i;//正确
auto sz = 0,pi = 3.14 //错误,sz和pi的类型不一样
复核类型、常量和auto
auto类型有时候和初始值的类型不一样
一是使用引用时,实际上是引用对象的值。
int i =0; &r=i;
auto a =r;
二是auto一般会忽略顶层const,底层const会被保留。
const int ci = i ,&cr =ci;
auto b = ci; //b是一个整数
auto c= cr; //c是一个整数
auto d = &i; //d是一个整数指针
auto e = &ci //e是一个指向整数常量的指针,对常量对象去地址是一种底层const
const auto f = ci //f是 const int
设置类型为auto引用时,初始值的顶层常量属性仍然保留。
auto &g = ci;
auto &h = 42; //错误
const auto &j = 42; //正确,可以为常量引用绑定字面值
auto &m=ci,*p=&ci //m是对整形常量的引用,p是指向整形常量的指针
auto &n=i,*p2=&ci //i类型是int,&ci类型是const int
2.5.3decltype 类型指示符
有时需要从表达式类型推断出变量类型,但是不想用表达式值初始化变量,需要用decltype
编译器分析表达式的值并得到他的类型,却不实际计算。
decltype(f()) sum = x; //sum类型就是f()返回的类型
decltype处理顶层const和引用方式与const不同。
const int ci = 0,&cj = ci;
decltype(ci) x= 0; // x is const int
decltype(cj) y = x; //y is const int&,
decltype和引用
如果decltype使用表达式不是一个变量,decltype返回表达式结果对应类型
如果表达式的内容是解引用操作,decltype将得到引用类型。
int i = 42,*p = &i;
decltype(*p) 的结果类型为int&,而非int.
如果变量名加上一对括号和不加括号会不同。不加括号得到该变量的类型,加上括号,编译器会把它当做一个表达式,变量是一种可以作为赋值语句左值的特殊表达式,所以这样decltype就会得到引用类型。
切记:decltype((variable))双层括号的结果永远是引用,而decltype(variable)结果只有当variable本身是一个引用是才是引用
2.6 自定义数据类型
struct Sales{
string bookno;
unsigned units=0;
double re{0.0};
} accum,*salesptr;
1.右侧必须跟着分号;
2.数据成员初始值,放在花括号内,或者等号右边,不能用圆括号。
2.6.2 编写自己的头文件
类通常被定义在头文件中,且类所在头文件的名字与类的名字一致。
头文件通常包含只被定义依次的实体、如类、const和constexpr。头文件一旦改变,相关文件必须重新编译。
头文件保护符。
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<string>
struct Sales{
.....
...
};
#endif