C++11学习笔记 9.docx

上传人:b****8 文档编号:10063188 上传时间:2023-05-23 格式:DOCX 页数:14 大小:21.94KB
下载 相关 举报
C++11学习笔记 9.docx_第1页
第1页 / 共14页
C++11学习笔记 9.docx_第2页
第2页 / 共14页
C++11学习笔记 9.docx_第3页
第3页 / 共14页
C++11学习笔记 9.docx_第4页
第4页 / 共14页
C++11学习笔记 9.docx_第5页
第5页 / 共14页
C++11学习笔记 9.docx_第6页
第6页 / 共14页
C++11学习笔记 9.docx_第7页
第7页 / 共14页
C++11学习笔记 9.docx_第8页
第8页 / 共14页
C++11学习笔记 9.docx_第9页
第9页 / 共14页
C++11学习笔记 9.docx_第10页
第10页 / 共14页
C++11学习笔记 9.docx_第11页
第11页 / 共14页
C++11学习笔记 9.docx_第12页
第12页 / 共14页
C++11学习笔记 9.docx_第13页
第13页 / 共14页
C++11学习笔记 9.docx_第14页
第14页 / 共14页
亲,该文档总共14页,全部预览完了,如果喜欢就下载吧!
下载资源
资源描述

C++11学习笔记 9.docx

《C++11学习笔记 9.docx》由会员分享,可在线阅读,更多相关《C++11学习笔记 9.docx(14页珍藏版)》请在冰点文库上搜索。

C++11学习笔记 9.docx

C++11学习笔记9

大多数算法都是定义在头文件algorithm中。

标准库还在头文件numeric中定义了一组数值泛型算法

算法不依赖于容器,但依赖于元素类型

泛型算法本身不会执行容器的操作,它们只会运行于迭代器之上,执行迭代器的操作。

因此,算法不会改变底层容器的大小。

find(bigin(ia),end(ia),val);

intsum=accumulate(vec.cbegin(),vec.cend(),0);

这条语句将sum设置为vec中元素的和,和的初始值为0,第三个参数说明了函数中使用哪个加法运算符以及返回值得类型。

vec中的元素必须可以是int,或者是double,longlong或者其他任何可以加到int上的类型,

stringsum=accumulate(v.cbegin(),b.cend(),string("")); ///正确

stringsum=sccumulate(v.cbegin(),b.cend(),"")//错误,constchar*上么有定义+运算符

另一个只读算法equal(),用于确定两个序列是否保存相同的值。

equal(roster1.cbegin(),roster1.cend(),roster2.cbegin());

两个容器的元素不必一样,只要可以用==比较。

而且有一个假设,第二个序列至少与第一个序列一样长。

写入元素:

fill(vec.begin(),vec.end(),0);

vectorvec; //空vector;

fill_n(vec.begin(),10,0); // 灾难,修改vec中不存在的元素

fill_n(vec.begin(),vec.size(),0);//将所有元素置零

插入迭代器back_inserter,定义在头文件iterator中的一个函数。

接受一个指向容器额引用。

返回一个与该容器绑定的插入迭代器。

vectorvec;

autoit=back_inserter(vec);

*it=42;

vectorvec:

//空向量

fill_n(back_inserter(vec),10,0);////正确,back_inserter创建一个插入迭代器,可用来添加元素

 

int a1[]={0,1,2,3,4,5,6,7,8,9};

int a2[sizeof(a1)/sizeof(*a1)];//a2 和a1大小一样

//  ret指向拷贝到a2的尾元素之后的位置

autoret=copy(begin(a1),end(a1),a2);

前两个迭代器是被拷贝元素范围,第三个是目的序列的其实位置

replace(ilst.begin(),ilst.end(),0,42);//将所有的0都替换为42;

replace_copy(ilst.cbegin(),ilist.cend(),back_inserter(ievc),0,42);

ilist中的元素不变,ivec包含ilst的一份拷贝,不过原来在ilst中的值为0的元素用42替换

sort用元素类型的<来实现排序

重排容器元素的算法:

voidelimDups(vector&words)

{

  sort(words.begin(),words.end());

  autoend_unique=unique(words.begin(),words.end());//将重复元素移到末尾,并返回最后一个不重复元素之后的位置

  words.erase(end_unique,words.end());//删除元素

}

标准库算法对迭代器而不是容器进行操作。

因此,算法不能(直接)添加或删除元素。

删除是调用成员函数

 

若果使用sort时,比较元素么有定义< 我们可以使用谓词:

一个可调用的表达式,其返回结果是一个能用作条件的值

一元谓词,意味着只接受单一参数,二元谓词,元素类型必须可以转换成谓词的参数,定义的是元素之间的一种关系

