##C++ Primer 学习笔记(第二章:变量和基本类型)安全
[TOC]数据结构
###2.1 基本内置类型函数
wchar_t
(16位)类型用于确保能够存放机器最大扩展字符集中的任意一个字符,char16_t
和char32_t
则为Unicode
字符集服务(Unicode
表示全部天然语言中字符的标准)。学习
一般,float
以1个字(32比特)来表示,double
以2个字(64比特)来表示,longdouble
以3或4个字(96或128比特)来表示。指针
整型类型(int
,short
,long
,longlong
)都是带符号(省略signed
),加上unsigned
就是无符号类型。unsigned int
能够缩写为unsigned
。code
字符型有三种:char
、signedchar
、unsigned char
。但字符型的表现只有两种,有符号和无符号,而char到底有没有符号要根据编译器决定。对象
类型选择经验: (1)明确知晓不可能为负时选用无符号类型。 (2)使用int
执行整数运算。short
过小而long
通常和int
具备相同尺寸。若是范围超过int
表示范围,再选用longlong
。 (3)执行浮点数运算使用double
。由于float
一般精度不够,而双精度和单精度在计算代价上相差无几。long double
通常状况下也无必要。生命周期
关于类型转换:当赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模以后的余数;当赋给带符号类型一个超出它表示范围的值时,结果是未定义的(undefined
)。ip
整型字面值:以0开头的整数表明八进制,以0x
或0X
开头的表明十六进制数。内存
字符字面值:'A'
,只有一个单独字符;字符串字面值:"A"
,有两个字符,一个A
,一个空字符'\0'
。
若是两个字符串字面值位置紧邻且仅由空格、缩进和换行符分隔,则他们其实是一个总体。即当书写的字符串字面值较长,一行写不下,可采起分开书写的方式。
###2.2变量
初始化不是赋值,初始化的含义是建立变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。
C++11
新标准:用花括号初始化变量:
int unit=0; int unit={0}; int unit{0}; int unit(0);
被称为列表初始化,不管是初始化对象仍是某些时候为对象赋新值均可以用花括号。
若是内置类型的变量未被显式初始化,它的值由定义的位置决定。定义于任何函数体以外的变量被初始化为0;定义在函数体内部的内置类型变量不被初始化,其值是未定义的(undefined
)。
为了支持分离式编译,C++
语言将声明和定义区分开来。若是想声明一个变量而非定义它,就在变量名前添加关键字extern
,并且不要显式地初始化变量:
extern int i;//声明而非定义 int j;//声明并定义j
变量能且只能被定义一次,可是能够被屡次声明。若是要在多个文件中使用同一个变量,就必须将定义和声明分离,变量的定义必须出如今一个文件中,而其余用到该变量的文件必须对其进行声明,却绝对不能重复定义。
用户自定义的标识符中不能连续出现两个下划线,也不能如下划线紧连大写字母开头。定义在函数体外的标识符不能如下划线开头。
若是函数内部定义了一个与全局变量同名的新变量,则直接访问变量名时,局部变量将会覆盖全局变量;若想显式访问全局变量须要在变量名前使用做用域操做符(::
)。(注:函数内部不宜定义相同变量名)
###2.3 复合类型(引用和指针)
定义引用时(&d
),程序把引用和它的初始值绑定在一块儿,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值绑定在一块儿。引用必须初始化。
引用并不是对象,它只是一个已经存在的对象所起的另一个名字,因此不能定义引用的引用。
容许在一条语句中定义多个引用,其中每一个引用标识符都必须以符号&
开头。
全部引用的类型要和绑定的对象严格匹配(仅是初始化时严格匹配,实际上引用也没法从新绑定到另外一个对象上),且引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一块儿。
指针和引用的区别:指针自己就是一个对象,容许对指针赋值和拷贝,并且在指针的生命周期内它能够前后指向几个不一样的对象(而引用非对象,一旦定义就没法绑定到另外对象上);指针无须在定义时赋初值。
指针的类型要和它指向的对象严格匹配。
指针的解引用符(*
),若是给解引用的结果赋值,就至关于给指针所指对象赋值。
空指针的表示形式有三种:
int *p1 = nullptr;(推荐) int *p2 = 0; int *p3 = NULL;(#include<cstdlib>)
把int
变量直接赋给指针是错误的操做,即便int变量的值刚好等于0也不行。
两个指针存放的地址值相同有三种可能: 都为空;都指向一个对象;都指向同一对象的下一地址。 也有多是一个指针指向某对象,另外一指针指向另外对象的下一地址。
void*
是一种特殊指针类型,它能够存听任意对象的地址。 利用void*
只能作:和别的指针比较;做为函数的输入输出;赋给另一个void*
指针。 不能直接操做void*
指针所指的对象,由于咱们并不知道它是什么类型,也没法肯定它能作哪些操做。
指针是内存中的对象,像其余对象同样也有本身的地址,所以容许把指针的地址再存放到另外一个指针当中(即**
表示指向指针的指针)
引用不是对象,不能定义指向引用的指针;但指针是对象,因此存在对指针的引用:
int i = 42, *p; int *&r = p; r = &i; *r = 0;
###2.4 const限定符(const
、constT*
、constT&
)
const
对象一旦建立后其值不可改变,因此const
对象必须初始化。能够运行初始化,也可编译初始化。const int i = get_size(); const int i = 52;
默认状况下,const
对象被设定为仅在文件内有效。当多个文件中出现了同名的const
变量时,其实等同于在不一样文件中分别定义了独立的变量。
只在一个文件中定义const
,在其余多个文件中声明并使用它:对于const
变量无论是声明仍是定义都添加extern
关键字,并只需定义一次。
extern const int i = fcn();//file.cc extern const int i;//file.h
若是想在多个文件中共享const
对象,必须在变量的定义以前添加extern
关键字。
const T&
(只读引用):初始化常量引用时容许用任意表达式做为初始值,只要该表达式的结果能转换成引用的类型便可。 const auto&
的做用是:避免对元素的拷贝;避免对象的写操做。int i1 = 42;double i2 = 3.14; const int &r1 = i1; const in t&r2 = i2;
但当常量引用被绑定到另一种类型上时,它只绑定到了一个临时量对象,若是改变被绑定对象或其另一个同类型引用的值时(注意此处的值与常量引用的类型不一样),常量引用的值将不会改变。而若是常量引用和绑定类型一致时,则常量引用可当作一个只读功能,也会对被绑定对象的值改变。 可是须要注意:要想绑定一个常量必须用常量引用。
尽管常量引用的初始化要求比较宽松,但不能忘记很是量引用严格要求类型匹配(表达式不行,常量同一类型也不行,必须是同类型变量)。其缘由能够理解为:既然定义了很是量引用,就但愿经过引用修改绑定对象的值,但实际上只要类型稍有不一样就必定存在类型转换,存在类型转换就须要编译器临时建立一个临时量对象用于很是量引用的绑定,而这种绑定将致使对引用的修改没法反映到原始的对象上(只反映到临时量对象,而这个对象毫无心义,也没法访问)。因此C++
禁止这种类型不用的引用绑定。
指向常量的指针(const T*
)不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针:
const double pi = 3.14; const double *cptr = π//这里不能随随便便定义一个很是量指针double*来存放常量的地址
存放常量对象的指针必须使用指向常量的指针,但并不表明指向常量的指针不能指向很是量。(见第7条)
double dval = 3.14; const double *cptr = &dval;
指向常量的指针也仅仅要求不能经过该指针改变对象的值,而没有规定那个对象的值不能经过其余途径改变。 指向常量的指针能够随时改变其所指对象,只要不改变值便可。
T *const
)必须初始化,且一旦初始化完成,它的值(即存放的地址)就不能再改变了,即不变的指针自己的值(即存放的地址)而非指向的那个值。(可是能够经过常量指针修改所指对象的值)int errNumb = 0; int *const curRrr = &errNumb; const double pi = 3.14159; const double* const pip = π//指向常量对象的常量指针
指向常量的指针(const T *name
)Vs
常量指针(T *const name
): 从右向左阅读,const
离谁近,谁不能变(“谁”包括指向的地址和所指对象的值)。const T1 * const T2
就都不能变。
顶层const
和底层const
: 顶层const
表示指针自己是个常量,而底层const
表示指针所指的对象是一个常量。通常的,顶层const
能够表示任意的对象是常量,对任何数据类型都适用;而底层const
和指针引用等复合类型的基本类型部分有关。而指针类型既能够有顶层const
也能够有底层const
。 当执行拷贝操做时,常量的顶层const
不影响。而若是是底层const
,拷入和拷出对象必须具备相同的底层const
资格,或者两个对象的数据类型必须可以转换。通常来讲很是量能够转化为常量。 即两条准则:(1)顶层const
不影响拷贝;若是被拷贝的有底层const
,必须具备相同的底层const
资格才能拷贝;(2)很是量能够转化为常量,反之不行。 例:
int i= 0; int *const p1 = &i;//顶层const const int ci = 42;//顶层const const int *p2 = &ci;//底层const const int *const p3 = p2;//靠右的是顶层const,靠左的是底层const const int &r = ci;//用于声明引用的都是底层const //顶层const不影响拷贝: i = ci;//ci是一个顶层const //底层const: int *p = p3;//错误,p3有底层const,而p没有 p2 = p3;//正确,p2和p3都有底层const,这里p3的顶层const不受影响 p2 = &i;//正确,很是量能够转化为常量 int &r = ci;//错误,普通的int&不能绑定到int常量上,即常量不能够转化为很是量 const int &r2 = i;//正确,至关于制度,即很是量能够转化为常量
const
对象也是常量表达式。const int max_files = 20;//是 const int limit = max_files + 1;//是 int staff = 27;//不是 const int sz = get_size();//不是
C++11
容许将变量声明为constexpr
类型以便编译器来验证变量的值是不是一个常量表达式。声明为constexpr
的变量必定是一个常量,并且必须用常量表达式来初始化。constexpr int sz = size();//size()必须是一个constexpr类型函数。
通常来讲,若是认定一个变量是常量表达式,那就把它声明成constexpr
类型。
constexpr
用到的类型称为“字面值类型”,这些类型通常比较简单,值也显而易见、容易获得。算数类型、引用和指针属于字面值类型。类、IO
库、string
不属于。###2.5 处理类型
typedef
和使用别名声明。typedef double wages; typedef wages base, *p;//p是double*的同义词 using SI = Sales_item;//C++11
typedef char *pstring; const pstring cstr = 0;//cstr是指向char的常量指针
const pstring
是指向char
的常量指针,而非指向常量char
的指针。
C++11
引入auto
类型说明符,让编译器经过初始值来推算变量的类型。而auto
的变量必须有初始值。
auto
能够在一条语句中声明多个变量,但一条声明语句只能有一个基本类型。
当auto
的初始值为引用时,参与初始化的是其引用对象的值;auto
通常会忽略掉顶层const
,同时底层const
则会留下来:(即auto
忽略引用,忽略顶层const
)
int i = 0, &r = i; auto a = r;//a是一个整数 const int ci = i, &cr = ci; auto b = ci;//整型,忽略顶层const auto c = cr;//整型,cr和ci无差异 auto d = &i;//整型指针 auto e = &ci;//指向整型常量的指针(对整数常量取地址是一种底层const) //若是但愿推断出的auto类型是一个顶层const则须要明确给出const: const auto f = ci; //也能够将引用类型设为auto,此时初始化规则仍然适用: auto &g = ci;//g是引用,绑定到ci auto &h = 42;//不能为很是量引用绑定字面值 const auto &j = 42;//能够为常量引用绑定字面值
C++
定义了类型说明符decltype
来选择并返回操做数的数据类型。此过程当中编译器分析表达式并获得它的类型,却不实际计算表达式的值。decltype(f()) sum = x;//sum的类型就是f函数返回的类型
auto
不一样,若是decltype
使用的表达式是一个变量,则decltype
返回该变量的类型(包括顶层const
和引用在内):const int ci = 0, &cj = ci; decltype(ci) x = 0;//x是const int decltype(cj) y = x;//y是const int&,并绑定到x上 decltype(cj) z;//错误,z是一个引用,必须初始化
引用历来都做为其所指对象的同义词出现,只有用在decltype
处是例外。
decltype
使用的表达式若是是一个解引用操做,则decltype
将获得引用类型而不是解引用类型:int i = 42, *p = &i, &r = i; decltype(r + 0) b;//正确,加法的结果是int,b是一个未初始化的int decltype(*p) c;//错误,c是int&,必须初始化
decltype
使用的是一个不加括号的变量,则获得的结果就是该变量的类型;若是给变量加上了一层或多层括号,编译器就会把它当成一个表达式,而这样的decltype
就会获得引用类型。int i = 42; decltype((i)) d;//错误,d是一个int&,必须初始化 decltype(i) e;//正确,e是一个未初始化int
即decltype((variable))
的结果永远是引用,而decltype(variable)
的结果只有当variable
自己就是一个引用时才是引用。
i = x
的类型是int &
。###2.6 自定义数据结构
struct
)定义时,最好不要把对象的定义和类的定义放到一块儿。struct Sales_data{/*...*/}; Sales_data accum, *saleptr;
C++11
新标准规定能够为数据成员提供一个类内初始值。建立对象时,类内初始值将用于初始化数据成员。没有初始值的成员将被默认初始化。
类一般定义在头文件中,并且类所在头文件的名字应与类的名字同样。 头文件一般包含那些只能被定义一次的实体,如类、const
和constexpr
变量等。
预处理器:确保头文件屡次包含仍能安全工做的经常使用技术。 #include
:用指定的头文件内容代替#include
头文件保护符:依赖于预处理变量。 预处理变量有两种状态:已定义和未定义。#define
指令把一个名字设为预处理变量,#ifdef
和#ifndef
分别检查某个指定的预处理变量是否已经定义。#ifdef
当且仅当变量已定义时为真,#ifndef
当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操做直到遇到#endif
指令为止。 例如某个头文件:
#ifndef SALES_DATA_H//第一次包含时为真,第二次为假不执行后面) #define SALES_DATA_H #include <string> struct Sales_data{ std::string bookNo; unsigned units_sold = 0; double revenue = 0.0; }; #endif
预处理变量包括头文件保护符必须惟一,一般的作法是基于文件中类的名字来构造,预处理变量的名字所有大写。