txt文档转存
'***************
-exec disassemble /m main --vscode 中查看程序汇编代码
位操作,文件操作
书上跳过的
1.线程
2.volatile mutable
https://zh.cppreference.com/

编程方式
C++:
新术语:
(C语言代表的过程性语言-大任务分解成小任务函数-top-down)
代码重用
面向对象(C基础上添加的以类为代表的的OOP-bottom-up)–管理大型项目的工具–强调数据
对象,类;封装,继承(旧类派生新类),多态(为运算符和函数创建多个定义,通过上下文确定用哪个);数据隐藏(保护数据,避免非法访问)
泛型编程–模板STL–执行常见任务的工具–创建独立于类型的代码,应对多数数据类型

第二章

标准的C++结构
include
/*
兼容C, 所以可以这样
include <stdio.h>
include 等价于 inlcude <math.h>—c语言中
*/
int main()
{
using namesapce std;
cout << “hello world” << endl;
std::cout << “haha” << endl // std空间中的cout函数
cin.get() // 让黑框框等待
return 0;
}

类:用户定义的一种数据结构,需要给出数据类型和科执行的操作,相当于数据结构汇总的抽象数据类型
类之于对象,类型之于变量。对象是类的实际应用,即实体。

初始化器,防止类型转换错误?
{}-列表初始化,用于给复杂的数据类型提供值列表,不允许数据缩窄(narrowing),允许类型扩大.
也就是输入的数据必须小于等于定义的数据类型大小
int a, b = 1;—ok
int a = {1} int a{1} —ok
int a = {} int a{} 初始化为0

cout << hex;
cout << 12 << endl;
输出12的16进制表示
C++ 将整型默认存储为int,除非它太大或者人为规定后缀

char ch = 75
cout << ch +1 << endl; // 76

bool a true;
int ans = true; // 1
bool start = -100; // true
bool stop = 0; // false

(typename) value //C
typename (value) //C++
static_cast (value)

第四章 复合类型

数组

不指定数组大小,让编译器自己计算,对于int等类型来说很糟糕。但是对于字符串来说可以。因为
你输入的就是需求的
通常数组大小在定义时就要确定
部分初始化时,其余默认为0

int a[4] {1,2,3,4};–列表初始化–C++11 ,{不允许类型“降级”}// C语言的初始化方式也OK

vetor(容器)

–模板类 & array–数组的替代品–自由存储区或者堆
vetore使用了new和delete自动管理内存
(包含在std中) 效率低,功能强大
vector vt[n_elem]
vector vi //长度为0
vector vd[n] n可以是整型常量,也可以是变量
vi vd 都是vector的对象。
插入或添加值自动调整长度,所以初始化长度为0

定义完成后可以像数组一样使用
vd[0] = 1

array C++11

定义完成后可以像数组一样使用
模板类–长度固定的数组首选,使用栈–静态内存分配。
对象长度固定
array<typeName,n_elem> arr e.g. array<int,2> = {1,2};
n_elem 不能是变量
可以array1 = array2 直接赋值
数组只能一个一个赋值

C/C++不检查数组越界,只能自认倒霉。
对于vector和array 你可以和数组一样,也可以用成员函数at()来花时间检查 。如array<int,2> arr; arr.at(1) = 2 at()会将第一个空格前的捕获非法索引,并中断。

字符串

cin 使用空白,如空格、制表符和换行符来确定字符串的结束位置,cin读到输入字符串之间空格时候,会将第一个空格前的
读入,然后自动在结尾添加空字符。没读的会放在缓冲区

中getline类方法 cin是个对象
getline()使用回车键输入的换行符来确定结尾–cin.getline()
cin.getline(name,20)-- 两个参数,第一个存储输入的数组名,第二个是要读取的字符数。参数20,最多读取19,结尾需要添加空符

读取时,get() 类似getline()。但是它不会丢弃多余的字符,而是保留在输入队列,等待get()再次读入。
但是,换行符也会保留。所以,可以cin.get(arr,20)之后可以用cin.get()处理换行符,为下一行输入做准备。不然就卡在这儿了。
cin.get()可以用来处理不需要的输入(如回收换行符)。
另外,可以用get将两个类成员函数拼接起来, cin.get(arr,20).get()–因为cin.get(arr,20)返回cin对象,可以调用get
getline类似。
cin也会将回车生成的换行符留在输入队列,需要get()或者上方将cin.get()串联
getline简单,但是get排查错误简单。

get在读到空行后,会设置失效位,阻断接下来的输入。可以用cin.clear() 清除
大于设定长度时候,getline设置失效位,关闭后面的输入。

string类

string类位于std中,必须用using编译命令,或者std::string引用。
string类隐藏字符串的数组性质,当做普通变量来处理。
string str = “haha”;
string str = {“haha”}
string str {“haha”}
可以使用C-风格字符串来初始化string对象
可以cin输入,cout输出,可以使用数组表示法访问sting对象汇总的字符。

相对于数组,string类可以赋值“=”给另一个string类
可以使用“+”将两个string对象合并。
C风格可以使用中strcpy,strcat,但是存在目标字符串数组太小的问题。string类可以自动调整大小。
C中strlen, c++ str.size()
string的 cin 每次读取一行,而不是一个单词

重点待解决:getline函数和getline方法
getline(cin,str) cin–从哪儿找输入, str–存储在哪儿。str为string类,长度自动变化。
cin.getline(str,20)–方法

其他类型的字符串字面值 C++ Primer Plus P87 省略

结构体struct

struct xx
{
}bb;
C-风格: struct xx a;
C++风格:xx a; 省略了struct关键字。在C中这么干得typedef struct aa{} xx;
结构体初始化,参数可以放在一行。 xx a= {“a”, “b”};
初始化列表赋值: xx a {“a”, “b”};
初始化大括号内为空,则每个字节初始化为0
e.g.
#include
struct aa
{
std::string name; // 一定要定义在std空间内
}
结构体可以 “=” 直接赋值 同样不允许缩窄

共用体union 数据项多种格式不同时使用,可以用来节省空间
同时只能存储一个数据类型

枚举enum–另一种创建符号常量的方式,可以替代const
枚举量默认从0开始,考科一单独赋值
当使用枚举量时,直接使用0,1 等数字是错误的。枚举量可以相互加减但没意义。除非经过强制转换
e.g. enum bits{one = 1, four = 4}
enum bigstep{first, second = 10, third} 此时first默认0 third 默认101
同时,enum{zero,null= 0}成立

强制类型转换,将枚举类型转换到枚举取值范围,即使没有对应的枚举量,但也成立(范围确立P97)

指针

int* p; p = 123; error, p不知道指向哪儿. 所以,在指针解引用前一定要初始化为一个确定的,适当的地址,null不行。

new一个。啊哈
new代替malloc calloc 分配内存
过程:程序员告诉new要求哪种类型的内存,new找到一个合适的内存块,返回地址,程序员再将地址赋值给一个指针:
int* p = new int -->typeName * pointer_name = new typeName
变量存储于栈,new出来的存储于堆heap或者自由存储区
new返回0–空指针(较老的实现),表示没空间了。
delete释放new分配出来的内存
delete p;释放p指向的内存(针对上方,会删除*p 被赋予的值索在的内存,delete p前后, p内容不变),但是不会删除p本身,因此可以重定向到新内存块,重复使用。否则内存泄露memory leak

释放已经释放的内存块,结果未知。但是空指针随便搞

大型数据 可以用到new
int *arr = new int [10]; 返回第一个元素的地址 在运行时,按需创建,然后 arr可以当数组名
delete [] p; [] 指明是数组
new 也可创建结构