boolisShorter(conststring&s1,conststring&s2)

{

  returns1.size()

}

sort(words.begin(),words.end(),isShorter);

通常我们不在意排序时相等元素的相对位置,但是我们可以通过stable_sort这种稳定的排序算法来排序,保证排序后相等元素相对位置不变

elimDups(words);

stable_sort(words.begin(),words.end(),isShorter);

for(constauto&s:

words)

  cout<

cout<

find_if:

接受一对迭代器,表示一个范围,第三个参数是一个谓词。

find_if对输入序列中的每个元素调用给定的谓词。

它返回第一个使谓词返回非0值得元素,如果不存在则返回尾迭代器,find_if接受的一元谓词

对于一个对象或表达式,如果可以对其使用调用运算符,则称它为可调用的。

如果e是一个可调用的表达式,则我们可以编写代码e(args),args是用逗号分隔的参数列表

可调用的对象:

函数,函数指针,重载了函数调用符的类,lambda表达式

一个lambda表达式表示一个可调用的代码单元。

我们可以将其理解为一个未命名的内联函数。

[capaturelist](parameter)->returntype{functionbody}

capturelist(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空),lambda表达式必须使用尾置返回

autof=[]{return42};//我们定义了可调用对象f,不接受参数,返回42;

cout<

如果忽略返回类型,函数体中只有一个return,lambda根据函数中的代码推断出返回类型, 

如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void

lambda不能有默认参数,因此一个lambda调用的实参数目永远和形参数目相等

空的捕获列表表示lambda不使用它所在函数中的任何局部变量

 一个lambda表达式通过局部变量包含在捕获列表中来指出将会使用这些变量。

[sz](conststring&a) {returena.size()>=sz;};  //注意,花括号后的分号!

autowc=find_if( words.begin(),words.end(),[sz](conststring&a){returna.size()>=sz;} )

