基本内置类型:算术类型和空类型;算术类型又包括整型(字符和布尔类型也包括在内)和浮点型。express
可寻址的最小内存块称为“字节”(byte),存储的基本单元称为“字”(word);一个字一般由几个字节组成。数据结构
1字节(byte)=8比特(bit),比特非0即1。函数
类型unsigned int能够缩写为unsigned。spa
int与singed int相同(除了布尔型和扩展字符类型(例如宽字符wchat_t)以外,其余的整型类型(例如short、long)也相似),但字符型例外,字符型有char、unsigned char、signed char三种类型,但char与signed char不必定相同;字符的表现形式只有两种:带符号的和无符号的,char实际上会表现为上述二者的其中一种,具体由编译器决定。指针
在算数表达式中不要使用char或bool。由于char在一些机器上多是有符号的,而在另外的机器上多是无符号的,使用char进行运算可能出错;若是硬要使用一个不大的整数,那么明确指定它为unsigned char或signed char。bool取值非0即1,不适宜用于算数表达式。code
当赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值的总数取模后的余数。对象
例如:unsigned char占8个比特(1个字节),能够表示[0, 255]的共256个数值,当赋予区间外的一个值,假设是-1,则实际结果是(-1) % 256 = 255。blog
当赋给一个带符号类型一个超出它表示范围的值时,结果是未定义的,此时程序可能继续工做、崩溃或产生垃圾数据。生命周期
当一个算数表达式中既有无符号数又有int值时,那个int值会转换成无符号数。把int转换成无符号数的过程至关于把int赋给无符号类型。ip
切勿混用带符号类型和无符号类型。若是表达式既有带符号类型又有无符号类型,当带符号类型取值为负时会出现异常结果,这是由于带符号数会自动转换成无符号数。
例如:当a=-1,b=1,c=a*b,若a、b都是int,则c为-1;若a是int,b是unsigned int,则c的结果是(-1)%(2^32) * 1 = 4294967295(视当前机器上int所占bit数而定,这里假设int是32bit)。
整型字面值可写做十进制数、八进制数、十六进制数的形式。
0开头:八进制
0x、0X开头:十六进制
浮点数字面值可省略整数部分或小数部分(若该部分为0)。
例如:0.、.001
浮点数字面值默认是一个double。
能够之前缀或后缀指定字面值类型。
前缀
u:char16_t(Unicode16字符)
U:char32_t(Unicode32字符)
L:wchar_t(宽字符)
u8:char(UTF-八、仅用于字符串字面常量)
后缀
对整型字面值:
u、U:unsigned
l、L:long
ll、LL:long long
对浮点型字面值:
f、F:float
l、L:long double
初始化与赋值是不一样的概念。
初始化:建立变量时赋予其一个初始值
赋值:把对象的当前值擦除,并以一个新值替代
因此赋值的开销大于初始化。
若是内置类型的变量未被显式初始化,它的值由定义的位置决定。
定义在任何函数体外:被初始化为0
定义在函数体内:将不被初始化,其值未定义
注:指针类型并不是基本内置类型,但也有上述特性。
若是类的对象没有显式初始化,其值由类肯定。(经过类的默认构造函数、类内初始值等)
声明使得名字为程序所知,而定义建立与名字关联的实体。
若是想声明一个变量而非定义它,就在变量名前添加关键字extern,而不要显式地初始化变量。
例如:
extern int i; //声明i而非定义i
int j; //声明并定义j
包含显式初始化的声明即成为定义。
extern double pi = 3.14; //定义;容许在函数体外这么作,但extern关键字的存在就没意义了
不容许在函数体内部初始化一个由extern关键字标记的变量。
引用自己并不是对象,也没有实际的地址(若尝试用取地址符&取得某个“引用的地址”,实际取得的是所引用的对象的地址),它只是已存在的对象的别名。没法定义引用的引用,也没法定义指向引用的指针。
引用必须初始化,由于没法令引用从新绑定到另外一对象。
全部引用的类型都要和与之绑定的对象严格匹配(有两个状况例外,将在后面提到)。
引用必须绑定到对象上,不能绑定到字面值或某个表达式的计算结果。
(能够将引用绑定到const对象上,就像绑定到其它的对象上同样,称之为“对常量的引用”。与普通引用不一样的是,对常量的引用不能被用做修改它所绑定的对象。)
指针自己是一个对象,容许对指针赋值和拷贝,指针在其生命周期内能够指向不一样对象。
指针无需在定义时赋初值。和基本内置类型相同,定义在全部函数体外的指针若未初始化,将有默认初值0,而块做用域内定义的指针将拥有一个不肯定的值。
全部指针的类型都要和所指的对象严格匹配(有两个状况例外,将在后面提到)。
&、*出如今声明语句中,用来组成复合类型;&出如今表达式中,是一个取地址符;*出如今表达式中,是一个解引用符(或乘法运算符)。
nullptr = NULL = 0
void*是一种特殊的指针类型,可用于存听任意对象的地址。但不能直接操做void*指针所指的对象,由于对象的类型决定了能对对象所作的操做,而咱们并不知道对象的类型。
指针是内存中的对象,像其它对象同样也有本身的地址,所以容许把指针的地址再存放到另外一个指针当中。
经过*的个数区分指针的级别。**是指向指针的指针,***是指向指针的指针的指针,等等。
int i = 1024;
int *pi = &i; //pi指向一个int型的数
int **ppi = π //ppi指向一个int型的指针
解引用int型指针会获得一个int型的数,解引用一个指向指针的指针会获得一个指针。
这里有三种方式取得i的值:i、*pi、**ppi。
int i = 1;
int *p; // p是一个int型指针
int *&r = p; // r是一个对指针p的引用
r = &i; // 由于r是对指针p的引用,即r是p的别名,所以给r赋值&i就是令p指向i
*r = 0; // 解引用r获得i,也就是p指向的对象,所以i的值将改成0
const对象一旦建立后其值就不能再改变,因此const对象必须初始化(初始值容许是任意的表达式,不必定是字面量常量)。
const int i = get_size(); // i将在运行时初始化
const int j = 1; // j将在编译时初始化
const int k; // 错误,k没有初始化
默认状态下,const对象仅在文件内有效;若是想在多个文件共享const对象,必须在变量的定义前添加extern关键字。
当多个文件出现了同名的const对象时,其实等同于在不一样文件中分别定义了独立的变量。
若是想要:只在一个文件中定义const对象,而在其它多个文件中声明并使用它,则:对于const变量无论是声明仍是定义都添加extern关键字,这样只需定义一次就够了。
【file_1.cc】
int get_size() {...}
extern const int bufSize = get_size(); //定义并初始化了一个常量(extern表示bufSize能被其它文件访问)
【file_1.h】
extern const int bufSize; //只是声明,这个bufSize与file_1.cc中定义的bufSize是同一个(extern表示bufSize并不是本文件全部,它的定义在别处出现)
能够将引用绑定到const对象上,就像绑定到其它的对象上同样,称之为“对常量的引用”(“reference to const”,或“对const的引用”,或“常量引用”)。与普通引用不一样的是,对常量的引用不能被用做修改它所绑定的对象。
const int ci = 1;
const int &r1 = ci; //正确;引用及其对应的对象都是常量
r1 = 2; //错误;r1是对常量的引用
int &r2 = ci; //错误;试图让一个很是量引用指向一个常量对象(设想:若这条语句合法,即默认“容许经过r2来修改ci的值”,而ci是常量,显然矛盾)
前面提到引用的类型必须与其所引用的对象一致,可是有两个例外。这里说它的第一种例外:
在初始化常量引用时,容许用任意表达式做为初始值(只要该表达式的结果能转换成引用的类型便可);
尤为,容许为一个常量引用绑定很是量的对象、字面值,或者通常的表达式。
int i = 1;
const int &r1 = i; //容许将const int&绑定到一个普通int对象上
const int &r2 = 1; //正确;r2是一个常量引用
const int &r3 = r1 * 2; //正确;r3是一个常量引用
int &r4 = r1 * 2; //错误;r4是一个普通的很是量引用,不容许绑定到字面值或某个表达式的计算结果
看看当一个常量引用被绑定到另一种类型(合法)时发生了什么:
double dval = 3.14;
const int &ri = dval; //合法;ri值为3
ri引用了一个int型的数,但dval倒是一个double型的浮点数而非整数。所以为了确保让ri绑定一个整数,编译器把上述代码变成了:
const int temp = dval; //由double型浮点数生成一个临时的int型整数常量
const int &ri = temp; //让ri绑定这个临时量(临时量是临时量对象的简称,是内存中的一个未命名对象)
顺便,对前例,探讨一下当ri不是常量(非法)时会发生什么:
若是ri不是常量,意味着容许对ri赋值(或者说经过ri修改其绑定的对象)。上例谈到,ri绑定到了一个临时量,而非dval。
咱们既然让ri引用dval,确定想经过ri改变dval的值,因此不会想要把引用绑定到临时量上,因此,C++认为“普通的很是量指针去绑定另一种常量类型”的行为是非法的。
对const的引用可能引用一个并不是const的对象
对const的引用的机制是:不容许经过引用来修改所引用的对象的值,但所引用的对象的值可经过其它途径修改。
int i = 1;
int &r1 = i; //引用r1绑定非const的对象i
const int &r2 = i; //r2也绑定对象i
r1 = 0; //合法;i的值修改成0
r2 = 0; //非法;r2是一个常量引用,不容许经过r2修改i的值
修改i的值的“其它途径”能够是各类合法的途径,例如直接对i赋值。
与引用同样,也能够令指针指向常量或很是量。
相似于常量引用,指向常量的指针(pointer to const)不能用于改变其所指对象的值。
要想存放常量对象的地址,只能使用指向常量的指针。
const double pi = 3.14;
double *ptr = π //非法;ptr是一个普通指针
const double *cptr = π //合法
*cptr = 1.0; //非法
前面提到,指针的类型必须与其所指的对象的类型一致,可是有两个例外。这里先讨论第一种例外:
容许令一个指向常量的指针指向一个很是量对象。
double dval = 3.14; //dval是一个很是量对象
const double *cptr = &dval; //合法;可是不能经过cptr改变dval的值(与指向常量的引用相似,这里的dval仍然可能经过其它途径修改)
常量指针的意义是指针自己是常量。
注:注意“指向常量的指针”与“常量指针”相区别。前者机制是:不能经过指针改变所指对象的值;后者的机制是:不能改变指针自己的值(也就是存放在指针中的那个地址)。
注:在讨论“对const的引用”(reference to const,对常量的引用)时,说“对const的引用”简称“常量引用”,但严格来讲并不存在“常量引用”。由于引用并非一个对象,因此没办法让引用“恒定不变”。事实上,因为C++不容许改变引用所绑定的对象,因此从这层意义上理解,全部的引用都算是“常量”。因此以“常量引用”代称“对const的引用”何尝不可。
const double pi = 3.14; //pi是一个常量对象
const double *const pip = π //pip是一个指向常量对象的常量指针
从右往左,离pip最近的那个const说明pip自己是一个常量对象,对象的类型由声明符的其他部分决定。声明符中的下一个符号是*,说明pip是一个常量指针,最后const double肯定了常量指针指向的是一个double型常量对象。
当仅对指针而言:
顶层const:指针自己是个常量
底层const:指针所指的对象是一个常量
更通常的:
顶层const能够表示任意对象是常量,这一点对任何数据类型都适用,好比算数类型、类、指针等;
底层const则与指针和引用等复合类型的基本类型部分有关。
特殊的是:
指针既能够是顶层const也能够是底层const;用于声明引用的const都是底层const。
int i = 0;
int *const p1 = &i; //不能改变p1的值,顶层const
const int ci = 1; //不能改变ci的值,顶层const(PS:事实上,“const int ci = 1”等价于“int const ci = 1”)
const int *p2 = &ci; //容许改变p2的值,底层const
const int *const p3 = p2; //靠右的是顶层const,靠左的是底层const
const int &r = ci; //用于声明引用的const都是底层const
当执行对象的拷贝操做时,常量的顶层const不须要被考虑,由于拷贝操做不改变被拷贝对象的值,所以,拷入(指的是对常量对象的初始化时的拷贝动做,固然,初始化完成后就不能对常量对象有拷入动做了)和拷出的对象是不是常量都没什么影响。(例如上述示例的倒数第二条语句)
而底层const的限制不能被忽略,拷入和拷出的对象必须有相同的底层const资格,或者两个对象的数据类型可以转换。(很是量能够转换成常量,反之不行)
int *p = p3; //非法;p3包含底层const定义,而p没有
p2 = p3; //合法;p二、p3都是底层const
p2 = &i; //合法;int*能转换成const int*(很是量能够转换成常量,反之不行)
int &r = ci; //非法;普通的int&不能绑定到int常量上
const int &r2 = i; //合法;const int&能够绑定到一个普通int上
思考一下:为什么“很是量能够转换成常量,反之不行”?
看第一条语句(非法:常量不可转为很是量),假设第一条语句合法,也就是说,假设容许以p3初始化p,看会发生什么:
p3包含底层const定义,即p3指向的对象是常量;而p不包含底层const定义,p指向的是一个普通的(很是量)整数,也就是说:“可能经过p修改它所指的对象的值”,但所指的对象是常量,这是矛盾的。
常量表达式(const expression):值不会改变,且在编译过程就能获得计算结果的表达式。
const int i = 1; //i是常量表达式
const int j = i + 1; //j是常量表达式
int k = 2; //k不是常量表达式(值可能改变)
const int sz = get_size(); //sz不是常量表达式(在运行过程才能获得具体值)
C++11能够用constexpr来明确声明常量表达式。
constexpr int ce = 1; //ok
constexpr int sz = size(); //仅当size()函数是一个constexpr函数时才是一条合法语句
字面值类型:包含算术类型、引用、指针、字面值常量类、枚举类型。
字面值类型能够定义成constexpr。
指针和引用定义成constexpr时,它们的初始值有严格限制:
关于指针:
一个constexpr指针的初始值必须是0(nullptr),或者是存储于某个固定地址中的对象;
故constexpr指针不能指向函数体内定义的变量,能够指向函数体外的变量;
函数体内的local static object局部静态对象与函数体外的对象同样有固定地址,故constexpr指针能指向局部静态对象。
在constexpr的声明中若是定义了一个指针,限定符constexpr仅对指针有效,而与指针所指的对象无关;
const int *p = nullptr; //p是一个指向整型常量的指针(p自己可被改变)
constexpr int *q = nullptr; //q是一个指向整数的常量指针(q自己不可被改变)
缘由是:constexpr把它所定义的对象置为了顶层const。
与const指针相似,constexpr指针既能够指向常量,也能够指向很是量。(指向很是量时,必须是存储于某个固定地址中的对象)
constexpr int i = 1; //i是整型常量(i必须定义在函数体外,不然编译错误)
constexpr const int *p = &i; //p是常量指针,指向整型常量i
注意这个特殊写法,若以const限定符声明常量指针,const的位置紧邻p的左边:const int *const p = &i;
而这里因为constexpr的特殊性(限定符constexpr仅对指针有效)而有其特殊语法:constexpr const int *p = &i。
关于引用:
constexpr引用只能绑定到局部静态对象。
类型别名
方式一:使用关键字typedef。
typedef double wages; //wages是double的同义词
方式二:(C++11)使用别名声明(alias declaration)。
using wages = double;
指针、常量和类型别名
若是某个类型别名指代的是复合类型或常量,那么把它用到声明语句里会产生意想不到的后果。
typedef char *pstring; //类型pstring是类型char*的别名
const pstring cstr = 0; //cstr是指向char的常量指针(指针自己是常量)
const pstring *ps; //ps是一个指针,它所指的对象是一个指向char的常量指针
上述两条语句的基本数据类型都是const pstring,const是对类型pstring的修饰,类型pstring是指向char的指针,所以const pstring就是指向char的常量指针,而非指向常量字符对象的指针。
一个错误的理解方式:
尝试把类型别名替换成它原本的样子,以理解该语句的含义。(这种方式是错误的)
尝试把pstring替换成char*,有:
const char *cstr = 0;
这样看来,*成了声明符的一部分,用以说明cstr是一个指针,const char成了基本数据类型,const是对类型char的修饰,这条语句的含义就是“指向const char的(普通)指针”。这与替换前的实际意义大相径庭。
使用auto也容许在一条语句中声明多个变量,可是,由于一条语句只能有一个基本数据类型,因此该语句中的全部变量的初始基本数据类型都必须相同。
auto i = 0, *p = &i; //合法;i是int型、p是int型指针
auto sz = 0, pi = 3.14; //非法;sz是int,pi是double,类型不一致
const int ci = i;
auto &n = i, *p2 = &ci; //非法;i的类型是int,而ci的类型是const int
编译器推断出来的auto类型有时候和初始值的类型并不彻底同样,编译器会适当地改变结果类型使其更符合初始化规则。
例如:
使用引用其实就是使用引用的对象,特别是当引用做为初始值时,真正参与初始化的实际上是引用对象的值。此时编译器以引用对象的类型做为auto的类型。
int i = 0, &ri = i;
auto a = ri; //a是一个整数,由于ri是i的别名,而i是一个整数
其次,auto通常会忽略顶层const,保留底层const。(后面会谈到特例)
例如:
当初始值是一个指向常量的指针时:
const int ci = i, &cr = ci; //ci是一个常量,cr是对常量的引用
auto b = ci; //b是一个整数(非const),由于ci的顶层const特性被忽略了
auto c = cr; //c是一个整数(非const),由于cr是ci的别名,ci自己是一个顶层const,而顶层const被忽略了
auto d = &i; //d是一个整型指针,由于一个整数的地址也就是指向整数的指针
auto e = &ci; //e是一个指向整数常量(const)的指针,由于ci是一个常量对象,而对常量对象取地址是一种底层const,且auto会保留底层cons
若是但愿推断出的auto类型是一个顶层const,须要明确指出:
const auto f = ci; //ci的推演类型为int(而非const int),但明确指出后,f是const int
前面说到“auto通常会忽略顶层const”,此例即为特例:
当设置一个类型为auto引用时,初始值的顶层const仍然保留。
auto &g = ci; //g绑定到ci,g是一个整型常量引用(reference to const,对const的引用),由于ci的顶层const特性被保留了
decltype:编译器分析表达式并获得它的类型,却不实际计算表达式的值。
decltype(f()) sum = x; //sum的类型被设定为函数f的返回类型(注意:编译器并不实际调用函数f)
decltype处理顶层const和引用的方式与auto不一样。若是decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内)。
const int ci = 0, &cj = ci;
decltype(ci) x = 0; //x的类型被设定为const int
decltype(cj) y = x; //y的类型是const int&,y绑定到变量x
decltype(cj) z; //非法;z的类型是const int&,是一个引用,引用必须初始化
引用历来都做为其所指对象的同义词出现,只有用在decltype处是一个例外。
也就是说:不要由于cj引用ci,就认为“decltype(cj) y = x”等同于“decltype(ci) y = x”。
若是decltype使用的表达式不是一个变量,则decltype返回表达式结果的对应类型。
int i = 1, *p = &i, &r = i;
decltype(r+0) b; //合法;加法的结果是int,所以b是一个未初始化的int
decltype(*p) c; //非法;c是int&(而非int;下面会解释),必须初始化
解释:由于r是一个引用,因此decltype(r)的结果是引用类型(而不是引用所指对象的类型)。若是想让结果是r所指对象的类型,能够把r做为表达式的一部分,如r+0,显然这个表达式的结果是一个具体值而非一个引用;
若是表达式的内容是解引用操做,则decltype将获得引用类型。解引用指针能够获得指针所指的对象,并且还能给这个对象赋值。所以,decltype(*p)的结果是int&,而非int。
给变量加上一层或多层括号,编译器就会把它看成一个表达式。变量是一个能够做为赋值语句左值的特殊表达式,因此这样的decltype就会获得引用类型。
int i = 1;
decltype(i) d; //合法;d是一个(未初始化的)int
decltype((i)) e; //非法;e是int&,必须初始化
总结:decltype((variable))的结果永远是引用,而decltype(variable)的结果仅当variable自己就是一个引用的时候才是引用。
补充例子:
赋值是会产生引用的一类表达式,引用的类型就是左值的类型。例如:若是i是int,则表达式i=x的类型是int&。
int a = 3, b = 4;
decltype(a) c = a; //c是int,值为3
decltype(a=b) d = a; //d是int&,值为3
d的值为什么不是4?
因为“a=b”,故a的值变为4,而d是a的引用,故d的值是4?这是错误的,“a=b”只是用于编译器推断表达式的类型,实际上该表达式并不会真的执行,因此a的值仍旧是它原来的值3。
C++11容许为类内数据成员提供一个类内初始值(in-class initializer)。建立对象时,类内初始值将用于初始化数据成员。没有初始值的成员将被默认初始化(前面讨论过各类变量的默认初始化的规则)。
struct Sales_data
{
std::string bookNo; //将会进行默认初始化;将会初始化为空字符串
unsigned sold = 0; //将由类内初始值初始化;将会初始化为0
double revenue; //将会进行默认初始化(结果:其值未定义,多是乱七八糟的值)
};
头文件保护符
【Sales_data.h】(类一般定义在头文件中,头文件的名字应与类名相同)
#ifndef SALES_DATA_H
#define SALES_DATA_H
//#include <...>
struct Sales_data {
//...
};
#endif
防止头文件被屡次include,避免重复定义。
预处理指令#ifdef用于判断给定预处理变量是否已经定义;#ifndef用于判断给定预处理变量是否还没有定义。