delete new配对
不要delete 同一块内存两次
new[]为数组分配内存, delete[]释放
new[]为一个实体分配内存,则直接delete释放
new出来的数组的名字不会被sizeof理解为数组,而只是理解为一个这个指针的长度
(C风格数组名被解释为第一个元素地址,对数组名取地址,得到的是整个数组的地址, 虽然输出的结果是一样的,但概念上,
int t[12];
cout << t << endl; // 首元素占用的4个字节的内存的地址 t+1是下一个元素
cout << &t << endl;//长度为12*4内存块的地址 &t+1是加了一个t数组

int {*t2) [12] = &t //数组指针 *t2 [12] 则 t2[12]结合,t2就是一个指针数组,20个指针在数组里

多数情况下 数组名,char指针及双引号括起来的字符串常量都被结识为字符串第一个字符的地址
因此 cout 指针,则打印其地址,但是cout char指针,则打印其指向的字符串,如果要显示字符串地址,则必须(int *)强制转换
C:
strcpy内存不够,则会覆盖后续内存, strncpy,设定内存不够,在不会添加到空字符,因此,需要手动为他加上空字符—真麻烦

存储类:
自动存储:函数内部定义的常规变量存储空间,自动变量,自动产生和消亡。局部变量。作用域为代码块 花括号内-----存储在栈
静态存储:整个程序执行期间都存在。一种是函数外定义,一种是static定义。
动态存储:new和delete管理的内存池—free store/heap,导致分配出来的最终不连续

第五、六章

内置类型,前后缀速度差多, 但是你写的类,前缀函数,值+1,然后返回, 但是后缀函数先复制副本,副本+1,再返回。 前缀块一丢丢

*++pt; //pt递增,再取值
++*pt;// 先取值pt,再递增,
(*pt)++
pt++;//后缀优先级更高,++作用于pt。而不是pt,但是解除引用是对原来的地址,这条语句执行完后,pt指向下一个

e.g.
double arr[5] = {1.1, 1.2, 1.3, 1.4, 1.5};
for(double x : arr)
cout << x << std::endl;

for(int x : {1, 2})cout << x << std::endl;for(double &x : arr) // &引用修改x = x * 0.1;

cin
cin.get(ch) 在 中参数已经被声明为引用了。 修改内存中值的方式:引用或者传递地址进去
函数重载:cin.get() cin.get(ch) cin.get(arr,20)

EOF ctrl +d/z 键盘模拟输入文件结尾----哨兵字符
检测到EOF, cin将eofbit和failbit都设置为1, 使用eof()和fail()查看。此时cin.eof() 返回bool值 true.
同样 eofbit或者failbit 为 1 则 fail()返回true
eof() 和fail()方法报告最近读取的结果,事后诸葛亮。
C语言中判断结尾:(ch = getc(fp))!= EOF
C++中,cin.fail() == false //test for EOF
程序中, 可以cin,clear()清楚EOF标记,但是键盘ctrl+Z则没办法

检测输入
while(cin.fail() == fail)
while(cin.fail()) //cin.get(char) 返回cin
while(cin)//while input is successful 可以检测输入失败的其他原因

ch = cin.get() 类似getchar()–>返回字符编码的int值
cin.get(ch)返回一个对象

C中引入 <iso646.h> C++中不用
ze &&–and ||–or !–not

字符判断函数库

5 > 3 ? 10 : 20 – 10

switch(integer-expression) // 类似VBA select case
{
case lable1 : statement // 标签须为整数
break;
case lable2 : statement

default : statement
}
continue 跳过余下的diamante,开始新循环;break 跳出该循环
文件读写–需要的时候再看

第七章

函数原型中变量名不是必须的
可以将数组塞进结构体返回
ansi C 中 不指定参数,留待后续定义参数列表 则参数括号为空, C++ 为(…)
函数参数通常按值传递–传值传值,传的是值的副本

函数声明中的数组名 实际是个指针 且函数使用原本的数组,因为指针,所以不是副本
e.g.
int fill_array(double ar[], int limit)
{
using namespace std;
double temp;
int i;
for(i = 0; i < limit; i++)
{
cout << “Enter value #” << (i + 1) 《《 “:”;
cin >> temp;
if(!cin) // 是否为有效输入
{
cin.clear(); // 回复输入
while(cin.get() != ‘\n’)
{
continue;
}
cout << “bad.\n”;
break;
}
else if(temp < 0)
break;
ar[i] = temp;
}
return i;
}

void swap_(int a[]) //这样就可以交换数组元素了–传递的数组参数实际传进去的是要给数组指针(int a*也可)
{
int temp = 0;
temp = a[0];
a[0] = a[1];
a[1] = temp;
}

尽可能使用const

  1. 可以避免由于无意间修改数据导致的错误
  2. 使用const使得函数可以处理const和非const实参,否则只能接受非const实参,因为非const的函数可能会修改const指向的位置,那const实参就没意义了。
  3. 如果条件允许,则应该将形参声明为指向const的指针,这样函数可以正确生成并使用临时变量

先介绍一条const的规则:const默认与其左边结合,当左边没有任何东西则与右边结合。重难点
const int* 与int const* const * int都表示“不能通过这个指针去修改那个变量, 可改变指针本身所指向的地址”。这并不能使得那个变量成为const
int *const,则是表示“一旦得到了某个变量的地址,不能再指向其他变量,可以通过指针改变其所指向的内容但只能指向该地址”,const此时修饰指针

bottom-up programming,从组件到整体,适合OOP。强调对数据的表示和操纵。
top-dowm programming 强调模块化设计方案,然后研究细节。
最终的产品都是模块化程序

函数与二维数组,参数是一个指针 ☆
原型:
typeName func(int (*ar)[4] );//二维数组 n行4列
typeName func(int ar[] [4]);//二维数组
typeName func(int *ar );//可表示一维数组
typeName func(int ar[] );//可表示一维数组

函数与结构
1.按值传递整个结构
2.传递结构地址
3.按引用传递

函数与string & array
const std::arraystd::string,Seasions Snames = {“Spring”, “Summer”, “Fall”, “Winter”} //二维数组

函数指针–指向函数地址的指针—☆ MMP重难点
函数地址:即函数名(后面不跟参数)。think()–函数, think–函数地址
声明函数指针的结构类似声明一个函数,必须指出参数和返回值类型。
e.g. double func(int) --> 函数指针声明:double (*pf)(int)–> pf 函数指针 int参数 double返回值
因为(*pf)代替func 都为函数。则pf就是函数指针
使用时候pf = func; double (*pf)(int); 即可调用func 当然 double pf(int)也可以,但是函数指针的特征
兵线了
const double * (*p_fun[3]) (const double *, int) = {f1, f2, f3} 指向三个函数的函数指针数组
typedef const double * (*p_fun)(const double *, int); //p_fun 就成为了类型名,
可这样使用:p_fun p1 = f1 /p1 指向f1函数
p_fun pa[3] = {f1, f2, f3}; //pa 是一个三个函数指针的数组
p_fun (*pd)[3] = &pa; //指向这个包含了三个函数指针的数组???

第八章

1.C++内联函数–不能递归

普通函数执行过程:执行到函数时候,保存当前位置,并跳转到函数内存地址,并将函数参数复制到堆栈,执行。结果
保存到寄存器。之后再跳回断点处继续程序执行。
内联函数,将函数编译代码与其他程序代码内联(相当于把函数复制到其他代码汇总,取代调用),无需跳转执行函数。
速度更快,但是内存占用更高。
当执行函数的时间很短的时候,内联函数更好。但是除非经常调用该函数,不然节省的时间绝对值不大。

使用内联,函数声明前加上inline 或者 函数定义前加上inline
一般直接将函数定义放在声明的位置,从而省略声明,能一行定义完的函数最合适,多行的不太好
出现在声明位置的函数定义,整个被当做了函数原型。

内联的原始实现—C语言中的#define

2.引用变量

–已定义的变量的别名,主要用作函数的形参,这样函数将使用原始数据,而不是副本。起到了和指针类似的作用(引用与被引用的指向同一块内存)
与指针区别
1.表示方法不同
2.必须在声明引用时将其初始化,不能像指针那样,先声明再赋值。
3.引用更接近const指针,初始化后,无法更改。

引用传递参数,可以改动原始值。按值传递使用的是参数的副本。
fun(x+3.0)有时可以执行是因为C++自动创建了一个临时变量,并初始化为x+3.0.然后再使用临时变量
——如果实参与形参不匹配,C++将生成临时变量。参数为const引用时候,才可以这么干。
引用参数是const,创建临时变量情况:
1.实参类型正确,但不是左值; 形参为const double 引用, 但是传给它的实参却是7.0这个数
则编译器创建一个匿名变量并初始化为7.0,并让引用指向他
2.实参类型不正确,但可以转换正确;
double edge = 99; 这种情况类型不符合,但是可以转换
(devpp mingw9.2 c++ 14)

左值:是可以被引用的数据对象:变量,数组元素,结构成员,引用和解除引用的指针C中,可以在赋值语句左边的实体视为左值,C++中常规变量和const变量都可视为左值左值:可以通过地址访问
非左值:字面常量(引号括起来的字符串除外,他们可以用地址表示),包含多项的表达式。

尽可能使用const

  1. 可以避免由于无意间修改数据导致的错误

  2. 使用const使得函数可以处理const和非const实参,否则只能接受非const实参,因为非const的函数可能会修改const指向的位置,那const实参就没意义了。

  3. 如果条件允许,则应该将形参声明为指向const的指针,这样函数可以正确生成并使用临时变量 。

    将引用声明为const,C++必要时生成临时变量
    左值引用: & int rats; int & rodents = rats
    右值引用: &&–》 double && rref = std::sqrt(36,00); cout << rref;

返回引用 参数& a 则 return a返回引用
不要返回函数终止时候不再存在的内存单元引用。返回输入的参数或者new一块新内存,返回指针
const free_throws & cloe(free_throws & ft)
{
free_throws * pt; //创建无名结构,并pt指向
*pt = ft;
return *pt //返回结构的引用
} //隐藏了new ,需要配套delete

传统的返回值,函数先return复制到一个临时位置,

只要=好左边是可修改的左值,长啥样子都行
const用于引用返回类型–不想修改的时候☆

string类定义了一种char*到string的转换功能,这样可以使用C-风格的字符串初始化string对象
函数内部局部变量不能被外部引用,但是可以被传出给C++自动创建的临时变量。之后消失

继承:将特性从一个类传递到另一个类 基类 派生类
基类引用可以指向派生类,无需强制类型转换
——这样可以创建一个可以接收基类对象的函数,调用时候也可以将派生类作为参数。

引用的使用
P291 8.2.7

3.默认参数

不同个数的参数列表可以调用同一个函数
省略了实参的时候。自动使用的值
编译器通过原型了解函数的参数数目
e.g. char * left(const char * str, int n = 1); 将值赋给原型中的参数 就行
对待参数列表的函数, 必须从右向左添加参数默认值,不能跳过,而提供实参赋值形参时候,会从实参列表左往右,不能跳过

4.函数重载–函数多态。使用多个同名函数

通过上下文确定要使用的重载版本
两个函数的参数类型,数目,位置相同–特征标相同–参数列表相同,变量名无所谓,返回值类型也无所谓
C++允许定义名称相同的函数,条件是参数列表不同。这样才可以重载。

其实也就是函数名器一样,其他乱七八糟的不一样,尤其是参数列表不一样。然后编译器根据参数列表自动选择合适的
函数实体运行,就是所谓的函数重载。

使用场景:执行形同的任务,但是使用不同类型的数据
C++编译器通过名称修饰将函数名称转换为加密的内部表示–按一定规则编码以区分

此种情形 可能会使 实参与形参不匹配的问题。导致产生匿名变量的问题得到解决

5.函数模板

使用泛型(无具体数据类型)来定义函数–而不是具体数据类型–通用编程,
实例化时,将类型作为参数的传递给模板创建该类型函数
实际上市对函数重载的进一步抽象

模板创建:
tmeplate //标明要建立一个模板
void Swap(AnyType &a, AnyType &b)
{

}
typename 可以用class替代,作用一样
模板知识告诉编译器如何定义函数,不创建具体函数。
同一种算法用于不同类型的数据时候,请使用模板
使用方法:
//声明
tmeplate
void Swap(AnyType &a, AnyType &b)
int main()
{
}
//实体
tmeplate
void Swap(AnyType &a, AnyType &b)
{
}
当传递进实参,编译器自动创建合符合实参类型的函数,程序员看不到生成的函数
模板通常放在头文件中

模板重载

–并非所有类型使用相同的算法,可以同名模板不同参数列表实现不同算法。模板重载的函数特征标必须不同。并非所有的模板参数都是模板参数类型。具体类型也可以。

具体化:模板失效的时候—传入的数据类型,模板无法生成函数,为具体数据类型提供具体的函数
方法:
对于给定的函数名,可以有非模板函数,模板函数和显示具体化模板函数以及他们的重载版本
显示具体化的原型和定义以 template<> 打头,并通过名称来指出类型
而非模板函数优先于具体化和常规模板, 具体化优先于常规模板,
e.g.
//非模板函数 -1
void swap(job &, job &);
//模板 --3
template
void swap(T &, T &);
//具体化模板函数 --2
template <> void swap(job &, job &);
可以简写为:
template <> void swap(job &, job &);
因为job是一个具体化的,因此不需要重申

//模板都需要在main前声明,main后提供定义
int main()
{double u, v;swap(u, v); //没有对应的具体模板和具体函数,使用模板job a, b;swap(a, b); //有具体化的模板函数,使用之
}

实例化:编译器通过模板生成的对应具体数据类型的函数。分显式和隐式
显式实例化:template void swap(int, int) //意思是:使用swap()模板生成int类型的函数定义
显示具体化:template <> void swap(int &, int &)
template <> void swap(int &, int &) //意思是:不使用swap()模板,使用专门为int
// 类型显示定义的函数定义
在同一个文件中使用同一种类型的显示实例和显示具体化将出错

隐式实例化,显式实例化和显示具体化统称 具体化,均表示使用具体类型的函数定义,而不是通用描述

函数重载,函数模板,函数模板重载–重载解析P290 8.5.5
1.创建候选函数列表,包括函数和模板函数
2.创建可行函数列表,都是参数数目正确的函数
3.匹配查询:
//整数类型不可被转换为指针类型
1.完全匹配,常规函数优先于模板
2.提升转换,如char和shorts 自动转换为int, float转换为double
3.标准转换,如int转换为char,long转换为double, char到float
4.用户自定义

    完全匹配&最佳匹配允许从实参到形参无关紧要的转换 P290-----int实参 和 int&匹配有时候,完全匹配的有两个,但是:指向非const数据的指针和引用优先 与非const指针和引用参数匹配然后const和非const的区别只适用于指针和引用指向的数据显式优先于隐式部分排序规则最具体--编译器认为哪种类型执行的转换最少

重载解析将寻找最匹配的函数。
如果只存在一个这样的函数,用
如果存在多个,其中只有一个非模板函数,则选择该函数
如果存在过个合适的函数,且都是模板函数,但只有一个函数比其他函数更具体,转换更少,则选择该函数
如果有多个同样合适的非模板函数或模板函数,但没有一个函数比其他更具体,则报错,

    自己选择可以将模板实体定义在文件开头,充当原型。从而无需编译器选择

第九章

头文件内容:函数原型,使用#define和const定义的符号常量, 结构声明, 类声明, 模板声明,内联函数
结构变量知识告诉编译器如何创建变量,相当于创建了一个数据类型。
用户自动以的头文件,用双引号, 编译器首先查找工作目录或者源代码目录,如果没找到,则在标准位置查找
#pragma once // 防止文件被包含多次
#ifndef XX_H_
#define XX_H_
#endif
并不能防止包含两次,知识让编译器忽略第一次包含之外的内容

名称修饰,编译器按照一定规则将函数名和参数列表加密编码以确定唯一性。
不同编译器对同一个函数生成的修饰名称可能不同,从而无法链接。

存储的连续性、作用域和链接性

C++描述存储方式
自动存储持续性、静态存储持续性、动态存储持续性
线程存储持续性:变量使用thread_loccal声明,则线程生命周期内,变量存在。

链接性描述 名称 如何在不同单元间共享。
性质为为外部的那些名称可以在文件间共享。
为内部的只能在一个文件内的函数间共享。自动变量的名称没有链接性,无法被共享。

作用域:局部,全局(文件作用域,定义位置到文件结尾)
自动变量的作用域为局部 静态变量作用域取决于它被如何定义的
函数原型作用域,旨在参数列表的括号内可用
在类中声明的成员作用域是整个类
在名称空间声明的变量作用域是整个名称空间
C++不能在代码块内定义函数

自动存储持续性:
默认情况下:函数声明/定义中的参数和变量存储持续性为自动,作用域为局部,没有链接性
当代码块内外存在同名定义,内部掩盖外部定义,直到代码块结束。
自动变量 初始化:值已知的表达式
自动变量的数目随着程序的开始结束而增减–栈–放在相邻的内存单元中
寄存器变量 register–占位置而已,C++中不用了

静态存储持续性:–程序执行期间存在,数目不变,编译器只将他们放在固定的内存块中存储静态变量
如果没有显式的初始化,编译器把他们设置为0.静态数组和结构成员的每一位都为0
外部链接性 代码块外声明它 其他文件使用
内部链接性 代码块外声明它,用static限定符(比外部链接性多了个static) 类似自动变量,但是自动变量用到时才创建,而这个程序一开始就在 当前文件可用
无链接性 代码块内部声明它,static限定符P310 当前代码块可用

静态变量初始化:默认零初始化,常量表达式初始化--两者被称为静态初始化,编译时初始化动态初始化--编译后初始化
一开始,所有常量都被初始化为0,之后根据实际再更正外部链接性--外部变量--静态--全局变量
每个使用外部变量的文件都要声明它,且只能有一次定义。1.定义声明--定义 给变量分配空间2.引用声明--声明 不分配空间,引用已有的变量 extern 不用初始化,否则成了定义声明作用域解析符:: 放在变量名前面,表示使用变量的全局版本外部性,多文件程序不同部分共享数据;内部性,同一个文件不同函数间共享数据
无链接性: 代码块内static 一直存在,不可共享,两次调用之间不用对这个变量初始化

存储说明符:说明有关存储类型 static extern。。。
限定符:cv-限定符
const ——定义类型为常量 类型。直接修改编译不通过,间接修改则结果未定义
volatile ——定义类型为易变 类型。 告诉编译器直接查找两次,而不是对变量进行缓存优化
两次使用了某个变量的值,编译器会将值缓存在寄存器中,而不是查找两次,这种优化,假设两次之间变量的值不会变化。不将变量声明为volatile,则进行这种优化。
const volatile int i = 10; const 的可修改了

    mutable 即使结构(类)变量为const,其对应的某个成员也可以被修改const
默认的全局变量是外部的,加上const就成了内部的,和加上了static一样p318
当希望某个常量是外部的 可以用external 覆盖constexternal const int a = 4; 变为外部了重点  http://t.csdn.cn/vx9WT   :C++:头文件中的const全局变量// 头文件中包含的变量声明,在不同cpp文件中各自赋予不通的值--ok,//(const 无法修改)头文件中定义并赋予初始值的变量加不加const, 都没有报警,且正常输出----这是因为第一次编译产生的.o文件残留,vscode与devc++会出现此种问题,vs则依然直接报错// 一个cpp文件外部性的全局变量,另一个文件extren后可以重赋值, 除非它加了static//包含头文件,得给绝对路径……这也是vscode的臭毛病

函数和链接性
默认外部。可以加上external 标记函数声明的实体在其他文件
若只想内部,则声明,定义都加上static
非内联函数,只能包含一个定义,外部性的函数来说只能有个一个文件定义了函数(如库文件),使用了该函数的文件 都要要有其原型
内联函数允许将定义放在头文件,被多个文件包含,相当于到处都定义了一遍,但是定义必须一样。

内存动态分配–只被new和delete掌握

编译器三块独立内存:静态变狼,自动变量,动态变量
int * pi = new int (9)
int * ar = new int [4] {2,4,5,6}
new失败则异常运算符new 和new[]分别调用如下函数
void * operator new(std::size_t);   void * operator new[](std::size_t)----分配函数
void * operator delete(std::size_t);   void * operator delete[](std::size_t)----分配函数int *pi = new int; --> int *pi = new(sizeof(int));

定位new运算符

,指定要使用的内存位置,返回要到的地址,里面没有数据哒
#include
然后将new运算符用于提供了所需地址的参数
struct charff…
char buf[6]

charff *p1, *p2;
//通常用法,放在堆中
p1 = new charff
p2 = new int[20]
//指定了位置,放在buf中
p1 = new (buf) charff;
p2 = new (buf) int[20]注意:这些都是从指定的内存块的开头要内存。
int a[20];
int *p;a[0] = 12;
p = new(a) int;
*p = 88;

这样,88会覆盖12 在a[0]
定位new 和常规new区别
1.
如果内存格式与需要分配的格式不同,程序会使用(void*)将内存格式强制转换成需要的
定位运算符new不管那些内存单元被使用,也不查找未使用的内存块。让程序员自己管,提供一个偏移量
p = new (buf + N * sizeof(int)) int[N] //从buf起始地址开始偏移了N个int大小的内存

但是常规new没有这些烦恼,因为它把他放在了堆中
  1. 常规new会自己找新块 ,定位new不会
  2. 常规new,delete后,块可复用
    定位new,使用的buf是静态内存时,不可以delete,然后delete只能清楚常规new分配的堆内存,不归delete管,当然,如果buf是new创建的,那delete就可以

定位new可以将信息放在特定的硬件地址处
定位new返回地址,转换为 void*

定位new 调用一个接收两个参数的new()–> 指定请求的字节数指定的位置
int *p = new int -->new(sizeof(int))
int p = new(buf) int -->new(sizof(int),buf)

9.3 名称空间

声明区域、潜在作用域,(实际)作用域

定义一个新的声明区域来创建命名的名称空间,避免与另外一个名称空间发生冲突,同时允许程序其他部分使用该名称中的内容

名称空间可以是全局的,也可以位于另一个名称空间中,但不能位于代码块中,因此,在名称空间中声明的名称链接性是外部的,除非引用了常量
还存在一个系统给的全局名称空间
作用于解析符 ::
未被装饰的名称–未限定的名称;包含名称空间的名称–限定的名称

using声明和using编译指令
using声明使特定的标志符可用 之后就不需要名称前缀解析 就和普通变量一样,可以覆盖全局变量
using Jill::aa; // using声明
using声明之后不可以定义同名变量,不然会报错多次定义,但是你可以直接用解析符直接用
using编译指令使整个名称空间可用
using namespace std;
namespace a{
int aa;
}
int main(){
using namespace a;
aa = 2;
double aa;
aa = 3.1;
std::cout << aa << std::endl;
std::cout << a::aa <<std::endl;
return 0;
}

本地新的同名变量可以覆盖其他名称空间里的同名变量,但是可以通过解析符调出来
using编译指令的空间名称只能在一个大的代码块中生效
//这个也是能运行的
using namespace std;
int main()
{
using namespace a;
aa = 2;
double aa;
aa = 3.1;
cout << aa << endl;
cout << a::aa <<endl;
return 0;
}

或许避免使用编译指令
尽量std::cout using std::cout这类
main前std是将所有内容都放到全局名称空间中,方便
main后std是将头文件放到名称空间std中,然后using将该名称空间在main中可用

名称空间可以套娃,解析符套娃而已
namesapce a {
namesapce b {}
}
using namespace a; 则a b 都导入了
或一个空间包含其他空间的内容
namespace aa{
using bb::c;
}
访问方式 std::cin >> aa::c //省略了c的前缀bb::
std::cin >> bb::c 也是ok的
要是没冲突
using namespace aa;
cin >> c; //using命令是可以传递的
可以给名称空间创建别名: namespace ml = my_love::girfild:: … (后续可以有一串) 使用: using ml::name 简化对嵌套名称空间的使用

未命名点灯名称空间,不可以using 只能本地使用

名称空间指导规则

1.使用在命名的名称空间中声明的变量,不使用外部/静态全局变量
2.函数库和类库,放到名称空间中
3.不要在头文件中使用using编译指令。因为,其他文件会包含头文件,☆
4.导入名称,用作用域解析运算符:: 或者using声明
5.using声明,首选将其作用域设置为局部而不是全局。


#pragma once 确保只编译一次
字面量:用于代码中编写数字或字符串:不同进制的数字表示,单字符,字符串。。。。。。
C++14,允许数字间用引号分隔:int a = 12’456

基于范围for循环 for(int i :arr)

trailing return type 用来支持替代的函数法:
auto func(int i) ->int
{
reutrn i + 2;
}

C++14 类型推断
auto用途:1 编译时,自动推断变量类型 auto x=123 2.替代函数语法 3 函数返回类型推断 4 通用的 lambda表达式
因自动推断表达式类型,一次不需要引用限定符和const限定符,它会自动去除附加给变量的限定符,建立一个副本。如果要求cosnt 则:const auto& a;相当于又加回去了。
decltype:把表达式作为实参,计算出表达式类型。 int x = 123; decltype(x) y = 456;
不会去除任何附加给变量的限定符。decltype(func()) f1 = func();这里func()制定两次,C++14 优化为
dectype(auto) f2 = func();
C++ 中空指针 = nullptr。为防止释放指针指向的内存后再使用指针,可以将其设置为nulllptr
delete a; a = nullptr;

智能指针中:在超出作用域,会自动释放内存
std::unique_ptr,类似普通指针,只属于它指向的对象,发生异常时也会释放资源,这个可以存储C风格数组
int * p = new int; ====> auto p = std::make_unique(); c++14
之前的c++ std::unique_ptr p(new int);
可以指向任意类型的内存,是个模板,因此需要<>指定内存类型

shared_ptr 允许数据多方所有。每次指定shared_ptr,都递增一个引用技术,拥有者+1。超出作用域,递减引用技术,为0则释放指针引用的对象,不能在这里存储数组。std::make_shared<>() 创建

weak_ptr可以观察shared_ptr,而不会改变shared_ptr的计数
《高级C++》ing P27

异常处理(cpp编译器不强求捕获全部异常):堆栈变量的行为
函数定义异常行为,发生时,throw对应参数,try-catch捕获并抛出显示。

int *p = new int[10]
delete [] p
array<int, 10> p
vector a
unique_ptr p(new int)
tmeplate //标明要建立一个模板
void Swap(AnyType &a, AnyType &b)
{

}

以下Cpp重点


第十章 对象和类

OOP特性:抽象,封装和数据隐藏,多态(重载),继承,代码的可重用性

过程性编程方法,考虑的是运转的步骤,然后考虑如何表示。
OOP 考虑数据的表示和使用。数据类型+操作,定义+实现接口 和 数据存储

2.抽象和类
指定 基本类型 要完成的三项工作:
决定数据对象需要的内存数量
决定如何结识内存中的位
决定针对对象执行的操作和方法
C++中的类
类规范:
类声明:以数据成员的方式描述数据部分,以成员函数(方法)的方式描述公有接口
类方法定义:描述如何实现成员函数

类定义–接口放在头文件中; 实现–类方法的代码。放在源代码文件中
类–对象/实例

  1. 访问控制:private/public
    使用类对象可以直接访问公有部分public
    通过公有成员函数或友元函数访问私有成员private
    公有成员函数是程序和对象的私有成员之间的桥梁
    数据隐藏:防止程序直接访问数据被
    protected–第三个访问控制关键字

    公有接口要与实现细节分开。
    公有接口表示设计的抽象组件,实现细节独立出来–封装。

    数据部分通常放在私有部分,接口的成员函数放在公有部分

    类对象默认private,强调的可以标出来
    结构的默认访问类型为public
    //first step
    class className
    {
    private:
    data member declarations
    public:
    member function prototypes
    };
    //second step 实现函数,可以在类声明中就给出定义而不是声明原型,但通常还是单独定义,除非
    函数太小。
    char * a::func()

    class a //类接口
    {
    private:
    char…
    public:
    void func(…)
    }
    2.成员函数的实现
    使用作用域解析运算符 :: 来标识函数所属的类
    类方法可以访问类的private组件–接口
    a::func()函数的限定名
    func 全名的缩写, 非限定名 只能在类作用域 中使用

定义位于声明中的函数 都将自动成为内联函数。类声明通常将短小的成员函数作为内联函数。
也可以在类声明之外定义成员函数,并使之成为内联函数----实现函数时使用inline限定符P348如果已定义的函数多于一行,编译器会忽略 inline 限定符
在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符方法只能调用本类实体的对象的成员
同类的不同对象,数据占据不通过的内存块,但是调用的方方都在同一个代码块调用成员函数--> 发送消息
将同样的消息发送给不同的对象将调用同一个方法,应用于不同的对象

10.3 类构造函数和析构函数

因为数据被设计成类私有,程序无法直接访问,只能通过成员函数访问,因此需要设计合适的函数来初始化对象变量
设计成公有的的确可以解决问题,但是违背了数据隐藏的意愿

类构造函数,专门用于构造新对象,将值赋给他们的数据成员–>C++为这些成员函数提供了名称和使用语法,程序员
需要提供方法定义,

构造函数没有返回类型。原型位于类声明的公有部分
e.g.
//声明的原型
func()
//可能函数定义或者实体
a::func()
{
}
程序声明对象时,将 自动调用 构造函数–没有返回类型,连void 都没有的函数–构造函数

构造函数的参数不是类成员,而是准备赋给给成员的值,所以,参数名不能与类成员相同,
可以在数据成员名前加前缀m_, 或者加后缀 _

显式地调用构造函数:className object = func();–>定义了一个对象,并用构造函数func()对成员变量初始化
隐式地调用构造函数:className object (…)–>与上面那个等价
与new一起用:className *p_object = new func(…);–>创建一个对象,并初始化赋值,并将对象地址赋给p_object
这样,对象是没有名称的,但是可以使用指针来管理该对象

无法使用对象调用函数,object.func()无效。

默认构造函数 未提供显式初始化值,用它来创建对象的构造函数。
如果没有提供任何构造函数,C++自动提供默认构造函数,它是默认构造函数的隐式版本,不做任何工作。
e.g. object::func() {}. 所以className object_1 创建新的对象,但不初始化成员 和int x, 不给值一样
默认构造函数没有参数,声明中不包含任何值

当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。
如果为类定义了构造函数,则需要额外提供默认构造函数, 不然类似 int x 这样的语句就会报错。这样是为了禁止
创建未初始化的对象。但是你非得不初始化,则不接受任何参数的默认构造函数是必须的。

创建默认的构造函数
1.给已有的构造函数提供默认值。。。啊这,
2.通过函数重载来定义另外一个函数–没有参数的构造函数,与构造函数同名就行
两种方法选一个
e.g.
通常需要初始化一个已知的合理值,因此用户定义的默认构造函数通常给所有成员提供隐式初始值
className::func()
{
给成员赋值
}

设计类时,应提供对所有成员变量做隐式初始化的默认构造函数,声明对象变量时,就不用显式初始化
概念:构造函数,默认构造函数,显式调用, 隐式调用P355
构造函数可以用类名,这样同一漂亮些

析构函数
对象过期,程序自动调用一个特殊的成员函数–析构函数
(什么都不做的析构函数,编译器可以自己生成)

析构函数在类名前加上  ~
//原型
~className();
//定义
object::~objct()
{//可以加点东西,显示
}编译器决定什么时候调用析构函数,不应该显式调用析构函数
静态存储类:程序结束的时候自动调用
自动春畜类:程序执行完代码块时调用--该对象是在代码块内定义的
new创建的:它将驻留在栈内存 或者自由存储区,当使用delete来释放内存时候,析构函数自动调用
临时对象--程序创建的:结束对该对象的使用时候,自动调用其析构函数类对象过期调用析构函数,必须要有。如果程序员没提供,编译器自己声明一个默认的,并在发现删除对象的
代码后,提供默认析构函数的定义---真是狗,不到最后不给定义占空间

e.g.
//头文件
#ifndef OBJECT_H_
#define OBJECT_H_
#include
//class
class object
{
private:
std::string str;
int a;
void func(…);
public:
//两个构造函数
object(); //默认构造函数
object(…);//接收参数的构造函数
~object(); //析构函数

    void func_1(const std::string & b);void func_2();
}

//实现
#inlcude
#include “object.h”

object::object()
{//变量设置为默认值
}obiect::object(const std::string & co, long n, ...) //接收参数
{//接收的值赋给成员变量
}object::~object()
{//啥都不用写
}void object::func_2(paramete)
{using std::cout;//body
}

//客户文件–测试用
#include
#include “object,h”

int main()
{   //跳出这个代码块后,调用析构函数,不然只能等main结束后调用析构函数//不加大括号,意味着黑框框关闭前,不会调用析构函数,导致无法看到析构函数的消息,如果有的话{using std::cout;object object_1 (argument); //隐式调用构造函数 object.show();object object_2 = object (argument); //显式调用构造函数object_1 = object(argument); //临时对象 }return0;
}

显式调用构造函数 一种是直接创建对象,并初始化,另一种是先创建一个临时变量,之后将内容复制到要创建的类
,立即或者过一会儿调用析构函数
object object_1 = object(argument)—初始化,可能会创建临时对象—效率高高哒
object_1 = object(argument)—赋值,总是会赋值前创建一个临时对象

C++列表初始化
只要提供和某个构造函数参数列表匹配的内容,用大括号括起来 就可以
object object_1 = {argument}//显示调用构造函数 用的是括号
//在这里,等于号可以省略

object object{};//与默认构造函数匹配还有std::initialize_list的类 可当做函数参数或方法参数的类型,这个类可以表示任意长度的列表只需要所有列表项的类型都相同或者 可转换为相同的类型

const 成员函数
const object object_1 = object(argumet);
object.func();

C++编译器将拒绝第二行,func()不能保证调用对象不被修改
将声明变为:void func() const;
实体为:void object::func() const
通过以上来保证不会修改const 类,定义的函数被称为const成员函数
就像尽可能将const 引用和指针作为函数形参一样, 只要类方法不修改调用对象,就应该将其声明为constP362小结只接受一个参数的构造函数允许使用赋值语句将对象初始化

10.4 this指针–涉及到多个对象
const object & func(const object & s) const;
参数-显式访问另外一个对象,隐式访问改方法所在的对象
参数const,不修改显式访问的对象
括号后const,函数不修改隐式访问的对象
该函数返回了两个const之一的引用,返回类型也就是const

const object & object::func(const object & s) const
{...return s; //参数对象 ...return *this; //隐式调用的函数所在对象
}不用this指针,隐式调用的对象没有别名怎么返回。
this指针指向用来调用成员函数的对象(this被作为隐藏参数传递给方法)每一个成员函数,报货构造函数和析构函数,都有一个this指针,指向调用对象。如果方法需要引用整个调用对象
可以用 *this--作为调用对象的别名  (this本身是地址)

10.5 对像数组
同一个类的多个对象,就成了数组嘛 和基本数据结构一样
object object_1[N];//调用默认构造函数
这个类要么没有显式定义任何构造函数(此时,使用隐式构造函数),要么定义了显式默认构造函数。

调用方法 object_1[num].func();const object *p = object_1[num].func[object_1[num+1])
可以用构造函数初始化对象数组元素,但是每个元素都得调用一遍构造函数
列表初始化这个时候就是个好东西了
const int n = 4;
object object_1[n] = {object(argument),...object(), //当类存在多个构造函数的时候,可以对不同元素使用不同的构造函数
}初始化对象数组方案:线使用默认构造函数创建数组元素,然后花括号中的构造函数们创建临时对象,接着,将临时对象复制到相应元素中。因此,要创建类对象数组,类必须有默认构造函数。

10.6 类作用域
类中名称,作用域是整个类,类外不可知。所以,可以在不同类使用同名
声明类 相当于声明了一个新的数据结构,没有实体。

要想常量伴随类1.在类中声明一个枚举,--不会创建类数据成员,所以,所有对象中都不包含枚举。类似defineclass aa{private:enum {Months = 12}; //只当做符号常量,因此不用提供枚举名}2.calass aa{private:static const int Months = 12; //静态变量,一开始就有,const,不可改变}要想类中枚举enum calss name{...}; //struct此时可以代替classname choice = name::no1; name枚举中的第一个C++11 有些情况下,常规枚举类型自动转换为整型,如赋给int变量 或者比较表达式但作用域内枚举不能隐式地转换为整型,显式可以:int a = int(name::no1);默认情况下,类作用域内枚举的底层为int,但是,可以选择其他enum class : short name{...}; // short替代int成为底层类型。但是依然必须是整型,不能是double等

第十一章 使用类
11.1运算符重载–实现多种功能
重载运算符之前,需使用运算符函数:operatorop(argument-list)
e.g.
operator+(),重载 + 运算符
op必须是C++的运算符
operator 将重载[]运算符,[]是数组索引运算符

    object_1 = object_2 + object_3;编译器改为:object_1 = object_2.operator+(object_3); //显式调用object_3(参数),隐式调用object_2Time Time::sum(const Time & t) const{Time sum;return sum;}total = coding.sum(fixing)-----1参数引用,提高效率;返回类型不是引用,,函数创建了一个新的对象,返回它,函数将创建该对象的副本,调用函数可以使用它。而如果也返回引用,新对象的作用域局限于该函数,函数结束,对象销毁。重载运算符的函数头Time Time::operator+(const Time & t) const{同上}total = coding.operator+(fixing) //函数表示法----2total = coding + fixing //运算符表示法,+ 左侧是调用对象,右侧是传递的参数-----3以上三种效果一样operator+()函数的名称使得可以使用函数表示法或运算符表示法来调用它,编译器根据操作数的类型来决定如何做 因此 调用该方法时 t1 = t2 +t3 +t4 合法

11.2.2重载限制
1.重载后的运算符必须至少有一个操作数是用户定义的类型,这将防止用户为标准型重载运算符。所以 - 号不能重载为两个基本数据类型的加号
2.运算重载符不可以改变语法结构。不可以改变操作数,不可以改变优先级,不可以改变结合性,不可以创造新的预算符, 相当于现有的运算符运算的数据类型是用户定义的外,其余不变
3. = 赋值运算符 ()函数调用运算符 []下标运算符 ->通过指针访问类成员的运算符 只能通过成员函数重载 其余都可以通过成员函数或者非成员函数重载
4.最好保留运算符原有的基本功能

11.3友元
C++控制对类对象私有部分的访问,通常,公有方法提供唯一的访问路径
但是友元可以提供另一种形式的访问权限:友元函数,友元类,友元成员函数

为类重载二元运算符,常需要友元
A = B。operator*(2.75) 对应于 A = B * 2.75,
但反过来A = 2.75 * B  但是2.75 不是对象,不能调用成员函数来替换该式子
解决法一,只能按照B*2.75写
法二,非成员函数--不是由对象调用,使用的所有值,包括对象都是显式参数
A = 2.75 * B----->>>>> A = operator*(2.75,B)
对应原型 Time operatpr*(double m, const Time & t)
但是因为不能直接访问类的私有数据,因此需要友元函数 11.3.1 创建友元函数---friend
e.g.friend Time operator*(double m, const Time & t);1.虽然是在类中声明,但他不是类成员函数,不能用成员运算符调用2.它与成员函数有着相同的访问权限Time operator*(double m, const Time & t) // friend 不需要出现在定义中{和之前一样{这样 2.75 * B  和B * 2.75 都找到各自的成员数
只有类声明可以决定哪个函数是友元Time operator*(double m, const Time & t)
{return t * m; //使用t.operator(m)
}
这样可以交换乘法操作数的顺序,将友元函数编写为非友元函数
该函数作为友元,可以当作类的正式接口之一,如果需要直接访问私有数据,只需要修改函数定义
tip,如果需要为类重载运算符,并将非类的项作为第一个操作数,可以使用友元函数来反转操作数顺序11.3.2 常用的友元:重载 <<运算符
重载 << 使之能与cout一起来显示对象的内容
版本1:使用友元函数,谁重载,谁是第一个操作数 ,如Time类trip对象,输出trip对象需要重载cout这就成了trip << cout 但是 可以通过友元函数重载void operator<<(ostream & os, const Time & t){os << t.hours ;}相当于借着友元函数不需要对象调用,想办法实现正常的输出功能Time类声明operator<<()函数是它的一个友元函数,但不是ostream类的友元operator()<<使用了Time类的数据,所以,必须是Time类的友元 同时ostream对象作为一个整体在使用,所以不必是友元另外,ostream 对象 cerr将输出发送到标准输出流——默认心啊时期,也可以将标准错误流重定向到文件ofstream对象可将输出写入文件,通过继承,ofstream可以使用ostream方法,这样operator<<()定义类数据写到文件和屏幕上,因此只需要传递经过适当初始化的ofstream对象调用cout<<trip使用cout对象本身,而不是拷贝,因此需要按引用来传递对象版本2:因为即使重载后cout<<trip 可行 但是cout <<"haha" <<trip 仍然不行这就需要就每次cout 都返回一个指向ostream对象的引用--返回一个指向调用对象的引用ostream & operator<<(ostream & os, const Time & t){os << t,hours <<" haha" <<t.minutes;return os; // 返回指向调用对象的引用}在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符

11.4 重载运算符:作为成员函数还是非成员函数
Time operator+(const TIme & t) const;//成员函数 ,一个操作数通过this指针隐式传递,另一个操作数作为参数显示传递
friend Time operator+(const TIme & t1, const Time t2);//友元函数,二元运算符
以上两种只能选其一
11.5 矢量类 vector
向量(Vector)是一个封装了动态大小数组的顺序容器(Sequence Container)
399
自定义名称空间
namespace NAME
{

}
内置数学函数使用角度时以弧度为单位
描述对象状态的成员--状态成员
如果方法通过计算得到一个新的对象,则应考虑是否可以使用类构造函数来完成这种工作,不仅可以避免麻烦,还可以确保鑫的对象是按照正确的方式创建的
P407同一个运算符可以重载多次 如 - 即可以表示相减  也可以表示负号
namespace VECTOR
{class vector{POL;}
}int main()
{using namespace std;
using VECTOR::vector; //使用vector类vector::POL;
}随机数:
rand()返回0到某个数之间随机整数,使用一个初始种子通过算法生成随机数,该随机数当做下一次生成的种子。但是,实际上这是伪随机数,通常生成的可能都是一样的值
srand()允许覆盖默认的种子,重新启动另外一个随机数序列。使用time(0)的返回值来设置种子, time(0)返回当前日期从0点开始的秒数(扩大解释:time()接收time_t变量的地址,将时间放到该变量中,并返回他,将0用作地址参数,可以省略time_t变量声明)因此srand(time(0))每次运行程序都将设置不同的种子,使得随机数看上去更为随机 rand 和srand共用了种子?

rand 有返回值, srand()没有返回值 (cstdlib ctime)

结果输出:
包含<fstream>,并声明一个ofstream对象,并与一个文件关联
#include <fstream>
ofstream fout;
fout.open("file.txt")
fout << result << endl;//调用友元函数operator<<(fout,result),导致引用参数os指向fout,从而输出文件

11.6 类的自动转换和强制类型转换
互相兼容的数据类型,C++将自动转换,保留或降低精度
但是指针不可以转换为int

基本数据类型--类对象 --类函数
classname object;
object = 15.6; //使用了定义的object(double)来初始化
程序使用定义好的构造函数创建临时的无名classname类对象,将15.6作为初始值,    随后采用逐成员赋值的方式将临时对象的内容复制到object中--隐式转换,自动进    行
只有接收一个参数的构造函数才能作为转换函数强制转换int(2.5)和(int)2.5 都可以。但是如果第二个参数提供默认值,如
object(int stn,double lbs = 0) 则可以,实际上还是只能输入一个参数值当然,当你需要关闭自动转换,这样做
explicit object(double lbs);//关闭隐式转换,但显式强制转换仍然ok
object = classNmae(19.6)//ok
object = (classNmae)19.6 //ok
object = 19.6 //ng

11.6.1 转换函数–运算符函数–operator
int可以转化为对象,反之也可以。构造函数只用于从某种类型转换到类类型,相反的操作需要转换函数–用户定义的强制类型转换
double lbs = double(object);//手动强制转换
double lbs = (double)object //以上两个显示
double lbs = object;//让编译器决定,隐式

转换函数的定义:
operator typeName();1.转换函数必须是类方法--需要通过类对象来调用,从而告知函数要转换的  值2.不能指定返回类型3.不能有参数;
e.g.operator double();在没有提供显示强制类型转换的情况下,程序不会自动转换(二义性报警,因   为有多种可能),除非定义了唯一可行的转换函数 则自动隐式的来转换当你不希望转换函数自动运行时,这么做
explicit operator typeName();//只能强制转换
或者int object::obj_to_int(){return int(pounds + 0.5);}替换掉object::operator int(){return int(pounds + 0.5);}这样int plb = object非法int plb = object.obj_to_int()就行
P419

11.6.2 转换函数与友元函数
成员函数重载加法:P420
Stonewt Stonewt::operator+(const Stonewt &st) const
{
double pds = pounds + st.pounds;
Stonewt = sum(pds);
return sum;
}
友元函数实现
Stonewt operator+(const Stonewt & st1, const Stonewt & st2) const
{
double pds = st1.pounds + st2.pounds;
Stonewt sum(pds);
return sum;
}
二选一,均可以实现以下
Stonewt jest(9,12);
Stonewt best(12,8);
Stonewt total;
total = jest + best;
如果有Stonewt(double)构造函数,则可以这样
Stonewt jest(9,12);
double best = 176.0;
Stonewt total;
total = jest + best; //best在后 jest是对象
等效于:
total = jes.operator+(best);//成员函数

但是只有友元函数
Stonewt jest(9,12);
double best = 176.0;
Stonewt total;
total = best + jest;
等效于:total = operator+(jest, best);//友元函数
因此,建议加法重载建议定义为友元函数,可以让程序更容易适应自动类型转换double量与object量相加:
1.友元函数,不用对象调用,两个参数,然后让接收单值的构造函数将double转换为object类型
2.将加法运算符重载为一个显式使用double类型参数的函数
objec operator+(double x); //成员函数
friend object operator+(double x, object & s);这样,
total = object + double与成员函数operator+(doublex)完全匹配,隐式转换,过程少,但是调用构造函数多,增加开销
total = double + object与友元函数operatpr(double x, object s);完全匹配,正好相反
经常需要double与object对象相加,重载加法,偶尔使用,则依赖自动转换,但是保险起见,显式转换为妙 P421

第十二章 类与动态内存分配
12.1动态内存和类
静态类成员:
用指针声明数组,可以在使用的时候给它分配空间。避免了预定义字符串的长度
静态存储类,无论创建了多少对象,程序都只创建一个静态变量副本。类的所有对象共享一个静态成员
就像家里的电话号码,全家使用一样

不能在类声明中初始化静态成员变量,因为声明只是告诉你如何分配内存,十个模板,不参与实际内存分配
可以在类声明之外使用单独的语句进行初始化,因为它是单独存储的,不是具体对象的组成部分,只是用到的时候调用一下。声明需要static 但是初始化不需要
静态成员变量不可以在类声明中初始化,因为它是在头文件中,头文件可以到处有,可能会出现重复初始化。初始化方法是使用作用域运算符指出它是哪个类的。注意:如果静态成员是整型或者枚举 const,则可以在声明中初始化。
相当于类对象之间共享的变量,可以统一管理同一个对象的所有类P429
因为指针的缘故,字符串并不是保存在对象之中,而是单独保存在堆内存中--new的缘故。对象仅保存了去哪查找字符串析构函数调用时机:
总结
1、在匿名对象使用完后立刻调用析构函数;对象生命周期结束
2、在栈区的对象,空间被释放后立刻调用析构函数;
3、在堆区的对象,空间被delete后调用析构函数;free不能调用;调用delete,会删除指针类对象。
4. 对象A是对象B的成员,B的析构函数被调用时,对象A的析构函数也会被调用重点
P431--headline2导致析构函数调用的解释
类对象作为函数参数传递时,将会产生对象的副本,而这个副本是调用类中的拷贝构造函数实现的,如果类中没有自定义拷贝构造,编译器会自动生成一个缺省的函数,该函数有以下特点:
1、对于基本类型的成员变量,按字节复制
2、对于类类型的成员变量(成员子对象),将自动调用相应类的拷贝构造函数来初始化
3、指针只是简单的赋值,而不会自动分配内存。当退出该函数时,副本将调用析构函数,删除该指针,指针所指向内存也已被释放。将原始对象只保留了一个指针值。当在类中释放对象再次调用析构函数,会出现double free 的错误。
两种解决方式:
1、自定义拷贝构造函数。
2、将函数对象作为引用传递,引用时操作不会释放对象。自动存储对象被删除的顺序与创建顺序相反。   classname object_1 = object;
初始化形式等效于:
classname object_1 = classname(object);
原型如下:
classname(const classname &); //编译器自动生成被称为复制构造函数,因为它创建对象的一个副本,自动生成的构造函数不知道更新类对象共享的静态变量。

12.1.2 特殊成员函数
C++自动提供以下成员函数–在没有人为给出定义的前提下
默认构造函数,默认析构函数,复制构造函数,赋值运算符,地址运算符
更准确的说,编译器将生成最后三个函数的定义,如果程序使用对象的方式要求这样做。例如,将一个对象赋给另外一个对象
另外两个特殊成员函数:移动构造函数,移动赋值运算符

默认构造函数:
不做任何操作,不接受任何参数,因为创建对象时总会调用
classname::classname() {};//隐式默认构造函数,在一行
classname object ;//使得object类似于常规的自动变量,未初始化前值未知如果定义了构造函数,默认构造函数将不会被调用,如果希望在创建对象时不显式地对它进行初始化,则必须显式地定义默认构造函数,没有参数,但是可以使用它来设置特定的值
e.g.
classname::object() //显式默认构造函数
{data = 0;
}带参数的构造函数也可以是默认构造函数,只要参数值是提前知道的默认值
e.g.
object(int n = 0){ data = n;}//内敛构造函数,在一行
默认构造函数同一个类只能有一个复制构造函数
将一个对象复制到新创建的对象中,应用于初始化过程中(包括按值传递参数),而不是常规的赋值过程中,原型通常如下:
Class_name(const Class_name &);
接受一个指向类对象的常量引用作为参数:
e.g. StringBad(const StringBad &);1.何时调用
新建一个对象,病将其初始化为同类现有对象时,如:
classname object_2(object_1);
classname object_2 = object_1;
classname object_2 = classname(object_1);
classname *pt_object_2 = new classname(object_1);
中间的两种声明可能会使用复制构造函数直接创建对象,也可能生成一个临时对象,然后将临时对象的内容赋给要创建的对象,具体是哪一种方式取决于实现
最后一种是使用现有对象初始化一个匿名对象,并将新对象的地址赋给 指针每当程序生成了对象副本,编译器都将使用复制构造函数。如按值传递对象或者函数返回对象的时候(生成临时对象)。如三个对象相加,编译器将生成一个临时对象保存中间结果因为复制构造函数占据空间,所以,引用传递对象是一个好的选择2.功能 P434
默认的复制构造函数 逐个 复制 非静态成员(成员复制也称为浅复制),复制的是成员的值
如果,类成员本身也是个对象,则将使用该成员对象的复制构造函数继续复制成员对象,
但是,静态函数不受影响,因为它属于整个类(1)静态变量关于对象计数错误
默认的复制构造函数不说明其行为,因此它不指出创建过程,不增加静态成员计数值,但是析构函数任何对象过期都会调用,不管对象是否创建。???有问题
因此,如果类中包含这样的静态数据成员,即其值将在新对象被创建时发生变化,则应该提供一个显式复制构造函数来处理计数问题。
e.g.
classname::object(const String & s)
{num++;
}
(2)乱码
因为隐式复制构造函数按值复制。例子中,sailor初始化sports后,得到的是两个指向同一个字符串的指针。operator<<() 函数使用指针来显式字符串,ok。但是析构函数会出现问题。析构函数释放了str指针指向的内存,但是这个指针指向的内存被sailor和sports里面连个指针同时指向。一个对象析构后清除内存,将会导致已经被析构释放内存再被释放一次,这将导致不确定,可能有害的结果。    或者程序直接终止
受损的字符串,通常为内存管理不善(3).解决方法--定义一个显式的复制构造函数
深度复制(deep copy)解决此问题。复制构造函数应当复制字符串,并将副本的地址赋给str(指针)成员,而不是仅仅复制字符串的地址,这样每个对象都有了自己的字符串,而不是引用另一个对象的字符串。书本中的例子构造函数如下
StringBad::StringBad(const StringBad & st)
{num_strings++;//更新静态成员变量len = str.len;//同样的长度str = new char(len + 1);//分配空间std::strcpy(str,st.str);//复制
}
必须定义的原因是一些类成员使用new初始化、指向数据的指针,而不是数据的本身

赋值运算符
C++允许对象赋值,通过自动为类重载赋值运算符实现,原型如下:
Class_name & Class_name::operator=(const Class_name&);
接受并返回一个指向类对象的引用

将已有对象赋给另一个对象时,将使用重载的赋值运算符,直接等于的那种
P436
初始化总是会调用复制构造函数,使用=也可能会调用赋值运算符,在分两步处理新对象初始化的情况下。对于由于默认赋值运算符不合适而导致的问题,解决办法是认为定义赋值运算符进行深度复制。类似复制构造函数,但有以下不同1.由于目标对象可能引用了以前分配的数据,所以函数应使用delete释放这些数据2.函数应当避免将对象赋值给自身,否则,给对象重新赋值前,释放内存的操作可能删除对象的内容3.函数返回一个指向调用对象的引用,通过返回一个对象,函数可以像常规赋值操作那样,进行连续赋值。e.g.对象s1 = s2 = s3,函数表示法如下:s0.operator=(s1.operator=(s2));修正后的赋值运算符
SringBad & StringBad::operator=(const StringBad &)
{if(this == &st) // 自己赋值给自己return *this;delete []str; //删除旧的len = st.lenstr = new char(len + 1);//分配空间std::strcpy(str,st.str);//复制return *this;}赋值操作不会创建新对象,不需要调整静态数据成员

12.2 改进后的新string类–自己编的
构造函数中 str = new char[1] 与str = new char; 分配的内存量相同,区别在于前者与析构函数兼容:delete [] str;后者与析构函数不兼容 delete[] 对应new[]
str = 0//指针str设置为0,即空指针 或者str = nullptr
等价于str = new char[1]; str[0] = ‘\0’

其他方式初始化的指针,delete[]结果不确定    []中括号运算符 city[5], 二元运算符,一个操作数在第一个操作数之前,另外一个在两个括号之间
city--第一个操作数,5第二个操作数
P440
char & String::operator[](int i)
{return str[i];
}
之后 cout << opera[4]-----> couture << opera.operator[4]const String answer("haha")
如果只有operator[]()一种定义(此操作改变数据)下面这个操作将会报错
cout <<answer[1];
因为,无法保证数据不被修改
重载时,C++将区分常量和非常量函数的特征标,因此,可以额外提供一个仅const String对象可以使用的operator[]()版本
const char & String::operator[](int i) const
{
const str[i];
}

12.2.4 静态类成员函数–公有,不与特定对象相关联
将成员函数声明为静态的(声明前加 static,独立的定义不加)将会导致
1.不能通过对象调用静态成员函数,静态成员函数也不可以使用this指针。
如果声明在公有部分,则可以使用类名和作用域解析运算符调用
e,g,
static int howmany(){ return num_strings;}
调用方式:
int count = String::howmany();//调用了静态成员函数

类名 而不是对象名2.只能使用静态数据成员,因为他们都是类公有
3.可以用来设置类标记(classwide)

12.2.5
String name;
char temp[10];
cin.getline(temp,10);
name = temp;//调用了构造函数来转换类型

1.只有一个参数的构造函数用作转换函数。最后一句调用构造函数String(const char *)创建临时String对象,其中包含temp字符串的副本
2.使用String & String::operator=(const String &)将临时对象中的信息复制到name对象中
3.调用析构函数~String()删除临时对象优化:
String & String::operator=(const char *)
{delete [] str; //释放一下,之前可能用过了 len = std::strlen(s);str = new char[len + 1];std::strcpy(str,s);return *this;
}
P442String 输出例子函数
ostream & operator<<(ostream & os, const String & st)
{os << st.str;return os;
}String 输入 重载>>
istream & operator>>(istream & is, String & st)
{char temp[String::CINLIM]; //输入限制is.get(temp, String::CINLIM);if(is)st = temp;while(is && is.get()!= '\n')continue;return is;
}
较早的get(char *, int)版本读取空行后,返回的不是false,如果读入空行,字符串第一个字符是一个空字符
if( !cin || temp[0] = '\0')break;

如果使用了新的C++标准,!cin用作检测空行 ,第二个条件是旧版本的空行检测

12.3 构造函数中new的注意事项
使用new初始化对象的指针的注意事项:
1.如果再构造函数中使用new初始化指针成员,则应在析构函数中使用delete
2.new和delete要互相兼容,new–delete,new[]–delete[]
3.如果有多个构造函数,必须以相同的方式使用new。因为只有一个析构函数。可以在一个构造函数中使用new初始化指针,在另一个构造函数中将指针初始化为空,因为,delete可以用于空指针
4.应该定义一个复制构造函数,通过深度赋值将一个对象初始化为另一个对象
空指针 C:0 或者NULL都可以,C++: 0 NULL nullptr,都可以,但nullptr更好

    这种构造函数与下方类似:String::String(const String &st){num_strings++;len = st.len;str = new char[len +1];std;:strcpy(str,sst.str);}复制构造函数应该分配足够的空间来存储复制的数据,并复制数据,而不仅仅是数据的地址。同时更新所有受影响的静态类成员5.应当定义一个赋值运算符,通过深度复制将一个对象复制给另一个对象:String & String::operator=(const String & st){if(this == &st)return *this; //对象自我复制,则退出delete [] str;    //清楚旧字符len = st.len;str = new char [len + 1];std::strcpy(str, st,str);return *this;}

12.4 有关返回对象的说明
12.4.1 返回指向const对象的引用
返回对象将调用赋值构造函数,返回引用不会。
引用指向的对象,应该在调用函数执行前就已经存在
参数被声明为const,返回也必须const
12.4.2 返回指向非const对象的引用
常见的返回非const对象:重载赋值运算符,重载与cout<<运算符。前者为提高效率,后者是必须的

operator=() 用于连续赋值, 如s3 = s2, s2.operator=()返回值赋给s3, 则返回String对象或String
对象的引用。
返回类型非const,则指向的对象可以修改
cout<< 最好引用

12.4.3 返回对象
当返回的对象是被调用函数的局部变量时,应该返回对象本身,而不是指向对象的引用。因为被调用函数结束后,局部对象将会被释放
12.4.4 返回const对象
object_1+object_2 = object_3
object_1+object_2结果存储于临时对象中,然后 3 被赋给这个临时对象
释放的时候,临时对象被丢弃,原有对象不便,相当于啥都没干

将返回类型声明为const object时, obj+obj =obj 将是非法的

sum:
方法或者函数返回局部对象,则应返回对象,而不是指向对象的引用,这种情况下,使用赋值构造函数来生成返回的对象。
返回的是一个没有公有复制构造函数的类的对象的话,则必须返回一个指向这种对象的引用。
最后,有些方法或函数(如重载赋值运算符)可以返回对象,也可以返回指向对象的引用,首选引用

12.5 使用指向对象的指针
String * p = new String(sayings[choice]);
p 指向new 新创建的未被命名的对象,使用对象saying[choice]来初始化新的String对象,将调用赋值构造函数。

使用enw初始化对象
如果,Class_name是类,value的类型是Type_name,则语句:
Class_name * pclass = new Class_name(value);
将调用构造函数:
Class_name(Type_name);
这里可能需要转换,如 Class_name(const Type_name &);
P453
Class_name * ptr = new Class_name, 将调用默认构造函数

12.5.1 再谈new new和delete
class act {…};
act nice;//外部对象(静态对象)
int main()
{
act p = new act;//动态对象
{
act up;//自动对象
} //此时,将调用自动对应up的析构函数
delete p;//对指针p应用运算符delete时,将调用动态对象
p的析构函数
} 整个程序结束时,将调用静态对象niec的析构函数

delete 只释放保存p指针的空间,不释放p指向的内存,释放内存由析构函数完成析构函数调用时机:
1.变量是动态变量,执行完定义该对象的程序块时
2.静态变量(外部,静态,静态外部或来自名称空间),程序结束时
3.new创建的,仅当显式使用delete删除该对象

12.5.2 小结P454 指针与对象小结
12.5.3 再谈定位new运算符
1.定位new运算符重复在同一个buffer为不同的对象分配内存是危险的————
程序员得自己手动管理,不同对象,不同的内存单元,提供不同的缓冲区buffer地址。
e.g.
pc1 = new(buffer) justtesting;
pc3 = new(buf + sizeof(justtesting)) justtesting(…); //第二个设定偏移量

2.并且内存单元buffer数组里面的对象,在delete[]时,不会调用里面对象对应的析构函数。定位new运算符为对象分配的内存,析构函数调用方式:定位new不会返回地址,因为本来就知道了,因此delete pc3错误当pc1地址与buffer相同,但是buffer是使用new[]初始化的,必须用delete[],即使buffer是使用new初始化,delete pc1 也是释放buffer,而不是pc1.这是因为,mew/delete系统知道已经分配的buffer,但是对于定位new运算符对该内存块做了啥一无所知解决方法:显式地为使用定位new运算符创建的对象调用析构函数。这是需要显式调用析构函数的少数集中情况之一。显式调用析构函数,必须制定要销毁的对象。pc3->~justtest();//point-> ~func()对于定位new运算符创建的对象,应该使用与创建顺序星饭的顺序进行删除,因为晚创建的对象可能依赖于较早创建的对象,另外,仅当所有的对象销毁后,才可以释放对应的缓冲区buffer    直接new的对象,在堆中,因此释放方法:delete pc2;//释放pc2指向的对象

12.6 技术总结P459
12.7 应用
(例子的声明再私有部分)类声明中结构定义在一行,可以使其作用域为整个类,struct结构作为一个类型,声明其他类成员或者作为类中其他类方法的类名名称,但只能在类中使用。—类嵌套结构。此时的结构声明不会创建数据对象,知识制定了可以在类中使用的类型,如果声明位于私有部分,则只能在这个类中使用。如果在公有部分,则可以从类的外部通过作用域解析符使用被声明的类型。

const初始化
一般来说,调用构造函数时,对象将在括号中的代码执行之前被创建。即先给成员变量分配内存,然后进入括号内,将值存储到内存中。const成员,必须在执行到构造函数体之前,即创建对象时进行初始化。成员初始化列表:e.g.Queue::Queue(int qs) : qsize(qs), mdata(value), ...  //将qsize初始化为qs ,qsize为const{...}只有构造函数可以使用这种初始化列表语法除了const类成员,被声明为引用的类成员也必须使用这种语法(简单类成员,成员初始化列表和函数体内使用赋值没区别)class agency {..};class agent {private:agency & belong; //必须使用初始化列表来初始化...};agent::agent(agency & a) : belong(a) {...}上方这个是agent的构造函数,传入了a的agency类引用,赋给belong成员初始化列表的语法:
Classy是一个类,mem1、mem2、和mem3是它的数据成员,可以使用以下语法来初始化
Classy::Classy(int n, int m): mem1(n), mem2(m), mem3(n*m + 2)
{...
}
在创建对象时完成,此时括号内代码还没有执行
注意:1.这种格式只能用于构造函数2.必须使用这种格式来初始化非静态const成员3.必须使用这种格式初始化引用数据成员C++的类初始化
C++11可以这么做class Classy{int mem1 = 10;const int mem2 = 29;...};
与构造函数使用成员初始化列表等价:Classy::Classy() : mem1(10), mem2(29) {...}以上是默认构造函数,构造函数也可以这么做,这将覆盖默认构造函数初始化列表的值向队列中添加节点使用new,需要显示析构函数删除到期的所有结点。
Queue::~Queue()
{Node * temp;while(front != NULL){tem = front;fornt = front->next;delete temp;}
}使用new的类通常需要包含 显式复制构造函数 和执行深度复制的 解决办法是认为定义赋值运算符进行深度复制。类似复制构造函数要克隆或复制队列,必须提供以上两个函数。复制对象将会生成一个和原来一模一样的,指针也是,这将导致不必要的修改发生。
方法:将所需的方法定义为 伪私有方法
class Queue
{
private:Queue(const Queue &q) :qsize(0) {}Queue & operator=(const Queue & q) { return *this};...
} 重点 待解决 P468
作用:1.避免了本来将自动生成的默认方法定义,2.因为方法私有,所以不会被广发使用,如果nip和truck是Queue对象,则以下编译器不通过Queue snick(nip);truck = nip;提供编译错误,指出某一些方法不可使用在没有提供深度复制运算符和显式复制构造函数的的、具体定义代码的情况下,程序可能仍然能运行,
但是结果不确定。为了防止这种情况下,程序不崩溃,将所需的方法定义为伪私有方法:
class Queue
{private:Queue(const Queue & q) : qsize(0) {}Queue & operator=(const Queue & q) { reuturn *this}...
}
这可以避免编译器自动给你生成临时的定义,也可以避免方法被广泛使用
nip 和truck都是Queue的对象
Queue fun(nip)和truck = nip 都会编译报错
这样可以避免未来因为未提供上两个函数定义而突然报错。如果未来定义该类不可复制,也可使用这种方法

第十三章 类继承

#include
#include
using namespace std;
class Obj
{
private:
int a;
public:
Obj(int c) { a = c;};
// Obj(int c = 0) {a = c;}; // Obj() and this function, you can chose only one
Obj() {int a = 5;}; // default constructor function don’t have any argument
~Obj(){ cout << “the clas Obj has been released!” << endl;};
void show() {cout << a << endl; };
};
class Ob : public Obj
{
private:
int b;
public:
//Ob(int c, int d) {Obj::a = c; b = d;}; //error
//Ob(int d,int c):Obj© {b = d;};// true
Ob(int d,int c):Obj©, b(d) {};
~Ob() { cout << " release" << endl; };
void show (){cout << "a = " << Obj::a << " b = " << b << endl:};
};
int main()
{
int c;
cout << “please enter a number!” << endl;
cin >> c;
Obj tr = Obj© ; //new cobject should be initialized after it be build immediately
// tr.Obj©;
tr.show(); // call class functioin ,use “.” not " :: " classame :: func()it only be used in function code

// tr.~Obj();
Obj trial;
trial.show();
// trial.~Obj(); //object will be released by system automatically.
return 0;
}

函数库
类库 = 类声明+类实现 以源代码的方式提供
继承:
可以在已有类的基础上添加功能,给类添加数据,修改类方法。
在只有类方法头文件和编译后代码的情况下,仍然可以使用库中的类派生出新的类,同时在不公开实现的情况
下将自己的类分给别人,同时允许他们也添加新特性

13.1 一个简单的基类
一个类派生出另一个类,原始类–基类,继承类–派生类
class class_name_1 : public class_name;
{

}
class_name类的基类是class_name。class_name是一个公有基类—公有派生
派生类对象也包含基类对象。
使用公有派生,基类的公有成员将成为派生类的公有成员,基类的私有部分也将成为派生类的一部分
,但只能通过基类的公有和保护方法访问,继承类无法直接访问。

新的class_name_1类对象有以下特征: P483图
1.派生类对象存储了基类的数据成员(派生类继承了基类的实现)
2.派生类对象可以使用基类的方法(派生类继承了基类东街口)

需要在继承特性中添加:
1.派生类需要自己的构造函数
2.派生类可以根据需要添加额外的数据成员和成员函数
构造函数必须给新成员(如果有)和继承的成员提供数据

????提供了默认参数的默认构造函数怎么弄
—默认构造函数不带参数,构造函数可以设置默认值代替默认构造函数

13.1.2 构造函数:访问权限
派生类不能直接访问基类的私有成员,必须通过基类公有方法进行访问基类的
私有成员。即:派生类构造函数必须使用基类构造函数。

创建派生类对象时,程序首先创建基类对象。即基类对象应当在程序进入派生类构造函数之前被创建,
C++使用成员初始化列表完成该工作
e.g.
RatedPlayer::RatedPlayer(unsigned int r, const sring & fn, const string & In, bool ht)
: Table(fn, In, bt)
{
rating = r;
}
其中,Table(fn, In, bt)是成员初始化列表,它执行的代码调用Table构造函数。
如:

RatePlayer rplayer1(1140,"haha", "ff", true);
RatePlayer 构造函数将实参"haha", "ff", true 赋给形参 fn, In, ht,然后将这些参数作为实
参传递给Table构造函数,后者将创建一个嵌套Table对象,并将数据   存储在该对象中,然后,程序进入
RatePlayer构造函数中,完成RatePlayer对象的创建,并将r赋给rating成员如果不提供成员初始化列表,
RatedPlayer::RatedPlayer(unsigned int r, const sring & fn, const string & In, bool ht)
{rating = r;
}
则与
RatedPlayer::RatedPlayer(unsigned int r, const sring & fn, const string & In, bool ht) : Table()
{rating = r;
}等效,相当于调用了Table 的默认构造函数,出否有意调用默认构造函数,否则请显式调用正确的基类构造函数
RatedPlayer::RatedPlayer(unsigned int r, const Table & tp) : Table(tp)
{rating = r;
}
这里也是将出赤化需要的Table信息传递给了Table构造函数tp的类型为Table &,因此将调用基类的复制构造函数,如果此时,基类没有定义构造函数,编译器给他
自动生成一个,此时,动用隐式复制构造函数合适,因为没有动态分配内存。可以对派生类成员使用成员初始化列表,此时应使用成员名而不是类名。
RatedPlayer::RatedPlayer(unsigned int r, const sring & fn, const string & In, bool ht) : Table(), rating(r)
{
}

要点:
1.首先创建基类
2.派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数
3.派生类构造函数应初始化派生类新增的数据成员

析构时,先派生类,后基类
P486
除了虚基类,类智能将值传递回相邻的基类

13.1.4 派生类和基类之间的特殊关系
1.派生类可以使用基类非私有的方法
2.基类指针可以在不进行显式类型转换的情况下指向派生类对象,基类引用可以在不进行显示类型转换的情况下引用派生类对象
e.g.
RatedPlayer rplayer1(…);
Table & rt = rplayer;
Table * pt = &rplayer;
rt,name();
pt->name(); //以上这些操作只能基类指向派生类
但是也只能用于调用基类方法,无法调用派生类的方法,派生类新增的方法无法调用,因为派生类继承了基类的方法,转半天相当于调用了自己的方法。
C++要求引用和指针类型与付给的类型匹配,但对于继承例外。但也只能将派生类对象和地址赋给基类引用和指针,反之,派生类通过基类调用派生类方法,error,基类没有派生类方法

如果基类引用和指针可以指向派生类对象,则基类引用定义的函数或指针参数,科用于基类对象,或派生类对象
P489 真鸡儿绕 重点待解决

13.2 继承:is-a关系
三种方式:
私有继承
保护继承
公有继承:最常见,建立is-a关系,即派生类对象也是一个基类对象,可以对基类对象做的任何事,在派生类身上也可以做,又因为派生类通常会添加新特性,所以用is-a-kind-of描述这种关系更准确
公有继承不建立has-a关系。
午餐中可能包含水果,但午餐不等于水果,不能从fruit派生出lunch,类来在午餐中添加水果。在午餐中加入水果的正确方法是将其关系作为一种
has-a关系。最简单的是将fuit最为lunch的数据成员

    公有继承不建立is-like-a关系,也即是说不采用明喻。说律师是鲨鱼,其实并不是。所以不应从shark类派生出shark类。继承可以在基类的基础上添加属性,但是不能删除属性。有时,可以设计一个包含共有特征的类,然后以is=a或者has-a关系在这个类基础上定义相关类。公有继承不建立is-implemented-as-a关系(作为...实现)。例如,可以通过数组来实现栈,但从array类派生出的stack类是不是适合的。因为栈不是数组正确的方法是,让栈包含一个私有array对象成员来隐藏数组实现公有继承不建立use-a关系例如,计算机可以使用激光打印机。但computer类派生出printer类(或反过来)是没意义的。然而,可以使用友元函数或类处理printer对象和computer对象之间的通信。C++ 可以通过公有继承来建立has-a,is-implemeted,或uses-a关系。坚持is-a最好

13.3多态公有继承–虚函数
希望从基类继承而来的派生类的方法与基类有区别——方法的行为取决于调用该方法的对象
——多态——具有多种形态,即同一种方法的行为随上下文而异。两种方法:
1.再派生类中重新定义基类的机制
2.使用虚函数

    is-a 关系通常是不可逆的,也就是说 水果不是香蕉。要实基类和派生类之间同名方法功能不同,需要在派生类中重新定义方法,并用虚函数打头虽然习惯于对象调用方法如果方法是通过引用或者指针而不是对象调用,它将确定使用哪一种方法。如果没有关键字virtual,程序将根据引用类型或者指针类型来选择方法。如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。没有virtual,引用或指针的类型使用virtual,引用或指针指向的对象的类型在基类中将派生类会重新定义的方法声明为虚方法,派生类同名的方法将自动成为虚方法,但是在派生类声明中使用virtual更显眼。基类中声明了一个虚析构函数,这将确保释放派生类对象时,按照正确的顺序调用析构函数,即先调用派生类析构函数,再自动调用基类析构函数此时这个虚析构函数不能执行任何操作,如果派生类中的析构函数虚函数定义代码中virtual不要加派生类不能直接访问基类的私有数据,必须使用基类的公有方法才能访问派生类构造函数在出书画基类私有数据时,采用的时成员初始化列表语法,将基类信息传递给基类构造函数,然后使用基类构造函数初始化基类。非构造函数不嫩使用成员初始化列表语法。但是派生类可以调用基类的公有的基类方法只需要加上基类名和作用域解析运算符数组的元素类型必须相同,因此无法存储不同的类对象。但是在基类和派生类中,因为时公有继承,具有
is-a的关系,基类指针可以指向派生类,所以可以创建指向基类的指针数组,这样就保证了类型的相同,同时也可以通过基类的指针指向了派生类对象。--使用一个数组来表示多种类型的对象,这就是多态性当然,此时因为virtual虚函数,同名方法不同实现,且时指针或者引用调用了方法,因此这里实现了虚函数的应用
new出来的是地址,指针数组里面存储的时指针如果析构函数不是虚的,则只调用对应于指针类型的析构函数,即使实际上它指向的时派生类。
如果析构函数时虚的,则将调用相应对象的析构函数,顺道调用基类的析构函数
如果派生类中自己声明了一个执行某些操作的析构函数,基类中就必须声明一个 虚析构函数 即使该函数啥都不做

13.4 静态联编和动态联编
编译器决定程序调用函数时,执行哪个代码块–函数名联编。
编译过程中进行联编–静态联编 static bingding/早期联编 early binding
编译过程发生在程序运行时–动态联编 dynamic bingding/晚期联编 late binding

C++中,动态联编与通过指针和引用调用方法有关--由继承控制,
C++通常不允许将一种类型的地址赋给另一种类型的指针,也不允许一种类型的引用指向另一种引用类型
然而,基类可以,不必进行显式类型转换
将派生类指针或引用转换为基类指针或引用--向上强制转换,upcasting,这使得公有继承不需要进行显式类型转换,该规则是is-a关系的一部分因为派生类继承了所有基类的成员,所以为基类设计的函数也可以对派生类执行同样的操作。向上强制转换具有传递性。基类指针可以无限向下延申相反,将基类指针或引用转换为派生类指针或引用--向下强制转换,downcasting,如果不使用显式类型转换,向下强制类型转换是不允许的。因为is-a关系通常不可逆。因为派生类具有基类所不具有的成员。使用基类引用或指针作为参数,将进行向上转换,然后按照虚函数 :如果方法是通过引用或者指针而不是对象调用,它将确定使用哪一种方法。如果没有关键字virtual,程序将根据引用类型或者指针类型来选择方法。如果使用了virtual,程序将根据引用或指针指向的对象的类型来选择方法。没有virtual,引用或指针的类型使用virtual,引用或指针指向的对象的类型
按值传递将导致传进去的派生类只将声明中要求的基类部分传递进函数,因此只调用基类方法
但传递引用和指针发生了隐式向上转换,由因为虚函数,P503隐式向上强制转换需要动态联编当基类指针指向派生类。如果,调用的函数不是虚函数,则静态联编,根据指针类型,将调用基类的函数
如果,调用的函数时虚函数,则动态联编,根据指针指向的对象,决定调用哪个类的函数动态联编需要不断跟踪对象,在不需要的时候,静态联编效率更高,仅用到虚函数的时候再使用动态联编
P504--虚函数的编译器实现给每个对象添加一个一藏匿成员——它保存了一个指向函数地址数组的指针。这个数组叫做:虚函数表/virtual function lable,vtbl虚函数表中存储着类中声明的虚函数的地址。同时,每个类都有自己独立的虚函数表派生类如果虚函数有了新定义,则相应的保存新定义的虚函数的地址,如果没有,则保存原始版本的地址,如果派生类增加了新的虚函数,也将被添加到虚函数表中。调用虚函数时,通过vtbl指针找到虚函数表,再找到相应的虚函数。

13.4.3 有关虚函数的注意事项
1.基类方法中使用virtual关键字,可以使得它在基类和相应的派生类中是虚函数
2.使用指向对象的引用或指针来调用虚方法,程序将使用为对象类型定义的方法,而不是为引用或指针类型定义的方法–动态联编,这样基类指针或引用可以指向派生类对象
3.应该在基类中将那些可能会在派生类中 充型定义的方法声明为虚的。

构造函数 不能为虚的,派生类有自己的构造函数,同时通过成员初始化列表,将基类信息传递给基类构造函数进行相应创建。
析构函数 应当是虚函数,除非类不用做基类。但是给非基类定义了一个虚析构函数也没啥影响。
友元 不能是虚函数,因为它不是类成员,只有类成员才能是虚函数
没有重新定义如果派生类没有重新定义函数,则使用基类版本。如果派生类位于派生链中,则将使用最新的虚函数版本。除非基类版本隐藏
重新定义将隐藏原有方法在派生类中重新定义方法,将隐藏基类中同名方法,而不是重载
(overwrite & override)经验规则: leads to a couple rules of thumb1.重新定义继承的方法,需要确保原型完全相同。可以加一些派生类私有的成员数据但如果基类方法的 返回类型 是基类引用或指针,则可以修改为指向派生类的引用或指针——返回类型协变/covariance of return type, 因为允许返回类型随着类的类型比那花而变化如果基类声明被重载了,则应该在派生类中重新定义所有的基类版本。e.g.class a{public://类中被重载virtual void haha(int a) const;virtual void haha(double a) const;virtual void haha() const;}class b : public a{public://因为基类中已经发生中重载,因此派生类中需要全部重定义virtual void haha(int a) const;virtual void haha(double a) const;virtual void haha() const;}如果重定义只有要给版本,则另外两个版本将被隐藏,派生类对象无法使用他们。当然,如果没有修改的需求,则新定义可只调用基类版本

13.5 访问控制:protected
与private类似,区别在于:派生类成员可以直接访问基类的保护成员l,但不能直接方位基类的私有成员。
因此对于外部来说,保护成员的l行为与私有成员类似。但是对于派生类来说,保护成员的行为与公有成员类似。对于成员函数来说,可以让派生类访l问外l部不能使用的内部函数。但是l成员变量最好还是定义为i欸私有的,不然就成公有的了
13.6 抽象基类 abstract bsse class ABC
circle是特殊的ellipse(椭圆),但是定义园的时候,如果从ellipse派生,将有大量信息是circle所不需要的。因此可以从circle 和ellipse 中提取出abc,然后从abc分别派生出circle和ellipse,这样就可以使用基类指针管理circle和ellipse对象–即多态的方法。对于面积来说,circle 和ellipse 计算方式不同,Area()实现方式不同。
高度抽象下,ABC不具备 area()需要的数据成员
C++通过使用纯虚函数-pure virtual function提供未实现的函数。纯虚函数声明的结尾处为 =0
e.g.
class BaseEllipse //抽象基类
{
private:
double x;
double y;

public:
BaseEllipse(double x0 = 0, double y0 = 0) : x(x0),y(y0) {}
virtual ~BaseEllipse {}
void Move(int nx, ny) {x = nx; y = ny;}
virtual double Area() const = 0; // 纯虚函数

}
当类声明中包含纯虚函数,则该类不能创建对象,因为纯虚函数的实现用到了不存在的变量
只能用作基类。原型中 =0 使得虚函数成为纯虚函数。
C++甚至允许纯虚函数有定义的代码。例如Move(),虽然没必要,但你仍然可以将其声明为虚函数
void Move(int nx, ny) = 0; 这将使得基类成为抽象的。但仍可以在实现文件中提供方法的定义
void BaseEllipse::Move(int nx, ny) {x = nx; y = ny;}
=0指出类是一个抽象类,在类中可以不定义函数。
针对抽象类BaseEllipse来说 ,circle 和ellipse 就是具体类 concrete。

总之,ABC至少有一个使用纯虚函数的接口,从ABC派生处的类将根据派生类的具体特征,使用常规虚函数来实现这种接口 P510

13.7 继承和动态内存分配
13.7.1第一种情况,派生类不适用new
基类使用动态内存分配,P516,声明中包含构造函数使用new时所需要的特殊方法:
析构函数,复制构造函数,重载赋值运算符
class baseDMA
{
private:

public:
baseDMA(const char * l = “null”, int r = 0);
baseDMA(const baseDMA & rs);
virtual ~baseDMA();
baseDMA & operator=(const baseDMA &rs);
};
从基类派生到lackDMA,派生出来的不使用new,无特殊特性。不需要显式定义析构函数,复制构造函数和赋值运算符。
class lackDMA : public baseDMA
{

};
分析
析构函数:
如果没有定义析构函数,编译器会自己定义一个不执行任何操作的默认析构函数。派生类的默认析构函数执行自己的代码之后会调用基类的析构函数,
——因为,不需要执行任何特殊操作的派生类,编译器构造的默认析构函数就足够了,它自己会去调用基类的析构函数。

    复制构造函数:默认复制构造函数执行成员复制,对于动态内存分配不合适。但是对于不需要动态内存分配的派生类来说是合适的。对于类对象来说。复制派生类对象时,派生类的用默认复制构造函数将使用显式的基类复制构造函数来复制派生类对象的基类部分。因此默认复制构造函数对于新的派生类对象成员来说很合适,对于继承的基类对象来说也ok赋值:派生类的赋值运算符将自动使用基类的赋值运算符来对基类组件进行赋值。第二种情况:派生类使用new
class hasDMA : public baseDMA
{
};
必须为派生类定义显式析构函数,复制构造函数和赋值运算符。
析构函数:派生类的析构函数自动调用基类的析构函数,故自身的职责是对派生类构造函数做过的动作进行清理。即,如果派生类有了自己私有指针或new出来的东西,只能使用自己的析构函数清楚,和基类一样的数据就交给基类析构函数。复制构造函数:派生类的复制构造函数只能访问派生类的数据,因此必须调用基类复制构造函数来处理共享的基类数据baseDMA::baseDMA(const baseDMA & rs);haseDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) //成员初始化列表 {style = new char[std::strlen(hs.style) + 1];std::strcpy(style, hs.style);}成员初始化列表将一个hasDMA引用传递给baseDMA构造函数,虽然baseDMA(基类)需要一个baseDMA的引用,但是因为基类引用可以指向派生类,因此,基类复制构造函数可以使用派生类中的基类部分来构造新派生类中的基类部分。而不需要实际上的给它一个基类引用。赋值运算符:一般模式baseDMA & baseDMA ::operator=(const baseDMA & rs){if(this == &rs)return *this //自己等于自己了delete [] label;label = new char[ste::strlen(re.label) + 1];std::strcpy(label, rs.label);rating = rs.rating;return *this;}此时的派生类hasDMA使用了动态内存分配,所以需要一个显示赋值运算符。虽然它只能直接访问派生类的数据,但是派生类的显示赋值运算符必须负责所有继承的baseDMA基类对象的赋值,可以通过显式调用基类赋值运算符来完成该工作baseDMA & baseDMA ::operator=(const baseDMA & rs){if(this == &rs)return *this //自己等于自己了baseDMA::operator=(hs); //复制基类部分  含义是 *this = hs; 使用了基类复制运算符复制派生类中基类的部分delete [] label;    //准备一个新类label = new char[ste::strlen(re.label) + 1];std::strcpy(label, rs.label);rating = rs.rating;return *this;}总之,当基类和派生类都采用动态内存分配时,派生类的析构函数,复制构造函数和赋值运算符都必须使用相应的基类方法来处理基类元素。对于析构函数,自动完成。对于构造函数,将通过初始化成员列表中调用基类的复制构造函数来完成,如果不这样做,将自动调用基类的默认构造函数。对于赋值运算符,通过使用作用域解析运算符显式地调用基类的赋值运算符来完成。

13.7.3 P519示例
P522
友元函数不是成员函数,不能使用作用域解析运算符指定哪个(当前派生类的函数还是基类中的函数)成员函数来访问基类中的成员数据,因此可以通过强制类型转换,将派生类转换为基类,以便匹配原型时找到正确的函数,
e.g.
std::ostream & operator<<(std::ostream & os, const hasDMA & hs)
{
type cast to match operator<<(ostream &, const baseDMA &)
os << (const baseDMA &) hs;
os << "Style: " << s,style << endl;
return os;
}
13.8 小结
13.8.1 编译器生成的成员函数
1.默认构造函数
要么每参数,要么都有默认值——状态确定的参数列表。在没有定义任何构造函数,编译器将定义默认构造函数。
e,g,
Star rigel;//创造了一个没有显式初始化的对象,需要默认构造函数。
自动生成的默认构造函数另一个功能:是可以调用基类的默认构造函数以及调用本身本身是其他类成员时,该类的默认构造函数。

    另外,如果派生类构造函数的成员初始化列表没有显式调用基类构造函数。则编译器将使用基类的默认构造函数(此时,的默认构造函数时人为提供的)来构造派生类对象的基类部分,这种情况下,如果基类没有构造桉树,将编译错误。如果定义了某种构造函数,编译器将不会定义默认构造函数,这种情况下,如果需要默认构造函数,必须自己提供。提供构造函数是为了保证对象能被正确的初始化,包含指针成员,必须初始化。最好提供一个显式默认构造函数,将所有类数据成员初始化为合理的值2.复制构造函数 接受同类对象作为参数。使用场景:将新对象初始化为同类对象按值传递给函数 按值返回对象 编译器生成临时对象 如果程序没有使用(不论显式还是隐式)复制构造函数,编译器将提供原型,但不提供定义。否则,程序将定义一个执行成员初始化的复制构造函数,即,新对象的每个成员都被初始化为原始对象相应成员一样的值。如果成员为类对象,则初始化成员时,将使用相应类的复制构造函数某些情况下,如new初始化成员指针,必须进行深度复制。e.g.//深度复制baseDMA::baseDMA(cosnt baseDMA & re){label = new char[std::strlen(rs.label) + 1];std::strcpy(label, rs.label)rating = rs.rating;}
3.赋值运算符 默认的赋值运算符处理同类对象之间赋值了——已有对象之间。如果需要显式定义复制构造函数,基于相同的原因,需要显式定义赋值运算符。Star & Star::operator=(const char *) {...}-----快也可以使用转换函数,将字符串转换为Star对象,然后将Star赋给Star的赋值函数--可能导致编译器混乱 18章:移动构造函数,移动赋值运算符

13.8.2 其他类方法
1.构造函数
构造函数创建新对象,其他类方法只是使用先用类,因此构造函数不被基层的原因之一。继承意味着派生类对象可以使用基类的方法。
2.析构函数
一定要显式定义析构函数来释放使用new分配的内存,并完成任何特殊清理工作。对于基类,即使不需要,也清提供虚析构函数
或者,类中包含需要独立修改的静态变量,这需要自定义复制构造函数

3.转换 使用一个参数就可以调用的构造函数定义了从参数类型到类类型的转换e.g. Star(const Spectral &, int members = 1); //将Spectral转化为Star将可转换的类型传递给以类参数的函数时,将调用转换构造函数 e,g, Star north;north = "polaris";第二句调用Star::operator=(const Star *)函数,首先将char字符串生成了一个Star对象,该对象之后被用作赋值运算符的参数(这里假设没有定义将char*赋给Star的赋值运算符)在待一个参数的构造函数原型中使用explicit将禁止隐式转换,但允许显示转换e,g,class Star{...explicit Star(cosnt char *);...};...Star north;north = "polaris"; //not alowednorth = Star("polaris"); //allowed}将类对象转换为其他类型,就需要定义转换函数了(第十一章)转换函数可以是没参数的类成员函数,也可以是返回类型被声明为目标类型的类成员函数,即使没有声明返回类型,函数也因该返回所需的转换值e.g.Star::Star double() {...}Star::Star const char *() {...} --难点,重点4.按值传递对象与传递引用为了效率,按引用传递。按值传递设计生成临时拷贝:调用复制构造函数,析构函数。不修改对象的情况下,const引用更好继承使用虚函数,被定义为接受基类引用参数的函数可以接受派生类5.返回对象和返回引用如果可以不返回对象,返回引用更好代码方面,返回对象和返回引用的区别在于引用多 了个 & 符号,其他没区别返回对象也涉及到返回对象的临时副本。函数不能返回函数内的临时对象的引用。函数返回的是函数内临时对象,则不反悔引用。函数返回的是通过引用或指针传递给他的对象,返回引用6.const使用参数列表内 保证不修改参数, 函数名后面,不修改调用它的对象函数名前,返回的东西不能用于修改对象中的数据e.g.const Stock & Stock::topval(coanst Stock & s) const {if(s.total_val > total_val)return s; // 参数对象 else return *this; //调用方法的对象 }该方法返回对this或s的引用。都被声明为const ,所以函数不能修改他们,意味着返回引用也必须被声明为const如果函数将参数声明为指向const的引用或指针,则不能将该参数传递给另一个函数,除非后者也不会修改该参数

13.8.3 公有继承
1.is-a关系
派生类不是一个特殊的基类,不要使用公有派生。
某些情况下,可以创建包含纯虚函数的抽象数据类,从它派生其他的类。
is-a关系无需进行显式类型转换。基类指针可以指向派生类对象,基类引用可以引用派生类对象。反过来就不行了,这时候就需要将派生类显示强制转换为基类。
2.不能被继承的东西
构造函数不能被继承。创建派生类必须调用派生类的构造函数,派生类构造函数通过成员初始化列表语法调用基类构造函数创建派生类对象中的基类部分。如果没有显式调用基类构造函数,将使用基类的默认构造函数。
析构函数也不能被继承。但是析构函数首先作用与派生类
赋值运算符不能被继承,它的特征标随类而异
3.赋值运算符
编译器发现一个对象赋给另一个对象,自动为这个类提供赋值运算符,这个运算符的默认或隐式版本将采用成员赋值,即将原对象相应成员赋给目标对象的每一个成员。 然而,如果对象属于派生类,编译器将使用基类赋值运算符来处理派生类对象中的基类部分的赋值,
如果显式地为基类提供赋值运算符,将使用它,
当然,成员是另一个类的对象,将使用对应的赋值运算符。

    如果,类构造韩素使用了new初始化指针,则需要提供一个显式赋值运算符。对于派生类的基类部分,C++使用基类的赋值运算符,不需要派生类重新定义赋值运算符,除非添加了需要特别留意的数据成员。例如baseDMA类显式地定义了赋值,但是派生类lackDMA使用了它生成的隐式赋值运算符对于使用了new,则必须提供显式赋值运算符,类的每个成员都必须有自己的赋值运算符。hasDMA & hasDMA::operator=(const baseDMA & hs){if(this == &hs)return *this;baseDMA::operator=(hs); //赋值基类部分delete [] style; //prepare for new style style = new char[std::strlen(hs.strlen) + 1];std::srcpy(style, hs.style);return *this;}当派生类对象赋给基类对象时,赋值语句将被转换成左边对象调用自己的赋值运算符,参数是派生类。e.g.base = derived;--->base::operator=(const base &)is-a关系允许base指向derived,该函数只能处理基类成员。总之,可以那么干,但只涉及到基类成员。
相反 derived = base 将调用 derived::operator=(const derived &)。但派生类不能指向基类,error除非,有derived(const base &)----转换构造函数两个函数合作,首先将base转换为一个derived的临时对象,然后将他用作赋值运算符的参数。另一个方法是定义一个将基类赋给派生类的赋值运算符derived * derived::operator=(const base &) {...}

4.私有成员和保护乘员
派生类而言,保护成员类似公有成员;但是对外部而言,保护成语与私有成员类似。派生类可以直接访问基类的保护乘员。
5.虚方法
设计基类时,必须确认方法是否需要声明为虚的。如果希望派生类能重定义方法,则定义为虚。这样可以启用动态联编。
6.析构函数
基类的因当是虚的。这样当通过指向对象的基类指针或引用可删除派生类对象和基类,而不仅仅删除基类
7.友元函数
友元函数非成员函数,不能继承,可以通过强制类型转换,将派生类引用或指针转换为基类的,这样就可以让派生类使用基类的友元函数
第十五章 dynamic_cast<>也可以,更好
8.基类使用方法
P529

第十四章 C++中的代码重用
法一,类成员是另一个对象的类—包含、组合、层次化。
法二,使用私有或保护继承

包含,私有继承和保护继承---用于has-a关系---新的类将包含另一个类的对象。多重继承能基类的功能组合在一起生成更多的类法三,第十章 函数模板,本章,类模板使用通用术语定义类,然后使用模板创建针对特定类型顶一个的特殊类。

14.1包含对象成员的类
valarray类由头文件valarray支持。处理数值。支持诸如数组中的一系列操作-----是一个模板类
valarray模板类声明一个对象,需要指出对象的数据类型
valarray values;
P535 其他声明变量的方法。 类似基本数据类型的格式

    valarray<type_name> value_name( number/array/... , array_size);valarray<int> v5 = {22,32}; //c++11初始化列表方法:operator[](); 访问各个元素size();元素个数sum() 总和max(); /min()组合(包含)建立has-a关系——创建一个包含其他类对象的类
e.g.class Student{private:string name; //string类 valarray<double> scores; //valarray 类 }
student类的成员函数可以使用string类的公共接口访问其内部值,但是student类外部不可以这么做。只能通过student类的公有接口访问string类的值。但是,student类获得了其成员对象的实现,没有继承接口。
使用公有继承,类可以继承接口,可能还有实现(基类的纯虚函数提供接口,但不提供实现)。获得接口时is-a关系的组成部分。使用组合,类可以获得实现,但不获得接口。不继承接口时has-a关系的组成部分对于has-a不继承接口是个好事,例如,string类将+运算符重载为将两个字符串连接起来。但是对于两个student类对象串接起来没意义。
另一方面,被包含的类的接口部分对于新类来说可能是有意义的,例如,可能希望使用string类接口中的
operator<()方法将student对象按姓名排序,可以定义student::operator<()成员函数,在内部使用string::operator<()。 explicit Student(int n) : name("Nully"), scores(n) {}一个参数调用的构造函数,可以用作从参数类型->类类型的隐式转换函数。通常会导致问题,加上explict,关掉隐式转换,按需手动转换P538

1.初始化被包含的对象
成员初始化列表
Queue::Queue(int qs) : qsize(qs) {…}
使用数据成员qsize的名称,初始化它
hasDMA::hasDMA(const hasDMA & hs) : baseDMA(hs) {…}
使用类名称baseDMA调用特定的基类构造函数,初始化派生类的基类部分

    Student(const char * str, const double * pd, int n) : name(str), scores(pd, n) {}对于成员对象,构造函数使用成员名(私有数据名称)该构造函数初始化的是成员对象,而不是继承的对象,所以在初始化列表中使用的是成员名,而不是类名初始化列表中的每一项都调用与之匹配的构造函数,即name(str)嗲用构造函数string(const char *)scores(pd, n)调用构造函数Array Db(const double *, int)此时的成员对象的背后是一个类。当不使用成员初始化列表,C++将使用成员对下个所属类的默认构造函数初始化顺序当初始化列表包含多个项目,初始化顺序为他们被声明的顺序,而不是他们在初始化列表中的顺序。一般来说顺序不太重要,但是当一个成员的值是另外一个成员的一部分的时候,很重要

2.使用被包含对象的接口
被包含对象的接口不是公有的,但是可以在类方法中使用它。
double Student::Average() const
{
if (scores.size() > 0)
return scores.sum() / scores.size();
else
return 0;
}
Student调用自己定义的average,在其内部,使用了valarray的方法size(),sum()。

当类方法没有提供时,可以自行提供私有辅助方法

14.2 私有继承
另一种实现has-a关系的途径。
此时,基类的公有成员和包含成员都将成为派生类的私有成员。基类方法也不会成为派生对象公有接口的一部分,但是可以在派生类成员函数中使用他们。

公有继承,基类的的 公有方法 将成为派生类的公有方法,派生类将继承基类的接口,--is-a关系的一部分
私有继承,基类的公有方法->派生类的私有方法,派生类不继承基类接口--不完全继承-has-a关系的一部分例如String类->Student类 包含,将对象作为一个命名的成员对象添加到类中
私有继承,将对象作为一个未被命名的继承对象添加到类中---子对象,表示通过继承或包含添加的对象获得实现,不提供接口

14.2.1 示例
private默认值,省略也导致私有继承
class Student : private std::string, private std::valarray
{
}
此student类从多个类继承而来,多重继承(multiple inheritance).公有多重继承将导致很多问题。

1.初始化
因为只公有成员和保护成员,因此原先的私有成员将无法使用。
包含与私有继承区别
一、
包含提供了被显式命名的对象成员
私有继承提供了无名称的子对象成员

2.访问基类方法
二、
包含,它的构造函数
Student (const char * str, const double * pd, int n) : name(str), scores(pd, n) {}
使用对象名
私有继承,使用类名
Student (const char * str, const double * pd, int n) :
std::string(str), ArrayDb(pd, n) {} // ArrayDb <–std::valarray

使用私有继承,只能在派生类的方面里面使用基类方法。有时候希望工具是公有的,
则可以在公有函数中使用私有函数
包含的方法:
double Student::Average() const
{if (scores.size() > 0)return scores.sum() / scores.size();else    return 0;
}私有继承使得能够使用类名称和作用域解析运算符来调用基类方法double Student::Average() const
{if (ArrayDb.size() > 0)return ArrayDb.sum() / ArrayDb.size();else    return 0;
}

3.访问基类对象
使用作用域解析运算符可以访问基类的方法,如果使用基类本省,如Student类的包含版本实现了Name()方法,返回string对象成员name,私有继承时,string对象没有名称,无法返回name。
此时,强制类型转换

因为student是string类派生而来,因此可以通过强制类型转换,将Student类转换为string类对象,结果就成了继承而来的string对象。 this指向用来孝勇方法的对象, *this为用来调用方法的对象。const string & Student::Name() const {return (const string &) *this;}返回一个引用,指向用于调用该方法的student对象中继承而来的string对象翻译:student由string类私有继承而来,因此它要访问string对象的私有成员,只能先将自己强制类型转换为string类型的公有继承对象

4.访问基类的友元函数
用类名显式地限定函数名不合适,友元函数不属于类,可以通过显式的转换为基类来调用它
ostream & operator<<(ostream & os, const Student & stu)
{
os <<(const String &) stu;
}
cout << plato;
如果plato是一个student对象,则上面代码可用。stu将是指向plato的引用,os指向cout的引用

引用stu不会自动转换为string引用,是因为在不进行显示类型转换的情况下,不能将指向派生类的引用或指针赋给基类引用或指针在本例,即使公有继承,不强制转换也不行。会变成这样os <<stu;不停的与友元函数匹配,导致递归另外,student例子是多重调用,编译器无法确定应转换成哪个基类。

包含还是私有继承???
大多数倾向于包含!!!,
首先,容易理解,类声明中包含表示被包含类的显式命名对象,代码可以通过名称引用这些对象。
而私有继承是关系更首相
其次,继承会引起很多问题,尤其从多个基类继承。
另外,包含能够包含多个同类的对象,如三个string对象,继承只能使用一个这样的秀昂,对象呢没名称,那一区分
私有继承提供的特性比包含多
如果类包含了保护乘员(可以是数据,也可以是函数)。则这样的成员在派生类中是可用的,在继承层级结构之外不可用。如果使用组合将这样的类包含在另一个类中,则后者将不是派生类,而是为育继承层次结构之外,因此不能访问保护乘员,(多重包含组合)
但可以通过继承得到的是派生类,可以访问保护成员
另外,需要重新定义虚函数时候,只能使用私有继承,派生类可以重定义虚函数,但是包含类不能。私有继承重定义的函数将只能在类中使用,而不是公有的。
tip:通常,应该使用包含建立has-a关系,如果需要访问原有类的保护乘员,或重定义虚函数,则私有继承

14.2.3 保护继承
私有继承的变体,关键字:protected
class Student : protected std::string, protected std::valarray
{…}

保护继承,基类的公有成员和保护成员将成为派生类的保护成员,和私有继承一样,基类的接口在派生类中也可以使用,但是继承层次结构之外的不可用。
使用私有继承,第三代类将不能使用基类的接口,因为基类的公有方法在派生类中变成私有方法
使用继承类。基类的公有方法在第二代将变成受保护的,因此第三地派生类仍然可以使用他们
P550 表14.1

14.2.4使用using重新定义访问权限
使用保护派生或私有派生,要让基类的方法在派生类外可用,
法一,定义一个使用该基类方法的派生类方法,如希望Student类能使用valarray的sum()方法
double Student::sum() const //公有的派生类方法
{
return std::valarray::sum(); //使用私有继承的方法
}
法二,将函数调用包装在另一个函数调用中,即用using声明(像名称空间那样)
class Studen : private std::string, private std::valarray
{
public:
using std::valarray::min;
using std::valarray::max;
}

    上述using声明使得valarray<double>::min 可用,就好似他们是student的公有方法一样cout << ada[i].max();using声明只是用成员名,没有圆括号!! 函数特征标是返回类型e.g. 为使student类可以使用valarray的operator[]()方法,只需在student类声明的公有部分报下一下using std::valarray<double>::operator[]这将使得const或非const版本都可用 student自己的operator[]()的原型和定义可以删除
using只适用于继承,不适用于包含

14.3 多重继承
MI描述的是有多个直接基类的类。公有MI表示的也是is-a关系。
class SingingWater : public Water, public Singer {…}
每一个基类都需要public,不然,编译器默认是私有派生

会碰到的主要问题:从两个激烈继承而来的同名方法,从多个相关基类那里继承同一个类的多个实例。
慎用!!!e.g.worker|   ||      |
singer    waiter |      ||   |singingwaitersingingwaiter ed;
worker * pw = &ed; //二义性通常,应该使用类型转换来制定对象
worker * pw1 = (waiter *) &ed; //the worker is waiter
P556但是,即使这样,其中一份worker基类部分也是多余的,不需要的
在此,用到了虚基类。

1.虚基类
虚基类使得从多个类(具有相同的基类)派生出的对象只继承一个基类对象。 又是virtual
virtual 与public顺序没关系
class singer : virtual public worker {…}
class waiter : public virtual worker {…}

class singingwaiter : public singer, public waiter {...}
现在,singingwaiter对象只包含worker对象的一个副本。本质上来说,singer和waiter对象共享一个worker对象,也因此,可以使用多态。小问题解答:
1.虚函数和虚基类之间并不存在明显的联系,但是不能增加新关键字了。
2.为啥还使用将基类声明为虚的方式?第一,某些情况下,可能需要基类的多个拷贝;第二个,将基类作为虚的要求程序完成额外的计算,为不需要的工具付出代价是不应当的。第三,有缺点----这啥垃圾问题,重难点
3.为了使得虚基类能工作,C++需要调整。另外,虚基类还可能需要修改已有的代码。如增加virtual关键字

2.新的构造函数规则
对于非虚基类,唯一可以出现在初始化列表中的构造函数是即时基类构造函数。但这些构造函数可能需要将信息传递给其基类。
e.g.
class A
{
int a;
public:
A(int n = 0) : a(n) {}
};

    class B:public A {int b;public:B(int m = 0, int n = 0):A(n), b(m) {}};class C : public B {int c;public:C(int q = 0, int m = 0, int n = 0) : B(m,n), c(q) } {}};
如果 worker是虚基类,以上自动传递信息给各自基类将失效。C++在基类是虚的时候,禁止信息通过中间类自动传递给基类。
singingwaiter(const worker & wk, int p = 0, int v = singer::other):waiter(wk,p), singer(wk,v) {}
因此,这种wk被通过两条路径传递,行不通,这种情况下使用worker的默认构造函数不然,只能显式调用所需的基类构造函数。
singingwaiter(const worker & wk, int p = 0, int v = singer::other):worker(wk), waiter(wk,p),singer(wk,v) {}
waiter 和singer中wk基类部分数据不会被传递,只能通过worker被传递
上述代码将显式调用worker(const worker &).对于虚基类,必须,对于非虚基类,非法如果类有间接虚基类,则除非只使用虚基类的默认构造函数,否则必须显式调用该虚基类的某个构造函数

14.3.2哪个方法
对于单继承,如果没有重新定义方法,则将使用最近祖先中的定义,而在多重继承中,每个直接祖先都有一个show()函数,因此二义性。
可以使用作用域解析运算符来表明意图:
singingwaiter newhire(“aaa”, 2,2,soprano);
newhire singer::show(); //使用singer version

更好的方法是在singingwaiter中重新定义show,并指出使用哪个show
对于单继承来说,让派生方法调用基类的方法是可以的。还可以无线套娃。但是对于多重继承无效
如worker->waiter->singer  ,单例继承ok,多重继承中,singer将忽略waiter组件
同样 void singingwaiter::show(){singer::show();}无效补救措施:void singingwaiter::show(){singer::show();waiter::show();}但是这样会重复显示worker最终基类的内容多次解决措施:模块化方式,而不是递增方式。只提供一个显示worker组件的方法,一个只显示waiter组件或singer组件的方法(是或,不是和)然后在singingwaiter:show()中组合void worker::Data() const
{cout <<...;
}
void waiter::Data() const
{cout <<...;
}
void singer::Data() const
{cout <<...;
}void singingwaiter:Data() ocnst
[singer::data();waiter::Data();
}
void singingwaiter::show() const
{worker::Data();Data();
}
MMP,不就是相当于把公有的基类单独下是剩余的各自单独显示嘛这种方式,对象仍然可以沿用show()方法,但是Data()方法只能在类内部使用,作为协助公有接口的辅助函数。然而,Data()方法成为私有的将组织waiter中的代码使用worker::Data(),这正是保护访问类的用武之地。如果Data()方法是保护的,则只能在继承层次结构中的类使用它,其他地方不能使用另外一种方法:将所有的数据组件都设置为保护的,而不是私有的。不过保护方法(而不是保护数据)将可以更严格地控制对数据的访问。
P560

有关MI的一些问题
1.混合使用虚基类和非虚基类
通过多种途径继承了一个基类的派生类。如果基类是虚基类,派生类将 包含 基类的一个子对象。
如果基类不是虚基类,派生类将包含多个子对象。
混合使用的话,虚基类的子对象,和非虚基类的多个子对象……
那个SB会这么用
2.虚基类和支配
使用虚基类不一定会导致二义性。如果某个名称优先于其他所有名称,则使用它时,不用限定符,也无所谓

优先级:派生类中的名称优先于直接或简介祖先类中的相同名称。
虚二义性规则与访问规则无关 只要在派生链中存在同名,但是你没有限定是谁,则会存在二义性P567

14.4类模板
除了对象类型不同之外,代码相同。可以使用泛型–独立于类型。然后再将具体的类型作为参数传递给这个类。这样就可以使用通用的代码生成存储不同类型的类
14.4.1 定义类模板
和模板函数一样,模板类开头如下:
template

    template--定义一个模板<>--参数列表 class--变量的类型名Type--变量的名称,不一定必须是一个类,知识说Type是一个通用的类型说明符。新版C++:template <typename Type>可以使用自己的泛型名代替Type,其命名规则与其他标志符相同。使用模板成员函数替换原有类的类方法,每个函数头都将以相同的模板声打头:template <class Type>同样应使用泛型名Type替换typedef的标志符,还需将Stack::改为Stack<Type>::e.g.bool stack::push(const Item & item){}----->>>>>template <typename Type>bool stack<Type>::push(const Type & itme) {}如果在类声明中定义了方法(内联定义),则可以省略模板前缀和类限定符。模板,不是具体的定义。他们是C++编译指令,说明如何生成类和成员函数定义,具体实现---实例化或者具体化不能将模板成员函数放在独立的实现文件中。模板必须与特定的模板实例化请求一起编译使用。因此,模板信息放在一个头文件中。

14.4.2使用模板类
通过模板生成具体可用的类——实例化,具体类型替换泛型名
e.g.
Stack kernels; //int Stack
Stack colonels; //object Stack
使用的算法必须与类型一致,如stack类假设可以将一个项目赋给另一个项目。这种假设对于基本类型,结构和类来说是成立的(除非将赋值运算符设置为私有),但是对于数组是不成立的。

泛型标志符--例如这里的Type--被称为:类型参数,类似于变量,但只能将类型赋给他们。
必须显式地提供所需类型,与常规的函数模板不同,函数模板可以根据参数类型确定要生成哪种函数

14.4.3 深入探讨模板类
可以将内置类型或类对象用作类模板stack的类型(stack),指针也可以,如指针栈,但是要修改代码
2.正确的使用指针栈
法一,
让调用程序提供一个指针数组,每个指针指向不同的字符串。这样的指针栈,实际上还相当于是指针数组。

bool pop(Type & item);
template <class Type>
bool Stack<Type>::pop(Type & item)
{if (top > 0){item = items[--top];return true;}elsereturn false;
}Stack & operator=(const Stack & st);
template <class Type>
Stack<Type> & Stack<Type>::operator=(const Stack<Type> & st)
{if (this == &st)return *this;delete [] items;stacksize = st.stacksize;top = st.top;items = new Type [stacksize];for (int i = 0; i < top; i++)items[i] = st.items[i];return *this;
}
原型将赋值运算符函数的返回类型声明为stack引用,是stack<type>的缩写,稚嫩在类中这么使用。即可以在模板声明或模板函数定义内使用stack(而不是stack<type>),在类外面,即指定返回类型或使用作用域解析运算符时,必须完整的使用stack<type>初始化一组字符串常量,P577 类型可以为const char*
析构函数对于保存了指针的栈来说,只是山粗了构造函数中new出来的数组,对于指向的字符串无影响

14.4.4数组模板示例和非类型参数
模板常用作容器类,因为类型参数的概念适合将相同从存储方案应用于不同的类型。
允许指定数组大小的简单数组模板,14.3法一。另外就是使用模板参数来提供常规数组的大小。C++ array模板就是这么干

与法一使用构造函数方法相比,当前方法使用表达式参数方法是用到了为自动变量维护的内存栈,速度块,尤其小型数组构造函数用的是new和delete管理的堆内存
表达式参数方法的主要缺点是,每个数组带线啊哦都将生成自己的模板,也就是一下是俩个独立的声明:ArrayTP<double, 12> egg;ArrayTP<double, 13> dcounts;但是,Stack<int> egg[12];Stack<int> dunker[13];只生成了一个类声明,并将数组大小信息传递给类的构造函数另一个却别是,构造函数方法更通用,因为数组大小是作为类成员,而不是硬编码存储在定义中的,这样可以将一种大小的数组赋给另一种大小的驻足,也可以创建允许数组大小可变的类。表达式参数的大小是写死的。https://www.runoob.com/w3cnote/c-templates-detail.htmltemplate<class T1,class T2=int> class CeilDemo{
public:int ceil(T1,T2);
};template<class T1,class T2=int>
class CeilDemo
{
public:int ceil(T1,T2);
};格式问题1、类模板的格式为:
template<class  形参名,class 形参名,…>
class 类名
{ ... };4、在类模板外部定义成员函数的方法为:
template<模板形参列表> 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体},
template<class T1,class T2> void A<T1,T2>::h(){}。template <class T, int n>
关键字class指出T为类型参数,int 指出n的类型为int---指定特殊的类型而不是用作泛型名,成为非类型或表达式参数
模板的非类型形参也就是内置类型形参
非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。模板代码不能修改非类型参数的值,也不能使用参数的地址,因此n++ &n无效。实例化时,表达式参数的值必须是常量表达式
类模板类型形参不存在实参推演的问题,必须明确指定,且必须有表达式参数可以是整型,美剧,引用或指针。因此,double m是不合法的,但double * pm是合法的。

14.4.5 模板的多功能性
模板类可以用作基类,也可以用作组件类,还可以用作其他模板的类型参数。如,可以使用数组模板实现栈模板,也可以使用数组模板来构造数组–数组元素是基于栈模板的栈。
e.g.
template
class Array
{
privat:
Tentry;
}

    template <typebname Type>class GrowArray : public Array<Type> {...};tmeplate <tmeplate Tp>class Stack {Array<Tp> ar; //使用Array<> 作为元素}Array<Stack<int> > asi; //an Array of stacks of int

1.递归使用模板
ArrayTP< ArrayTP<int,5>, 10> twodee;
teodee是一个包含了10个元素的数组,其中每个元素都是一个包含5个int元素的数组。等价于int twodee[10][5];
参数顺序正好与二维数组相反。
2.使用多个类型参数
如希望类可以保存多种值
tmeplate <class T1, class T2>
class Pair
{
private:
T1 a;
T2 b;

}
3.默认类型模板参数
可以为类型参数提供默认值
template <class T1, class T2 = int> class Tp {…}
类模板类型参数可以这么干,但是函数模板参数不能这么干。然而,可以为非类型参数提供默认值,类模板和函数模板都可以

14.4.6 模板的具体化
类模板与函数模板类似,因为可以有隐式实例化,显式实例化和显式具体化。统称具体化。
模板以泛型的方式描述类。据具体化用具体的类型生成类声明。
1.隐式实例化
声明一个或多个对象,指出所需类型。编译器使用通用模板提供的处方生成具体的的定义
Array<int, 100> stuff; //隐式实例化,Array<class Type, int n>
编译器在需要对象之前,不会生成类的隐式实例化:
Array<int, 100> * pt; //指针,无需对象
pt = new Array<double, 30>; //现在需要对象了,此时需要编译器生成类定义,并根据定义创建要给对象

pt(1,2)这个过程就是一个隐式实例化的过程,它实例化生成了一个T为int的函数。因此我们得知,隐式实例化实际上就是模板函数根据传入的实参类型实例化一个函数的过程。

2.显式实例化
当使用template,并指出所需类型来声明类时,编译器将生成类声明的显式实例化。声明必须位于模板定义所在的名称空间中,如:
template class Array<string, 100>; //生成类了 类模板 Array<string, int n>
这种情况下,虽然没有创建或提及类对象,编译器也将生成类声明,包括方法定义。和隐式实例化一样,也嫁给你根据通用模板生成具体化。
对,你猜的没错,实际上就是我们显式地写明了是何种类型

3.显式具体化
所谓具体化,是指我对此函数做出的具体的定义。注意:具体化需要给出函数的具体实现。通过以下例子将说明。
针对特殊情况,更改具体的实现,而不是使用泛型定义的模板得到的方法。

当具体化模板和通用模板都和实例化请求匹配时,具体化更优先C++98标准选用了下面的方法
对于给定的函数名,可以有非模板函数、模板函数和显示具体化模板函数以及他们的重载版本。
显示具体化的原型和定义应以template<>打头,并通过名称支出类型
具体化优先于常规模板,而非模板函数优先于具体化和常规模板格式:
template <typename T>
class SortedArray
{...
}
具体化格式:
template <> class Classname<specialized-type_name> {...}

4.部分具体化
即,部分限制模板的通用性。例如,部分具体化可以给类型参数之一指定具体的类型
//general template
template <class T1, class T2> class Pair {…}
//specialization with T2 set to int
template class Pair<T1, int> {…}
当T1也指定类型
tmeplate <> class Pair<int, int) {…} 成了显式具体化

在有多种选择的情况下,编译器偏向于具体化程度最高的。
也可以通过指针提供特殊版本来部分具体化现有的模板如果提供的类型不是指针,编译器使用通用模板,如果时指针,编译器使用指针具体化版本--指针版本需要自己的特殊模板
P589 重难点??????

14.4.7成员模板----模板套模板—相对独立,在四有部分有完整声明
模板可用于结构,类或模板类的成员
查阅其他资料 重点
模板嵌套
template
template
U beta::blab(U U, T t)
{
}

        T,V和U作为模板参数

14.8 模板用作参数----模板套模板,融入
模板可以包含类型参数,非类型参数和模板的参数,用于实现STL
template <template class Thing> class Crab
template class Thing-------模板参数,template class时类型 Thing 是参数
模板参数Thing将被替换为声明Crab对象时,被用作模板参数的模板类型

可以混合使用模板参数和常规参数
template <template <typename T> class Thing, typename U, typename v> cLASS Crab
{
private:Thing<U> s1;Thing<V> s2;
}
Crab<Stack, int, double> nebula

14.4.9模板类和友元
模板类声明也可以有友元,分三类
1.非模板友元
2.约束模板友元,即友元的类型去救鳄鱼类被实例化的类型
3.非约束模板友元,即友元的所有具体化都是泪的没有给具体化的友元

1.模板类和非模板友元函数
模板类中将一个馋鬼函数声明为友元
template
class HasFriend
{
public:
friend void counts();
};
上述声明使得counts()函数成为模板所有实例化的友元,
counts()不是通过对象调用的,因为它是友元,不是成员函数,也没有对象参数,访问HasFriend对象的方式:
可以访问全局对象,可以使用全局指针访问非全局对象,可以创建自己的对象,可以访问独立于对象的模板类的静态数组成员

为了向友元函数传递对象,只能特定的具体化,如HasFriend<short>
template <class T>
class HasFriend
{friend void reprot(HasFriend<T> &); }report本身并不是模板函数,而只是使用一个模板用作参数,意味着必须为要使用的友元定义显式具体化
void report(HasFriend<short> &) {...} 成为了HasFriend<int>类的友元

2.模板类的约束模板友元函数
友元函数本生成为模板,使得类的每一个具体化都获得与友元匹配的具体化

首先,在类定义的前面声明每个模板函数
template <typename T> void counts();
template <typename T> void reprot(T &);
然后,在函数中再次将模板声明为友元,这些语句根据模板参数的类型声明具体化
template <typename TT>
class HasFriendT
{//counts()没有参数,无法推断类型,只能使用模板参数语法<TT>指明具体化,TT是HasFriendT类的参数类型friend void counts<TT>();// <>指出这是模板具体化,对于reprot,<>可以为空,因为可以从函数参数推断出如下模板类型参数HasFriendTT<TT>,然而,也可使用reprot<HasFriendT<TT> > (HasFriendT<TT> &)friend void reprot<>( HasFriendT)<TT> & );
}e,g,
HasFriendT<int> squack;
生成如下定义
class HasFriendT<TT>
{friend void coutns<int >();friend void report<>(HasFriendT<int> &);
}
此时友元也成了模板最后,为友元提供模板定义
每种类型多有自己的友元函数

3.模板类的非约束模板友元函数
约束模板友元函数是在类声明外面声明的模板的具体化。
通过在类内部声明模板,可以创建非约束友元函数。即每个函数具体化都是每个类具体化的友元函数,对于非约束友元,友元模板类型参数与模板类类型参数都是不同的
template
class ManyFriend
{
template <typename C, typename D> friend void show2( C &, D &):
};
函数调用show2(hfi1, hfi2),与下面的具体化匹配
//友元函数模板
template<> void show2 <ManyFriend &, ManyFriend &> (ManyFriend & c, ManyFriendM & d);
因为踏实所有ManyFriend具体化的友元,所以能够访问所欲呕具体化对象中数据成员。

//函数模板
template <class 形参名,class 形参名,......> 返回类型 函数名(参数列表)
{函数体
}

14.4.10 模板别名C++11
typedef std::array<double,12> arrd;

C++11新功能使用模板提供一系列别名template<typename T>using arrtype = std::array<T,12> //using 模板别名 = 引用细节;//arrtype被定义为一个模板别名arrtype<double> days; // days is type std::array<int, 12>
总之,arrtype<T> 表示类型std::array<T, 12>C++11允许将语法using = 用于非模板。 用于非模板时,与常规typedef 等价第十八章 新增 可变参数模板

第十五章 友元、异常和其他
友元类 及方法
嵌套类
引发异常、Try块和catch块
异常类
运行阶段类型识别RTTI
dynamic_cast typeid
static_cast, const_const, reiterpret_cast

15.1 友元
友元类的所有方法都可以访问原始类的私有成员和保护成员。
可以将特定的成员函数指定为另一个类的友元,只能有类内部定义,外部不可强加。
尽管友元被授予从外部访问类的私有部分的权限。

15.1.1友元类
例子,remote类可以改变tv类的状态,因此可以成为tv类的友元
friend class Remote;使得Remote成为友元
友元可以位于公有,私有或保护部分,所在位置无关紧要。但是由于Remote提到了Tv类的具体方法,所以编译器必须了解Tv类后,才能处理Remote类。位次,最简单的方法是首先定义Tv类。当然也可以使用前向声明
P603

隐式表示——在类中直接定义
显式表示——类外定义时声明按位异或 a^=b  返回bool值https://blog.csdn.net/WHEgqing/article/details/100089649
一个类 A 可以将另一个类 B 声明为自己的友元,类 B 的所有成员函数就都可以访问类 A 对象的私有成员。在类定义中声明友元类的写法如下:
class CCar
{
private:int price;friend class CDriver;  //声明 CDriver 为友元类
};
class CDriver
{
public:CCar myCar;void ModifyCar()  //改装汽车{myCar.price += 1000;  //因CDriver是CCar的友元类,故此处可以访问其私有成员}
};
int main()
{return 0;
}
第 5 行将 CDriver 声明为 CCar 的友元类。这条语句本来就是在声明 CDriver 是一个类,所以 CCar 类定义前面就不用声明 CDriver 类了。第 5 行使得 CDriver 类的所有成员函数都能访问 CCar 对象的私有成员。如果没有第 5 行,第 13 行对 myCar 私有成员 price 的访问就会导致编译错误。一般来说,类 A 将类 B 声明为友元类,则类 B 最好从逻辑上和类 A 有比较接近的关系。例如上面的例子,CDriver 代表司机,CCar 代表车,司机拥有车,所以 CDriver 类和 CCar 类从逻辑上来讲关系比较密切,把 CDriver 类声明为 CCar 类的友元比较合理。友元关系在类之间不能传递,即类 A 是类 B 的友元,类 B 是类 C 的友元,并不能导出类 A 是类 C 的友元。“咱俩是朋友,所以你的朋友就是我的朋友”这句话在 C++ 的友元关系上 不成立。

15.1.2友元成员函数
让特定的类成员成为另一个类的友元,但是各种声明的顺序很重要

将全局函数声明为友元的写法如下:
friend 返回值类型 函数名(参数表);将其他类的成员函数声明为友元的写法如下:
friend 返回值类型 其他类的类名::成员函数名(参数表);但是,不能把其他类的私有成员函数声明为友元。
#include<iostream>
using namespace std;
class CCar;  //提前声明CCar类,以便后面的CDriver类使用
class CDriver
{
public:void ModifyCar(CCar* pCar);  //改装汽车
};
class CCar
{
private:int price;friend int MostExpensiveCar(CCar cars[], int total);  //声明友元friend void CDriver::ModifyCar(CCar* pCar);  //声明友元
};
void CDriver::ModifyCar(CCar* pCar)
{pCar->price += 1000;  //汽车改装后价值增加
}
int MostExpensiveCar(CCar cars[], int total)  //求最贵气车的价格
{int tmpMax = -1;for (int i = 0; i<total; ++i)if (cars[i].price > tmpMax)tmpMax = cars[i].price;return tmpMax;
}
int main()
{return 0;
}书中例子remote::set_chan()在Tv中充当友元函数,所以,remote必须在Tv前先定义,
但是,Remote提到了Tv对象,Tv又需要放在remote前面,
避免这种循环依赖的方法:使用前向声明 forward declaration:在remote定义的前面插入:class Tv成这样:class Tv; //forward declarationclass Remote; //用到了Tv对象class Tv {...}; //用到了remote中的方法不能这么干 ,Tv类对象处于被调用的局面,只能在后。Remote用到了Tv类中的数据,在Tv中充当友元class Remote;class Tv {...};calss Remote {...};因为在编译器在Tv类的声明中看到Remote的一个方法被声明在Tv类的友元之前,应该先看到Remote类的声明和set_chan--这个会从当友元函数的声明前一种,只是用到了对象,因此可以先告诉remote,下面会有这么个对象另外,remote中包含内联代码调用了Tv类的方法,所以编译器此时必须已经看到Tv类的声明,这样才能知道Tv类有哪些方法。但是已经在后面了,解决方法是,使Remote声明中只包含方法声明,并将实际的定义放在Tv类之后,顺序如下:class Tv; //forward declarationclass Remote; //用到了Tv对象,用到了一个Tv的一个方法的方法---只有声明class Tv {...}; //用到了remote中的方法//放置remote方法的定义通过在方法定义中使用inline关键字,仍然可以时期成为内联方法#include <iostream>
using namespace std;class B;  //前向声明class A{
private: int a;
public: A() { a = 1; }void print( B & b );
};/* 示例位置1 */class B{
private: int b;
public: B() { b = 6; }void print() { cout << b << endl; }friend void A::print( B & b );  //友元成员函数
};/* 被定义为友元成员函数的函数必须在类外(另一个使用该函数的类后面)定义 */
void A::print( B & b ) {cout << "b = " << b.b << endl;
}int main() {A a;B b;a.print( b );return 0;
}/*输出结果:
b = 6*/
需要注意的是:

(1)类的前向声明。由于在A中的print函数需要使用B,如果不进行B的前向声明,编译器就不知道B是一个类,会报错。
(2)类的排列顺序。在类B中会用到A的方法print(),因此需要先定义A,再定义B。
(3)友元成员函数的定义位置。友元成员函数不能使用内联代码,类中只能有函数声明。函数定义需要放到类之后,在类外进行定义,而且必须放到另一个类定义的后面。(对上面代码来说,若将A::print()的定义放在类B定义之前——示例位置1处,也会报错,提示类B未完成)

对于友元的位置在何处生明,可以简单记为friend在哪,哪个就可以被外面直接访问。(friend在类A,A就可以被其他特定位置访问

内联函数链接性是内部的,意味着函数定义必须放在使用函数的文件中。放于头文件,因此在使用函数的文件中包含头文件可确保将定义放在正确的位置。
也可以将定义放在实现文件中,但必须删除inline,这样函数的链接性将是外部的
另外Tv类中提到了Remote是个友元类,不需要前向声明,友元语句本省已经指出Remote是一个类。

15.1.3 其他友元关系
互相成为友元,对于使用Remote对象的Tv方法,其原型可在Remote类声明之前声明,但必须在Remote类声明之后定义,以便编译器有足够的的信息来编译该功能
e.g.
class Tv
{
friend class Remote;
public:
void buzz(Remote &r);
};

    class Remote{friend class Tv;public:void Bool volup(Tv & t) { t.volup(); }};inline void Tv::buzz(Remote & r){...};由于Remote声明位于Tv声明的后面,所以可在类声明中定义Remote::volup(),但Tv::buzz()必须在Tv声明的外部定义,使其位于Remote声明的后面。

15.1.4 共同的友元
函数 需要访问连个类的私有数据。可以将函数用作两个类的友元。
如prober 和analyzer ,
class Analyzer; //forward declartion
class Prober
{
friend void sync(Analyzer & a, const Probe & p); //sync a to p
friend void sync(Probe & a, const Analyzer & p); //sync a to p
};
class Analyzer
{
friend void sync(Analyzer & a, const Probe & p); //sync a to p
friend void sync(Probe & a, const Analyzer & p); //sync a to p
};

//define the friend functions
inline void sync(Analyzer & a, const Probe & p) {...}
inline void sync(Probe & a, const Analyzer & p) {...}
前向声明使编译器看到Probe类声明重点友元声明时,知道Analyzer是一个类型。

15.2 嵌套类
在另一个类中声明的类–嵌套类。它通过提供new type class scope来避免名称混乱.嵌套类所在的类,它的成员函数可以创建和使用嵌套类的对象。仅当声明位于公有部分,才能在包含类的外面使用嵌套类,而且必须使用 :: 才能使用

对类进行嵌套与包含并不同,包含意味着将类对象用作另一个类的成员。对类进行嵌套并不创建类成员,而是定义了一种类型,该类型尽在包含嵌套类声明的类中有效。
对类进行嵌套是为了帮助实现另一个类,并避免名称冲突。
e.g.class Queue {class Node {public:Item item;Node * next;Node(const Item & i) :item(i),next(0) {}};};在Queue中定义了一个Node类调用方式:Queue::Node::NOde(const Item & i) : item(i), next(0) {}

15.2.1 嵌套类和访问权限
嵌套类的声明位置决定了嵌套类的作用域。即它决定了程序的哪些部分可以创建这种类的对象。其次和其他类一样,嵌套类的公有,私有,保护部分控制了对类成员的访问。
在哪些地方可以使用嵌套类以及如何使用,取决于作用域和访问控制。
1.作用域
如果在另一个类的私有部分,则只有后者知道它,
类的默认访问控制权限是私有的。上述Queue成员可以使用Node对象和指向Node对象的指针,不能使用里面的数据,但是程序其他部分不知道Node这个类。从Queue派生出来的类,Node也是不可见的,因为派生类不能直接访问基类的私有部分

如果在另一个类的保护部分声明,则它对于后者来说是可见的,可以使用里面的数据。但是对于外部世界是不可见的。派生类将知道这个嵌套类,并可以直接创建和使用这种类型的对象
如果声明再公有部分,则全世界都能看到他,但是嵌套类的作用域是包含它的类,外部世界使用需要类限定符嵌套结构和枚举的作用域与此相同,公有枚举提供程序员的类常数P613

2.访问控制
15.2.2 模板中的嵌套
程序15.5
上述Queue成员可以使用Node对象和指向Node对象的指针

15.3 异常
15.3.1调用abort()
位于cstdlib头文件中。向标准错误流–cerr使用的错误流发送 abonormal program termination,然后终止程序,返回一个随实现而异的值。告诉操作系统或父进程,处理失败。
abort()是否刷新文件缓冲区取决于实现。exit(),也可以刷新缓冲区,但不显示消息。
15.3.2 返回错误码
更灵活的是使用函数返回值来指出具体的问题,单独参数专门干这个,引用或者指针,全局变量也ok
15.3.3 异常机制
C++异常是对程序运行过程中发生的异常情况的响应,提供了将控制权从程序的一个部分传递到另一个部分的途径,对异常的处理包括三个部分:
1.引发异常–程序出现问题 throw语句实际上是跳转,并终止throw之后的语句,命令程序跳转到另一条语句,throw关键字表示引发异常,紧随其后的值(例如字符串或对象)指出了异常的特征。
2.使用处理程序捕获异常(exception handler。异常处理程序位于要处理问题的程序中,catch关键字表示捕获异常,
处理程序以关键字catch开头,随后是位于括号中的类型声明,指出了异常处理程序要相应的异常类型;然后是一个花括号内的代码块,指出要采取的措施。catch和异常类型用作标签,指出当程序引发异常,跳转到此处执行。异常处理程序也叫catch块。
3.使用try块 try块标识其中特定的异常可能激活的代码块,后面跟着一个或多个catch块。try关键字。后面花括号内的代码块表明需要注意这些代码引发的异常。

    e.g.try{...}  其中的语句可能导致异常,发生异常,后面的catch会处理try{{...throw "..."; //此处为异常类型,可以是字符串或类或者其他C++类型}}catch(const char *s){std::cout << s << std::endl;std::cout << "Enter a new ...";continue;}catch(...){...break; //跳出执行块外,如果是exit(EXIT_FAIL_URE),则程序立即结束}执行thorw语句类似与执行返回语句,它将终止函数执行;导致程序沿着函数调用序列后退,直到找到包含try块的函数。然后将控制权返回给包含try块的函数,并在该函数中寻找引发的异常类型匹配的异常处理程序执行完try块后,如果没有依法异常,跳过后续的catch块如果没有对应的catch块,默认情况下,最终会调用abort()函数

15.3.4 将对象用作异常类型
通常,引发异常的函数将传递一个对象,这样可以使用不同的异常类型来区分函数在不同情况下引发的异常。另外,对昂可以携带信息,方便确定异常原因,和catch采取什么措施。 P623

15.3.6 栈解退 --函数栈,每次返回上一个函数
如果try块没有直接调用引发异常的函数,而是调用了对一番异常的函数进行调用的函数(套娃),则程序流程将从引发异常的函数 直接 跳转到包含try块和处理程序的函数,而不是一层一层返回(return)—栈解退(unwinding the stack)。栈内容也会一层一层释放。随后控制权交给块尾巴上的异常处理程序,而不是调用函数或函数调用后的第一条语句

重新引发的异常将由下一个捕获这种异常的try—catch块组合进行处理。没找到,程序就会异常终止。
catch (bad_hmean & bg) // start of catch block
{bg.mesg();std::cout << "Caught in means()\n";throw;             // rethrows the exception
}
这是主动返回已有的异常到上一层代码块中,寻找可以处理的catch块,否则在没有找到处理块的情况下,也只能被动的返回到上一层,因为没找到嘛
try-throw_catch

15.3.7 其他异常特性
throw 语句将控制权向上返回到第一个这样的函数:包含能够捕获相应异常的try-catch组合所在的代码块。
普通函数中的return将控制权返回到该函数函数顶

另外,出现异常,编译器总会创建一个临时拷贝。
class problem {...}
...
void super() throw (problem)  //???????? 重难点
{...if(oh_no){problem oops; //construct object throw oops; //throw it ...}
}
...
try{super();
}
catch(problem &p)
{...
}
p指向oops的副本而不是它本身。执行完super后,oops将消失。
代码中使用引用。引用另一个重要特征:基类引用可以执行派生类对象。假设有,一组通过继承关联起来的异常类型,则在异常规范中只需列出一个基类引用,它将与任何派生类对象匹配。如果有一个异常类继承层次结构,应这样排列catch块:将捕获位于层次结构最下面的异常的catch语句放在最前面,将捕获基类异常的catch 语句放在最后面。在不知道异常类型的情况下,这样catch (...) {...}  可以捕获任何异常 类似于switch 中的default

15.3.8 exception类—STL
C++异常为设计容错程序提供语言级支持
exception头文件定义了该类,C++可以把它用作其他异常类的基类
其中,有个what()虚拟成员函数,返回一个字符串,其特征随实现而异。派生成其他的时候要重定义
#include
class bad_mean : public std::exception
{
public:
const char* what() { return “…”;}
}
或者使用基类处理程序捕获他们
catch(std::exception &e)
{
cout << e.hwat() << endl;
}

基于exception的异常类型
1.stdexcept异常类定义在<stdexcept>中
logic_arror 和runtime_error类
class logic_error : public exception
{
public:logic_error( const std::string& what_arg );
}
构造函数接受一个string对象作为参数,该参数提供了方法what()以C-风格字符串方式返回的字符数据

15.3.9 异常、类和继承 P638
15.3.10 异常何时会迷失方向
1.在带有异常规范的函数中引发,则必须与规范列表中的某种异常匹配
在继承层次结构中,类类型与这个类及其派生类的对象匹配,否则成为unexpected exception
默认情况下,程序异常终止,C++17摒弃
2.异常不是在函数中引发,或者函数没有异常规范,则必须捕获他,如果没有捕获,则称为
uncaught exception,默认情况下,程序终止。

未捕获异常不会导致程序立即终止,首先调用terminate(),默认情况下terminate()调用abort()函数。因此,可以修改terminate调用的函数来修改他之后的行为
因此可以调用头文件 <exception>中的set_terminate()函数。 typedef void (*terminate_handler)();
std::terminate_handler is the function pointer type (pointer to function that takes no arguments and returns void), which is installed and queried by the functions std::set_terminate and std::get_terminate and called by std::terminate.The C++ implementation provides a default std::terminate_handler function, which calls std::abort(). If the null pointer value is installed (by means of std::set_terminate), the implementation may restore the default handler instead.Defined in header <exception>
std::terminate_handler set_terminate( std::terminate_handler f ) throw();
(until C++11)
std::terminate_handler set_terminate( std::terminate_handler f ) noexcept;
(since C++11)
Makes f the new global terminate handler function and returns the previously installed std::terminate_handler.Parameters
f   -   pointer to function of type std::terminate_handler, or null pointer
Return value
The previously-installed terminate handler, or a null pointer value if none was installed.e.g.
#include <iostream>
#include <cstdlib>
#include <exception>
int main()
{std::set_terminate([](){ std::cout << "Unhandled exception" << std::endl; std::abort();});throw 1;
}>> Unhandled exception
set_terminate 使用函数的名称作为参数,并返回该函数的地址
多次使用,则terminate调用最后一次set_terminate调用设置的函数set_terminate 设置terminate 调用的函数
e.g.void myQuit() {...};set_terminate(myQuit);之后terminate将调用myQuit

15.3.11 有关异常的注意事项

15.4 RTTI
运行阶段类型识别–Runtime Type Identification
旨在为程序运行阶段确定对象的类型提供一种标准方式。因为不同库可能互不兼容,无法互相识别。RTTI语言标准为此而生。

15.4.1 RTTI用途
例如,从基类派生了许多其他类,其中都有各自私有的方法,要确定那个类对象赋给了指针,就需要识别类类型了
15.4.2 RTTI工作原理
C++有三个支持RTTI的元素
dynamic_cast运算符将使用一个指向基类的指针来生成一个指向派生类的指针,否则返回0–空指针
typeid返回一个指出对象类型的值
type_info结构存储了有关特定类型的信息
RTTI只能用于包含虚函数的类层次结构–基类-派生-派生,只有这种才能将派生类的地址赋给基类指针

1.dynamic_cast运算符 -- 最常用的RTTI组件
不能回答指针指向的是哪类对象,但能回答,是否可以安全地将对象的地址赋给特定类型的指针只有指针类型与对象类型(或对象的直接或间接基类的类型)相同的类型转换才安全,如a类赋给a类 指向的对象(*pt)的类型为Type或者是从Type直接或间接派生而来的类型,则下面的表达式将指针pt转换为Type 类型的指针:
Type *p = dynamic_cast<Type *>(pt)否则,结果为0,即空指针。RTTI可能默认关闭,嘚自己开dynamic_cast也可用于引用:没有与空指针对应的引用值,因此无法使用。特殊的引用值来指示失败。当请求不正确时,dymamic_cast 将引发类型为 bad_cast 的异常,这种异常是从 exception 类派生而来的,它是在头文件 typeinfo中定义的。因此,可以像下面这样使用该运算符,其中rg是对Grand对象的引用。#inlcude <typeinfo>try{Super & rs = dymamic_cast<Superb &>(rg);}catch(bad_cast &) {}2.typeid运算符和type_info类
typeid运算符能够确定两个对象是否为同种类型,类似sizeof,接受参数:类名或结果为对象的表达式
返回一个对type_info对象的引用,type_info是头文件typeinfo(C--typeinfo.h)中的类
type_info类重载了==和!=运算符如果用于判断的等式的右侧==typeid参数为空指针,则引发bad_typeid异常,从exception类派生,位于typeinfo中
type_info类的实现随厂商而已,但都包含一个name()成员,用于返回一个随实现而已的字符串:通常是类的名称。cout << typeid(*p).name() //显式p指针指向的对象所属的类定义的字符串RTTI受人诟病

15.5 类型转换运算符
C语言中的 只是基于机器数上的转换–重新解读二进制位,可能无意义。
C++中添加四种类型转换运算符,加以限制
dymamic_cast 需要转换和被赋值的双方都是可访问基类,不然传递值为空指针,使得类层次结构中进行向上转换(由于is-a,安全),而不允许其他转换

    const_cast,执行只有一种用于的类型转换,即改变值为const或volatile,语法同上const_cast<type-name> (expression)除了const或volatile特征(有或者无)可以不同,type_name和expression的类型必须相同e.g.High bar;const High * pbar = &bar;High *pb = const_cast<High *>(pbar); //ok。 删除了const标签const Low * pl = const_cast<const Low *> (pbar);//ng 试图从const high *->const low*有些值多数时候常量,有时候必须可以修改,因此,声明为const,需要修改,const_cast  static_const 语法同上,仅当type_name可被隐式转换为expression所属的类型或expression可被隐式转换为type_name所属的类型时,才合法如 A 时B的基类,则从A到B,从B到A都合法,其余违法A *pa = static_cast<A *> (&B_obj);//向上转换B *pa = static_cast<B *> (&A_obj);//向下转换, 从基类指针到派生类指针,在不进行显示类型转换,无法进行,但由于无需进行类型转换,便可以进行另一个方向的类型转换,因此向下转换合法。同理,无需类型转换,枚举也可以被转换为振兴,同样可以将基本数据类型之间转换。reinterpret_cast 用于天生危险的类型转换,它不允许删除const。简化对依赖实现的跟踪。语法同上,适用于依赖实现的底层技术,不可移植 不支持所有的类型转换,可以将指针类型转换为足以存储指针的整形,不能将指针转换为更小的整形或浮点型。不能将函数指针转换为数据指针,反之亦然

第十六章 string类和STL
16.1 string类
string实际上时模板具体化basic_string的一个typedef,同时省略内存管理相关的参数
size_type是一个依赖于实现的整形 定义于string头文件
string类将string::npos 定义为字符串的最大长度 通常为unsigned int 的最大值
NBTS:null-terminated string 表示以空字符结束的字符串–传统的C字符串
P656 string初始化方式

C++新增的构造函数
string(string && str)类似于复制构造函数,导致新创建的string为str的副本,与复制构造函数不同的是,不能保证将str视为const。----移动构造函数,move constructor,
string(initializer_list<char>il)将初始化列表语法用于string类,string aa = {'f', 'f'}; 合法 string bb {'f','f'};  合法 P659 string类输入C-风格 cin>>cin.getline(str, 100, 'a'); //读取一行,100字符,读到a停止cin.get(str, 100);string对象 cin>>getline(cin, str, 'f'); //读到 f停止getline 忽略\n get则保留 ‘f’限定读取到的字符十个可选参数区别:string版本的getline()自动调整目标string对象的大小,使之刚好能够存储输入的字符c风格的字符串的函数是istream类的方法,而string版本是独立的函数这就是c风格字符串输入,cin是调用对象,而对于string对象的输入,cin是函数参数的原因 自动调整输入大小的限制:1.string对象最大允许长度的限制,string::npos制定,通常为max unsigned int值,大文件ng2.程序可使用的内存。string版本的getline从输入中读取字符,转存到string中,直到:1.文件结尾,输入流的eofbit将被设置,方法fail()he EOF()都将返回true2.遇到分节字符(默认\n), 将分解字符从输入流中删除,并且不存储它3.读取的字符数达到最大允许值,将设置failbit,fail()将单独返回true输入流对象是一个统计系统,用于跟踪流的错误状态。在这个系统中,检测到文件尾后将设置 eofbit寄存器,检测到输入错误时将设置failbit 寄存器,出现无法识别的故障(如硬盘故障)时将设置badbit寄存器,一切顺利时将设置goodbit 寄存器。第17章将更深入地讨论这一点。string版本的opcrator>>()函数不断读取,直到遇到空白字符并将其留在输入队列中,而不是不断读取,直到遇到分界字符并将其丢弃。空白字符指的是空格、换行符和制表符,是任何将其作为参数来调用 isspaceC)时,该函数返回ture 的字符。P662 find重载  返回索引  正向查找rfind--参数最后一次竖线的位置find_first_of 参数首次出现的位置find_last_of 参数最后一次出现的位置find_first_not_of,在字符串中查找第一个不包含在参数中的字符,e.g. string str("abcdf");int a = str.find_first_not_of("abc", 0);   //0可省略//a = 3find_last_not_of,在原字符串中最后一个与指定字符串(或字符)中的任一字符都不匹配的字符,返回它的位置e.g.string str("abcdf");int a = str.find_last_not_of("hf");npos变量是string类的静态成员,值是string 对象能存储的最大字符数。由于索引从0开始,所以它比最大的索引值大1,因此可以使用它来表示没有查找到字符或字符串。自动调整大小 新增字符,可能占用了其他人的地方,可能需要分配新内存块,并将原有内容复制过去。实践中,C++实现分配一个比实际需求大的内存块,但还不够,则,程序将分配一个原来两倍的内存块。capacity()返回当前分配的大小,reserve()请求的最小长度的内存块fout.open(filename.c_str());
open要求使用C风格字符串作为参数,c_str返回一个指向C风格字符串的指针,该字符串内容与用于调用c_str方法的string对象相同,因此可以打开一个名称存储在string对象中的文件

16.1.5字符串种类
string库基于一个模板类:
template<class chatT, class traits = char _traits, class Allocator = allocator >
basic_string {…};
有多个具体化,都有typedef名称
typedef basic_string string;

Template parameters
CharT   -   character typeTraits    -   traits class specifying the operations on the character type
Allocator   -   Allocator type used to allocate internal storage ,use new and delete
以上两个有预定义的模板

16.2智能指针模板类
行为类似于指针的类对象
函数终止,内部生成的指针占据的栈内存将被释放,但是new出来的内存堆只能delete释放,不然一直被占用
16.2.1 使用只能指针
三个智能指针模板–auto_ptr, unique_prt, shared_ptr都定义了类似指针的对象,可以将new获得的地址赋给这种对象,无论直接或间接。当只能指针过期时,其析构函数将使用delete释放内存。

auto_ptr模板中的构造函数
Template<class X> class auto_ptr {
public:expolicit auto_ptr(Type * p) throw(); //显式构造函数 指针作为参数
};auto_ptr<double> pd(new double> 其他两种语法相同
智能指针对象的操作有些类似常规指针,如解除引用 *pd, 访问结构成员 pd->data, 将他赋给指向相同类型的常规指针, 将它赋给另一个同类型的智能指针对象。注意:string vaction("afhfhf");shared_ptr<string> pvac(&vaction>); //ng
pvac过期,delete将会作用于非堆内存,这是错误的

16.2.2 只能指针的注意事项
指向同一个内存两次的智能指针将会释放它两次,这不行,解决方法:
1.定义赋值运算符,执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副
2.建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的构造函数会删除该对象。然后,让赋值操作转让所有权。这就是用于auto_ptr和unique ptr 的策略,但unique_ptr 的策略更严格。
3.创建智能更高的指针,跟踪引用特定对象的智能指针数。称为引用计数(reference coumi)例如,赋值时,计数将加1,而指针过期时,计数将减1。仅当最后一个指针过期时,才调用delete。这是shared_ptr 采用的策略。

auto_ptr转让所有权的过程中,前夫将会销毁保存的数据,之后再次访问该内存将会报错。
unique_ptr 不同的是 p = f[2]; 立即报错 这总比运行出错好

16.2.3 unique_ptr 相对于auto_ptr的好处
当一个函数返回一个临时unique_ptr指针,然后p接管临时指针的对象, 这是可以的,因为函数内的指针之后会被立即销毁,根本没有时间被其他函数访问

总之,将unique_ptr赋给另一个时,可以是临时右值,但是如果unique_ptr不能存在一段时间new 对应auto_ptr shared_ptr, new[]不能使用他们
不使用new, 不能使用auto_ptr,huo shared_ptr,
不使用new或new[] 不能使用unique_ptr

16.2.4 智能指针选择
程序使用多个指向同个对象的指针----shared_ptr
如,一个指针数组,并使用辅助指针来标识特定的元素
两个对象都包含指向第三个对象的指针
STL容器包含指针。STL算法支持赋值和赋值操作,用到了shared_ptr,不能使用unque_ptr和auto_ptr

不需要多个指向同一个对象的指针,使用unique_ptr。  new分配内存,并返回指向该内存的指针,将返回类型声明为unique_ptr也okshared_ptr 包含一个显示构造函数,将右值unique_ptr转换为shared_ptr,shared_ptr接管unique_ptr的对象

16.3 标准模板库STL
STL提供了一组表示容易、迭代器、函数对象和算法的模板。
容器,一个与数组类似的单元,可以存储若干值。存储的类型必须相同
迭代器能够用来遍历容器的对象,与能够遍历数组的指针类似,是广义指针
函数对象是类似函数的对象,可以是类对象或函数指针(包括函数名,函数名称被用作指针)
STL 可以构造各种容器(包括数组,队列和链表),执行各种操作(查找,排序,随机排列)

STL 不是面向对象的编程,而是一种不同的编程模式---泛型编程 generic programming

16.3.1 模板类 vector–对应数组
vector类提供了与第14章介绍的valaray和ArayTP 以及第4章介绍的aray类似的操作,即可以创建 vector对象,将一个vector对象赋给另一个对象,使用[]运算符来访问vector元素。
要使类成为通用的,应将它设计为模板类,STL正是这样做的———在头文件vector(以前为 vectorh)中定义了一个vector模板

 int n;vector<int> aa{n};for( int i = 0; i < n; i++)cout << aa[i] << end;分配器,与string类似,各种STL容器模板都接收一个可选的模板参数,用来制定使用那种分配器对象来管理内存,vector 模板:template<class T, class Allocator = std::allocator<T>> class vector {...};
省略的话,默认使用allocator<T>,它可以使用new和deleteSTL容器的基本方法
size--元素数目
swap--交换内容
begin--返回一个指向容器第一个元素的迭代器
end--返回表示超过容器尾的迭代器迭代器:广义指针。它可以是指针,也可以是一个可对象执行类似指针的操作--如解除引用和递增--的对象
这样,STL能够为不同的容器类提供统一的接口。迭代器的类型是iterator的typedef 作用域为整个类
如,为vector的double类型规范声明一个迭代器
vector<double>::iterator pd;
vector<double> scores;
可以有以下操作:
pd = scores.begin*();     //指向第一个元素
*pd = 22.3;              //解除pd引用,并赋值给第一个元素
++pd;                     //指向下一个元素真像指针自动类型腿短C++11
vector<double>::iterator pd = scores.begin();
可以简化为:
auto pd = scores.begin();超过结尾 past_the_end,指向容器最后一个元素 后面的那个元素vector特有的方法
push_back() 将元素放到vector尾巴vector<double> scores;double temp;while(cin >> temp && temp >=0)scores.push_back(temp);erase() 删除制定区间的元素,接收两个迭代器参数 第一个区间起始,第二是区间个终止的后一个位置(1) iterator erase( iterator pos );(until C++11)iterator erase( const_iterator pos );(since C++11)(until C++20)constexpr iterator erase( const_iterator pos );(since C++20)(2)  iterator erase( iterator first, iterator last );(until C++11)iterator erase( const_iterator first, const_iterator last );(since C++11)(until C++20)constexpr iterator erase( const_iterator first, const_iterator last );(since C++20)Erases the specified elements from the container.1) Removes the element at pos.2) Removes the elements in the range [first, last).Invalidates iterators and references at or after the point of the erase, including the end() iterator.The iterator pos must be valid and dereferenceable. Thus the end() iterator (which is valid, but is not dereferenceable) cannot be used as a value for pos.The iterator first does not need to be dereferenceable if first==last: erasing an empty range is a no-op.Return valueIterator following the last removed element.If pos refers to the last element, then the end() iterator is returned.If last==end() prior to removal, then the updated end() iterator is returned.If [first, last) is an empty range, then last is returned.insert(),接收三个迭代器参数,依次,插入的位置,第二三个迭代器定义了被插入的区间--通常是另一个容器对象的一部分old_v.insert(old)v.begin(), new_v.begin() + 1, new_v.end() );将new 除了第一个元素之外的所有元素 插入到old的第一个元素  前面vector的其他操作    STL 定义了非成员 non-member函数来执行如搜索,排序。随机排序之类的操作,--定义一次,适用所有
e.g.
for_each(): 三个删除,前两个是定义容器中区间的迭代器,最后一个是指向函数的指针(更普遍,是一个函数对象)。将被指向的函数应用于容器区间中的各个元素,被指向的函数不能膝盖容器元素的值因此可以替代for循环---避免显式使用迭代器变量
Random_shuffle(),两个特定区间的迭代器参数,并随机排列该曲建忠的元素--要求容器允许随机访问
sort(),也要求容器支持随机访问,版本一,接收两个定义区间的迭代器参数,使用为存储在容器中的类型元素特别定义的 < 运算符,来操作区间内的元素,排序时,使用内置的 < 运算符 对值进行比较如,下面升序排序vector<int> coolstuff;sort(coolstuff.begin(), coolstuff.end() );版本二, 为对象进行排序,需要三个参数,第三个是指向要使用的函数的指针--返回值使用bool值表示顺序是否正确全排序 total ordering: a<b 和a>b都不成立,则a = b完整若排序 strict weaking ordering, 上述情况不一定,可能相同,也可能知识某方面相同(因为是对象)

16.3.4 基于范围的for循环 C++11
double prices[5] = {1.1, 1.2, 1.3, 1.4, 1.5};
for(double x : prices)
cout << x << endl;
声明了与容器相同的变量,并指出了容器的名称
因此 for_each(books.begin(), book.end(), show);
转换,for(auto x : books) show;
for_each 不可改变容器内容, 基于范围的for循环可以,诀窍是指定一个引用参数
for (auto & x : books) func–改变内容的函数

16.4 泛型编程
STL 是一种泛型编程。
面向对象编程关注的是编程的数据方面,泛型编程关注的是算法。
共同点是为了抽象和代码复用

泛型编程是为了编写独立于数据类型的代码,C++中完成通用程序的工具是 模板模板使得能够按泛型定义函数或类, STL通过叠加算法使得这个程度更进一步。
模板让一切成为可能,但必须对元素进行仔细地设计

16.4.1 为何使用迭代器
模板使得算法独立于存储的数据类型,迭代器使得算法独立于使用的容器
模板提供了存储在容器中的数据类型的通用表示,因此还需要遍历容器中的值的通用表示,迭代器就来了

按功能强弱,定义了多种级别的迭代器C++ 将operator++ 作为前缀, operator++(int)作为后缀版本--其中参数永远不会使用,所以无名称iterator & operator++(){pt = pt->p_next;return *this;}iterator operator++(int){iterator tmp = * this;pt = pt->p_next;return tmp;}STL: 基于算法需求,设计基本迭代器的特征和容器特征
首先,每个容器类定义了相应的迭代器类型,对于某个类,迭代器可能是指针,也可能是对象,迭代器提供所需的操作,如*和++。
其次,每个容器类都有一个超尾标记,当迭代器增加到超尾容器的最后一个值后,这个值将被赋给迭代器。每个容器类都有begin()he end()方法,都有++操作作为编程风格,最好避免直接使用迭代器,尽可能使用STL函数来处理细节

16.4.2迭代器类型
STL定义了5种:输入/输出迭代器,正向/双向迭代器,随机访问迭代器
都可以执行解除引操作,进行比较。如果两个迭代器相同,则对他们解除引用后得到的值相同

1.输入迭代器
可被程序用来读取容器中的信息,即来自容器的信息被视为输入。对输入迭代器解除引用将使得程序能读取容器中的值,不一定可被修改。
基于输入迭代器的算法都是 单通行 single pass,
不能保证第二次遍历时,之前的顺序不便,之前的数据可被解除一弄,因此不依赖前一次遍历的迭代器值,也不依赖本次遍历中前面的迭代器值。
输入迭代器 可以递增,不能倒退2.输出迭代器  支持 前缀和后缀
将信息从程序输出给容器, 程序的输出 就是容器的输入。解除引用,为了让程序修改容器的值,而不是读取。单通行,只读算法--输入迭代器;只写算法--输出迭代器3.正向迭代器 forward iterators 支持 前缀和后缀只使用++运算符遍历容器,与上两个类似。每次向前move forward 一个元素 不同的是,总是按照相同的顺序遍历,递增后的正向迭代器,让然可以对前面的迭代器值解除引用,并可以得到相同的值,多次通行成为可能4.双向迭代器  具有正向迭代器的所有特性,同时 支持 前缀和后缀 递减运算符5.随机访问迭代器具有双向迭代器的所有特性,同时支持随机访问的操作和对元素排序的关系运算符

16.4.3迭代器层次结构
正向包含输入输出 双向包含正向, 随机包含双向
目的,使用要求最低的迭代器,适用于容器的最大区间
16.4.4 概念、改进和模型
STL有若干个用C++语言无法表达的特性,如迭代器种类。因此,虽然可以设计具有正向迭代器特征的类,但不能让编译器将算法限制为只使用这个类。原因在于,正向迭代器是一系列要求,而不是类型。所设计的迭代器类可以满足这种要求,常规指针也能满足这种要求。STL算法可以使用任何满足其要求的迭代器实现。STL文献使用术语概念(concept)来描述一系列的要求。因此,存在输入迭代器概念、正向迭代器概念,等等。顺便说一句,如果所设计的容器类需要迭代器,可考虑 STL,它包含用于标准种类队迭代器模板。

概念可以具有类似继承的关系。例如,双向迭代器继承了正向迭代器的功能。然而,不能将C++继承机制用于迭代器。例如,可以将正向迭代器实现为一个类,而将双向迭代器实现为一个常规指针。因此,对C++而言,这种双向迭代器是一种内置类型,不能从类派生而来。然而,从概念上看,它确实能够继承。有些 STL 文献使用术语改进(refnement)来表示这种概念上的继承,因此,双向迭代器是对正向迭代概念的一种改j概念的具体实现被称为模型(model)。因此,指向 int 的常规指针是一个随机访问迭代器模型,也是个正向迭代器模型,因为它满足该概念的所有要求。说了一对,迭代器分类 是概念上的要求,实现上可以五花八门如,常规指针应用于迭代器, 则STL算法可以用于常规数组 只要提供适当的迭代器和超尾指示器,可以将STL算法用于自己设计的数组形式假设要将信息复制都显示器上,如果有一个表示输出流的迭代器,则可以使用copy()。STL为这种迭代器提供了ostteam_iterator模板。用STL的话来说,该模板是输出迭代器概念的一个模型,它也是一个适配器adapteor--一个类或者函数,可以将一些其他接口转换为STL使用的接口。可以通过包含头文件iteraotr,并使用下面的声明来创建这种迭代器
#include <iterator>
ostream_iteraotr<int, char> out_iter(cout, " ");
out_iter迭代器现在是一个接口,可以使用cout来显示信息
第一个模板参数指出被发送给输出流的数据类型。第二个模板参数指出了输出流使用的字符类型(char或者wchar_t)。构造函数的第一个参数(这里是cout)指出了要使用的输出流,也可以是用于文件输出的流,最后一个字符串参数是在发送给输出的流的每个数据项后显示的分隔符。*out_iter++ = 15; //works like  cout << 15 << " ";
这意味着将 15 和由空格组成的字符串发送到cout 管理的输出流中,并为下一个输出操作做好了准备。可以将copy()用于迭代器,如下所示:
copy(dice.begin(), dice.end(),out_iter);//copy vector to output stream
也可以直接使用迭代器
cout(dice.begin(), dice.end(), ostream_iterator<int, char>(cout , " " ) );
// <int, char>中 char可以省略, int 为要输出的内容类型
copy(istream_iterator<int, char>(cin), istream_iterator<int, char>(), dice,begin() );
第一个参数指出要读取的数据类型,第二个--输入流要使用的字符类型, 构造函数的参数cin指出由cin管理输入流。
若省略构造函数参数,如cin 用于表示因某种原因输入失败而停止输入的输入流尾巴 上述代码表示从输入流中读取,直到文件结尾、类型不匹配或出现其他问题为止。Class template std::ostream_iterator
template< class T, class CharT = char, class Traits = std::char_traits<CharT>>class ostream_iterator;               (since C++17)ostream_iterator( ostream_type& stream, const CharT* delim );    (1)
ostream_iterator( ostream_type& stream );    (2)
1) 以 stream 为关联流并以 delim 为分隔符构造迭代器。
2) 以 stream 为关联流并以空指针为分隔符构造迭代器。
参数
stream  -   此迭代器所访问的输出流
delim   -   在每次输出后插入流的空终止字符串#include <iostream>
#include <iterator>
#include <algorithm>
#include <numeric>
int main()
{std::ostream_iterator<char> oo {std::cout};std::ostream_iterator<int> i1 {std::cout, ", "};std::fill_n(i1, 5, -1);*oo++ = '\n';std::ostream_iterator<double> i2 {std::cout, "; "};*i2++ = 3.14;*i2++ = 2.71;*oo++ = '\n';std::common_iterator<std::counted_iterator<std::ostream_iterator<float>>,std::default_sentinel_t>first { std::counted_iterator{std::ostream_iterator<float>{std::cout," ~ "}, 5} },last { std::default_sentinel };std::iota(first, last, 2.2);*oo++ = '\n';
}重难点 待解决STL其他迭代器
reserve_iterator, back_insert_iterator, front_insert_iterator, insert_iterator
反转 反向打印。vector类有rbegin()的成员函数和rend()的成员函数,前者返回一个指向超尾的反向迭代器,后者返回一个指向第一个元素的反向迭代器。,因为对迭代器执行递增操作将导致它被递减,所以可以使用下面的语句来反向显示内容:copy(dice.rbegin(), dice.rend(), out_iter);//display in reverse order如果可以在显式声明迭代器和使用STL函数来处理内部问题,请使用后者copy(casts, casts + 10, dice,begin() );
上述copy方法,将内容从dice容器初始位置开始覆盖内容
back_insert_iterator, front_insert_iterator, insert_iterator 三个插入迭代器,不会覆盖已有。并使用自动内存分配来确保新信息存储
insert_iterator将元素插入到insert_iterator构造函数参数制定的位置 前面
back_insert_iterator 只允许尾部快速插入--一种时间固定的算法,vector满足 针对性强,块
front_insert_iterator 值允许起始位置时间固定插入,vector不行,queue满足这些迭代器将容器类型作为模板参数,实际容器标识作为构造函数参数,名为dice的vector<int> 容器创建一个back_insert_iterator:template< class Container > class back_insert_iterator; //模板back_insert_iterator< vector<int> > back_iter(dice);//ostream_iteraotr<int, char> out_iter(cout, " "); 类比//front_insert_iterator类似
#include <iostream>
#include <iterator>
#include <algorithm>
#include <vector>int main()
{std::vector<int> v;std::generate_n(std::back_insert_iterator<std::vector<int>>(v), // C++17: std::back_insert_iterator(v)10, [n=0]() mutable { return ++n; }             // or use std::back_inserter helper);for (int n : v)std::cout << n << ' ';std::cout << '\n';
}    声明容器类型的原因:迭代器必须使用合适的容器方法insert_iterator< vector<int> > insert_iterator(dice,dice.begin() ); //dice是个vector名

16.4.5 容器种类
是可用于创建具体容器对象的模板:
deque–双端队列,list, queue, priority_queue, stack, vector, map, multimap, set, multiset,bitset
C++新增:
forward_list, unordered_map, unordered_multimap, unordered_set, unordered_multiset,
且bitset被开除容器类,视为独立的类别

容器是存储其他对象的对象,被存储的对象必须是同一种类别,可以OOP对象,也可以是内置类型值
而且,类型必须是可复制构造的和可赋值的--C++改进为:可复制插入的copyinsertbale和可移动插入的moveinsertableP696复制构造, 复制赋值 移动构造,移动赋值:赋值操作保留源对象,移动操作可修改源对象,还可能转让所有权,而不做任何复制。源对象是临时的,移动操作效率高序列--sequence7种序列容器
vector数组的一种表示,自动内存管理,随机访问,反转容器--resersible Containerfor_each(dice.begin(). dice.end(). show); //display in roder for_each(dice.rbegin(), dice.rend(). show);//dispaly in reserved order //rbegin..返回的迭代器都是类级类型 reserve_iterator,这样的迭代器递增,将导致反向遍历可反转容器 除非其他类型更好,不然默认这个好用 deque --<deque>中 双端队列 double_ended queue  支持随机访问
list void splice(iterator pos, list<T, Alloc> x)insert将原始区间的副本插入到目标地址,splice 将原始区间移动到目标地址,之后x为空,全部并到pos指向的位置,之后指向one的迭代器仍然有效,当splice()将它重新定位到调用splice的对象后,迭代器仍然指向相同的元素unique()只能将相邻的相同值压缩为单值,非成员sort不能用于listforward_list 单链表 只需要正向迭代器
queue--<queue>是一个适配类,让底层类(默的deque)展示典型的队列接口,不允许随机访问,不允许遍历The std::queue class is a container adaptor that gives the programmer the functionality of a queue - specifically, a FIFO (first-in, first-out) data structure.
priority_queue--<queue>支持的操作同queue。最大元素被移动到队首。内部区别在于,默认的底层类时vector,可以修改用于确定哪个元素放到队首的比较方式,方法是提供一个可选的构造函数参数priority_queue<int> pg1; //默认版本 priority_queue<int> pg2(gerater<int>);//使用geater<int> 排序
stack--<stack>与queue类似。也是一个适配器类,底层类默认vector, 提供了典型的栈接口
array C++11  并非STL容器,长度是固定的,没有调整大小的操作

16.4.4 关联容器 – associative container
将值与键关联。使用键来查找值,如值可以是一对信息的结构,但其中一个作为标识–键
快速访问元素,允许插入,但是不能制定插入位置–关联容器有用于确定放置位置的算法。
数据结构通常是树
4种关联容器:Set, multiset, map, multimap,前两个在 后一个在中

最简单的是set 值类型与键相同,键唯一,集合中不会有多个相同的键。值就是键
multiset类似set,可能多个值对应一个键。如值和键的类型为int, multiset对象包含的内容可以是1、2、2、3、5、7、7
map中,值和键类型不同,键唯一,一个键对应一个值,multimap与map类似,一个键对应多个值set  集合
是关联集合,可翻转,可排序,键唯一。与vector和list类似
set<string> A;// a set of string objects
第二个模板参数可选,用于指示用来对键进行排序的对比函数或对象,默认情况下使用less<>
set<string, less<string> > A; //老式隐式
set<string> A(s1, s1+N); //使用array初始化A, s1-字符串数组
set也有一个将迭代器区间作为参数的构造函数,可以将集合初始化为数组内容的简单方法,区间最后一个元素是超尾,键是唯一的,即使数组中出现两次,集合也只会出现一次,且集合将会被排序
数学方法--通用方法,包括set在内的对象都可以用,所有set对象都自动满足使用这些算法的先决条件,即容器是经过排序的set_union(A.begin(), A.end(), B.begin(), B.end(), ostream_iteraotr<string, char> out(cout, " "); A 和B的并集
前两个迭代器定义了第一个集合的区间,后两个定义了第二个集合区间,最后一个迭代器是输出迭代器,指出结果集合复制到什么位置)
如果输出到C,则最后一个参数是指向C的迭代器--C.begin()不可以,因为关联集合将键看做常量,所以,C.begin()返回的迭代器是常量迭代器,不能作为输出迭代器,另外,与copy()相似,set_union()将覆盖容器中已有的数据,并要求容器有足够的空间存放新的信息,C是空的,不能满足需求可以创建匿名insert_iterator,将信息赋值给C,其构造函数将容器名称和迭代器作为参数
set_union(A.begin(), A.end(), B.begin(), B.end(), insert_iteraotr<string>(C, C.begin());
set_intersectioin() 交集 set_difference()集合的差。接口与set_union()相同。 lower_bound,upper_bound, 分别返回集合第一个不小于键参数的成员的迭代器,第一个大于键参数的成员的迭代器
排序算法决定插入位置,所以,这种类包含只指定要插入的信息,不指定位置的插入方法。
如,string s("aa");A.insert(s); //插入一个值B。insert(A.begin(), A.end() );//插入一个范围P704

multimap
可反转,经过排序的关联容器,键和值类型不同,一个键可以于多个值关联
multimap<int, string> codes; // 键类型:int,存储的值类型:string
第三个模板参数可选,指出用于对键进行排序的比较函数或对象。默认情况下为模板less<>,将键类型作为参数
为将信息组合在一起,实际的值类型将与键类型和数据类型结合成一对–类似于VBA中字典的关键词
STL 使用模板类pair<class T, class L> 将两种值存储在一个对象中。
pair<const keytype, datatype>
e.g.
pair<const int , string> item(213, “Los Angeles”);
codes.insert(item);

codes.insert(pair<const int , string> (213, “Los Angeles”));
数据按键顺序排序,不许指定插入位置
对于pair对象,可以使用first 和second成员来访问
pair<const int , string> item(213, “Los Angeles”);
cout << item.first << " " << item.second << endl;

count(), 接收键作为参数,并返回具有该键的元素个数
lower_bound,upper_bound将键作为参数,工作原理与set相同
equal_rnge将键作为参数,返回两个迭代器,表示区间与该键匹配。返回值封装在pair对象中,这里的pair的两个参数都是迭代器
e.g.
pair<multimap<ketype, string>::iterator, multimap<kekytype, string>::iterator> range= codes.equal_range(718);//打印codes对象中区号718的所有城市
std::multimap<keytype, std::string>::iterator it;
for( it = range.first; it != range.second; ++it)ocut << (*it).second << endl;
或
auto range = codes.equal_range(718);
for( it = range.first; it != range.second; ++it)ocut << (*it).second << endl;关联容器--自动有序

16.4.5 无序关联容器 C++11
与关联容器一样,无序关联容器也将值与键关联起来,并用键查找值。底层的差别:关联容器基于树结构,无序关联容器基于哈希表–旨在提高添加和删除元素的速度以及提高查找的效率

有四种:unordered_set, unordered_multiset, unordered_map, unordered_multimap

16.5 函数对象
很多STL算法都使用函数对象–函数符functor,函数符是可以以函数方式与 () 结合使用的任意对象
包括 函数名, 指向函数的指针 重载了()运算符的类对象–定义了函数operator() ()的类

for_each  代码将具有 一个使用f()的表达式 f是指向函数的指针,f()调用该函数 或者f()将是调用其重载的()运算符的对象

16.5.1 函数符概念
生成器generator, 不用参数就可以调用的函数符
一元函数 unary function 用一个参数可以调用的函数符
二元函数 binary function 两个参数可以调用的函数符
改进版:
返回bool值的一元函数是谓词:predicate
返回bool值的二元函数是二元谓词 binary predicate
e.g.
bool a(…);
sort(b.begin(), b.end(), a);
//a --函数名 作为参数

    template<class T>class Toobig{private:T cutoff;public:TooBig(const T & t); cutoff(t) {}bool operator() (const T & v) { return v > cutoff;}}TooBig<int> (200)V--作为函数参数传递, cutoff 由类构造函数设置P709        int vls[10] = {...};list<int> a(vls, vls + 10);list<int> b = {...};liset<int> c {...};
接收两个参数的模板函数
template<class T> bool tooBig(const T & val, ocnst T& lim)
{ return val> lim; }
可以转变为接收一个
template<class T>
class TooBig2
{
private:T cutoff;
public:TooBig2(const T & t) : cutoff(t) {}bool operator()(const T & v) { return tooBig<T>(v, cutoff); }
};TooBig2<int> tB10(100);
int x;
if(tB100(x))
...
//same as if( tooBig(x, 100))

16.5.2 预定义的函数符
#include
plus add;
double y = add(2.2, 3.4); // using plus::operator()()
transform(rg8.begin(), gr8.end(), m8,begin(), out, plus() );
P711 表16.12
16.5.3 自适应函数符和函数适配器----C++17呢? 重难点
自适应生成器 adaptable generator
自适应一元函数 adaptable unary function
自适应二元函数 adaptable binary function
自适应谓词 adaptable predicate
自适应二元谓词 adaptable binary predicate

使函数符成为自适应是因为它携带了标识参数类型和返回类型的typedef成员。分别是result_typefirst_argument_type, second_argument_type.
e.g. plus<int> 返回类型被标识为plus<int>::result_type, 这是int的typedef作用在于,函数适配器对象可以使用函数对象,并认为存在这些typedef成员
接受一个自适应函数符参数的函数可以使用result type 成员来声明一个与函数的返回类型匹配的变量。

16.6 算法
STL包含了很多处理容器的非成员函数,

总体设计相同,都是用迭代器标识要处理的数据区间和结果的放置位置,有些函数还接收一个函数对象参数,
对于算法函数设计,有两个主要的通用部分。首先,它们都使用模板来提供泛型;其次,它们都使用迭代器来提供访问容器中数据的通用表示

16.6.1 算法组
STL将算法库分成4组:

非修改式序列操作–对区间中的每个元素操作,不修改容器内容
修改式序列操作–修改内容,也可以修改值的排列顺序
排序和相关操作–排序函数和其他各种函数

通用数字运算–包括区间的内容累计、计算两种容器的内部乘积。计算小计、计算相邻对象差的函数
通常都是数组的操作特性,vector最合适
16.6.2 算法的通用特征
结果防止在原始数据位置–sort()就地算法–in place algorithm
异地放置–复制算法–copying algorithm–相对于原地输出,需要给出结果防止的位置
有两个版本的 复刻版本的名称以_copy结尾,返回迭代器,并指向复制的最后一个值的后面一个位置
根据函数应用于容器元素得到的结果来执行操作–以_if结尾。如果将函数用于旧值,返回值为true,则
replace_if()将旧值替换为新的值。谓词是返回bool值的一元函数

重难点,谓词或迭代器参数输入类型不对,编译器不会检查,知识会在编译时候报大量错误template< class ForwardIt, class T >
constexpr void replace( ForwardIt first, ForwardIt last,const T& old_value, const T& new_value ); (since C++20)template< class ForwardIt, class UnaryPredicate, class T >
constexpr void replace_if( ForwardIt first, ForwardIt last,UnaryPredicate p, const T& new_value ); (since C++20)Replaces all elements satisfying specific criteria with new_value in the range [first, last).
1) Replaces all elements that are equal to old_value.
3) Replaces all elements for which predicate p returns true.Parameters
first, last -   the range of elements to process
old_value   -   the value of elements to replace
policy  -   the execution policy to use. See execution policy for details.
p   -   unary predicate which returns true if the element value should be replaced.
The expression p(v) must be convertible to bool for every argument v of type (possibly const) VT, where VT is the value type of ForwardIt, regardless of value category, and must not modify v. Thus, a parameter type of VT&is not allowed, nor is VT unless for VT a move is equivalent to a copy (since C++11).
new_value   -   the value to use as replacementReturn value
(none)template< class InputIt, class OutputIt, class T >
constexpr OutputIt replace_copy( InputIt first, InputIt last, OutputIt d_first,const T& old_value, const T& new_value ); (since C++20)template< class InputIt, class OutputIt, class UnaryPredicate, class T >
constexpr OutputIt replace_copy_if( InputIt first, InputIt last, OutputIt d_first,UnaryPredicate p, const T& new_value ); (since C++20)Copies the elements from the range [first, last) to another range beginning at d_first replacing all elements satisfying specific criteria with new_value. The source and destination ranges cannot overlap.
1) Replaces all elements that are equal to old_value.
3) Replaces all elements for which predicate p returns true.Parameters
first, last -   the range of elements to copy
d_first -   the beginning of the destination range
old_value   -   the value of elements to replace
policy  -   the execution policy to use. See execution policy for details.
p   -   unary predicate which returns true if the element value should be replaced.
The expression p(v) must be convertible to bool for every argument v of type (possibly const) VT, where VT is the value type of InputIt, regardless of value category, and must not modify v. Thus, a parameter type of VT&is not allowed, nor is VT unless for VT a move is equivalent to a copy (since C++11).
new_value   -   the value to use as replacementReturn value
Iterator to the element past the last element copied.e.g.
template<class InputIt, class OutputIt, class UnaryPredicate, class T>
OutputIt replace_copy_if(InputIt first, InputIt last, OutputIt d_first,UnaryPredicate p, const T& new_value)
{for (; first != last; ++first) {*d_first++ = p( *first ) ? new_value : *first;}return d_first;
}

16.7 其他库–需要时候

第十七章 输入、输出和文件
flushing the buffer 刷新缓冲区

streambuf类为缓冲区提供了内存,并提供了用于填充缓冲区、访问缓冲区内容、刷新缓冲区和管、理缓冲区内存的类方法
ios_base类表示流的一般特征,如是否可读取、是二进制流还是文本流等
ios类基于ios_base,其中包括了一个指向streambuf对象的指针成员
ostream 类是从ios类派生而来的,提供了输出方法
istream类也是从ios类派生而来的,提供了输入方法
iostream类是基于istream 和ostream 类的,因此继承了输入方法和输出方重新定义I/O
为成为国际语言,C++必须能够处理16位的国际字符集或更宽的字符类型,在传统的8位char(“窄”)类型的基础上添加了wchar_t(“宽”)字符类型, C++11 添加了char16_t和char32_t。
并开发了一套I/O类模板  包括basic_istream<charT, traits<charT>> 和basic_ostream<charT, traits<charT>>.traits<charT>>模板是一个模板类,为字符类型定义了具体特征,如如何比较字符是否相等以及字符的EOF值等。C++提供了具体化----istream/ostream 都是char具体化的typedef, wistream/wostream都是wchar_t具体化,如wocut对象用于输出宽字节流c++的iosteam将自动创建8个流对象,4个用于窄字符流,4个用于宽字符流 cin,cout,cerr / wcin, wcout, wcerr  err--无缓冲clog/wclog也对应标准错误流--有缓冲cout凭借streambuf对象的帮助,管理流中的字节流

17.1.3 重定向
输入重定向 > 和输出重定向<
17.2 使用cout进行输出
17.2.1 重载的<<运算符
C++与C一样,<<运算符的默认含义是按位左移运算符。ostream重新定义了<<运算符,重载为输出,叫做插入运算符,插入运算符被重载,使之能够识别C++中所有基本类型

1.输出和指针
C++用指向字符串存储位置的指针来表示字符串,指针形式可以是char数组名,显示的char指针或用引号括起的字符串
使用字符串中的终止空字符确定结尾
ostream为以下指针类型定义了插入运算符const signed char*const unsigned char*const char*void*
对于非字符串,C++将其对应于void*,要获得字符串地址,必须强制转换为其他类型char * s = "aa";cout << &aa;cout << (void*) aa;//均打印字符串地址 2.拼接输出
插入运算符的返回类型是ostream &: ostream & operator<< (type);
运算符的返回值为调用运算符的对象, cout<<"", 返回cout对象
这样可以连续输出3.其他ostream方法
put()和write(),前者显示显示字符,后者显示字符串
put可以接收数值型参数, 系统将自动将其转换为对应int的对应的ascll码write显示整个字符串

basic_ostream<CharT,Traits> & write( const char_type* s, std::streamsize count );
outputs the characters from successive locations in the character array whose first element is pointed to by s. Characters are inserted into the output sequence until one of the following occurs:
1)exactly count characters are inserted
2)inserting into the output sequence fails (in which case setstate(badbit) is called

Parameters
s   -   pointer to the character string to write
count   -   number of characters to write
Return value
*thiscout调用write() ,将调用char具体化, 返回ostream &
std::cout.write(c, 4).write("!\n", 2); //c--stringwrite()并不会在遇到空字符时自动停止,只打印制定数目的字符,即使超出了字符串的边界。
wtire()也可用于数值数据,可以将数值的地址强制转换为char*,然后传递给该方法long vla = 560031841;cout.write( (char *) & val, sizeof(long));这不会将数字转换为相应的字符,而是传输内存中存储的位表示,输出设备将每个字节作为ascll进行结识,因此vla将被显示未4个字符的组合--可能会乱码

17.2.3 刷新输出缓冲区 ios_base
cout填满缓冲区,然后刷新缓冲区并将内容发出,清空缓冲区。512字节或其整数倍
换行符也可手动刷新缓冲区
C++会在输入即将发生时刷新缓冲区,输出字符串没有换行符–这将导致cout立即显示消息,不然,程序将等待输入,不显示
如果实现不能在希望的时刻刷新输出,可以使用两个控制符中的一个来强行刷新,flush刷新缓冲区,endl刷新缓冲区的同时插入一个换行符

控制符也是函数,如可以这样flush(cout) 直接刷新cout缓冲区
ostream 对<<进行了重载,使得下述表达式将被替换为函数调用flush(cout)
cout << flush;

17.2.4 用cout格式化输出
1.修改显示时使用的计数系统
ostream插入运算符将值转换为文本格式。在默认情况下,格式化值的方式如下
1.对于char值,如果它代表的是可打印字符,则将被作为一个字符显示在宽度为一个字符的字段中
2.对于数值整型,将以十进制方式显示在一个刚好容纳该数字及负号(如果有的话)的字段中
3.字符串被显示在宽度等于该字符串长度的字段中。
4.浮点数的默认行为有变化。下面详细说明了老式实现和新实现之间的区别:
新式:浮点类型被显示为6位,末尾的0不显示(显示数字位数与数字被存储时精度没有任何关系)。数字以定点表示法显示还是以科学计数法表示(参见第3章),取决于它的值。当指数大于等于6或小于等于-5时,将使用科学计数法表示。另外,字段宽度恰好容纳数字和负号(如果有的话)。默认的行为对应于带%g 说明符的标准C库函数 fprint。
老式:浮点类型显示为带6位小数,末尾的0不显示(注意,显示的数字位数与数字被存储时的精度没有任何关系)。数字以定点表示法显示还是以科学计数法表示(参见第3章),取决于它的 值。另外,字段宽度恰好容纳数字和负号(如果有的话)。
因为每个值的显示宽度都等于它的长度,因此必须显式地在值之间提供空格:否则,相邻的值被分开。

ios_base类存储了描述格式状态信息,通过其成员函数,可以控制字段宽度和小数位数。可以将方法用于他的ostream子类如将cout对象的计数系统格式设为十六进制:hex(cout)-->通常cout<<hex        之后将以十六进制打印,知道重新设置了格式状态控制符不是成员函数,不必通过对象调用2.调整字段宽度
方法原型
with成员设置:
int widht(); ---返回字段宽度的当前设置,
int width(int i);--覅段宽度设置为i个空格,并返回以前的字段宽度值
这个方法只影响将显示的下一个项目,然后字段宽度将恢复默认值
cout<<"#";
cout.width(12);
cout << 12;
结果: #          12; //默认右对齐C++永远不会截断数,字段不够,自动增加用来填充的空格被承能、称为 填充字符 3.填充字符
默认情况下,cout用空格填充字段中未被使用的部分,下述方法可以改变填充字符,这个时一直有效,知道改变它cout.fill("*");4.设置浮点数精度
默认指的是总位数,在定点模式和科学模式下,指的是小数点后面的位置,C++默认精度 6位,末尾0不显示, precison()成员函数可以定义其他精度值cout.precison(2); 精度为2 ,一直有效知道重置5.打印末尾的0和小数点
ios_base提供了setf()函数,如cout.setf(ios_base::showpoint);  显示末尾小数点默认的浮点格式,还可以将末尾0显示出来,即默认情况下 cout将把2.0 显示为2.000 0006.再谈setf()
setf方法控制小数点被显示时的其他几种格式选项,ios_base类有一个受保护的数据成员,其中的各位(标记)分别控制着格式化的各个方面。打开也给标记称为设置标记(或位),并意味着相应的位被设置为1
Defined in header <ios>
class ios_base;
The class ios_base is a multipurpose class that serves as the base class for all I/O stream classes. It maintains several kinds of data:1) state information: stream status flags.
2) control information: flags that control formatting of both input and output sequences and the imbued locale.
3) private storage: indexed extensible data structure that allows both long and void* members, which may be implemented as two arbitrary-length arrays or a single array of two-element structs or another container.
4) callbacks: arbitrary number of user-defined functions to be called from imbue(), std::basic_ios::copyfmt(), and ~ios_base().
Typical implementation holds member constants corresponding to all values of fmtflags, iostate, openmode, and seekdir shown below, member variables to maintain current precision, width, and formatting flags, the exception mask, the buffer error state, a resizeable container holding the callbacks, the currently imbued locale, the private storage, and a static integer variable for xalloc().P747使用这些常量 需要作用域解析符 ios_base::xxx C++对十六进制和八进制视为无符号的fmtflags setf( fmtflags flags );(1)    fmtflags setf( fmtflags flags, fmtflags mask );  (2)//第二个参数指出要清除第一个参数中的哪些位,用第一个参数指出要设置哪位
Sets the formatting flags to specified settings.
1) Sets the formatting flags identified by flags. Effectively the following operation is performed fl = fl | flags where fl defines the state of internal formatting flags.
2) Clears the formatting flags under mask, and sets the cleared flags to those specified by flags. Effectively the following operation is performed fl = (fl & ~mask) | (flags & mask) where fl defines the state of internal formatting flags.Parameters
flags, mask -   new formatting setting. mask defines which flags can be altered, flags defines which flags of those to be altered should be set (others will be cleared). Both parameters can be a combination of the following constants:
Constant       Explanation
dec use decimal base for integer I/O: see std::dec
oct use octal base for integer I/O: see std::oct
hex use hexadecimal base for integer I/O: see std::hex
basefield   dec|oct|hex. Useful for masking operations
left    left adjustment (adds fill characters to the right): see std::left
right   right adjustment (adds fill characters to the left): see std::right
internal    internal adjustment (adds fill characters to the internal designated point): see std::internal
adjustfield left|right|internal. Useful for masking operations
scientific  generate floating point types using scientific notation, or hex notation if combined with fixed: see std::scientific
fixed   generate floating point types using fixed notation, or hex notation if combined with scientific: see std::fixed
floatfield  scientific|fixed. Useful for masking operations
boolalpha   insert and extract bool type in alphanumeric format: see std::boolalpha
showbase    generate a prefix indicating the numeric base for integer output, require the currency indicator in monetary I/O: see std::showbase
showpoint   generate a decimal-point character unconditionally for floating-point number output: see std::showpoint
showpos generate a + character for non-negative numeric output: see std::showpos
skipws  skip leading whitespace before certain input operations: see std::skipws
unitbuf flush the output after each output operation: see std::unitbuf
uppercase   replace certain lowercase letters with their uppercase
equivalents in certain output operations: see std::uppercaseReturn value
the formatting flags before the call to the function定点表示法意味着使用格式123.4来表示浮点值,而不管数字的长度如何,
科学表示法则意味着使用格式1.23e04,而不考虑数字的长度。
如果您熟悉c语言中 printf()的说明符,则可能知,
默认的c++模式对应于%g说明符,定点表示法对应于%f说明符,而科学表示法对应于%e说明符。在C++标准中,定点表示法和科学表示法都有下面两个特征:精度指的是小数位数,而不是总位数;   显示末尾的0。cout左对齐ios_base::fmflags old = cout.seft(ios::left, ios::adjustfield);
清楚设置cout.seft(old, ios::adjustfield);p750程序 重难点void unsetf( fmtflags flags );Unsets the formatting flags identified by flags.flags为1 将对应恢复没有专门指示浮点数默认显示模式的标记。系统的工作原理如下仅当只有定点位被设置时使用定点表示法;仅当只有科学位被设置时使用科学表示法对于其他组合,如没有位被设置或两位都被设置时,将使用默认模式。因此,启用默认模式的方法之一如下:cout.setf(0,ios_base::floatfield);//go to default mode第二个参数关闭这两位,而第一个参数不设置任何位。简捷方式是,使用ios:floatfield 来调用函数 unsetfcout.unsetf (ios_base::floatfield);//go to default mode如果已知cout 处于定点状态,则可以使用参数 ios_base:fxed 调用函数unsetf()来切换到默认模式然而,无论cout的当前状态如何,使用参数 ios_basc:foatfield 调用函数unsetf都将切换到默认模式,因此这是一种更好的选择。7.标准控制符
使用setf()不是进行格式化的、对用户最为友好的方法,C++提供了多个控制符,能够调用setf,并自动提供正确的参数。前面已经介绍过 dec、hex 和 oct,这些控制符工作方式都与hex 相似。例如,下面的语句打开左对齐和定点选项:cout <<left << fixed;P751 表17.3  能用就用 8.头文件iomanipiostream 设置格式值不太方便,iomanip 方便一点。最常用的setprecision(),setfill(), setw()分别设置精度、填充字符和字段宽度setprecison()接受一个指定精度的整型参数,out << setprecision(n) or in >> setprecision(n)setfill()接受一个指定填充字符的char参数out << setfill(c)setw()接受一个指定字段宽度的整数参数used in an expression out << setw(n) or in >> setw(n)