for_each(wc,words.end(),[](conststring&a){cont<

cout<

完整的biggles:

stringmake_plural(size_tctr,conststring&word,conststring&ending)

{

   return(ctr>1)?

word+ending:

word;

}

voidbiggies(vector&words,vector:

:

size_typesz)

{

  elimDups(words); //将words按字典顺序排序,删除重复单词

  stable_sort(words.begin(),words.end(),[](conststring&a,conststring&b)

      {returna.size()

  autowc=find_if(words.begin(),words.end(),[sz](conststring&a),

      [sz](conststring&a)

        {returna.size()>=sz;});//获得一个迭代器,指向第一个满足size()>=sz的元素

  autocount=words.end()-wc;

  cout<

      <<"oflength"<

  for_each(wc,words.end(),[](conststring&s){cout<

  cout<

}

当定义一个lambda时,编译器会生成以个与lambda对应的新的(未命名)类类型。

当使用lambda初始化的变量时,定义一个从lambda生成的对象。

默认情况,从lambda生成的类包含一个对应这个lambda所捕获的变量的数据成员,类似普通成员,lambda的数据成员也在lambda对象被创建时被初始化

捕获变量的值在lambda创建时拷贝,因而随后对变量的修改不会影响到对应lambda内对应的值

autof2=[&v1]{returnv1;};//返回的是v1指向的对象的值

io对象不可拷贝,所以对于io对象来说,我们就可以用引用来传递了。

特别的,对于捕获引用来说,必须保证引用是存在的,且有预期的值

一般来说,我们应该尽量减少捕获的数量,来避免潜在的捕获导致的问题,而且,如果可能的话,应该避免捕获指针或引用

隐式捕获,除了显示的列出希望使用的来自所在函数的变量之外,我们也可以让编辑器根据lambda中的代码来推测,

&捕获引用;=值捕获

//sz采用隐式捕获,值捕获方式

wc=find_if(words.begin(),words.end(),[=](conststring&s){returns.size()>=sz;);

如果我们希望对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获和显示捕获 

for_each(words.begin(),words.end(),[&,c](conststring&s){os<

当我们混合使用了隐式捕获和显示捕获时,捕获列表中的第一个元素必须是一个&或=。

此符号指定了默认的捕获方式

但使用混合捕获方式时,显示与隐式必须采用不同的捕获方式。

如,如果隐式捕获采用引用方式,则显式捕获命名变量必须采用值得方式

默认情况下,对于一个被拷贝的变量,lambda不会改变其值,若果要改变被捕获的值,就必须在参数列表首加上mutable,因此可以省略参数列表

autof=[v1]()mutable{return++v1;};

一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const还是非const类型

autof2=[&v1]{return++v1;};

默认情况下,如果一个lambda体包含return之外的任何语句,则编译器假定返回void。

就是没有返回值、

transform(vi.begin(),vi.begin(),vi.begin(),[](inti){returni<0?

-i:

i;});//将序列中的每一个负数替换成绝对值。

前两个迭代器是输入范围,第三个迭代器是目的位置,我们无须指定返回类型,可以根据条件运算符的类型推断出来

transform(vi.begin(),vi.end(),vi.begin(),[](inti){if(i<0)return-i;elsereturni;});//错误

编辑器推断这个版本的lambda返回类型为void,但返回一个int,此时我们就好指定返回类型

transform(vi.begin(),vi.end(),vi.begin(),[](inti)->int{if(i<0)return-i;elsereturni;});

对于那种只要一两个地方使用的简单操作,lambda表达式是最有用的,若果在多个地方,或这比较复杂,通常用的是函数

如果lambda表达式的捕获列表是空的,通常可以用函数来代替

定义在functional头文件中的bind函数:

可以看做是一个通用的函数适配器,它接受一个可调用的对象,生成一个新的可调用对象“适应”源对象的参数列表,autonewCallable=bind(callable,arg_list)

当我们调用newCallable时,newCallable会调用callable,并传递它arg_list中的参数

arg_list中的参数可能使_n.n是整数。

这些参数是占位符,数值n表示的是生成可调用对象的位置:

_1为newCallable的第一个参数,依次类推

autowc=find_if(words.bengin(),words,end(),bind(check_size,_1,sz);

_n定义在placeholders的命名空间中,而这个命名空间本身定义在std命名空间中。

_1的声明为 usingstd:

:

placeholders:

:

_1;

与bind函数一样,palceholders命名空间也定义在functional头文件中

用bind重排参数顺序

sort(words.begin(),words.end(),isShorter);///按单词长度由短到长排序

sort(words.begin(),words.end(),bind(isShorter,_2,_1));///由长到短

默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中,但是我们可以使用引用

for_each(words.begin(),words.end(),[&os,c](conststring&s){os<

ostream&print(ostream&os,conststring&s,charc)

{

  returnos<

}

for_each(words.begin(),words.end(),bind(print,os,_1,''));//错,不能拷贝os

for_each(words.begin(),words.end(),bind(print,ref(os),_1,''));

ref返回的一个对象,包含给定的引用,此对象可以拷贝。

标准库中还有一个cref函数,生成的是const引用的类。

这两个函数在functional头文件中(老版c++用的是bind1st和bind2nd,已经被弃用)

除了为每个容器定义的迭代器之外,标准库的iterator头文件中还定义了额外的几种迭代器

插入迭代器:

这些迭代器被绑定到一个容器上,可用来向容器插入元素

流迭代器:

这些迭代器被绑定到输入或输出流上,可用来遍历所关联额IO流

反向迭代器:

这些迭代器向后而不是向前移动,除了forward_list之外的标准库容器都有反向迭代器。

移动迭代器:

这些专用的迭代器不是拷贝其中的元素,而是移动它们

插入迭代器是一种迭代器适配器,它接受一个容器,生成一个迭代器,能实现向给定容器添加元素。

it=t;  在it指定的当前位置插入t。

假定c是it绑定的容器,依赖于插入迭代器的不同种类,此赋值会分别调用c.push_back(t)  c.push_front(t)   c.insert(t,p),其中p为传递给inserter的迭代器位置

 *it ,++it,it++ 这些操作虽然存在,但不会对it做任何事情。

每个操作都返回it

插入迭代器有三种,差异在于元素的插入位置

back_inserter 创建一个使用push_back的迭代器

front_inserter 创建一个使用push_front的迭代器

insert  创建一个使用insert的迭代器。

此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器,元素被插入到给定迭代器所表示的元素之前

只有在容器支持对应操作的情况下,我们才能使用对应的迭代器

调用insert(c,iter)时,得到一个迭代器,接下来使用它会将元素插入iter原来所指向的元素之前的位置。

*it=val;

其效果与下面代码一样:

it=c.insert(it,val);//it指向新插入的元素

++it; //递增it使他指向原来元素

当我们使用front_inserter时,元素总是插入到容器第一个元素之前。

而对于inserter指向第一个元素,只要我们插入新元素,此元素就不是首元素了,当调用front_inserter(c),我们得到一个插入迭代器,接下来会调用push_front。

当每个元素被插入c中时,它变为首元素,可见,front_inserter生成的迭代器会将插入的元素序列顺序颠倒过来,而inserter和back_inserter则不会

istream_iterator 读取输入流,ostream_iterator 向一个输出流写数据

当创建一个流迭代器时,必须指定迭代器将要读写对象的类型,一个istream_iterator使用>>来读取流,因此该类型必须定义了输入允许符。

创建时,我们可以将它绑定到一个流上,也可以默认初始化,来创建一个可以当作尾后值使用的迭代器

istream_iteratorint_it(cin);

istream_iteratorint_eof; //定义成空

while(int_it!

=int_eof) //当有数据可供读取时执行,一旦其关联的流遇到文件尾或遇到IO错误,迭代器的值就与尾后迭代器相同

  vec.push_back(*in_iter++); //存入vector

使用一对表示范围的迭代器来构造vector:

istream_iteratorin_iter(cin),eof:

vectorvec(in_iter,eof);

in_iter 返回从流中读取的值

in->men  与(*in).men含义相同

使用算法:

istream_iteratorin(cin),eof:

 cout<

istream_iterator绑定到一个流时,标准库不保证迭代器立即从流读取数据,保证的是,在我们第一次解引用迭代器之前,从流中读取数据的操作已经完成。

一般情况无所谓,如果我们创建了一个istream_iterator,么有使用就销毁了,或者我们正在从两个不同的对象同步读取同一个流,那么何时读取就很重要了

我们可以对任何带有输出运算符的类型定义ostream_iterator。

创建时我们可以提供第二个参数,一个c风格的字符串。

在输出每个元素后都会打印此字符串。

必须将ostream_iterator绑定到一个流上,不允许空的

out=val; 用<<运算符将val写入到out所绑定的ostream中。

val的类型必须与out可写的类型兼容

*out  ++out  out++这些运算符值存在的,但是不对out做任何事情,每个运算符都返回out

ostream_iteratorout_iter(cout,"");

for(autoe:

vec)

    *out_iter++=e;

cout<

值得注意的是,当我们向out_iter赋值时,可以忽略解引用可递增运算。

循环写成

for(autoe:

vec)

    out_iter=e;

cout<

也可以通过copy类打印:

copy(vec.begin(),vec.end(),out_iter);

cout<

除了forward_list之外,其他容器都支持反向迭代器。

我们可以通过调用rbegin、rend、crbegin、crend成员函数来获得反向迭代器。

返回的是指向容器尾元素和首元素之前一个位置的迭代器。

sort(vec.begin(),vec.end());//按正常顺序

sort(vec.rbegin().vec.rend());//逆序

因为流迭代器不支持递减运算,因为不可能在一个流中反向移动。

因此不能砸iforward_list和流迭代器创建反向迭代器

反向迭代器的目的是表示元素的范围,而这些范围是不对称的,导致一个结果:

当我们从一个普通迭代器初始化一个反向迭代器,或是给反向迭代器赋值,结果迭代器与原迭代器指向的不是相同的元素

五类迭代器:

输入迭代器、输出迭代器、前向迭代器、双向迭代器、随机访问迭代器

除了输出迭代器之外,一个高层类别的迭代器支持低层类别迭代器的所有操作

(对于传递错误类别迭代器,很多编译器不会给出任何警告或提示)

find算法在一个序列上进行一遍扫描,对元素进行只读操作,至少需要输入迭代器

replace函数需要一对迭代器,至少是前向迭代器。

replace_copy前两个迭代器参数要去至少是前向迭代器。

其第三个表示目的位置,必须至少是输出迭代器。

输入迭代器:

可以读取序列中的元素。

必须支持:

==   !

=  ++  *   ->

输入迭代器只用于顺序访问,*it++保证是有效的,但递增科恩能够导致所有其他指向流的迭代器失效,因此,输入迭代器只能用于单遍扫描的算法。

find和accumulate

输出迭代器,必须支持:

++  *  

只能向输出迭代器赋值一次,而且只能用于单遍扫描的算法。

用作目的位置的迭代器通常就是输出迭代器,如,copy的第三个参数

前向迭代器:

支持输入输出的说有操作,使用前向迭代器可以进行多遍扫描,replace要求的就是前向迭代器,forward_list上的就是

双向迭代器,支持前向迭代器的所有操作,还支持递减运算符(--)reverse要去的就是双向迭代器

随机访问迭代器,sort要求随机访问,array、deque、string、vector的迭代器都是随机访问迭代器

使用谓词的算法

unique(beg,end);

unique(beg,end,comp);

_if版本:

find(beg,end,val)//查找输入范围中val第一次出现的位置

find(beg,end,pred);//查找第一个令pred为真的元素

默认情况,重排算法将重排后的元素写回给定的输入序列中,这些算法还提供了另一个版本,将元素写到一个指定的输出目的位置。

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 自然科学 > 物理

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2