Vector(std::initializer_list
关键字virtual的意思是可能随后在其派生类中重新定义;含有纯虚函数的类称为抽象类;
Vector::Vector(Vector&& a):elem{a.elem},sz{a.sz}
{ a.elem = nullptr;
a.sz = 0;
}
//移动语义的关键在于:1:移动过来的对象要清零;2.要把移动的对象给当前对象;要令源对象处于可析构和赋值的状态。
y = std::move(x);
Vcetor(Vector&) = delete; // 抑制操作;删除默认的构造
typename另外一个作用为:使用嵌套依赖类型(nested depended name),如下所示:
class MyArray
{
public:
typedef int LengthType;
}
template
void MyMethod( T myarr )
{
typedef typename T::LengthType LengthType;
LengthType length = myarr.GetLength;
}
这个时候typename的作用就是告诉c++编译器,typename后面的字符串为一个类型名称,而不是成员函数或者成员变量,这个时候如果前面没有typename,编译器没有任何办法知道T::LengthType是一个类型还是一个成员名称(静态数据成员或者静态函数),所以编译不能够通过。
[&](int a){ return a < x; } // 返回bool;
using size_t = unsigned int;
using value_type = T;
---------------------------
forward_list
deque
set
multiset
map
multimap
unordered_map
unordered_set
unordered_multiset
---------------------------------
template
vector
----------------------------------
ostream_iterator
unique_copy(b.begin(),b.end(),oo) ; // 将不重复的单词拷贝输出,丢弃重复值;
mutex m
void f(){
unique_lock
}
unique_ptr
using namespace std::chrono;
auto t0 = hign_reslution_clock::now();
this_thread::sleep_for(milliseconds{20});
auto t1 = high_resolution_clock::now();
cout << duration_cast
async()会根据系统有多少内核来决定启用多少线程,不要试图对共享资源且需要用锁机制的任务使用async().
numeric_limits
类型函数是C++的编译时计算机制的一部分,它允许程序进行更严格的类型检查以获取更优的性能,我们通常把这种用法称为元编程,或者模板元编程.iterator_traits机制检查当前容器支持哪种迭代器;
template
using iterator_category = typename
std::iterator_traits
bool b1 = Is_arithmetic
bool b2 = Is_arithmetic
用于诊断: static_assert(Is_arithmetic
tuple表示任意形式的元素序列;
auto pp = make_pair(v.begin(),2); //pair
tuple
string s = get<0>(t); //获取tuple第一个元素;
char16_t : 该类型存放utf16位字符集;
char32_t : 该类型存放utf32等32位字符集;
如果你需要更精细地控制整数的尺寸,可以使用
u'c' ; //char16_t
U'char32_t'; //char32_t
L'c'; //wchar_t
u8R""前缀 utf8原始字串
U"foo"
LR"foo"
线程局部:thread-local对象,随着线程的创建而创建,随着线程的销毁而销毁;
utf8字符串的结尾是\0,utf16是u'\0',utf32是U'\0';
前缀u和R对于顺序和大小写敏感
reinterpret_cast
const int x ; //错误,因为我们无法给const对象赋值,所以它必须初始化;
int stat()//同一作用域,结构和函数可以同名;
普通旧数据(内存中连续字节序列),没有高级语义的概念,比如运行时多态,用户自定义的拷贝语义,这么做的主要动机是在硬件条件允许 的范围尽可能高效地移动对象;
std::memcpy();POD(普通旧数据)是指能被”仅当作数据”处理的对象,程序员无须顾及类布局的复杂性以及用户自定义的构造、拷贝和移动语义;
有默认构造函数的不行,有自定义默认构造函数的不行,有虚函数的不行;
POD条件:
不具有复杂的布局(比如含有vptr),不具有非标准(用户自定义的)拷贝语义;含有一个最普通的默认构造函数;
POD必须是属于下列类型的对象:标准布局类型,平凡可拷贝类型;
如果需要定义一个默认构造函数,使用=default,我们认为它是平凡的;
不是POD:含有一个非标准布局的非static成员或基类包含virtual函数;含virtual基类;
含有引用类型的成员,其中的非静态数据成员有多种访问修饰符,阻止了重要的布局优化,在多个基类中都含有非static数据成员,或者在派生类和基类中都含有非static数据成员,或者基类类型与第一个非static数据成员的类型相同;
基本上,标准布局是指与C语言的布局兼容的类型,并且应该能被常规的C++应用程序 二进制接口中ABI处理;如果一个拷贝操作能实现成逐位拷贝的形式,则它是平凡的。
一般操作是用户定义的,操作所属的类含有virtual函数,含有virtual基类,这些操作所属的类含有非平凡的基类或者成员;
is_pod
template
void mycopy(T* to, const T* from, int count)
{
if(is_pod
memcpy(to,from,count* sizeof(T));
else
for(int i = 0; i != count; ++i)
to[i] = from[i];
}
位域:
struct ppn{
int: 3;
bool dirty:1; //把=换成了://有的去掉了变量名;
};
//注意语法:int中间无名,且分号;bool中间无初始化符号;冒号;
域必须是整形或枚举类型,我们无法获取域的地址;
union是一种特殊的struct,它的所有成员都分配在同一地址空间上,在同一时刻只能保存一个成员的值;
C++允许为联合的最多一个成员指定类内初始化器,此时,该初始化器被用于默认初始化;
union U2{
int a;
const char* p{""};
};
U2 x1; //默认x1=''
U2 x2{8}; //x2.a ==7
---------------------------
class Entry{
enum class Tag { number,text};
Tag type;
...
if (type!=Tag::number) throw Bad_entry{};
if (type==Tag::text) s.~string(); //显示地消毁
enum class 它的枚举值名字位于enum的局部作用域内,枚举值不会隐式地转换成其他类型;
普通的enum它的枚举值名字与枚举类型本身位于同一作用域中,枚举值隐式地转换成整数;
enum class color:char; // 声明
enum class Color_code:char{red,yellow,green,blue};
enum {arrow_up=1,arrow_down,arrow_sideways};
如果想紧凑地存储数据,则把结构中尺寸较大的成员布局在较小的成员之前;
对于内置数组T v[N]来说,编译器使用V和V+N代替begin(v)和end(v).
如果想在范围for循环中修改元素值,则应该使用元素的引用;for(int& x:v) ++x;
break语句负责跳出最近的外层switch语句,或者循环语句;
为每个类,模板,名字空间分别编写注释;为每个非平凡的函数分别编写注释并指明:函数的目的,用到的算法,以及该函数对其应用环境所做的某些设定;为全局和名字空间内的每个变量及常量分别编写注释;为某些不太明显或不可移植的代码编写注释;
如果可能的话,优先选用switch而非if;优先选用范围for而非普通的for;
typeid(type)
dynamic_cast
static_cast
reinterpret_cast
const_cast
new (expr-list> type)放置语法
new (expr-list) type{expr-list}
new(expr-list) type(expr-list)
new(expr-list) type{expr-list}
delete poointer
delete[] pointer
后置++的优先级高于一元*,因此*p++的意思是*(p++);//若*p优先级高,则增的是值,而事实是,增的是地址,但是后增的结果要反应在下一次。所以cout<<*p++,第一次仍是首元素的结果;
设计了一组关键词作为等效的替代:
and &&
and_eq &=
bitand &
bitor |
compl ~
or || 注意位与什么的加了bit, bitor,bitand;
==的优先级高于&,所以if(i&mask==0) // if( (i&mask)==0 )
临时量可以用作const引用或者命名对象初始化器;const string& s = s1+s2;
string ss = s1+s2;
Error: const char* cs = (s1+s2).c_str(); //临时对象给了指针。。。野指针;不要这样用
常量表达式是指由编译器求值的表达式,它不能包含任何编译时未知的值。
含有constexpr构造函数的类称为字面值常量类型,构造函数必须足够简单才能声明成constexpr,函数体必须为空且所有成员都是用潜在的常量表达式初始化的。
全局变量等静态分配的对象的地址是一个常量,然而,该地址值是由链接器赋值的,而非编译器。如果我们转换了某个值的类型,然后能够把它再转回原类型并且保持初始值不变,则称该转换是值保护的。
float f = FLT_MAX;
double d2 = DBL_MAX;
numeric_limits 定义在
long double ld2 = numeric_limits
任何指向对象类型的指针都能隐式地转换成void*,指向派生类的指针(或引用)能隐式地转换成指向其可访问的且明确无二义的基类的指针或引用。
T*可以隐式地转换成const T*,类似的,T&能隐式地转换成const T&.
constexpr是一个关于值的概念
通用complex
template<> class complex
public:
constexpr complex(double re= 0.0,double im = 0.0);
}
除非万不得己,不要把对象放在自由存储上,优先使用作用域内的变量;
int* p1 = new int[n];
unique_ptr
放置语法:X* p2 = new(buf) X;
命名对象的生命周期由作用域决定,然而,某些情况下我们希望对象与创建它的语句所在的作用域独立开来,很多时候我们在函数内部创建了对象,并且希望在函数返回后仍能使用这些对象,new负责创建这样的对象,运算符delete则负责销毁它们。
函数的一项重要作用:把一个复杂的运算分解为若干有意义的片段;函数的规模大约在7-40行左右;如果发现函数调用的代价比较高,可以考虑用内联机制来消除其影响。程序员应该把函数视作代码的一种结构化机制。形如[[...]]的概念被称为属性,属性可以置于C++语法的任何位置,[[noreturn]]void exit(int); exit永远不会返回任何结果;当程序调用函数时,我们为该函数的形参申请内存空间,并用实参初始化对应的形参,参数传递 的语义与初始化的语义一致(严格地说是拷贝初始化)。.
f(vector
(*f1)("Mary rose");
void f(int) noexcept;
using Pc = extern "c" void(int);
#define printx(x) cout<< #x "=" << x <<'\n'; //cout << 'a' << "=" << a << '\n';
__cplusplus: 在C++编译器中有定义,__DATE__,__TIME__,__FILE__源文件的名字;
__LINE__,__FUNC_——,__STDC_HOSTED__。
C语言有两种可能的运行环境,1. 独立(freestanding),在独立环境中,C程序执行不需要操作系统的支持,因此只具有最小的链接库能力。2. 宿主(hosted) :在宿主的环境中,C程序会在操作系统的控制下执行,并且会使用操作系统所提供的支持,具有完整的标准链接库能力。为宿主环境而编译的程序必须定义一个名为main的函数,这是程序开始时调用的第一个函数。析构函数不抛出异常;文件,锁,网络连接,线程都是系统资源,函数在抛出异常前必须释放这些资源或者把它们移交给其它的资源句柄;
------------------------------
File* f = open(fn,"r");
try{
use f;
}
catch(...){
fclose(f);
throw;
}
fclose(f);
}
------------------------------
注意类的定义:
operator FILE*(){return p;}
---------------------------------------------
template
struct Final_action {
Final_action(F f) :clear{ f } {}
~Final_action() { clear(); }
F clear;
};
template
Final_action
{
return Final_action
}
---------------------------------------------
ostringstream是C++的一个字符集操作模板类,定义在sstream.h头文件中。ostringstream类通常用于执行C风格的串流的输出操作,格式化字符串,避免申请大量的缓冲区,替代sprintf。
ios_base<-ios<-ostream<-oostringstream;
处理异常的时候不要抛出异常;不要抛出一个无法捕获的异常;任何措施都无法捕获在名字空间和线程局部对象的初始化及析构过程中抛出的异常,这也是我们尽量避免使用全局变量的另一个原因。
在设计初期尽早确定异常处理策略;当无法完成即定任务时抛出异常;用异常机制处理错误;为特定任务设计用户自定义异常类;使用层次化异常处理;抛出异常前先释放局部资源
可以把构造函数的函数体(包括成员初始化列表在内)放在一个try块中,这样该构造函数就能自己捕获异常了。
class X{
vector
vector
public:
X(int,int);
};
X:X(int sz1,int sz2)//注意这,构造函数初始化器这里;
try:vi(sz1),vs(sz2)
{
}
catch(std::exception& err){
}
能捕获成员构造函数抛出的异常;“结束时留有throw"的意思是在某处抛出了异常,但是在该异常未被捕获,因而运行时系统试图把该异常从函数传递给函数的调用者。
默认情况下,terminate()会调用abort(),对于大多数用户来说,也可以通过调用
如果异常在线程中未被捕获,系统将调用std::terminate();
可以用标准库函数current_exception()把某一线程的异常传给另一线程的处理程序。
库,源文件,编译单元则提供了粗粒度的分离,逻辑上最理想的方式是模块化,独立的事物保持分离。只允许通过良好定义的接口访问“模块”。
C++通过其他语言特性(如函数,类,名字空间)的组合和源码的组织来表达模块化。
全局作用域也是一个名字空间,用::来引用。
类也是名字空间;
using std::string;//using声明
using namespace std; using指示;
参数依赖查找:
void f(Chrono::Date d,int i)
{
std::string s = format(d);//Chrono::format() //会查找参数类型的Chrono::中的format()
std::string t = format(i);//Err ..作用域中没有format()的定义;
}
如果一个参数是一个类成员,关联名字空间即为类本身(包括其基类)和包含类的名字空间;
如果一个参数是一个名字空间的成员,则关联名字空间即为外层的名字空间;
如果一个参数是内置类型,则没有关联名字空间;
名字空间是开放的:不需要连续放在单一的文件中。
namespace A{ int f(); } namespace A{ int g(); }
使用名字空间别名无法重新打开名字空间,理想情况下,一个名字空间应该:表达一组逻辑相关的特性;不会让用户相关的特性;不会给用户增加符号表示上的严重负担;通常应该避免将using指示放在头文件中,因为这样会大大增加名字冲突的机会;如果名字空间中有inline,指出是默认含义;如果使用using namespace *,则默认用inline版本;这种inline namespace方法是侵入式的;
如必要,使用名字空间别名为长名字空间名提供简写;不要给你的名字空间的使用者增加太多符号表示上的负担 ;为接口和实现使用人离的名字空间;用inline名字空间支持版本控制;将using指示用于代码转换,用于基础库以及用于局部作用域内;
将一个源文件提交给编译器后,首先对文件进行预处理(处理宏,以及将#include指令指定的文件包含进来),预处理的结果称为编译单元(translation unit) 。链接器是将分离编译的多个部分绑定在一起的程序。编译器有时候叫加载器(loader),关键字extern;声明,而非一个定义,如果己经初始化,extern将会被忽略。如果一个名字在其定义处之外的编译单元中也可以使用,我们称其具有外部链接,如果一个名字只能在其定义所在的编译单元中被引用,我们称具有内部链接;
我们可以通过使用头文件来保持inline函数定义的一致性;
默认情况下,名字空间中的const对象,constexpr对象,类型别名以及任何声明为static的实体都具有内部链接。如果必须使用全局变量,至少应限制它们只在单一源文件中使用;将声明放在无名名字空间中,声明实体时使用static.使用无名名字空间可以令名字成为编译单元的局部名字,无名名字空间的效果非常像内部链接。
在包含指令以中,<>""内的空格不会被忽略。
头文件一般包括:具名的名字空间: namespace N{},inline名字空间;类型定义,模板定义,声明。函数声明。inline函数定义;constexpr函数定义;数据声明,const定义,constexpr定义;枚举。名字声明,类型别名;编译时断言。包括指令,宏定义,条件编译指令;
头文件不应该包括:普通 的函数定义。数据定义。集合定义,无名名字空间,using指示;
单一定义规则(one-defination rule,ODR).一个类,模板,内联函数的两个定义可以被接受,被认为是相同唯一定义。它们出现在不同的编译单元中,源码一样,含义一样;只要不违反ODR,一个模板定义就可以在多个编译单元中被#include,甚至是函数模板定义以及包含成员函数定义的类模板都可以。可以指定extern声明中使用哪种链接(linkage)规范;extern "C" 表示的是链接规范而非语言,extern "C"通常用于将函数链接到恰好符合C实现规范的Fortran和汇编程序;
C++提供了一种机制为一组声明指定链接规范:例如:
extern "C"{
char* strcpy(char*,const char*);...
} 这种构造通常称为链接块(linkage block),甚至可用来封装C头文件,从而使之适用于C++程序;
extern "C" { #include
extern "C" int g3; //声明,非定义;
extern "C" { int g4;} // 定义;
调用规范:extern "C" {
typedef int (*CFT)(const void*, const void*);
}
extern "C" int ccmp (const void*,const void*);
unix系统中的编译器通常将可执行文件命名为.out.
查看程序执行后的返回值:$ echo $?
win: echo %ERRORLEVEL%
g++ -o prog prog.c // -o prog是参数;
-std=c++0x 参数打开C++11支持;
cerr clog 而clog用来输出程序运行时的一般性信息;
写入endl的效果是结束当前行,并将与设备关联的缓冲区(buffer)内的内容刷到设备中。
curly brace:花括号{};wchar_t 宽字符16位char16_t unicode 16 char32_t unicode 32位
double 10位有效数字 long double 10位有效数字 Unicode是用于表示所有自然语言中字符的标准;
计算机以比特序列存储数据,每个比特非0即1.可寻址的最小内存块称为“字节”,存储的基本称为“字”(4-8字节)通常几个字节组成。
注意面值前缀后缀:
u char16_t unicode16字符;U char32_t unicode32字符;L 宽字符 wchar_t
u8 utf8(仅用于字符串字面常量)ll LL = long long 。
初始化列表有一个重要特点,如果存在丢失信息的风险,则编译器将报错(不支持窄转换);声明为constexpr的变量一定是一个常量,而且必须用常量表达式来初始化;const int *p = nullptr // p是一个指向整形常量的指针;typedef double wages; // 原始类型在前,别名在后using SI = Sales ; // 别名在前,原始名在后;decltype(r+0) b; 如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型;
while(getline(cin,line))
cout << line << endl;
cctype头文件:
isalnum(c),isalpha(c),iscntrl(c),isgraph(c),islower(c)isprint(c) ispunct(c)是标点符号,isspace(c),isupper(c),isxdigit(c),tolower(c),toupper(c)
vector
vector
v.empty(),v.size(),v.push_back(t),v[n]
item->mem //解引用迭代器,等价于(*iter).mem;
if ( s.begin() != s.end() ) //确保非空,vector
注意括号不能少:(*it).empty() ; // 等于 it->mem:
不能在范围for循环中向vector对象添加元素,任何一种可能变改vector对象容量的操作,都会使对象的迭代器失效(你在加,尺寸在变);
但凡使用了迭代器的循环体,都不要向迭代器所属的容器添加元素;
距离指的是右侧的迭代器向前移动多少位置能追上左侧的迭代器(3210),其类型名为difference_type的带符号整形数,string,vector都定义了difference_type;
constexpr unsigned sz = 42; //常量表达式,string strs[get_size()]; 当get_size是constexpr时正确,否则错误,int(&arrRef)[10] = arr ; arrRef引用一个含有10个整数的数组;
int* (arrRef)[10] //arrRef是数组的引用,该数组含有10个整形指针;
int (*p)[10] = &arr //p 指向一个含有10个整数的数组;
cstddef头文件中定义了size_t类型;
int ia[] = {10,2,2,2}; int *beg = begin(ia) ; //注意一般是v.begin();
ptrdiff_t,两种指针相减的类型是一种名为ptrdiff_t;
ptrdiff_t是cstddef头文件中定义的一种与机器实现有关的带符号整数类型,空间够大能表示数组中任意两个指针之间的距离;
当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置);
右移>>如果是带符号类型,在左侧插入符号位的副本或值为0的二进制位;
sizeof(type),sizeof expr 返回所占字节数;
逗号表达式:首先对左侧的表达式求值,然后将求值结果丢掉,逗号运算符真正的结果是右侧表达式的值;如果两个对象类型不同,不会直接将两个不同的类型相加,而是先根据类型转换规则设法将运算对象的类型统一后再求值;const_cast只能改变运算对象底层const;只有const_cast能改变表达式的常量属性。reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释;
() // 类型构造 type(expr_list)
typeid(tyep) //类型id
typeid(expr) 运行时类型ID
ptr->*ptr_to_member; 指向成员选择的指针;// ->*
obj.*ptr_to_member ; 指向成员选择的指针 // .*
对于异常程序:时刻清楚异常何时发生,发生后应如何确保对象有效,资源无泄漏,程序处于合理状态;exception头文件定义了最通常的异常类exception,它只报告异常的发生,不提供任何额外的信息;stdexcept头文件定义了几种常用的异常类;runtime_error.
range_error,overflow_error,underflow_error,logic_error,domain_error[逻辑错误,参数对应的结果值不存在],invalid_argument,length_errr,out_of_range;
new定义了bad_alloc,type_info定义了bad_cast异常类型;
返回一个值的方式和初始化一个变量或形参的方式完全一样,返回的值用于初始化调用点的一个临时量,该临时量就是函调用的结果;EXIT_FAILURE //定义在cstdlib头文件中;EXIT_SUCCESS; //定义在cstdlib头文件中;typedef int arrT[10] ; //arrT是一个类型别名,它表示的类型含有10个整数的数组;func(int i) 函数声明:(*func(int i)) 意味着我们可以对函数的结果执行解引用操作,(*func(int i))[10] //解引用func的调用将得到一个大小是10的数组;int (*func(int i))[10] ; //int (*func(int i))[10] 表示数组中的元素是int类型;声明一个返回数组指针的函数;type (*func(parament))[dimension]
auto func(int i) -> int(*)[10]; //接受一个int参数,返回一个指针;指针指向含10个整数的数组;
decltype并不负责把数组类型转换成对应的指针;
const是否影响重载,看内容:
Record lookup(Account*);
Record lookup(const Account*); //新函数,作用于指向常量的指针;
constexpr函数是指能用于常量表达式的函数,返回类型及所有形参的类型得是字面值,只有一条return.
和其他函数不一样,内联函数和constexpr函数可以在程序中多次定义,多个定义必须完全一致,内联函数和constexpr函数通常定义在头文件中;如果两个函数唯一的区别是它的指针形参指向常量或非常量,则编译器能通过实参是常量决定选用哪个函数,如果实参是指向常量的指针,调用形式是const*函数,如果实参是指向非常量指针,调用形参是普通指针函数;
int (*f1(int))(int*,int); //f1有形参表,所以f1是个函数,f1前面有*,所以f1返回一个指针,进一步发现,指针的类型本身也含形参表,因此指针指向函数,该函数的返回类型是int.
当我们将decltype作用于某个函数时,它返回函数类型而非指针类型,因为,我们显示地加上*以表明我们需要返回指针;而非函数本身;
g++ -std=c++0x aue_aut.cpp -o main.exe
#include
int py{} //括号里不包括任何东西,则将初始化为零;
通用字符名的用法类似于转义序列,通用字符名可以以\u或\U打头,\u后面是8位16进制位,\U后面则是16个16进制位。
wchar_t bob = L'P';
wcout << L"tall" << endl;
d.dddE+n指的是将小数点向右移动n位,而d.dddE-n指的是将小数点向左移n位。之所以称为浮点,就是因为小数点可移动。
cout.setf(ios_base::fixed,ios_base::floatfield);
长度固定的数组,array,使用栈(静态内存分配)。
using namespace std;
array
array
ctime头文件中有一个clock(),返回程序开始执行后所用的系统时间,clock()返回的时间单位不一定是秒;符号常量:CLOCKS_PER_SEC,该常量等于每秒包括的系统时间单位数;将系统时间除以这个值,得到秒数;clock()返回clock_t类型;
float sec;//输入秒数;
clock_t delay = sec * CLOCKS_PER_SEC;
clock_t start = clock();
while(clock() - start < delay ) ; //每次调用的时候,返回系统时间,减去开始;
cin.get();
处理文件:fstream头文件 (ofstream),open(),close()
ofstream of ; of.open("xxx.txt");
cout.precision(2);
of.precision(4); //of /write.txt
读文件:fstream头文件,ifstream; // eof(),fail();读取过程中发生了类型不匹配的情况
if.is_open() //是否打开;
如果文件受损或硬件故障,bad()返回true,没有发生任何错误返回good() true;
调用函数时,将结构的地址(&pplace)而不是结构本身(pplace)传递给它;
const double* (*pa[3])(const double*,int) = {f1,f2,f3}; //函数指针数组;f1,f2,f3为函数名;
常规变量和const变量都可以视为左值,因为可通过地址访问它们,但常规变量属于可修改的左值,而const变量属于不可修改的左值;
函数调用讲明白了,如果一个函数调用,形参与实参类型不匹配,则先要生成一个临时匿名变量,进行一定的转换,然后再调用。而如果类型匹配正确,则不需要临时变量;生成的临时变量只在调用期间存在,此后编译器可以随意删除;
尽可能的使用const,使用const引用使函数能够正确生成并使用临时变量。使用const使函数能够处理const和非const实参,否则,将只能接受非const数据;
double&& ref = 3.0; //右值引用语法;注意&&在类型右侧;
free_throws& jolly = clone(three) ;
int f(int n,int m = 7, int x = 3);默认参数只能是最后几个;只有函数原形声明时指定了默认值,定义的时候可以不指定;函数重载:名称修饰,名称矫正,函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数,其中的泛型可用具体的类型来替换;通过将类型作为参数来传递给模板,可使编译器生成该类型的函数,模板特性有时候也称参数化类型(parameterized types)
在标准C++98添加关键字typenae之前,C++使用class来创建模板,c++98后用typename,使参数表示类型这一点更为明显;模板函数也可以重载,特征标不同;可以提供一个具体化函数定义-称为显式具体化(explicit specialization);template<> swap
Template void swap
候选,常规函数优于模板;非const的优先于const,具体化优于模板化;用于找出最具体的模板的规则被称为函数模板的部分排序规则(partia ordering rules).
如果expression是一个左值,则var为指向其类型的引用。
double xx = 4.4;
dceltype ((xx)) r2 = xx ; // r2 is double&
注意:decltype((variable))(注意是双层括号)的结果永远是引用,
而decltype(variable)的结果只有当variable本身是一个引用时才是引用
线程存储持续性(C++11),如果变量是使用关键字thread_local声明的,则其生命周期与所属的线程一样长。
动态存储持续性:用new,直到delete将其释放;
链接性(linkage)描述了名称如何在不同的单元间共享,链接性为外部的名称可在文件间共享,链接性为内部的名称只能由一个文件中的函数共享,自动变量没有链接性,因为它们不能共享;
虽然该变量只在该代码中可用,但它在该代码块不处于活动状态时仍然存在,因此在两次函数调用之间,静态局部变量的值将保持不变;
volatile 为了改善编译器的优化能力,程序在几条语句中两次使用了某个变量的值,则编译器可能不是让程序查询二次这个值,而是将这个值缓存到寄存器中,这种优化假设以量的值在这两次使用之间不会变化,如果不声明为volatile,则编译器将进行这种优化,将变量声明为volatile,相当于告诉编译器,不要进行这种优化;
mutable说明符解const,即使函数声明为const,只要有mutable,变量也不受这种限制;
const全局变量的链接性为内部的,也就是说,在C++看来,全局const定义,就像使用了static说明符一样。
可以使用static将函数的链接性设置为内部的,使之只能在一个文件中使用;只在这个文件中可见;
extern "C" void spiff(int); // use C protocol for name look-up
extern void spoff(int); // use C++ protocol for name look-up
extern "C++" void spaff(int) // use c++ protocol for name look-up
动态内存由new和delete控制,而不是由作用域和链接性规则控制。通常,编译器使用三块独立的内存,一块用于静态变量,一块用于自动变量,一块用于动态存储;
假设: float * p_fees = new float[20]; //由new分配的80个字节的内存一直保留在内存中,直到使用delete运算符将其释放;但是当包含声明的语句块执行完结后,p_fees指针将消失。
new失败,std::bad_alloc;
定位(placement)new运算符:让您能够指定要使用的位置;1.要包含new头文件;
p1 = new(buffer1) char;
p2 = new(buffer2) int[20]; // new后面跟括号,括号内是可用地址;
定位new,放置语法使用传递给它的地址,它不跟踪也不查找未使用的内存块。
定位new函数不可替换,但可以重载,它至少需要接收两个参数,其中第一个总是std::size_t;
namespace MFC = myth:elements::fire; //名字别名
using MFC::fire;
explicit构造函数:用单一参数调用一个构造函数,其行为类似于从参数类型到类自身类型的转换。
explicit可以指明构造函数不能用作隐式的转换;如果构造函数的声明带有explicit,则它只能用于初始化和显式类型转换; 防隐式转换的,避免含糊错误。
用=进行的初始化可看作是拷贝实始化,一般来说,初始化器的副本会被放入待初始化的对象,但是,如果初始化器是一个右值,这种拷贝可能被优化掉(取消),而采用移动操作,省略=会将实始化变为显式初始化。显式初始化 Data d1{15} 也称为直接初始化。
默认情况下,应该将单参数的构造函数声明为explicit,除非你有很好的理由。否则注释原因。
explicit在定义中不能重复;即外面不能带explicit.也可以用于无参或多参;
一个函数不仅在类中声明,还在类中定义,那么它被当作内联函数处理;
class data{
int month() const { return m;} //const指出这些函数不会修改Date的状态
} 试图在const函数中改变成员的值的操作都会出错
const定义不能省,无论在类内还是类外;
class Date{
public:...
private:
mutable bool cache_valid;
mutable string cache;
};
d.add_day(1).add_maon(1); //连续操作就要返回data&;
这时要返回*this;表达式*this引用就是调用此成员函数的对象;非static成员函数中,this指向调用它的对象的指针;this被当作一个右值,因此我们无法获得this的地址或给他赋值;
如果我们希望引用类的一个公共成员,而不是某个特定对象的成员,应该使用类名后接::的限定方式。
int X::sm {7}; //X的静态成员。
成员指针语法: int(S::*) pmf() {&S::f} ; //X的成员f;课本中的,但是编译器不认
int (B::*pFun)(int); //声明指向类成员函数的指针pFun; 这个编译器认;
在多线程代码中,static数据成员需要某种锁机制或访问规则来避免竞争条件。
语言必须为用户提供定义小的具体类型机制,这种类型称为具体类型(concrete type)具体类,以区别抽象类和类层次中的类;
若不修改对象值,定义为const成员函数;
X& operator = (const X&);拷贝赋值运算;
X& operator = (X&&); 移动赋值运算符;
类不变式(class invariant),就是当成员函数(从类外)被调用时必须保持的某些东西。
注意一个问题:
Vector* f(int s)
{
return new Vector(s+s);
}
void g(int ss)
{
Vector* p = f(ss); //这里没有new,但是,返回的也是new.所以可以用delete
delete p;
}
构造函数调用其基类的构造函数,然后调用成员的构造函数;执行自身的函数体,析构相反;
构造函数按声明顺序(而非初始化的顺序)执行成员和基类的构造函数;
struct X{ X(int);};
struct S2{ X x;};
S2 x1; //err.没有为x1.x提供值
S2 x2{1}; //x2.x用1进行初始化 ;注意这,类的成员,直接用初始化的值给了
void C::push_back(cons X& a)
{
new(p) X{a};
}
void C::pop_back(){
p->~X(); //显示调用析构函数
}
通过声明构造函数=delete或private,就可以阻止其析构;
可以显示的销毁,但不能隐式的销毁;
class Nonlocal{
...public: destroy(){this->~Nonlocal();} //中转;
private:
~Nonlocal(); //把析构声明为私有
};
delete p;/err.出错。
p.destroy(); //正确
}
一般析构函数是virtual,因为指向基类的指针可以指向派生类,如果不是虚的,就会导致资源泄漏;
一般是用基类指针来调用派生类以求通用性,这时候,虚析构函数就起作用了;
如果一个类有私有的数据成员(不是静态的),它就需要一个构造函数来进行初始化;
构造函数常用来建立类的不变式并获取必要的资源;只要是在构造对象的地方,我们都可以用{}初始化语法为构造函数提供参数;{}初始化有时也称通用初始化,这种语法可以用在任何地方。{}可以进行逐成员初始化;接受单一std::initializer_list参数的构造函数被称为初始化器列表构造函数(initializer-list).一个初始化器列表构造函数使用一个{}列表作为其初始化值来构造对象。一个初始化器列表的长度可以任意,但它必须是同构的。就是说,类型一样:{"bjan","bs"},{192,22,22}必须同类,第么全是字串,要么全是数字;
可以接受一个initializer_list
为了显示使用一个initializer_list,必须#include头文件
成员的构造函数按成员在类中声明的顺序调用,而不是按成员在初始化器列表中出现的顺序。
class Person{
string name;
string addr;
}
Persoon::Person(const string&n, const String&a):name{n}{
}
{
address = a;
}
注意这个;addr首先被初始化为空字串,然后用a的副本。
使用一个成员风格的初始化器,但用的是类自身的名字(也是构造函数名),它会调用另一个构造函数,作为这个构造过程的一部分,这样的构造函数称委托构造函数(delegating constructor).有时也称为转发构造函数。forwording constructor).注意不能同时显式和委托初始化一个成员。X():X{42},a{56}{...} 出错。X{42}是委托,但又初始化a;
委托者执行完才表明构造完成,仅仅被委托者执行完成是不够的;
在少数情况下,在类内声明中初始化static成员也是可能的,条件是static成员必须是整形或枚举类型的const.或字面值类型的constexpr.且初始化器必须是一个常量表达式(constant-expression)
class P{
static const int Pi = 3.14;
成员常量的主要用途是为类声明中其他地方用到的常量提供符号名称;如:
template
class Fixed {
public:
static constexpr int max = N;
private:
T a[max];
};
当我们需要从a到b传输一个值的时候,通常有两种逻辑上不同的方法:拷贝x=y,移动:将x变为y的旧值,y变为某种移出状态(moved from state).对我们最感兴趣的情况(容器,移出状态就是空)。
一般来说,移动操作不能抛出异常,而拷贝操作则可以。
编写一个移动操作时,应该将源对象置于一个合法的但未指明的状态,因为它最终会被销毁,而析构函数不能销毁一个处于非法状态的对象,而且,标准库算法要求能够向一个移出状态的对象进行赋值(使用移动或拷贝)。因此,设计移动时不要让它抛出异常,并令源对象处于可析构和赋值的状态。
类X的拷贝构造函数有二种:X(const X&)加拷贝赋值运算符: X& operator(const X&)
一个拷贝构造函数应该创建给定对象的副本。不应该修改它。
array
首选创建一个副本,然后交换其内容,可以用这种方法提供强保保障;swap(tmp,*this);
拷贝构造函数可以用派生类初始化基类;拷贝操作必须满足两个准则:等价性和独立性;
默认拷贝语义是逐成员拷贝,因此一个默认拷贝操作会拷贝指针成员,但不会拷贝指针指向的对象。
对于共享子对象生命周期相关的问题,我们可以通过引入某种形式的垃圾收集机制来解决:
struct S2{
shared_ptr
};
写前拷贝:在共享状态被修改之前,副本其实并不真的需要独立性,可以推迟共享状态的拷贝,直至首次修改副本前才真正进行拷贝。
类定义了一个bool shared;变量记录是否要修改。还定义了clone(),rep;
if(shared){ rep = clone(); shared = false; //不再共享;}}
如何防止切片:1.禁止拷贝基类,delete拷贝操作;2.防止派生类指针转换为基类指针,将基类声明为private或protected基类。
移动赋值背后的思想是将左值的处理与右值的处理分离,拷贝赋值操作和拷贝构造函数接受左值,而移动赋值操作和移动构造函数则接受右值。对于return值,采用移动构造函数;
move()是一个标准库函数,它返回其实参的一个右值引用,move(x)意味着给我一个X的右值引用,即std::move(x)本身不移动任何东西;它只是允许用户移动x。它返回的是实参的一个右值引用。可能将move()改名为rval()更好,习惯了。
-------
struct S{
string a;
int b;
};
S f(S arg){
S s0{}; //默认构造{“”,0}
S s1 {s0}; //拷贝构造
s1 = arg; //拷贝赋值
return s1 ;// 移动构造
}
可以声明希望由编译器自动生成一个;Y(const string& s); //用一个字串生成Y
Y() = default; //显示生成默认;二个构造,一个自定义,一个自动生成;
在构造函数中建立不变式;在拷贝和移动操作中保持不变式;在析构中处理清理工作;
如果一个类有一个指针成员,就要对默认的拷贝和移动操作保持警惕。
可以使用delete删除任何我们能声明的函数;
Foo* clone(Foo*) = delete;
Shape* ps2 = clone(ps); //没问题
Foo* pf2 = clone(pf); //错误:clone(Foo*)己经被删除;
另一种是删除不需要的类型转换:Z(int) = delete; //不能用整数初始化;
void * operator new(size_t) = delete; //这个类型没办法用new了;
注意使用=delete删除的函数与只是未声明的函数的差别:前一种情况下,编译器会发现程序试图使用delete删除的函数并报错;后一种可能用默认的代替。
如果一个类有虚函数,它就需要一个虚析构函数;如果一个类没有构造函数,就可以进行逐成员初始化;如果一个类是一个容器,为它定义一个初始化器列表构造函数;
按声明顺序初始化成员和基类,如果一个类有一个引用成员,它可能需要拷贝操作(拷贝构造和拷贝赋值),在构造函数中优先选择成员初始化而不是赋值操作;
假设b和c的类型是complex,则b+c等价于b.operator+(c).
c++语法规定符号{}只能表示初始化器,并且只能位于赋值运算符的右侧;
用户无权定义下列运算符: ::作用域解析 .成员选择 .* 通过指向成员的指针访问成员
具名“运算符”,负责报告其运算对象的某些基本情况,也不能重载:
sizeof,alignof(对象的对齐方式),typeid(对象的type_info).
此外,我们用operator""表示用户定义的字面值常量,operator T()表示向T的类型转换;
aa@bb可以理解成aa.operator@(bb)或者operator@(aa,bb).
对于一元运算符,不管它是前置还是后置的,我们既可以用不接受任何参数的非static成员函数定义它,也可以用接受一个参数的非成员函数定义它。对于任何一个前置运算符@,@aa可以理解成a.operator@()或者operator@(aa)非成员。
对于一元后置运算符@,aa@可以理解成aa.operator@(int) 或者opearator@(aa,int).非成员
运算符预置:当作用于类的对象时,运算符=(赋值),&(取地址),自带预置的含义;
枚举类型是用户自定义类型,我们可以为其定义运算符;
表达式中通过构造函数显式或者隐式构造的对象是自动对象,一有机会就会销毁它。
在.或者->的左侧不会执行隐式的用户自定义类型转换,即使.本身是隐式的也是如此;
例如:3.operator+=(z); 3+=z; //err 3不是对象.
运算符是容器vector,智能指针,迭代器以及承担资源管理功能的类的设计关键;
[] () -> ++ -- new delete ;
const int& operator[](const string&) const;
int& ooperator[](const string&);
struct Assoc{
vector
const int& operator[](const string &) const;
int& ooperator[](const string&);
重载()运算符:函数调用;函数调用可以看成是一个二元运算。
struct Action{
int opeartor()(int);
}
Action act = act(2);
调用运算符(call operator)又称为应用运算符(application operator).
运算符()最直接也是最重要的目标是为某些行为类似函数的对象提供函数调用语法,其中,行为模式与函数类似的对象称为函数对象(function-like object)或者简称函数对象。
operator()()必须是非static成员函数,函数调用运算符通常是模板;
-〉箭头解引用运算符可以定义成一个一元后置运算符;重载->的主要目的是创建“智能指针”,即行为与指针类似的对象;
operator++() 前置++ // operator++(int) 后置++;(作为成员函数重载)
成员函数operator new()和operator delete()是隐式的static成员,因此,它们无法使用this指针,也不能修改对象的值。
int operator"" Mb(unsigned long long m) {
return m * 1024;
}
实现继承:通过共享基类所提供的特性来减少实现工作量;
接口继承:通过一个公共基类提供的接口允许不同派生类互换使用。接口继承常被称为运行时多态,或动态多态;
模板所提供的类的通用性与继承无关,常被称为编译时多态或静态多态;
类层次中析构函数通常应该是virtual的。
给定一个Base*类型的指针,它指向的对象的真正派生类型是什么?C++提供了四种基本解决方法:
[1]保证指针只能指向单一类型的对象
[2]在基类中放置一个类型域,供函数查看
[3]使用dynamic_cast。
[4]使用虚函数
虚函数机制允许程序员在基类中声明函数,然后在每个派生类中重新定义这些函数。
虚成员函数有时也称方法(method),默认情况下,覆盖虚函数的函数自身也变为virtual的,我们在派生类中可以重复关键字virtual,但这不是必须的,建议是不重复virtual,如果你希望明确标准覆盖版本,可以使用override.显然为了实现多态性,编译器必须在每个Employee类的对象中保存某种类型信息,并利用它选择虚函数print()的正确版本,在一个典型的C++实现中,这只会占用一个指针大小的空间,常用的编译器实现技术是将虚函名转换为函数指针表中的一个索引。这个表通常称为虚函数表(the virtual function table),或简称为vbtl.每个具有虚函数的类都有自己的vbtl,用来标识它的虚函数。从构造函数或析构函数中调用虚函数能反映出部分构造状态或部分销毁状态的对象,因此,从构造函数或析构函数中调用虚函数通常是一个糟糕的主意。
=0纯虚函数,必须被覆盖;override: 函数要覆盖基类中的一个虚函数;
final:函数不能被覆盖;注意加override的位置:
void g(int) override; //和const加的位置一样,参数类型,要符合重载的类型;
void f(int) const noexcept override; //正确;override只能放在最后;
说明符override不是函数的一部分,而且在类外定义中不能重复(只在类定义的时候声明就好)
注意:override,final不是一个关键字,它只是所谓的上下文关键字,放在函数后面说明一下;这样做是为了以前的代码兼容性;final也是放在函数后面,这个函数不能覆盖;
class For_statment final:public Node{}//在类名后加上final,我们可以将一个类的所有virtual成员函数声明为final;
可以用using声明将一个函数加入作用域中:
struct D2:Base{
using Base::f;
void f(double);
};
可以使用using 从基类引入名字,然后函数会根据参数解析调用相应的函数;
声明继承构造函数:
template
struct Vector:std::vector
using vector
如果在派生类中继承了构造函数,又定义了需要显示初始化的新成员变量,注意;
struct B1{
b1(int){}
};
struct D1:B1{
using B1::B1;//隐式声明D1(int)
string s;
int x;//这个值
};
D1 d{4}; // d.x未初始化,原因是继承的构造函数等价于只初始化基类的构造函数;
D1 e; //err D1没有默认构造函数;
下面的代码好理解一些,和上面的等价:
struct D1:B1{
D1(int):B1(i){}
string s;
int x;
};
返回类型放松:覆盖函数的类型必须与它所覆盖的虚函数的类型完全一致,C++对这一规则提供了一种放松规则,返回类型B&可放松为D&,这一规则有时称为协变返回规则(covariant return).这一规则适合返回类型是指针或引用的情况,但不能是unique_ptr;
由于new_expr()和clone()这样的函数是virtual的且间接构造对象,它们常被称为虚构造函数;这种函数都是简单地使用了普通构造函数来创建一个适合的对象;
为了创建一个对象,构造函数并不完全是一个普通函数,因此,构造函数不能是virtual的,而且,一个构造函数并不完全是一个普通函数。因此,你不能接受一个构造函数的指针并将其传递给一个对象创建函数;可以通过定义一个函数来调用构造函数并返回构造的对象。
抽象类就是要作为通过指针和引用访问的对象的接口(为保持多态行为)。因此,对一个抽象类来说,定义一个虚析构函数通常很重要,抽象类提供的接口不能用来创建对象,因此抽象类通常没有构造函数;
抽象类所支持的设计风格称为接口继承(interface inheritance),它与实现继承(implementation inheritance)相对,实现继承由带状态或定义了成员函数的基类所支撑的,两种风格组合使用是可能的。
如果是protected的,仅可以被所属类的成员函数和友元以及派生类的成员函数和友元函数所使用。
一个高效的非侵入性链表通常要求数据结构掌握元素的动态,若一个链表不要求修改其元素(例如,要求元素类型有链接域),那么它就是非侵入性的(nonintrusive).
使用返回类型后置语法: auto List
访问基类: class X : public B{} //公有继承 可以省略,类默认为私有继承,结构为公有
基类的访问说明符控制基类成员的访问以及从派生类类型到基类类型的指针和引用转换。
如果B是一个private基类,其公有和保护成员只能被D的成员函数和友元函数使用,只有D的友元和成员可以将一个D*转换为一个B*。
struct B{};
class D1:public virtual B{}
class D2:public virtual B{} //都从B继承,虚基类
class D12:public D1,private D2{///} //多重继承
成员指针是一种类似偏移量的语言构造,允许程序员间接引用类成员。 ->* 和 .*
使用->*,可以访问一个类成员,其名字保存在一个成员指针中,pt::p->*ptm.
成员指针不能赋予void*或任何其他普通指针。
函数成员指针:using Pstd =void (std_interface::*)(); //成员指针类型;
用类型声明变量: Pstd ps = &std_interface::suspend; //指向类函数成员suspend()的指针。
std_interface * p ; //指向对象的指针;
p->suspend() ; //正常通过指针调用;
p->*s(); //通过成员指针调用;
获得成员指针的方法是对一个完全限定的类成员名使用地址运算符&,如:&std_interface::suspend;我们可以使用如X::*的声明
成员指针更像一个结构内部的偏移量或数组内的下标;
成员函数的应用场景是在我们需要引用一个函数,但又不知道其名字的时候。
一个static成员不关联某个特定对象,因此static成员的指针就是一个普通指针;
class Task{
static void schedule();
};
void (*p)() = &Task::schedule; //ok. //前面是普通函数指针语法
using pf = void(c::*)(int); //C的成员函数指针,接收int.
using pm = const char* C::*; //C的char*数据成员指针
pf = &C::print;
pm = &C::val;
调用:
C& z1;
(z1.*pf)(2);
z1.*pm = "csn" ;
通过指针或引用来访问多态对象;使用抽象类,以便聚焦于清晰接口的设计应该提供什么;
使用override显示说明覆盖,使用抽象类说明接口,使用抽象类保持实现细节和接口分离;
有虚函数也应该有虚析构函数;确实有需要才使用protected,不要将数据成员声明为protected;
二义性,假设基类都有一个同名函数Debug_info di1 = Satellite::get_debug();
Debug_info di2 = Displayed::get_debug();
上面是显示的消除二义性,但是,比较繁琐,解决此问题的方法是在派生类中定义一个新函数,class Comm_sat:public Statllite,public Displayed{
Debug_info di1 = satellite::get_debug();
Debug_info di2 = Displayed::get_debug();
return Merge_info(di1,di2);
}
virtual void win_draw() = 0; //强制派生类必须覆盖;
对象的各个部分必须共同享同一个Storable,我们应该通过把基类声明成virtual来避免重复,因为派生类的每个virtual基类都是同一个(共享)对象表示的。class Transmitter:public virtual Storable{};
为虚基类提供一部分实现(但非全部实现)的类称为混入类(mixin).在运行时检测对象类型最明显也是最有用的操作就是类型转换,若对象确为预期类型,该操作应该返回合法指针,否则返回一个空指针;
if(auto pb = dynamic_cast
}
else{
}
以上代码把基类指针转换成类层次的lval_box*,如果转换成功,就OK。
在运行时使用类型信息通常称为“运行时类型信息”,RTTI。从基类到派生类称向下转换;从基类到兄弟的转换,则称交叉crosscast转换;dynamic_cast的真正用武之地是编译器无法确定类型转换正确性的情形,在此情况下,dynamic_cast
向void*进行dynamic_cast可以用来确定一个多态类型的对象的起始地址:
void* pb2 = dynamic_cast
对于一个引用r,dynamic_cast
void(lval_box& r){
lval_slider& is = dynamic_cast
}
dynamic_cast可以从一个多态虚基类转换到一个派生类或是一个兄弟类,static_cast则不行,因为它不检查要转换的对象。对于一个void*所指向的内存,编译器不能做任何假设,这意味着dynamic_cast不能将一个void*转换为其他类型,因为dynamic_cast必须探查一个对象的内部来确定其类型。如果这时要转换成类对象,就需要用static_cast.
强制去除const或volatile,则需要使用const_cast.但即使是这样,只有当对象最初不是声明为const(或volatile)时,转换结果才能安全使用。要避免在构造和析构过程中调用虚函数;
typeid可以满足这种需求,它生成一个对象,表示它所处理的类型。
typeid(expr)必须指向一个完整定义的类型,如果expr的值为nullptr,则typeid(expr)会抛出一个std::bad_typeid异常,typeid(*p); //获得p指向对象的类型;typeid(p); // 获得指针的类型,如果typeid()的运算对象不是多态类型或者不是一个左值,则结果在编译时即可确定;无须运行时对表达式求值;
type_info类定义看起来像是这样的:
class type_info{
bool operator == (const type_info&) //可以比较
bool operator != (coonst type_info&)//不等
size_t hash_code()
const char* name();名字
bool before(const type_info&) //定义了序,与继承关系间并不存在关联;
};
如果使用了动态链接,很可能有重复的type_info对象。应该使用==比较type_info对象是否相等,而不是比较type_info指针;
用得最多的就是用于诊断信息:
Component *p
##debug typeid(*p).name();
}
type_index是一个标准库类型,用于比较和哈希type_info对象;
C++模板机制允许在定义类,函数或类型别名时将类型或值作为参数;
概念:模板为实参设定的一组要求;
标准库提供了basic_string,它很像模板化了的String,在标准库中,string是basic_string
using string = std::basic_string
map
只有一个成员函数被使用时才会为其生成代码(模板);
当定义一个类模板时,通常一种好的方式是,先编写调试一个特定类,如String,然后再将其转换为一个模板,如String
template instantiation,一个模板针对某个特定模板实参列表的版本被称为特例化(specialization)
可以将“C必须为一个容器”看作一个谓词,它接受一个类型参数C,若C是一个容器则返回true,否则返回假,如Container>()为真,我们把这种谓词称为概念。用来推理对模板实参的要求。
什么样的条件才是容器? T重载[],有size(),必须有value_type元素类型;
template
class Buffer;
Buffer
编译器可以对常量求值;
注意模板在处理派生类时,不能转换;要注意这点;
Shape* p {new Circle(p,100)};
vector
vector
模板参数T只能被模板自身访问,如果其他代码使用元素类型,目前我们能用的方法只有提供一个别名;using value_type = T;
通过成员别名来表示的类型名称常被称为关联类型(associated type).
模板类的静态成员:
static constexpr Point p{100,250};
static int m3;
//初始化:
template
template
int X
一个static成员只有真被使用时才需要定义:
int* p // int* p = &X
int* p = &X
成员枚举可以在类外定义,但在类内声明中必须给其出其基础类型;
成员模板不能是virtual的:
template
类模板的友元:friend Vector
后面的<>是必须的,它清楚的指出友元函数是一个模板函数,如果没有<>,友元函数将被假定是非模板函数,有了上述定义,乘法运算符即可直接访问Matrix和Vector中的数据。
友元类:
friend class C4; //正确,引入了一个新的类C4.
template
class my_other{
friend T; //friend class T; //err.class不用。应 friend T;
};
友元不能继承,不能传递;
推断:
template
void f(const T*,U(*)(U));//第二个参数为函数指针;
int g(int);
void h(const char* p)
{
f(p,g);//T是char,U是int;
}
模板实参推断过程中是区分左值和右值的,X类型的左值被推断为一个X&,而右值被推断为X。
如果两个函数模板都可以调用,且其中一个比另一个更特殊化,则接下来的步骤只考虑最特殊化的版本。普通函数和一个模板特例化一样好,普通函数优先;如果无匹配,则出错;
如果推断有二义性,可以通过显式限定来消解二义性; max
template
{
int i;
std::vector<int> ivec;
std::vector
T type;
std::vector
std::vector
};
/* 前3个成员变量是不依赖于模板参数,所以是non-dependent name,后3个是dependent name
,直到实例化该模板的时候才会知道到底是什么类型。*/
//下面来讨论typename的第二种用法。现在假设我们有一个类如下:
template
{
T::iterator *iter; ...
};
/* 我们可能本意是想定义一个迭代器对象,例如我们如果用vector
class cType
{
static int iterator;
};
/* 那么T::iterator *iter会被编译器解释为两个数相乘。事实上,C++编译器会采用第二种解释方法
,即使iterator的确是一个类型名。
为了避免这种矛盾,当我们适用qualified dependent name的时候,需要用typename来指出这是一个
类型名.即: */
template
{
typename T::iterator *iter;
typedef typename T::iterator iterator; //定义了Y::iterator类型名称
...
};
//typename 指出下面紧跟着的名称是一个类型
------------------------------------------------
template
typename Iter::value_type mean(Iter first,Iter last);
代入后,假设Iter为int*, 则为: int*::value_type mean(int*, int*);
//指出value是一个依赖类型名,只有实例化后才知道是什么类型;这个类型作为mean的返回值;
C++不支持模板分别编译,在每个用到模板的编译单元中都#include模板定义;
将大的模板和较严重依赖上下文的模板分开编译;
模板为编译时计算和类型处理提供了一种强有力的机制,可以生成非常紧凑和高效的代码。
类型(类)即可以包含代码也可以包含值;
模板是C++支持泛型程序设计的主要特性,它提供了(编译时)参数化多态。
标准库对一些常见的运算,如plus,multiply提供了对应的函数对象,可用作实参。
std::plus
类型名和模板名首字母大写;
template
constexpr bool Small_size()
{
return N <= 8;
}
//产生函数模板,调用则要加括号:Small_size<1>()
将概念表达为编译时谓词(constexpr函数),并用static_assert(),enable_if<>测试它们
值参数可以是:外部链接的对象或函数的指针或引用;指向非重载成员的指针,空指针
一个指针必须具有&of或是f的形式,才能作为模板实参,其中of是对象或函数的名字,f是函名,指向成员的指针必须具有&X::of的形式。其中of是成员的名字;字符串字面值不能作为模板实参;
char lx2 [] = "BMW3"; X
整数常量必须是一个常量;
在模板参数列表中,一个类型模板参数出现后即可当作一个类型来使用;
template
将比较类型作为一个模板类型参数传递,是STL采用的方式;
函数对象可携带状态: Complex_compare f3{"French",3};
map
传递函数对象的优点:
lambda不能转换为函数对象类型,可以命名lambda,然后使用名字:
auto cmp =[](const string& x, const string& y) const {return x < y;} //命名cmp
map
模板作为实参:
template
class X{
C
特例化:
template<> //指明模板参数特例化版本;
class Vector
template
class Vector
模式中包含模板参数的特例化版本称为部分特例化(partial specialization)与完整特例化相对,其中“模式”指一个特定类型;
最通用的模板定义为主模板;主模板必须在特例化版本前声明;
最特例化版本优于通用其它版本;
只有在需要定义时才必须生成类模板的特例化。特别是,如果只是为了声明类的指针,是不需要实际的类定义;
模板函数,只有当它真正被使用时(调用,取地址),才需要一个函数实现来实例化它。实例化并不意味要实例化所有成员函数;
一个显式实例化请求(explicit instantiation)在语法上就是一个特例化声明加上关键字template前缀(template).
template class Vector
template int& vecot
template int convert
模板声明以template<开始,而简单的template则表示一个实例化请求的开始:
禁止隐式实例化: extern template class MyVector
C++语言将模板定义中使用名字分为以下两类:1.依赖性名字:即依赖于模板参数的名字,这类名字在实例化点完成绑定;2.非依赖性名字:
如果被调用函数的实参或形参明显依赖于模板参数,则函数名是依赖性名字。
注意:默认情况下,编译器假定依赖名字不是类型名,因此,为了使依赖性名字可以是一个类型,你须用关键字typename显示说明。如:
template
void fct(Container& c){
{
Container::value_type v1 = c[7] ; //语法错误,编译器默认假设依赖名不是类型名
typename Container::value_type v2 = c[9]; //ok.显示说明value_type是类型
auto v3 = c[11] ; //也正确,让编译器推断;
这时,我可以引用类型别名来代替typename的尴尬:
template
using Value_type
template
void fct2(Container& c){
value_type
类似的,命名点号 . ->或::后面的成员模板需要使用关键字template.
class Pool {
public:
template
...
template
void f(Alloc& all)
{
int* p1 = all.get
int* p2 = all.template get
}
void user(Pool& pool){
f(pool)
实参依赖查找:argument-dependent lookup,通常简称为ADL。
避免完全通用的模板可被ADL找到,使用概念或static_assert避免选择不恰当的模板,使用using声明限制ADL触及的范围,恰当地使用->或T::限定来自模板基类的名字;
操纵类和函数这种程序实体的编程通常称为元编程(metaprogramming).
元编程就是元数据和程序设计的组合,一个元程序就是一个编译时计算的代码,它生成运行时使用的类型或函数;
标准库模板conditional是一个编译时选择器,可实现两种方案中的一个,如果第一个实参求值为true,则结果(以成员type的方式给出)是第二个实参,否则,结果是第三个实参。
Conditional
谓词是返回bool值的函数;
void copy(T* p,const T* q,int n)
{
if (std::is_pod
memcpy(p,q,n);
else
for (int i = 0; i != n; i++)
p[i] = q[i];
}
如果在编译时无法获知对象的实际类型,就必须使用类层次;
一个基类指针可用作模板实参来提供运行时多态,一个模板参数可用来指定一个基类接口从而提供类型安全性;C++语言不支持virtual函数模板;
不存在set
当定义一个指针模板时,我们可能希望反映所指对象间的继承关系,成员模板允许我们在需要时指定这种关系。
template
class Ptr{
template
explicit operator Ptr
一个模板的模板参数列表和模板成员不能组合在一起; //模板成员:模板内套模板
模板类可用来为公共实现提供一个灵活类型安全的接口,
template
class Vector
此技术通常可用来提供类型安全的接口,以及将类型转换的工作交给编译器,而不是强制用户编写转换操作;
继承和参数化的组合具有很强的表达力,类型安全,性能以及源代码规模最小化,而类层次和虚函数所提供的扩展性也不会被妥协掉;
类型函数,它接受至少一个类型参数或至少生成一个类型结果,如sizeof(T)。
类型函数不一定像常规则函数,is_polymorphic
template
struct Array_type{
using type= T;
static const int dim = N;
};
using Array = Array_type
Array::type x;
constexpr int s = Array::dim;
---------------------------------
constexpr int on_stack_max = sizeof(std::string);
template
struct Obj_holder{
using type = typename std::conditional<(sizeof(T) <= on_statck_max),Scoped
使用:typename obj
typename obj
另一种:
template
using Holder = typename Obj_holder
Holder
Holder
template
constexpr bool Is_pod(){
return std::is_pod
}
Conditional<(sizeof(int)>4),fun1,fun2>{}(7);创建fun1或fn2,并调用;
可以将萃取理解为返回很多结果的类型函数或是一组类型函数;
Conditional<(std::is_polymorphic
Conditional
constexpr版本即可以用于编译时求值,也可以用于运行时求值,模板(元编程)版本则只能用于编译时;
标准库提供了enable_if(type_traits)
Enable_if
C++程序通常使用类来实现类描述,而把结构限制为只表示纯粹的数据对象(POD)。
std::cout.setf(std::ios_base::fixed,std::ios_base::floatfield);
std::cout.precision(3);
std::streamsize prec = std::cout.precision(3);
std::ios_base::fmtflags orig = std::cout.setf(std::ios_base::fixed);
std::cout.setf(orig,std::ios_base::floatfield);
-------------------------------------------------
using std::cout;
using std::ios_base;
ios_base::fmtflags orig = cout.setf(ios_base::fixed,ios_base::floatfield);
std::streamsize prec = cout.precision(3);//saved..
//use std;;
..
cout.setf(orig,ios_base::floatfield);
cout.precision(prec);
构造函数:成员函数私有成员:string m_company //加m 或是末尾加下划线
string company_ //私有数据成员;
如果构造函数可以接受一个参数,则可以Bozo truby = 32; //可以这样初始化;
即接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值;
this指针指向用来调用成员函数的对象,this指向调用对象,如果方法需要引用整个调用对象,则可以使用表达式*this。
class Bakery{
private:
enum {Months = 12};
doouble const[Months];
用这种方式声明枚举并不会创建类数据成员,所有对象中不含枚举,Months只是一个符号名称,遇到他时,编译器用12替换它;
C++提供了另一种定义常量的方式:使用static:
static const int Months = 12;
传统枚举存在的一些问题:如果有二个Small;
enum egg{Small,Medium,Large,Jumbo};
enum t_shirt{Small,Medium,Large};
将无法通过编译,位于相同的作用域内,冲突;C++引入了新枚举;
enum class egg{Small...
enum class t_shirt{Small...}
t_shirt roollf = t_shirt::Large;
声明底层类型:enum class:char pizza{Small,...
t4=t1+t2+t3;
t4 = t1.operator+(t2.operator+(t3));
-------------------------------------------
#include
ofstream fout;
fout.open("save.txt");
fout << "xxxx";
重载负号: Vector Vector::operator-() const {...}
#include
srand(time(0))
转换函数必须是类方法,转换不能指定返回类型,转换函数不能有参数;
operator double(); //返回double.不必再声明一次;
C++11可以显示声明转换函数: explicit operator int() const;//C++11支持将关键字
explicit用于转换函数,允许显式转换,但不允许隐式转换;
1.strlen(s).2.new char[len+1] 3.std::strcpy(str,s);num_string++
方法的行为取决于调用该方法的对象。
通常编译器处理虚函数的方法是:给每个对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,这种数组称为虚函数表,虚函数表中存储了为类对象进行声明的虚函数地址。基类对象包含一个指针,该指针指向基类中所有虚函数的地址表,派生类对象将包含一个指向独立地址表的指针,如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址,如果派生类没有重新定义虚函数,该vtbl将保存函数原始版本。调用虚函数时,程序将查看存储在对象中的vtb地址,然后转向相应的函数地址表。每个对象,新增一个指针;每个类,创建一个虚表(数组),对于每个函数调用,需要执行一项额外的操作,即到表中查找地址。
如果重新定义的继承的方法,应确保与原来的原型完全相同,但如果返回值类型是基类引用或指针,则可以修改为指向派生类的引用或指针(这种例外是新出现的)。这种特性被称为返回类型协变(convariance of return type),因为允许返回类型随类类型变化而变化。(说白了,就是如果只是返回类型不同,基类返回基类,派生类也可以返回派生类,如果只是返回类型有基类和派生类的差别,也算是重载;否则算隐藏基类版本)。
复制构造函数调用的时机:
1.新对象初始化为一个同类对象;2.按值将对象传递给函数;3.函数按值返回对象;4.编译器生成临时对象;
赋值运算符用于处理同类对象之间的赋值,不要将赋值与初始化混淆了,如果语句创建新的对象,则使用初始化,如果语句修改己有对象的值,则是赋值;
对于基类,即使它不需要析构函数,也应提供一个虚析构函数;
并不总是可以返回引用,临时对象消失,在这种情况下,应返回对象,以生成一个调用程序可以使用的副本。
blips = snips; // 将调用blips.operator=(snips);
派生类引用不能自动引用基类对象,除非有转换构造函数;另一种方式是定义一个用于将基类赋给派生类的赋值运算符;
valarray
valarray
方法:operator[],size(),sum(),max(),min();
初始化的顺序为它们被声明的顺序,而非初始化列表中的顺序;
如果是私有继承,不能隐式向上转换。注意;
使用using重新定义访问权限:将函数调用包装在另一个函数调用中,即使用一个using声明来指出派生类可以使用特定的基类成员,即使采用的是私有派生。如
class Student:private std::string,private std::valarray
public :
using std::valarray
虚基类使得从多个类(它们的基类相同)派生出的对象只继承一个基类对象。
class Singer:virtual public Worker{...};
C++在基类是虚的时候,禁止信息通过中间类自动传递给基类,有虚基类的构造函数应该是:
SingingWaiter(const Worker& wk,int p = 0, int v = Singer::other):
Worker(wk),Waiter(wk,p),Singer(wk,v){}
对于虚基类,显式调用构造函数Worker(const Worker&),这是合法的,对于虚基类,必须这样做,但对于非虚基类,则是非法的;调用的问题,如果调用基类的show.有二个,可以使用作用域解析运算符来澄清编程者的意图:
SingingWaiter newhire("Elise Hawks",2005,5,spo);
newhire.Singher::show(); //用了作用域解析符;
模板不是函数,它不能单独编译,模板必须与特定的模板实例化请求一起使用。
泛型标识符type,称为类型参数,
template
称为非类型参数(non-type)表达式参数,假设有下面的声明;ArrayTP
模板也可以有默认参数: template
具体化template<> class T
template
class bate{
template
class hold;
hold
};类声明完了,
在类外定义的语法:
template
template
class bate
这就会出现双重template;
将模板用作参数:template class Thing>
class Crab
Crab
// template
class King{....};//Thing
--------------------------
template <template
/正常是template
class Crab{
private:
Thing
Thing
实例化使用:
Crab
友元函数不是成员函数.
模板类的约束模板友元函数;
1.使用前声明: template
2.类中定义:template
friend void report<> (HasFriendT &);
模板模板参数(template template parameter),Grid
注意其中int出现了两次,必须指定Grid和vector的元素类型都是int。
如果写成:
Grid
一个模板模板参数。首先要知道作为参数的模板的原型vector:
template
class vector
{...};
然后就可以定义:
template
Container=vector>
class Grid
{
public:
//Omitted for brevity
Container
};
使用的时候,与一般的没有什么区别:
Grid
注意:不要拘泥于它的语法实现,只要记住可以使用模板作为模板的一个参数。
模板类声明友元有3种:
1.非模板友元;就是普通的友元函数,生成的所有实例,这个都是友元;
2.约束模板友元,即友元的类型取决于被实例化时的类型;
3.非约束模板友元,友元的所有具体化都是类的每一个具体化的友元;
通过在类内部声明模板,可以创建非约束友元函数,即每个函数具体化是每个类具体化友元.
template
class ManyFriend{
template
};
具体化匹配:void show2
(ManyFriend
int main(){
MaynFriend
show2(hfil1,hfil2); //注意:友元模板不是类成员,只要想到这点,就会降复杂度
模板别名:typedef std::array
2.template
使用:array
二维数组指针定义:
{
int a[4][5];
int (*p)[5]=a;
}
//枚举符号:
class TV{
public:
enum {Off,On};
void onOff(){state = (state==On));
private:
int state;
};
对类进行嵌套通常是为了帮助实现另一个类,并避免名称冲突;
#include
1.try{fun();} 2.catch(const char* s){} 3.fun(){throw "xxx";}
C++11支持的异常规范,double marm() noexcept; //marn() doesn't throw an exception
和const,override一样,有的认为有必要,可以知道函数不会引发异常而助编译器优化,通过这个声明,编写函数的程序员相当于做出了承诺;
C++11抛弃了98的定义,98的定义
double harn() throw(); //不抛异常 double harm(double a) throw(bad_thing);//可能抛出bad_thing异常;
栈解退(unwinding the stack):C++通过将信息放在栈中来处理函数调用,程序将调用函数的指令的地址(返回地址)放在栈中,当被调用的函执行完毕后,程序将使用该地址来确定从哪里开始继续执行。函数调用将函数参数放到栈中,在栈中,这些函数参数视为自动变量,结束时,程序流程跳到被调用存储的地址处,同时栈顶元素被释放.假设函数由于异常而终止,则程序也将释放栈中内存,但不会在释放栈的第一个返回地址后停止,而是继续释放栈,直到找到一个位于try块中的返回地址。随后控制权转到块尾的异常处理程序,而不是函数调用后面的第一条语句,这个过程称栈解退。
注意:引发异常时编译器总是创建一个临时拷贝,即使异常规范和catch块中指定的是引用;
class problem{};
void super()throw(prooblem){
probllem oops;
throw oops;
try{super();}catch(probem & p){ //p指向oops的副本,而不是引用。
== throw problem();
#include
try{}catch(std::exception& e) { cout << e.what() << endl;}
C++标准提供了一种在失败时返回空指针的new;
int* pi = new (std::nothrow) int;
int* pa = new (std::nothrow) int [550];
if pa ==0 err.
未捕获异常不会导致程序立刻异常终止,先会调用terminate(),terminate()调用abort().
exception-> set_terminate();
如果函数引发了其异常堆满中没有的异常,情况将如何?程序调用unexpected()函数;这个
函数将调用terminate(), 可修改 set_unexpected();
3种方法支持RTTI.
1.dynamic_cast;使用一个指向基类的指针来生成一个指向派生类的指针,否则,返回0;
2.typeid.
3.type_info.结构存储了有关特定类型的信息;
RTTI只适合于包含虚函数的类;
typeid运算符能够确定两个对象是否为同种类型,类似sizeof(),接受类名,结果为对象的表达式;返回
type_info对象的引用;
比较 typeid(Magnificent) == typeid(*pg) //如果pg为空,引发bad_typeid异常;
包含.name;
------
Grand *pg; // 基类
Superb* ps ; //直接派生类;
if(ps = dynamic_cast
ps->Say(); // 这样好,应该避免用typeid判断;
如果能转成功,就调用方法;
理解:派生类可以指向派生类;dynamic_cast也只是把基类指针作为指针,能不能转换,还是要看源对象到底是哪个,如果是派生类源对象,只是指针是基类的,这时候,可以转换。但如果派对象就是new的基类对象,dynamic_cast成派生类是不行的。上面的例子,就是用一个基类的指针,指向了随机生成的基类或派生类对象,最后转换成SuperB*的时候,基类指针转换失败了,nullptr,而Super*成功了,因为本身源对象就是,只是用基指针指了它,Magnificent也成功了,因为基类的指针可以指向派生类,源对象也是,他有Speak()函数;而源类是基类的,返回nullptr.提示失败。因为基类不可能有Say()方法;//要有虚函数;
const_cast运算符改变值为const或volatile
High bar;
const High* pbar = &bar;
High* pb = const_cast
dynamic_cast运算符用于将派生类指针转换为基类指针,主要用途是确保可以安全调用虚函数;
string::npos是字符串可存储的最大字符数;智能指针是行为类似于指针的类对象。对象过期时,自动调用析构函数删除所指向的内存;
unique_ptr
shared_ptr
为了避免浅复制带来的多次删除,建立所有权概念,对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的构造函数会删除该对象,然后,让赋值操作转让所有权,unique_ptr更严格;而shared_ptr,创建智能更高的指针,跟踪引用特定对象的智能指针数,引用计数,赋值加1,过期减1,最后一个过期调用delete.
auto_ptr
程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做,如果源unique_ptr将存在一段时间,编译器将禁止这样做;
使用new分配内存时,才能使用auto_ptr,shared_ptr,使用new[]分配内存时,不能使用它们。
std::unique_ptr
如果程序不需要多个指向同一个对象的指针,则可以使用unique_ptr,如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。
在unique_ptr为右值时,可将其赋给shared_ptr;
Boost库提供的scoped_ptr,与unique_ptr类似;
函数对象是类似于函数的对象,可以是类对象或函数指针。各种STL容器模板都接受一个可选的模板参数,该参数指定使用哪个分配器对象来管理内存。
template
STL文档使用[p1,p2)来表示从p1到p2(不含p2)的区间。
for_each(books.begin(),books.end(),ShowRecive);
Random_shuffle(x.begin(),x.end()); #include
sort(x.begin(),x.end()) //须重载operator <()函数;
模板使得算法独立于存储的数据类型,而迭代器使算法独立于使用的容器类型;
STL定义了5种迭代器,并根据所需要的迭代器类型对算法进行了描述。
不同的算法对迭代器的要求不同,如查找,只需要定义++,不需要写。随机访问,要定义operator+,如i+10
1.输入迭代器,2.输出迭代器,3,正向迭代器,4.双向迭代器,5.随机访问迭代器
InputIterator,通过重载++来实现的;并不能保证第二次遍历容器时,顺序不变;单行,不依赖前一次遍历时的迭代器值;单向迭代器,递增,不倒退;
输出迭代器,和输入相似,只是解除引用让程序能修改容器的值,但不能读取;
正向迭代器:使用++来遍历容器,每次沿容器向前移动一个元素;总是按相同顺序遍历一系列值,将正向迭代器递增后,仍可对前面的迭代器值解引用,即可读,又可写;
双向迭代器:同时支持递减运算符;
RandomAccessIterator,具有双向迭代器的所有特性,同时添加了随机访问。
STL文献使用术语concept来描述一系列的要求。
int casts[10]= {1,2,3,4,5};
vector
copy(casts,casts+10,dice.begin());
#include
ostream_iterator
out_iter迭代器现代是一个接口,让cout来显示信息,第一个int模板参数指出发送给输出流的数据类型,第二模板指出了输出流使用的字符类型(也可以是wchar_t);第一个cout也可以是文件输出的流;后面的" "是每个元素的分隔符;
*out_iter++ = 15; //相当于 cout << 15 << " ";
copy(dice.begin(),dice.end(),out_iter);
copy(dice.begin(),dice.end(),ostream_iterator
iteratoor头文件还定义了istream_iterator模板,迭代器接口,用于输入模型;
copy(istream_iterator
istream_iterator也使用两个模板参数,第一个参数指出要读取的数据类型,第二个参数指出输入流使用的字符类型;表示从输入流中读取,直到文件结尾,类型不匹配或出现其他输入故障为止。
其它迭代器:reverse_iterator,back_insert_iterator,front_insert_iterator,insert_iterator;
反向:rbegin(),rend() //begin(),end()前加r,反向迭代器;
反向迭代器由于指向超尾,不能解引用,要先递减后,再解引用。
vector
for(ri = dice.rbegin(); ri != dice.rend(); ++ri) cout << *ri << ' ';
back_insert_iterator将元素插入到容器尾部,front_insert_iterator插到容器前端;而insert_iterator
构造函数通过参数指定;这是三个输出容器概念模型;
vector支持back_insert_iterator,不支持front_insert_iterator,但queue支持;
insert_iterator没有这些限制,front_insert_iterator速度快;
vector
序列容器,关联容器:11个容器:
deque,list,queue,priority_queue,stack,vector,map,multimap,set,multiset,bitset.
C++11新增:
forward_list,unorder_map,unordered_multimap,unordered_set,unorder_multiset;
c++11添加了术语可复制插入(CopyInsertable)和可移动插入(MoveInsertable).
a.cbegin()//c++11 const_iterator,返回指向容器第一个元素的const迭代器;
复制和移动的差别在于,复制操作保留原对象,而移动操作可以修改源对象,还可以转让所有权,而不做任何复制;
序列容器:deque,forward_list,list,queue,priority_queue,stack,vector;
序列中的元素有确定的顺序,因此可以执行诸如将值插入到特定位置,删除特定区间等;
序列容器要求至少是正向迭代器;
支持的算法:a.inert(),erase(),claer().
部分支持的算法:a.front(),a.back(),a.push_front(),a.push_back(),a.pop_front(),a.pop_back(),a
[n],a.at(t);
vector:提供了随机访问,尾追与删时间固定,在头或中间插入和删除元素复杂度为线性时间。
可反转;reverse_iterator;
deque.表示双端队列(double-ended queue.)支持随机访问,在开始位置插入和删除时间为固定,如果多数操作发生在起始和结尾处,则应考虑使用deque.
list双向链表,在任何地方插入删除都是固定的;vector主打快速访问,list主打快速插入和删除;list可反转;list不支持随机访问;list成员:merge(),remove(),sort(),splice(将原始区间移动到目标地址),unique(去重);注意只将相邻的合成一个,要全部去重,先sort,再去重;
forward_list:单链表,每个节点只链接到下个节点,而没有链到前一个节点。因此,forward_llist只需要正向迭代器,而不需要双向迭代器;不可反转;
queue:队列,不支持随机访问,不允许遍历,它把使用限制在定义队列的基本操作上,可以尾追,删头,查看首尾值,查看数目和测试是否为空;
queue的成员:empty(),size(),front(),back(),push(),pop();
priority_queue.和queue相同,区别在于最大元素被移到队首。内部区别在于,默认的底层类是vector.比较大小可以提供可选的比较构造函数;
priority_queue
stack.的操作: empty(),size(),top(),push(),pop();
array.固定大小,所以没有扩容操作,但是有下标和at;
对于容器X,表达式 X::value_type通常指出了存储在容器中的值类型;
关联容器(associative container):X::key_value指出键类型;优点:提供对元素的快速访问,不能指定插入位置,树的查找速度更快;四种关联容器:map,multimap,set,multiset;
1.set 最简单的关联容器,值就是键;而且不能相同;可反转,可排序,键唯一;
set
2.multiset:允许相同,允许重复值;
map:键是唯一的,每个键只对应一个值;
multimap:一个键可以与多个值相关联;
//std::multimap people {{"Ann",25},{"Bill", 46}, {"Jack", 77}, {"Jack", 32},{"Jill", 32}, {"Ann", 35} };
set
ostream_iterator
copy(A.begin(),a.end(),out);
集合操作:并集(参数1代表集合1,后面2个集合2,最后一个参数是将结果复制到哪):
set_union(a.begin(),a.end(),b.begin(),b.end(),ostream_iterator
set_union(a.begin(),a.end(),b.begin(),b.end(),insert_iterator
set_intersection(交集).set_difference(差集)
set.lower_bound(),upper_bound().将键作为参数,并返回一个失代器,指向集合中第一个不小于(大于)键参数的成员。例如:a[i]={12,15,17,19,20,22,23,26,29,35,40,51};用值21调用lower_bound(),返回一个指向22的iterator。用值22调用lower_bound(),也返回一个指向22的iterator。(值界限)
multimap
pair
codes.insert(item);
cout << item.first << item.second;
-------------------------------------
multimap
codes.insert(Pair(415, "good"));
codes.insert(Pair(315, "goodxx"));
-------------------------------------
C++11无序关联容器:4种:
unordered_set, unordered_map, unordered_multiset, unordered_multimap
关联容器是基于树结构的,无序关联容器是基于数据结构哈希表的。旨在提高增删查效率;
函数对象:包括函数名,函数指针,重载了()的类对象;
函数符的概念:生成器:是不用参数就可以调用的函数符;
一元函数,是用一个参数可以调用的函数符;
二元函数是用两个参数可以调用的函数符;
bool tooBig(int n){return n>100} //为真则删
list
scores.remove_if(tooBig);
---------------------------------
template
class TooBig {
private:
T cutoff;
public:
TooBig(const T& t) :cutoff(t) {}
bool operator()(const T& v) { return v > cutoff; }
};
//forward declare
void outint(int n) { std::cout << n << " "; }
int main()
{
using std::list;
using std::cout;
using std::endl;
TooBig
int vals[10] = { 50,100,90,180,60,210,415,88,188,201 };
list
list
for_each(y.begin(), y.end(), outint);
y.remove_if(f100);//调用默认
y.remove_if(TooBig
cout << endl;
for_each(y.begin(), y.end(), outint);
return 0;
}
--------------------------------------------------------------
#include
transform(b1,e1,b2,op) //把一个区间[b1,e1)内的数据经过(op)转化,放入第二个容器内
//也就是复制+修改(变换) 当然b2可以等于b1
transform(b1,e1,b2,b3,op) //把两个集合里的数据整合(op)到第三个集合,当然b3=b2=b1也可以,注意be代表begin(),e=end();
STL算法库分成4组:
非修改式序列操作;不修改容器内容,如:find(),for_each()
修改式序列操作:transform(),random_shuffle,copy();
排序和相关操作 sort(),集合操作
通用数字运算(
自适应生成器,自适应一元函数,自适应二元函数,自适应谓词,自适应二元谓词。
函数符自适应性的意义在于:函数适配器对象可以使用函数对象,并认为存在这些typedef成员。
对算法进行分类的方式之一是按结果放置的位置进行分类,有些算法就地完成工作,有些创建拷贝,如sort()函数完成时,结果被存放在原始数据的位置上,因此,sort()是就地算法,而copy函数将结果发送到另一个位置,所以它是复制算法;而transform()函数可以以这两种方式完成工作。
STL约定,复制版本以_copy结尾,将接受一个额外输出迭代器参数。
算法同时读写容器,迭代器必须是ForwardIterator或更高级的;
replace_copy(...) 复制版本;对于指定区间迭代器,输入迭代器就足够了;
根据将函数应用于容器元素得到的结果来执行操作,通常以_if结尾。如:replace_if();
predicate谓词;
next_permutation() 全排序算法提供唯一的排列组合;while (next_permutation(x.begin(),x.end()))
方法更适合容器;
三个数组:vector,valarray,array:
valarray类模板是面向数值计算的,不是STL的一部分,但为很多数学运算提供了一个简单直观的接口。array是为替代内置数组而设计的,通过提供更好,更安全;array表示固定长度数组。很容易将STL算法用于array对象;
array
valarray
transform(ved1.begin(),ved1.end(),ved2.begin(),ved3.begin(),plus
bindlst(multiplies
也可以使用apply()方法,该方法也适用于非重载函数;
slice(1,4,3) // 索引为4个,开始为1,步长3,共4个; 1.4,7,10
gslice类可以表示多维下标;
要在代码中使用initalizer_list,必须包含文件#include
缓冲区是用作中介的内存块,它是将信息从设备传输到程序或从程序到设备的临时存储工具。如磁盘每次以512字节的块为单位来传输信息,而程序每次只能处理一个字节信息,缓冲区帮助匹配这两种不同的信息传输速率。
char和wchar_t,char16_t,char32_t;
ios类基于ios_base,其中包括一个指向streambuf对象的指针成员;ios_base类,steambuf类,流缓冲ostream,istream,->派生出iostream;
输入输出重定向到文件 dir < x.txt > y.txt ;
cout.put('w');
cout.write();
缓冲区通常为512字节,输出流flush(cout)
继承关系:ostream->ios->ios_base;
width()方法调整输出宽度,只影响下一个项目,然后恢复默认值;
cout.fill()填充字符;
cout.precision(2)设置浮点数显示精度;
cout.setf(ios_base::showpoint);打印末尾的0和小数点;
fmtflags setf(fmtflags); //fmtflags是bitmask类型的typedef名,
调用setf()的效果通过unsetf()取消;
头文件iomanip提供了一些控制符;setprecision(),setfill(),setw().
cin 或 cout 对象包含一个描述流状态(stream state)的数据成员,流状态被定义为iostate类型(bitmask),由3个ios_base元素组成,eofbit,badbit,failbit.其中每个元素是一位,到达文件末尾时,eofbit被置位;goodbit; good(),eof(),bad(),fail(),rdstate()返回流状态;
exceptions()返回一个位掩码,指出哪些标记导致异常被引发;
exceptions(isostate ex) 设置哪些状态导致clear()引发异常;
clear(iostate) 清状态;setstate(iostate s)
get()提供不跳过空白字符输入功能;cin.get()
cin.ignore(255,'\n');读取并丢弃接下来的255个字符;
getline(char*, int) 如果没有读取任何字符,但换行符被视为读取了一个字符,置failbit位;
char gross[512];
read(gross,512);读取指定数字节,然后存储在指定的位置中;
peek()返回输入中的下一个字符,但不抽取输入流中的字符;while((ch == cin.peek()) != '.' && ch !=
'\n') { cin.get() } 如果下一个为句号或换行,继续;
gcount()返回最后一个非格式化抽取方法读取的字符数;
putback()函数将一个字符插入到输入字符串中,
以使用cout的方式使用对象,唯一的区别是输出的是文件,不是屏幕;
流状态检查:is_open();
文件模式: ios_base::in(读),ios_base::out,ios_base::ate(移动文件尾,仅指针移动到),ios::app
(只允许将数据添加到文件尾),ios::trunc,ios_base::binary(二进制文件)
seekg()将输入(读)指针移动到指定文件位置,for ifstream
seekp()将输出(写)指针移动到指定文件位置 for ofstream
fstream使用缓冲区来存储中间数据,因此指向的是缓冲区的位置,未必是实际的文件;
istream& seekg(streamoff,ios_base::seekdir);//离第二个参数的位置;
istream& seekg(streampos); //开头距离,单位为字节;
fin.seekg(30,ios_base::beg);ios_base::cur,ios_base::end;
对于输入流,tellg().输出流tellp()得到当前位置
c++提供了sstream族,它们使用相同的接口提供程序和string对象之间的IO.
读取string对象中的格式化信息或将格式化信息写入string对象中被称为内核格式化;
头文件sstream定义了一个从ostream类派生而来的ostringstream类,如果创建一个ostringstream对象,则可以将信息写入其中,它将存储这些信息。可以将可用于cout的方法用于ostringstream对象;
ostringstream.str() 返回一个被初始化为缓冲区内容的字符串对象,该方法可以“冻结”该对象;
ostringstream 类用来格式化字符串,避免申请大量的缓冲区,替代sprintf。该类能够根据内容自动分配内存,其对内存管理也是相当到位。istringstream : 用于执行C风格字符串的输入操作。
istringstream,ostringstream类使得能够使用istream和ostream类的方法来管理存储在字符串中的字符数据。
iostream和fstream文件构成了IO库
C++11新增long long ,unsigned long long,以支持64位的整形;新增char16_t,char32_t,以支持16位和32位的字符;增加原始字串R("");头文件initializer_list提供了对模板类initializer_list的支持,这个类包含成员begin(),end()可以用于获得列表的范围。decltype(&x);如果x是double,则推断的类型为double*,是指针;
int j = 3;decltype((j)) //推断为int& 如果加一个括号,就变成了引用了,而引用必须在首次初始化;
using新语法可以用于模板部分具体化,如:template
arr
新增三种智能指针:unique_ptr,shared_ptr,weak_ptr;
weak_ptr是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响所指对象的生命周期,也就是将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数。不论是否有weak_ptr指向,一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放。从这个角度看,weak_ptr更像是shared_ptr的一个助手而不是智能指针。
std::shared_ptr
std::weak_ptr
//weak_ptr不会改变shared_ptr,但是会和shared_ptr的引用保持一致
std::cout << "fsPtr use_count:" << fsPtr.use_count() << " fwPtr use_count:" < 如何判断weak_ptr指向对象是否存在呢?C++中提供了lock函数来实现该功能。如果对象存在,lock()函数返回一个指向共享对象的shared_ptr,否则返回一个空shared_ptr。weak_ptr还提供了expired()函数来判断所指对象是否已经被销毁。weak_ptr并没有重载operator->和operator *操作符,因此不可直接通过weak_ptr使用对象,典型的用法是调用其lock函数来获得shared_ptr示例,进而访问原始对象。 explicit禁止单参数构造函数导致的自动转换; explicit con(double); b = 0.5 ; // err. // 2. b = con(0.5);//ok. explicit opeartor double() const //C++11可以用explicit显示声明转换函数 x = double(b) // 假设b是对象;显示转换; std::vector for (auto& x: vi) x = std::rand(); c++11新增array; std::array 新增const版本,如crbegin(),crend()前面的c都是const版本; 右值包括字面常值,如x+y等表达式以及返回值的函数。 int && r1 = 13; int && r2 = x+y; double && r3 = std::sqrt(2.20); 将右值关联到右值引用导致该右值被存储到特定的位置,且可以获取该位置的地址,也就是说,虽然不能将运算符&用于13,但可以将其用于r1;通过将数据与特定地址关联,使得可以通过右值引用来访问数据。要实现移动语义,需要采取某种方式,让编译器知道什么时候需要复制,什么时候不需要,这就是右值引用发挥作用的地方,可定义两个构造函数;复制构造函数执行深复制,移动构造只调整记录。移动构造要修改实参;移动构造函数和移动赋值运算符使用右值,如果要让它们使用左值,用std::move()强制移动;four = std::move(one);//强制移动语法 ;表达式std::move(one)是右值; 一共有6个默认的特殊成员:1默认构造,2复制构造,3移动构造,4复制赋值,5移动赋值,6析构; default只能用于6个特殊成员函数,但delete可用于任何成员函数,delete的一种可能用法是禁止特定转换。C++11允许您在一个构造函数的定义中使用另一个构造函数;这被称为委托;委托使用成员初始化列表语法的变种; Notes::Notes(int kk,double xx):Notes(kk,xx,"uh"){} //二个参数被委托给三个参数的构造函数namespace Box{ int fun(int){...}} using Box::fn; //可以让其他可以用,也可以使用这种方法让基类的所有非特殊成员函数对派生类可用。 class C1 class C2 :public C1{ using C1::fn; //如果有匹配,则用派生类的,如无,则继承使用基类的; 可以使用虚说明符override指出要覆盖一个虚函数,将其放在参数列表后面。如果声明与基类方法不匹配,将视为错误。防止由于参数不匹配隐藏基类的虚方法;final用于禁止派生类覆盖特定方法; [](double x)->double{ reutrn x=y;} C++11提供了其它的包装器,包括bind,men_fn,reference_wrapper,function。其中bind可替代bind1st,bind2nd;mem_fn能够将成员函数作为常规函数传递,reference_wrapper创建行为像引用但可被复制的对象,function以统一的方式处理多种类似于函数的形式。 模板function是在头文件functional中声明的,它从调用特征标的角度定义了一个对象,可用于包装调用特征标相同的函数指针、函数对象、或lambda表达式; std::function use_f(y,fdci); //use_f(T v,F f) { return f(v);} 正则:\d,\w,\t 数字,单词,制表符匹配; 如果对对齐有要求,可以使用alignas; -----------------part3-------- 一般来说,最好在类定义开始或结束前的位置集中声明友元; class Screen; 这种声明被称为前向声明,它向程序中引入了名字Screen并且指明Screen是一种类类型,对于类型Screen来说,在它声明之后定义之前是一个不完全类型(incomplete type),也就是说,知道是类,但是不清楚到底包含哪些成员。不完全类型只能在非常有限的情景下使用:可以定义指向这种类型的指针或引用,也可以声明(但是不能定义)以不完全类型作为参数或者返回类型的函数;然后一旦一个类的名字出现后,它就认为是被声明这过了(但尚未定义),因此类允许包含指向它自身类型的引用或指针; class Link_screen{ Screen window; Link_screen * next ; Link_screen *prev;}; //链表结点 friend void Window::clear(Screenindex); 编译器处理完类中的全部声明后才会处理成员函数的定义; 如果成员是const,引用,或属于某种未提供默认构造函数的类类型,必须通过构造函数初始值列表 为这些成员提供初值; 如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数; 聚合类(aggregate class)使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。 所有成员是public,没有任何构造,基类,没有virtual.可以用{}初始化; 一个字面值常量类必须至少提供一个constexpr构造函数; static constexpr int period = 30; 如果某个静态成员的应用场景仅限于编译器可以替换它的值的情况,则一个初始化的const或constexpr static不需要分别定义,相反,如果我们将它用于值不能替换的场景中,则该成员必须有一条定义语句;类有两项基本能力,一是数据抽象,即定义数据成员和函数成员的能力,二是封装,即保护类的成员不能被随意访问的能力。 sstream定义了读写内存string对象的类型; IO库条件状态: strm::iostate 是一种IO类型,iostate是一种机器相关的类型,提供了表达条件状态的完整功能。badbit流己经崩溃;failbit:IO操作失败;eofbit,goodbit:指出流未处于错误状态,此值保证为零 s.clear()所有条件状态位复位,将流的状态设置为有效,返回void; s.clear(flags)根据给定的flags标志位,将流s中对应条件状态位复位; s.setstate(flages)根据给定的标志位,将流s中对应条件状态位置位; s.rdstate()返回流的当前条件状态,返回值类型为strm::iostate; 一个流一旦发生错误,其上后续的IO操作都会失败,只有当一个流处于无错状态时,我们才可以从它读取数据。向它写入数据。IO库定义了4个iostate类型的constexpr值;表示特定的位模式,这些值用来表示特定类型的IO条件,可以与位运算符一起使用来一次性检测或设置多个标置位;badbit表示系统级错误,如不可恢复的读写错误;一但被置,流就无法使用了,failbit可恢复。如果到达文件尾,eofbit和failbit都会被置位;(badbit,failbit,eofbit三个条件任一个被置位,流状态条件会失败)。流对象的rdstate成员返回一个iostate值,对应流当前状态,setstate操作将给条件位置位,表示发生了对应错误。 auto old_state = cin.rdstate(); //存储cin的状态 cin.clear(); //使cin有效; process_input(cin); //使用cin cin.setstate(old_state) //恢复原有状态 cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit); //复位failbit和badbit,保持其他标志位不变; 可以使用endl显示刷新缓冲区;在每个输出操作之后,我们可以用操纵符unitbuf设置流的内部状态,来清空缓冲区; 如果想每次输出操作后都刷新缓冲:可以用unitbuf操纵符;它告诉流在接下来的每次写操作后进行一次flush操作,而nounitbuf操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制;当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流,cin.tie(&cout);在默认的情况下cin绑定的是cout,每次执行 << 操作符的时候都要调用flush,这样会增加IO负担。可以通过tie(0)(0表示NULL)来解除cin与cout的绑定,进一步加快执行效率。 #include int main() { std::ios::sync_with_stdio(false); std::cin.tie(0); // IO } 这俩一个输入同步,一个输出同步,sync_with_stdio是输入同步开关. C++的cin和cout为了兼容C中的scanf、printf(在stdio.h中)函数,让C++代码中可以同时用这四个函数。C++中的输入输出都是先放到缓冲区,再输出。所以效率非常的低,甚至有人做过实验,如果用scanf来进行操作只要2秒,而用cin居然要8秒。ios::sync_with_stdio(false);的作用就是取消iostream的这种兼容。但是需要注意的是,如果取消了这种兼容,C++和C中的输入输出就不能混用了(理论上C++的cin要比scanf快)。然后cin.tie(nullptr);的作用是取消cin跟cout的绑定。tie是将两个stream绑定的函数,比如可以将输入跟某个文件流指针绑定,就可以直接往文件中输入了。默认cin与cout绑定,解除后可以加快两者的速度. 文件的输入输出:三个io: fstream,ifstream,ofstream; fstream新增的成员: fstream(s,mode),open(),close(),is_open() 当一个fstream对象被销毁时,close会自动被调用;以out模式打开文件会丢弃己有数据; 默认是out,同时使用trunk,默认会清空文件; sstream 头文件定义了三个类型来支持内存IO,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样;istringstream从string读取数据,ostringstream向string写入数据;而头文件stringstream即可从string读数据,也可以向string写数据。 list双向链表,支持双向顺序访问,在list中任何位置进行插入删除都很快; 只支持单向顺序访问,如果程序要求随机访问,一般是vector,deque;容器的成员:aconst_iterator size_type: 无符号整型,用于保存此种容器最大可能的大小;difference_type: 两个容器之间的距离 value_type:元素类型;reference:元素的左值类型:与value_type&含义相同; const_reference:元素的const左值类型。 swap(),.size(),.empty().max_size(). c.insert();c.emplace(inits)使用inits构造c中的一个元素;c.erase(args)删除args指定的元素; c.clear();reverse_iterator. const_reverse_iterator.crbegin().crbegin();(left-inclusive interval 左闭合区间)[begin,end)list list list auto it7 = a.begin(); //a是const时,为const_iterator; auto it8 = a.cbegin()// const_iterator; 只有顺序容器(不含array)的构造函数才能接受大小参数; 将一个容器初始化为另一个容器的拷贝: 当将一个容器初始化为另一个容器的拷贝时,两个容器的容器类型和元素类型都必须相同。 顺序容器(array除外)还提供一个构造函数,接受一个容器大小和一个可选元素的初始值; vector deque seq.assign(il) //赋值运算; emplace_back的用法比较简单,直接通过构造函数的参数就可以构造对象,因此,也要求对象有对应的构造函数,如果没有对应的构造函数,编译器会报错。 在C++11情况下,果断用emplace_back代替push_back 。 emplace_back和push_back都是向容器内添加数据.对于在容器中添加类的对象时, 相比于push_back,emplace_back可以避免额外类的复制和移动操作.push_back的参数对象必须是已经存在的对象,emplace_back不是 c.push_front(t),c.emplace_front(args);c.insert(p,t);c.emplace(p,args);c.insert(p,n,t); c.insert(p,b,e); 当我们用一个对象来初始化容器时,或将一个对象插入到容器中时,实际上放入到容器中的是对象值的一个拷贝,而不是对象本身; insert的返回值为一个迭代器,循环使用insert可以在特定位置反复插入元素; c++11引入了三个新成员:emplace_front,emplace,emplace_back; emplace使用元素的构造函数直接构造对象,参数必须与元素类型的构造函数相匹配; c.back() 返回C中尾元素的引用,若C为空,行为未定义; c.front() 首元素;c[n],c.at(n);返回下标为n的元素的引用;不能对空容器调用front(),back(); 访问成员函数返回的都是引用,所以可以改变其值; 删:erase(),pop_back(),pop_front();.clear() forward_list中的插入与删除: lst.befor_begin(); 首元素之前 lst.insert_after().emplace_after(p,args);lst.erase_after(); 改变容器的大小: resize(). capacity()返回容器在不扩张内存空间的情况下,可以容纳多少元素,reserve()通知容器应该准备保存多少个元素; shrink_to_fit()只适合vector,string,deque. 将capacity()减少到size();(请求,不保证退成功) c.reserve(n)分配至少能容纳n个元素的内存空间; substr(pos,n),string.insert(),string.assign(cp,7),s.erase(); string.append(),string.replace(); string定义了6个不同的搜索函数,每个函数都有4个重载版本;每个搜索返回一个 string::size_type值,表示匹配发生的位置下标,如果失败,返回string::npos的static成员,初始化为-1;1.find();s.rfind(),find_first_of(),find_last_of(),find_first_not_of(),find_last_not_of();compare()字串比较;返回0,正,负三值;字串转换:to_string(i); stod(s) //转字串为浮点数;stoi(),stol(),stoul(),stoull(),stof(),stod(),stold();count 指定值在容器中出现的次数;只读算法:accumulate(),-> fill();fill_n();算法写元素的操作,不检查写操作,目的位置足够大,能容纳要写入的元素; auto it back_inserter(vec); //返回一个与该容器绑定的插入迭代器; *it = 42 ; //会调用push_back将一个具体的值添加到容器中; fill_n(back_inserter(vec),10,0); //添加10个元素到vec; replace_copy(),copy(),replace(); lambda可以省掉参数列表,但是捕获列表和函数体不能省;find_if(); 可变lambda:对于一个值被拷贝的变量,不会改变其值,希望变,加mutable; auto f = [v1]() mutable{ return ++v1; }; auto newcallable = bind(callable,arg_list); arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是占位符;_1,_2; // auto chek6 = bind(check_size,_1,6); using std::placehoolders::_1; ---------------------------------------------- #include using namespace std; class A { public: void fun_3(int k,int m) { cout< } }; void fun(int x,int y,int z) { cout< } void fun_2(int &a,int &b) { a++; b++; cout<
} int main(int argc, const char * argv[]) { //f1的类型为 function auto f1 = std::bind(fun,1,2,3); //表示绑定函数 fun 的第一,二,三个参数值为: 1 2 3 f1(); //print:1 2 3 auto f2 = std::bind(fun, placeholders::_1,placeholders::_2,3); //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别由调用 f2 的第一,二个参数指定 f2(1,2);//print:1 2 3 auto f3 = std::bind(fun,placeholders::_2,placeholders::_1,3); //表示绑定函数 fun 的第三个参数为 3,而fun 的第一,二个参数分别由调用 f3 的第二,一个参数指定 //注意: f2 和 f3 的区别。 f3(1,2); //print:2 1 3 //指示1为第2个,指示2为第1个占位符; int n = 2; int m = 3; auto f4 = std::bind(fun_2, n,placeholders::_1); //表示绑定fun_2的第一个参数为n,fun_2的第二个参数由调用f4的第一个参数(_1)指定。 f4(m); //print:3 4 //上面的函数,引用各加了1,2,3 cout< cout< A a; //f5的类型为 function auto f5 = std::bind(&A::fun_3, a,placeholders::_1,placeholders::_2); //使用auto关键字 f5(10,20);//调用a.fun_3(10,20),print:10 20 std::function fc(10,20);//调用a.fun_3(10,20) print:10 20 return 0; } ----------------------------------------------------------------- bind()实际上就是把函数保存参数后,以后调用的返回对象就是函数带参; 假设f有5个参数: auto g = bind(f,a,b,_2,c,_1); //原型,产生g g(1,2); //g是通过bind成生的新可用对象,新的可用对象也有参数,第一个参数映射到bind()函数的占位符_1上,则= f(a,b,_2,c,1) // _1 替换为1,_2替换为2; 调试笔记: auto g = bind(f,a, b,c);//需要三个参数,这里给出了,调用g()就可以; 但如果:auto g = bind(f,placeholders::_1, b,c);这里就有一个占位符,则g(a)必须给这个占位符填值来代替1号参数,其余的默认;还要注意,bind要#include 移动迭代器;5个迭代器: 输入:只读 不写,单遍扫描,只能增; 输出:只写,不读,单遍扫描,只能递增; 前向迭代器:读写,多遍,递增 双向: 读写,多遍,可加减; 随机:全包; map,set; multi map,multiset;关键字可重复; 无序:unordered_map:unordered_set,unordered_multimap;unordered_multiset; 关联容器: key_type,value_type; c.equal_range(k) 返回一个迭代器pair,表示关键字等于k的元素的范围,若k不存在,pair的两个成员均等于c.end();关联容器关键字类型必须定义元素比较的方法。在实际的编程中,重要的是,如果一个类型定义了“行为正常的”< 运算符,则它可以作用关键字类型; make_pair(v1,v2) ; 返回一个用v1,v2初始化的pair,pair的类型从v1,v2的类型推断出来的; 对map进行下标操作,如果不存在,则新增;c.at(k)不同,如k不在,抛出out_of_range异常; 对map下标和解引用返回的类型不同,对一个map进行下标操作时,会获得一个mapped_type对象,但当解引用一个map迭代器时,会得到一个value_type对象;下标和at操作只适用于非const的map和unordered_map;新标准定义了4个无序容器,这些容器不使用比较运算符来组织元素,而使用hash function.维护元素的序代价非常高;无序容器在存储上组织为一组桶,每个桶保存零个或多个元素,无序窗口使用一个哈希函数将元素映射到桶,为了访问一个元素,首先计算元素的hash值,指出应该搜索哪个桶,如果容器允许重复关键字,所有具有相同关键字的元素都会在同一个桶中,因此,无序容器的性能依赖于哈希函数的质量和桶的数量和大小。 桶接口: c.bucket_count()正在使用桶的数量; c.max_bucket_count()容器能容纳的最多的桶的数量; c.bucket_size(n)第n个桶中有多少元素; c.bucket(k); 关键字为k的元素在哪个桶中; 桶迭代:local_iterator:可以用来访问桶中元素的迭代器类型; const_local_iterator:桶迭代器的const版本; local_iterator可以用来访问桶中元素的迭代器类型; const_local_iterator:桶迭代器的const版本; c.begin(n),c.end(n);桶n的首元素迭代器和尾后迭代器 c.cbegin(n),c.cend(n);与前面两个函数类似,但返回const_local_iterator; c.load_factor();每个桶的平均元素数量,返回float值; c.max_load_factor();在必要时添加新的桶; c.rehash(n)重组,使bucket_count > = n c.reserve(n) 重组,使得c可以保存n个元素且不必rehash; 无序容器使用关键字类型的==运算符来比较元素,它们还使用hash 所管理的对象。p->mem等价于(*).mem(); 对于ptr .get()返回p中保存的指针, make_shared shared_ptr 只有当括号中仅有单一初始化器时才可以使用auto. auto p1 = new auto(obj); int * p2 = new (nothrow) int; //若分配失败,new返回一个空指针,不抛出std::bad_alloc; 不能将一个内置指针隐式转换为智能指针,必须采用直接初始化的形式; shared_ptr 推荐make_shared,而非new. p.reset()释放此对象。 对于智能指针:不使用相同的内置指针值初始化多个智能指针;delete get()返回的指针;不使用get()初始化或reset另一个智能指针;如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效的了;如果你使用智能指针管理的资源不是new分配的,记住给它一个删除器;拥有权,所有权指针:unique_ptr;某个时刻只能有一个unique_ptr指向给定对象; u = nullptr, u.release()放弃对指针的控制权,返回指针,并将u置空; u.reset()释放u指向的对象, u.reset(q); 如果提供了内置指针q,令u指向这个对象,否则将u置为空; 虽然不能拷贝或赋值unique_ptr,但可以通过调用release或reset将指针的所有权从一个转移到另一个unique; 如果我们不用另一个智能指针来保存release返回的指针,我们的程序就要负责资源的释放; 不能拷贝unique_ptr,例外,可以拷贝将要销毁的unique_ptr, 如从函数返回; return unique_ptr weak_ptr是一种不控制所指向对象生存期的指针,它指向由一个shared_ptr管理的对象,将一个weak_ptr绑定到一个shared_ptr不会改变shared_ptr的引用计数,一旦最后一个指向对象的shared_ptr被销毁,w.use_count() w.expired();use_count()为0,返回true w.lock() .如use_count()为0,返回空shared_ptr,否则返回指向w对象的shared_ptr; auto p = make_shared 由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock.此函数检查weak_ptr指向的对象是否存在,如果存在,返回一个指向共享对象的shared_ptr. 标准库中包含一个名为allocator的类,允许我们将分配和初始化分离;使用allocator通常提供更好的性能和更灵活的内存管理能力。 string *pas = new int[10]{1,2,3,4} //后面可以跟初始化表; unique_ptr up.release(); //自动用delete[]销毁其指针; 当unique_ptr指向一个数组时,可以使用下标来访问数组元素; 与unique_ptr不同,shared_ptr管理动态数组,必须提供自己定义的删除器: share_ptr allocator是一个模板,指定可分配对象类型,会根据给定对象类型确定恰当的内存大小和对齐位置 allocator auto const p = alloc.allocate(n); a.deallocate(p,n); 释放从T*指针p中地址开始的内存;在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destroy. a.construct(p,args); p为T*指针,指向原始内存,arg被传递给类型为T的构造函数;用来在p指向 的内存中构造一个对象;a.destroy(p) p为T*类型的指针,此算法对p指向的对象执行析构函数。 alloc.construct(q++,"hi"); //*q为hi。 uninitialized_copy(b,e,b2);从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中,b2指向的内存必须足够大,能容纳输入序列中元素的拷贝; uninitialized_copy_n(b,n,b2);uninitialized_fill(b,e,t),uninitialized_fill_n(b,n,t); 如果一个构造函数的第一个参数是自身类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数;拷贝构造函数的第一个参数必须是一个引用类型;拷贝构造函数通常不应该是explicit的; 由于析构函数不接受参数,因此不能重载,对一个给定类,只有唯一一个析构函数; 三五法则:有三个基本操作可以控制类的拷贝操作:拷贝构造函数,拷贝赋值,析构; 在类内用=default修饰成员的声明时,合成的函数将隐式的声明为内联的;如果不希望内联,应该只对成员的类外定义使用=default.就像对拷贝赋值运算符所做的那样; 管理资源的类通常还定义swap(),定义swap可能是一种很重要的优化手段; 标准库容器,string,shared_ptr类即支持移动也支持拷贝,io类和unique_ptr类可以移动但不能拷贝; 右值引用有一个重要的性质,只能绑定到一个将要销毁的对象; 虽然不能将一个右值引用直接绑定到一个左值上,但我们可以显式地将一个左值转换为对应的右值引用类型,我们还可以通过调用一个名为move的新标准库函数来获得绑定到左值上的右值引用; move相当于:我们有一个左值,但是我们希望像一个右值一样处理它,我们必须认识到,调用move 就意味着承诺;除了对rr1赋值或销毁它外,我们将不再使用它;调用了move后,不能对移动的源对象的值做任何假设; 我们可以销毁一个移后源对象,也可以赋予它新值,但不能使用一个移后源对象的值; StrVec::StrVec(StrVec && s) noexcept 移动操作不应抛出任何异常; :elements(s.elements),first_free(s.first_free),cap(s.cap){s.elements = s.first_free = s.cap=nullptr;} 必须在声明和实现都指定 noexcept; 除了将移后源对象置为析构安全的状态之外,移动操作还必须保证对象仍然是有效的。对象有效是指可以安全地为其赋值或者可以安全使用而不依赖其当前值; 如果一个类没有移动构造函数,函数匹配规则保证该类型对象会被拷贝,即使我们试图通过调用move来移动它们时也是如此; 所有的五个拷贝控制成员应该看作一个整体,如果定义了任何一个,应该定义所有的五个; 移动迭代器适配器通过改变给定迭代器的解引用运算符的行为来适配此迭代器,移动迭代器的解引 用运算符生成一个右值引用;通过调用标准库的 make_move_iterator函数将一个普通迭代器转换为 一个移动迭代器,此函数接受一个迭代器参数,返回一个移动迭代器; 我们指出this的左值/右值属性的方式与定义const成员函数相同,即在参数列表后放一个引用限定符; 如:public: Foo & operator = (const Foo&) &; //只能向可修改的左值赋值; Foo & Foo::operator = (const Foo & rhs) &{ return *this;} 引用限定符可以是&或&&,分别指出this可以指向一个左值或右值;且必须同时出现在函数的声明和 定义中;和const一样;如果还带const,则必须在const之后; 如:Foo anotherMem() const &; 引用限定符也可以区分重载版本;Foo sorted() &&; //可用于改变的右值; Foo sorted() const &; //可用于任何类型的Foo; 引用限定的函数如果定义两个或两个以上具有相同名字和相同参数列表的成员函数,就必须对所有 函数都加上引用限定符,或者所有都不加;Foo sorted() &&; Foo sorted() const ; //err 必须加上引用限定符;他的意思好像是说,引用限定符可以用来区分重载 ---------------------- class A { public: A() { std::cout << "construction A" << std::endl; } void doWork() & { std::cout << "1&" << std::endl; } void doWork() && { std::cout << "2&&" << std::endl; } }; int main() { A a; a.doWork(); std::cout << "--------------------" << std::endl; A().doWork(); //输出了2&&; 右值版本;//注意这个语法,调用了构造函数生成了临时对象(右值),这个右值调用了&&版本; } 除了重载的函数调用运算符opeartor()之外,其他重载运算符不能含有默认实参; 引用限定符的作用: 1)下面这种情况将对一个右值调用成员函数、对右值赋值 string s1 = "abc", s2 = "def"; auto n = (s1 + s2).find('a'); //对右值调用成员函数 s1 + s2 = "wc"; //对右值赋值 重载输出运算符: << 通常情况下,输出运算符的第一个形参是非常量 ostream对象的引用,之所以ostream 是非常量是因为向流写入内容会改其状态;而是引用是因为流没有办法复制,只能引用; 第二个形参通常是常量引用;如: ostream& opeartor << (ostream& os, const SEAL_Data & item); auto data = alloc_n_cop-y(il.begin(),il.end()) ; // alloc_n_copy分配内存空间并从给定范围内拷贝元素; 后置版本接受一个额外的(不被使用的)int类型的形参,当我使用后置运算符时,编译器为这个形参提供一个值为0的实参;如:class: str operator++(int); //后置 后置应该返回原值,返回形式为一个值,而非引用; 显示的调用前置与后置: p.operator++(0);//后 p.operator++(); //前置; 重载箭头运算符->必须返回类的指针或者自定义了箭头运算符的某个类的对象; 预定义的函数对象 C++98在头文件functional中定义了下述函数对象: plus minus divides equal_to not_equal_to less greater less_equal greater_equal logical_not logical_and logical_or 可调用对象也有类型,每个lambda有它自己唯一的类型,函数及函数指针的类型则由其返回类型和 实参类型决定;然而,两个不同类型的可调用对象却可能共享同一种调用形式(call signature)。 调用形式指明了调用返回类型和实参类型,一个调用形式对应一个函数类型,例如 int(int,int); 不同类型可能具有相同的调用形式; map 但是,map不能将lambda表达存入map,因为类型不同; 标准库function类型function用于解决上面的问题。 function result_type:该function类型的可调用对象返回的类型; argument_type:参数类型:first_argument_type,second_argument_type; function是一个模板,function 不能直接将重载的名字存入function类型的对象中,可以存指针来解决重载函数的二义性; 类型转换运算符:operator type() const ;//通常是const;参数表一定是空; 显示的类型转换运算符: explicit operator int() const { return val; } si +3 ;//err.此处是隐式的,但定义的是显示的; static_cast 显示的还有例外,如表达式被用作条件,如if,while,do,for,!,三元; 最好不要定义两个转换源都是算术类型的类型置换; 基类通常都应定义一个虚析构函数,即使该函数不执行任何实际的操作也是如此; 如果类作基类,则该类必须己经定义,而非仅仅声明; 动态类型则是变量或表达式表示的内存中的对象的类型,动态类型直到运行时才可知。 如果基类中含有一个或个虚函数,我们可以使用dynamic_cast请求一个类型转换,该转换的安全检查将在运行时执行,同样,如果我们己知某个基类向派生类的转换是安全的,则可以使用static_cast来强制覆盖掉编译器的检查工作; 如果我们通过基类的引用或指针调用函数,则使用基类中定义的默认实参,即使实际运行的是派生类中的函数版本也是如此,如果虚函数使用默认实参,则基类和派生类中定义的默认实参最好一致; 强制使用基类定义版本:double undiscoounted = baseP->Quote::net_price(42); 抽象基类负责定义接口; class Derived:private base{//私有继承 public: Base::size; protected: using Base::n; //使用using声明改变成员的可访问性;不然本来n是私有的; }; 假定我们调用p->mem();则执行4个步骤:1.确定p的静态类型,2.在p的静态类型对应的类中查找mem.依次在直接基类中直到继承链的顶端,找不到,报错;找到,确认合法性;合法,4判断是否虚函数:若虚又是引用或指针:根据动态类型在运行时确定;否则,产生常规函数调用; 如果派生类隐藏了基类的成员,则可以用::如:d.Base::memfcn(); 虚析构函数会阻止合成移动操作:即便通过=default的形式使用了合成的版本; 只有公有继承时,用户代码才能使用派生类向基类的转换;D的成员函和友元能使用派生类向基类转换;派生类向其直接基类的类型转换对于派生类的成员和友元来说永远是可访问的; 如果D继承B的方式是公有的或受保护的,则D的派生类的成员和友元可以使用D向B的类型转换,反之,如果D继承B的方式是私有的,则不能使用; 当存在继承关系时,派生类的作用域嵌套在基类的作用域之内;如果一个名字在派生类的作用域内无法解析,则编译器继续在外层的基类作用域中寻找该名字的定义;析构函数只负责销毁派生类自己分配的资源;派生类对象的基类部分也是自动销毁的; 派生类的赋值运算符: D& D::operator = (const D& rhs){ Base::operator=(rhs); //调用了基类的进行转接... return *this; } 使用using继承构造函数,若一个基类构造函数含有默认实参,这些实参不会被继承,当一个基类构造函数有默认实参,这些实参并不会被继承,相反,派生类将获得多个继承的构造函数,其中每个构造函数分别省略掉一个含有默认实参的形参。如果基类有一个接受两个形参的构造函数,其中第二个有默认实参,则派生类将获得两个构造函数,一个构造函数有二个形参,无默认,另一个有一个形参,没有默认值的那个;在容器中放置(智能)指针而非对象: vector basket.push_back(make_shared template template 模板的头文件通常既包括声明也包括非类型模板参数,表示一个值而非一个类型:当一个模板被实例化时,非类型参数被一个用户提供或编译器推断出的值所代替: template int compare 调用:compare("hi","mon") 生成 int compare(const char(&p1)[3],const char(&p2)[4]); 注意:生成的推断是依据模板函数的参数类型,而不是typename的类型,要注意这点; 函数模板声明为inline,constexpr的位置: template template if (less } 类模板是用来生成类的蓝图的,与函数模板的不同之处是,编译器不能为类模板推断模板参数类型,须在模板名后的尖括号提供额外的信息。 template public: typedef T value; typedef typename std::vector 当我们使用一个类模板类型时必须提供模板实参,有一个例外,在自己的作用域中,可以直接使用模板名而不提供实参;template BlobPtr& operator ++(); //这里直接使用了,而不是BlobPtr 在类模板外使用类模板名:当我们在类模板外定义其成员时,必须记住,我们并不在类的使用域中,直到遇到类名才表示进入类的使用域; template BlobPtr BlobPtr ret = *this; ++*this; return ret; } //ret 返回类型位于类的作用域外: 则相当于: BlobPtr template class C{ friend class Pal template }; 在新标准中,可以将模板类型声明为友元: template friend type; }; 新标准允许我们为类定义一个类型别名: template twins 类模板的静态成员的初始化:分两种情况: template { public: static int knownTypeVar;//知道其类型为int; static T unKnownTypeVar;//不知道类型,依赖模板参数T }; template <> int TestTemStatic template 两种初始化可以并存,对于特定类型T如果存在具化定义,则以具化定义为准,对于特定类型T的具化定义不可在不同的(CPP)实现文件中重复,范化定义可以放在头文件中,具化定义放在唯一的cpp文件中。 template typename auto ct = Foo 使用类型成员:如果我们写下string::size_type;编译器有string的定义,知道size_type是类型,但模板就不同,T::mem,不知道是类型,还是static数据成员;直到实例化时才知道;默认情况下,通过作用域访问的名字不是类型,若想要类型,就必须显示用typename T::value_type声明; 当我们希望通过编译器一个名字表示类型时,必须使用typename,不能使用class; 默认实参:template Number<> avs ; //空<>表示我们希望使用默认实参; 使用中,非实现中。 一个类(普通类,类模板)可以包含本身是模板的成员函数;这种成员被称为成员模板(member template).成员模板不能是虚函数; 类模板的成员模板:一个是类的,一个是成员的,都是模板; template template Bob 实例化: Blob 控制实例化: extern template class Blob 当编译器遇到extern模板声明时,它不会在本文件中生成实例化代码,将一个实例化声明为extern 就表示承诺在程序其它位置有该实例化的一个非extern声明(定义); 二个智能指针,另一个差异,允许用户重载默认删除器的方式;重载shared_ptr的删除器,只要在创建或reset指针时传递给它一个可调用对象即可。相反,unique_ptr必须在定义时以显示模板实参的形式提供删除器的类型; const转换:可以将非const对象的引用(指针)传递给一个const的引用(或指针)形参。 数组或函指针转换:数组实参可以转换为一个指向其首元素的指针;函数实参可以转换为函数指针;其他的类型转换:如算术转换,派生类向其类的转换,用户定义的转换都不能应用于函数模板; 将实参传递给带模板类型的函数形参时,能够自动应用的类型转换只有const转换及数组或函数到指针的转换; template T1 sum(T2,T3); //auto V3 = sum 没有办法为T1来推断,所以必须显示指定: 显式模板实参; 可以用remove_reference来获得元素的类型,这个模板有一个模板类型参数和一个名字为type的成 员,如果我们用一个引用类型实例化remove_reference,则type将表示被引用类型,如果我们实例化remove_reference template auto fcn2(It beg,It end)-> typename remove_reference { return *beg ;//返回一个序列中的拷贝; } 相关: remove_reference // X& ; X&& -> X add_const add_lvalue_reference; add_rvalue_reference; remove_pointer; add_pointer; make_signed; make_unsigned; remove_extent; remove_all_extents; 如果不能从函数指针确定模板实参,则产生错误; template int (*pf1)(const int&, const int&) = compare; // int ---------------------------------------------- func(compare ----------------------------------------------- void f2(const T&) //一个const&参数可以绑定一个右值; voif f2(T&&) //右值 f3(i); //编译器推断T的类型为int&,而非int; 通常我们不能直接定义一个引用的引用,但是通过类型别名,或通过模板类型参数间接是可以的; 引用折叠:对于一个给定类型X: X& &,X& &&和X&& & 都折叠成类型X&;类型X&& &&折叠成X&&; 引用折叠只能应用于间接创建的引用的引用,如类型别名或模板参数; 如果一个函数参数是一个指向模板类型参数的右值引用如(T&&),则它可以被绑定到一个左值;且如果实参是一个左值,则推断出的模板实参类型将是一个左值引用,且函数参数将被实例化为一个(普通)左值引用参数(T&);在实际中,右值通常用于两种情况,模板转发其实参,或模板被重载; template templaet 虽不能直接将一个右值绑定到一个左值上,但可以用move获得一个绑定到左值上的右值引用。move可以用于任何类型,是一个函数模板;从一个左值static_cast到一个右值引用是允许的;可以用static_cast显示地将一个左值转换为一个右值引用; 统一使用std::move使得我们在程序中查找潜在的截断左值代码变得很容易; 通过将一个函数参数定义为一个指向模板类型参数的右值引用,我们可以保持其对应实参的所有类型信息,使用引用参数可以保持const属性;将函数参数定义为T&&,通过引用折叠,就可以保持翻转的左值、右值属性。 如果一个函数参数是指向模板类型参数的右值引用(如T&&),它对应的实参的const属性和左值、右值属性将得到保持。在调用中使用std::forward保持类型信息;#include 例:template void flip(F f, T1 && t1, T2 &&t2){ f(std::forward class...或typename...指出接下来的参数表示零个或多个类型的列表,一个类型名后面跟一个省略号表示0个或多个给定类型的非类型参数的列表,在函数列表中, template void foo(const T &t,const Args&... rest); //Args是一个模板参数包,rest是一个函数参数包;Args表示零个或多个模板参数,rest表示零个或多个函数参数;对于一个可变参数模板,编译器还会推断参数数目。当需要知道包中有多少元素,可以用sizeof... 运算符;返回常量; #include using namespace std; template ostream& print(ostream& os, const T& t) { return os << t; } template ostream& print(ostream& os, const T& t, const Args& ... rest) { os << t << " ,"; cout << sizeof...(rest) << endl; //cout << "g"; return print(os,rest...);//递归,调用了自己。因为rest为包参数,到最后一个,才不是包,所以,最后一个调用了上个print() //递归算法在分解的同时,又改变了参数,此时,rest递归分解的时候,把rest的第一个参数变成了t;当递归回退到0 个的时候,调用了最上面的参数,此时,包无参数;所以,收尾是由上一个; return os; } int main() { //std::cout << "cout " << std::endl; print(cout, "1", 2, "sing", "good");//输出321.递归,适用于不知道类型, initialize_list仅能同类型。这个可以处理未知不同类型;} 输出结果是:1,2,sing,good //321.前3个调用有逗号,最后一个good是调用的上一个,无逗 号; 递归调用,实际上在不断减少,所以要调用自己; 扩展:return print(os,debug_rep(rest)...); 可变参数模板与forward机制转发编写函数,将其实参不变地传递给其他函数; template void StrVec::emplace_back(Args&&... args){ alloc.construct(first_free++,std::forward } //work(std::forward 模板特例化:必须为原模板中的每个模板参数都提供实参,template<>,空尖括号指出我们将原模板的所有模板参数提供实参; template<> int compare(const char* const &p1,const char* const &p2){} 特例化的本质是实例化一个模板,而非重载它;因此,特例化不影响函数匹配; 模板及特例化版本应该声明在同一个头文件中,所有同名模板的声明应该放在前面,然后是这些模板的特例化版本;类模板特例化一样:namespace std{ template<> class hash } 也可以只特例化部分函数成员: template<> void Foo tuple.希望将一些数据组合成单一对象;tuple make_tuple(v1,v2); get(t); tuple_size tuple_element::type 一个类模板,可以通过一个整型常量和一个tuple类型来初始化,它有一个名为type的public成员,表示给定tuple类型中指定成员的类型; tuple tuple auto item = make_tuple("0",3,2.3); auto book = get<0>(item); size_t sz = tupe_size tuple_element<1,trans>::type cnt = get<1>(item); 可以使用tuple从函数返回多个值; bitset<32> bitvec(1U); //32位,低位为1,其它位为0; bitset<12> ibtce(0xbef); bitset<32> bit("1100"); b.any() //是否存在置位; b.all() b.none(),b.count(),b.size(),b.test(pos) b.set(),b.set(pos,v);b.rest(pos),b.reset()b.flip(pos) b.flip(),b[pos]; b.to_ulong() b.to_ullong(); b.to_string(zero,one); ------------------------------- regex regex_match 将一个字符序列与一个正则匹配 regex_search 寻找第一个与正则表达式匹配的子序列 regex_replace 使用给定格式替换一个正则表达式 sregex_iterator:迭代器适配器; smatch容器类,保存在string中搜索的结果; ssub_match string中匹配的子表达式的结果; regex_match,regex_search确定一个给定字符序列与一个给定regex是否匹配; 整个输入序列与表达式匹配,则regex_match函数返回true,如果输入序列中一个子串与表达式匹配,则regex_search函数返回true。 (seq,m,r,mft),(seq,r,mft) //seq序列,m是match对象,用来保存匹配结果的细节,r正则;mft可选项:regex_constants::match_flag_type; ------------------ string s = "abcdefgabcdefghijkabcdefhiadfasdfasdf"; string pattern("[^hij]"); regex r(pattern); smatch results; if (regex_search(s, results, r)) ---------------------------- regex_constants::syntax_option_type: icase:忽略大小写; nosubs:不保存匹配的子表达式; optimize:执行速度优于构造速度 ECMAScript:使用ecma指定的语法; basic:使用posix基本的正则语法 extended:使用posix扩展的正则语法 awk:使用posix版本的awk语言的语法; grep:使用posix版本的grep语法; egrep:使用posix版本的egrep的语法 ---------------------------------- regex r("[[:alnum:]]+\\.(cpp|cxx|cc)$",regex::icase); try{}catch(regex_error e) { cout << e.what() << e.code(); smatch表示string类型的输入,cmatch表示字符数组序列,wsmatch表示宽字符串(wstring)输入;wcmatch表示宽字符数组;sregex_iterator:一个sregex_iterator,遍历迭代器b和e表示的string; it(b,e,r) 它调用sregex_search(b,e,r)将it定位到输入中的第一个匹配的位置;string pattern("[^c]ei");//查找前一个字符不是c的*ei;match子匹配操作:matched:是否匹配;first,second:如果未匹配,则first=second;regex_replace(dest,seq,r,fmt,mft);string fmt = "$2.$5.$7"; //将号改为ddd.ddd.dddd; string number = "(908) 555-1800"; regex_replace(number,r,fmt)< 随机: 引擎:类型,生成随机unsigned整数序列; 分布:类型,使用引擎返回服从特定概率分布的随机数 Engine e; e(time(0));//以时间为种子; Engine e(s); e.seed(s); //使用种子S重置引擎的状态 e.max();e.main(); static default_random_engin e; // e() ; //unsigned整数; ---------------------------------- static uniform_int_distribution static uniform_real_distribution *.reset() 重建引擎状态,使得随后对d的使用不依赖于d己经生成的值; #include std::default_random_engine r(time(NULL)); uniform_int_distribution<unsigned> u(0, 9); cout << u(r)//产生随机数; 生成非均匀随机数: default_random_engine e; normal_distribution<> n(4,1.5); //均值4,标准差1.5 bernoulli_distribution b(.5); //55机会; bernoulli_distribution b(.55) //55/45的机会; 流: 向cout写入boolalpha,改变bool的值的方式; 控制进制:hex,dec,oct 影响整形运算; setprecision(3); //设置精度 cout.precision() // 获取精度 showbase; //显示进制前缀; showpoint //对浮点值总是显示小数点 showpos 显示正号 uppercase:在16进制中打印0X,科学计数E; left:在值的右侧添加填充字符; right:在值左侧添加填充字符 internal:在符号和值之间添加填充字符; fixed;scientific,hexfloat(浮点16进制) defaultfloat:重置浮点数格式为10进制; unitbuf;每次输出操作后刷新缓冲区; skipws;输入运算跳空白 ends:插入空字符;然后刷新ostream缓冲; setw:下个数或字符最小空间 right:右对齐:left:左对齐 setfill('#') ------------------------------- is.get(ch) os.put(ch) is.get() is.putback(ch) 将字符ch放回is,返回is is.unget() 将is向后移动一个字节,返回is,输入流向后移动,从而最后读取的值又回到流中。 is.peek() 将下一个字节作为int返回,但不从流中删除它; is.getline(sink,size,delim) //字符数组起始地址sink is.read(sink,size)//delim 终止字符 is.get(sink,size,delim) is.gcount() //返回上一个未格式化读取操作从is读取的字节数 is.write(source,size) //将字符数组source中的size个字节写入os; is.ignore(size,delim) 读取并忽略最多size个字符,包括delim,size默认1,delim默认为文件尾; 不要将get,peek的返回值赋给一个char,给int; istream和ostream类型通常不支持随机访问,一般fstream和sstream类型: tellg(),tellp(),seekg(pos),seekp(pos),seekp(off,from),seekg(off,from); //from的三个值: beg,cur,end; pos通常是一个tellg或tellp的返回值; 输出流tellp(),输入流tellg(); //off 多少个; --------------------------- int main() { fstream inOut("c:\\a.txt",fstream::ate|fstream::in|fstream::out); if (!inOut) { cout << "unable to openfile" << endl; return EXIT_FAILURE; } auto end_mark = inOut.tellg(); //记住原文件尾的位置; inOut.seekg(0, fstream::beg); //重定位到文件开始 size_t cnt = 0; //字节数累加 string line; //保存输入中的每行; while (inOut && inOut.tellg() != end_mark && getline(inOut, line)) {//读取成功 cnt += line.size() + 1;//记住读取的 auto mark = inOut.tellg();//记住读取位置,用于恢复位置 inOut.seekp(0, fstream::end);//将写标记移动到文件尾//这个用于写每行多少个字符,所以要写,所以移动到尾 inOut << cnt; //输出累计的长度,写到未尾 if (mark != end_mark) inOut << " ";//比较未尾和以前保存的读取的,如果还有没有读完的,则在末尾加个空格; inOut.seekg(mark);//写完了,继续读;把光标定位到上次读保存地址;开始下一次循环; }//循环条件为2个:1.未到结尾,且get得到行内容; inOut.seekp(0, fstream::end);//跳到文件未尾,加个回车符结束; inOut << "\n"; return 0; } 产生异常:throw后面的语句不再被执行,类似于return, 通常作为条件的一部分或者作为某个函数最后一条语句;控制权从一处转到另一处,沿着调用链的函数可能会提早退出;沿着调用链创建的对象将被销毁; 块退出后它的局部对象也将随之销毁,这条规则对于栈展开过程同样适用。与实参和形参匹配规则相比,异常和catch异常声明的匹配规则受到更多限制,允许非常量向常量,允许派生类向基类,数组,函数转指针,其它的隐式转换都不允匹配catch的过程中使用; 要想处理构造函数初始值抛出的异常,必须将构造函数写成函数try语句块(也称为函数测试块)的形式,函数try语句块使得一组catch语句既能处理构造函数体,也能处理构造函数初始化过程; template Blob (ill)){....} catch(const std::bad_alloc &e) { handle_out_of_momory(e); } 处理构造函数初始值异常的唯一方法是将构造函数写成函数try语句块; 初始化构造函数参数时也可能发生异常,函数try语句块只能处理构造函数开始执行后发生的异常。 class A { public: A(int a) try: m_p(new char[a]) { } catch(...) { m_p = NULL; cout << "catch1..." << endl; } ~A() { delete m_p; } char * m_p; }; 参数异常属调用表达式的一部分,并将在调用者所在的上下文中处理; void alloc(int) noexcept(false); //可能抛出异常;若为true,不会抛出异常; void (*pf1)(int) noexcept = recoup; //函数指针和函数都不会抛出异常; 如果一个虚函数承诺了它不会抛出异常,则后续派生出来的虚函数也必须做出同样的承诺; 名字空间后无分号; 命名空间的定义可以不连续的特性使得我们可以将几个独立的接口和实现文件组成一个命名空间,此时,命名空间的组织方式类似于我们管理自定义类及函数的方式;模板特例化必须定义在原始模板所属的命名空间中。(::全局命名空间) C++11引入新的嵌套命名空间,称为内联命名空间,和普通嵌套命名空间不同,内联命名空间中的名字可以被外层命名空间直接使用;无须添加前缀; inline namespace A{} namespace A{class Query_base{}} inline必须出现在第一次定义的地方,后续可以写,也可以省掉;(main中可以直接使用Query_base类;不需要A::,去掉inline,则未定义) 未命名空间中定义的变量拥有静态生命周期,第一次使用前创建,并且直到程序结束才销毁;可以在同文件中不连续,但不能跨多个文件;若两个文件均有未命名的名字空间,两无关;未命名的命名空间仅在特定的文件内有效,其作用范围不会横跨多个不同的文件; namespace primer = cplusplus_primer; 限定符以相反的次序指出被查找的作用域:如: A::C1::f3() 先找f3(),再找C1,再找A; 当我们给函数传递一个类类型的对象时,除了在常规作用域查找外还会查找实参所属的命名空间,对于传递类的引用或指针的调用同样有效; 如:std::string s; std::cin >> s; //第二句= operator >> (std::cin,std::string s);编译器会查找cin,string的所属空间std;找到operator >> ; using声明语句声明的是一个名字,而非一个特定的函数;所以不能指定形参: using NS::print; // using指示(using namespace)引入一个与己有函数形参列表完全相同的函数不会产生错误,调用的时候须指明,不要产生二义性就可; C++11新标准允许派生类继承构造函数:struct D1:public Base1{using Base::base1; } //使用using 继承构造函数;若一个类从它的多个基类中继承了相同的构造函数,则这个类必须定义自己的构造函数;多重继承的情况下,如果名字在多个基类中被找到,则名字具有二义性;要用Zoo::max作用域解析符消失二义性;class Bear:virtual public ZoolAnimal{}; virtual虚基类,说明符表明了一种愿望,即在后续的派生类当中共享虚基类的同一份实例。虚基类总是先于非虚基类构造,与次序,位置无关,所以派生类的构造函数,先构造虚基类; new 表达式调用 operator new (operator new[])的标准库函数; 如果new的对象是对象,先在类和基类作用域中查找,然后全局 ::new ,最后使用标准库版本;operator delete不允许抛出异常,重载要加noexcept: void* operator delete(void*) noexcept;而且这些函数是隐式静态的,无须加static;如果要自定义operator new,则可以为它提供额外的形参;必须使用new的定位形式; 有一个形参不能被重载: Void *operator new(size_t,void*); 对于operator delete,operator delete[]函数来说,返回类型必须是void,第一个形参必须是 void*;执行delete表达式将调用相应的operator函数,并用指向待释放内存的指针来初始化void*形参;一条new表达式的执行过程总是先调用operator new函数以获取内存空间,然后在得到的内存空间中构造对象。相反 ,delete表达式的执行过程总是先销毁对象,然后调用operator delete函数释放对象所占的空间;重载new,delete目的在于改变内存分配方式; cstdlib头文件中的malloc ,free,函数,malloc(size_t) //返回指针,0失败;free(void*); ---- void *operator new(size_t size){ if (void *mem = mallc(size)) return mem; else bad_alloc(); } void operator delete(void *mem){ free(mem);} new定位构造对象: new (*addr) type(initializers); new (*addr) type[size]; new (*addr) type [size]{ braced initalizer list} 定位new语法,允许我们在一个特定的、预先分配的内存地址上构造对象; allocate类对象也分配内存,但是new定位语法只需要一个地址,甚至可以不是堆内存; dynamic_cast e的类型必须符合3个条件中的任意一个:e的类型是目标type的公有派生类,e的类型是目标的公有基类或者e的类型就是目标type类型; dynamic_cast语句:若是指针类型失败了是0,若是引用失败了,抛出bad_cast异常;因为不存在空引用,所以,dynamic_cast的时候,注意参数,若是引用,则要try;失败catch.bad_cast; typid也多态,如果类定义了虚函数的话,typeid的结果直到运行时才会求得; if(typeid(*bp) == typeid(*dp) type_info: t1 == t2; t1 != t2; t.name(), t1.before(t2); C++11引入了限定作用域的枚举:加class;每个枚举成员本身就是一个条件表达式; 数据成员指针:const string screen::*pdata; // 没有区别,只是加了个限定符; string Screen::*pdata = &Screen::x;//首先,string,代表这个成员是string类型,第二个限定符代表是成员,三个*pdata 代表是指什,合起来,就是string类型的Screen成员指针,指向其x数据成员,不与任何对象关联,auto pdata = &Screen::x;//C++11新语法,快速;对类的成员直接取地址;生成一个成员指针; cout << c1.*pdata; //c1->*pdata; //if is pointer;//使用对象时关联;用*萃取; 指向函数成员的指针:auto pf = &Screen::display; //display是函数成员,直接取地址,注意这里的&还不能省略;void (Screen::*pf)() = &Screen::display;//返回值,形参表要和类对应 (c1.*pf)();//调用;注意括号必不可少; using Action = Screen& (Screen::*)(); static Action Menu[]; //函数表; 成员和函数适配器:function 标准库mem_fn可以推断成员。也定义在functional头文件中;并且生成一个可调用对象; find_if(sv.begin(),svec.end(),mem_fn(&string::empty)); mem_fn可以根据成员指针的类型推断可调用对象的类型,而无须用户显式指定; 使用mem_fn(&string::empty)生成一个可调用对象;mem_fn生成的可调用对象可以通过对象调用,也可以通过指针调用; auto f = mem_fn(&string::empty); f(*sve.begin()); f(&svec[0]); auto f = bind(&string::empty,_1) //bind也可以使用; f(*sve.begin()); f(&svec[0]); union{ char cval; int ival; double dval; } cval = 'c'; //为刚刚定义的未命名匿名union对象赋一个新值; ival = 42; //该对象当前保存的值是42; 类可以定义在某个函数的内部,称为局部类(local class); volatile的确切含义与机器有关,想让volatille的程序在移植到新机器或新编译后仍然有效,通常需要对程序进行某些改变;不能使用合成(默认)的拷贝、移动构造,赋值运算初始化volatile对象或从volatile对象赋值;合成的成员接受的形参类型是常量引用; 通过内存模型,线程,原子操作等来支持本地并行编程; 通过constexpr ,POD,更好支持系统编程; 统一初始化表达式,auto,decltype,move移动主义来统一对泛型编程的支持; 内联命名空间,继承构造函数,右值引用等,更好支持库的构建; alignas alignof static_assert(4<=sizeof(int),"err"); //确定编译时(不是运行时),系统的int整型长度不小于4字节;C++11中的线程,被实现炎库特性的一部分:std::thread;没有关联数组,而实现为 std::unorder_map;不过由于初始化依旧是动态的,这对rom设备来说并不适用,这就要求在动态初始化前就将常量计算出来,为此标准增加了constexpr,它站函数和变量可以被编译时的常量取代;而从 效果上来说,函数和变量在固定内存设备的空间变得更少,而而对于嵌入设备的ROM支持更好;__func__ #pragma用来向编译器传达语言标准以外的一些信息;C++11定义了与预处理指令相同的操作符如: _pragma("once"); long long int lli = -111LL; //注意LL后缀代表Long long,而ULL代表 unsigned long long 相同大小的有符号类型和夫符号类型等级相同,LLI 等级和ULL相同;有符号向无符号转换; 编译器会在内部就将原子操作实现为具体的机器指令,无需在稍后去链接实实在在的库进行存档; template void fun() noexcept(noexcept(T()){} //若为true,不会抛出异常; 什么都没有声明的类,其析构函数被默认为noexcept(true)不会抛出异常; 新增可以推导是否抛出异常的noexcept异常描述符; 类中直接赋值,叫就地初始化,注意,如果是用括号,则出错,花括号不出错; 对非静态成员变量使用sizeof是不能够通过编译的; void fun() final; //声明为final; template 只需要声明 template void fun (int); 参数默认值会导致多个构造函数版本的产生,因此,程序员在使用有参数默认值的构造函数的基类时候,必须要小心; 重复定义相同的继承构造函数这种情况,可以通过显式定义继承类的冲突的构造函数;阻止隐式生成相应的继承构造函数来解决; 如果基类的构造函数被声明为私有成员函数,或者派生类是从基类中虚继承的,那么就不能够在派生类中声明继承构造函数; 委托构造函数: info() {InitRest();} info(int i) : info(){ type=i;} //委托后再初始化另一个值,不能有初始化表,如果要给变量赋初值,初始化代码必须放在函数体中; 可以取地址的,有名字的就是左值,不能取地址,没名字的就是右值;C++11中,右值是由两个概念构成的一个是将亡值(xvalue,eXpiring value),另一个则是纯右值(pure Rvalue).纯右值是C++98的 概念;类型转换函数的返回值,lambda表达式等也都是右值;将亡值是C++11新增的跟右值引用相关的表达式,比如返回TT&的函数返回值,move的返回值;转换为T&&的类型转换函数的返回值;所有值必 属左值,将亡值,纯右值三者之一; 引用为什么必须初始化:此乃类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名;左值引用是具名变量的别名,而右值引用则是不具名变量的别名; const T& f 在c++98中就是个万能引用;可以接受非常量左值,常量左值,右值对其进行初始化,而且在使用右值对其初始化的时候,常量左值引用还可以像右值引用一样将右值的生命期延长; 右值引用的来由从来就跟移动语义紧紧相关,这是右值存在的一个最大的价值;另一个用于转发; is_rvalue_reference,is_lvalue_reference,is_reference 不知道左右,可以进行判断; cout << is_rvalue_reference std::move:将一个左值强制为右值引用;等于static_cast 注意不要使临时变量常量化,造成临时变量的引用无法修改,从而导致无法实现移动语义; 在c++11中,拷贝/移动构造函数实际上有以下3个版本: T Object(T &); T Object(const T&); T Object(T &&); 判断一个类型是否可以移动(is_move_constructible,is_trivialy_move_constructible,is_nothrow_move_constructible) 通过为其添加一个noexcept关键字,可以保证移动构造函数中抛出的异常会直接调用terminate程序终止运行;标准库中可以用 std::move_if_noexcept的模板函数代替move函数;在类的移动构造函数 没有noexcept关键字修饰时返回一个左值引用从而使变量可以使用拷贝语义;而在类的移动构造函数有noexcept关键时,返回一个右值引用,从而使变量可以使用移动语义; 完美转发,指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数; 转发的不简单在于函数调用的隐式转换;其次,在于匹配,目标函数可能需要能够既接受左值引用,又接受右值引用。 C++引入一条引用折叠的规则,结合新的模板推导规则来完成完美转发: 将复杂的未知表达式折叠成为己知的简单表达式: 一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用,而模板对类型的推导规则就总是优先将其折叠为左值引用,那么模板参数被推导为X&类型;而转发函数的实参是类型X的一个右值引 用的话,那么模板的参数被推导为X&&类型,结合以上的引用折叠规则,就能确定出参数的实际类型; move通常就是一个static_cast,不过,C++中叫forward;move,forward差别并不大,为了让每个名字对应不同的用途; template void PerfectForwadr(T &&t){ RunCode(forward 感觉上叫引用坍塌好理解点(reference-collapsing rules) 就这样 A& & 变成 A& A& && 变成 A& A&& & 变成 A& A&& && 变成 A&& 就是左值引用会传染,只有纯右值&& && = &&,沾上一个左值引用就变左值引用了 A a1 = GetA(); // a1是左值 A&& a2 = GetA(); // a2是右值引用 右值引用是值得大力使用的。但是,在使用的时候,有例外情况了:T&&并不是一定表示右值,比如,如果它绑定的类型是未知的话,既可能是左值,又可能是右值; 由于存在T&&这种未定的引用类型,当它作为参数时,有可能被一个左值引用或右值引用的参数初始化,这是经过类型推导的T&&类型,相比右值引用(&&)会发生类型的变化,这种变化就称为引用折叠。 引用折叠的规则如下(配合@jun-jun的答案)[和上一段的出处一样]: 1.所有右值引用折叠到右值引用上仍然是一个右值引用。(A&& && 变成 A&&) 2.所有的其他引用类型之间的折叠都将变成左值引用。 (A& & 变成 A&; A& && 变成 A&; A&& & 变成 A&) -------------------------- 引用折叠 一般来讲,我们不能定义一个引用的引用,但是通过类型别名或者模板参数可以间接定义。引用折叠规则适用于这种情况。当我们间接创建一个引用的引用,这些引用形成了“折叠”。 对于一个给定的类型X,下面是折叠规则: X& . & . X& && . X && &都被折叠为X& X&& && 折叠为X && 除了引用折叠规则之外,c++标准还规定了一个“右值转换”的规则。 右值转换的含义是: 形容funRef(T && arg)的这种参数为右值引用的模板,当实参为一个左值时,调用仍然成功,此时编译器推断模板参数(也就是T)为左值的引用。例如调用funRef(i),那么T是int&而非int,展开可知 funRef(int & && arg)再采用上述的引用折叠规则,可知最后arg是int& funRef(i);//实参是左值,int,T是int& funRef(ci);//实参是左值,const int,T是const int & 以上这两条规则意味着,我们可以将任意类型的实参传递给T&&类型的函数参数。 完美转发可以减少一些版本的重复(如const,非const); ------part4---- 通过内存模型,线程,原子操作等来支持本地并行编程; 通过constexpr ,POD,更好支持系统编程; 统一初始化表达式,auto,decltype,move移动主义来统一对泛型编程的支持; 内联命名空间,继承构造函数,右值引用等,更好支持库的构建; alignas alignof static_assert(4<=sizeof(int),"err"); //确定编译时(不是运行时),系统的int整型长度不小于4字节 ;C++11中的线程,被实现炎库特性的一部分:std::thread;没有关联数组,而实现为std::unorder_map; 不过由于初始化依旧是动态的,这对rom设备来说并不适用,这就要求在动态初始化前就将常量计算出来,为 此标准增加了constexpr,它站函数和变量可以被编译时的常量取代;而从效果上来说,函数和变量在固定内 存设备的空间变得更少,而而对于嵌入设备的ROM支持更好;__func__ #pragma用来向编译器传达语言标准以外的一些信息;C++11定义了与预处理指令相同的操作符如: _pragma("once"); long long int lli = -111LL; //注意LL后缀代表Long long,而ULL代表 unsigned long long 相同大小的有符号类型和夫符号类型等级相同,LLI 等级和ULL相同;有符号向无符号转换; 编译器会在内部就将原子操作实现为具体的机器指令,无需在稍后去链接实实在在的库进行存档; template void fun() noexcept(noexcept(T()){} //若为true,不会抛出异常; 什么都没有声明的类,其析构函数被默认为noexcept(true)不会抛出异常; 新增可以推导是否抛出异常的noexcept异常描述符; 类中直接赋值,叫就地初始化,注意,如果是用括号,则出错,花括号不出错; 对非静态成员变量使用sizeof是不能够通过编译的; void fun() final; //声明为final; template 只需要声明 template void fun 版本的函数,这种做法也称为强制实例化;外部模板声明加extern template void fun 实例化,再外部实例化;1. template void fun 参数默认值会导致多个构造函数版本的产生,因此,程序员在使用有参数默认值的构造函数的基类时候,必 须要小心; 重复定义相同的继承构造函数这种情况,可以通过显式定义继承类的冲突的构造函数;阻止隐式生成相应的 继承构造函数来解决; 如果基类的构造函数被声明为私有成员函数,或者派生类是从基类中虚继承的,那么就不能够在派生类中声 明继承构造函数; 委托构造函数: info() {InitRest();} info(int i) : info(){ type=i;} //委托后再初始化另一个值,不能有初始化表,如果要给变量赋初值,初 始化代码必须放在函数体中; 可以取地址的,有名字的就是左值,不能取地址,没名字的就是右值;C++11中,右值是由两个概念构成的一 个是将亡值(xvalue,eXpiring value),另一个则是纯右值(pure Rvalue).纯右值是C++98的概念;类型转换 函数的返回值,lambda表达式等也都是右值;将亡值是C++11新增的跟右值引用相关的表达式,比如返回TT& 的函数返回值,move的返回值;转换为T&&的类型转换函数的返回值;所有值必属左值,将亡值,纯右值三者 之一; 引用为什么必须初始化:此乃类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名;左值引用 是具名变量的别名,而右值引用则是不具名变量的别名; const T& f 在c++98中就是个万能引用;可以接受非常量左值,常量左值,右值对其进行初始化,而且在使 用右值对其初始化的时候,常量左值引用还可以像右值引用一样将右值的生命期延长; 右值引用的来由从来就跟移动语义紧紧相关,这是右值存在的一个最大的价值;另一个用于转发; is_rvalue_reference,is_lvalue_reference,is_reference 不知道左右,可以进行判断; cout << is_rvalue_reference std::move:将一个左值强制为右值引用;等于static_cast 注意不要使临时变量常量化,造成临时变量的引用无法修改,从而导致无法实现移动语义; 在c++11中,拷贝/移动构造函数实际上有以下3个版本: T Object(T &); T Object(const T&); T Object(T &&); 判断一个类型是否可以移动 (is_move_constructible,is_trivialy_move_constructible,is_nothrow_move_constructible) 通过为其添加一个noexcept关键字,可以保证移动构造函数中抛出的异常会直接调用terminate程序终止运行 ;标准库中可以用 std::move_if_noexcept的模板函数代替move函数;在类的移动构造函数没有noexcept关 键字修饰时返回一个左值引用从而使变量可以使用拷贝语义;而在类的移动构造函数有noexcept关键时,返 回一个右值引用,从而使变量可以使用移动语义; 完美转发,指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另外一个函数; 转发的不简单在于函数调用的隐式转换;其次,在于匹配,目标函数可能需要能够既接受左值引用,又接受 右值引用。 C++引入一条引用折叠的规则,结合新的模板推导规则来完成完美转发: 将复杂的未知表达式折叠成为己知的简单表达式: 一旦定义中出现了左值引用,引用折叠总是优先将其折叠为左值引用,而模板对类型的推导规则就总是优先 将其折叠为左值引用,那么模板参数被推导为X&类型;而转发函数的实参是类型X的一个右值引用的话,那么 模板的参数被推导为X&&类型,结合以上的引用折叠规则,就能确定出参数的实际类型; move通常就是一个static_cast,不过,C++中叫forward;move,forward差别并不大,为了让每个名字对应不 同的用途; template void PerfectForwadr(T &&t){ RunCode(forward 感觉上叫引用坍塌好理解点(reference-collapsing rules) 就这样 A& & 变成 A& A& && 变成 A& A&& & 变成 A& A&& && 变成 A&& 就是左值引用会传染,只有纯右值&& && = &&,沾上一个左值引用就变左值引用了 A a1 = GetA(); // a1是左值 A&& a2 = GetA(); // a2是右值引用 右值引用是值得大力使用的。但是,在使用的时候,有例外情况了:T&&并不是一定表示右值,比如,如果它 绑定的类型是未知的话,既可能是左值,又可能是右值; 由于存在T&&这种未定的引用类型,当它作为参数时,有可能被一个左值引用或右值引用的参数初始化,这是 经过类型推导的T&&类型,相比右值引用(&&)会发生类型的变化,这种变化就称为引用折叠。 引用折叠的规则如下(配合@jun-jun的答案)[和上一段的出处一样]: 1.所有右值引用折叠到右值引用上仍然是一个右值引用。(A&& && 变成 A&&) 2.所有的其他引用类型之间的折叠都将变成左值引用。 (A& & 变成 A&; A& && 变成 A&; A&& & 变成 A&) -------------------------- 引用折叠 一般来讲,我们不能定义一个引用的引用,但是通过类型别名或者模板参数可以间接定义。引用折叠规则适 用于这种情况。当我们间接创建一个引用的引用,这些引用形成了“折叠”。 对于一个给定的类型X,下面是折叠规则: X& . & . X& && . X && &都被折叠为X& X&& && 折叠为X && 除了引用折叠规则之外,c++标准还规定了一个“右值转换”的规则。 右值转换的含义是: 形容funRef(T && arg)的这种参数为右值引用的模板,当实参为一个左值时,调用仍然成功,此时编译器推 断模板参数(也就是T)为左值的引用。例如调用funRef(i),那么T是int&而非int,展开可知funRef(int & && arg)再采用上述的引用折叠规则,可知最后arg是int& funRef(i);//实参是左值,int,T是int& funRef(ci);//实参是左值,const int,T是const int & 以上这两条规则意味着,我们可以将任意类型的实参传递给T&&类型的函数参数。 完美转发可以减少一些版本的重复(如const,非const); -------------------------------------------- template void fun void test1(){ fun(3);} #include "test.h" extern template void fun void test1(){fun(3);} -------------------------------------------- 转发函数: template void IamForwording(T && t){ iRunCodeActually(forward(t)); } double * d = new double{1.2f}; ----------------------------------- enum Gender {boy,girl}; people(initializer_list 自己的类也可以用列表初始化;声名构造函数为列表加pair; pod可以用老的memcpy()进行复制,memset()进行初始化;C++11将pod划分为两个基本概念的合集,即平 凡的(trivial)和标准布局的(standard layout); is_trivial 标准布局:1.所有非静态成员有相同的访问权限;2.派生类中有非静态成员,且只有一个仅包含静态成员的 基类;基类有非静态成员,而派生类没有非静态成员; 非静态成员只要同时出现在派生类和基类间,就不属于标准布局;而多重继承也会导致类型布局的一些变化 ,所以一旦非静态成员出现在多个基类,派生类也不属于标准布局; 3.类中每个非静态成员的类型与基类不同。 同样,也有一个模板类来判断: is_standard_layout; id_pod 使用pod有什么好处: 1.字节赋值;可以安全的使用memset,memcpy初始化和拷贝; 2.提供对C内存布局兼容; 3.保证了静态初始化的安全有效;而静态初始化在很多时候能够提高程序的性能; 任何非引用类型都可以成为联合体的数据成员,这样的联合体即所谓的非受限联合体; 联合体会自动对未初始化成员列表中出现的成员赋默认初值; 联合有一个非平凡的成员,因此构造函数会被删除,解决办法是由程序员自己为非受限联合体定义构造函数 : union T{ string s;//非pod,构造函数被删,无法初始化 int n; public: T() {new (&s) string;} //自己定义构造函数、注意放置语法 ~T(){s.~string();} //申请了资源,自己显示调用析构; }; struct Watt{ unsigned int v;}; Watt operator "" _w(unsigned long long v){ return {(unsigned int) v}; } C++98标准不允许在不同的名字空间中对模板进行特例化;c++11内联名字空间允许程序员在父名字空间中定 义或者特化子名字空间的模板。名字空间的内联会破坏该名字空间本身具有的封装性; ADL带来了一些使用上的便利,不过也在一定程度上破坏了namespace的封装性;最好还是在使用前打开名 字空间,或者使用::列出变量、函数完整名字空间; c++标准库is_same模板类可以判断两个类型是否一致; auto * e = &fo();//假设fo()是一个函数,则编译失败,指针不能指向一个临时变量; volatile和const代表了变量的两种不同的属性,易失的和常量的。cv限制符; const auto* m = &x,n=1 ; // n是int; auto z = new auto(1) ; //new 用于auto; auto不能作为函数参数,不能作为非静态成员变量;不能声明auto数组;模板实例化的时候不能auto; RTTI的机制是为每个类型产生一个type_info类型的数据,hash_code这个成员函数,返回该类型唯一的哈希 值; RTTI有运行时开销,可以关; 在泛型中,类型变成了未知数, decltype(i) j = 0; decltype总是以一个普通的表达式为参数,返回该表达式的类型; using size_t = decltype(sizeof(0)); using ptrdiff_t = decltype((int*)0-(int*)0); using nullptr_t = decltype(nullptr); using vectype = decltype(vec.begin()); decltype只能接受表达式做参数,函数名不行; ------------------------------ int hash(char*); map ------------------------------- std::result_of用法:用于在编译的时候推导出一个可调用对象(函数,std::funciton或者重载了operator() 操作的对象等)的返回值类型.主要用于模板编写中. ----------------------------------------- typedef double (*func)(); result_of ----------------------------------------- 表达式有两种属性:值与类型。值与类型也是两种不同的数据。 decltype 作用于表达式,返回表达式的类型。declval 作用于类型,返回该类型的表达式(准确的来说是该 类型加上右值引用的类型)。 std::declval (c++11 only):返回一个类型的右值引用,不管是否有没有默认构造函数或该类型不可以创建 对象。(可以用于抽象基类); ----------------------------------------- struct Default { Default(int i) { cout << "default construct!"; } int foo() const { return 1; } }; struct NonDefault { NonDefault(const NonDefault&) { cout << "construct !"; } double foo() const { return 1; } }; int main() { Default(1).foo(); //调用构造函数 decltype(Default(1).foo()) n1 = 1; // type of n1 is int,没有调用构造 函数 // decltype(NonDefault().foo()) n2 = n1; // error: no default constructor decltype(std::declval std::cout << "n1 = " << n1 << '\n'; std::cout << "n2 = " << typeid(n2).name() << '\n'; } --------------------------------------------------------- 注意decltype,declval 这些,推断出,但是却没有调用构造函数; ------------------------------------------------ typedef double (*func)(); void printfx(const char* s) { cout << s << endl; } template void printfx(T1 first,T... t) { //用T1接受输出的第一个参数,遍历; cout << first << endl; //sizeof...(t) //321 printfx(t...);//循环调用;每次用n-1去填充T1;最后一个参数匹配自己特例化的函数; } int main() { printfx("ab","cd","ef"); } ------------------------------------ template void display(T t) { //#3 cout << t << "_"<< endl; } template void display(T2 var,T... t) { //#2 cout << sizeof...(t) << ":" << var << endl; display(t...); //#4 } int main() { display("ab","cd","c","c"); //#1 } //当运行#1时,匹配display(...)函数; //此时,函数匹配#2成功; //调用函数,运行函数;display(T2 var= "ab", t... = "cd","c","c"); //又调用display(t...) //注意,这时只有一个参数,调用 display(T2 var = "cd", t = "c","c") //运行display(T2 var = "cd", t = "c","c") t = 2个参数 //又调用自己,...参数只有2个了,又给一个给模板T2 var,余下...t = "c". //余下一个的时候,这时,没有办法匹配模板函数了,只能匹配自定义的#3;结束; //实际上是之所以能工作,是因为模板匹配,要给模板减匹配 ------------------------------------------------------ 如果e是一个没有带括号的标记表达式或者类成员访问表达式,那么decltype(e)就是e所命名的实体的类型, 此外,如果e是一个被重载的函数,则会导致编译时错误; 否则假如e的类型是T,如果e是一个将亡值,那么decltype(e)为T&&; e是一个左值,则decltype(e)为T&; 否则假设e是T,则decltype(e)为T; decltype((i)) : 由于(i)不是一个标记符表达式(arr[3],arr[3]+0也不是);但却是一个左值,所以是引 用; int&& Rvalref(); //将亡值,返回一个临时右值; decltype(Rvalref()) x = 1;// 推断为int&&; decltype(++i) //int& //也是推断为int&; // (i++)却推断为int decltype(*ptr) //int& *; decltype("lval") var = "lval"; // const char(&)[9]; --------------- const bool Func(int); decltype((Func(1))) var15; // 推断为const bool 括号忽略 ---------------- is_lvalue_reference auto类型推导不能带走cv限制符,decltype能带走cv限制符; is_const is_volatie int* p = &a; decltype(p) x = &a; //注意p是p*,是指针;不是整型;引用也一样; template auto Sum(T1& t1, T2 & t2) -> decltype(t1+t2){ return t1+t2; } ----------------- int( *(*pf())() ) () { return nullptr;} auto pf1()-> auto(*)() -> int(*)(){ reutrn nullptr; } is_same 返回函数指针,而该函数指针又指向一个返回函数指针的函数; --------------------------- template auto Forward(T t) -> decltype(foo(t){ return foo(t);} ------- auto (*pf)() -> int; //可以用函数名,也可以用&加函数名; auto (&fr)() ->int //函数引用;函数引用可以用函数名初始化; 基于范围的for循环还要求迭代的对象实现++和==等操作符,且要求范围确定; for (auto e: v) cout << e ; //e是解引用后的对象,不需要*来解引用。迭代器则不同; C++受推荐的静态常量: const static int Male = 0; enum class C: char{C1 = 1,C2 = 2}; //加了一个class关键字; sizeof(C::C1) //1; char类型; auto_ptr的缺点:拷贝时返回一个左值,不能调用delete[]等。 无法复制unique_ptr;有所有权,但可以用move()函数来转移; .reset(); 显示释放内存; 从实现上讲,unique_ptr则是一个删除了拷贝构造函数,保留了移动构造函数的指针的封装类型; weak_ptr的使用更为复杂,可以指向shared_ptr,却并不拥有该内存,而使用weak_ptr成员lock,则可以返回 一个shared_ptr对象,失效时返回nullptr.这在验证share_ptr智能指针的有效性上有用. ----------------- void Check(weak_ptr shared_ptr if (sp != nullptr) cout << ok. } //weak_ptr get_pointer_safety() noexcept; declare_reachable(p) ; // 声明为可达,避免回收;void*参数 undeclare_reachable declare_no_pointers,undeclare_no_pointers(char* p, size_t n).告诉回收器,该内存区域不存在有效的 指针;以起始地址开始的n个内存; c++11标准对指针垃圾回收支持仅限于系统提供的new操作符分配的内存,而malloc分配的内存则会被认为总 是可达的,即无论何时都不回收; const描述的是运行时常量性,数组下标,枚举初始化这些值,需要编译时常量; constexpr,就是编译时期常量表达式;用于函数,数据声明,类的构造函数; 要求:1.只有单一的return返回语句;2.函数必须返回值;3.在使用前必须己有定义;4.reutrn返回语句表 达式中不能使用非常量表达式的函数\全局数据,且必须是一个常量表达式;. 对于类,自定义constexpr构造函数; struct MyType{ constexpr MyType(int x):i(x){} int i; }; constexpr MyType mt = {0}; 常量表达式构造函数:函数体为空,初始化列表只能由常量表达式来赋值; 当声明为常量表达式的模板函数后,而某个该模板函数的实例化结果不满足常量表达式的需求的话, constexpr会被自动忽略,实例化后的函数成为一个普通函数; 图灵完备:任何程序中需要表达的计算,都可以通过constexpr元编程的方式来表达; std::make_tuple(9.3,'g',"gravity"); 模板参数包也可以是非类型的,比如:template NonTypeVariadicTemplate<1,0,2> ntvwt; template 解包通过包扩展:A...(参数包A加上三个点,就是一个包括展) template template #1解包为class T #2解包为 class T ------------------- template template cout << t; return t; } template void VTPrint(A... a) { DummyWrapper(pr(a)...); // 先展开pr(1),pr(1.2)....最后调用dumpWrapper(-); } int main() { VTPrint(1, ", ", 1.2, 13); } ------------------------------- 常见的并行编程有多种模型:共享内存,多线程,消息传递等。实用性上讲,多线程具有较大优势。 多线程模型允许同一时间内有多个处理器单元执行统一进程中的代码部分。通过分离的栈空间和共享的数据 区及堆栈空间,线程可以拥有独立的执行状态以及进行快速的数据共享; 原子操作:多线程程序中,最小的且不可并行化的操作; 原子操作通常通过“互斥”来访问来保证的; join()函数是一个等待线程函数,主线程需等待子线程运行结束后才可以结束(注意不是才可以运行,运行 是并行的),如果打算等待对应线程,则需要细心挑选调用join()的位置 detach()函数是子线程的分离函数,当调用该函数后,线程就被分离到后台运行,主线程不需要等待该线程 结束才结束。 #include #include atomic_llong total{ 0 }; //长度等于long long, bool,char,int,char16_t,char32_t,ullong...等; atomic atomic atomic_flag是无锁的,访问无需加锁; TLS线程的局部存储:thread local storage 拥有线程生命期及线程可见性的变量; 线程会拥有自己的栈空间;但是堆空间,静态数据区则是共享的;(全局、静态变量); g++:每个编译器有自己的TLS标准:__thread int errCode; 即在全局或者静态变量的声明中加上 __thread,即可将变量声明为TLS变量,每个线程将拥有独立的errCode的拷贝;一个线程中对errCode的读写 并不会影响另一个线程中的errCode数据; C++11对tls做了一些统一的规定:int thread_local err;变量取值&也只可以获得当前线程中的TLS变 量的地址值; abourt更加低层,不会调用任何的析构函数;是系统在无办法的下策-终止进程; exit属于正常退出,会正常调用自动变量的析构函数,并且还会调用atexit注册的函数; --------------- int main(){ atexit(closeDevice);//#2 atexit(resetDeviceStat);//#1 openDevice(); exit(0); //调用#1,#2,己注册的顺序相反,和析构函数一样; } C++11引入quick_exit函数,该函数并不执行析构函数而只是使程序终止,与about不同的是,abort的结果 通常是异常退出,quick_exit与exit同属正常退出。使用at_quick_exit注册的函数也可以在quick_exit的时 候调用。可以用于解除因为退出造成的死锁等不良状态,也可以用来免除大量不必要的析构函数调用。 typedef decltype(nullptr) nullptr_t; null_ptr不允许隐匿转换成非指针类型; 要让编译器推断出nullptr的类型,必须做显示的类型转换; default关键字,显示地指示编译器生成该函数的默认版本; delete会指示编译器不生成函数缺省版本;删除缺省版本; class NoHeapAlloc{ public void* operator new (std::size_t) = delete;} //不允许分配到堆 禁用new; ~NoStatck() = delete; //禁用析构; [capture](paramenters) mutable ->return-type { statement } 捕获列表能够捕获上下文中的变量以供lambda函数使用;mutable:因为默认lambda函数总是一个const函数; mutable可以取消其常量性,在使用mutable时,参数列表不可以省略; lambda函数与普通函数可见的最大区别之一,就是lambda函数可以通过捕捉列表访问一些上下文中的数据,[ =]以值传递,包括this指针;[&]引用传递,也包括this; [this]捕获当前this指针; [=,a]//出错,不允许重复传递; 仿函数(functor)重定义了成员函数operator()的一种自定义类型对象; lambda实际上是仿函数的封装; alignof(C) : 32; //指定数据C对齐到32字节的地址边界上,只需要声明alignas(32)即可; alignas(32) //按32位对齐; alignof获得的也是一个与平台相关的值; void* aligned = align(alignof(double)*4,size,p,sz); aligned_storage,aligned_union; //使用aligned_storage使其对齐要求更加严格; typedef aligned_storage IntAligned* pia = new(&sa) IntAligned; cout << alignof(sa) << endl; aligned_union对象的对齐要会是多个类型中要求最为严格的一个; 扩展属性:__attribute__; __declspec(extended-decl-modifier) c++语言中的通用属性使用了左右双中括号的形式: [[attribute-list]] c++11的通用属性可以作用于类型,变量,名称,代码块等,[[noreturn]]主要用于标识那些不会将控制流返 回给原调用函数的函数。[[carries_dependency]]则跟并行情况下的编译器优化有关,事实上, [[carries_dependency]]主要是为了解决弱内存模型平台上使用memory_order)consume内存顺序枚举问题; 在unicode中,使用从0到10FFFF的十六进制数唯一地表示所有字符;但由于计算机存储数据通常是以字节为 单位,需要一种具体的编码方式来对字符码位进行存储,比较常见的基于unicode字符集的编码方式有utf- 8,utf-16,utf32. gb2312作为简体中文的国家标准被颁布使用,gb2312收录了6763个汉字和682个非汉字图形字符,而在编码上 ,是采用基于区位码的一种编码方式。采用2字节表示一个中文字符; c++11引入以下两种新的内置数据类型来存储不同编码长度的unicode数据; char16_t用于存储utf16编码的unicode数据,char32_t用于存储utf32编码的unicode数据; u8,表示为utf8编码; u表示utf16编码; U表示为utf32编码; u"a""b"和"a"u"b",其效果跟u"ab"是完全相同的;字符串中用'\u'加4个16进制数编码的unicode码位来标识一个unicode字符; 转换函数: #include mbrtoc16() //mb 多字节;c16=char16; c16rtomb() //rt=convert;转换; mbrtoc32(); c32rtomb();//mbstate_t是用于返回转换中的状态信息外,其余部分意义比较明显; std::codecvt std::codecvt std::codecvt std::codecvt 每种facet负责不同类型编码数据的转换, #include locale lc("en_us.UTF-8"); bool can_cvt = has_facet 原生字符串:R"(heell)" ;前缀:u8R(utf8),uR(utf-16),UR(utf32); --------------------------part 5------------------------------------- [31][51]各个容器的性能参数表 为了对内置数组使用范围for,需要#include lexicographical_compare();//字典序比较 输入迭代器:++向前 迭代器萃取: auto inc = bind(incr,_1); function std::tuple shared_ptr的循环链表会导致资源泄漏,所以有weak_ptr; auto p = make_shared using Link_alloc = typename A::template rebind::other; auto bs = get_temporary_buffer raw_storage_iterator()进行初始化; //写序列的标准算法假定序列中元素己经初始化,赋 using nanoseconds = duration istream_iterator p{st}; 为输入流st创建迭代器; seekp()函数用来在ostream中定位输出位置,后缀p表示这是将字符放入(putting)流的位置。 线程的分离状态决定一个线程以什么样的方式来终止自己,在默认的情况下,线程是非分离状 recursive_mutex与普通mutex很相似,区别仅在于它允许单一thread反复获取; try{ mtx.lock(); mtx.lock();//二次加锁,出错} string s{ lock_guard packaged_task -------part-5---- 当表达式带有const属性时,auto会把const属性抛弃掉,推导成non-const类型int;当auto和引用 结合时,auto的推导将保留表达式的const属性; 引用; 一个左值引用; foo<(100 >>2)> xx; // 注意(100>>2) 括号的使用; 当所有的模板参数都有默认参数时,函数模板的调用如同一个普通函数,对于类模板而言,哪怕所 有参数都有默认参数,在使用时也必须在模板名后跟随<>来实例化; 注意区分赋值和初始化,重载operator=; 还要注意,构造函数不能是私有的;一般地,operator=返回值是被赋值者的引用,即*this 的实现延迟求值; struct Bar{ using fr_t = void(*)(void); //函数指针; std::function还可以入参作为函数的形参; 存,并延迟调用到任何我们需要的时候; A a; std::placeholders::_1,std::placeholders::_2); count_if(vi.begin(), vi.end(), bind(less 在lamda中使用当前类的成员函数和成员变量; 临时对象,一个区分左值与右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值;所 有的具名变量都是左值,而右值不具名; 的表达式,比如将要移动的对象,T&&函数返回值,move返回值和转换为T&&的类型的转换函数的返 回值; 值; 始化,这时经过类型推导的T&&类型,相比右值引用&&会发生类型的变化,这种变化被称为引用折 叠:规则:1.所有的右值引用叠加到右值引用上仍然还是一个右值引用;2.所有的其他引用类型之 间的叠加都将变成左值引用; 给一个右值引用类型该怎么做呢?用move; 可能是右值引用,取决于初始化的值类型;所有的右值引用叠加到右值引用仍是一个右值引用,其 他引用都为左值引用,当T&&为模板参数时,输入左值,它会变成左值引用;而输入右值时则变为 具名的右值引用;编译器会将己命名的右值引用视为左值,而将未命名的右值引用视为右值;右值 引用的一个重要目的是用来支持移动语义的; 以通过右值引用使用该值,以用于移动语义,强制转换为右值的目的是为了方便实现移动构造; 值,这时需要用完美转发函数std::forward(),所谓的完美转发,是指在函数模板中,完全依照模 板的参数的类型(即保持参数的左、右值特性),将参数传递给函数模板中调用的另外一个函数; std::forward puch_back更好地避免内存的拷贝与移动;优先使用 emplace,emplace_hint,emplace_front,emplace_after,emplace_back; 序来快速操作元素,效率高; 比较函数; std::integral_constant派生; 最终的返回类型;但是这个也太复杂了,有简单的: 助方法来初始化,对于一个未初始化的智能指针,可以通过reset方法来初始化; 值将一个unique_ptr赋值给另外一个unique_ptr; 其它的unique_ptr,这样它本身就不再拥有原来指针的所有权了; 返回shared_from_this(); 的内部指针,主要是为了监视shared_ptr的生命周期,更像是shared_ptr的一个助手;增加不会增 加计数,析构不会减少计数,纯粹只是作为一个旁观者来监视shared_ptr中管理的资源是否存在, weak_ptr还可以用来返回this指针和解决循环引用的问题; 用unlock()来解除对互斥量的占用; 线程多次获得同一个互斥量; 通知或超时,才会唤醒阻塞的线程;条件变量需要和互斥量配合;c++11提供了两种条件变量: 将数据和future绑定起来,方便线程赋值;std::package_task用来包装一个可调用对象,将函数 和future绑定起来,以便异步调用; 果的通道。我们可以以同步等待的方式来获取结果,通过查询future状态(future_status)来获 取异步操作的结果;三种状态: deferred,异步操作还未开始,ready,己完成,timeout:超时; ;在线程函数中为外面传进来的promise赋值,在线程函数执行完成后就可以通过promise的future 获取该值了。取值是间接地通过promise内部提供的future来获取的; expression,bind expression,another function object),将函数和future绑定起来,以便异步 调用,它和std::promise在某种程度上有点像,promise保存了一个共享状态的值,而 package_task保存的是一个函数; 在std::future之上的高一层是std::package_task和std::promise,它们内部都有future以便访问 异步操作结果;packaged_task包装的是一个异步操作,而promise包装的是一个值; std::shared_future来获取异步调用的结果。future是不可拷贝的,只能移动;shared_future可 以拷贝;当需要将future放到容器中则需要使用shared_future; 步task,异步任务返回的结果也保存在future中,当需要获取异步任务的结果时,调用future.get (),不要结果,只是等任务完成则调用future.wait(); 行,还是延迟加载(即等到get,wait时才创建线程)。 std::chrono::time_point 宽窄字符转换: 的问题,通过using Base::Fun来表示基类的同名名函数; 后可以加其他字符串,所加的字串是会被忽略的,而且加的字符串必须在括号的两边同时出现。 重写基类的虚函数; 并且这个整数必须是2的N次方(1,2,4,8.1024)当我们说一个数据类型的内存对齐为8时,就是指 这个数据类型所定义出来的所有变量的内存地址都是8的倍数。 默认情况下,字节对齐也是4; x86CPU,一个时钟周期可以读取4个连续的内存单元,即4个字节,按4个字节对齐之后,一次就可以 读出来; 在这块内存上构建对象,但是分配的内存可能是按1对齐的;可能会引起效率问题,这时用 std::aligned_storage来构造内存块; 将buffer从pt开始的长度放入space; 早期:任务切换 返回同时并发在一个程序中的线程数量;而在多核系统中,这个值只是CPU核芯的数量。 C++中通过实例化mutex创建互斥量,通过调用lock()进行上锁,unlock()进行解锁;不推荐直接调用,C++标准库提供了一个RAII语法模板类:std::lock_guard;构造时锁,析构解锁;("0-201-8",50));
is_polymorphic,is_scalar,is_nothrow_constructible;
基于任务的并发支持,future(),async()
uninitialized_fill();memmove();
declare_reachable();
双向链表
出错后,应抛出专门表示异常的类型的对象;
标准库exception层次可以用于用户自定义异常;
如果发生严重错误,调用terminate();
大量使用static_assert(),assert()
如果不能使用异常,考虑使用
容器适配器提供对底层容器的特殊访问。拟容器保存元素序列,提供容器的大部分但非全部功
能;
stl容器都是资源句柄,都定义了拷贝和移动操作;
list
模板参数A是一个分配器,容器用它来分配和释放内存:
template
class vector{...};
A的默认值是std::allocator
配和释放内存;
链表或关联容器中的元素不会因为插入或删除而移动;
forward_list(单向链表)是一种专为空链表和极短链表优化过的数据结构;
map
类型;
multimap
set
对于映射,A的默认值是std::allocator
std::allocator
unordered_map
是分配器类型
unorder_multimap
这些窗口都是采用溢出链表法的哈希表实现;关键字类型K的默认哈希函数H为
std::hash
关键字类型K的默认相等性判定函数类型E为std::equal_to
希值相同的两个对象是否相等;
容器适配器是一类特殊容器,它为其他容器提供了特殊的接口:
priority_queue
queue
某些数据类型具有标准容器所应有的大部分特性,但又非全部,我们有时称这些数据类型为拟
容器;
拟容器:T[N],array
u16string // basic_string
u32string;
常量表示操作花费的时间不依赖于容器中的元素数目,常量时间的另一种表示方式为O(1);
O(n)表示花费时间与元素的数目成正比;后缀加表示在极少数情况下代价会有大幅上升;
O(n)+的习惯术语是摊还线性时间(amortized linear time).
O(n)意味代价高,而O(log(n))意味着代价较低;
forward_list没有size()操作,任何容器的size()操作都是常量时间;
value_type:元素类型;
allocator_type: 内存管理器类型;
size_type:容器下标,元素数目等的无符号类型;
difference_type:迭代器差异的带符号类型;
iterator:行为类似于value_type*
const_iterator:行为类似const_value_type*
reverse_iterator:行为类似于value_type*;
const_reverse_iterator:行为类似于const_value_type*;
referrnce:value_type&;
const_reference: const_value_type&;
pointer: 行为类似于value_type*
const_pointer: 行为类似于const_value_type*;
key_type:关键字类型;
mapped_type:映射值类型,仅关联容器具有;
key_compare:比较标准类型;仅有序容器具有;
hasher:哈希函数类型,仅无序容器具有;
key_equal:等价性函数类型;仅无序容器具有;
local_iterator桶迭代器类型,仅无序容器具有;
const_local_iterator: 桶迭代器类型,仅无序容器具有;
c.assign(n,x): 将x的n个拷贝赋予c;关联容器不适用;
c.assign(b,e)将[b:e)中的元素赋予c;
元素的访问:
c.front(),c.back(),c[i],c.at(i),c[k],c.at(k);
c.emplace_back(args) 用args构造一个对象,将它添加到c的尾元素之后;
c.pop_back();
lst.splice(p,lst2); 将lst2元素插入到p之后,lst2为空;
关联:有序,无序,普通(关键字唯一),多重:每个关键字对应多项;
p = c.emplace_hint(h,args) //从args构造一个类型为c的value_type对象,将它插入到C中
,p指向该对象,h为指向c中的迭代器,可能用来提示从哪里开始搜索存放新元素的位置;
默认情况下,unordered_map
unorder_map
unordered_map m {b,e,n,hf,eql,a}; 构造n个桶的m,初始元素来自于[b:e);使用哈希函数
hf,相等比较函数eql,分配器a;
编写哈希函数最简单的方式是使用标准库hash的特例化版本:
size_t hf(const Record& r) {return hash
bool eq(const R& r1,const R& r2){ return r.name ==r2.name&& r.val=r2.val;};
如果不想定义和实现分享,可以使用labmda:
unordered_set
function
m {10,[](const R& r){ return hash
{(r.val);},
[](const R& r,const R& r2){ return r.name == r2.name && r.val
==r2.val;}};
//模板参数用function()包装,然后实现的时候用了lambda表达式;
也可以使用命名的lambda:
auto hf =[](const R& r){ return hash
auto eq = [](const R& r,const R& r2){ return r.name == r2.name && r.val ==r2.val;
unordered_set
//注意,中间用decltype推断了一下,而不是直接用的lambda命名对象;
在std中特例化hash,equal_to;
namespace std{
template struct hash
二个都一样,都重载了(),成为了函数对象;
使用: unordered_set
h = c.hash_function() // 获取c的哈希函数
eq = c.key_eq() // eq是c的相等检测函数
d = c.load_factor() d是元素数除以桶数:double(c.size()) / c.bucket_count();
d = c.max_load_factor() d是c的最大装载因子;不抛出异常
c.max_load_factor(d) // 将c的最大装载因子设置为d,若c的装载因子己经接近其最大装载因
子,c将改变哈希表大小(增加桶数);
c.rehash(n) 令c的桶数 >=n
c.reserve(n) 留出能空纳n个表项的空间;
无序关联容器的装载因子(load factor)定义为己用空间的比例,如capacity()为100,元素个
数size()=30,则load_factor()为0.3;
比例因子:70%即0.7通常是一个好的选择。
n = c.bucket_count() : n是c中桶数(哈希表大小);不抛出异常;
n = c.max_bucket_count() ; n是一个桶中最大元素数;不抛出异常;
m = c.bucket_size(n) ; m为第n个桶中的元素数;
i= c.bucket(k); 关键字k的元素在第i个桶中
容器适配器为容器提供不同的接口,仅通过其特殊接口使用;不提供直接访问其底层容器的方
式,也不提供迭代器下标操作;
默认情况下,stack用deque保存其元素;
template
stack
queue是一个容器接口,允许在back()中插入元素,在front()中提取元素;
priority_queue是一种队列,其中每个元素都被赋予一个优先级,用来控制元素被top()获取
的顺序;默认情况下,用<运算符比较元素,用top()返回优先级最高的元素;
将forward_list用于通常为空的序列;stl容器都是资源句柄;
map通常实现为红黑树;unordered_map是哈希表;
以传引用方式传递容器参数,以传值方式返回容器;用()初始化器语法初始化大小,用{}初
始化器语法初始化元素列表;
用reserve()避免指向容器元素的指针和迭代器失效;
使用容器上的push_back(),resize(),而不是数组上的realloc();
有序容器序列由其比较对象(默认为<)定义;
如需要在大量数据中快速查找元素,使用无序容器;对无自然序的元素,使用无序容器;
如果需要按顺序遍历元素,使用有序关联容器(map,set);
用异或操作组合标准哈希函数得到的哈希函数通常有很好的性能;
用概念Range表示具有begin(),end()迭代器的任何东西;
迭代器的存在主要是为了将算法从它所处理的数据结构上分离开来;
序列谓词:
all_of(b,e,f)
any_of(b,e,f)
none_of(b,e,f)
bool in_quote(const string& s){
auto p = search(quote.begin(),quote.end(),s.begin(),s.end());
return p!= quote.end();
}
p = copy(b,e,out) // 将[b:e)中的所有元素拷贝至[out:p); p = out+(e-b);
p = copy_if(b,e,out,f) //将[b:e)中满足f(x)的元素x拷贝到[out:);
copy_backward() 从尾元素开始拷贝;带backward的都是从尾部开始;
unique() 去重;unique(b,e,f) 带条件;
unique_copy()不拷贝连续重复的元素;去重拷贝;
remove(),replace();
reverse(b,e) //逆序排序
reverse_copy(b,e,out)
replace() 替换
rotate(),random_shuffle(),partition();
random_shuffle(b,e)//默认随机发生器
random_shuffle(b,e,f);
shuffle(b,e,f) //使用发生器f;
p = partition(b,e,f) //将满足f(*p1)的元素置于区间[b:p)内,将其它置于区间[p:e)内;
p = partition_point(b,e,f) 对[b:e),p指向满足all_of(b,p,f)且none_of(p,e,f)的位置;
is_partitioned(b,e,f) ; [b:e]中满足f(*p)的元素都在满足!f(*p)的元素之前吗?
排列:next_permutation(b,e),prev_permutation(b,e),is_permutation(b,e,b2);
fill,fill_n(),generate(b,e,f); 将f()赋予[b:e)中的每个元素;generate_n(),后面有n的
都是尾部延长了的;
uninitialized_fill(b,e,v) 将[b:e)中的每个元素初始化为v;
uninitialized_copy(b,e,out);
关联容器中元素的关键字是不可变的,因此,我们不能改变set中的值;(集合值不能改变,
因为值就是key).
默认情况下,priority_queue简单用<运算符比较元素,用top()返回优先级最高的元素;
auto p = find(b,e,pred) //问题引出:查找元素pred,还是应用谓词pred()?编译器无法消除
这类代码的二义性,标准库一般使用后缀_if指出算法接受一个谓词。使用两个名字来区分不
同版本减少二义性;
如:auto p1 = find(v1.begin(),v1.end(),pred); //找值为pred的元素;
auto p1 = find_if(v1.begin(),v1.end(),pred); //找令pred为true的元素数;
is_sorted(b,e)
is+sorted_until(b,e);
if(binary_search(c.begin(),c.end(),7)){ //二分法排序,7在c中吗?
auto q = lower_bound(c.begin(),c.end(),7); //二分法:c必须排序好,c.begin(),c.end
().
inplace_merge(b,m,e); 原址合并;
make_heap(b,e) //将[b:e)整理为一个堆;堆的关键特点是提供了快速插入新元素和快速访问
最大元素的能力;堆的最主要用途是实现优先队列;
min(a,b),max(a,b); pari(x,y) = minmax(a,b) // x.放最小值,y放最大值;
min_element(b,e),max_element(b,e), minmax_element(b,e);
pair(x,y) = minmax_element(b,e) //最小最大值
如果不得不处理未初始化的对象,考虑uninitialized_*系列算法;
迭代器机制是为了最小化算法与所操作的数据结构间的依赖性;
一对迭代器定义一个半开区间[begin:end),即所谓序列(sequence);
永远不要从*end读取数据,也不要向它写入数据;空序列满足begin == end;
输出迭代器: ++向前,并用*写入数据;
前向迭代器:反复用++向前童并用*读写元素;
双向迭代器:++,--,并用*(可重入)读取;
随机访问:[]支持下标操作
iterator_traits
iteraotr_traits
input_iterator_tag 输入迭代器类别
output_iterator_tag 输出迭代器类别
forward_iterator_tag 前向迭代器类别,bidirectional_iterator_tag:双向迭代器类别
random_access_iterator_tag: 随机访问迭代器类别;
迭代器标签的本质是类型,用来基于迭代器类型来选择算法,如:
template
void advance_helper(Iter p, int n,random_access_iterator_tag){ p+=n;}
//后面标随机访问标签,这时,迭代器可以随意移动;
template
void advance(Iter p, int n){
advance_helper(p,n,typename iterator_traits
}//根据迭代器不同,定义了多个advance_helper()函数,这个函数根据迭代器不同,自动调
用最优的函数;
标签分发优化(tag dispatch);
typename iterator_traits
Iter::value;
对于迭代器:++p返回p的引用,而p++必须返回一个保存p的旧值副本。因此,对更复杂的迭代
器而方,++p比P++更高效;
迭代器操作: advance(p,n) ; x = distance(p,q); next(p,n), prev(p,n);
在
型;
reverse_iterator 反向遍历
back_insert_iterator: 在尾部插入
forn_insert_iterator
insert_iterator: 任意位置;
move_iterator:移动而不是拷贝
raw_storage_iterator 写入未初始化的存储空间;
*(next(lst.rbegin(),3))='4';//部分迭代器不支持+,[],用next();
插入器(inserter):当写入数据时,插入器将新元素插入序列而不是覆盖己有元素;
fill_n(back_inserter(vi),200,7);//将200个7添加到vi末尾;
通过插入迭代器写元素时,迭代器插入新值而不是覆盖所指向的元素。
3种插入迭代器:
insert_iterator用insert()
front_insert_iterator用push_front()在序列首元素之前插入新值
back_insert_iterator用push_back()在序列尾元素之后插入新值;
一个插入器是一个输出迭代器;不能通过插入器读取数据;
移动迭代器:通过他读取元素时会移动元素而非拷贝元素:mp = make_move_iterator(p);//p
必须是一个输入迭代器;
一个移动迭代器的operator*()简单返回所指向元素的右值;std::move(q);
copy(vs2,make_move_iterator(back_inserter(vs3)));
给自定义容器提供begin()
-----------------------------
template
Iterator
return Iterator
谓词(返回bool的函数)
p = equal_to
p = not_equal_to
logical_and
用法: sort(v.begin(),v.end(),greater
函数适配器:接受一个函数参数,返回一个可用来调用该函数的对象:
g = bind(f,args)
g = mem_fn(f)
g = not1(f); g(x) 表示!f(x);
g = not2(f) g(x,y)表示!f(x,y);
适配器bind()和mem_fn()进行实参绑定,也称柯里化(部分求值)
double cube(double); //只是函数
auto cube2 = bind(cube,2) ;//函数加参数包装成一个函数对象;//_1占位符;
假设有二个重载的pw(int,int); double pow(double,double);
auto pow2 = bind((double(*)(double,double))pow,_1,2)//函数风格的强转;
auto inc= bind(incr,_1);
inc(i); // 如果要引用变量,标准库也有;
r = ref(t) ;
r = cref(t) ; r是const T& t的一个reference_wrapper;
inc(ref(i));
ref()也被用于向thread传递引用实参;
mem_fn(mf)生成一个函数对象,可以作为非成员函数调用;
auto draw = mem_fn(&shape::draw);
draw(p); //p是Shape*的对象;
mem_fn()主要用途是服务于需要非成员函数的算法。通常用lambda替代;
fct可以再赋值; fct = [](double d){ return round(d);};
标准库function是一种类型,它可以保存你能用调用运算符调用的任何对象。即一个function
类型对象就是一个函数对象;
function
function对回调,将操作作为参数传递等机制非常有用;
一个输出序列由单一迭代器定义,程序员应该负责避免溢出;使用序列尾表示“未找到”;
注意bind()会提前解引用,如果你希望推迟解引用,使用ref();
可以使用mem_fn()或lambda将p->f(a)调用规范转换成f(p,a);
如果你需要一个可以保存各种可以调用对象的变量,使用function.
拟容器:T[N]:固定大小数组;
array
pair,tuple是异构的(元素类型可以不相同);array,vector,tuple连续保存元素;
array
初始化了
tuple_size
tuple_element<5,decltype(a)>::type x3 = 13 //x3是一个int;
auto sz = Tuple_size
bitset
不同之处是二进制位用整数而不是关联值;
用内置指针是不可能寻址一个二进制位的,bitset提供了一种位引用类型,bitset不提供迭代
器,位位置从右到左编号,与二进制位在机器中的顺序相同;b[i] 的值为pow(2,i);
bitset<10> b4 {"1010101010"};
b4[i] // b4.test(i); .flip(),flip(i),reset(i),set(i); //移位和一些操作符;
bs.to_ulong(), bs.to_string
bs.none();
vector
tuple_element
get
int a, b, c;
tie(a, b, c) = ti;
一个指针指向一个对象,但是并不能指出谁拥有对象,智能指针出现:
unique_ptr,表示互斥的所有权;
shared_ptr,共享所有权;
weak_ptr,可打破循环共享数据结构中的回路;
一个unique_ptr拥有一个对象,提供了严格的所有权语义;unique_ptr有责任用所保存的指针
销毁所指向的对象;不能拷贝,但可以移动;为动态分配的内存提供异常安全;
将动态分配内存的所有权传递给函数;从函数返回动态分配的内存;在容器中保存指针;
当一个unique_ptr被销毁时,会调用其释放器(deleter)销毁所拥有的对象,释放器表示销
毁对象的方法。局部变量的释放器应该什么也不做,unique_ptr默认(无释放器),使用
delete,它甚至不保存默认的释放器。
unique_ptr up{p,del}; 使用释放器del,不抛出异常;
shared_ptr表示共享所有权,当两段代码需要访问同一个数据,但两者都没有独享所有权(负
责销毁对象)时,可以使用shared_ptr这种计数指针,计数为0释放所指向的对象;
共享对象析构函数的执行时间不可预测,更新算法,逻辑比普通对象的相应算法、逻辑更易出
错;
shared_ptr sp{p,del,a} //使用释放器del,和分配器a;
p= sp.get() // p = sp.cp 不抛出异常
n= sp.use_count();(1,"Ankh mor",4.65);
weak_ptr指向一个shared_ptr所管理的对象,为了访问对象,可使用成员函数lock()将
weak_ptr转换为shared_ptr,weak_ptr允许访问他人拥有的对象;
115-分配器-容器和string都是资源句柄,获取和释放内存来保存其元素,为此,使用分配器
,分配器的基本目的是为了给定类型提供内存资源以及提供内存不再需要时将其归还的地方;
基本的分配函数有:
p = a.allocate(n) 为n个类型为T的对象获取空间 //申请
a.deallocate(p,n); 释放p所指向的保存n个类型为T的对象的空间; //释放 归还
所有标准库容器都(默认)使用默认分配器,它用new分配空间,用delete释放空间;
分配器萃取:allocator_traits
使用萃取技术使得我们可以为一个类型构建分配器,该类型的成员类型可以不满足分配器集合
提供了默认值;分配器用pointer_traits确定指针及指针代理类型的属性;
scoped_allocator类,提供一种机制来跟踪外部分配器(用于元素)和内部分配器(传递给元
素供它们所用);和allocator一样,只是有能力跟踪传递给包含的容器使用的内部分配器;
垃及收集器可能无法避免并非纯内存的资源的泄漏,如文件句柄,线程句柄,锁;
void declare_reachable(void* p) //p指向的对象不能被回收;
void declare_no_pointers(char* p,size_t n) //p[0:n)中没有指针
void undeclare_no_pointers(char*p, size_t n); //撤销一次declare_no_pointers();
c++垃圾收集器传统上是保守收集器(conservative collector);即它们不移动内存中的对象
且假定内存中的每个字都可能包含指针;程序员可以询问哪些指针安全性规则和回收规则是有
效的:
enum class pointer_safety{relaxed,prefered,strict};
pointer_safety get_pointer_safety();
当编写内存分配器,实现容器以及直接处理硬件时,直接使用未初始化内存,也称为裸内存
(raw memory)是必要的;除了标准的allocator,
处理未初始化内存;
临时空间分配器:get_temporary_buffer(ptrdiff_t); //分配但不初始化;
return_temporary_buffer(T*); //释放但不销毁;是一对,用上面的申请,用这个释放才能
再用;
get_temporary_buffer
管理临时缓冲优化,不能替代new;
second为申请的长度,这里是100;
值代价可;
他绝不能用于写入己初始化的数据;
auto bs = get_temporary_buffer
auto p = raw_storage_iterator
generate_n(p,a.size(),[&]{next_permutation(seed,seed+sizeof(seed)-1); return
seed;});//用
return_temporary_buffer(p); //归还;raw_storage_iterator没有==或!=,不要范围[b:e)
写入数据;不要随意使用未初始化内存,除非确实有必要这么做;
使用pair,tuple时,使用make*进行推断;
尽量不使用weak_ptr,仅当由于逻辑上或性能上的原因,常用的new/delete语义不能满足需求
时才使用分配器;优先选择有特定语义的资源句柄而不是智能指针;优先选择unique_ptr,而
不是shared_ptr;
优先使用智能指针而不是垃圾回收;
在大量使用指针的程序中处理泄漏问题,垃圾收集是非常有用的;如果使用垃圾收集,使用
declare_no_pointers()令垃圾收集器忽略不可能包含指针的数据;
duration等待一段时间;给定时刻:time_point;
如果希望获得当前的time_point,可以调用now
(),system_clock,steady_clock,high_resolution_clock
steady(稳定的)
steady_clock::time_point t = steady_clock::now();
//operator...
steady_clock::duration d = steady_clock::now() - t;
std::cout << duration_cast
duration表示两个时间点time_point间的距离;
r=d.count() //r是d中的时间周期数;constexpr;
duration
duration
)
duration的period保存时间周期(clock tick )数:
duration
microseconds,milliseconds,seconds,minutes,hours;
一个纪元(epoch)就是由给定clock确定的一个时间范围,用duration来衡量,从
duration::zero()开始;
is_steady:此时钟类型稳定吗?间隔是常量吗?
system_clock:系统实时时钟,steady_clock:时间稳定推移时钟,即,时间不会回退且时钟周
期的间隔是常量;high_resolution_clock:一个系统上具有最短时间增量的时钟;
类型萃取:is_void
is_lvalue_reference
is_member_object_pointer,is_member_function_pointer
s_function;
is_reference
型,is_object
is_compound
is_const
贝?
is_standard_layout
的成员?
is_polymorphic
is_unsigned
is_default_constructible
is_copy_constructible
is_assignable
is_move_assignable
if(is_trivially_copyable
uninitialized_copy(...)
如果是平凡可拷贝的,调用memcpy(),否则,调用uninitialized_copy(...)使用拷贝构造函数
;
is_same
类型生成器:type_traits中提供了从一个给定类型实参生成另一个类型的类型参数:
remove_const
remove_volatile
一个类型转换器返回类型:pair
remove_reference
add_lvalue_reference
add_rvalue_reference
decay
make_signed
make_unsigned
数组修改: remove_extent
remove_all_extents
指针修改:remove_pointer
add_pointer
对齐:aligned_storage
aligned_storage
aligned_union
enable_if 若b==true得到X;否则将不会有成员::type,从而导致
conditional 若b==true,得到X,否则得到F;
common_type
类型,则它们是共同的;解释?:三元运算符,通过隐式转换:确定所有类型 T... 的共用类
型,即所有 T... 都能隐式转换到的类型。若这种类型(根据后述规则确定)存在,则成员
type 指名该类型。否则,无成员 type 。
underlying_type
result_of
result_of用来抽取一个可调用对象的结果类型;//获取返回值类型;通常用于模板中我们不
能容易地从程序上下文获取答案的地方;
declval
declval的返回值;永远不要使用declval的返回值;
正则表达式迭代器: regex_replace(); //希望遍历一个字符序列,对模式的每个匹配执行一
些操作;
默认的正则表达式符号表示为ECMASCRIPT;
用regex_search()在字符流中查找模式,用regex_match()查找特定字符串;
ostream将有类型的对象转换为字符流(字节流);
流缓冲区streambufs;
ios_base: 区域无关的格式状态; basic_streambuf<> 缓冲 basic_ios<>区域相关的格式
状态流状态;
basic_iostream<>格式化<< ,>>等; locale:格式信息;
using int_type = typename Tr::int_type;
ios_base定义的打开模式:ios_base::app,ios_base::ate(打开文件并定位到文件
尾),ios_base::binary;ios_base::in(读),ios_base::out(写);
ios_base::trunc(清空);
ostringstream oss {"Label:",ios::ate}; //ios::ate尾追,如果不加第二个参数,就覆盖
oss << "val"; // 尾追了 “val”;
把字串当流处理了;
istringstream iss;
iss.str("Fooooobar");
cout << iss << '\n'; 输出1;其实输出是流状态,要输出字串,得.str()
4种状态:good(),eof(),fail(),bad();
if(cin>>i){ ....
} else if (cin.fail(){ cin.clear(); //失败,只是清了一个状态,继续读取;
异常控制:
st = ios.exceptions() ; st为ios的iostate(io状态码)
ios.exceptions(st) ; 将ios的iostate(状态码)设置为st;
cin.setstate(ios_base::badbit) ;
cin.exceptions(cin.exceptions()|ios_base::badbit);
in.sync() 同步缓冲区;
pos = in.tellg() // in的读取指针的位置
in.seekg(pos) 设置pos
in.seekg(off,dir); //将in的读取指针在方向dir上移动偏移off;
在程序执行过程中,若在第一个iostream操作之前调用了sync_with_stdio(true),标准库保
证iostream和标准输入输出操作共享缓冲区,而在第一个流操作之前调用sync_with_stdio
(false)则会阻止共享缓冲区,这在某些实现中能显著提升IO性能;
操作seekg(),seekp()的定位方向符: beg,end,cur;//ios_base方向seekdir成员常量;
st = ios.rdstate() //st为ios的iostate;
ios.setstate(st) //将st添加到ios的iostate;
ios.bad()//为故障吗?
st = ios.exceptions() //st为ios的iostate的异常位;
p = ios.tie() p为指向连接的流的指针或nullptr;
p = ios.tie(os) 将输出流os连接到ios,p指向之前连接的流或为nullptr;
p = ios.rdbuf(p2) 将ios的流缓冲区设置为p2指向的缓冲区;p指向之前的流缓冲区;
iosOld = ios.copyfmt(ios2); //复制格式信息;
locOld = ios.imbue(loc); //将ios的区域设置为loc,locOld为之前的区域设置;
s>>skipws //ios操纵符,忽略空白符;
endl; 输出回车 ;ends 输出\0;
-----
ostream& operator<<(ostream& o, Person& p) {
o <<"User Name:"<< p.name << endl;
o << "User Age:" << p.age << endl;
return o;
}
----------------
1.ostream& 返回引用,无变量,因为没办法复制;
2.ostream& o也是引用;无法复制;
3.返回值为o的引用不要忘;
输出流将字符放入一个缓冲区中,这种缓冲区被称为streambuf,
streambuf实现不同类型的缓冲策略,通常,streambuf将字符保存在一个数组中,直至发生溢
出;才被迫将字符写到其真正的目的地。
每个streambuf都有一个存放区(put area), << 和其他输出操作会写入到这里,以及一个读取
区(get area) >>和其他输入操作从此读取数据。两个区域都由一个头指针,当前位置指针
和一个尾后指针描述;
ios_base::seekdir指出方向;fout.seekp(-4,ios_base::cur) //当前位置向后移动4字符;
函数readsome()是一个底层操作,它允许用户窥探一个流是否有字符可供读取;
一个locale对象表示一组文化编好,如:字符串如何比较,人类可读的数值输出格式以及字符
在外存中的表示形式。区域设置的概念是可扩展的,程序员可以向一个locale添加新的facet,
表示标准库不直接支持的区域相关的实体;locale在标准库中的主要用途是控制输出到
ostream的信息的呈现形式以及格式化从istream读取的数据;
不要假定每个人都像你一样使用相同的字符集;
数值计算:numeric_limits
CHAR_BIT一个char有多少位;
INT_MIN 最小int值
LONG_MAX 最大long值;
FLT_MIN最小浮点值;DBL_MIN 最小double值
前题条件域指出分布参数的要求: uniform_int_distribution
uniform_int_distribution
default_random_eigine e;
uid2(e); //指出结果(分布范围对象以一个随机引擎为参)
如果一项活动可能与其他活动并发执行,称之为任务,线程是执行任务的计算机特性在系统层
面的表示;
一个标准库thread可执行一个任务,一个线程可与其他线程共享地址空间;
线程库:thread,condition_variable,mutex;
提高效率,减少错误,应尽可能高的层次上编程,优先选择future,而不是mutex实现信息交换
,优先mutex,则非atomic;
一个锁就是一个mutex以及任何构建于mutex之上的抽象,用来提供对资源的互斥访问或同步多
个并发任务的进度;
进程(process)即运行于独立地址空间;标准库线程支持是类型安全的;内存模型是计算机
设计师和编译器实现之间关于计算机硬件最佳表示方式的讨论结果;对内存中对象的操作永远
不直接处理内存中的对象,
对内存中对象的操作永远不直接处理内存中的对象,而是先将对象加载到处理器的寄存器中,
在那里修改,然后再写回内存;通常首先从主存加载到缓存中,然后再加载到寄存器。内存可
被多个线程共享,而缓存也可以被运行于相同的不同;C++语言将内存位置定义为能保证合理
行为的内存单元,从而排除了单独的位域;
如果你需要共享数据,应确保在恰当的位置正确的同步;
内存序用来描述一个线程从内存访问一个值时会看到什么;
不必忍受数据竞争的原语操作通常被称为原子操作(atomic operatoion),可用来实现高层并
发机制,如锁,线程和无锁数据结构;内存序默认为memory_order_relaxed,
consume,acquire,release,acq_rel,seq_cst;
memory_order_acq_rel,memory_order_seq_cst
同步特性:原子标志和栅栏;
atomic_flag: 是最简单的原子类型,有二种可能的值: set,clear;
ATOMIC_FLAG_INT:清除是初始化atomic_flag的唯一可移植且可靠的方法;atomic_flag自旋锁
;
class spin_mutex{
atomic_flag flag = ATOMIC_FLAG_INIT;
public:
void lock(){ while(flag.test_and_set());}
void unlock(){ flag.clear();} //自旋锁容易产生很高的代价;
};
栅栏:fence:内存屏障(是一种根据某种指定内存序)来限制操作重排的操作,可以看作一种
简单地减慢程序到安全速度的方法,从而令内存层次达到定义良好的合理状态;
atomic_thread_fence(order) 强制内存序为order;
atomic_signal_fence(order) 强制内存序为order,用于线程及运行于线程上的信号处理函数
;
volatile用来指出一个对象可能被线程控制范围之外的东西修改;
volatile说明符主要是告知编译器不要优化掉明显冗余的读写操作。一般用于硬件底层;
优先使用packaged_task和future,而不是直接使用thread,mutex;优先选择
mutex,condition_variable,而不是直接使用atomic;
如果一项活动可能与其他活动并发执行,我们就称为任务,线程是执行任务的计算机特性的系
统层面表示;
thread是计算的概念在计算机硬件层面的抽象,C++标准库thread的设计目标是与操作系统形
成一对一映射,
所有thread工作于同一个地址空间中,如果你希望硬件能防止数据竞争,则应使用进程;
thread不共享栈,局部变量不会产生数据竞争问题,要特别注意lambda中的引用绑定上下文;
若一个thread不能继续前进,我们称为阻塞(blocked)或睡眠(asleep)状态;
native_handle_type:系统线程句柄类型:由具体c++实现定义;
thread t{}; //默认构造函数,创建一个还没有任务的线程,无异常;
thread t{t2}; //移动构造函数,不抛出异常;
thread t{f,args}; //线程上执行f(args);
t.~thread(). 析构 若t.joinable(),则terminate(),否则无效;
t=move(t2); //移动赋值;t.swap(t2), t.joinable(); t.join(), t.detach(), x =
t.get_id()
x = t.native_handle(); n = hardware_concurrency(); swap(t,t2);
一个thread表示一个系统资源,一个系统线程,甚至可能有专用硬件: thread=系统线程;
thread可以移动但不能拷贝;作为一个源被移动后,thread就不再表示一个计算线程了;不能
被join()了;
thread::hardware_concurrency() 报告硬件支持多少个任务同时执行;
t.get_id() 得到线程id; 当前线程id: this_thread::get_id();
一个系统线程仍可以在没有id的情况下运行(即detach()之后);
在下列情况下,一个thread的id可以是id{};
它并未被赋予一个任务,它己经结束,它己经被移动,它己经被detach();
t.join()告诉当前thread在t结束之前不要继续前进;
如果thread是joinable()的,即get_id() != id{}.therad析构函数调用terminate()结束程序
;
this_thread::sleeep_for(second{1});
t.join()告诉当前线程在t结束之前,不要继续前进;(即t运行,当前线程挂起,t运行完后
,当前线程继续)
join令主程序等待timer结束;
如果希望一个系统线程比其thread(句柄)活跃更久,应使用(detach());
thread t{heartbeat};
t.detach(); //令heartbeat独立运行;//分离的线程;
如果必须detach()一个线程,请确保它没有引用其作用域中的变量;
不要将一个局部对象的指针传递出其作用域之外,
在任何一个时间点上,线程是可结合(joinable)或者是可分离的(detached),一个可结合
的线程能够被其他线程回收资源和杀死,在被其他线程回收之前,它的存储器资源如栈,是不
释放的,相反,一个分离的线程是不能被其他线程回收或杀死的,它的存储器资源在它终止时
由系统自动释放。
态的,这种情况下,原有的线程等待创建的线程结束,只有当pthread_join函数返回时,创建
的线程才算终止,释放自己占用的系统资源,而分离线程没有被其他的线程所等待,自己运行
结束了,线程也就终止了,马上释放系统资源。
对当前线程的操作定义在名字空间 this_thread中:
x = get_id(), yield(), sleep_until(tp); sleep_for(d);
如:thread::id me {this_thread::get_id()};
this_thread::yield() 用来给其他线程运行机会,当前thread不会被阻塞,无需任何其他的
线程做任何特殊操作来唤醒它,它最终也会重新运行,因此,yield()主要用来等待一个
atomic改变状态以及用于协调多线程。
sleep_for(n)效果更好,参数能令调度器更好地合理选择运行哪个thread,可将yield()看作一
个很罕见和特殊的情况下用来进行优化的特性;
不应乱用系统时钟:wait_until()会受系统时间影响。wait_for()不会;
一个thread_local变量是一个thread专有对象,其他的thread不能访问,除非指向它的指针传
给了其他线程;thread_local则被一个线程所有函数共享,只要线程活跃,它就活跃,
thread_local对象可以是extern的;
在很多的系统中,一个线程的栈空间是有线的,因此对于大量非共享数据的任务需要
thread_local存储;每个thread对thread_local变量都有自己的拷贝,thread_local在首次使
用前初始化,如果己构造,会在thread退出时销毁。
thread_local存储的一个重要用途是供线程显式缓存互斥访问数据,在具有共享缓存的机器上
,有时能提升性能,通过数据的大批量传输能简化或降低锁的代价;
非局部内存是并发编程的一个难题,因为确定数据是否共享通常不是那么简单;
应保证不同thread_local的构造与它们的顺序无关;而且尽可能使用编译时或链接时初始化;
避免数据竞争的最好方法是不共享数据;
互斥量(mutex):用来表示某个资源互斥访问权限的对象,为访问资源,先获取互斥量,然后
访问数据,最后释放互斥量;
条件变量:一个线程用条件变量等待另一个线程或计时器生成的事件;避免引入可能引起数据
竞争的共享数据。
mutex: 一个非递归互斥量,尝试获取一个己被获取的mutex,线程会阻塞;
recursive_mutex:可被单个线程重复获取的互斥量;
timed_mutex:只在指定时间内获取的互斥量;
recursive_timed_mutex: 递归限时互斥量;
lock_guard
unique_lock
在任何时刻,一个互斥量只能被一个thread所拥有,获取(acquire)一个互斥量意味着获得
它的排他所有权,获取操作可能阻塞执行它的线程;
释放(release)一个互斥量意味着放弃排他所有权;释放能令正在等待的thread退出阻塞状
态;若多个线程阻塞在同一个mutex上,系统调度器会选择其中一个解除阻塞状态.
code:
mutex cout_mutex; //表示使用cout的权限
void write(){
thread::id id_name = this_thread::get_id();
cout_mutex.lock();操作前锁
cout << "from thread" << id_name << endl;
cout_mutex.unlock();//操作完合解锁
}
被锁保护的代码段被称为临界区(critical section).为了保持代码的高效性以及免受锁相
关问题,应尽量减小临界区;
标准库提供排他所有权语义,单一线程在某个时刻拥有对资源的排他访问权;
mutex 相关操作: mutex m{} ; 默认,m不被任何线程所拥有,constexpr;不抛出异常;
m.lock(); 获取m;线程阻塞直至获取所有权;
m.try_lock() 尝试获取m;返回是否获取成功的结果;
m.unlock(); 释放m
nh = m.native_handle() //nh为互斥量m的系统句柄;
mutex错误: 抛出system_error;
catch(system_error& e){ mtx.unlock();cout << e.what(); << e.code()}
device or resource busy 错误代码:16
如果不希望阻塞,可以使用mtx.try_lock();当获取mtx失败时,常希望等一会再试,
timed_mutex和recursive_timed_mutex提供了这种功能;
timed_mutex相关操作:m.try_lock_for(d)//最多等待d;
m.try_lock_until(tp) 最多等待到timepoint tp;
对于this_thread,可以sleep_until(tp)到一个time_point,以及sleep_for(d)一个duration;
锁是一种资源,
s = vs[i];
} //利用RAII,资源声明即初始化原则,离开作用区即解锁;
lock_guard不含什么有趣的操作,它所做的事情就是对mutex实现RAII,为了获得一个提供内含
mutex上的RAII和操作的对象,应使用unique_lock:
-----
unique_lock
unique_lock lck {m}; //{m,try_to_lock_t},{m,tp}均可作为构造参数
lck2=move(lck)移动赋值操作:lck保有lck2原有的(如果有的话)互斥量,lck2不再保有
互斥量;
lck.try_lock(),lck.try_lock_for(d),try_lock_until(tp),lck.unlock(),lck.swap(lck2)
pm = lck.release(),lck.owns_lock()
-----------------code
timed_mutex mtx2; //仅当互斥量是一个timed_mutex或recursive_timed_mutex时,才允许限
时操作;
unique_lock
lck2.try_lock_for(milliseconds{2});
如果传给unique_lock的构造函数一个duration或time_point作为第二个参数,构造函数会执
行恰当的尝试加锁操作,owns_lock()操作允许我们检查获取操作是否成功;
code:
timed_mutex mtx2;
void use2(){
unique_lock
if(lck2.owns_lock()){ //获取成功,执行一些操作;esle { //超时,做一些其它}}}
ref()是引用包装器来自
locks是一个或多个可锁对象构成的的序列lck1,lck2,lck3...
x = try_lock(locks) 尝试获取locks的所有成员;这些锁是按顺序获取的,若所有锁都成功
获取,x=-1,否则x=n,其中n为不能获取的锁的数量,且不会保有任何锁;
lock(locks) 获取locks的所有成员,不会产生死锁;
类型once_flag和函数call_once()提供了一种高效且简单的底层工具:
once_flag fl{};默认构造函数,fl未使用;
call_once(fl,f,args);若 fl尚未使用,调用f(args),它简单地修改并发前代码,这些代码
依赖于己初始化的static数据;
class X{
public:
x();
private:
static once_flag static_flag;
我们用条件变量管理thread间通信,一个thread可等待(阻塞)在一个condition_variable上
,直至发生某个事件,如到达一个特定时刻或者另一个thread完成;
condition_variable相关操作(lck必须是一个unique_lock
condition_variable cv{};
cv.notify_one()解除一个等待thread(如有)的阻塞状态,不抛出异常;
cv.notify_all();解除所有等待线程的阻塞状态;
cv.wait(lck) // lck必须被调用线程所拥有,cv.wait(lck,pred);
cv.wait_for(lck,d),cv.wait_for(lck,d,pred)
//enum class cv_status { no_timeout,timeout };
cv.wait_until(lck,tp,pred)
nh=cv.native_handle() //nh为cv的系统句柄;
condition_variable可能依赖系统资源,因此最好将condition_variable理解为资源本身,而
非资源句柄;
任务支持:
packaged_task
promise
future
shared_future
x = async(policy,f,args) 根据policy调用f(args)
x = async(f,args) //根据默认策略调用:x=async(launch::async|
launch::defered,f,args)
res = task(args); //给定参数,执行一个任务,得到结果
并行变成: auto handle = async(task,args);
//……进行一些操作
res = handle.get() //得到结果
任务间的通信由一对future和promise处理,任务将其结果放一个promise,需要此结果的任务
则从对应的future提取结果;
一个promise就是一个共享状态的句柄,它是一个任务可用来存放其结果的地方;供其他任务
通过future提取;
packaged_task保存了一个任务和一个future/promise对;
将待执行任务(一个函数或一个函数对象)传递给package_task,当任务执行到reutrn,会引发
在packaged_task的promise上调用set_value(x).
make_ready_at_exit()的优点是直至thread_local变量函数执行完毕,任务的结果才可用;
int,参数有一个int,这里是指ff函数;
pt1(1); //令pt1调用ff(1);
pt2(0) //令pt2调用ff(0);
get_future()操作用来获取future,其中保存着包装线程执行的结果;
auto v1 = pt1.get_future();
auto v2 = pt2.get_future();
try{ cout << v1.get();v2.get();}
catch(exception& e){ e.what() }
主要目的可以集中精力指明任务,而不是思考线程和锁;packaged_task被调用,其任务会将
结果保存在future中,即不必了解是哪个thread执行它,也不必了解是哪个线程接收结果;
future就是共享状态的句柄,它是任务提取由promise存放结果的地方;
sf = fu.share() 将fu的值移入一个shared_future sf; fu不再包含共享状态;
x = fu.get() fu的值被移入x; 如果fu中保存的是一个异常,抛出它,fu不再包含共享状态;
不要尝试get()两次;
fu.valid() // fu有效吗?
fu.wait() // 阻塞,直到有一个值来
fs = fu.wait_for(d) // duration d
fs = fu.wait_until(tp)
future保存一个独一无二的值,它并不提供拷贝操作;只能被移出,因此get()只能调用一次
;如果要多次读取一个结果,应使用shared_future;
future的状态: enum class future_status: ready,timeout,deferred;
wait_for_all(args) 等待,直至args中每个future都有一个值;(所有)
wait_for_any(args): 等待,直至args中某个future有了一个值;(有就行)
future_status s = fu.wait_for(seconds{0}); //获取future是否就绪的方法;
shared_future与future非常相似,关键差别是shared_future将其值移动到可以被反复读取及
共享的位置;
有了future,promise,以及packaged_task,我们就可以编写简单的任务而不必过于担心thread
了;启动多少个线程,决策由线程启动器完成,这是一个函数,它决定是否创建一个新的
thread,回收一个旧thread或简单地在当前thread上运行任务。
fu = async(policy,f,args) 根据启动策略policy执行f(args);
fu = async(f,args) // = fu=async(launch::async| launch::deferred,f,args);
async()返回一个future
code: double square(int i) { return i*i;}
future
double d = fd.get();
// auto fd = async(square,2);
auto d = fd.get();
2种策略: async:就像创建了一个新thread执行任务一样; deferred:在对任务的future执
行get()的时刻执行任务;
thread是系统线程的类型安全接口;不要销毁正在运行的thread,用join等待线程结束,考虑
用guarded_thread为线程提供RAII;不要轻易detach()一个线程;用lock_guard,unique_lock
管理互斥量;用lock()获取多重锁;用condition_variable管理线程间通信;用promise返回
结果,从future获取结果;不要对一个promise两次执行set_value()或set_exception();用
packaged_task管理任务抛出异常以及安排返回值;不要使用future两次使用get();
尽量将并发隐藏在并行算法接口之后;
C风格内存操作:q = memcpy(),memmove(),memcmp(),memchr(),memset(),calloc(),realloc
(),free();
t=clock() //t为clock_t类型;
t=time(pt)
d= difftime(),ptm=localtime(pt);ptm = gmtime(pt); t = mktime(ptm)
p = asctime(ptm) c风格字串表示;
p = ctime(t) // p = asctime(localtime(t));
n = strftime(p,max,fmt,ptm) //格式由fmt控制;
只对裸内存使用C内存管理例程,如memcpy();C库不了解构造析构,要小心使用;
优先使用chrono,而不是ctime;
当不声明为指针或引用时,auto的推导结果和初始化表达式抛弃引用和cv限定符后类型一致;
当声明为指针或引用时,auto的推导结果将保持初始化表达式的cv属性;
仅希望得到类型,不需要定义变量的时候用decltype(expr)
decltype(exp) exp是函数调用,则推断返回值类型;若expr是一个左值,则推断的是一个左值的
decltype(()) : 括号表达式也是一个左值,因此可以按照推导规则3,知道decltype的结果也将是
delimiter界定符;
----
template
using str_map_t = std::map
str_map_t
模板参数的填充顺序从右往左;
类对象一开始没值,这时,重载构造;(this&); 如果有值,这时,重载赋值运算符;operator=,
C++11增加了std::function和std::bind,不仅让我们使用标准库函数时变得更加方便,且还能方便
operator fr_t (void) { return func; } //声明了函数指针转换函数的类可以转换为函数指针;
可调用对象包装器-std::function; //std::function
void call_when_even(int x, const std::function
std::bind用来将可调用对象与其参数一起进行绑定,绑定后的结果可以使用std::function进行保
bind可以将多个形参的可调用对象转换成一个形参或N个形参的对象,即只绑定部分参数;
auto fr = std::bind(output_add2,std::placeholders::_1);
class A{
public:
int i_ = 0;
void output(int x,int y){ std::cout << x << " " < y << std::endl;}};
//A对象有一个成员函数;
std:: function
fr(1,2);//把对象的成员函数也一起绑定;
std::cout_if(c.begin(),c.end(),std::bind(less
[this]捕获当前类中的this,让lambda表达式拥有和当前类成员函数同样的访问权限;目的是可以
如果需要修改按值捕获的外部变量,需要显示指明lambda为mutable;
int a = 0;
auto f1 = [=]{ return a++;};
auto f2 = [=]() mutable {return a++;};
//没有捕获任何变量的lambda表达式,可以被转换为一个普通的函数指针:
using func_t = int(*)(int); //声明函数指针,有一个int形参,返回int;
func_t f = [](int a){return a;}; //没有捕获任何变量,把值直接给了指针;
f(120); //调用
lambda和std::function的效果是一样的,如果老版本的boost不支持lambda,还需要function;
tuple
std::tuple
int x, y, z;
std::tie(x, y, z) = tp; //用tuple结合tie赋三个值;
std::tie(std::ignore, y, z) = tp; //用tuple结合tie赋三个值;跳过第一个值;
std::get
右值引用:T&&,左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不再存在的
右值由两个概念构成,一个是将亡值,另一个是纯右值;将亡值是C++11新增的,与右值引用相关
const A& a = GetA(); //常量左值引用是一个万能引用类型;
需要注意的是普通的左值引用不能接受右值;A& a = GetA();//错误,非常量左值引用只能接受左
template
void f(const T&& param); //是右值引用类型,T&&在被const修饰后成为右值引用了;
由于存在T&&这种未定的引用类型,当它作为参数时,有可能被一个左值引用或右值引用的参数初
int w1,w2;
auto&& v1 = w1;//v1被一个左值初始化,导致最终v1是一个左值;
decltype(w1)&& v2 = w2; //v2是一个右值引用,却被左值初始化,出错;如果希望把一个左值赋
if(std::is_rvalue_reference
abi::__cxa_demangle在GCC中将低级符号解码成用户看得懂的名字;
char* name = abi::__cxa_demangle(typeid(Foo*[10]).name(),nullptr,nullptr,nullptr)
//name为Foo*[10] 获取类型且可读;
左值和右值是独立于它们的类型的,右值引用可以是左值,也可能是右值;
auto&&,自动推导T&&,是一个未定的引用类型,被称为universal references,它可能是左值引用也
move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存拷贝。
move实际上并不能移动任何东西,它唯一的功能是将一个左值强制转换为一个右值引用,使我们可
一个右值引用参数作为函数的形参,在函数内部再转发该参数时,由于是具名变量,己变为一个左
用emplace_back减少内存拷贝和移动;能就地通过参数构造对象,不需要拷贝或移动内存,比
应尽量用emplace,emplace_back;依赖于构造函数构造;
map和set内部是红黑树,在插入元素时会自动排序,而无序容器内部是散列表,通过hash而不是排
由于是散列表,无序容器的key需要提供hash_value函数;对于基本类型来说,不需要提供Hash和
type_traits提供了丰富的编译期计算,查询,判断,转换和选择的帮助类;
在C++11中定义编译期常量,无须自己定义static const int或enum类型,只需要从
template
struct GetLeftSize:std::integral_constant
is_same 判断两个类型是否相同;
is_base_of 判断类型是否为derived类型的基类;
is_convertible; 是否能转换;
std::is_same
class A{}; // class B:A{}; //B继承A;
std::is_base_of::value ; // true; A是基类,B是派生类,注意方向;
类型转换:remove_const,add_const,remove_reference;add_lvalue_reference;
remove_extent;移除数组顶层的维度;
remove_all_extents;移动数组所有维度;remove_pointer;add_pointer;
对于普通类型来说,std::decay是移除引用和cv符,对于函数来说是添加指针;
typedef std::decay
根据条件选择的traits:
conditional;
typedef std::conditional
typedef std::conditional
如果希望推导出成员函数的返回类型,则需要借助std::declval;
通过declval()获得了A的临时值,declval获取的临时值不能用于求值,需要用decltype来推断
std::result_of::type i =4 ; // A是函数对象,int是形参类型;
实则上面的等价于: decltype(std::declval()(std::declval
//先declval函数对象,再declval参数;
template
typename std::enable_if
return t;}
auto r = foo(1); //返回1,给个字串,类型不符,则出错err;
如需要用参数包中的参数,则一定要将参数包展开,则一定要将参数包展开,
逗号表达式:
template
void expand(Args... args){
int arr[] = {(printarg(args),0)...};
//std::initializer_list
//std::initializer_list
}
不能将一个原始指针直接赋给一个智能指针,他需要自己管理分配的内存,需要通过构造函数和辅
std::shared_ptr
std::shared_ptr
int* p = ptr.get(); //获取原始指针;
指定删除器:
void DeleteIntPtr(int* p){
delete p;
}
std::shared_ptr
std::shared_ptr
不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针;
unique_ptr是一个独占型的智能指针,它不允许其它的智能指针共享其内部的指针,不允许通过赋
unique_ptr不允许复制,但可以通过函数返回给其它的unique_ptr,也可以通过std::move来转移到
unique_ptr
unique_ptr指定删除器的时候需要确定删除器的类型;
std::unique_ptr
正确返回this的shared_ptr的做法是让目标通过派生std::enable_shared_from_this
自定义删除器:std::unique_ptr
weak_ptr弱引用的智能指针是用来监视shared_ptr的,不会使引用计数加1;它不管理shared_ptr
通过use_count()观测引用计数:
shared_ptr
weak_ptr
通过expired()方法判断所观测的资源是否被释放;if(wp.expired())
通过lock()方法来获取所监视的shared_ptr; // auto spt = wp.lock();
void* p = GetHandle()->Create();
std::shared_ptr
--------
std::vector
std::vector
glist.push_back(std::move(t));
glist2.push_back(std::make_shared
c++11提供了四种互斥量:
std::mutex;独占的互斥量,不能递归使用;
std::timed_mutex;带超时的独占互斥量,不能递归使用;
std::recursive_mutex: 递归互斥量
std::recursive_timed_mutex; 带超时递归互斥量;
lock()来阻塞线程,直到获得互斥量的所有权为止,在线程获得互斥量并完成任务之后,就必须使
try_lock()尝试锁定互斥量,如果成功则返回true,失败则返回false;它是非阻塞的;
lock_guard可以简化lock/unlock的写法;//std::lock_guard
一个线程同一时间只能获取一个互斥量,一个线程要多次加同一把锁,就要用递归锁;允许同一个
std::chrono::milliseconds timeout(100);
std::timed_mutex mutex;
if( mutex.try_lock_for(timeout) //{ 100毫秒后;解锁
条件变量:另一种用于等待的同步机制,它能阻塞一个或多个线程;直到收到另外一个线程发出的
condition_variable: 配合std::unique_lock
condition_variable_any,和任意带有lock,unlock的mutex搭配
条件变量的使用过程如下:
1.拥有条件变量的线程获取互斥量;
2.循环检测某个条件,如果不满足,阻塞直到条件满足,如果条件满足,则向下执行;
3.某个线程满足条件执行完之后调用notify_one,或notify_all唤醒一个或者所有的等待线程;
std::condition_variable_any m_not_Empty;
std::mutex m_mutex;
m_not_Empty.wait(m_mutex);
使用原子变量就不需要mutex了,更底层;
call_once,在多线程中保证某个函数仅被调用一次;
std::once_flag flag;
std::call_once(flag,[](){ std::cout << "called once" << std::endl;}
多线程调用这个函数多次,也只会执行一次;
异步相关的类:std::future,promise,std::package_task;
std::future作为异步结果的传输通道;获取线程函数的返回值;std::promise用于包装一个值,
因为一个异步操作的结果是一个未来的期待值,所以被称为future,future提供了获取异步操作结
do{...} while (status != std::future_status::ready);
协助线程赋值的类:std::promise; 将数据和future绑定起来,为获取线程函数中某个值提供方便
std::promise
std::thread t([](std::promise
可调用对象的包装类:std::package_task; 包装了一个可调用对象的包装类(如function,lambda
std::future提供了一个访问异步操作结果的机制,它和线程是一个级别的,属于低层次的对象,
future和promise和package_task用来作为异步操作或异步结果的连接通道,用std::future和
std::async比std::promise,std::packaged_task和std::thread更高一层,它可以用来直接创建异
原型:aysnc(std::launch::async| std::launch::deferred,f,args...)多了一个参数,是立即执
std::future
std::cout <
typedef duration
typedef duration
std::this_thread::sleep_for(std::chrono::seconds(3));
-----------------------
using namespace std::chrono;
void main() {
system_clock::time_point now = system_clock::now();
std::time_t last = system_clock::to_time_t(now - hours(24));//当前日期减一天
std::cout << std::put_time(std::localtime(&last), "%F %T") << '\n';
}
------------------------
std::chrono::duration_cast
from_time_t方法将ctime转换为time_point;
Timer():m_begin(high_resolution_clock::now()){}//高精度计时器;
将字串转换成整形或浮点:
atoi: ascii to int;
atol
atoll
atof //字串转浮点
std::wstring str = L"中国人";
C++11的继承构造函数可以让派生类直接使用基类的构造函数;用于解决派生类隐藏基类同名函数
struct Derived:Base{ using Base::Base;};//声明使用基类的构造函数;
原始字符串字面量定义的是R"XXX(RAW STRING)XXX,其中原始字串必须用括号()括起来,括号的前
final只能修饰虚函数;
virtual void foo() final;
struct b final : A{...};
override关键字确保派生类中声明的重写函数与基类的虚函数有相同的签名;同时也明确表示将会
内存对齐(字节对齐)是一个数据类型所能存放的内存地址的属性,这个属性是一个无符号整数,
当一个基本数据类型的对齐属性和这个数据类型的大小相等时,这种对齐称为自然对齐;如int=4,
为什么要对齐,32位的CPU处理内存的方式,一个时钟周期可以读取4个连续的内存单元,32位的
alignas(int) char c;
struct alignas(ZZ) mystr{...};
利用alignof和std::alignment_of获取内存对齐大小;
std::cout << alignof(XX) ; //xx为对象名;
std::alignment_of继承自std::integral_constant,拥有val_type,type,value成员;
如:int alignsize = std::alignment_of
std::aligned_storage:很多时候需要分配一块单纯的内存块,然后使用放置语法,placement new
std::aligned_storage
::new(&xx) MyStruct;
std::align(4,1024,pt,space); //在指定内存对齐为4,找一块1024大小的内存,将地址放入pt,
新增算法:all_of 检测区间first,last中是否所有元素都满足一元判断式p,都满足true;
any_of():至少有一个元素满足一元判断式p;none_of 都不满足;
例:vector
bool isallOdd = std::all_of(v.begin(),v.end(),isEven);
find_if_not 查找不符号条件的元素;copy_if();
------------------------------------
#include
using namespace std;
void main() {
array
iota(ai.begin(), ai.end(), 5);//用初始值5开始填充容器序列
for_each(ai.begin(), ai.end(), [](int& i) {cout << i; });
输出5……26....
is_sorted(),是否己排序?is_sorted_until()则用来返回序列中前面己经排好序的部分序列;
单芯多核:硬件并发;
并发的另一个途径:单个进程中运行多个线程:每个线程相互独立运行,且线程可以在不同的指令序列中运行,但是,进程中的所有线程都共享地址空间;
同一数据的内存地址在不同的进程中是不相同的;
C++11:全新的线程感知内存模型,C++标准库也扩展了:包含用于管理线程,保护共享数据,线程间同步操作,以及低级原子操作的各种类;
C++线程库中提供一个native_handle()成员函数,允许通过使用平台相关API直接操作底层实现,就其本质而言,任何使用native_handle()执行的操作是完全依赖于平台的;
代码中,提供的函数对象会复制到新线程的存储空间中,函数对象的调用和执行都在线程的内存空间中进行,要避免传入一个临时变量,而不是一个命名变量。
std::thread my_thread{background_task()}; //用大括号进行初始化;
lambda表达式允许使用一个可以捕获局部变量的局部函数(可以避免传递参数);
启动了线程,你需要明确是要等待线程结束(加入式),还是让其自主运行(分离式),如果对象销毁之前还没有做出决定 ,程序就会终止(std::thread)的析构函数会调用std::terminate())。
将detach()替换为.join(),就可以确保局部变量在线程完成后,才被销毁,只能对一个线程使用一次join(),一旦己经使用过了,就不能再次加入了,当对其使用joinable()时,将返回false();注意join()的位置,如果这个函数在被调用之前就被异常中断,则线程的join()函数未调用,所以要注意join()的位置;通常倾向于在无异常的情况下使用join(),需要在异常处理中调用join();
如果线程分离,那么就不能有thread对象能引用它;通常为守护线程;
线程调用参数如果涉及引用,使用std::ref将参数转换成引用的形式;
创建一个线程,并在函数中转移所有权,都必须要等待线程结束;
void do_work(unsigned id);
void f()
{
std::vector
for(unsigned i=0; i < 20; ++i)
{
threads.push_back(std::thread(do_work,i)); // 产生线程
} s
td::for_each(threads.begin(),threads.end(),
std::mem_fn(&std::thread::join)); // 对每个线程调用join()
} //注意mem_fn的用法,封装成员函数
unsigned long const length = std::distance(first,last);
因为不能直接从一个线程中返回一个值,所以需要传递result容器引用到线程中去;
线程标识类型是std::thread::id,通过std::thread对象的成员函数get_id()来直接获取,如果std::thread对象没有与任何执行线相关联,get_id()将返回std::thread::type默认构造值;std::this_thread::get_id();
if(this_thread::get_id() == master_thread_id){ do_master_thread_work();}else do_common_work();}
void add_to_list(int new_value)
{
std::lock_guard
some_list.push_back(new_value);//函数调用的时候生成锁,然后list.push_back.函数结束后自析构解锁;
}
避免死锁的一般建议,就是让两个互斥量总以相同的顺序上锁;
lock()一次性可以锁住多个(两个以上)的互斥量,并且没有副作用(死锁风险)。
// 这里的std::lock()需要包含
class some_big_object;
void swap(some_big_object& lhs,some_big_object& rhs);
class X
{ p
rivate:
some_big_object some_detail;
std::mutex m;
public:
X(some_big_object const& sd):some_detail(sd){}
friend void swap(X& lhs, X& rhs)
{
if(&lhs==&rhs)
return;
std::lock(lhs.m,rhs.m); // 1
std::lock_guard
std::lock_guard
swap(lhs.some_detail,rhs.some_detail);
}
};
一个互斥量可以在同一线程上多次上锁。
static thread_local unsigned long this_thread_hierarchy_value; // 1 每个线程都有其拷贝副本
std::unique_lock使用更为自由的不变量,这样,std::unique_lock实例不会总与互斥量的数据类型相关,使用起来要比std::lock_guard更加灵活,可以将std::adopt_lock作为第二个参数传入构造函数,也可以将std::defer_lock作为第二个参数传递进去,表明互斥量应保持解锁状态。允许std::unique_lock实例不带互斥量,信息己被存储,且己被更新;
std::unique_lock 在这种情况下工作正常,在调用unlock()时,代码不需要再访问共享数据;
而后当再次需要对共享数据进行访问时,就可以再调用lock()了。下面代码就是这样的一种情
况:
void get_and_process_data()
{
std::unique_lock
some_class data_to_process=get_next_data_chunk();
my_lock.unlock(); // 1 不要让锁住的互斥量越过process()函数的调用
result_type result=process(data_to_process);
my_lock.lock(); // 2 为了写入数据,对互斥量再次上锁
write_result(data_to_process,result);
}
调用join()的行为,还清理了相关的存储部分,这样,std::thread对象将不再与己经完成的线程有任何关联,同时也意味着,只能对一个线程使用一次join();一但使用过,就不能再加入了,调用joinable()返回false;