cout<< fixed << right; //显示结尾的0

17.3 使用cin进行输入
cin对象将标准输入表示为字节流,之后根据变量类型进行类型转换
通过重载 抽取运算符>> 适配基本类型,指针类型
istream & operator>>(int &);
参数和返回值都是引用,处理参数本身而不是副本 返回调用对象(此处cin)的引用

可以使用hex, oct, dec控制符于cin一起使用,来制定将证书输入解释为对应格式,如 cin >> hex针对字符指针,抽取运算符将读取输入中的下一个单词,放置在制定的地址,并加上一个空值字符,使之成为一个字符串

17.3.1 cin>>如何检查输入
跳过空白(空格,换行符,制表符),直到非空白符,对于单字符模式(参数类型为char,unsigned char,signed char)也是如此。
单字符模式,>>读取该字符放到指定位置,
其他模式,读取指定类型的数据,从空字符开始直到第一个类型不匹配为止的全部内容
不符合的字符将被留在字符中,等待下次读
输入可能完全不满足需求,则返回0–false 可以用来判断输入是否汉族需求

17.3.2 流状态
cin/cout对象包含一个描述流状态(stream state)的数据成员(从ios_base继承),被定义为iostate类型–iostate是一种bitmask类型。由eofbit,badbit或bailbit 组成
每个元素都是以为,可以为1-设置或0–清除。
cin到达文件末尾,设置eofbit; 未能读到预期字符时 或 I/O失败,设置failbit;在一些无法诊断的失败破坏流,设置badbit。 三个都是0一切顺利
goodbit 另一种全0表示法

1.设置状态
clear()把状态设置成它的参数,没有传入参数,则默认为0,这将清除全部三个状态位
clear(eofbit)将设置eofbit为1,其余全部清除
setstate()方法只影响其参数中已经设置的部分,setstate(eofbit)只设置eofbit,其余不变clear--重新打开输入 setstate主要是为了提供一个修改状态的途径2.I/O 和异常
输入函数设置了eofbit,默认情况下不会导致异常,但可以使用exceptions()方法来控制异常如何被处理

exception()方法返回一个位字段,三位,分别对应于eofbit,failbit,badbit。修改设计clear()和setstate()。修改流状态之后,clear()方法将当前流状态于exceptions()返回值进行比较,如果返回值中某一位被设置,则当前状态中对应位也被设置,则clear()将引发ios_base::failure异常。如过两个值都设置了badbit,将导致ios_base::failure.如果exceptions()返回goodbit,则不会引发任何异常。ios_base::failure异常类是从std::exceptions类派生而来,因此包含一个what()方法。

重载的exceptions(iostate)可以用try-catch块来控制异常发生时的行为, cin.exceptions(badbit),将导致异常被传递,期待try-catch块处理
位预算OR,可以指定多为, cin.exceptions(badbit | eofbit);将导致异常
这就是如何在接受输入时使用异常3.流状态的影响
设置流状态位将导致流对后面的输入输出关闭,直到位被清除
while( cin >> input)
{sum+= input;
};
//脱出循环时候eofbit= 1 是可能的
if(cinleof() )cout << "..."cin >> input; //无法输入,此之前加上cin.clear() 重置流状态
此时,导致程序终止的不匹配循环输入仍留在输入队列中,可以一直读取字符,直到遇到空白,isspace()函数是一个cctype函数,它在参数是空白字符时返回true,
另外一种方法是,丢弃行中剩余的部分, continue如果循环遇到文件尾巴或硬件故障,则处理错误输入的代码将毫无意义,可以使用fail()来检测假设是否成立。fail()在failbit或eofbit被设置时返回true

17.3.3 其他istream类方法
get(char&)和get(void)提供不跳过空白的单字符输入
get(char *, int, char)和getline(char *, int, char)在默认情况下读取整行而不是一个单词
这两被称为非格式化输入函数,只读取字符输入,不会跳过空白,也不会数据转换

1.单字符输入
istream
使用char参数或没有参数时,get()方法读取下一个输入字符,即使是空格,制表符或换行符,
getchar(char & ch)将输入字符赋给参数,
get(void)将输入字符转换为整型(通常为int),并将其返回
(1)get(char &)
cin会跳过空格返回一个指向用于调用它的istream对象的应用,因此可以拼接后续抽取
cin.get(c1.get(c2); >> c3
到达文件结尾,无论真结尾还是模拟的,都不会给其参数赋值,同时调用setstate(failbit), 导致cin.get(ch)的返回值为false(2)get(void)
读取空白,但使用返回值来讲输入传递给程序,所以ch = cin.get()
返回值类型不是对象,不能对它应用成员运算符,因此cin.get().get() 将报错
到达结尾,cin.get(void)将返回EOF,可以用来判断
while( (ch = cin.get()) !=EOF)  // EOF可能无法使用char类型 EOF一般为 -1--头文件定义的2.采用哪种单字符输入形式
希望跳过空白, >>
get() 会处理非空白字符,如\n,希望程序检查每个字符, get()get(char &)更好,get(void)于C中的getchar()类似,意味着可以通过包含iostream而不是stdio.h,并用cin.get()替换所有getchar(),用cout.put(ch)替换putchar(ch),将C转换成C++3.字符串输入,getline(),get() ignore()getline()成员函数和get()的字符串读取版本 都去读字符串,函数特征标相同
istream & get(char *, int, char)istream & get(char *, int)
第一个参数用于防止输入字符串的内存单元地址,第二个参数比要读取的最大字符数大1,用于防止空字符。第三个参数指定用作分解符的字符,只有两个参数的版本将换行符用作分解符。上述函数都在读取到最大数目512字节?或遇到换行符为止。
get()和getline()区别,get()将换行符留在输入流中,这样接下来的输入操作首先看到的是换行符,而getline()抽取并丢弃掉输入流中的换行符。
第三个参数指定了分界符,遇到它,输入立即停止
ignore()接受两个参数,一个数字,指定要读取的最大字符数,另一个是字符,用它做输入分界符,如下面的函数调用读取并丢弃掉接下来的255个字符或指导到达第一个换行符
cin.ignore(255, '\n');
原型的两个参数默认值分别为1 和EOF,返回类型为istrem &;
istream & ignore(int = 1, int = EOF);
默认参数值EOF将导致ignore()读取到指定书目的字符或读取到文件结尾,之后全部丢弃
该函数也可以拼接输入。没读取一次丢弃掉当前行多余未读,直接下一行4.意外字符输入
get(char*, int) 和getline()遇到文件结尾将设置eofbit,遇到流被破坏,将设置badbit,另外两种特殊情况是无输入,以及输入到达或超过函数调用指定的最大字符数
上述两种方法,如果不能抽取字符,空字符将把空字符放置到输入字符串中,并使用setstat()设置failbit。不能抽取的原因可能是输入立刻到达了文件尾巴, get(char *, int)还可能时输入了一个空行
空行不会导致getline()设置failbit,这是因为它仍将抽取换行符,虽然又扔了。如果希望getline()遇到空行立即停止,这样:
char temp[8];
while( cin.getline( temp, 8) && temp[0] != '\0')
效果等同于
while(cin.get( temp, 8))
空行终止大于指定数目,则设置failbit,包含30个或更多字符的输入行将被终止
get(char *,int)先测试字符数,然后测试是否为文件尾巴以及下一个字符是否为换行符。如果读取到了最大数目,则不会设置failbit标记。因此可以知道是不是读取了过去字符。可以用peek()查看下一个字符,如果是换行符,则说明get()读取了整行,如果不是,则说明到达一行的结尾停止的,这种办法getline()不能用,因为它丢弃掉了换行符

17.3.4 其他istream方法
read()读取指定数目的字节,并存储在指定位置,
char gross[144];
cin.read( gross, 144);
它不会在输入后加上空值字符,因此不能将输入转换为字符串,常常与ostream_write()结合使用,来完成文件输入和输出,返回istream &, 因此可以拼接

peek()返回输入中的下一个字符,但不抽取输入流中的字符,可以用来查看是否到一行结尾gcount()返回最后一个非格式化抽取方法读取的字符数,这意味着由get(),getline(),ignore(),read()方法读取的,不是由抽取运算符>>去读的,抽取运算符对输入进行格式化,使之与特定的数据类型匹配,如cin.get(myarray,80) 想知道读取了多少字符, strlen可以, 比cin.grount()计算读取了输入流中多少个字符快pushback()函数将一个字符插入到字符串中,被插入的字符将是下一条输入语句读取的第一个字符,接收一个char参数--要插队的字符,返回istream &,可以拼接。使用peek()的效果相当于先用get()读取一个字符,然后使用pushback将字符放回到输入流中,然而,pushback()允许将字符放到不是刚才要读取的位置

17.4 文件输入和输出m
重定向来自操作系统
写入文件,要创建ofstream对象,使用ostream方法,如<<或write()。
读取文件,要创建ifstream对象,使用istream方法,如>>或get().
定义了fstream类用于同步文件I/O,都从中的类派生出来的

17.4.1 简单的文件I/O
写入文件步骤:
1.创建一个ifstream对象来管理输出流
2.将该对象与特定的文件关联起来
3.以使用cout的方式使用该对象,输出的被写进文件

包含fstream头文件,多数情况,该头文件自动包含iostream文件。ofstream fout; //创建一个ofstream对象,任意有效的C++名称,fout可以,dida也行
fout.open("name.txt"); //将fout对象与具体文件连接
或:
ofstream.fout("jar,txt");
由于ostream是ofstream的基类,因此可以使用所有ostream方法
ofstream使用被缓冲的输出因此程序创建fout这样的ofstream对象时,将为输出缓冲区分配空间
。每个ofstream对象都有自己的空间。这种方式,在没有关联的文件时候,自己会创建一个新文件以默认模式打开文件进行输出将自动把文件的长度截断为零,这相当于删除已有的内容,填充新的内容
即覆写 读取文件步骤:
1.创建一个ifstream对象关联输入流
2.将该对象与特定的文件关联起来
3.以使用cin的方式使用该对象与写出声明对象类似
ifstream fin;
fin.open("jamjar.txt");
或
ifstream fin("jamjar.txt");char ch;
fin >> ch; //从txt中读取一个字符
char buf[12];
fin >> buf; //从文件中读取一个单词
fin.getline(buf, 80); //从文件中读取一行
string line;
getline(fin, line); //读取一个文件传送给一个string对象当输入和输出流对象过期(如程序终止)时,到文件的链接将自动关闭。close()方法可以显示关闭到文件的链接。fout.close();fin.close();
这并不会删除流,而只是断开流到文件的链接。流管理装置仍然被保留,如fin对象与它管理的输入缓冲区仍然存在string对象提取出C风格字符串 stringname.c_str

17.4.2 流状态检查和is_ioen()
C++文件流类从ios_base类继承了一个流状态成员,如果一切顺利,已到达文件尾巴,I/O操作失败等。
如果一切顺利,则流状态为零,no news is good news。其他状态都是通过特定位设置为1来记录的。
文件流类还继承了ios_base类中报告流状态的方法,可以通过检查流状态来判断最后一个流操作是否成功

试图打开不存在的文件,将设置failbit位
fin.open(aoff);
if (fin.fail())
{...};ifstream对象和istream对象一样,被放在需要bool类型的地方时,将被转换为boos值
因此,可以这么判断
if(fin)
{...}bool is_open() const; //(since C++11)
Checks if the file stream has an associated file.if(fin.is_ioen() ) 之前的方法无法检测不合适的文件模式打开文件 这个可以
{...}

17.4.3 打开多个文件
按需分配流

一个流也可以依次关联多个文件
fin.close() 断开连接
fin.clear() 重置fin状态

17.4.4 命令行处理技术–黑框框中使用
C++ 中有一种让在命令行环境中运行的程序能够访问命令行参数的机制
int main(int argc, char *argv[]);
argc–命令行中参数个数,argv[]–指针数组,其中的指针指向命令行参数,
argv[0]是一个指针,指向存储第一个命令行参数的字符串的第一个字符,以此类推

wc report1 report2 report3  //wc 程序名 后续文件名
四个参数,argv[0]为wc程序名

17.4.5 文件模式–描述文件如何被使用
P774
ios_base::in 打开文件,以便读取
out 以便写入
ate 并移动到文件尾
app 追加到文件尾
trunc 如果文件存在,则截短文件
binary 二进制

mode -   specifies stream open mode. It is bitmask type, the following constants are defined:Constant        Explanationapp              seek to the end of stream before each writebinary           open in binary modein               open for readingout             open for writingtrunc           discard the contents of the stream when openingate              seek to the end of stream immediately after open

ifstream和ofstream构造函数可以接收两个参数,构造函数原型为第二个参数(文件模式参数)提供了默认值
ifstream::open默认 in,
ofstream默认out | trunc , 位运算符将两个位值合并成一个可用于设置两个位的值
fstream不提供默认值,因此必须显式提供文件模式

trunc 意味着 打开已有的文件,,以接收程序输出时将被短,以前的内容将被删除。可能会将不希望删除的内容删除,可以这样解决
ofstream fout9(“sf”,ios_base:cout | ios_base::app); 可以保留文件内容,并在文件尾添加重要信息
意味着启用模式out.app

C++文件打开模式与C模式 P775将数据存储在文件中,可以存储为 文本格式  或 二进制格式
文本格式:将所有内容,如数字,存储为文本,
二进制格式:存储值的金算计内部01表示。对于字符,二进制表示与文本表示一样,即字符的ASCII码的二进制表示
对于数字,二进制表示与文本表示有差别,二进制表示是计算机内部01表示,转换成文本是ascii码对应的二进制文本格式便于读取,可以方便的计算机间传送
二进制格式,对于数字精确,因为它的值是计算机硬件的表示,不会有转换误差和舍入误差,速度更快但是,不同的解读方法可能无法读取到同样的值将p1内容以文本格式保存
ofstream fout("plantes.dat", ios_base::out | ios_base::app);
fout << p1.name << " " << p1.population << p1.g << "\n";
数量多的话,麻烦
以二进制格式存储相同的信息
ofstream fout("plantes.dat", ios_base::out | ios_base::app | ios_base::binary);
fout.write( (char *) &p1, sizeof p1);
这样使用计算机内部数据表示,将数据保存为一个整体,不能将该文件作为文本读取,但与文本对比,信息的保存更紧凑精确二进制文件和文本文件
使用二进制文件模式,程序将数据从内存传输给文件(反之亦然)时,将不会发生任何隐藏的转换
而默认的文本模式并非如此,要使用二进制格式存储,可以使用write函数,将内存中制定数目的字节复制到文件中,可以逐字节复制,也可成段复制文本,而不进行任何转换。唯一不方便的是,必须将地址强制转换为char的指针
读取二进制文件
ifstream fout("plantes.dat", ios_base::out | ios_base::app | ios_base::binary);
fin.read((char *) &p1, sizeof p1);有些系统只有一种文件类型,因此可以将二进制操作(如read write)用于标准文件格式。如果,实现认为ios_base::binary为非法常量,删除就行
如果不支持fixed和right控制符,则可以使用cout.setf(ios_base::fixed.ios_base::floatfield)和 fout.setf(ios_base::right、ios_base::adjustfield),另外,也可能必须使用ios替换ios_base。使用string对象而不是字符数组来表示planet结构的name成员不可行,因为string本省实际上并没有包含字符串,二十包含了一个指向其中存储了字符串的内存单元的指针,将结构复制到文件中时,复制的是字符串的存储地址

17.4.6随机存取
文件中移动的方式,fstream类为此继承了两种方法,seekg()和seekp().前者将输入指针移动到指定的文件位置,后者将输出指针移动到指定位置,因为缓冲区的缘故,实际上指针指向的是缓冲区中的位置,而不是实际的文件。

std::basic_istream<CharT,Traits>::seekg
basic_istream& seekg( pos_type pos );
basic_istream& seekg( off_type off, std::ios_base::seekdir dir);Sets input position indicator of the current associated streambuf object. In case of failure, calls setstate(std::ios_base::failbit).
Before doing anything else, seekg clears eofbit.    (since C++11)Parameters
pos -   absolute position to set the input position indicator to. start from the beginning
off -   relative position (positive or negative) to set the input position indicator to.单位:字节
dir -   defines base position to apply the relative offset to. It can be one of the following constants:
Constant    Explanation
beg         the beginning of a stream
end         the ending of a stream
cur         the current position of stream position indicatore.g.fin.seekg(30, ios_base::beg);// 30 bytes beyond the beginningfin.seekg(-1, ios_base::cur);//back up one byte fin.seekg(0, ios_base::endl);//go to the end of the fileReturn value
*thisExceptions
failure if an error occurred (the error state flag is not goodbit) and exceptions() is set to throw for that state.
If an internal operation throws an exception, it is caught and badbit is set. If exceptions() is set for badbit, the exception is rethrown.Defined in header <istream>
template<class CharT,class Traits = std::char_traits<CharT>
> class basic_istream : virtual public std::basic_ios<CharT, Traits>ios_base   <----- base_ios<charT, Traits>  <-----  base_istream<charT, Traits>Two specializations for common character types are defined:
Defined in header <istream>
Type        Definition
istream     basic_istream<char>
wistream    basic_istream<wchar_t>应用:istream & seekg(streamoff,ios_base::seekdir);basic_istream& seekg( pos_type pos );
pos 可以是类,此时类必须包含一个接受off_type参数的构造函数和一个接受整数参数的构造函数,以使将两种类型转换为pos_type值如果要检查指针的当前位置,对于输入流, tellg(),对于输出流tellp(),以字节为单位,从文件头开始计算
std::basic_istream<CharT,Traits>::tellg
pos_type tellg();
Returns input position indicator of the current associated streambuf object.
Return value
The current position of the get pointer on success, pos_type(-1) on failurepos_type tellp();
Returns the output position indicator of the current associated streambuf object.
Return value
current output position indicator on success, pos_type(-1) if a failure occurs.if(finout.eof())finout.clear(), //clear eof flag
else
{cerr << "Error";exit(EXIT_FAILURE);
}

上述代码重置eofbit,这样可以再次访问文件

系统自己生成临时文件名–cstdio中tmpnam()
char* tmpnam(char * pszName);
创建一个临时文件名,并放在pszName指向的C风格字符串中,常量L_tmpnam和TMP_MAX限制了文件名包含的字符数以及再确保当前目录中不生成重复文件名的情况下tmpnam()可被调用的最多次数
tmpnam()可以生成TMP_NAM个不同的文件名,每个文件名字符不超过L_tmpnam个,具体是啥取决于实现

第十八章 探讨C++新标准
C++11
移动语义和右值引用
Lambda表达式
包装器模板function
可变参数模板

P796

18.8.2 统一的初始化
C++11 提供了模板类 initializer_list,可将其用作构造函数的参数,此时,初始化列表语法只能用于该构造函数,列表中的元素必须是同一种类型或可转换为同一种类型

    vector<int> a1(10) 10个元素 vector<int> a2[10]  a2 初始化为10 vector<int> a3{4,6,1}  三个元素 4,6,1 #include <initializer_list>
double sum(std::initializer_list<double> i1);int main()
{double total = sum({2.5, 3.1});...
}

double sum(std::initializer_list i1)
{
double tot = 0;
for(auto p = i1.begin(); p !=i1.end(), p++) //begin, end 指针
tot += *p;
return tot;
}
18.1.3 声明
2.decltype
将变量的类型声明为表达式指定的类型,
decltype(x) y; //让变量y的类型与x表达式 类型相同

    常用于定义模板
3.返回类型后置再函数名和参数列表后面指定返回类型double f1(double, int); // traditional syntax auto f2(double ,int) ->double;// new syntax, return type is double  搭配decltype指定模板函数返回类型
4.模板别名:using=
typedef std::vector<std::string>""iterator itType;
C++11
using itType = std::vector<std::string>::iterator
新语法可以用于模板部分具体化 typedef不能
template<typename T> using arr12 = std::array<T, 12>
因此:
std::array<double,12> a1; 可以简化为 arr12<double> a1;
5.nullptr

18.1.5 异常规范方面的修改
void f1(int) throw(bad_dog); //throw type bad_dog exception
void f2(int) throw(); //dosen’t throw an exception

指出函数不会引发异常--noexcept
void fun() noexcept;//doesn't throw an exceptioin

18.1.6 作用域内枚举
同一作用域内枚举成员不能同名
传统枚举,类型检查低级
enum old {yes, no, maybe};
新枚举, 要求显示下你的那个,以免发生名称冲突,引用特定枚举时,new1::never 这样的限定
enum class new1 {…}
enum struct new2 {…}

18.1.7 对类的修改
1.显式转换运算符
自动转换可能存在问题,C++引入关键字explicit,以禁止单参数构造函数导致的自动转换
class Flebe
{
Flebe(int); // 自动转换
explicit Fleb(double); // requires explicit use
}

Flebe a, b;
a = 5, //隐式转换,调用Flebe(int)
b = 0.5;//不允许
b = Flebe(0.5); //显式调用转换C++11拓展了explicit,使得可对转换函数做类似的处理
class Flebe
{operator int() const; // 自动转换explicit operator double() const; // requires explicit use
}Flebe a, b;
int n = a; //隐式转换
double x = b; //不允许
x = double(b); //显式调用转换2.类成员初始化
C++11中,可以在类定义中初始化成员了
class Session
{int mem1 = 10;double mem2 {123.123};short mem3;
public:Sesson() {};Session(short s) :mem3(s) {};Session(int n, double d, short s) : mem1(n), mem2(d), mem3(s) {}}
可以使用等号或大括号初始化,但不能使用圆括号版本的初始化,其结果与给前两个构造函数提供成员初始化列表,并制定mem1,mem2的值相同Session() : mem(10), mem2(123,32) {}Session(short s) : mem1(10), mem2(23,12), mem3(s) {}通过内部初始化,可避免在构造函数中编写重复的代码

18.1.8 模板和STL方面的修改
前面提到过了模板别名和适用于STL的智能指针

1.基于范围的for循环
对于内置数组以及包含方法begin() 和end()的类和STL容器,基于范围for循环简化工作
double proces[5] = {...};
for(double x : prices)std::cout << x << std::endl;
更安全的方式
double proces[5] = {...};
for(auto x : prices)std::cout << x << std::endl;
需要修改
std::vector<int> v(4);
for (auto & x: v)x = std::rand();2.新的STL容器
C++11新增STL容器forward_list、unordered_map unordered_multimap unordered_set unordered_multiset
forward_list 单向链表,只能沿一个方向遍历,剩余集中哈希表实现
C++11还新增了array,可制定元素类型和个数 std::array<int, 360> ar;3.新的STL方法
cbegin() cend() 返回一个迭代器指向容器的第一个元素和最后一个元素的后面,并将元素视为const
4.valarray升级
C++11 添加的begin()和end(),都可以接收valarray作为参数,并返回迭代器,这样基于范围的STL算法可以用于valarray第十六章
6.尖括号
std::vector<std::list<int>> vl; //ok in C++11

18.1.9 右值引用
在 C++ 或者 C 语言中,一个表达式(可以是字面量、变量、对象、函数的返回值等)根据其使用场景不同,分为左值表达式和右值表达式。确切的说 C++ 中左值和右值的概念是从 C 语言继承过来的。
值得一提的是,左值的英文简写为“lvalue”,右值的英文简写为“rvalue”。很多人认为它们分别是"left value"、“right value” 的缩写,其实不然。lvalue 是“loactor value”的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据,而 rvalue 译为 “read value”,指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。

通常情况下,判断某个表达式是左值还是右值,最常用的有以下 2 种方法。
1) 可位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值
2) 有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。左值是可以放在赋值号左边可以被赋值的值;左值必须要在内存中有实体;
右值当在赋值号右边取出值赋给其他变量的值;右值可以在内存也可以在CPU寄存器。
一个对象被用作右值时,使用的是它的内容(值),被当作左值时,使用的是它的地址。反之,字面量 5、10,它们既没有名称,也无法获取其存储地址(字面量通常存储在寄存器中,或者和代码存储在一起),因此 5、10 都是右值。 左值引用
左值引用就是我们平常使用的“引用”。引用是为对象起的别名,必须被初始化,与变量绑定到一起,且将一直绑定在一起。
我们通过 & 来获得左值引用,
type &引用名 = 左值表达式;
可以把引用绑定到一个左值上,而不能绑定到要求转换的表达式、字面常量或是返回右值的表达式。举个例子:
int i = 42;
int &r = i;    //正确,左值引用
int &r1 = i * 42;   //错误, i*42是一个右值
const int &r2 = i * 42; //正确,可以将一个const的引用绑定到一个右值上左值引用通常也不能绑定到右值,但常量左值引用是个“万能”的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。不过常量左值所引用的右值在它的“余生”中只能是只读的。相对地,非常量左值只能接受非常量左值对其进行初始化。
int &a = 2;       # 左值引用绑定到右值,编译失败 非常量引用必须左值
int b = 2;        # 非常量左值
const int &c = b; # 常量左值引用绑定到非常量左值,编译通过
const int d = 2;  # 常量左值
const int &e = c; # 常量左值引用绑定到常量左值,编译通过
const int &b =2;  # 常量左值引用绑定到右值,编程通右值值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要std::move()将左值强制转换为右值,例如:
int a;
int &&r1 = c;             # 编译失败
int &&r2 = std::move(a);  # 编译通过右值引用
右值引用是C++11中引入的新特性 , 它实现了转移语义和精确传递。
它的主要目的有两个方面:
消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
能够更简洁明确地定义泛型函数。
右值引用就是必须绑定到右值的引用,他有着与左值引用完全相反的绑定特性,我们通过 && 来获得右值引用。
右值引用的基本语法type &&引用名 = 右值表达式;
右值有一个重要的性质——只能绑定到一个将要销毁的对象上。举个例子:
int  &&rr = i;  //错误,i是一个变量,变量都是左值
int &&rr1 = i *42;  //正确,i*42是一个右值右值引用和左值引用的区别
左值可以寻址,而右值不可以。
左值可以被赋值,右值不可以被赋值,可以用来给左值赋值。
左值可变,右值不可变(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变)。C++ 新增右值引用--第八章 &&表示,右值引用可关联到右值。即可出现再赋值表达式右边,但不能对其应用地址运算符的值。
int && r1 = 13;
int && r2 = x + y; //关联刀x+y的结果,即使之后x + y 修改了 r2 也不变
double && r3 = std::sqrt(2,0);右值关联到右值引用导致该右值被存储刀特定的位置,且可以获取该位置的地址。也即是说&13不成立,但是&r1是可以的,通过将数据与特定的地址关联,可以通过右值引用访问该数据

18.2 移动语义和右值引用
数据保留在原来位置,但是将所有权移交–移动语义。这样避免了移动原始数据,而只修改了记录。
感觉就像,新指针指向了旧指针的内存,然后旧指针被删除了

要实现移动语义,旧必须让编译器知道什么时候要复制,什么时候不需要,就是为了解决复制构造函数带来的不必要的临时操作。右值引用用上了。作为函数参数,右值引用不能为cosntnoexcept
由于移动操作时“拦截窃取操作”,通常不会抛出异常。当编写一个不抛出异常的库函数时,应该通知标准库,使之不要做一些额外的操作。
在vector重新分配空间并执行原有对象从老空间到新空间的拷贝时,vector此时并不会默认调用移动构造函数而是拷贝构造函数,这是因为如果重新分配过程使用了移动构造函数,且在移动了部分而不是全部元素后抛出一个异常就会产生问题——旧空间的移动源元素已经改变了,而新空间中为构造的元素可能尚不存在,在此情况下,vector将不能满足自身保持不变的要求。另一方面,如果vector调用的是拷贝构造函数,它很容易得可以保持旧元素不变且释放新分配的(但还未成功构造的)内存并返回。vector原有的元素仍然存在。因此,如果我们希望vector在重新分配空间时执行的是移动操作而不是拷贝,我们通过将移动构造函数标记为noexcept来做到这一点。复制构造函数:Useless::Useless(const Useless & f) : n(f, n({++ct;pc = new char[n];for(int i = 0, i < n, i++)pc[i] = f.pc[i];}执行深度复制 Useless two = one;//引用f将指向one
移动构造函数:Useless::Useless(const Useless & f) : n(f, n({++ct;pc  = f.pc; //steal addressf.pc = nullptr;// 将旧对象指向空指针方便delete[]f.n = 0;}将pc指向现有的数据,以获取这些数据的所有权,pc与f.pc指向相同的数据,后续f.pc = nullptr 方便了delete[], delete 空指针完全没问题-----窃取 pilfering修改了f对象, 因此不能constUseless four(one + three); // 调用operator+(),,右值引用f将关联到operator()返回的临时对象temp,并调用移动构造函数g++4.5 以后的c++11,自动优化,并忽视代码中的移动构造函数

18.2.3移动构造函数解析
要让移动语义发生,需要两个步骤,首先,右值引用让编译器知道合适可以使用移动语义
Useless two = one;// matches Useless::Useless(const Useless &)
Useless four (one+three);//matches Useless::Useless(Useless &)
one是左值,与左值引用匹配,而one+three是右值,与右值引用匹配。因此,右值引用让编译器使用移动构造函数来初始化four
实现移动语义的第二步,编写移动构造函数,并提供所需的行为
g++编译器可以自动消除额外的复制工作,但是通过右值引用,程序员可指出何时使用移动语义
18.2.4 赋值运算符 --移动语义
Useless & Useless::operator=(const Useless & f) //copy assigment
{
if(this = &f)
return *this;
delete [] pc;
n = f.n;
pc = new char[n];
for(int i = 0; i < n; i++)
pc[i] = f.pc[i];
return *this;
}
Useless & Useless::operator=(Useless && f) //move assigment
{
if(this = &f)
return *this;
delete [] pc;
n = f.n;
pc = f.pc;
f.n = 0;
f.pc = nullptr;
return *this;
}

18.2.5 强制移动
移动构造函数和移动赋值运算符使用右值。但是如果想把左值用得像右值一样,如从对象数组中挑选一个并舍弃其他,默认左值引用,但是右值的话方便。
可以使用static_cast<>将对象的类型强制转换为Useless &&, 但C++11 提供了一种更简单的方式:
在头文件utility中声明有函数:std:move()。
int && rr3 = std::move(rr1); //ok
move告诉编译器,我们希望将左值rr1用得像右值一样,
e.g.
#include
#include
using namespace std;
int main()
{
int x = 1;
int y = 2;
int && r = x + y;
x = y = 5;
cout << r << " ";
int rr1 = 2;
int && rr3 = move(rr1);
++rr1;
cout << rr3 << " ";
int z = rr1 * 5;
cout << z << endl;
return 0;
}
输出:3 3 15

18.3 新的类功能
18.3.1 特殊的成员函数
原有四个——默认构造函数,复制构造函数,复制赋值运算符和析构函数,的基础上,C++11新增了:
移动构造函数,移动赋值运算符。这些成员函数是编译器在各种情况下自动提供的

没有提供任何参数的情况下,将调用默认构造函数,如果你没给任何构造函数,编译器自己提供一个默认的默认构造函数。
对于使用内置类型的成员,默认的默认构造函数将不对其进行初始化,对于属于类对象的成员,将调用其默认构造函数
如果没有提供赋值构造函数,代码又需要他,编译器将提供一个默认的赋值构造函数,同样的情况,编译器也会提供一个默认的移动构造函数
假定类名Classname:
Classname::Classname(const Classname &); //编译器给 默认复制构造函数
Classname::Classname(Classname &&); //编译器给 默认移动构造函数
同样 赋值运算符
Classname & Classname::operator(cosnt Classname &); //copy
Classname & CLassname::operator(Classname &&); //move 最后,没有定义析构函数,编译器也会给一个
class a
{
public:int s;
};
int main()
{a o;o.s = 1;cout << o.s << endl;return 0;
}
输出:1如果你提供了以上函数,编译器就不会多此一举
默认的移动构造函数和移动赋值运算符 工作方式和复制版本类似

18.3.2 默认的方法和禁用的方法
如果非要使用编译器提供的默认函数 加上default显示声明出来
class a
{
public:
a() = default; //使用编译器提供的默认构造函数

delete 用于禁止编译器使用特定方法
a & operator=(const a &) = delete; //禁止使用复制运算符 default只能用于6个特殊成员函数,毕竟编译器也只能提供这六个,delete可以用于任何成员函数
delete可以用来禁止特定的转换
class类中有函数void func(double)
class sc;
sc.func(5);
将会把5 变成5.0参与运算
当有 void func(int) = delete;上述方法产生编译错误

18.3.3 委托构造函数
多个构造函数可能包含相同的代码。也就是说有些构造函数可能需要包含其他构造函数已有的代码。但是为使得编码简单,C++11允许在一个构造函数的定义中使用另一个构造函数。即:委托。因为构造函数暂时将穿件对象的工作委托给另一个构造函数。委托使用成员初始化列表语法的变种
e.g.
class Notes
{
int k;
double x;
std::string st;
public:
Notes();
Notes(int);
Notes(int ,double);
Notes(int, double, std::string);
};
Notes::Notes(int kk, double xx, std::string stt) : k(kk), x(xx), st(stt) {/* do stuff*/}
Notes::Notes(int kk) : Notes(kk, 0.01, “Ah”) {/do other stuff/}
构造函数Notes(int),先 使用了第一个构造函数初始化数据成员并执行函数体,之后才执行自己的函数体

18.3.4 继承构造函数
为进一步简化编码工作, C++11 提供了一种让派生类能够继承基类构造函数的机制。默认构造函数,复制构造函数和移动构造函数除外。
也不会使用与派生类的构造函数的特征表匹配的构造函数
e.g.
class BS
{
int q;
double w;
public:
BS() : q(0), w(0) {}
BS(int k) : q(k), w(100) {}
BS(double x) : q(-1), w(x) {}
BS(int k, double x) : q(k), w(x) {}
void show() const { std::cout << q << w;}
};

class DR : public BS
{short j;
public:using BS:BS //将BS的构造函数继承了过来DR() : j(-100) {} //DR 需要他自己的构造函数,默认构造函数不继承DR(double x) : BS(2*X), j(int(x)) {}DR(int i) : j(-2), BS(i, 0.5*i) {} //委托构造函数 void show() const (std::cout <<j; BS.show();}
};int main()
{DR o1; //use DR()DR o2(18.81);// use DR(double) insted of BS(double)DR o3(10, 1.8); // use BS(int, double)
}
继承来的基类构造函数只能初始化基类成员,派生类自有的成员,使用成员初始化列表语法

18.3.5 管理虚方法:override 和final
虚方法对实现多态类层次结构很重要,让基类引用或指针能够根据指向的队形类型调用相应的方法。虚方法陷阱:基类中声明的虚方法在派生类中重写将覆盖旧版本,特征表不匹配将 隐藏而不是覆盖旧版本
C++11 中,使用虚方法说明符overrid指出要覆盖的一个虚函数,将其放在参数列表后面,如果声明与基类方法不匹配,编译器将报错
如果想禁止派生类覆盖特定的虚方法,参数列表后加上final。加上后,你讲无法在派生类中重新定义该虚函数

18.4 Lambda函数/Lambda表达式
18.4.1 比较函数指针、函数符和Lambda函数
补充知识
【C++】回调函数与仿函数
https://blog.csdn.net/weixin_43723269/article/details/121595303
回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现,在C++、Python等更现代的编程语言中还可以使用仿函数或匿名函数。
而这个约束我们希望不将他写死

C-函数指针
bool (*compare)(int, int);//函数指针定义
compare=compareUp;//compareup 定义好的函数
void bubbleSort(vector<int>& arr, bool (*compare)(int&, int&))//将函数指针作为排序函数的参数,它接收一个函数的地址
{for (int i = 0; i < arr.size(); i++){for (int j = i + 1; j < arr.size(); j++){if (compare(arr[i], arr[j])){int tmp = arr[i];arr[i] = arr[j];arr[j] = tmp;}}}
}lambda表达式(匿名函数)
另外,在我们不想声明定义一个过于简单的函数时,可以使用lambda表达式(匿名函数),匿名函数也可以传入函数指针中使用:
void test02()
{//使用匿名函数作为函数指针vector<int>b = { 9,3,5,23,12,34,16,8,20 };cout << "从小到大排序(匿名函数)" << endl;bubbleSort(b, [](int& v1, int& v2) { return v1 > v2 ? true : false; });printVectorInt(b);
}仿函数 --函数符--类对象
仿函数(Functor)又称为函数对象(Function Object)是一个能行使函数功能的类
顾名思义,仿函数即仿照的函数,表示它功能与函数类似但并不是真正的函数,仿函数又叫函数对象。在C++中它通过重载函数调用运算符即()运算符谓词 是一个可调用的表达式,其返回结果是一个能用作条件的值,标准库算法所使用的的谓词分两类:一元谓词--unary predicate,意味着只接受单一参数二元谓词--binary predicate,接受两个参数接受谓词参数的算法对输入序列中的元素调用谓词,因此元素类型必须能转换谓词的参数类型返回bool类型的仿函数称为谓词如果operator()接受一个参数,那么叫做一元谓词如果operator()接受两个参数,那么叫做二元谓词//定义排序规则的仿函数类--从小到大
在仿函数的类中,通常不需要定义构造函数和析构函数,这将由系统帮我们自动完成。值得一提的是,我们最好将重载operator()的函数定义为常函数,这表明我们并不会改变传入的参数,避免一些麻烦。
class compareUp
{
public:bool operator()(const int& val1, const int& val2) const {//定义为常函数if (val1>val2){return true;}return false;}
};总结
使用回调函数(函数指针实现)与使用仿函数优缺点比较
1.当需要回调的功能函数比较简单时,通常声明为内联函数,这时函数调用将被展开,此时与仿函数性能差别不大;但是使用函数指针实现的回调函数它终究是函数,如果我们需要保留某些状态,这时函数无法实现,因为在函数调用结束后,内部数据都被释放了。而仿函数可以做到这一点,例如我们需要实现记录某一个回调逻辑在程序运行中被调用了多少次,这在普通函数内只能通过一些外部的静态变量来实现;而在仿函数中,我们可以通过给类添加属性,来记录一些状态,而不是使用外部的变量。
2.当需要回调的功能函数比较复杂时,此时回调函数作为一个函数指针传入,其代码亦无法展开。而仿函数则不同。虽然功能函数本身复杂不能展开,但是对仿函数的调用是编译器编译期间就可以确定并进行inline展开的。因此在这种情形下,仿函数比之于回调函数,有着更好的性能。Lambda表达式------重要
原文链接:https://blog.csdn.net/YGG12022/article/details/124043116
C++11的一大亮点就是引入了Lambda表达式。利用Lambda表达式,可以方便的定义和创建匿名函数。声明Lambda表达式
Lambda表达式完整的声明格式如下:[capture list] (params list) mutable exception-> return type { function body }各项具体含义如下:
capture list:捕获外部变量列表
params list:形参列表
mutable指示符:用来说用是否可以修改捕获的变量
exception:异常设定
return type:返回类型
function body:函数体
此外,我们还可以省略其中的某些成分来声明“不完整”的Lambda表达式,常见的有以下几种:
[capture list] (params list) -> return type {function body}  //声明了const类型的表达式,这种类型的表达式不能修改捕获列表中的
[capture list] (params list) {function body}   //省略了返回值类型,但编译器可以根据以下规则推断出Lambda表达式的返回类型: (1):如果function body中存在return语句,则该Lambda表达式的返回类型由return语句的返回类型确定; (2):如果function body中没有return语句,则返回值为void类型。
[capture list] {function body}                                //格式3中省略了参数列表,类似普通函数中的无参函数。捕获外部变量
Lambda表达式可以使用其可见范围内的外部变量,但必须明确声明(明确声明哪些外部变量可以被该Lambda表达式使用)。Lambda表达式通过在最前面的方括号[]来明确指明其内部可以访问的外部变量,这一过程也称过Lambda表达式“捕获”了外部变量。
int main()
{int a = 123;auto f = [a] { cout << a << endl; }; f(); // 输出:123//或通过“函数体”后面的‘()’传入参数auto x = [](int a){cout << a << endl;}(123);
}C++11中的Lambda表达式捕获外部变量主要有以下形式:
[变量名, …]                默认以值得形式捕获指定的多个外部变量(用逗号分隔),如果引用捕获,需要显示声明(使用&说明符)
[=]                        以值的形式捕获所有外部变量 都隐式地以传值方式加以引用。
[&]                        以引用形式捕获所有外部变量 都隐式地以引用方式加以引用
[=, &x]                    变量x显式的以引用形式捕获,其余变量以传值形式捕获
[&, x]                     变量x显式的以值传递形式捕获,其余变量以引用形式捕获
[]                         不捕获任何外部变量
[this]                     以值的形式捕获this指针 对于[=]或[&]的形式,lambda 表达式可以直接使用 this 指针。但是,对于[]的形式,如果要使用 this 指针,必须显式传入:[this]() { this->someFunc(); }();
1、值捕获
值捕获和参数传递中的值传递类似,被捕获的变量的值在Lambda表达式创建时通过值拷贝的方式传入,因此随后对该变量的修改不会影响影响Lambda表达式中的值。
int main()
{int a = 123;auto f = [a] { cout << a << endl; }; a = 321;f(); // 输出:123
}
这里需要注意的是,如果以传值方式捕获外部变量,则在Lambda表达式函数体中不能修改该外部变量的值。2、引用捕获
使用引用捕获一个外部变量,只需要在捕获列表变量前面加上一个引用说明符&。如下:
int main()
{int a = 123;auto f = [&a] { cout << a << endl; }; a = 321;f(); // 输出:321
}
从示例中可以看出,引用捕获的变量使用的实际上就是该引用所绑定的对象。3、隐式捕获
上面的值捕获和引用捕获都需要我们在捕获列表中显示列出Lambda表达式中使用的外部变量。除此之外,我们还可以让编译器根据函数体中的代码来推断需要捕获哪些变量,这种方式称之为隐式捕获。隐式捕获有两种方式,分别是[=]和[&]。[=]表示以值捕获的方式捕获外部变量,[&]表示以引用捕获的方式捕获外部变量。
int main()
{int a = 123;auto f = [=] { cout << a << endl; };    // 值捕获f(); // 输出:123
}int main()
{int a = 123;auto f = [&] { cout << a << endl; };    // 引用捕获a = 321;f(); // 输出:321
}4、混合方式
上面的例子,要么是值捕获,要么是引用捕获,Lambda表达式还支持混合的方式捕获外部变量,这种方式主要是以上几种捕获方式的组合使用。怎样修改捕获的变量
int main()
{int a = 123;auto f = [a]()mutable { cout << ++a; }; // 不会报错cout << a << endl; // 输出:123f(); // 输出:124
}Lambda表达式的参数
Lambda表达式的参数和普通函数的参数类似,还有一些限制:参数列表中不能有默认参数,因此lambda调用的实参数目永远与形参相等不支持可变参数所有参数必须有参数名

18.4.2 为何使用lambda
将相关的代码放在一起,lambda是理想的选择,因为其定义和使用在同一个地方进行

重复使用的lambda表达式,可以制定一个名称
auto mod3 = [](int x) { return x % 3 == 0;} //mod3 成为了lambda表达式的名称,科重复用
count1 = std:count_if(n1.begin(), n1.end(), mod3);可以向常规函数一样使用有名称的lambda
bool result = mod3(z); // result is true if z % 3 == 0

18.5 包装器
包装器wrapper,也叫适配器adpater。这些对象用于给其他编程接口提供一致或更适合的接口
bind 第十六章// https://blog.csdn.net/dongkun152/article/details/123992292
men_fun 能够将成员函数作为常规函数进行传递
reference_wrapper 能够创建行为像引用但可被复制的对象
function 能够以统一的方式处理多种类似于函数的形式

参数绑定--bind()--<functional>
接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表
auto newCallable = bind(callable, arg_list);
newCallable 本身是一个可调用对象, arg_list是一个都好分隔的参数列表,对应给定的callable参数
即,我们调用newcallable,它会接着调用callable,并传递arg_list中的参数
arg_list中的参数可包含形如 _n的数字,用来表示占位符,表示newcallable中的参数,占据了传递给newcallable的参数的位置,数值n表示生成的可调用对象总参数的位置: _1 newcallable的第一个参数, _2 第二个。。。e.g.
使用bind生成一个调用check_size的对象,
//check6是一个可调用对象,接受一个string类型的参数,并使用此string和值6调用check_size
auto check6 = bind(check_size, _1, 6);
只有一个占位符_1,表示check6只接受一个参数,并且因为出现在参数列表第一个位置,因此check6的这个参数对应check_size中的第一个参数,是一个const string& 的参数
翻译过来就是:调用check6必须传递给他一个string类型的参数,check6拿着它调用check_size
string s = "hello";
bool b1 = check6(s); // check(s)调用check_size(s,6)
因此可以将以下lambda表达式替换掉
auto wc = find_if(words.begin(), words.end(), [sz](const string &a));
-->
auto wc = find_if(words.begin(), words.end(), bind(check_size, _1, sz));
此bind调用生成一个可调用对象,将check_size 的第二个参数绑定到sz。当find_if 对words的strind嗲用这个对象时,这些对象会调用check_size,将给定的string和sz传递给他名字_n都定义在一个名为placeholders的命名空间中,它本身又定义在std中
using namespace std::placeholders#include <iostream>
#include <functional>
using namespace std;using namespace std::placeholders;void func(int a, int b, int c)
{cout << (a -b -c) << endl;
}int main(int argc, char *argv[])
{auto fn1 = bind(func, _1, 2, 3);auto fn2 = bind(func, 2, _1, 3);fn1(10);fn2(10);return 0;
}
输出:5-11

18.5.1 包装器function及模板的低效性
answer= ef(q)
ef可以是函数名,函数指针,函数对象或有名称的lambda表达式,导致模板效率低,正常情况下,每种可调用类型都会实例化模板一次
调用特征标:由返回类型及参数类型决定
function模板定义在中,从调用特征标的角度定义一个对象,用于包装调用特征标相同的函数指针 函数对象 或lambda
std::function<double(char, int)> fdci; 然后可以将满足要求的可调用类型付给它
P825 例子中 可以使用function<double(double)> 创建六个包装器,用于六个函数,仿函数和lambda
这样对use_f()六次调用中, 访问相同的(functioin<double(double))—只实例化一次
function<double(double)> ef3 = Fq(10.0);
use_f(y, ef3);

优化
不用声明六个function<double(double)>
typedef function<double(double)> fdd; //简化
cout << use_f(y, fdd(dub)) << endl; // 创建并初始化指向dub函数的对象P 826
...

18.6 可变参数模板
创建参数数量可变的函数模板和模板类
要点:
模板参数包 函数参数包 展开参数包 递归
18.6.1 模板和函数参数包
C++11提供了一个用省略号表示的元运算符 meta-operator, 用来声明表示模板参数包的标志符
模板参数包基本上是一个类型列表。同事,能够用来声明表示函数参数包的标志符,它是一个值列表
template<typename…Args> //Args is a template parameter pack
void show_list(Args…args) //args is a function parameter pack
{}

Args可以匹配任意数量的类型
show_list1('s',80,“haha”,4.5);

18.6.2 展开参数包
可将省略号放在函数参数包右边,将参数包展开
template<typename…Args> //Args is a template parameter pack
void show_list(Args…args) //args is a function parameter pack
{
show_list1(args…); // passes unpacked args to show_list1()…为了访问某一个参数
} // 导致无限递归

18.6.3 在可变参数模板函数中使用递归
上述代码提供了思路:将函数参数包展开,对列表中的第一项进行处理,剩余的传递给递归调用,交给下一层处理,直到列表为空。
template<typename T, typename…Args>
void show_lis3(T value, Args…args)
{
std::cout << value << ", ";
show_list3(args…);
}
可以显示T value 并将剩余的args 向下传递

对于大量值,替换为
show_list3(const Args&...args); 将每个函数参数引用const &
// variadic2.cpp#include <iostream>#include <string>// definition for 0 parametersvoid show_list() {}// definition for 1 parametertemplate<typename T>void show_list(const T& value){std::cout << value << '\n';}// definition for 2 or more parameterstemplate<typename T, typename... Args>void show_list(const T& value, const Args&... args){std::cout << value << ", ";show_list(args...); }int main(){int n = 14;double x = 2.71828;std::string mr = "Mr. String objects!";show_list(n, x);show_list(x*x, '!', 7, mr);return 0;}

18.7 C++11 新增的其他功能
18.7.1 并行编程
C++定义了一个支持线程化执行的内存模型,添加了关键字thread_local,和相关库
thread_local 将变量声明为静态存储,其持续性与特定线程相关:即定义这种变量的线程过期时,变量过期
库支持由原子操作(auto operation)库和线程支持库组成
, 线程支持库提供了thread mutex condition_variable future
18.7.2 新增的库
正则表达式

18.8 语言变化
18.8.3 使用Boost库
18.9 接下来的任务
OOP方法的基本活动之一是发明能够表示正在模拟的情形–问题域的类,逐步迭代
常用技术:
用例分析/use-case analysis,
开发小组列出常见的使用方式和最终系统将用于的场景:找出元素、操作、职责。
以确定可能要是用的类和类特性
CRC 卡:
Class/Responsibilities/Collaborators
卡片是一种分析场景的简单方法,开发小组为每个类创建索引卡片,卡片上列出类名,类职责(如表示的数据和执行的操作),类的写作者(如必须与之交互的其他类)。然后,小组使用CRC卡片提供的接口模拟场景,这可能提出新的类。转换责任等

更大规模上,是用于整个项目的系统方法。最新的建模语言,Unified Modeling Language, UML--软件工程
用于表示编程项目的分析和设计语言。重要的类库

附录
alignof --等用到的
noexcept–指出函数不会引发异常,也可用作运算符,判断操作数/表达式是佛能引发异常,如果引发了返回false,否则返回true
int halt(int) noexcept; //true

F string P870
G STL
H Reference

C++ Primer Plus-note相关推荐

  1. c++ primer note

    ---恢复内容开始--- 1.decltype 2.auto 3.cbegin 4.cend 5.constexpr 6.(*Parray)[10]=&arr; //Parray 指向一个含有 ...

  2. MDEV Primer

    /*************************************************************************** MDEV Primer* 说明:* 本文内容来 ...

  3. c++ primer 5th第13章拷贝控制知识点和自编习题答案

    首先,先给大家提个醒.在网上的随书源代码里关于hasptr类的类指针版本编写的移动构造函数.移动赋值运算符.和析构函数部分是有错误的.大家可以把hasptr累指针版本(里面带移动构造函数和移动赋值运算 ...

  4. C++Primer第五版学习笔记

    <C++ Primer>Learning Note 程序实例下载地址:http://www.informit.com/title/0321714113 第一章            开始 ...

  5. 《C++ Primer 5th》笔记(9 / 19):顺序容器

    文章目录 顺序容器概述 确定使用哪种顺序容器 容器库概览 迭代器 迭代器范围 使用左闭合范围蕴含的编程假定 容器类型成员 begin和end成员 容器定义和初始化 将一个容器初始化为另一个容器的拷贝 ...

  6. C primer plus 复习题答案(上)

    C primer plus 复习题答案 复习题答案目录 C primer plus 复习题答案 第一章 初识C语言 第二章 C语言概述 第三章 数据和C 第四章 字符串和格式化输入/输出 第五章 运算 ...

  7. 《C++ Primer 第5版》-11.2关联容器概述-康奈尔笔记

    引入:2018年10月看<C++ Primer 第5版>而写的简单笔记 11.2.1定义关联容器 关联容器概述 定义关联容器 初始化multimap或multiset map<str ...

  8. C++Primer阅读笔记

    文章目录 算术类型 基本内置类型 变量 const限定符 类型别名 auto decltype 字符串.向量和数组 String for vector begin和end运算符 note 数组 字符数 ...

  9. 《C++Primer 第五版》——第八章 IO 库

    <C++Primer 第五版>--第八章 IO 库 8.1 IO 类 IO 类型间的关系 8.1.1 IO 对象无拷贝或赋值 8.1.2 条件状态 查询流的状态 管理条件状态 8.1.3 ...

  10. Chapter 3. Strings, Vectors and Arrays -C++ Primer 5 notes

    Chapter 3. Strings, Vectors and Arrays What does built-in types include? chapter 2 array maybe more? ...

最新文章

  1. Linux学习之系统时间同步
  2. oracle db_files,如何解决 Oracle 中 DB_FILES 限制引起的 ORA-00059 问题
  3. win7下安装rose
  4. String类为什么是final的
  5. java的迭代器类中有哪些类_java中的集合类 以及 迭代器
  6. mysql 主从一致性_mysql 主从一致性保证
  7. 成功解决“ValueError: Unknown metric function:sensitivity”
  8. JQGrid 嵌套字表, json数据
  9. java中打印当前时间_在java中打印当前日期
  10. 沃尔沃汽车:通过基于模型的开发工具实现汽车软件模型结构度量
  11. 大腿神经网络解剖图片,大腿神经网络解剖图谱
  12. 微信小程序map组件 markers 展示当前位置修改标记点图标
  13. android 9.0user版本如何开启root,打开su
  14. HTML模拟电池页面,Html 电池图标
  15. python 全栈开发是什么意思_我为什么说Python是全栈式开发语言?
  16. jpeg 与 png 图片格式的区别
  17. cesium实现报警波纹
  18. Win7 下安装 Visual Studio 2008 失败的解决办法
  19. 加速度计与陀螺仪区别
  20. IDEA 公司推出新字体,极度舒适~

热门文章

  1. 如何将Android数据库操作通用化(四)
  2. 记录一个设备旁挂ikuai后收不到回包TTL为1的问题
  3. 连接数据库报Java::ComMysqlCjJdbcExceptions::CommunicationsException: Communications link failure
  4. 编程输出1000以内所有的完数,并输出其所有因子。所谓完数,即一个数的所有因子(除其自身)之和恰好等于其自身。如6就是一个完数,6=1+2+3。**输出格式要求:“\n%5d\n“, “%5d“
  5. 一文详解图像中通道相关知识
  6. 综述:基于图学习的推荐系统;论文笔记
  7. Arduino UNO 摇杆测试实例
  8. ros melodic解决报错ImportError: libcv_bridge.so: cannot open shared object file: No such file or directo
  9. 视频测试软件推荐,手机视频播放器哪个好用,视频播放软件评测推荐
  10. 【动态规划】【RQ82】又上锁妖塔