C++004进阶

 


6、  函数模板和类模板

前言

C++提供了函数模板(functiontemplate)。所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。

1)C++提供两种模板机制:函数模板、类模板

2)类属 —— 类型参数化,又称参数模板

使得程序(算法)可以从逻辑功能上抽象,把被处理的对象(数据)类型作为参数传递。

总结:

Ø  模板把函数或类要处理的数据类型参数化,表现为参数的多态性,称为类属。

Ø  模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。

6.1函数模板

6.1.1为什么要有函数模板

需求:写n个函数,交换char类型、int类型、double类型变量的值。

案例:

#include <iostream>

using namespace std;

/*

void myswap(int &a, int &b)

{

         int t = a;

         a = b;

         b = t;

}

 

void myswap(char &a, char &b)

{

         char t = a;

         a = b;

         b = t;

}

*/

 

//template 关键字告诉C++编译器 我要开始泛型了.你不要随便报错 

//数据类型T 参数化数据类型

template <typename T>

void myswap(T &a, T &b)

{

         T t;

         t = a;

         a = b;

         b = t;

}

 

void main()

{

         //char a = 'c';

        

         int  x = 1;

         int    y = 2;

         myswap(x, y); //自动数据类型 推导的方式

 

         float a = 2.0;

         float b = 3.0;

 

         myswap(a, b); //自动数据类型 推导的方式

         myswap<float>(a, b); //显示类型调用

 

         cout<<"hello..."<<endl;

         system("pause");

         return ;

}

 

6.1.2函数模板语法

函数模板定义形式

template    < 类型形式参数表>   

类型形式参数的形式为:

                     typenameT1 typenameT2, …… , typename Tn

或     class T1 class T2, …… , classTn  

函数模板调用

                   myswap<float>(a,b);       //显示类型调用

                  myswap(a, b);                   //自动数据类型推导

 

6.1.3函数模板和模板函数

6.1.4函数模板做函数参数

#include <iostream>

using namespace std;

 

template<typename T, typename T2>

void sortArray(T *a, T2 num)

{

         Ttmp ;

         inti, j ;

         for(i=0; i<num; i++)

         {

                   for(j=i+1; j<num; j++)

                   {

                            if(a[i] < a[j])

                            {

                                     tmp= a[i];

                                     a[i]= a[j];

                                     a[j]= tmp;

                            }

                   }

         }

}

 

template<class T>

void pirntArray(T *a, int num)

{

         inti = 0;

         for(i=0; i<num; i++)

         {

                   cout<<a[i]<<"";

         }

}

 

void main()

{

         intnum = 0;

         chara[] = "ddadeeettttt";

         num= strlen(a);

 

         printf("排序之前\n");

         pirntArray<char>(a,num);

 

         sortArray<char,int>(a, num); //显示类型调用模板函数 <>

         printf("排序之后\n");

         pirntArray<char>(a,num);

         cout<<"hello..."<<endl;

         system("pause");

         return;

}

 

6.1.5函数模板遇上函数重载

函数模板和普通函数区别结论:

/*

函数模板不允许自动类型转化

普通函数能够进行自动类型转换

*/

函数模板和普通函数在一起,调用规则:

/*

         1函数模板可以像普通函数一样被重载

         2C++编译器优先考虑普通函数

         3如果函数模板可以产生一个更好的匹配,那么选择模板

         4可以通过空模板实参列表的语法限定编译器只通过模板匹配

*/

案例1:

#include <iostream>

using namespace std;

 

template <typename T>

void myswap(T &a, T &b)

{

         T t;

         t = a;

         a = b;

         b = t;

         cout<<"myswap 模板函数do"<<endl;

}

 

void myswap(char &a, int &b)

{

         int t;

         t = a;

         a = b;

         b = t;

         cout<<"myswap 普通函数do"<<endl;

}

 

void main()

{

         char cData = 'a';

         int  iData = 2;

 

         //myswap<int>(cData, iData);  //结论 函数模板不提供隐式的数据类型转换  必须是严格的匹配

 

         myswap(cData, iData);

         //myswap(iData, cData);

        

         cout<<"hello..."<<endl;

         system("pause");

         return ;

}

 

案例2: 

 

#include "iostream"

using namespace std;

 

 

int Max(int a, int b)

{

         cout<<"int Max(int a, int b)"<<endl;

         return a > b ? a : b;

}

 

template<typename T>

T Max(T a, T b)

{

         cout<<"T Max(T a, T b)"<<endl;

         return a > b ? a : b;

}

 

template<typename T>

T Max(T a, T b, T c)

{

         cout<<"T Max(T a, T b, T c)"<<endl;

         return Max(Max(a, b), c);

}

 

 

void main()

{

         int a = 1;

         int b = 2;

 

         cout<<Max(a, b)<<endl; //当函数模板和普通函数都符合调用时,优先选择普通函数

         cout<<Max<>(a, b)<<endl; //若显示使用函数模板,则使用<> 类型列表

 

         cout<<Max(3.0, 4.0)<<endl; //如果 函数模板产生更好的匹配 使用函数模板

 

         cout<<Max(5.0, 6.0, 7.0)<<endl; //重载

 

         cout<<Max('a', 100)<<endl;  //调用普通函数 可以隐式类型转换

         system("pause");

         return ;

}

 

6.1.6 C++编译器模板机制剖析

思考:为什么函数模板可以和函数重载放在一块。C++编译器是如何提供函数模板机制的?

编译器编译原理

什么是gcc

gcc(GNU C Compiler)编译器的作者是Richard Stallman,也是GNU项目的奠基者。

什么是gcc:gcc是GNU Compiler Collection的缩写。最初是作为C语言的编译器(GNU C Compiler),现在已经支持多种语言了,如C、C++、Java、Pascal、Ada、COBOL语言等

gcc支持多种硬件平台,甚至对Don Knuth 设计的 MMIX 这类不常见的计算机都提供了完善的支持

 

gcc主要特征

1)gcc是一个可移植的编译器,支持多种硬件平台

2)gcc不仅仅是个本地编译器,它还能跨平台交叉编译。

3)gcc有多种语言前端,用于解析不同的语言。

4)gcc是按模块化设计的,可以加入新语言和新CPU架构的支持

5)gcc是自由软件

 

gcc编译过程

预处理(Pre-Processing)

编译(Compiling)

汇编(Assembling)

链接(Linking)

Gcc *.c –o 1exe (总的编译步骤)

Gcc –E 1.c –o 1.i  //宏定义 宏展开

Gcc –S 1.i –o 1.s

Gcc –c 1.s –o 1.o 

Gcc 1.o –o 1exe

结论:gcc编译工具是一个工具链。。。。

hello程序是一个高级C语言程序,这种形式容易被人读懂。为了在系统上运行hello.c程序,每条C语句都必须转化为低级机器指令。然后将这些指令打包成可执行目标文件格式,并以二进制形式存储器于磁盘中。

 

gcc常用编译选项

选项

作用

-o

产生目标(.i、.s、.o、可执行文件等)

-c

通知gcc取消链接步骤,即编译源码并在最后生成目标文件

-E

只运行C预编译器

-S

告诉编译器产生汇编语言文件后停止编译,产生的汇编语言文件扩展名为.s

-Wall

使gcc对源文件的代码有问题的地方发出警告

-Idir

将dir目录加入搜索头文件的目录路径

-Ldir

将dir目录加入搜索库的目录路径

-llib

链接lib库

-g

在目标文件中嵌入调试信息,以便gdb之类的调试程序调试

 

练习

gcc -E hello.c -o hello.i(预处理)

gcc -S hello.i -o hello.s(编译)

gcc -c hello.s -o hello.o(汇编)

gcc hello.o -o hello(链接)

以上四个步骤,可合成一个步骤

gcc hello.c -o hello(直接编译链接成可执行目标文件)

gcc -c hello.c或gcc -c hello.c -o hello.o(编译生成可重定位目标文件)

建议初学都加这个选项。下面这个例子如果不加-Wall选项编译器不报任何错误,但是得到的结果却不是预期的。

#include <stdio.h>

int main(void)

{

        printf("2+1 is %f", 3);

        return 0;

}

Gcc编译多个.c

hello_1.h

hello_1.c

main.c

一次性编译

gcc  hello_1.c main.c –o newhello

独立编译

gcc -Wall -c main.c -o main.o

gcc -Wall -c hello_1.c -o hello_fn.o

gcc -Wall main.o hello_1.o -o newhello

模板函数反汇编观察

命令:g++ -S 7.cpp -o 7.s

 

         .file  "7.cpp"

         .text

         .def  __ZL6printfPKcz;      .scl   3;      .type         32;   .endef

__ZL6printfPKcz:

LFB264:

         .cfi_startproc

         pushl         %ebp

         .cfi_def_cfa_offset 8

         .cfi_offset 5, -8

         movl          %esp, %ebp

         .cfi_def_cfa_register 5

         pushl         %ebx

         subl  $36, %esp

         .cfi_offset 3, -12

         leal   12(%ebp), %eax

         movl          %eax, -12(%ebp)

         movl          -12(%ebp), %eax

         movl          %eax, 4(%esp)

         movl          8(%ebp), %eax

         movl          %eax, (%esp)

         call   ___mingw_vprintf

         movl          %eax, %ebx

         movl          %ebx, %eax

         addl $36, %esp

         popl %ebx

         .cfi_restore 3

         popl %ebp

         .cfi_restore 5

         .cfi_def_cfa 4, 4

         ret

         .cfi_endproc

LFE264:

.lcomm __ZStL8__ioinit,1,1

         .def  ___main; .scl   2;      .type         32;   .endef

         .section .rdata,"dr"

LC0:

         .ascii "a:%d b:%d \12\0"

LC1:

         .ascii "c1:%c c2:%c \12\0"

LC2:

         .ascii "pause\0"

         .text

         .globl        _main

         .def  _main;      .scl   2;      .type         32;   .endef

_main:

LFB1023:

         .cfi_startproc

         .cfi_personality 0,___gxx_personality_v0

         .cfi_lsda 0,LLSDA1023

         pushl         %ebp

         .cfi_def_cfa_offset 8

         .cfi_offset 5, -8

         movl          %esp, %ebp

         .cfi_def_cfa_register 5

         andl $-16, %esp

         subl  $32, %esp

         call   ___main

         movl          $0, 28(%esp)

         movl          $10, 24(%esp)

         movb        $97, 23(%esp)

         movb        $98, 22(%esp)

         leal   24(%esp), %eax

         movl          %eax, 4(%esp)

         leal   28(%esp), %eax

         movl          %eax, (%esp)

         call   __Z6myswapIiEvRT_S1_  //66  ===>126

         movl          24(%esp), %edx

         movl          28(%esp), %eax

         movl          %edx, 8(%esp)

         movl          %eax, 4(%esp)

         movl          $LC0, (%esp)

         call   __ZL6printfPKcz

         leal   22(%esp), %eax

         movl          %eax, 4(%esp)

         leal   23(%esp), %eax

         movl          %eax, (%esp)

         call   __Z6myswapIcEvRT_S1_ //77 ===>155

         movzbl     22(%esp), %eax

         movsbl     %al, %edx

         movzbl     23(%esp), %eax

         movsbl     %al, %eax

         movl          %edx, 8(%esp)

         movl          %eax, 4(%esp)

         movl          $LC1, (%esp)

         call   __ZL6printfPKcz

         movl          $LC2, (%esp)

LEHB0:

         call   _system

LEHE0:

         movl          $0, %eax

         jmp  L7

L6:

         movl          %eax, (%esp)

LEHB1:

         call   __Unwind_Resume

LEHE1:

L7:

         leave

         .cfi_restore 5

         .cfi_def_cfa 4, 4

         ret

         .cfi_endproc

LFE1023:

         .def  ___gxx_personality_v0;  .scl   2;      .type         32;   .endef

         .section    .gcc_except_table,"w"

LLSDA1023:

         .byte         0xff

         .byte         0xff

         .byte         0x1

         .uleb128 LLSDACSE1023-LLSDACSB1023

LLSDACSB1023:

         .uleb128 LEHB0-LFB1023

         .uleb128 LEHE0-LEHB0

         .uleb128 L6-LFB1023

         .uleb128 0

         .uleb128 LEHB1-LFB1023

         .uleb128 LEHE1-LEHB1

         .uleb128 0

         .uleb128 0

LLSDACSE1023:

         .text

         .section    .text$_Z6myswapIiEvRT_S1_,"x"

         .linkonce discard

         .globl        __Z6myswapIiEvRT_S1_

         .def  __Z6myswapIiEvRT_S1_;         .scl   2;      .type         32;   .endef

__Z6myswapIiEvRT_S1_:  //126

LFB1024:

         .cfi_startproc

         pushl         %ebp

         .cfi_def_cfa_offset 8

         .cfi_offset 5, -8

         movl          %esp, %ebp

         .cfi_def_cfa_register 5

         subl  $16, %esp

         movl          8(%ebp), %eax

         movl          (%eax), %eax

         movl          %eax, -4(%ebp)

         movl          12(%ebp), %eax

         movl          (%eax), %edx

         movl          8(%ebp), %eax

         movl          %edx, (%eax)

         movl          12(%ebp), %eax

         movl          -4(%ebp), %edx

         movl          %edx, (%eax)

         leave

         .cfi_restore 5

         .cfi_def_cfa 4, 4

         ret

         .cfi_endproc

LFE1024:

         .section    .text$_Z6myswapIcEvRT_S1_,"x"

         .linkonce discard

         .globl        __Z6myswapIcEvRT_S1_

         .def  __Z6myswapIcEvRT_S1_;        .scl   2;      .type         32;   .endef

__Z6myswapIcEvRT_S1_: //155

LFB1025:

         .cfi_startproc

         pushl         %ebp

         .cfi_def_cfa_offset 8

         .cfi_offset 5, -8

         movl          %esp, %ebp

         .cfi_def_cfa_register 5

         subl  $16, %esp

         movl          8(%ebp), %eax

         movzbl     (%eax), %eax

         movb        %al, -1(%ebp)

         movl          12(%ebp), %eax

         movzbl     (%eax), %edx

         movl          8(%ebp), %eax

         movb        %dl, (%eax)

         movl          12(%ebp), %eax

         movzbl     -1(%ebp), %edx

         movb        %dl, (%eax)

         leave

         .cfi_restore 5

         .cfi_def_cfa 4, 4

         ret

         .cfi_endproc

LFE1025:

         .text

         .def  ___tcf_0; .scl   3;      .type         32;   .endef

___tcf_0:

LFB1027:

         .cfi_startproc

         pushl         %ebp

         .cfi_def_cfa_offset 8

         .cfi_offset 5, -8

         movl          %esp, %ebp

         .cfi_def_cfa_register 5

         subl  $8, %esp

         movl          $__ZStL8__ioinit, %ecx

         call   __ZNSt8ios_base4InitD1Ev

         leave

         .cfi_restore 5

         .cfi_def_cfa 4, 4

         ret

         .cfi_endproc

LFE1027:

         .def  __Z41__static_initialization_and_destruction_0ii;         .scl   3;      .type         32;   .endef

__Z41__static_initialization_and_destruction_0ii:

LFB1026:

         .cfi_startproc

         pushl         %ebp

         .cfi_def_cfa_offset 8

         .cfi_offset 5, -8

         movl          %esp, %ebp

         .cfi_def_cfa_register 5

         subl  $24, %esp

         cmpl          $1, 8(%ebp)

         jne    L11

         cmpl          $65535, 12(%ebp)

         jne    L11

         movl          $__ZStL8__ioinit, %ecx

         call   __ZNSt8ios_base4InitC1Ev

         movl          $___tcf_0, (%esp)

         call   _atexit

L11:

         leave

         .cfi_restore 5

         .cfi_def_cfa 4, 4

         ret

         .cfi_endproc

LFE1026:

         .def  __GLOBAL__sub_I_main;        .scl   3;      .type         32;   .endef

__GLOBAL__sub_I_main:

LFB1028:

         .cfi_startproc

         pushl         %ebp

         .cfi_def_cfa_offset 8

         .cfi_offset 5, -8

         movl          %esp, %ebp

         .cfi_def_cfa_register 5

         subl  $24, %esp

         movl          $65535, 4(%esp)

         movl          $1, (%esp)

         call   __Z41__static_initialization_and_destruction_0ii

         leave

         .cfi_restore 5

         .cfi_def_cfa 4, 4

         ret

         .cfi_endproc

LFE1028:

         .section    .ctors,"w"

         .align 4

         .long         __GLOBAL__sub_I_main

         .ident        "GCC: (rev2, Built by MinGW-builds project) 4.8.0"

         .def  ___mingw_vprintf;  .scl   2;      .type         32;   .endef

         .def  _system; .scl   2;      .type         32;   .endef

         .def  __Unwind_Resume;         .scl   2;      .type         32;   .endef

         .def  __ZNSt8ios_base4InitD1Ev;    .scl   2;      .type         32;   .endef

         .def  __ZNSt8ios_base4InitC1Ev;    .scl   2;      .type         32;   .endef

         .def  _atexit;    .scl   2;      .type         32;   .endef

 

函数模板机制结论

编译器并不是把函数模板处理成能够处理任意类的函数

编译器从函数模板通过具体类型产生不同的函数

编译器会对函数模板进行两次编译

在声明的地方对模板代码本身进行编译;在调用的地方对参数替换后的代码进行编译。

6.2类模板 

6.2.1为什么需要类模板

类模板与函数模板的定义和使用类似,我们已经进行了介绍。 有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类:

Ø  类模板用于实现类所需数据的类型参数化

Ø  类模板在表示如数组、表、图等数据结构显得特别重要,

    这些数据结构的表示和算法不受所包含的元素类型的影响

6.2.2单个类模板语法

//类的类型参数化 抽象的类

//单个类模板

template<typename T>

class A

{

public:

         A(T t)

         {

                   this->t = t;

         }

 

         T &getT()

         {

                   return t;

         }

protected:

public:

         T t;

};

void main()

{

   //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则

         A<int>  a(100);

         a.getT();

         printAA(a);

         return ;

}

 

6.2.3继承中的类模板语法

//结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)A<int>

//

class B : public A<int>

{

public:

         B(int i) : A<int>(i)

         {

 

         }

         void printB()

         {

                   cout<<"A:"<<t<<endl;

         }

protected:

private:

};

 

//模板与上继承

//怎么样从基类继承 

//若基类只有一个带参数的构造函数,子类是如何启动父类的构造函数

void pintBB(B &b)

{

         b.printB();

}

void printAA(A<int> &a)  //类模板做函数参数

{

          //

         a.getT();

}

 

void main()

{

         A<int>  a(100); //模板了中如果使用了构造函数,则遵守以前的类的构造函数的调用规则

         a.getT();

         printAA(a);

 

         B b(10);

         b.printB();

 

 

         cout<<"hello..."<<endl;

         system("pause");

         return ;

}

 

6.2.4类模板语法知识体系梳理

6.2.4.1所有的类模板函数写在类的内部

 

6.2.4.2所有的类模板函数写在类的外部,在一个cpp中

                   //构造函数 没有问题

                   //普通函数 没有问题

                   //友元函数:用友元函数重载<< >>

                   //      friend ostream& operator<< <T>(ostream &out, Complex<T> &c3) ;

                   //友元函数:友元函数不是实现函数重载(非 << >>)

                            //1)需要在类前增加 类的前置声明 函数的前置声明

                                     template<typename T>

class Complex; 

template<typenameT>

Complex<T> mySub(Complex<T> &c1, Complex<T>&c2);

 

                            //2)类的内部声明 必须写成:

friend Complex<T> mySub <T>(Complex<T> &c1, Complex<T> &c2);

                            //3)友元函数实现 必须写成:

                                     template<typenameT>

              Complex<T>mySub(Complex<T> &c1, Complex<T> &c2)

{

                  Complex<T> tmp(c1.a - c2.a,c1.b-c2.b);

                  return tmp;

}

           //4)友元函数调用必须写成

                                     Complex<int> c4 = mySub<int>(c1,c2);

               cout<<c4;

结论:友元函数只用来进行左移友移操作符重载。

6.2.4.3所有的类模板函数写在类的外部,在不同的.h和.cpp中,

也就是类模板函数说明和类模板实现分开

//类模板函数

构造函数

普通成员函数

友元函数

用友元函数重载<<>>;

用友元函数重载非<<>>

                   //要包含.cpp

6.2.4.4总结

归纳以上的介绍,可以这样声明和使用类模板:

1) 先写出一个实际的类。由于其语义明确,含义清楚,一般不会出错。

2) 将此类中准备改变的类型名(如int要改变为float或char)改用一个自己指定的虚拟类型名(如上例中的numtype)。

3) 在类声明前面加入一行,格式为:

   template <class 虚拟类型参数>

如:

   template <class numtype> //注意本行末尾无分号

   class Compare

    {…}; //类体

4) 用类模板定义对象时用以下形式:

    类模板名<实际类型名> 对象名;

    类模板名<实际类型名> 对象名(实参表列);

如:

   Compare<int> cmp;

   Compare<int> cmp(3,7);

5) 如果在类模板外定义成员函数,应写成类模板形式:

  template <class 虚拟类型参数>

   函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表列) {…}

关于类模板的几点说明:

1) 类模板的类型参数可以有一个或多个,每个类型前面都必须加class,如:

   template <class T1,class T2>

   class someclass

   {…};

在定义对象时分别代入实际的类型名,如:

   someclass<int,double> obj;

2) 和使用类一样,使用类模板时要注意其作用域,只能在其有效作用域内用它定义对象。

3) 模板可以有层次,一个类模板可以作为基类,派生出派生模板类。

6.2.5类模板中的static关键字

Ø  从类模板实例化的每个模板类有自己的类模板数据成员,该模板类的所有对象共享一个static数据成员

Ø   和非模板类的static数据成员一样,模板类的static数据成员也应该在文件范围定义和初始化

Ø   每个模板类有自己的类模板的static数据成员副本

 

 

 

 

原理图:

6.3类模板在项目开发中的应用

小结

Ø  模板是C++类型参数化的多态工具。C++提供函数模板和类模板。

Ø   模板定义以模板说明开始。类属参数必须在模板定义中至少出现一次。

Ø   同一个类属参数可以用于多个模板。

Ø   类属参数可用于函数的参数类型、返回类型和声明函数中的变量。

Ø   模板由编译器根据实际数据类型实例化,生成可执行代码。实例化的函数。

    模板称为模板函数;实例化的类模板称为模板类。

Ø   函数模板可以用多种方式重载。

Ø   类模板可以在类层次中使用 。

 

训练题

1)  请设计一个数组模板类( MyVector ),完成对int、char、Teacher类型元素的管理。
               需求

     设计:

类模板 构造函数 拷贝构造函数 <<[]  重载=操作符     

 a2=a1

                            实现

 

2)  请仔细思考:

a)        如果数组模板类中的元素是Teacher元素时,需要Teacher类做什么工作

b)        如果数组模板类中的元素是Teacher元素时,Teacher类含有指针属性哪?

 

class Teacher

{

    friend ostream & operator<<(ostream &out, const Teacher &obj);

public:

    Teacher(char *name, int age)

    {

       this->age = age;

       strcpy(this->name, name);

    }

 

    Teacher()

    {

       this->age = 0;

       strcpy(this->name, "");

    }

   

private:

    int age;

    char name[32];

};

 

 

class Teacher

{

    friend ostream & operator<<(ostream &out, const Teacher &obj);

public:

    Teacher(char *name, int age)

    {

       this->age = age;

       strcpy(this->name, name);

    }

 

    Teacher()

    {

       this->age = 0;

       strcpy(this->name, "");

    }

   

private:

    int age;

    char *pname;

};

 

 

         结论1: 如果把Teacher放入到MyVector数组中,并且Teacher类的属性含有指针,就是出现深拷贝和浅拷贝的问题。

         结论2:需要Teacher封装的函数有:

1)  重写拷贝构造函数 

2)  重载等号操作符 

3)  重载左移操作符。

 

理论提高:所有容器提供的都是值(value)语意,而非引用(reference)语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝(必须提供拷贝构造函数)。

 

3)  请从数组模板中进行派生

        

 

//演示从模板类 派生 一般类

#include "MyVector.cpp"

 

class MyArray01 : public MyVector<double>

{

public:

         MyArray01(int len) : MyVector<double>(len)

         {

                   ;

         }

protected:

private:

};

 

 

//演示从模板类 派生 模板类 //BoundArray

template <typename T>

class MyArray02 : public MyVector<T>

{

public:

         MyArray02(int len) : MyVector<double>(len)

         {

                   ;

         }

protected:

private:

};

测试案例:

 

//演示 从模板类 继承 模板类

void main()

{

         MyArray02<double> dArray2(10);

         dArray2[1] = 3.15;

 

}

 

 

//演示 从模板类 继承 一般类

void main11()

{

         MyArray01 d_array(10);

 

         for (int i=0; i<d_array.getLen(); i++)

         {

                   d_array[i] = 3.15;

         }

 

         for (int i=0; i<d_array.getLen(); i++)

         {

                   cout << d_array[i] << " ";

         }

 

         cout<<"hello..."<<endl;

         system("pause");

         return ;

}

 

6.4作业

封装你自己的数组类;设计被存储的元素为类对象;

思考:类对象的类,应该实现的功能。

//1  优化Teacher类, 属性变成 char*panme, 构造函数里面分配内存

//2  优化Teacher类,析构函数 释放panme指向的内存空间

//3  优化Teacher类,避免浅拷贝 重载= 重写拷贝构造函数

//4  优化Teacher类,在Teacher增加 <<

//5  在模板数组类中,存int charTeacher Teacher*(指针类型)

//=====>stl 容器的概念

7、C++的类型转换

7.1 类型转换名称和语法

C风格的强制类型转换(TypeCast)很简单,不管什么类型的转换统统是:

TYPE b =(TYPE)a   

C++风格的类型转换提供了4种类型转换操作符来应对不同场合的应用。

    static_cast                静态类型转换。如int转换成char

                   reinterpreter_cast  重新解释类型

           dynamic_cast           命名上理解是动态类型转换。如子类和父类之间的多态类型转换。

const_cast,            字面上理解就是去const属性。

4种类型转换的格式:

         TYPE B = static_cast<TYPE> (a) 

7.2 类型转换一般性介绍

         1)static_cast<>()  静态类型转换,编译的时c++编译器会做类型检查

基本类型能转换 但是不能转换指针类型

         2)若不同类型之间,进行强制类型转换,用reinterpret_cast<>() 进行重新解释

         3)一般性结论:

C语言中 能隐式类型转换的,在c++中可用 static_cast<>()进行类型转换。因C++编译器在编译检查一般都能通过;

C语言中不能隐式类型转换的,在c++中可以用 reinterpret_cast<>() 进行强行类型解释。总结:static_cast<>()和reinterpret_cast<>() 基本上把C语言中的 强制类型转换给覆盖

reinterpret_cast<>()很难保证移植性。

         4)dynamic_cast<>(),动态类型转换,安全的基类和子类之间转换;运行时类型检查

         5)const_cast<>(),去除变量的只读属性       

7.3 典型案例

7.3.1 static_cast用法和reinterpret_cast用法

 

void main01()

{

         double dPi = 3.1415926;

 

         //1静态的类型转换:  在编译的时 进行基本类型的转换 能替代c风格的类型转换 可以进行一部分检查

         int num1 = static_cast<int> (dPi); //c++的新式的类型转换运算符 

         int num2 = (int)dPi;                              //c语言的 旧式类型转换

         int num3 = dPi;                                                        //隐士类型转换

         cout << "num1:" << num1 << " num2:" << num2 << " num3:" << num3 << endl;

 

 

         char *p1 = "hello wangbaoming " ;

         int *p2 = NULL;

         p2 = (int *)p1;

 

         //2 基本类型能转换 但是不能转换指针类型

         //p2 = static_cast<int *> (p1); //“static_cast”: 无法从“char *”转换为“int *”

 

         //3 可以使用  reinterpret_cast 进行重新解释

         p2 = reinterpret_cast<int *> (p1);

         cout << "p1 " << p1 << endl;

         cout << "p2 " << p2 << endl;

 

         //4 一般性的结论:      c语言中  能隐式类型转换的 在c++中可以用 static_cast<>()进行类型转换  //C++编译器在编译检查一般都能通过

         //c语言中不能隐式类型转换的,在c++中可以用 reinterpret_cast<>() 进行强行类型 解释

        

         system("pause");

         return ;

}

 

7.3.2 dynamic_cast用法和reinterpret_cast用法

 

class Animal

{

public:

         virtual void  cry() = 0;

};

 

class Dog : public Animal

{

public:

         virtual void  cry()

         {

                   cout << "wangwang " << endl;

         }

 

         void doSwim() 

         {

                   cout << "我要狗爬" << endl;

         }

};

 

 

class Cat : public Animal

{

public:

         virtual void  cry()

         {

                   cout << "miaomiao " << endl;

         }

         void doTree() 

         {

                   cout << "我要爬树" << endl;

         }

 

};

 

class Book

{

public:

         void printP()

         {

                   cout << price << endl;

         }

 

private:

         int price;

 

};

 

void ObjPlay(Animal *base)

{

         base->cry();

         Dog *pDog = dynamic_cast<Dog *>(base);

         if (pDog != NULL)

         {

                   pDog->cry();

                   pDog->doSwim();

         }

 

         Cat *pCat = dynamic_cast<Cat *>(base);

         if (pCat != NULL)

         {

                   pCat->cry();

                   pCat->doTree();

         }

}

void main02()

{

         Animal *base = NULL;

 

         //1 可以把子类指针赋给 父类指针 但是反过来是不可以的 需要 如下转换

         //pdog = base; 

         Dog *pDog = static_cast<Dog *> (base);

 

         //2 把base转换成其他 非动物相关的 err

         //Book *book= static_cast<Book *> (base);

 

         //3  reinterpret_cast //可以强制类型转换

         Book *book2= reinterpret_cast<Book *> (base);

 

         //4 dynamic_cast用法

         ObjPlay(new Cat());

 

         system("pause");

}

 

7.3.3 const_cast用法

 

//典型用法 把形参的只读属性去掉

void Opbuf(const char *p)

{

         cout << p << endl;

         char *p2 = const_cast<char*>(p);

         p2[0] = 'b';

         cout << p << endl;

}

 

void main()

{

         const char *p1 = "11111111111";

 

         char *p2 = "22222222";

 

         char *p3 = const_cast<char *>(p1);

         char buf[100] = "aaaaaaaaaaaa";

 

         Opbuf(buf);

 

         //要保证指针所执行的内存空间能修改才行 若不能修改 还是会引起程序异常

         //Opbuf("dddddddddddsssssssssssssss");

 

         system("pause");

}

7.4 总结

结论1:程序员要清除的知道: 要转的变量,类型转换前是什么类型,类型转换后是什么类型。转换后有什么后果。

结论2:一般情况下,不建议进行类型转换;避免进行类型转换。

8、异常处理机制专题

前言

1)异常是一种程序控制机制,与函数机制独立和互补

   函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它依附于栈结构,却可以同时设置多个异常类型作为网捕条件,从而以类型匹配在栈机制中跳跃回馈.

2)异常设计目的:

     栈机制是一种高度节律性控制机制,面向对象编程却要求对象之间有方向、有目的的控制传动,从一开始,异常就是冲着改变程序控制结构,以适应面向对象程序更有效地工作这个主题,而不是仅为了进行错误处理。

异常设计出来之后,却发现在错误处理方面获得了最大的好处。

8.1 异常处理的基本思想

8.1.1传统错误处理机制

         通过函数返回值来处理错误。

8.1.2异常处理的基本思想

1)C++的异常处理机制使得异常的引发异常的处理不必在同一个函数中,这样底层的函数可以着重解决具体问题,而不必过多的考虑异常的处理。上层调用者可以再适当的位置设计对不同类型异常的处理。

2)异常是专门针对抽象编程中的一系列错误处理的,C++中不能借助函数机制,因为栈结构的本质是先进后出,依次访问,无法进行跳跃,但错误处理的特征却是遇到错误信息就想要转到若干级之上进行重新尝试,如图

3)异常超脱于函数机制,决定了其对函数的跨越式回跳。

4)异常跨越函数

8.2 C++异常处理的实现

8.2.1异常基本语法

1) 若有异常则通过throw操作创建一个异常对象并抛掷。

2) 将可能抛出异常的程序段嵌在try块之中。控制通过正常的顺序执行到达try语句,然后执行try块内的保护段。

3) 如果在保护段执行期间没有引起异常,那么跟在try块后的catch子句就不执行。程序从try块后跟随的最后一个catch子句后面的语句继续执行下去。

4) catch子句按其在try块后出现的顺序被检查。匹配的catch子句将捕获并处理异常(或继续抛掷异常)。

5) 如果匹配的处理器未找到,则运行函数terminate将被自动调用,其缺省功能是调用abort终止程序。

6)处理不了的异常,可以在catch的最后一个分支,使用throw语法,向上扔。

 

案例1:被零整除案例

int divide(int x, int y )

{

         if (y ==0)

         {

                   throw x;

         }

         return x/y;

}

 

void main41()

{

         try

         {

                   cout << "8/2 = " << divide(8, 2) << endl;

                   cout << "10/0 =" << divide(10, 0) << endl;

         }

         catch (int e)

         {

                   cout << "e" << " is divided by zero!" << endl;

         }

         catch(...)

         {

                   cout << "未知异常" << endl;

         }

        

         cout << "ok" << endl;

        system("pause");

         return ;

}

 

案例2:

class A{};

void f(){

 if(...) throw A;

}

void g(){

 try{

   f();

 }catch(B){

   cout<<“exception B\n”;

  }

}

int main(){

 g();

}

throw A将穿透函数f,g和main,抵达系统的最后一道防线——激发terminate函数.

该函数调用引起运行终止的abort函数.

最后一道防线的函数可以由程序员设置.从而规定其终止前的行为.

修改系统默认行为:

u  可以通过set_terminate函数修改捕捉不住异常的默认处理器,从而使得发生捉不住异常时,被自定义函数处理:

u   voidmyTerminate(){cout<<“HereIsMyTerminate\n”;}

u   set_terminate(myTerminate);

u   set_terminate函数在头文件exception中声明,参数为函数指针void(*)().

 

案例3:

v  构造函数没有返回类型,无法通过返回值来报告运行状态,所以只通过一种非函数机制的途径,即异常机制,来解决构造函数的出错问题。

 

7)异常机制与函数机制互不干涉,但捕捉的方式是基于类型匹配。捕捉相当于函数返回类型的匹配,而不是函数参数的匹配,所以捕捉不用考虑一个抛掷中的多种数据类型匹配问题

比如:

class A{};

class B{};

 

int main()

{

         try

         {

                   int   j = 0;   

                   double     d = 2.3;   

                   char         str[20] = "Hello";

                   cout<<"Please input a exception number: ";

                   int a;

                   cin>>a;

                   switch(a)

                   {

                   case  1:

                            throw d;     

                   case  2:

                            throw j;     

                  case  3:

                            throw str;

                   case  4:

                            throw A();     

                   case  5:

                            throw B();

                   default:

                            cout<<"No throws here.\n";   

                   }

         }

         catch(int)

         {

                   cout<<"int exception.\n";

         }

         catch(double)

         {

                   cout<<"double exception.\n";

         }

         catch(char*)

         {

                   cout<<"char* exception.\n";

         }

         catch(A)

         {

                   cout<<"class A exception.\n";

         }

         catch(B)

         {

                   cout<<"class B exception.\n";

         }

         cout<<"That's ok.\n";

         system("pause");

}//====================================

 

catch代码块必须出现在try后,并且在try块后可以出现多个catch代码块,以捕捉各种不同类型的抛掷。

异常机制是基于这样的原理:程序运行实质上是数据实体在做一些操作,因此发生异常现象的地方,一定是某个实体出了差错,该实体所对应的数据类型便作为抛掷和捕捉的依据

8)异常捕捉严格按照类型匹配

u  异常捕捉的类型匹配之苛刻程度可以和模板的类型匹配媲美,它不允许相容类型的隐式转换,比如,抛掷char类型用int型就捕捉不到.例如下列代码不会输出“int exception.”,从而也不会输出“That’s ok.”  因为出现异常后提示退出

int main(){

 try{

    throw‘H’;

  }catch(int){

   cout<<"int exception.\n";

  }

 cout<<"That's ok.\n";

}

8.2.2栈解旋(unwinding)

异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反。这一过程称为栈的解旋(unwinding)。

 

 

class MyException {};

 

class Test

{

public:

         Test(int a=0, int b=0)

         {

                   this->a = a;

                   this->b = b;

                   cout << "Test 构造函数执行" << "a:" << a << " b: " << b << endl;

         }

         void printT()

         {

                   cout << "a:" << a << " b: " << b << endl;

         }

         ~Test()

         {

                   cout << "Test 析构函数执行" << "a:" << a << " b: " << b << endl;

         }

private:

         int a;

         int b;

};

 

void myFunc() throw (MyException)

{

         Test t1;

         Test t2;

 

         cout << "定义了两个栈变量,异常抛出后测试栈变量的如何被析构" << endl;

 

         throw MyException();

}

 

void main()

{

         //异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上的构造的所有对象,

         //都会被自动析构。析构的顺序与构造的顺序相反。

         //这一过程称为栈的解旋(unwinding)

         try 

         {

                   myFunc();

         }

         //catch(MyException &e) //这里不能访问异常对象

         catch(MyException ) //这里不能访问异常对象

         {

                   cout << "接收到MyException类型异常" << endl;

         }

         catch(...)

         {

                   cout << "未知类型异常" << endl;

         }

        

         system("pause");

         return ;

}

 

8.2.3异常接口声明

1)为了加强程序的可读性,可以在函数声明中列出可能抛出的所有异常类型,例如:

         voidfunc() throw (A, B, C , D); //这个函数func()能够且只能抛出类型A B C D及其子类型的异常

2)如果在函数声明中没有包含异常接口声明,则次函数可以抛掷任何类型的异常,例如:

         voidfunc();

3)一个不抛掷任何类型异常的函数可以声明为:

         voidfunc()  throw();

4) 如果一个函数抛出了它的异常接口声明所不允许抛出的异常,unexpected函数会被调用,该函数默认行为调用terminate函数中止程序。

8.2.4异常类型和异常变量的生命周期

1)throw的异常是有类型的,可以使,数字、字符串、类对象。

2)throw的异常是有类型的,catch严格按照类型进行匹配。

3)注意 异常对象的内存模型 。

8.2.2.1 传统处理错误

//文件的二进制copy

int filecopy01(char *filename2, char *filename1 )

{

         FILE *fp1= NULL,  *fp2 = NULL;

 

         fp1 = fopen(filename1, "rb");

         if (fp1 == NULL)

         {

                   return 1;

         }

 

         fp2 = fopen(filename2, "wb");

         if (fp1 == NULL)

         {

                   return 2;

         }

 

         char buf[256];

         int  readlen, writelen;

         while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果读到数据,则大于0

         {

                   writelen = fwrite(buf, 1, readlen, fp2);

                   if (readlen != readlen)

                   {

                            return 3;

                   }

         }

 

         fclose(fp1);

         fclose(fp2);

         return 0;

}

测试程序

void main11()

{

         int ret;

         ret = filecopy01("c:/1.txt","c:/2.txt");

         if (ret !=0 )

         {

                   switch(ret)

                   {

                   case 1:

                            printf("打开源文件时出错!\n");

                            break;

                   case 2:

                            printf("打开目标文件时出错!\n");

                            break;

                   case 3:

                            printf("拷贝文件时出错!\n");

                            break;

                   default:

                            printf("发生未知错误!\n");

                            break;

                   }

         }

}

8.2.2.2 throw int类型异常

/文件的二进制copy

void filecopy02(char *filename2, char *filename1 )

{

         FILE *fp1= NULL,  *fp2 = NULL;

 

         fp1 = fopen(filename1, "rb");

         if (fp1 == NULL)

         {

                   //return 1;

                   throw 1;

         }

 

         fp2 = fopen(filename2, "wb");

         if (fp1 == NULL)

         {

                   //return 2;

                   throw 2;

         }

 

         char buf[256];

         int  readlen, writelen;

         while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果读到数据,则大于0

         {

                   writelen = fwrite(buf, 1, readlen, fp2);

                   if (readlen != readlen)

                   {

                            //return 3;

                            throw 3;

                   }

         }

 

         fclose(fp1);

         fclose(fp2);

         return ;

}

8.2.2.3 throw字符类型异常

//文件的二进制copy

void filecopy03(char *filename2, char *filename1 )

{

         FILE *fp1= NULL,  *fp2 = NULL;

 

         fp1 = fopen(filename1, "rb");

         if (fp1 == NULL)

         {

                   throw "打开源文件时出错";

         }

 

         fp2 = fopen(filename2, "wb");

         if (fp1 == NULL)

         {

                   throw "打开目标文件时出错";

         }

 

         char buf[256];

         int  readlen, writelen;

         while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果读到数据,则大于0

         {

                   writelen = fwrite(buf, 1, readlen, fp2);

                   if (readlen != readlen)

                   {

                            throw "拷贝文件过程中失败";

                   }

         }

 

         fclose(fp1);

         fclose(fp2);

         return ;

}

 

8.2.2.4 throw类对象类型异常

//throw int类型变量

//throw 字符串类型

//throw 类类型

class BadSrcFile

{

public:

         BadSrcFile()

         {

                   cout << "BadSrcFile 构造 do "<<endl;

         }

         ~BadSrcFile()

         {

                   cout << "BadSrcFile 析构 do "<<endl;

         }

         BadSrcFile(BadSrcFile & obj)

         {

                   cout << "拷贝构造  do "<<endl;

         }

         void toString()

         {

                   cout << "aaaa" << endl;

         }

 

};

class BadDestFile {};

class BadCpyFile {};;

 

void filecopy04(char *filename2, char *filename1 )

{

         FILE *fp1= NULL,  *fp2 = NULL;

 

         fp1 = fopen(filename1, "rb");

         if (fp1 == NULL)

         {

                   //throw new BadSrcFile();

                   throw  BadSrcFile();

         }

 

         fp2 = fopen(filename2, "wb");

         if (fp1 == NULL)

         {

                   throw BadDestFile();

         }

 

         char buf[256];

         int  readlen, writelen;

         while ( (readlen = fread(buf, 1, 256, fp1)) > 0 ) //如果读到数据,则大于0

         {

                   writelen = fwrite(buf, 1, readlen, fp2);

                   if (readlen != readlen)

                   {

                            throw BadCpyFile();

                   }

         }

 

         fclose(fp1);

         fclose(fp2);

         return ;

}

main测试案例

 

//结论://C++编译器通过throw 来产生对象,C++编译器再执行对应的catch分支,相当于一个函数调用,把实参传递给形参。

void main11()

{

         try

         {

                    //filecopy02("c:/1.txt","c:/2.txt");

                   // filecopy03("c:/1.txt","c:/2.txt");

                    filecopy04("c:/1.txt","c:/2.txt");

         }

         catch (int e)

         {

                   printf("发生异常:%d \n", e);

         }

         catch (const char * e)

         {

                   printf("发生异常:%s \n", e);

         }

         catch ( BadSrcFile *e)

         {

                    e->toString();

                   printf("发生异常:打开源文件时出错!\n");

         }

         catch ( BadSrcFile &e)

         {

                   e.toString();

                   printf("发生异常:打开源文件时出错!\n");

         }

         catch ( BadDestFile e)

         {

                   printf("发生异常:打开目标文件时出错!\n");

         }

         catch ( BadCpyFile e)

         {

                   printf("发生异常:copy时出错!\n");

         }

         catch(...) //抓漏网之鱼

         {

                   printf("发生了未知异常! 抓漏网之鱼\n");

         }

         //class BadSrcFile {};

         //class BadDestFile {};

         //class BadCpyFile {};;     

}

 

8.2.5异常的层次结构(继承在异常中的应用)

v  异常是类 – 创建自己的异常类

v  异常派生

v  异常中的数据:数据成员

v  按引用传递异常

Ø  在异常中使用虚函数

案例:设计一个数组类 MyArray,重载[]操作,

数组初始化时,对数组的个数进行有效检查

1)  index<0 抛出异常eNegative  

2)  index = 0 抛出异常eZero 

         3)index>1000抛出异常eTooBig

         4)index<10 抛出异常eTooSmall

         5)eSize类是以上类的父类,实现有参数构造、并定义virtual void printErr()输出错误。

8.3标准程序库异常

 

案例1:

// out_of_range

#include "iostream"

using namespace std;

#include <stdexcept> 

 

class Teacher

{

public:

         Teacher(int age)  //构造函数, 通过异常机制 处理错误

         {

                   if (age > 100)

                   {

                            throw out_of_range("年龄太大");

                   }

                   this->age = age;

         }

protected:

private:

         int age;

};

 

void mainxx()

{

         try

         {

                   Teacher t1(102);

         }

         catch (out_of_range e)

         {

                  

                   cout << e.what() << endl;

         }

 

         exception e;

         system("pause");

}

 

 

案例2

class Dog

{

public:

         Dog()

         {

                   parr = new int[1024*1024*100]; //4MB

         }

private:

         int *parr;

};

 

int main31()

{

         Dog *pDog;

         try{

                   for(int i=1; i<1024; i++) //40GB!

                   {

                            pDog = new Dog();

                            cout << i << ": new Dog 成功." << endl;

                   }

         }

         catch(bad_alloc err)

         {

                   cout << "new Dog 失败: " << err.what() << endl;

         }

 

         return 0;

 

}

 

案例3

8.4训练强化

 

9 C++输入和输出流

9.1 I/O流的概念和流类库的结构

程序的输入指的是从输入文件将数据传送给程序,程序的输出指的是从程序将数据传送给输出文件。

C++输入输出包含以下三个方面的内容:

    对系统指定的标准设备的输入和输出。即从键盘输入数据,输出到显示器屏幕。这种输入输出称为标准的输入输出,简称标准I/O。

    以外存磁盘文件为对象进行输入和输出,即从磁盘文件输入数据,数据输出到磁盘文件。以外存文件为对象的输入输出称为文件的输入输出,简称文件I/O。

    对内存中指定的空间进行输入和输出。通常指定一个字符数组作为存储空间(实际上可以利用该空间存储任何信息)。这种输入和输出称为字符串输入输出,简称串I/O。

 

C++的I/O对C的发展--类型安全和可扩展性

         在C语言中,用printf和scanf进行输入输出,往往不能保证所输入输出的数据是可靠的安全的。C++的输入输出中,编译系统对数据类型进行严格的检查,凡是类型不正确的数据都不可能通过编译。因此C++I/O操作是类型安全(type safe)的。C++的I/O操作是可扩展的,不仅可以用来输入输出标准类型的数据,也可以用于用户自定义类型的数据。

         C++通过I/O类库来实现丰富的I/O功能。这样使C++的输人输出明显地优于C语言中的printfscanf,但是也为之付出了代价,C++I/O系统变得比较复杂,要掌握许多细节。

         C++编译系统提供了用于输入输出的iostream类库。iostream这个单词是由3个部分组成的,即i-o-stream,意为输入输出流。在iostream类库中包含许多用于输入输出的类。常用的见表

ios是抽象基类,由它派生出istream类和ostream类,两个类名中第1个字母i和o分别代表输入(input)和输出(output)。 istream类支持输入操作,ostream类支持输出操作,iostream类支持输入输出操作。iostream类是从istream类和ostream类通过多重继承而派生的类。其继承层次见上图表示。

C++对文件的输入输出需要用ifstrcam和ofstream类,两个类名中第1个字母i和o分别代表输入和输出,第2个字母f代表文件 (file)。ifstream支持对文件的输入操作,ofstream支持对文件的输出操作。类ifstream继承了类istream,类ofstream继承了类ostream,类fstream继承了类iostream。见图

I/O类库中还有其他一些类,但是对于一般用户来说,以上这些已能满足需要了。

 

与iostream类库有关的头文件

iostream类库中不同的类的声明被放在不同的头文件中,用户在自己的程序中用#include命令包含了有关的头文件就相当于在本程序中声明了所需 要用到的类。可以换—种说法:头文件是程序与类库的接口,iostream类库的接口分别由不同的头文件来实现。常用的有

  • iostream  包含了对输入输出流进行操作所需的基本信息。
  • fstream  用于用户管理的文件的I/O操作。
  • strstream  用于字符串流I/O。
  • stdiostream  用于混合使用C和C + +的I/O机制时,例如想将C程序转变为C++程序。
  • iomanip  在使用格式化I/O时应包含此头文件。

在iostream头文件中定义的流对象

在 iostream 头文件中定义的类有 ios,istream,ostream,iostream,istream _withassign, ostream_withassign,iostream_withassign 等。

 

在iostream头文件中不仅定义了有关的类,还定义了4种流对象,

 

对象

含义

对应设备

对应的类

c语言中相应的标准文件

cin

标准输入流

键盘

istream_withassign

stdin

cout

标准输出流

屏幕

ostream_withassign

stdout

cerr

标准错误流

屏幕

ostream_withassign

stderr

clog

标准错误流

屏幕

ostream_withassign

stderr

 

在iostream头文件中定义以上4个流对象用以下的形式(以cout为例):
    ostream cout ( stdout);
在定义cout为ostream流类对象时,把标准输出设备stdout作为参数,这样它就与标准输出设备(显示器)联系起来,如果有
    cout <<3;
就会在显示器的屏幕上输出3。

 

在iostream头文件中重载运算符

“<<”和“>>”本来在C++中是被定义为左位移运算符和右位移运算符的,由于在iostream头文件中对它们进行了重载,使它们能用作标准类型数据的输入和输出运算符。所以,在用它们的程序中必须用#include命令把iostream包含到程序中。

    #include <iostream>

1)      >>a表示将数据放入a对象中。

2)      <<a表示将a对象中存储的数据拿出。

9.2标准I/O流

标准I/O对象:cin,cout,cerr,clog

cout流对象

cont是console output的缩写,意为在控制台(终端显示器)的输出。强调几点。

1) cout不是C++预定义的关键字,它是ostream流类的对象,在iostream中定义。 顾名思义,流是流动的数据,cout流是流向显示器的数据。cout流中的数据是用流插入运算符“<<”顺序加入的。如果有
    cout<<"I "<<"study C++"<<"very hard. << “wang bao ming ";

按顺序将字符串"I ", "study C++ ", "very hard."插人到cout流中,cout就将它们送到显示器,在显示器上输出字符串"I study C++ very hard."。cout流是容纳数据的载体,它并不是一个运算符。人们关心的是cout流中的内容,也就是向显示器输出什么。
2)用“ccmt<<”输出基本类型的数据时,可以不必考虑数据是什么类型,系统会判断数据的类型,并根据其类型选择调用与之匹配的运算符重载函数。这个过程都是自动的,用户不必干预。如果在C语言中用prinf函数输出不同类型的数据,必须分别指定相应的输出格式符,十分麻烦,而且容易出错。C++的I/O机制对用户来说,显然是方便而安全的。

3) cout流在内存中对应开辟了一个缓冲区,用来存放流中的数据,当向cout流插人一个endl时,不论缓冲区是否已满,都立即输出流中所有数据,然后插入一个换行符,并刷新流(清空缓冲区)。注意如果插人一个换行符”\n“(如cout<<a<<"\n"),则只输出和换行,而不刷新cout 流(但并不是所有编译系统都体现出这一区别)。
4) 在iostream中只对"<<"和">>"运算符用于标准类型数据的输入输出进行了重载,但未对用户声明的类型数据的输入输出进行重载。如果用户声明了新的类型,并希望用"<<"和">>"运算符对其进行输入输出,按照重运算符重载来做。

cerr流对象

cerr流对象是标准错误流,cerr流已被指定为与显示器关联。cerr的作用是向标准错误设备(standard error device)输出有关出错信息。cerr与标准输出流cout的作用和用法差不多。但有一点不同:cout流通常是传送到显示器输出,但也可以被重定向输出到磁盘文件,而cerr流中的信息只能在显示器输出。当调试程序时,往往不希望程序运行时的出错信息被送到其他文件,而要求在显示器上及时输出,这时应该用cerr。cerr流中的信息是用户根据需要指定的。

clog流对象

clog流对象也是标准错误流,它是console log的缩写。它的作用和cerr相同,都是在终端显示器上显示出错信息。区别:cerr是不经过缓冲区,直接向显示器上输出有关信息,而clog中的信息存放在缓冲区中,缓冲区满后或遇endl时向显示器输出。

 

缓冲区的概念:

9.2.1标准输入流

标准输入流对象cin,重点掌握的函数

         cin.get()//一次只能读取一个字符

         cin.get(一个参数) //读一个字符

         cin.get(三个参数) //可以读字符串

         cin.getline()

         cin.ignore()

         cin.peek()

         cin.putback()

标准输入流常见api编程案例

//1 cin cout能根据类型 获取数据 / 输出数据

//2 输入字符串 你 好  遇见空格,停止接受输入

void main01()

{

         char YourName[50];

         int myInt;

         long myLong;

         double myDouble;

         float myFloat;

         unsigned int myUnsigned;

 

         cout << "请输入一个Int: ";

         cin >> myInt;

         cout << "请输入一个Long: ";

         cin >> myLong;

         cout << "请输入一个Double: ";

         cin >> myDouble;

 

         cout << "请输入你的姓名: ";

         cin >> YourName;

 

         cout << "\n\n你输入的数是:" << endl;

         cout << "Int: \t" << myInt << endl;

         cout << "Long: \t" << myLong << endl;

         cout << "Double: \t" << myDouble << endl;

         cout << "姓名: \t" << YourName << endl;

         cout<< endl << endl;

         system("pause");

         return ;

}

 

//1 输入英文 ok

//2 ctr+z  会产生一个 EOF(-1)

int main02()

{

         char ch;

         while( (ch= cin.get())!= EOF)

         {

                   std::cout << "字符: " << ch << std::endl;

         }

         std::cout << "\n结束.\n";

         system("pause");

         return 0;

}

 

//演示:读一个字符 链式编程

void main03()

{

         char a, b, c;

         cin.get(a);

         cin.get(b);

         cin.get(c);

         cout << a << b << c<< endl;

 

         cout << "开始链式编程" << endl;

         cout.flush();

 

         cin.get(a).get(b).get(c);

         cout << a << b << c<< endl;

         system("pause");

         return ;

}

 

 

//演示cin.getline() 可以接受空格

void main04()

{

         char buf1[256];

         char buf2[256];

         cout << "\n请输入你的字符串 不超过256" ;

         cin.getline(buf1, 256, '\n');

         cout << buf1 << endl;

 

         //

         cout << "注意: cin.getline() 和 cin >> buf2 的区别, 能不能带空格 " << endl;

         cin >> buf2 ; //流提取操作符 遇见空格 停止提取输入流

         cout << buf2 << endl;

         system("pause");

}

 

//缓冲区实验

/*

1 输入 "aa bb cc dd" 字符串入缓冲区

2 通过 cin >> buf1; 提走了 aa

3 不需要输入 可以再通过cin.getline() 把剩余的缓冲区数据提走

*/

void main05()

{

         char buf1[256];

         char buf2[256];

 

         cout << "请输入带有空格的字符串,测试缓冲区" << endl;

         cin >> buf1;

         cout << "buf1:" << buf1 << endl;

 

         cout << "请输入数据..." << endl;

 

         //缓冲区没有数据,就等待; 缓冲区如果有数据直接从缓冲区中拿走数据

         cin.getline(buf2, 256);

         cout << "buf2:" << buf2 << endl;

         system("pause");

}

 

// ignore 和 peek

void main06()

{

         int  intchar;

         char buf1[256];

         char buf2[256];

 

         cout << "请输入带有空格的字符串,测试缓冲区 aa bb cc dd ee " << endl;

         cin >> buf1;

         cout << "buf1:" << buf1 << endl;

 

         cout << "请输入数据..." << endl;

         cin.ignore(2);

         //intchar = cin.peek();

         //cout << "缓冲区若有数据,返回第一个数据的asc码:" << intchar << endl;

 

         //缓冲区没有数据,就等待; 缓冲区如果有数据直接从缓冲区中拿走数据

         cin.getline(buf2, 256);

         cout << "buf2:" << buf2 << endl;

 

         intchar = cin.peek(); //没有缓冲区 默认是阻塞模式

         cout << "缓冲区若有数据,返回第一个数据的asc码:" << intchar << endl;

         system("pause");

}

 

//案例:输入的整数和字符串分开处理

int main07()

{

         cout << "Please, enter a number or a word: ";

         char c = std::cin.get();

 

         if ( (c >= '0') && (c <= '9') ) //输入的整数和字符串 分开处理

         {

                   int n; //整数不可能 中间有空格 使用cin >>n

                   cin.putback (c);

                   cin >> n;

                   cout << "You entered a number: " << n << '\n';

         }

         else

         {

                   string str;

                   cin.putback (c);

                   getline (cin,str); // //字符串 中间可能有空格 使用 cin.getline();

                   cout << "You entered a word: " << str << '\n';

         }        system("pause");

         return 0;

}

 

9.2.2标准输出流

/*

标准输出流对象cout

         cout.flush()

         cout.put()

         cout.write()

         cout.width()

         cout.fill()

         cout.setf(标记)

*/

/*

manipulator(操作符、控制符)

flush

endl

oct

dec

hex

setbase

setw

setfill

setprecision

*/

标准输出流常见api编程案例

#include "iostream"

using namespace std;

#include <iomanip>

 

void main81()

{

         cout << "hello world" << endl;

         cout.put('h').put('e').put('l').put('\n');

         cout.write("hello world", 4); //输出的长度

 

         char buf[] = "hello world";

         printf("\n");

         cout.write(buf, strlen(buf));

 

         printf("\n");

         cout.write(buf, strlen(buf) - 6);

 

         printf("\n");

         cout.write(buf, strlen(buf) + 6); //给的大于buf长度 不会帮我们检查 提高速度

 

         printf("\n");

        

         system("pause");

         return ;

}

 

//使用cout.setf()控制符

void main82()

{

         //使用类成员函数

         cout << "<start>";

         cout.width(30);

         cout.fill('*');

         cout.setf(ios::showbase); //#include <iomanip>

         cout.setf(ios::internal); //设置

         cout << hex << 123 << "<End>\n";

 

         cout << endl;

         cout << endl;

         //manipulator(操作符、控制符)

 

         //使用控制阀

         cout << "<Start>"

                   << setw(30)

                   << setfill('*')

                   << setiosflags(ios::showbase) //基数

                   << setiosflags(ios::internal)

                   << hex

                   << 123

                   << "<End>\n"

                   << endl;

 

         system("pause");

}

 

C++格式化输出,C++输出格式控制

在输出数据时,为简便起见,往往不指定输出的格式,由系统根据数据的类型采取默认的格式,但有时希望数据按指定的格式输出,如要求以十六进制或八进制形式输出一个 整数,对输出的小数只保留两位小数等。有两种方法可以达到此目的。

1)使用控制符的方法;

2)使用流对象的有关成员函数。分别叙述如下。

 

使用控制符的方法

int main()

{

         int a;

         cout<<"input a:";

         cin>>a;

         cout<<"dec:"<<dec<<a<<endl; //以十进制形式输出整数

         cout<<"hex:"<<hex<<a<<endl; //以十六进制形式输出整数a

         cout<<"oct:"<<setbase(8)<<a<<endl; //以八进制形式输出整数a

         char *pt="China"; //pt指向字符串"China"

         cout<<setw(10)<<pt<<endl; //指定域宽为,输出字符串

         cout<<setfill('*')<<setw(10)<<pt<<endl; //指定域宽,输出字符串,空白处以'*'填充

         double pi=22.0/7.0; //计算pi值

         //按指数形式输出,8位小数

         cout<<setiosflags(ios::scientific)<<setprecision(8);

         cout<<"pi="<<pi<<endl; //输出pi值

         cout<<"pi="<<setprecision(4)<<pi<<endl; //改为位小数

         cout<<"pi="<<setiosflags(ios::fixed)<<pi<<endl; //改为小数形式输出

         system("pause");

         return 0;

}

运行结果如下:
input a:34↙(输入a的值)
dec:34                   (十进制形式)
hex:22                   (十六进制形式)
oct:42                   (八进制形式)
         China               (域宽为)
*****China               (域宽为,空白处以'*'填充)
pi=3.14285714e+00        (指数形式输出,8位小数)
pi=3.1429e+00            (指数形式输出,4位小数)
pi=3.143                 (小数形式输出,精度仍为)

 

 

人们在输入输出时有一些特殊的要求,如在输出实数时规定字段宽度,只保留两位小数,数据向左或向右对齐等。C++提供了在输入输出流中使用的控制符(有的书中称为操纵符)

举例, 输出双精度数:
    double a=123.456789012345;  // 对a赋初值

1) cout<<a;  输出: 123.456
2) cout<<setprecision(9)<<a;  输出: 123.456789
3) cout<<setprecision(6);  恢复默认格式(精度为6)
4) cout<< setiosflags(ios∷fixed);  输出:123.456789
5) cout<<setiosflags(ios∷fixed)<<setprecision(8)<<a;  输出: 123.45678901
6) cout<<setiosflags(ios∷scientific)<<a;  输出: 1.234568e+02
7) cout<<setiosflags(ios∷scientific)<<setprecision(4)<<a; 输出: 1.2346e02

下面是整数输出的例子:
    int b=123456;  // 对b赋初值
1) cout<<b;  输出: 123456
2) cout<<hex<<b;   输出: 1e240
3) cout<<setiosflags(ios∷uppercase)<<b;  输出: 1E240
4) cout<<setw(10)<<b<<','<<b;   输出: 123456,123456
5) cout<<setfill('*')<<setw(10)<<b;  输出: ****123456
6) cout<<setiosflags(ios∷showpos)<<b;  输出: +123456

如果在多个cout语句中使用相同的setw(n),并使用setiosflags(ios::right),可以实现各行数据右对齐,如果指定相同的精度,可以实现上下小数点对齐。

 

例如:各行小数点对齐。

int main( )

{

         doublea=123.456,b=3.14159,c=-3214.67;

         cout<<setiosflags(ios::fixed)<<setiosflags(ios::right)<<setprecision(2);

         cout<<setw(10)<<a<<endl;

         cout<<setw(10)<<b<<endl;

         cout<<setw(10)<<c<<endl;

         system("pause");

         return0;

}

输出如下:
  123.46 (字段宽度为10,右对齐,取两位小数)
3.14
  -3214.67
先统一设置定点形式输出、取两位小数、右对齐。这些设置对其后的输出均有效(除非重新设置),而setw只对其后一个输出项有效,因此必须在输出a,b,c之前都要写setw(10)。

 

//

用流对象的成员函数控制输出格式

除了可以用控制符来控制输出格式外,还可以通过调用流对象cout中用于控制输出格式的成员函数来控制输出格式。用于控制输出格式的常用的成员函数如下:

流成员函数setf和控制符setiosflags括号中的参数表示格式状态,它是通过格式标志来指定的。格式标志在类ios中被定义为枚举值。因此在引用这些格式标志时要在前面加上类名ios和域运算符“::”。格式标志见表13.5。

例:用流控制成员函数输出数据。

int main( )

{

         int a=21;

         cout.setf(ios::showbase);//显示基数符号(0x或)

         cout<<"dec:"<<a<<endl; //默认以十进制形式输出a

         cout.unsetf(ios::dec); //终止十进制的格式设置

         cout.setf(ios::hex); //设置以十六进制输出的状态

         cout<<"hex:"<<a<<endl; //以十六进制形式输出a

         cout.unsetf(ios::hex); //终止十六进制的格式设置

         cout.setf(ios::oct); //设置以八进制输出的状态

         cout<<"oct:"<<a<<endl; //以八进制形式输出a

         cout.unsetf(ios::oct);

         char *pt="China"; //pt指向字符串"China"

         cout.width(10); //指定域宽为

         cout<<pt<<endl; //输出字符串

         cout.width(10); //指定域宽为

         cout.fill('*'); //指定空白处以'*'填充

         cout<<pt<<endl; //输出字符串

         double pi=22.0/7.0; //输出pi值

         cout.setf(ios::scientific); //指定用科学记数法输出

         cout<<"pi="; //输出"pi="

         cout.width(14); //指定域宽为

         cout<<pi<<endl; //输出pi值

         cout.unsetf(ios::scientific); //终止科学记数法状态

         cout.setf(ios::fixed); //指定用定点形式输出

         cout.width(12); //指定域宽为

         cout.setf(ios::showpos); //正数输出“+”号

         cout.setf(ios::internal); //数符出现在左侧

         cout.precision(6); //保留位小数

         cout<<pi<<endl; //输出pi,注意数符“+”的位置

         system("pause");

         return 0;

}

运行情况如下:
dec:21(十进制形式)
hex:0x15                 (十六进制形式,以x开头)
oct:025                  (八进制形式,以开头)
         China               (域宽为)
*****China               (域宽为,空白处以'*'填充)
pi=**3.142857e+00        (指数形式输出,域宽,默认位小数)
+***3.142857             (小数形式输出,精度为,最左侧输出数符“+”)

 

对程序的几点说明:

1) 成员函数width(n)和控制符setw(n)只对其后的第一个输出项有效。如:

cout. width(6);
    cout <<20 <<3.14<<endl;
输出结果为 203.14

在输出第一个输出项20时,域宽为6,因此在20前面有4个空格,在输出3.14时,width (6)已不起作用,此时按系统默认的域宽输出(按数据实际长度输出)。如果要求在输出数据时都按指定的同一域宽n输出,不能只调用一次width(n),而必须在输出每一项前都调用一次width(n>,上面的程序中就是这样做的。

2) 在表13.5中的输出格式状态分为5组,每一组中同时只能选用一种(例如dec、hex和oct中只能选一,它们是互相排斥的)。在用成员函数setf和控制符setiosflags设置输出格式状态后,如果想改设置为同组的另一状态,应当调用成员函数unsetf(对应于成员函数self)或 resetiosflags(对应于控制符setiosflags),先终止原来设置的状态。然后再设置其他状态,大家可以从本程序中看到这点。程序在开始虽然没有用成员函数self和控制符setiosflags设置用dec输出格式状态,但系统默认指定为dec,因此要改变为hex或oct,也应当先用unsetf 函数终止原来设置。如果删去程序中的第7行和第10行,虽然在第8行和第11行中用成员函数setf设置了hex和oct格式,由于未终止dec格式,因此hex和oct的设置均不起作用,系统依然以十进制形式输出。

同理,程序倒数第8行的unsetf 函数的调用也是不可缺少的。

3) 用setf 函数设置格式状态时,可以包含两个或多个格式标志,由于这些格式标志在ios类中被定义为枚举值,每一个格式标志以一个二进位代表,因此可以用位或运算符“|”组合多个格式标志。如倒数第5、第6行可以用下面一行代替:

   cout.setf(ios::internal I ios::showpos);  //包含两个状态标志,用"|"组合

3)      可以看到:对输出格式的控制,既可以用控制符(如例13.2),也可以用cout流的有关成员函数(如例13.3),二者的作用是相同的。控制符是在头文件iomanip中定义的,因此用控制符时,必须包含iomanip头文件。cout流的成员函数是在头文件iostream 中定义的,因此只需包含头文件iostream,不必包含iomanip。许多程序人员感到使用控制符方便简单,可以在一个cout输出语句中连续使用多种控制符。

9.3文件I/O

v  文件输入流 ifstream

v  文件输出流 ofstream

v  文件输入输出流 fstream

v  文件的打开方式

v  文件流的状态

v  文件流的定位:文件指针(输入指针、输出指针)

v  文本文件和二进制文件

9.3.1文件流类和文件流对象

输入输出是以系统指定的标准设备(输入设备为键盘,输出设备为显示器)为对象的。在实际应用中,常以磁盘文件作为对象。即从磁盘文件读取数据,将数据输出到磁盘文件。

和文件有关系的输入输出类主要在fstream.h这个头文件中被定义,在这个头文件中主要被定义了三个类,由这三个类控制对文件的各种输入输出操作,他们分别是ifstream、ofstream、fstream,其中fstream类是由iostream类派生而来,他们之间的继承关系见下图所示。

由于文件设备并不像显示器屏幕与键盘那样是标准默认设备,所以它在fstream.h头文件中是没有像cout那样预先定义的全局对象,所以我们必须自己定义一个该类的对象。

ifstream类,它是从istream类派生的,用来支持从磁盘文件的输入。

ofstream类,它是从ostream类派生的,用来支持向磁盘文件的输出。

fstream类,它是从iostream类派生的,用来支持对磁盘文件的输入输出。

9.3.2C++文件的打开与关闭

打开文件

所谓打开(open)文件是一种形象的说法,如同打开房门就可以进入房间活动一样。打开文件是指在文件读写之前做必要的准备工作,包括:

1)为文件流对象和指定的磁盘文件建立关联,以便使文件流流向指定的磁盘文件。

2)指定文件的工作方式,如,该文件是作为输入文件还是输出文件,是ASCII文件还是二进制文件等。

以上工作可以通过两种不同的方法实现。

 

1) 调用文件流的成员函数open。如

   ofstream outfile;  //定义ofstream类(输出文件流类)对象outfile

   outfile.open("f1.dat",ios::out);  //使文件流与f1.dat文件建立关联

第2行是调用输出文件流的成员函数open打开磁盘文件f1.dat,并指定它为输出文件,文件流对象outfile将向磁盘文件f1.dat输出数据。ios::out是I/O模式的一种,表示以输出方式打开一个文件。或者简单地说,此时f1.dat是一个输出文件,接收从内存输出的数据。

 

调用成员函数open的一般形式为:

    文件流对象.open(磁盘文件名, 输入输出方式);

磁盘文件名可以包括路径,如"c:\new\\f1.dat",如缺省路径,则默认为当前目录下的文件。

 

2) 在定义文件流对象时指定参数

在声明文件流类时定义了带参数的构造函数,其中包含了打开磁盘文件的功能。因此,可以在定义文件流对象时指定参数,调用文件流类的构造函数来实现打开文件的功能。如

   ostream outfile("f1.dat",ios::out); 一般多用此形式,比较方便。作用与open函数相同。

输入输出方式是在ios类中定义的,它们是枚举常量,有多种选择,见表13.6。

 

几点说明:
1) 新版本的I/O类库中不提供ios::nocreate和ios::noreplace。

2) 每一个打开的文件都有一个文件指针,该指针的初始位置由I/O方式指定,每次读写都从文件指针的当前位置开始。每读入一个字节,指针就后移一个字节。当文件指针移到最后,就会遇到文件结束EOF(文件结束符也占一个字节,其值为-1),此时流对象的成员函数eof的值为非0值(一般设为1),表示文件结束了。

3) 可以用“位或”运算符“|”对输入输出方式进行组合,如表13.6中最后3行所示那样。还可以举出下面一些例子:
    ios::in | ios:: noreplace  //打开一个输入文件,若文件不存在则返回打开失败的信息
    ios::app | ios::nocreate  //打开一个输出文件,在文件尾接着写数据,若文件不存在,则返回打开失败的信息
    ios::out l ios::noreplace  //打开一个新文件作为输出文件,如果文件已存在则返回打开失败的信息
    ios::in l ios::out I ios::binary  //打开一个二进制文件,可读可写

但不能组合互相排斥的方式,如 ios::nocreate l ios::noreplace。

4) 如果打开操作失败,open函数的返回值为0(假),如果是用调用构造函数的方式打开文件的,则流对象的值为0。可以据此测试打开是否成功。如
    if(outfile.open("f1.bat", ios::app) ==0)
        cout <<"open error";

    if( !outfile.open("f1.bat", ios::app) )
        cout <<"open error";

关闭文件

在对已打开的磁盘文件的读写操作完成后,应关闭该文件。关闭文件用成员函数close。如
    outfile.close( );  //将输出文件流所关联的磁盘文件关闭
所谓关闭,实际上是解除该磁盘文件与文件流的关联,原来设置的工作方式也失效,这样,就不能再通过文件流对该文件进行输入或输出。此时可以将文件流与其他磁盘文件建立关联,通过文件流对新的文件进行输入或输出。如
    outfile.open("f2.dat",ios::app|ios::nocreate);
此时文件流outfile与f2.dat建立关联,并指定了f2.dat的工作方式。

 

9.3.3C++对ASCII文件的读写操作

如果文件的每一个字节中均以ASCII代码形式存放数据,即一个字节存放一个字符,这个文件就是ASCII文件(或称字符文件)。程序可以从ASCII文件中读入若干个字符,也可以向它输出一些字符。

1)      用流插入运算符“<<”和流提取运算符“>>”输入输出标准类型的数据。“<<”和“ >>”都巳在iostream中被重载为能用于ostream和istream类对象的标准类型的输入输出。由于ifstream和 ofstream分别是ostream和istream类的派生类;因此它们从ostream和istream类继承了公用的重载函数,所以在对磁盘文件的操作中,可以通过文件流对象和流插入运算符“<<”及流提取运算符“>>”实现对磁盘文件的读写,如同用cin、cout和<<、>>对标准设备进行读写一样。

2)      用文件流的put、get、geiline等成员函数进行字符的输入输出,:用C++流成员函数put输出单个字符、C++ get()函数读入一个字符和C++ getline()函数读入一行字符。

案例1:写文件,然后读文件

 

 

#include <iostream>

using namespace std;

#include "fstream"

 

int main92()

{

         char fileName[80];

         char buffer[255];

 

         cout << "请输入一个文件名: ";

         cin >> fileName;

 

         ofstream fout(fileName, ios::app);

         fout << "1111111111111111111\n";

         fout << "22222222222222222\n";

         //cin.ignore(1,'\n');

         cin.getline(buffer,255); //从键盘输入

         fout << buffer << "\n";

         fout.close();

 

         ifstream fin(fileName);

         cout << "Here's the the content of the file: \n";

         char ch;

         while(fin.get(ch))

                   cout << ch;

 

         cout << "\n***End of file contents.***\n";

         fin.close();

         system("pause");

         return 0;

}

 

案例2(自学扩展思路)

ofstream类的默认构造函数原形为:

ofstream::ofstream(constchar *filename, intmode = ios::out,

 int penprot = filebuf::openprot);

 

·        filename:  要打开的文件名

·        mode:    要打开文件的方式

·        prot:    打开文件的属性

  其中mode和openprot这两个参数的可选项表见下表:

mode属性表

ios::app

以追加的方式打开文件

ios::ate

文件打开后定位到文件尾,ios:app就包含有此属性

ios::binary

以二进制方式打开文件,缺省的方式是文本方式。两种方式的区别见前文

ios::in

文件以输入方式打开

ios::out

文件以输出方式打开

ios::trunc

如果文件存在,把文件长度设为0

 

可以用“|”把以上属性连接起来,如ios::out|ios::binary。

openprot属性表

属性

含义

0

普通文件,打开访问

1

只读文件

2

隐含文件

4

系统文件

可以用“或”或者“+”把以上属性连接起来 ,如3或1|2就是以只读和隐含属性打开文件。

#include <fstream>
usingnamespace std;

int main() 
{
        ofstream myfile("c:\\1.txt",ios::out|ios::trunc,0);
        myfile<<"传智播客"<<endl<<"网址:"<<"www.itcast.cn";
        myfile.close()
        system("pause");
}

 

文件使用完后可以使用close成员函数关闭文件。

  ios::app为追加模式,在使用追加模式的时候同时进行文件状态的判断是一个比较好的习惯。

#include <iostream>
#include <fstream>
usingnamespace std;
int main() 
{
        ofstream myfile("c:\\1.txt",ios::app,0);
        if(!myfile)//或者写成myfile.fail()
        {
                cout<<"文件打开失败,目标文件状态可能为只读!";
                system("pause");
                exit(1);
        }
        myfile<<"传智播客"<<endl<<"网址:"<<" www.itcast.cn "<<endl;
        myfile.close();
}

 

  在定义ifstream和ofstream类对象的时候,我们也可以不指定文件。以后可以通过成员函数open()显式的把一个文件连接到一个类对象上。

  例如:

#include <iostream>
#include <fstream>
usingnamespace std;
int main() 
{
        ofstream myfile;
        myfile.open("c:\\1.txt",ios::out|ios::app,0);
        if(!myfile)//或者写成myfile.fail()
        {
                cout<<"文件创建失败,磁盘不可写或者文件为只读!";
                system("pause");
                exit(1);
        }
        myfile<<"传智播客"<<endl<<"网址:"<<"www.itcast.cn"<<endl;
        myfile.close();
}

 

下面我们来看一下是如何利用ifstream类对象,将文件中的数据读取出来,然后再输出到标准设备中的例子。

#include <iostream>
#include <fstream>
#include <string>
usingnamespace std;
int main() 
{
        ifstream myfile;
        myfile.open("c:\\1.txt",ios::in,0);
        if(!myfile)
        {
                cout<<"文件读错误";
                system("pause");
                exit(1);
        }
        char ch;
        string content;
        while(myfile.get(ch))
        {
                content+=ch;
                cout.put(ch);//cout<<ch;这么写也是可以的
        }
        myfile.close();
        cout<<content;
        system("pause");
}

 

上例中,我们利用成员函数get(),逐一的读取文件中的有效字符,再利用put()成员函数,将文件中的数据通过循环逐一输出到标准设备(屏幕) 上, get()成员函数会在文件读到默尾的时候返回假值,所以我们可以利用它的这个特性作为while循环的终止条件,我们同时也在上例中引入了C++风格的字符串类型string,在循环读取的时候逐一保存到content中,要使用string类型,必须包含string.h的头文件。

我们在简单介绍过ofstream类和ifstream类后,我们再来看一下fstream类,fstream类是由iostream派生而来,fstream类对象可以同对文件进行读写操作。

#include <iostream>
#include <fstream>
usingnamespace std;
int main() 
{
        fstream myfile;
        myfile.open("c:\\1.txt",ios::out|ios::app,0);
        if(!myfile)
        {
                cout<<"文件写错误,文件属性可能为只读!"<<endl;
                system("pause");
                exit(1);
        }
        myfile<<"传智播客"<<endl<<"网址:"<<"www.itcast.cn"<<endl;  
        myfile.close();
       
        myfile.open("c:\\1.txt",ios::in,0);
        if(!myfile)
        {
                cout<<"文件读错误,文件可能丢失!"<<endl;
                system("pause");
                exit(1);
        }
        char ch;
        while(myfile.get(ch))
        {
                cout.put(ch);
        }
        myfile.close();
        system("pause");
}

 

 

由于fstream类可以对文件同时进行读写操作,所以对它的对象进行初始话的时候一定要显式的指定mode和openprot参数。

 

9.3.4 C++对二进制文件的读写操作

二进制文件不是以ASCII代码存放数据的,它将内存中数据存储形式不加转换地传送到磁盘文件,因此它又称为内存数据的映像文件。因为文件中的信息不是字符数据,而是字节中的二进制形式的信息,因此它又称为字节文件

对二进制文件的操作也需要先打开文件,用完后要关闭文件。在打开时要用ios::binary指定为以二进制形式传送和存储。二进制文件除了可以作为输入文件或输出文件外,还可以是既能输入又能输出的文件。这是和ASCII文件不同的地方。

用成员函数read和write读写二进制文件

对二进制文件的读写主要用istream类的成员函数read和write来实现。这两个成员函数的原型为
    istream& read(char *buffer,int len);
    ostream& write(const char * buffer,int len);
字符指针buffer指向内存中一段存储空间。len是读写的字节数。调用的方式为:
    a. write(p1,50);
    b. read(p2,30);
上面第一行中的a是输出文件流对象,write函数将字符指针p1所给出的地址开始的50个字节的内容不加转换地写到磁盘文件中。在第二行中,b是输入文件流对象,read 函数从b所关联的磁盘文件中,读入30个字节(或遇EOF结束),存放在字符指针p2所指的一段空间内。

案例1

//二进制

int main()

{

         charfileName[255] = "c:/teacher.dat";

         ofstreamfout(fileName,ios::binary);

         if(!fout)

         {

                   cout<< "Unable to open " << fileName << " forwriting.\n";

                   return(1);

         }

 

         Teachert1(31, "31");

         Teachert2(32, "32");

         fout.write((char*)&t1,sizeof Teacher);

         fout.write((char*)&t2,sizeof Teacher);

         fout.close();

 

         cout<< "保存对象到二进制文件里成功!" << endl;

 

         ifstreamfin(fileName,ios::binary);

         if(!fin)

         {

                   cout<< "Unable to open " << fileName << " forreading.\n";

                   return(1);

         }

         Teachertmp(100,"100");

 

         fin.read((char*)&tmp,sizeof Teacher);

         tmp.printT();

         fin.read((char*)&tmp,sizeof Teacher);

         tmp.printT();

         system("pause");

 

         return0;

}

 

9.4作业练习

1 编程实现以下数据输入/输出:

   (1)以左对齐方式输出整数,域宽为12。

   (2)以八进制、十进制、十六进制输入/输出整数。

   (3)实现浮点数的指数格式和定点格式的输入/输出,并指定精度。

    (4)把字符串读入字符型数组变量中,从键盘输入,要求输入串的空格也全部读入,以回车符结束。

    (5)将以上要求用流成员函数和操作符各做一遍。

2编写一程序,将两个文件合并成一个文件。

3编写一程序,统计一篇英文文章中单词的个数与行数。

4编写一程序,将C++源程序每行前加上行号与一个空格。

4.5编写一程序,输出ASCII码值从20到127的ASCII码字符表,格式为每行10个。

 

参考答案:

第一题

Ios类成员函数实现

#include<iostream>

#include<iomanip>

using namespace std;

int main(){

         long a=234;

         double b=2345.67890;

         char c[100];

         cout.fill('*');

         cout.flags(ios_base::left);

         cout.width(12);

         cout<<a<<endl;

         cout.fill('*');

         cout.flags(ios::right);

         cout.width(12);

         cout<<a<<endl;

         cout.flags(ios.hex);

         cout<<234<<'\t';

         cout.flags(ios.dec);

         cout<<234<<'\t';

         cout.flags(ios.oct);

         cout<<234<<endl;

         cout.flags(ios::scientific);

         cout<<b<<'\t';

         cout.flags(ios::fixed);

         cout<<b<<endl;

         cin.get(c,99);

         cout<<c<<endl;

         return 0;

}

操作符实现

#include<iostream>

#include<iomanip>

using namespace std;

int main(){

         long a=234;

         double b=2345.67890;

         char c[100];

         cout<<setfill('*');

         cout<<left<<setw(12)<<a<<endl;

         cout<<right<<setw(12)<<a<<endl;

         cout<<hex<<a<<'\t'<<dec<<a<<'\t'<<oct<<a<<endl;

         cout<<scientific<<b<<'\t'<<fixed<<b<<endl;

         return 0;

}

 

 

第二题:

 

#include<iostream>

#include<fstream>

using namespace std;

int main(){

         int i=1;

         char c[1000];

         ifstream ifile1("D:\\1.cpp");

         ifstream ifile2("D:\\2.cpp");

         ofstream ofile("D:\\3.cpp");

         while(!ifile1.eof()){

                   ifile1.getline(c,999);

                   ofile<<c<<endl;

         }

         while(!ifile2.eof()){

                   ifile2.getline(c,999);

                   ofile<<c<<endl;

         }

         ifile1.close();

         ifile2.close();

         ofile.close();

         return 0;

}

 

第三题

#include<iostream>

#include<fstream>

using namespace std;

bool isalph(char);

int main(){

         ifstream ifile("C:\\daily.doc");

         char text[1000];

         bool inword=false;

         int rows=0,words=0;

         int i;

         while(!ifile.eof()){

                   ifile.getline(text,999);

                   rows++;

                   i=0;

                   while(text[i]!=0){

                            if(!isalph(text[i]))

                                     inword=false;

                            else if(isalph(text[i]) && inword==false){

                                     words++;

                                     inword=true;

                            }

                            i++;

                   }

         }

         cout<<"rows= "<<rows<<endl;

         cout<<"words= "<<words<<endl;

         ifile.close ();

         return 0;

}

bool isalph(char c){

         return ((c>='A' && c<='Z') || (c>='a' && c<='z'));

}

 

 

 

第四题

#include<iostream>

#include<fstream>

using namespace std;

int main(){

         int i=1;

         char c[1000];

         ifstream ifile("D:\\1.cpp");

         ofstream ofile("D:\\2.cpp");

         while(!ifile.eof()){

                   ofile<<i++<<": ";

                   ifile.getline(c,999);

                   ofile<<c<<endl;

         }

         ifile.close();

         ofile.close();

         return 0;

}

 

 

第五题

#include<iostream>

using namespace std;

int main(){

         int i,l;

         for(i=32;i<127;i++){

                   cout<<char(i)<<" ";

                   l++;

                   if(l%10==0)cout<<endl;

         }

         cout<<endl;

         return 0;

}

 

 

10、STL实用技术专题

10.1 STL(标准模板库)理论基础

10.1.1基本概念

STL(Standard Template Library,标准模板库)是惠普实验室开发的一系列软件的统称。现然主要出现在C++中,但在被引入C++之前该技术就已经存在了很长的一段时间。

STL的从广义上讲分为三类:algorithm(算法)、container(容器)和iterator(迭代器),容器和算法通过迭代器可以进行无缝地连接。几乎所有的代码都采 用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。在C++标准中,STL被组织为下面的13个头文 件:<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack> 和<utility>。

 

 

 

 

 

 

 

 

 


STL详细的说六大组件

–   容器(Container)

–   算法(Algorithm)

–   迭代器(Iterator)

–   仿函数(Function object)

–   适配器(Adaptor)

–   空间配制器(allocator)

使用STL的好处

1)STL是C++的一部分,因此不用额外安装什么,它被内建在你的编译器之内。

2)STL的一个重要特点是数据结构和算法的分离。尽管这是个简单的概念,但是这种分离确实使得STL变得非常通用。

例如,在STL的vector容器中,可以放入元素、基础数据类型变量、元素的地址;

STL的sort()函数可以用来操作vector,list等容器。

3)  程序员可以不用思考STL具体的实现过程,只要能够熟练使用STL就OK了。这样他们就可以把精力放在程序开发的别的方面。

4)  STL具有高可重用性,高性能,高移植性,跨平台的优点。

高可重用性:STL中几乎所有的代码都采用了模板类和模版函数的方式实现,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。关于模板的知识,已经给大家介绍了。

高性能:如map可以高效地从十万条记录里面查找出指定的记录,因为map是采用红黑树的变体实现的。(红黑树是平横二叉树的一种)

高移植性:如在项目A上用STL编写的模块,可以直接移植到项目B上。

                 跨平台:如用windows的VisualStudio编写的代码可以在Mac OS的XCode上直接编译。

5)  程序员可以不用思考STL具体的实现过程,只要能够熟练使用STL就OK了。这样他们就可以把精力放在程序开发的别的方面。

6)  了解到STL的这些好处,我们知道STL无疑是最值得C++程序员骄傲的一部分。每一个C++程序员都应该好好学习STL。只有能够熟练使用STL的程序员,才是好的C++程序员。

7)  总之:招聘工作中,经常遇到C++程序员对STL不是非常了解。大多是有一个大致的映像,而对于在什么情况下应该使用哪个容器和算法都感到比较茫然。STLC++程序员的一项不可或缺的基本技能,掌握它对提升C++编程大有裨益。

10.1.2容器

在实际的开发过程中,数据结构本身的重要性不会逊于操作于数据结构的算法的重要性,当程序中存在着对时间要求很高的部分时,数据结构的选择就显得更加重要。

  经典的数据结构数量有限,但是我们常常重复着一些为了实现向量、链表等结构而编写的代码,这些代码都十分相似,只是为了适应不同数据的变化而在细节上有所出入。STL容器就为我们提供了这样的方便,它允许我们重复利用已有的实现构造自己的特定类型下的数据结构,通过设置一些模板,STL容器对最常用的数据结构提供了支持,这些模板的参数允许我们指定容器中元素的数据类型,可以将我们许多重复而乏味的工作简化。

容器部分主要由头文 件<vector>,<list>,<deque>,<set>,<map>,<stack>和<queue>组成。对于常用的一些容器和容器适配器(可以看作由其它容器实现的容器),可以通过下表总结一下它们和相应头文件的对应关系。

10.1.2.1容器的概念

用来管理一组元素

   

10.1.2.2容器的分类

序列式容器(Sequence containers)

每个元素都有固定位置--取决于插入时机和地点,和元素值无关。

vector、deque、list 

关联式容器(Associated containers)

元素位置取决于特定的排序准则,和插入顺序无关

set、multiset、map、multimap

 

数据结构

描述

实现头文件

向量(vector)

连续存储的元素

<vector>

列表(list)

由节点组成的双向链表,每个结点包含着一个元素

<list>

双队列(deque)

连续存储的指向不同元素的指针所组成的数组

<deque>

集合(set)

由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序

<set>

多重集合(multiset)

允许存在两个次序相等的元素的集合

<set>

栈(stack)

后进先出的值的排列

<stack>

队列(queue)

先进先出的执的排列

<queue>

优先队列(priority_queue)

元素的次序是由作用于所存储的值对上的某种谓词决定的的一种队列

<queue>

映射(map)

由{键,值}对组成的集合,以某种作用于键对上的谓词排列

<map>

多重映射(multimap)

允许键对有相等的次序的映射

<map>

10.1.3迭代器

迭代器从作用上来说是最基本的部分,可是理解起来比前两者都要费力一些。软件设计有一个基本原则,所有的问题都可以通过引进一个间接层来简化,这种简化在STL中就是用迭代器来完成的。概括来说,迭代器在STL中用来将算法和容器联系起来,起着一种黏和剂的作用。几乎STL提供的所有算法都是通过迭代器存取元素序列进行工作的,每一个容器都定义了其本身所专有的迭代器,用以存取容器中的元素。

  迭代器部分主要由头文件<utility>,<iterator>和<memory>组 成。<utility>是一个很小的头文件,它包括了贯穿使用在STL中的几个模板的声明,<iterator>中提供了迭代器使用的许多方法,而对于<memory>的描述则十分的困难,它以不同寻常的方式为容器中的元素分配存储空间,同时也为某些算法执行期间产生的临时对象提供机制,<memory>中的主要部分是模板类allocator,它负责产生所有容器中的默认分配器。

10.1.4算法

函数库对数据类型的选择对其可重用性起着至关重要的作用。举例来说,一个求方根的函数,在使用浮点数作为其参数类型的情况下的可重用性肯定比使用整型作为它的参数类性要高。而C++通过模板的机制允许推迟对某些类型的选择,直到真正想使用模板或者说对模板进行特化的时候,STL就利用了这一点提供了相当多的有用算法。它是在一个有效的框架中完成这些算法的——可以将所有的类型划分为少数的几类,然后就可以在模版的参数中使用一种类型替换掉同一种类中的其他类型。

  STL提供了大约100个实现算法的模版函数,比如算法for_each将为指定序列中的每一个元素调用指定的函数,stable_sort以你所指定的规则对序列进行稳定性排序等等。这样一来,只要熟悉了STL之后,许多代码可以被大大的化简,只需要通过调用一两个算法模板,就可以完成所需要的功能并大大地提升效率。

算法部分主要由头文件<algorithm><numeric><functional>组 成。<algorithm>是所有STL头文件中最大的一个(尽管它很好理解),它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。<numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。<functional>中则定义了一些模板类,用以声明函数对象。

10.1.5C++标准库

C++强大的功能来源于其丰富的类库及库函数资源。C++标准库的内容总共在50个标准头文件中定义。在C++开发中,要尽可能地利用标准库完成。这样做的直接好处包括:(1)成本:已经作为标准提供,何苦再花费时间、人力重新开发呢;(2)质量:标准库的都是经过严格测试的,正确性有保证;(3)效率:关于人的效率已经体现在成本中了,关于代码的执行效率要相信实现标准库的大牛们的水平;(4)良好的编程风格:采用行业中普遍的做法进行开发。

在C++程序设计课程中,尤其是作为第一门程序设计课程,我们注重了语法、语言的机制等方面的内容。程序设计能力的培养有个过程,跨过基本的原理性知识直接进入到工程中的普遍做法,由于跨度决定了其难度。再者,在掌握了基本原理的基础上,在认识标准库的问题上完全可以凭借实践,逐步地掌握。标准库的学习不需要认认真真地读书,需要的是在了解概貌的情况下,在实践中深入。

这个任务就是要知道C++程序设计课程中不讲的,但对程序设计又很重要的这部分内容。至少我们要能先回答出“有什么”的问题。

 

C++标准库的内容分为10类,分别是(建议在阅读中,将你已经用过或听说过的头文件划出来):  

C1. 标准库中与语言支持功能相关的头文件

头文件

描        述

<cstddef>

定义宏NULL和offsetof,以及其他标准类型size_t和ptrdiff_t。与对应的标准C头文件的区别是,NULL是C++空指针常量的补充定义,宏offsetof接受结构或者联合类型参数,只要他们没有成员指针类型的非静态成员即可。

<limits>

提供与基本数据类型相关的定义。例如,对于每个数值数据类型,它定义了可以表示出来的最大值和最小值以及二进制数字的位数。

<climits>

提供与基本整数数据类型相关的C样式定义。这些信息的C++样式定义在<limits>中

<cfloat>

提供与基本浮点型数据类型相关的C样式定义。这些信息的C++样式定义在<limits>中

<cstdlib>

提供支持程序启动和终止的宏和函数。这个头文件还声明了许多其他杂项函数,例如搜索和排序函数,从字符串转换为数值等函数。它与对应的标准C头文件 stdlib.h不同,定义了abort(void)。abort()函数还有额外的功能,它不为静态或自动对象调用析构函数,也不调用传给 atexit()函数的函数。它还定义了exit()函数的额外功能,可以释放静态对象,以注册的逆序调用用atexit()注册的函数。清除并关闭所有 打开的C流,把控制权返回给主机环境。

<new>

支持动态内存分配

<typeinfo>

支持变量在运行期间的类型标识

<exception>

支持异常处理,这是处理程序中可能发生的错误的一种方式

<cstdarg>

支持接受数量可变的参数的函数。即在调用函数时,可以给函数传送数量不等的数据项。它定义了宏va_arg、va_end、va_start以及va_list类型

<csetjmp>

为C样式的非本地跳跃提供函数。这些函数在C++中不常用

<csignal>

为中断处理提供C样式支持

 

C2. 支持流输入/输出的头文件

头文件

描        述

<iostream>

支持标准流cin、cout、cerr和clog的输入和输出,它还支持多字节字符标准流wcin、wcout、wcerr和wclog。

<iomanip>

提供操纵程序,允许改变流的状态,从而改变输出的格式。

<ios>

定义iostream的基类

<istream>

为管理输出流缓存区的输入定义模板类

<ostream>

为管理输出流缓存区的输出定义模板类

<sstream>

支持字符串的流输入输出

<fstream>

支持文件的流输入输出

<iosfwd>

为输入输出对象提供向前的声明

<streambuf>

支持流输入和输出的缓存

<cstdio>

为标准流提供C样式的输入和输出

<cwchar>

支持多字节字符的C样式输入输出

 

C3. 与诊断功能相关的头文件

头文件

描        述

<stdexcept>

定义标准异常。异常是处理错误的方式

<cassert>

定义断言宏,用于检查运行期间的情形

<cerrno>

支持C样式的错误信息

 

C4. 定义工具函数的头文件

头文件

描        述

<utility>

定义重载的关系运算符,简化关系运算符的写入,它还定义了pair类型,该类型是一种模板类型,可以存储一对值。这些功能在库的其他地方使用

<functional>

定义了许多函数对象类型和支持函数对象的功能,函数对象是支持operator()()函数调用运算符的任意对象

<memory>

给容器、管理内存的函数和auto_ptr模板类定义标准内存分配器

<ctime>

支持系统时钟函数

 

C5. 支持字符串处理的头文件   

头文件

描        述

<string>

为字符串类型提供支持和定义,包括单字节字符串(由char组成)的string和多字节字符串(由wchar_t组成)

<cctype>

单字节字符类别

<cwctype>

多字节字符类别

<cstring>

为处理非空字节序列和内存块提供函数。这不同于对应的标准C库头文件,几个C样式字符串的一般C库函数被返回值为const和非const的函数对替代了

<cwchar>

为处理、执行I/O和转换多字节字符序列提供函数,这不同于对应的标准C库头文件,几个多字节C样式字符串操作的一般C库函数被返回值为const和非const的函数对替代了。

<cstdlib>

为把单字节字符串转换为数值、在多字节字符和多字节字符串之间转换提供函数

 

C6. 定义容器类的模板的头文件  

<vector>

定义vector序列模板,这是一个大小可以重新设置的数组类型,比普通数组更安全、更灵活

<list>

定义list序列模板,这是一个序列的链表,常常在任意位置插入和删除元素

<deque>

定义deque序列模板,支持在开始和结尾的高效插入和删除操作

<queue>

为队列(先进先出)数据结构定义序列适配器queue和priority_queue

<stack>

为堆栈(后进先出)数据结构定义序列适配器stack

<map>

map是一个关联容器类型,允许根据键值是唯一的,且按照升序存储。multimap类似于map,但键不是唯一的。

<set>

set是一个关联容器类型,用于以升序方式存储唯一值。multiset类似于set,但是值不必是唯一的。

<bitset>

为固定长度的位序列定义bitset模板,它可以看作固定长度的紧凑型bool数组

 

C7. 支持迭代器的头文件  

头文件

描        述

<iterator>

给迭代器提供定义和支持

 

C8. 有关算法的头文件  

头文件

描        述

<algorithm>

提供一组基于算法的函数,包括置换、排序、合并和搜索

<cstdlib>

声明C标准库函数bsearch()和qsort(),进行搜索和排序

<ciso646>

允许在代码中使用and代替&&

 

C9. 有关数值操作的头文件  

头文件

描        述

<complex>

支持复杂数值的定义和操作

<valarray>

支持数值矢量的操作

<numeric>

在数值序列上定义一组一般数学操作,例如accumulate和inner_product

<cmath>

这是C数学库,其中还附加了重载函数,以支持C++约定

<cstdlib>

提供的函数可以提取整数的绝对值,对整数进行取余数操作

 

C10. 有关本地化的头文件  

头文件

描        述

<locale>

提供的本地化包括字符类别、排序序列以及货币和日期表示。

<clocale>

对本地化提供C样式支持

 

C++标准库的所有头文件都没有扩展名。C++标准库以<cname>形式的标准头文件提供。在 <cname>形式标准的头文件中,与宏相关的名称在全局作用域中定义,其他名称在std命名空间中声明。在C++中还可以使用name.h 形式的标准C库头文件名

 

10.1.6模板简要回顾

²  模板是实现代码重用机制的一种工具,实质就是实现类型参数化,即把类型定义为参数。

²  C++提供两种模板:函数模板,类模板

 

函数模板的简介

²  函数模板就是建立一个通用的函数,其函数返回类型和形参类型不具体指定,而是用虚拟的类型来代表。

²  凡是函数体相同的函数都可以用函数模板来代替,不必定义多个函数,只需在模板中定义一次即可。

²  在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。

类模板的简介

²  我们先来看一下下面这个类,求最大值的类

²  和函数模板一样,类模板就是建立一个通用类,其数据成员的类型、成员函数的返回类型和参数类形都可以不具体指定,而用虚拟的类型来代表。

²  当使用类模板建立对象时,系统会根据实参的类型取代类模板中的虚拟类型,从而实现不同类的功能。

10.2容器

10.2.1 STL的string

1String概念

²  string是STL的字符串类型,通常用来表示字符串。而在使用string之前,字符串通常是用char*表示的。string与char*都可以用来表示字符串,那么二者有什么区别呢。

string和char*的比较

²  string是一个类, char*是一个指向字符的指针。

        string封装了char*,管理这个字符串,是一个char*型的容器。

²  string不用考虑内存释放和越界。

        string管理char*所分配的内存。每一次string的复制,取值都由string类负责维护,不用担心复制越界和取值越界等。

²  string提供了一系列的字符串操作函数(这个等下会详讲)

          查找find,拷贝copy,删除erase,替换replace,插入insert

2string的构造函数

²  默认构造函数:

string();       //构造一个空的字符串string s1。

²  拷贝构造函数:

string(const string &str);         //构造一个与str一样的string。如strings1(s2)。

²  带参数的构造函数

               string(const char *s);    //用字符串s初始化

               string(int n,char c);    //用n个字符c初始化

3string的存取字符操作

²  string类的字符操作:

const char &operator[] (int n) const;

const char &at(int n) const;

char &operator[] (int n);

char &at(int n);

²  operator[]和at()均返回当前字符串中第n个字符,但二者是有区别的。

       主要区别在于at()在越界时会抛出异常,[]在刚好越界时会返回(char)0,再继续越界时,编译器直接出错。如果你的程序希望可以通过try,catch捕获异常,建议采用at()。

4从string取得const char*的操作

²   const char *c_str() const;  //返回一个以'\0'结尾的字符串的首地址

5把string拷贝到char*指向的内存空间的操作

²  int copy(char *s, int n, int pos=0) const; 

把当前串中以pos开始的n个字符拷贝到以s为起始位置的字符数组中,返回实际拷贝的数目。注意要保证s所指向的空间足够大以容纳当前字符串,不然会越界。

6string的长度

int length() const;   //返回当前字符串的长度。长度不包括字符串结尾的'\0'。

bool empty() const;     //当前字符串是否为空

7string的赋值

string &operator=(const string&s);//把字符串s赋给当前的字符串

string &assign(const char *s); //把字符串s赋给当前的字符串

string &assign(const char *s, int n);//把字符串s的前n个字符赋给当前的字符串

string &assign(const string&s);  //把字符串s赋给当前字符串

string &assign(int n,char c);  //用n个字符c赋给当前字符串

string &assign(const string &s,intstart, int n);  //把字符串s中从start开始的n个字符赋给当前字符串

8string字符串连接

string &operator+=(const string&s);  //把字符串s连接到当前字符串结尾

string &operator+=(const char *s);//把字符串s连接到当前字符串结尾

string &append(const char *s);    //把字符串s连接到当前字符串结尾

string &append(const char *s,intn);  //把字符串s的前n个字符连接到当前字符串结尾

string &append(const string&s);   //同operator+=()

string &append(const string &s,intpos, int n);//把字符串s中从pos开始的n个字符连接到当前字符串结尾

string &append(int n, char c);   //在当前字符串结尾添加n个字符c

9string的比较

int compare(const string &s)const;  //与字符串s比较

int compare(const char *s) const;   //与字符串s比较

compare函数在>时返回 1,<时返回 -1,==时返回 0。比较区分大小写,比较时参考字典顺序,排越前面的越小。大写的A比小写的a小。

10string的子串

string substr(int pos=0, int n=npos)const;    //返回由pos开始的n个字符组成的子字符串

11string的查找和 替换

查找

int find(char c,int pos=0) const;  //从pos开始查找字符c在当前字符串的位置

int find(const char *s, int pos=0)const;  //从pos开始查找字符串s在当前字符串的位置

int find(const string &s, int pos=0)const;  //从pos开始查找字符串s在当前字符串中的位置

find函数如果查找不到,就返回-1

int rfind(char c, int pos=npos) const;   //从pos开始从后向前查找字符c在当前字符串中的位置

int rfind(const char *s, int pos=npos)const;

int rfind(const string &s, intpos=npos) const;

//rfind是反向查找的意思,如果查找不到, 返回-1

 

替换

string &replace(int pos, int n, constchar *s);//删除从pos开始的n个字符,然后在pos处插入串s

string &replace(int pos, int n, conststring &s);  //删除从pos开始的n个字符,然后在pos处插入串s

void swap(string &s2);    //交换当前字符串与s2的值

 

//4 字符串的查找和替换

void main25()

{

         strings1 = "wbm hello wbm 111 wbm 222 wbm 333";

         size_tindex = s1.find("wbm", 0);

         cout<< "index: " << index;

 

 

         //求itcast出现的次数

         size_toffindex = s1.find("wbm", 0);

         while(offindex != string::npos)

         {

                   cout<< "在下标index: " << offindex << "找到wbm\n";

                   offindex= offindex + 1;

                   offindex= s1.find("wbm", offindex);

         }

 

         //替换

         strings2 = "wbm hello wbm 111 wbm 222 wbm 333";

         s2.replace(0,3, "wbm");

         cout<< s2 << endl;

 

         //求itcast出现的次数

         offindex= s2.find("wbm", 0);

         while(offindex != string::npos)

         {

                   cout<< "在下标index: " << offindex << "找到wbm\n";

                   s2.replace(offindex,3, "WBM");

                   offindex= offindex + 1;

                   offindex= s1.find("wbm", offindex);

         }

         cout<< "替换以后的s2:" << s2 << endl;

}

12String的区间删除和插入

string &insert(int pos, const char *s);

string &insert(int pos, const string&s);

//前两个函数在pos位置插入字符串s

string &insert(int pos, int n, charc);  //在pos位置 插入n个字符c

 

string &erase(int pos=0, intn=npos);  //删除pos开始的n个字符,返回修改后的字符串

 

13string算法相关

void main27()

{

         strings2 = "AAAbbb";

         transform(s2.begin(),s2.end(), s2.begin(), toupper);

         cout<< s2 << endl;

 

         strings3 = "AAAbbb";

         transform(s3.begin(),s3.end(), s3.begin(), tolower);

         cout<< s3 << endl;

}

10.2.2Vector容器

1Vector容器简介

²  vector是将元素置于一个动态数组中加以管理的容器。

²  vector可以随机存取元素(支持索引值直接存取,用[]操作符或at()方法,这个等下会详讲)。

vector尾部添加或移除元素非常快速。但是在中部或头部插入元素或移除元素比较费时

2vector对象的默认构造

vector采用模板类实现,vector对象的默认构造形式

vector<T> vecT;

 

vector<int> vecInt;          //一个存放int的vector容器。

vector<float> vecFloat;     //一个存放float的vector容器。

vector<string> vecString;     //一个存放string的vector容器。

...                                      //尖括号内还可以设置指针类型或自定义类型。

Class CA{};

vector<CA*> vecpCA;             //用于存放CA对象的指针的vector容器。

vector<CA> vecCA;             //用于存放CA对象的vector容器。由于容器元素的存放是按值复制的方式进行的,所以此时CA必须提供CA的拷贝构造函数,以保证CA对象间拷贝正常。

3vector对象的带参数构造

理论知识

²  vector(beg,end);    //构造函数将[beg,end)区间中的元素拷贝给本身。注意该区间是左闭右开的区间。

²  vector(n,elem);   //构造函数将n个elem拷贝给本身。

²  vector(const vector &vec); //拷贝构造函数

 

int iArray[] = {0,1,2,3,4};

vector<int>  vecIntA( iArray,  iArray+5 );

 

vector<int> vecIntB (  vecIntA.begin() , vecIntA.end()  );   //用构造函数初始化容器vecIntB

vector<int> vecIntB (  vecIntA.begin() , vecIntA.begin()+3  ); 

vector<int> vecIntC(3,9); //此代码运行后,容器vecIntB就存放3个元素,每个元素的值是9。

 

vector<int> vecIntD(vecIntA);

 

4vector的赋值

理论知识

²  vector.assign(beg,end);    //将[beg, end)区间中的数据拷贝赋值给本身。注意该区间是左闭右开的区间。

²  vector.assign(n,elem);  //将n个elem拷贝赋值给本身。

²  vector& operator=(const vector &vec);          //重载等号操作符

²  vector.swap(vec);  // 将vec与本身的元素互换。

 

vector<int> vecIntA, vecIntB, vecIntC,vecIntD;

int iArray[] = {0,1,2,3,4};

vecIntA.assign(iArray,iArray+5);

 

vecIntB.assign( vecIntA.begin(),  vecIntA.end() );    //用其它容器的迭代器作参数。

 

vecIntC.assign(3,9);

 

vector<int> vecIntD;

vecIntD = vecIntA;

 

vecIntA.swap(vecIntD);

5vector的大小

理论知识

²  vector.size();         //返回容器中元素的个数

²  vector.empty();     //判断容器是否为空

²  vector.resize(num);   //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。

²  vector.resize(num, elem);  //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。

 

例如   vecInt是vector<int>  声明的容器,现已包含1,2,3元素。

int iSize = vecInt.size();             //iSize == 3;

bool bEmpty = vecInt.empty();         // bEmpty == false;

执行vecInt.resize(5);  //此时里面包含1,2,3,0,0元素。

再执行vecInt.resize(8,3);  //此时里面包含1,2,3,0,0,3,3,3元素。

再执行vecInt.resize(2);  //此时里面包含1,2元素。

 

6vector末尾的添加移除操作 

vector<int> vecInt;

vecInt.push_back(1);  //在容器尾部加入一个元素

vecInt.push_back(3);  //移除容器中最后一个元素

vecInt.push_back(5); 

vecInt.push_back(7);

vecInt.push_back(9);

vecInt.pop_back();   

vecInt.pop_back();

//{5 ,7 ,9} 

7vector的数据存取

理论知识

vec.at(idx);    //返回索引idx所指的数据,如果idx越界,抛出out_of_range异常。

vec[idx];         //返回索引idx所指的数据,越界时,运行直接报错

 

vector<int> vecInt;    //假设包含1 ,3 ,5 ,7 ,9

vecInt.at(2) == vecInt[2] ;                 //5

vecInt.at(2) = 8;  或  vecInt[2] = 8;

vecInt 就包含 1, 3, 8, 7, 9值

 

int iF = vector.front();       //iF==1

int iB = vector.back();       //iB==9

vector.front() = 11;  //vecInt包含{11,3,8,7,9}

vector.back() = 19;   //vecInt包含{11,3,8,7,19}

8迭代器基本原理

²  迭代器是一个“可遍历STL容器内全部或部分元素”的对象。

²  迭代器指出容器中的一个特定位置。

²  迭代器就如同一个指针。

²  迭代器提供对一个容器中的对象的访问方法,并且可以定义了容器中对象的范围。

²  这里大概介绍一下迭代器的类别。

输入迭代器:也有叫法称之为“只读迭代器”,它从容器中读取元素,只能一次读入一个元素向前移动,只支持一遍算法,同一个输入迭代器不能两遍遍历一个序列。

输出迭代器:也有叫法称之为“只写迭代器”,它往容器中写入元素,只能一次写入一个元素向前移动,只支持一遍算法,同一个输出迭代器不能两遍遍历一个序列。

正向迭代器:组合输入迭代器和输出迭代器的功能,还可以多次解析一个迭代器指定的位置,可以对一个值进行多次读/写。

双向迭代器:组合正向迭代器的功能,还可以通过--操作符向后移动位置。

随机访问迭代器:组合双向迭代器的功能,还可以向前向后跳过任意个位置,可以直接访问容器中任何位置的元素。

²  目前本系列教程所用到的容器,都支持双向迭代器或随机访问迭代器,下面将会详细介绍这两个类别的迭代器。

9双向迭代器与随机访问迭代器

双向迭代器支持的操作:

it++, ++it,    it--,   --it,*it, itA = itB,

itA == itB,itA != itB

         其中list,set,multiset,map,multimap支持双向迭代器。

随机访问迭代器支持的操作:

在双向迭代器的操作基础上添加

it+=i, it-=i, it+i(或it=it+i),it[i],

itA<itB,   itA<=itB, itA>itB,  itA>=itB  的功能。

         其中vector,deque支持随机访问迭代器。

10vector与迭代器的配合使用

vector<int>  vecInt; //假设包含1,3,5,7,9元素

vector<int>::iterator it;            //声明容器vector<int>的迭代器。

it = vecInt.begin();    // *it == 1

++it;                             //或者it++;  *it == 3 ,前++的效率比后++的效率高,前++返回引用,后++返回值。

it += 2;               //*it== 7

it = it+1;            //*it== 9

++it;                             //it == vecInt.end();  此时不能再执行*it,会出错!

 

 

正向遍历:

for(vector<int>::iteratorit=vecInt.begin(); it!=vecInt.end(); ++it)

{

     int iItem = *it;

     cout << iItem;    //或直接使用  cout << *it;

}

这样子便打印出1 3 5 7 9

 

逆向遍历:

for(vector<int>::reverse_iteratorrit=vecInt.rbegin(); rit!=vecInt.rend(); ++rit)    //注意,小括号内仍是++rit

{

                   intiItem  = *rit;

     cout << iItem;      //或直接使用cout<< *rit;

}

此时将打印出9,7,5,3,1

注意,这里迭代器的声明采用vector<int>::reverse_iterator,而非vector<int>::iterator。

 

 

迭代器还有其它两种声明方法:

vector<int>::const_iterator 与 vector<int>::const_reverse_iterator

 

以上两种分别是vector<int>::iterator 与vector<int>::reverse_iterator的只读形式,使用这两种迭代器时,不会修改到容器中的值。

备注:不过容器中的insert和erase方法仅接受这四种类型中的iterator,其它三种不支持。《Effective STL》建议我们尽量使用iterator取代const_iterator、reverse_iterator和const_reverse_iterator。

 

11vector的插入

理论知识

²  vector.insert(pos,elem);   //在pos位置插入一个elem元素的拷贝,返回新数据的位置。

²  vector.insert(pos,n,elem);  //在pos位置插入n个elem数据,无返回值。

²  vector.insert(pos,beg,end);  //在pos位置插入[beg,end)区间的数据,无返回值

简单案例

vector<int>vecA;

         vector<int>vecB;

 

         vecA.push_back(1);

         vecA.push_back(3);

         vecA.push_back(5);

         vecA.push_back(7);

         vecA.push_back(9);

 

         vecB.push_back(2);

         vecB.push_back(4);

         vecB.push_back(6);

         vecB.push_back(8);

        

         vecA.insert(vecA.begin(),11);                   //{11, 1, 3, 5, 7,9}

         vecA.insert(vecA.begin()+1,2,33);            //{11,33,33,1,3,5,7,9}

         vecA.insert(vecA.begin(), vecB.begin() , vecB.end() );   //{2,4,6,8,11,33,33,1,3,5,7,9}

12vector的删除

理论知识

²  vector.clear();    //移除容器的所有数据

²  vec.erase(beg,end);  //删除[beg,end)区间的数据,返回下一个数据的位置。

²  vec.erase(pos);    //删除pos位置的数据,返回下一个数据的位置。

简单案例:

删除区间内的元素

vecInt是用vector<int>声明的容器,现已包含按顺序的1,3,5,6,9元素。

vector<int>::iteratoritBegin=vecInt.begin()+1;

vector<int>::iteratoritEnd=vecInt.begin()+2;

vecInt.erase(itBegin,itEnd);

//此时容器vecInt包含按顺序的1,6,9三个元素。

 

假设 vecInt 包含1,3,2,3,3,3,4,3,5,3,删除容器中等于3的元素

for(vector<int>::iteratorit=vecInt.being(); it!=vecInt.end(); )   //小括号里不需写  ++it

{

  if(*it == 3)

   {

       it  =  vecInt.erase(it);       //以迭代器为参数,删除元素3,并把数据删除后的下一个元素位置返回给迭代器。

        //此时,不执行  ++it; 

   }

  else

   {

      ++it;

   }

}

 

//删除vecInt的所有元素

vecInt.clear();                    //容器为空

 

13vector小结

这一讲,主要讲解如下要点:

容器的简介,容器的分类,各个容器的数据结构

                   vector,deque,list,set,multiset,map,multimap

容器vector的具体用法(包括迭代器的具体用法)。

vertor简介,vector使用之前的准备,vector对象的默认构造,vector末尾的添加移除操作,vector的数据存取,迭代器的简介,双向迭代器与随机访问迭代器

vector与迭代器的配合使用,vector对象的带参数构造,vector的赋值,vector的大小,vector的插入,vector的删除。

10.2.3Deque容器

Deque简介

²  deque是“double-ended queue”的缩写,和vector一样都是STL的容器,deque是双端数组,而vector是单端的。

²  deque在接口上和vector非常相似,在许多操作的地方可以直接替换。

²  deque可以随机存取元素(支持索引值直接存取,用[]操作符或at()方法,这个等下会详讲)。

²  deque头部和尾部添加或移除元素都非常快速。但是在中部安插元素或移除元素比较费时。

²  #include <deque> 

deque对象的默认构造

deque采用模板类实现,deque对象的默认构造形式:deque<T>deqT; 

deque <int> deqInt;            //一个存放int的deque容器。

deque <float> deq Float;     //一个存放float的deque容器。

deque <string> deq String;     //一个存放string的deque容器。

...                                   

 //尖括号内还可以设置指针类型或自定义类型。

 

deque末尾的添加移除操作

理论知识:

²  deque.push_back(elem);   //在容器尾部添加一个数据

²  deque.push_front(elem);  //在容器头部插入一个数据

²  deque.pop_back();                  //删除容器最后一个数据

²  deque.pop_front();              //删除容器第一个数据

 

deque<int>deqInt;

         deqInt.push_back(1);

         deqInt.push_back(3);

         deqInt.push_back(5);

         deqInt.push_back(7);

         deqInt.push_back(9);

         deqInt.pop_front();

         deqInt.pop_front();

         deqInt.push_front(11);

         deqInt.push_front(13);

         deqInt.pop_back();

         deqInt.pop_back();

//deqInt { 13,11,5}

 

deque的数据存取

理论知识:

²  deque.at(idx);  //返回索引idx所指的数据,如果idx越界,抛出out_of_range。

²  deque[idx];  //返回索引idx所指的数据,如果idx越界,不抛出异常,直接出错。

²  deque.front();   //返回第一个数据。

²  deque.back();  //返回最后一个数据

                   deque<int>deqInt;

                   deqInt.push_back(1);

                   deqInt.push_back(3);

                   deqInt.push_back(5);

                   deqInt.push_back(7);

                   deqInt.push_back(9);

 

                   intiA = deqInt.at(0);                  //1

                   intiB = deqInt[1];                       //3

                   deqInt.at(0)= 99;                       //99

                   deqInt[1]= 88;                            //88

 

                   intiFront = deqInt.front();        //99

                   intiBack = deqInt.back();         //9

                   deqInt.front()= 77;                    //77

                   deqInt.back()= 66;                     //66

deque与迭代器

理论知识

²  deque.begin();  //返回容器中第一个元素的迭代器。

²  deque.end();  //返回容器中最后一个元素之后的迭代器。

²  deque.rbegin();  //返回容器中倒数第一个元素的迭代器。

²  deque.rend();   //返回容器中倒数最后一个元素之后的迭代器。

 

deque<int> deqInt;

                   deqInt.push_back(1);

                   deqInt.push_back(3);

                   deqInt.push_back(5);

                   deqInt.push_back(7);

                   deqInt.push_back(9);

 

                   for(deque<int>::iterator it=deqInt.begin(); it!=deqInt.end(); ++it)

                   {

                            cout<< *it;

                            cout<< "";

                   }

         //1 3 5 7 9

 

                   for(deque<int>::reverse_iterator rit=deqInt.rbegin(); rit!=deqInt.rend();++rit)

                   {

                            cout<< *rit;

                            cout<< "";

                   }

         //97 5 3 1

deque对象的带参数构造

理论知识

²  deque(beg,end);    //构造函数将[beg,end)区间中的元素拷贝给本身。注意该区间是左闭右开的区间。

²  deque(n,elem);   //构造函数将n个elem拷贝给本身。

²  deque(const deque &deq);  //拷贝构造函数。

 

deque<int> deqIntA;

                   deqIntA.push_back(1);

                   deqIntA.push_back(3);

                   deqIntA.push_back(5);

                   deqIntA.push_back(7);

                   deqIntA.push_back(9);

 

                   deque<int>deqIntB(deqIntA.begin(),deqIntA.end());              //13 5 7 9

                   deque<int>deqIntC(5,8);                                                                 //88 8 8 8

                   deque<int>deqIntD(deqIntA);                                                       //13 5 7 9

deque的赋值

理论知识

²  deque.assign(beg,end);    //将[beg, end)区间中的数据拷贝赋值给本身。注意该区间是左闭右开的区间。

²  deque.assign(n,elem);  //将n个elem拷贝赋值给本身。

²  deque& operator=(const deque &deq);    //重载等号操作符

²  deque.swap(deq);  // 将vec与本身的元素互换

 

deque<int> deqIntA,deqIntB,deqIntC,deqIntD;

                   deqIntA.push_back(1);

                   deqIntA.push_back(3);

                   deqIntA.push_back(5);

                   deqIntA.push_back(7);

                   deqIntA.push_back(9);

 

                   deqIntB.assign(deqIntA.begin(),deqIntA.end());    // 1 3 5 7 9

                  

                   deqIntC.assign(5,8);                                                       //88 8 8 8

 

                   deqIntD= deqIntA;                                                         //13 5 7 9

 

                   deqIntC.swap(deqIntD);                                                //互换

deque的大小

理论知识

²  deque.size();         //返回容器中元素的个数

²  deque.empty();     //判断容器是否为空

²  deque.resize(num);   //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。

²  deque.resize(num, elem);  //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。

 

                   deque<int>deqIntA;

                   deqIntA.push_back(1);

                   deqIntA.push_back(3);

                   deqIntA.push_back(5);

 

                   intiSize = deqIntA.size();  //3

 

                   if(!deqIntA.empty())

                   {

                            deqIntA.resize(5);             //1 3 5 0 0

                            deqIntA.resize(7,1);         //1 3 5 0 0 1 1

                            deqIntA.resize(2);             //1 3

                   }

deque的插入

理论知识

²  deque.insert(pos,elem);   //在pos位置插入一个elem元素的拷贝,返回新数据的位置。

²  deque.insert(pos,n,elem);   //在pos位置插入n个elem数据,无返回值。

²  deque.insert(pos,beg,end);  //在pos位置插入[beg,end)区间的数据,无返回值。

 

deque<int>deqA;

         deque<int>deqB;

 

         deqA.push_back(1);

         deqA.push_back(3);

         deqA.push_back(5);

         deqA.push_back(7);

         deqA.push_back(9);

 

         deqB.push_back(2);

         deqB.push_back(4);

         deqB.push_back(6);

         deqB.push_back(8);

        

         deqA.insert(deqA.begin(),11);                  //{11, 1, 3, 5, 7,9}

         deqA.insert(deqA.begin()+1,2,33);          //{11,33,33,1,3,5,7,9}

         deqA.insert(deqA.begin(), deqB.begin() , deqB.end() );          //{2,4,6,8,11,33,33,1,3,5,7,9}

deque的删除

理论知识

²  deque.clear();    //移除容器的所有数据

²  deque.erase(beg,end);  //删除[beg,end)区间的数据,返回下一个数据的位置。

²  deque.erase(pos);    //删除pos位置的数据,返回下一个数据的位置。

 

删除区间内的元素

deqInt是用deque<int>声明的容器,现已包含按顺序的1,3,5,6,9元素。

deque<int>::iteratoritBegin=deqInt.begin()+1;

deque<int>::iteratoritEnd=deqInt.begin()+3;

deqInt.erase(itBegin,itEnd);

//此时容器deqInt包含按顺序的1,6,9三个元素。

 

 

 

假设 deqInt 包含1,3,2,3,3,3,4,3,5,3,删除容器中等于3的元素

for(deque<int>::iterator it=deqInt.being();it!=deqInt.end(); )    //小括号里不需写  ++it

{

  if(*it == 3)

   {

       it  =  deqInt.erase(it);       //以迭代器为参数,删除元素3,并把数据删除后的下一个元素位置返回给迭代器。

        //此时,不执行  ++it; 

   }

  else

   {

      ++it;

   }

}

 

//删除deqInt的所有元素

deqInt.clear();                    //容器为空

 

10.2.4stack容器

Stack简介

²  stack是堆栈容器,是一种“先进后出”的容器。

²  stack是简单地装饰deque容器而成为另外的一种容器。

²  #include <stack> 

stack对象的默认构造

stack采用模板类实现, stack对象的默认构造形式:stack <T> stkT; 

stack <int> stkInt;            //一个存放int的stack容器。

stack <float> stkFloat;     //一个存放float的stack容器。

stack <string> stkString;     //一个存放string的stack容器。

...                                    

//尖括号内还可以设置指针类型或自定义类型。

stack的push()与pop()方法

stack.push(elem);   //往栈头添加元素

stack.pop();   //从栈头移除第一个元素

 

stack<int> stkInt;          

stkInt.push(1);stkInt.push(3);stkInt.pop();  

stkInt.push(5);stkInt.push(7); 

stkInt.push(9);stkInt.pop();         

stkInt.pop(); 

此时stkInt存放的元素是1,5 

stack对象的拷贝构造与赋值

stack(const stack &stk);               //拷贝构造函数

stack& operator=(const stack &stk);      //重载等号操作符

 

                   stack<int>stkIntA;

                   stkIntA.push(1);

                   stkIntA.push(3);

                  stkIntA.push(5);

                   stkIntA.push(7);

                   stkIntA.push(9);

 

                   stack<int>stkIntB(stkIntA);             //拷贝构造

                   stack<int>stkIntC;

                   stkIntC= stkIntA;                                //赋值

stack的数据存取

²  stack.top();           //返回最后一个压入栈元素

                   stack<int>stkIntA;

                   stkIntA.push(1);

                   stkIntA.push(3);

                   stkIntA.push(5);

                   stkIntA.push(7);

                   stkIntA.push(9);

 

                   intiTop = stkIntA.top();             //9

                   stkIntA.top()= 19;                      //19

stack的大小

²  stack.empty();   //判断堆栈是否为空

²  stack.size();            //返回堆栈的大小

 

                   stack<int>stkIntA;

                   stkIntA.push(1);

                   stkIntA.push(3);

                   stkIntA.push(5);

                   stkIntA.push(7);

                   stkIntA.push(9);

 

                   if(!stkIntA.empty())

                   {

                            intiSize = stkIntA.size();           //5

                   }

 

10.2.5Queue容器

Queue简介

²  queue是队列容器,是一种“先进先出”的容器。

²  queue是简单地装饰deque容器而成为另外的一种容器。

²  #include <queue> 

queue对象的默认构造

queue采用模板类实现,queue对象的默认构造形式:queue<T>queT;  如:

queue<int> queInt;            //一个存放int的queue容器。

queue<float> queFloat;     //一个存放float的queue容器。

queue<string> queString;     //一个存放string的queue容器。

...                                    

//尖括号内还可以设置指针类型或自定义类型。

 

queue的push()与pop()方法

queue.push(elem);   //往队尾添加元素

queue.pop();   //从队头移除第一个元素

 

queue<int> queInt;

queInt.push(1);queInt.push(3);

queInt.push(5);queInt.push(7);

queInt.push(9);queInt.pop();

queInt.pop();

此时queInt存放的元素是5,7,9

queue对象的拷贝构造与赋值

queue(const queue &que);                    //拷贝构造函数

queue& operator=(const queue &que); //重载等号操作符

 

                   queue<int>queIntA;

                   queIntA.push(1);

                   queIntA.push(3);

                   queIntA.push(5);

                   queIntA.push(7);

                   queIntA.push(9);

 

                   queue<int>queIntB(queIntA);         //拷贝构造

                   queue<int>queIntC;

                   queIntC= queIntA;                              //赋值

queue的数据存取

²  queue.back();   //返回最后一个元素

²  queue.front();   //返回第一个元素

 

                   queue<int>queIntA;

                   queIntA.push(1);

                   queIntA.push(3);

                   queIntA.push(5);

                   queIntA.push(7);

                   queIntA.push(9);

 

                   intiFront = queIntA.front();              //1

                   intiBack = queIntA.back();                //9

 

                   queIntA.front()= 11;                           //11

                   queIntA.back()= 19;                           //19

queue的大小

²  queue.empty();   //判断队列是否为空

²  queue.size();          //返回队列的大小

                   queue<int>queIntA;      

                   queIntA.push(1);          

                   queIntA.push(3);           

                   queIntA.push(5);              

                   queIntA.push(7);              

                   queIntA.push(9);              

 

                   if(!queIntA.empty())

                   {

                            intiSize = queIntA.size();                   //5

                   }

10.2.6List容器

List简介

²  list是一个双向链表容器,可高效地进行插入删除元素。

²  list不可以随机存取元素,所以不支持at.(pos)函数与[]操作符。It++(ok) it+5(err)

²  #include <list> 

 

list对象的默认构造

list采用采用模板类实现,对象的默认构造形式:list<T>lstT;  如:

list<int> lstInt;            //定义一个存放int的list容器。

list<float> lstFloat;     //定义一个存放float的list容器。

list<string> lstString;     //定义一个存放string的list容器。

...                                    

//尖括号内还可以设置指针类型或自定义类型。

list头尾的添加移除操作

²  list.push_back(elem);            //在容器尾部加入一个元素

²  list.pop_back();              //删除容器中最后一个元素

²  list.push_front(elem);     //在容器开头插入一个元素

²  list.pop_front();             //从容器开头移除第一个元素

 

         list<int>lstInt;

         lstInt.push_back(1);

         lstInt.push_back(3);

         lstInt.push_back(5);

         lstInt.push_back(7);

         lstInt.push_back(9);

         lstInt.pop_front();

         lstInt.pop_front();

         lstInt.push_front(11);

         lstInt.push_front(13);

         lstInt.pop_back();

         lstInt.pop_back();

// lstInt   {13,11,5}

list的数据存取

²  list.front();   //返回第一个元素。

²  list.back();  //返回最后一个元素。

 

list<int>lstInt;

         lstInt.push_back(1);

         lstInt.push_back(3);

         lstInt.push_back(5);

         lstInt.push_back(7);

         lstInt.push_back(9);

 

         intiFront = lstInt.front(); //1

         intiBack = lstInt.back();            //9

         lstInt.front()= 11;                      //11

         lstInt.back()= 19;                       //19

list与迭代器

²  list.begin();                    //返回容器中第一个元素的迭代器。

²  list.end();                      //返回容器中最后一个元素之后的迭代器。

²  list.rbegin();         //返回容器中倒数第一个元素的迭代器。

²  list.rend();         //返回容器中倒数最后一个元素的后面的迭代器。

 

         list<int>lstInt;

         lstInt.push_back(1);

         lstInt.push_back(3);

         lstInt.push_back(5);

         lstInt.push_back(7);

         lstInt.push_back(9);

 

         for(list<int>::iterator it=lstInt.begin(); it!=lstInt.end(); ++it)

         {

                   cout<< *it;

                   cout<< " ";

         }

 

         for(list<int>::reverse_iterator rit=lstInt.rbegin(); rit!=lstInt.rend();++rit)

         {

                   cout<< *rit;

                   cout<< " ";

         }

list对象的带参数构造

²  list(beg,end);    //构造函数将[beg,end)区间中的元素拷贝给本身。注意该区间是左闭右开的区间。

²  list(n,elem);   //构造函数将n个elem拷贝给本身。

²  list(const list &lst);  //拷贝构造函数。

 

list<int>lstIntA;

         lstIntA.push_back(1);

         lstIntA.push_back(3);

         lstIntA.push_back(5);

         lstIntA.push_back(7);

         lstIntA.push_back(9);

 

         list<int>lstIntB(lstIntA.begin(),lstIntA.end());                  //13 5 7 9

         list<int>lstIntC(5,8);                                                                //88 8 8 8

         list<int>lstIntD(lstIntA);                                               //13 5 7 9

list的赋值

²  list.assign(beg,end);    //将[beg, end)区间中的数据拷贝赋值给本身。注意该区间是左闭右开的区间。

²  list.assign(n,elem);  //将n个elem拷贝赋值给本身。

²  list& operator=(const list &lst);         //重载等号操作符

²  list.swap(lst);  // 将lst与本身的元素互换。

 

         list<int>lstIntA,lstIntB,lstIntC,lstIntD;

         lstIntA.push_back(1);

         lstIntA.push_back(3);

         lstIntA.push_back(5);

         lstIntA.push_back(7);

         lstIntA.push_back(9);

 

         lstIntB.assign(lstIntA.begin(),lstIntA.end());           //1 3 5 7 9

         lstIntC.assign(5,8);                                                         //88 8 8 8

         lstIntD= lstIntA;                                                              //13 5 7 9

         lstIntC.swap(lstIntD);                                                    //互换

list的大小

²  list.size();      //返回容器中元素的个数

²  list.empty();           //判断容器是否为空

²  list.resize(num);   //重新指定容器的长度为num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。

²  list.resize(num, elem);  //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。

 

         list<int>lstIntA;

         lstIntA.push_back(1);

         lstIntA.push_back(3);

         lstIntA.push_back(5);

 

         if(!lstIntA.empty())

         {

                   intiSize = lstIntA.size();            //3

                   lstIntA.resize(5);                        //1 3 5 0 0

                   lstIntA.resize(7,1);                     //1 3 5 0 0 1 1

                   lstIntA.resize(2);                        //1 3

         }

list的插入

²  list.insert(pos,elem);   //在pos位置插入一个elem元素的拷贝,返回新数据的位置。

²  list.insert(pos,n,elem);   //在pos位置插入n个elem数据,无返回值。

²  list.insert(pos,beg,end);   //在pos位置插入[beg,end)区间的数据,无返回值。

 

         list<int>lstA;

         list<int>lstB;

 

         lstA.push_back(1);

         lstA.push_back(3);

         lstA.push_back(5);

         lstA.push_back(7);

         lstA.push_back(9);

 

         lstB.push_back(2);

         lstB.push_back(4);

         lstB.push_back(6);

         lstB.push_back(8);

 

         lstA.insert(lstA.begin(),11);             //{11, 1, 3, 5, 7, 9}

         lstA.insert(++lstA.begin(),2,33);               //{11,33,33,1,3,5,7,9}

         lstA.insert(lstA.begin(), lstB.begin() , lstB.end() );          //{2,4,6,8,11,33,33,1,3,5,7,9}

list的删除

²  list.clear();          //移除容器的所有数据

²  list.erase(beg,end);  //删除[beg,end)区间的数据,返回下一个数据的位置。

²  list.erase(pos);    //删除pos位置的数据,返回下一个数据的位置。

²  lst.remove(elem);   //删除容器中所有与elem值匹配的元素。

 

删除区间内的元素

lstInt是用list<int>声明的容器,现已包含按顺序的1,3,5,6,9元素。

list<int>::iteratoritBegin=lstInt.begin();

++ itBegin;

list<int>::iteratoritEnd=lstInt.begin();

++ itEnd;

++ itEnd;

++ itEnd;

lstInt.erase(itBegin,itEnd);

//此时容器lstInt包含按顺序的1,6,9三个元素。

 

 

 

假设 lstInt 包含1,3,2,3,3,3,4,3,5,3,删除容器中等于3的元素的方法一

for(list<int>::iteratorit=lstInt.being(); it!=lstInt.end(); )   //小括号里不需写  ++it

{

  if(*it == 3)

   {

       it  =  lstInt.erase(it);       //以迭代器为参数,删除元素3,并把数据删除后的下一个元素位置返回给迭代器。

        //此时,不执行  ++it; 

   }

  else

   {

      ++it;

   }

}

 

删除容器中等于3的元素的方法二

lstInt.remove(3);

 

删除lstInt的所有元素

lstInt.clear();                      //容器为空

list的反序排列

²  lst.reverse();     //反转链表,比如lst包含1,3,5元素,运行此方法后,lst就包含5,3,1元素。

 

         list<int>lstA;

        

         lstA.push_back(1);

         lstA.push_back(3);

         lstA.push_back(5);

         lstA.push_back(7);

         lstA.push_back(9);

 

         lstA.reverse();                    //9 7 5 3 1

 

小结:

²  一、容器deque的使用方法

         适合         在头尾添加移除元素。使用方法与vector类似。

²  二、容器queue,stack的使用方法

         适合队列,堆栈的操作方式。

²  三、容器list的使用方法

         适合在任意位置快速插入移除元素

10.2.7优先级队列priority_queue

最大值优先级队列、最小值优先级队列

v  优先级队列适配器 STLpriority_queue

v  用来开发一些特殊的应用,请对stl的类库,多做扩展性学习

         priority_queue<int,deque<int>>   pq;

         priority_queue<int,vector<int>>   pq;

         pq.empty()

         pq.size()

         pq.top()

         pq.pop()

         pq.push(item)

#include <iostream>

using namespace std;

#include "queue"

void main81()

{

         priority_queue<int> p1; //默认是最大值优先级队列

         //priority_queue<int,vector<int>, less<int> > p1; //相当于这样写

         priority_queue<int,vector<int>, greater<int>> p2; //最小值优先级队列

 

         p1.push(33);

         p1.push(11);

         p1.push(55);

         p1.push(22);

         cout<<"队列大小" << p1.size() << endl;

         cout<<"队头" << p1.top() << endl;

 

         while(p1.size() > 0)

         {

                   cout<< p1.top() << " ";

                   p1.pop();

         }

         cout<< endl;

 

         cout<< "测试最小值优先级队列" << endl;

         p2.push(33);

         p2.push(11);

         p2.push(55);

         p2.push(22);

         while(p2.size() > 0)

         {

                   cout<< p2.top() << " ";

                   p2.pop();

 

         }

}

 

10.2.8Set和multiset容器

set/multiset的简介

²  set是一个集合容器,其中所包含的元素是唯一的,集合中的元素按一定的顺序排列元素插入过程是按排序规则插入,所以不能指定插入位置。

²  set采用红黑树变体的数据结构实现,红黑树属于平衡二叉树。在插入操作和删除操作上比vector快。

²  set不可以直接存取元素。(不可以使用at.(pos)与[]操作符)。

²  multiset与set的区别:set支持唯一键值,每个元素值只能出现一次;而multiset中同一值可以出现多次

²  不可以直接修改set或multiset容器中的元素值,因为该类容器是自动排序的。如果希望修改一个元素值,必须先删除原有的元素,再插入新的元素。

²  #include <set> 

 

 

set/multiset对象的默认构造

set<int> setInt;            //一个存放int的set容器。

set<float> setFloat;     //一个存放float的set容器。

set<string> setString;     //一个存放string的set容器。

multiset<int> mulsetInt;            //一个存放int的multi set容器。

multi set<float> multisetFloat;     //一个存放float的multi set容器。

multi set<string>multisetString;     //一个存放string的multi set容器。

set的插入与迭代器

²  set.insert(elem);     //在容器中插入元素。

²  set.begin();  //返回容器中第一个数据的迭代器。

²  set.end();  //返回容器中最后一个数据之后的迭代器。

²  set.rbegin();  //返回容器中倒数第一个元素的迭代器。

²  set.rend();   //返回容器中倒数最后一个元素的后面的迭代器。

 

set<int> setInt;

setInt.insert(3);setInt.insert(1);setInt.insert(5);setInt.insert(2);

for(set<int>::iteratorit=setInt.begin(); it!=setInt.end(); ++it)

{

     int iItem = *it;

     cout << iItem;    //或直接使用cout<< *it

}

//这样子便顺序输出  1 2 3 5。

 

set.rbegin()与set.rend()。略。

 

Set集合的元素排序

²  set<int,less<int> > setIntA;  //该容器是按升序方式排列元素。

²  set<int,greater<int>> setIntB;   //该容器是按降序方式排列元素。

²  set<int> 相当于 set<int,less<int>>。

²  less<int>与greater<int>中的int可以改成其它类型,该类型主要要跟set容纳的数据类型一致。

²  疑问1:less<>与greater<>是什么?

²  疑问2:如果set<>不包含int类型,而是包含自定义类型,set容器如何排序?

²  要解决如上两个问题,需要了解容器的函数对象,也叫伪函数,英文名叫functor。

²  下面将讲解什么是functor,functor的用法。

 

使用stl提供的函数对象

set<int,greater<int>>setIntB;  

setIntB.insert(3);

setIntB.insert(1);

setIntB.insert(5);

setIntB.insert(2);

此时容器setIntB就包含了按顺序的5,3,2,1元素

函数对象functor的用法

²  尽管函数指针被广泛用于实现函数回调,但C++还提供了一个重要的实现回调函数的方法,那就是函数对象。

²  functor,翻译成函数对象,伪函数,算符,是重载了“()”操作符的普通类对象。从语法上讲,它与普通函数行为类似。

²  greater<>与less<>就是函数对象。

²  下面举出greater<int>的简易实现原理。

 

下面举出greater<int>的简易实现原理。

struct greater

{

bool operator()(const int& iLeft, const int& iRight)

{

      return (iLeft>iRight);    //如果是实现less<int>的话,这边是写return(iLeft<iRight);

}

}

容器就是调用函数对象的operator()方法去比较两个值的大小。

 

题目:学生包含学号,姓名属性,现要求任意插入几个学生对象到set容器中,使得容器中的学生按学号的升序排序。

 

解:

//学生类

class CStudent

{

         public:

                   CStudent(intiID, string strName)

                   {

                            m_iID= iID;

                            m_strName= strName;

                   }

    int m_iID;                //学号

    string m_strName;       //姓名

}

//为保持主题鲜明,本类不写拷贝构造函数,不类也不需要写拷贝构造函数。但大家仍要有考虑拷贝构造函数的习惯。

 

//函数对象

struct StuFunctor

{

                   booloperator()  (const CStudent &stu1,const CStudent &stu2)

                   {

                            return(stu1.m_iID<stu2.m_iID);

                   }

}

 

//main函数

void main()

{

                   set<CStudent,StuFunctor> setStu;

                   setStu.insert(CStudent(3,"小张"));

                   setStu.insert(CStudent(1,"小李"));

                   setStu.insert(CStudent(5,"小王"));

                   setStu.insert(CStudent(2,"小刘"));

                   //此时容器setStu包含了四个学生对象,分别是按姓名顺序的“小李”,“小刘”,“小张”,“小王”

}

 

set对象的拷贝构造与赋值

set(const set &st);               //拷贝构造函数

set& operator=(const set &st);       //重载等号操作符

set.swap(st);                               //交换两个集合容器

 

set<int>setIntA;

         setIntA.insert(3);

         setIntA.insert(1);

         setIntA.insert(7);

         setIntA.insert(5);

         setIntA.insert(9);

 

         set<int>setIntB(setIntA);  //1 3 5 7 9

        

         set<int>setIntC;

         setIntC= setIntA;             //1 3 5 7 9

 

         setIntC.insert(6);

         setIntC.swap(setIntA);     //交换

set的大小

²  set.size();   //返回容器中元素的数目

²  set.empty();//判断容器是否为空

 

set<int>setIntA;

         setIntA.insert(3);

         setIntA.insert(1);

         setIntA.insert(7);

         setIntA.insert(5);

         setIntA.insert(9);

 

         if(!setIntA.empty())

         {

                   intiSize = setIntA.size();           //5

         }

set的删除

²  set.clear();          //清除所有元素

²  set.erase(pos);  //删除pos迭代器所指的元素,返回下一个元素的迭代器。

²  set.erase(beg,end);       //删除区间[beg,end)的所有元素  ,返回下一个元素的迭代器。

²  set.erase(elem);     //删除容器中值为elem的元素。

 

删除区间内的元素

setInt是用set<int>声明的容器,现已包含按顺序的1,3,5,6,9,11元素。

set<int>::iteratoritBegin=setInt.begin();

++ itBegin;

set<int>::iteratoritEnd=setInt.begin();

++ itEnd;

++ itEnd;

++ itEnd;

setInt.erase(itBegin,itEnd);

//此时容器setInt包含按顺序的1,6,9,11四个元素。

 

删除容器中第一个元素

setInt.erase(setInt.begin());            //6,9,11

 

删除容器中值为9的元素

set.erase(9);    

 

 

删除setInt的所有元素

setInt.clear();                     //容器为空

set的查找

²  set.find(elem);   //查找elem元素,返回指向elem元素的迭代器。

²  set.count(elem);   //返回容器中值为elem的元素个数。对set来说,要么是0,要么是1。对multiset来说,值可能大于1。

²  set.lower_bound(elem);  //返回第一个>=elem元素的迭代器。

²  set.upper_bound(elem);       //  返回第一个>elem元素的迭代器。

²  set.equal_range(elem);              //返回容器中与elem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。

²   

²  以上函数返回两个迭代器,而这两个迭代器被封装在pair中。

²  以下讲解pair的含义与使用方法。

²   

 

set<int>setInt;

         setInt.insert(3);

         setInt.insert(1);

         setInt.insert(7);

         setInt.insert(5);

         setInt.insert(9);

 

         set<int>::iteratoritA = setInt.find(5);

         intiA = *itA;              //iA == 5

         intiCount = setInt.count(5);    //iCount == 1

 

         set<int>::iteratoritB = setInt.lower_bound(5);

         set<int>::iteratoritC = setInt.upper_bound(5);

         intiB = *itB;     //iB == 5

         intiC = *itC; //iC == 7

 

pair<set<int>::iterator, set<int>::iterator > pairIt =setInt.equal_range(5);  //pair是什么?

pair的使用

²  pair译为对组,可以将两个值视为一个单元。

²  pair<T1,T2>存放的两个值的类型,可以不一样,如T1为int,T2为float。T1,T2也可以是自定义类型。

² pair.firstpair里面的第一个值,是T1类型。

² pair.secondpair里面的第二个值,是T2类型。

 

set<int> setInt;

... //往setInt容器插入元素1,3,5,7,9

pair< set<int>::iterator ,set<int>::iterator > pairIt = setInt.equal_range(5);

set<int>::iteratoritBeg = pairIt.first;

set<int>::iteratoritEnd = pairIt.second;

//此时 *itBeg==5  而  *itEnd == 7

小结

²  一、容器set/multiset的使用方法;

                            红黑树的变体,查找效率高,插入不能指定位置,插入时自动排序。

²  二、functor的使用方法;

    类似于函数的功能,可用来自定义一些规则,如元素比较规则。

²  三、pair的使用方法。

         对组,一个整体的单元,存放两个类型(T1,T2,T1可与T2一样)的两个元素。

 

案例:

       int x;

    scanf("%ld",&x);

    multiset<int>h;//建立一个multiset类型,变量名是hh序列里面存的是int类型,初始h为空

    while(x!=0){

        h.insert(x);//x插入h

        scanf("%ld",&x);

    }   

 

       pair< multiset<int>::iterator , multiset<int>::iterator > pairIt = h.equal_range(22);

       multiset<int>::iteratoritBeg = pairIt.first;

       multiset<int>::iteratoritEnd = pairIt.second;

 

       int nBeg= *itBeg;

       int nEnd= *itEnd;

 

    while(!h.empty()){//序列非空h.empty()==true时表示h已经空了

              multiset<int>::iterator c = h.begin();//c指向h序列中第一个元素的地址,第一个元素是最小的元素

        printf("%ld",*c);//将地址c存的数据输出

        h.erase(c);//h序列中将c指向的元素删除

    }    

 

10.2.9Map和multimap容器

map/multimap的简介

²  map是标准的关联式容器,一个map是一个键值对序列,即(key,value)对。它提供基于key的快速检索能力。

²  map中key值是唯一的。集合中的元素按一定的顺序排列。元素插入过程是按排序规则插入,所以不能指定插入位置。

²  map的具体实现采用红黑树变体的平衡二叉树的数据结构。在插入操作和删除操作上比vector快。

²  map可以直接存取key所对应的value,支持[]操作符,如map[key]=value。

²  multimap与map的区别:map支持唯一键值,每个键只能出现一次;而multimap中相同键可以出现多次。multimap不支持[]操作符。

²  #include <map> 

 

map/multimap对象的默认构造

map/multimap采用模板类实现,对象的默认构造形式:

map<T1,T2> mapTT;

multimap<T1,T2>  multimapTT; 

如:

map<int, char> mapA;

map<string,float> mapB;

//其中T1,T2还可以用各种指针类型或自定义类型

map的插入与迭代器

²  map.insert(...);    //往容器插入元素,返回pair<iterator,bool>

²  在map中插入元素的三种方式:

假设  map<int, string> mapStu;

²  一、通过pair的方式插入对象

mapStu.insert(  pair<int,string>(3,"小张")  );

²  二、通过pair的方式插入对象

mapStu.inset(make_pair(-1,“校长-1”));

²  三、通过value_type的方式插入对象

mapStu.insert(  map<int,string>::value_type(1,"小李")  );

²  四、通过数组的方式插入值

mapStu[3] = “小刘";

mapStu[5] = “小王";

        

²  前三种方法,采用的是insert()方法,该方法返回值为pair<iterator,bool>

²  第四种方法非常直观,但存在一个性能的问题。插入3时,先在mapStu中查找主键为3的项,若没发现,则将一个键为3,值为初始化值的对组插入到mapStu中,然后再将值修改成“小刘”。若发现已存在3这个键,则修改这个键对应的value。

²  string strName = mapStu[2];  //取操作或插入操作

²  只有当mapStu存在2这个键时才是正确的取操作,否则会自动插入一个实例,键为2,值为初始化值。

 

 

假设  map<int, string> mapA;

pair< map<int,string>::iterator,bool > pairResult = mapA.insert(pair<int,string>(3,"小张"));                       //插入方式一

 

int iFirstFirst =(pairResult.first)->first;            //iFirst== 3;

string strFirstSecond =(pairResult.first)->second;          //strFirstSecond为"小张"

bool bSecond = pairResult.second;                                                         //bSecond== true;

                  

mapA.insert(map<int,string>::value_type(1,"小李"));                     //插入方式二

 

mapA[3] = "小刘";                     //修改value

mapA[5] = "小王";                     //插入方式三

 

string str1 = mapA[2];                        //执行插入 string()操作,返回的str1的字符串内容为空。

string str2 = mapA[3];                        //取得value,str2为"小刘"

 

 

//迭代器遍历

         for(map<int,string>::iterator it=mapA.begin(); it!=mapA.end(); ++it)

         {

                   pair<int,string> pr = *it;

                   intiKey = pr.first;

                   stringstrValue = pr.second;

         }

map.rbegin()与map.rend()  略。

 

 

²  map<T1,T2,less<T1> > mapA;  //该容器是按键的升序方式排列元素。未指定函数对象,默认采用less<T1>函数对象。

²  map<T1,T2,greater<T1>> mapB;   //该容器是按键的降序方式排列元素。

²  less<T1>与greater<T1>  可以替换成其它的函数对象functor。

²  可编写自定义函数对象以进行自定义类型的比较,使用方法与set构造时所用的函数对象一样。

²  map.begin();  //返回容器中第一个数据的迭代器。

²  map.end();  //返回容器中最后一个数据之后的迭代器。

²  map.rbegin();  //返回容器中倒数第一个元素的迭代器。

²  map.rend();   //返回容器中倒数最后一个元素的后面的迭代器。

 

map对象的拷贝构造与赋值

map(const map &mp);                   //拷贝构造函数

map& operator=(const map &mp);         //重载等号操作符

map.swap(mp);                                    //交换两个集合容器

例如:

                   map<int,string> mapA;

                   mapA.insert(pair<int,string>(3,"小张"));       

                   mapA.insert(pair<int,string>(1,"小杨"));       

                   mapA.insert(pair<int,string>(7,"小赵"));       

                   mapA.insert(pair<int,string>(5,"小王"));       

 

                   map<int,string> mapB(mapA);                          //拷贝构造

                  

                   map<int,string> mapC;

                   mapC= mapA;                                                                           //赋值

 

                   mapC[3]= "老张";

                   mapC.swap(mapA);                            //交换

map的大小

²  map.size();          //返回容器中元素的数目

²  map.empty();//判断容器是否为空

                   map<int,string> mapA;

                   mapA.insert(pair<int,string>(3,"小张"));       

                   mapA.insert(pair<int,string>(1,"小杨"));       

                   mapA.insert(pair<int,string>(7,"小赵"));       

                   mapA.insert(pair<int,string>(5,"小王"));       

 

                   if(mapA.empty())

                   {

                            intiSize = mapA.size();              //iSize== 4

                   }

map的删除

²  map.clear();                 //删除所有元素

²  map.erase(pos);         //删除pos迭代器所指的元素,返回下一个元素的迭代器。

²  map.erase(beg,end);              //删除区间[beg,end)的所有元素  ,返回下一个元素的迭代器。

²  map.erase(keyElem);     //删除容器中key为keyElem的对组。

map<int, string> mapA;

                   mapA.insert(pair<int,string>(3,"小张"));       

                   mapA.insert(pair<int,string>(1,"小杨"));       

                   mapA.insert(pair<int,string>(7,"小赵"));       

                   mapA.insert(pair<int,string>(5,"小王"));       

 

                   //删除区间内的元素

                   map<int,string>::iteratoritBegin=mapA.begin();

                   ++itBegin;

                   ++itBegin;

                   map<int,string>::iteratoritEnd=mapA.end();

                   mapA.erase(itBegin,itEnd);                       //此时容器mapA包含按顺序的{1,"小杨"}{3,"小张"}两个元素。

 

                   mapA.insert(pair<int,string>(7,"小赵"));       

                   mapA.insert(pair<int,string>(5,"小王"));       

 

                   //删除容器中第一个元素

                   mapA.erase(mapA.begin());             //此时容器mapA包含了按顺序的{3,"小张"}{5,"小王"}{7,"小赵"}三个元素

 

                   //删除容器中key5的元素

                   mapA.erase(5);   

 

 

                   //删除mapA的所有元素

                   mapA.clear();                     //容器为空

map的查找

²  map.find(key);   查找键key是否存在,若存在,返回该键的元素的迭代器;若不存在,返回map.end();

²  map.count(keyElem);   //返回容器中key为keyElem的对组个数。对map来说,要么是0,要么是1。对multimap来说,值可能大于1。

map<int,string>::iterator it=mapStu.find(3);

if(it == mapStu.end())

{

                   //没找到

}

else

{

             //找到了

        pair<int, string> pairStu = *it;

           int iID = pairStu.first;            //或   int  iID = it->first;

        string strName = pairStu.second;   //或   string strName = it->second;

}

 

²  map.lower_bound(keyElem);  //返回第一个key>=keyElem元素的迭代器。

²  map.upper_bound(keyElem);       //  返回第一个key>keyElem元素的迭代器。

例如:  mapStu是用map<int,string>声明的容器,已包含{1,"小李"}{3,"小张"}{5,"小王"}{7,"小赵"}{9,"小陈"}元素。map<int,string>::iterator it;

it = mapStu.lower_bound(5);  //it->first==5    it->second=="小王"

it = mapStu.upper_bound(5);   //it->first==7   it->second=="小赵"

it = mapStu.lower_bound(6);  //it->first==7    it->second=="小赵"

it = mapStu.upper_bound(6);    //it->first==7   it->second=="小赵"

 

 

 

²  map.equal_range(keyElem);               //返回容器中key与keyElem相等的上下限的两个迭代器。上限是闭区间,下限是开区间,如[beg,end)。

 

以上函数返回两个迭代器,而这两个迭代器被封装在pair中。

 

例如 map<int,string> mapStu;

...  //往mapStu容器插入元素{1,"小李"}{3,"小张"}{5,"小王"}{7,"小赵"}{9,"小陈"}

pair< map<int,string>::iterator , map<int,string>::iterator > pairIt = mapStu.equal_range(5);

map<int, string>::iterator itBeg = pairIt.first;

map<int, string>::iterator itEnd = pairIt.second;

//此时 itBeg->first==5  ,  itEnd->first == 7,

itBeg->second=="小王", itEnd->second=="小赵"

 

 

 

Multimap 案例:

//1个key值可以对应多个valude  =è分组

//公司有销售部 sale (员工2名)、技术研发部 development (1人)、财务部 Financial (2人)

//人员信息有:姓名,年龄,电话、工资等组成

//通过 multimap进行 信息的插入、保存、显示

//分部门显示员工信息

 

10.2.10容器共性机制研究

10.2.9.1容器的共通能力

C++模板是容器的概念。

理论提高:所有容器提供的都是值(value)语意,而非引用(reference)语意。容器执行插入元素的操作时,内部实施拷贝动作。所以STL容器内存储的元素必须能够被拷贝(必须提供拷贝构造函数)。

 

²  除了queue与stack外,每个容器都提供可返回迭代器的函数,运用返回的迭代器就可以访问元素。

²  通常STL不会丢出异常。要求使用者确保传入正确的参数。

²  每个容器都提供了一个默认构造函数跟一个默认拷贝构造函数。

²  如已有容器vecIntA。

²  vector<int> vecIntB(vecIntA); //调用拷贝构造函数,复制vecIntA到vecIntB中。

²  与大小相关的操作方法(c代表容器):

c.size();   //返回容器中元素的个数

c.empty();   //判断容器是否为空

²  比较操作(c1,c2代表容器):

c1 == c2     判断c1是否等于c2

c1 != c2      判断c1是否不等于c2

c1 = c2        把c2的所有元素指派给c1

 

10.2.9.2各个容器的使用时机

 

 

 

²  Vector的使用场景:比如软件历史操作记录的存储,我们经常要查看历史记录,比如上一次的记录,上上次的记录,但却不会去删除记录,因为记录是事实的描述。

²   deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。

²  vector与deque的比较:

²  一:vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置却是不固定的。

²  二:如果有大量释放操作的话,vector花的时间更少,这跟二者的内部实现有关。

²  三:deque支持头部的快速插入与快速移除,这是deque的优点。

²  list的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入

²  set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。 

²  map的使用场景:比如按ID号存储十万个用户,想要快速要通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。

10.2.11其他

 

10.3算法 

10.3.1算法基础

10.3.1.1算法概述

²  算法部分主要由头文件<algorithm>,<numeric>和<functional>组成。

²  <algorithm>是所有STL头文件中最大的一个,其中常用到的功能范围涉及到比较、交换、查找、遍历操作、复制、修改、反转、排序、合并等等。

²  <numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。

²  <functional>中则定义了一些模板类,用以声明函数对象。

²  STL提供了大量实现算法的模版函数,只要我们熟悉了STL之后,许多代码可以被大大的化简,只需要通过调用一两个算法模板,就可以完成所需要的功能,从而大大地提升效率。

²  #include <algorithm>

²  #include <numeric>

²  #include <functional>

10.3.1.2 STL中算法分类
  • 操作对象
    • 直接改变容器的内容
    • 将原容器的内容复制一份,修改其副本,然后传回该副本
  • 功能:
    • 非可变序列算法 指不直接修改其所操作的容器内容的算法
      • 计数算法        count、count_if
      • 搜索算法        search、find、find_if、find_first_of、…
      • 比较算法        equal、mismatch、lexicographical_compare
    • 可变序列算法 指可以修改它们所操作的容器内容的算法
      • 删除算法        remove、remove_if、remove_copy、…
      • 修改算法        for_each、transform
      • 排序算法        sort、stable_sort、partial_sort、
    • 排序算法 包括对序列进行排序和合并的算法、搜索算法以及有序序列上的集合操作
    • 数值算法 对容器内容进行数值计算

 

10.3.1.3查找算法(13个):判断容器中是否包含某个值

函数名

头文件

函数功能

adjacent_find

<algorithm>

在iterator对标识元素范围内,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素的ForwardIterator .否则返回last.重载版本使用输入的二元操作符代替相等的判断

函数原形

template<class FwdIt> FwdIt adjacent_find(FwdIt first, FwdIt last);

template<class FwdIt, class Pred> FwdIt adjacent_find(FwdIt first, FwdIt last, Pred pr);

binary_search

<algorithm>

在有序序列中查找value,找到返回true.重载的版本实用指定的比较函数对象或函数指针来判断相等

函数原形

template<class FwdIt, class T> bool binary_search(FwdIt first, FwdIt last, const T& val);

template<class FwdIt, class T, class Pred> bool binary_search(FwdIt first, FwdIt last, const T& val,Pred pr);

count

<algorithm>

利用等于操作符,把标志范围内的元素与输入值比较,返回相等元素个数

函数原形

template<class InIt, class Dist> size_t count(InIt first, InIt last,const T& val, Dist& n);

count_if

<algorithm>

利用输入的操作符,对标志范围内的元素进行操作,返回结果为true的个数

函数原形

template<class InIt, class Pred, class Dist> size_t count_if(InIt first, InIt last, Pred pr);

equal_range

<algorithm>

功能类似equal,返回一对iterator,第一个表示lower_bound,第二个表示upper_bound

函数原形

template<class FwdIt, class T> pair<FwdIt, FwdIt> equal_range(FwdIt first, FwdIt last,const T& val);

template<class FwdIt, class T, class Pred> pair<FwdIt, FwdIt> equal_range(FwdIt first, FwdIt last,const T& val, Pred pr);

find

<algorithm>

利用底层元素的等于操作符,对指定范围内的元素与输入值进行比较.当匹配时,结束搜索,返回该元素的一个InputIterator

函数原形

template<class InIt, class T> InIt find(InIt first, InIt last, const T& val);

find_end

<algorithm>

在指定范围内查找"由输入的另外一对iterator标志的第二个序列"的最后一次出现.找到则返回最后一对的第一个ForwardIterator,否则返回输入的"另外一对"的第一个ForwardIterator.重载版本使用用户输入的操作符代替等于操作

函数原形

template<class FwdIt1, class FwdIt2> FwdIt1 find_end(FwdIt1 first1, FwdIt1 last1,FwdIt2 first2, FwdIt2 last2);

template<class FwdIt1, class FwdIt2, class Pred> FwdIt1 find_end(FwdIt1 first1, FwdIt1 last1,FwdIt2 first2, FwdIt2 last2, Pred pr);

find_first_of

<algorithm>

在指定范围内查找"由输入的另外一对iterator标志的第二个序列"中任意一个元素的第一次出现。重载版本中使用了用户自定义操作符

函数原形

template<class FwdIt1, class FwdIt2> FwdIt1 find_first_of(FwdIt1 first1, FwdIt1 last1,FwdIt2 first2, FwdIt2 last2);

template<class FwdIt1, class FwdIt2, class Pred> FwdIt1 find_first_of(FwdIt1 first1, FwdIt1 last1,FwdIt2 first2, FwdIt2 last2, Pred pr);

find_if

<algorithm>

使用输入的函数代替等于操作符执行find

 

template<class InIt, class Pred> InIt find_if(InIt first, InIt last, Pred pr);

lower_bound

<algorithm>

返回一个ForwardIterator,指向在有序序列范围内的可以插入指定值而不破坏容器顺序的第一个位置.重载函数使用自定义比较操作

函数原形

template<class FwdIt, class T> FwdIt lower_bound(FwdIt first, FwdIt last, const T& val);

template<class FwdIt, class T, class Pred> FwdIt lower_bound(FwdIt first, FwdIt last, const T& val, Pred pr);

upper_bound

<algorithm>

返回一个ForwardIterator,指向在有序序列范围内插入value而不破坏容器顺序的最后一个位置,该位置标志一个大于value的值.重载函数使用自定义比较操作

函数原形

template<class FwdIt, class T> FwdIt upper_bound(FwdIt first, FwdIt last, const T& val);

template<class FwdIt, class T, class Pred> FwdIt upper_bound(FwdIt first, FwdIt last, const T& val, Pred pr);

search

<algorithm>

给出两个范围,返回一个ForwardIterator,查找成功指向第一个范围内第一次出现子序列(第二个范围)的位置,查找失败指向last1,重载版本使用自定义的比较操作

函数原形

template<class FwdIt1, class FwdIt2> FwdIt1 search(FwdIt1 first1, FwdIt1 last1,FwdIt2 first2, FwdIt2 last2);

template<class FwdIt1, class FwdIt2, class Pred> FwdIt1 search(FwdIt1 first1, FwdIt1 last1, FwdIt2 first2, FwdIt2 last2, Pred pr);

search_n

<algorithm>

在指定范围内查找val出现n次的子序列。重载版本使用自定义的比较操作

函数原形

template<class FwdIt, class Dist, class T> FwdIt search_n(FwdIt first, FwdIt last,Dist n, const T& val);

template<class FwdIt, class Dist, class T, class Pred> FwdIt search_n(FwdIt first, FwdIt last,Dist n, const T& val, Pred pr);

 

10.3.1.4堆算法(4个)

函数名

头文件

函数功能

make_heap

<algorithm>

把指定范围内的元素生成一个堆。重载版本使用自定义比较操作

函数原形

template<class RanIt> void make_heap(RanIt first, RanIt last);

template<class RanIt, class Pred> void make_heap(RanIt first, RanIt last, Pred pr);

pop_heap

<algorithm>

并不真正把最大元素从堆中弹出,而是重新排序堆。它把first和last-1交换,然后重新生成一个堆。可使用容器的back来访问被"弹出"的元素或者使用pop_back进行真正的删除。重载版本使用自定义的比较操作

函数原形

template<class RanIt> void pop_heap(RanIt first, RanIt last);

template<class RanIt, class Pred> void pop_heap(RanIt first, RanIt last, Pred pr);

push_heap

<algorithm>

假设first到last-1是一个有效堆,要被加入到堆的元素存放在位置last-1,重新生成堆。在指向该函数前,必须先把元素插入容器后。重载版本使用指定的比较操作

函数原形

template<class RanIt>void push_heap(RanIt first, RanIt last);

template<class RanIt, class Pred> void push_heap(RanIt first, RanIt last, Pred pr);

sort_heap

<algorithm>

对指定范围内的序列重新排序,它假设该序列是个有序堆。重载版本使用自定义比较操作

函数原形

template<class RanIt> void sort_heap(RanIt first, RanIt last);

template<class RanIt, class Pred> void sort_heap(RanIt first, RanIt last, Pred pr);


10.3.1.5关系算法(8个)

函数名

头文件

函数功能

equal

<algorithm>

如果两个序列在标志范围内元素都相等,返回true。重载版本使用输入的操作符代替默认的等于操作符

函数原形

template<class InIt1, class InIt2> bool equal(InIt1 first, InIt1 last, InIt2 x);

template<class InIt1, class InIt2, class Pred> bool equal(InIt1 first, InIt1 last, InIt2 x, Pred pr);

includes

<algorithm>

判断第一个指定范围内的所有元素是否都被第二个范围包含,使用底层元素的<操作符,成功返回true。重载版本使用用户输入的函数

函数原形

template<class InIt1, class InIt2> bool includes(InIt1 first1, InIt1 last1,InIt2 first2, InIt2 last2);

template<class InIt1, class InIt2, class Pred> bool includes(InIt1 first1, InIt1 last1,InIt2 first2, InIt2 last2, Pred pr);

lexicographical_compare

<algorithm>

比较两个序列。重载版本使用用户自定义比较操作

函数原形

template<class InIt1, class InIt2> bool lexicographical_compare(InIt1 first1, InIt1 last1,InIt2 first2, InIt2 last2);

template<class InIt1, class InIt2, class Pred> bool lexicographical_compare(InIt1 first1, InIt1 last1,InIt2 first2, InIt2 last2, Pred pr);

max

<algorithm>

返回两个元素中较大一个。重载版本使用自定义比较操作

函数原形

template<class T> const T& max(const T& x, const T& y);

template<class T, class Pred> const T& max(const T&  x, const T& y, Pred pr);

max_element

<algorithm>

返回一个ForwardIterator,指出序列中最大的元素。重载版本使用自定义比较操作

函数原形

template<class FwdIt> FwdIt max_element(FwdIt first, FwdIt last);

template<class FwdIt, class Pred> FwdIt max_element(FwdIt first, FwdIt last, Pred pr);

min

<algorithm>

返回两个元素中较小一个。重载版本使用自定义比较操作

函数原形

template<class T> const T& min(const T& x, const T& y);

template<class T, class Pred> const T& min(const T& x, const T& y, Pred pr);

min_element

<algorithm>

返回一个ForwardIterator,指出序列中最小的元素。重载版本使用自定义比较操作

函数原形

template<class FwdIt> FwdIt min_element(FwdIt first, FwdIt last);

template<class FwdIt, class Pred> FwdIt min_element(FwdIt first, FwdIt last, Pred pr);

mismatch

<algorithm>

并行比较两个序列,指出第一个不匹配的位置,返回一对iterator,标志第一个不匹配元素位置。如果都匹配,返回每个容器的last。重载版本使用自定义的比较操作

函数原形

template<class InIt1, class InIt2> pair<InIt1, InIt2> mismatch(InIt1 first, InIt1 last, InIt2 x);

template<class InIt1, class InIt2, class Pred> pair<InIt1, InIt2> mismatch(InIt1 first, InIt1 last, InIt2 x, Pred pr);

 

10.3.1.6集合算法(4个)

函数名

头文件

函数功能

set_union

<algorithm>

构造一个有序序列,包含两个序列中所有的不重复元素。重载版本使用自定义的比较操作

函数原形

template<class InIt1, class InIt2, class OutIt> OutIt set_union(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, OutIt x);

template<class InIt1, class InIt2, class OutIt, class Pred> OutIt set_union(InIt1 first1, InIt1 last1,InIt2 first2, InIt2 last2,OutIt x, Pred pr);

set_intersection

<algorithm>

构造一个有序序列,其中元素在两个序列中都存在。重载版本使用自定义的比较操作

函数原形

template<class InIt1, class InIt2, class OutIt> OutIt set_intersection(InIt1 first1, InIt1 last1,InIt2 first2, InIt2 last2, OutIt x);

template<class InIt1, class InIt2, class OutIt, class Pred> OutIt set_intersection(InIt1 first1, InIt1 last1,InIt2 first2,InIt2 last2, OutIt x, Pred pr);

set_difference

<algorithm>

构造一个有序序列,该序列仅保留第一个序列中存在的而第二个中不存在的元素。重载版本使用自定义的比较操作

函数原形

template<class InIt1, class InIt2, class OutIt> OutIt set_difference(InIt1 first1, InIt1 last1,InIt2 first2, InIt2 last2, OutIt x);

template<class InIt1, class InIt2, class OutIt, class Pred> OutIt set_difference(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, OutIt x, Pred pr);

set_symmetric_difference

<algorithm>

构造一个有序序列,该序列取两个序列的对称差集(并集-交集)

函数原形

template<class InIt1, class InIt2, class OutIt> OutIt set_symmetric_difference(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, OutIt x);

template<class InIt1, class InIt2, class OutIt, class Pred> OutIt set_symmetric_difference(InIt1 first1, InIt1 last1, InIt2 first2, InIt2 last2, OutIt x, Pred pr);

 

10.3.1.6列组合算法(2个)

提供计算给定集合按一定顺序的所有可能排列组合

函数名

头文件

函数功能

next_permutation

<algorithm>

取出当前范围内的排列,并重新排序为下一个排列。重载版本使用自定义的比较操作

函数原形

template<class BidIt> bool next_permutation(BidIt first, BidIt last);

template<class BidIt, class Pred> bool next_permutation(BidIt first, BidIt last, Pred pr);

prev_permutation

<algorithm>

 取出指定范围内的序列并将它重新排序为上一个序列。如果不存在上一个序列则返回false。重载版本使用自定义的比较操作

函数原形

template<class BidIt> bool prev_permutation(BidIt first, BidIt last);

template<class BidIt, class Pred> bool prev_permutation(BidIt first, BidIt last, Pred pr);


10.3.1.7排序和通用算法(14个):提供元素排序策略

函数名

头文件

函数功能

inplace_merge

<algorithm>

合并两个有序序列,结果序列覆盖两端范围。重载版本使用输入的操作进行排序

函数原形

template<class BidIt> void inplace_merge(BidIt first, BidIt middle, BidIt last);

template<class BidIt, class Pred> void inplace_merge(BidIt first, BidIt middle, BidIt last, Pred pr);

merge

<algorithm>

合并两个有序序列,存放到另一个序列。重载版本使用自定义的比较

函数原形

template<class InIt1, class InIt2, class OutIt> OutIt merge(InIt1 first1, InIt1 last1,InIt2 first2, InIt2 last2, OutIt x);

template<class InIt1, class InIt2, class OutIt, class Pred> OutIt merge(InIt1 first1, InIt1 last1,InIt2 first2, InIt2 last2, OutIt x, Pred pr);

nth_element

<algorithm>

将范围内的序列重新排序,使所有小于第n个元素的元素都出现在它前面,而大于它的都出现在后面。重载版本使用自定义的比较操作

函数原形

template<class RanIt> void nth_element(RanIt first, RanIt nth, RanIt last);

template<class RanIt, class Pred> void nth_element(RanIt first, RanIt nth, RanIt last, Pred pr);

partial_sort

<algorithm>

对序列做部分排序,被排序元素个数正好可以被放到范围内。重载版本使用自定义的比较操作

函数原形

template<class RanIt> void partial_sort(RanIt first, RanIt middle, RanIt last);

template<class RanIt, class Pred> void partial_sort(RanIt first, RanIt middle, RanIt last, Pred pr);

partial_sort_copy

<algorithm>

与partial_sort类似,不过将经过排序的序列复制到另一个容器

函数原形

template<class InIt, class RanIt> RanIt partial_sort_copy(InIt first1, InIt last1,RanIt first2, RanIt last2);

template<class InIt, class RanIt, class Pred> RanIt partial_sort_copy(InIt first1, InIt last1,RanIt first2, RanIt last2, Pred pr);

partition

<algorithm>

对指定范围内元素重新排序,使用输入的函数,把结果为true的元素放在结果为false的元素之前

函数原形

template<class BidIt, class Pred> BidIt partition(BidIt first, BidIt last, Pred pr);

random_shuffle

<algorithm>

对指定范围内的元素随机调整次序。重载版本输入一个随机数产生操作

函数原形

template<class RanIt> void random_shuffle(RanIt first, RanIt last);

template<class RanIt, class Fun> void random_shuffle(RanIt first, RanIt last, Fun& f);

reverse

<algorithm>

将指定范围内元素重新反序排序

函数原形

template<class BidIt> void reverse(BidIt first, BidIt last);

reverse_copy

<algorithm>

与reverse类似,不过将结果写入另一个容器

函数原形

template<class BidIt, class OutIt> OutIt reverse_copy(BidIt first, BidIt last, OutIt x);

rotate

<algorithm>

将指定范围内元素移到容器末尾,由middle指向的元素成为容器第一个元素

函数原形

template<class FwdIt> void rotate(FwdIt first, FwdIt middle, FwdIt last);

rotate_copy

<algorithm>

与rotate类似,不过将结果写入另一个容器

函数原形

template<class FwdIt, class OutIt> OutIt rotate_copy(FwdIt first, FwdIt middle, FwdIt last, OutIt x);

sort

<algorithm>

以升序重新排列指定范围内的元素。重载版本使用自定义的比较操作

函数原形

template<class RanIt> void sort(RanIt first, RanIt last);

template<class RanIt, class Pred> void sort(RanIt first, RanIt last, Pred pr);

stable_sort

<algorithm>

与sort类似,不过保留相等元素之间的顺序关系

函数原形

template<class BidIt> void stable_sort(BidIt first, BidIt last);

template<class BidIt, class Pred> void stable_sort(BidIt first, BidIt last, Pred pr);

stable_partition

<algorithm>

与partition类似,不过不保证保留容器中的相对顺序

函数原形

template<class FwdIt, class Pred> FwdIt stable_partition(FwdIt first, FwdIt last, Pred pr);

 

10.3.1.8删除和替换算法(15个)

函数名

头文件

函数功能

copy

<algorithm>

复制序列

函数原形

template<class InIt, class OutIt> OutIt copy(InIt first, InIt last, OutIt x);

copy_backward

<algorithm>

与copy相同,不过元素是以相反顺序被拷贝

函数原形

template<class BidIt1, class BidIt2> BidIt2 copy_backward(BidIt1 first, BidIt1 last, BidIt2 x);

iter_swap

<algorithm>

交换两个ForwardIterator的值

函数原形

template<class FwdIt1, class FwdIt2> void iter_swap(FwdIt1 x, FwdIt2 y);

remove

<algorithm>

删除指定范围内所有等于指定元素的元素。注意,该函数不是真正删除函数。内置函数不适合使用remove和remove_if函数

函数原形

template<class FwdIt, class T> FwdIt remove(FwdIt first, FwdIt last, const T& val);

remove_copy

<algorithm>

将所有不匹配元素复制到一个制定容器,返回OutputIterator指向被拷贝的末元素的下一个位置

函数原形

template<class InIt, class OutIt, class T> OutIt remove_copy(InIt first, InIt last, OutIt x, const T& val);

remove_if

<algorithm>

删除指定范围内输入操作结果为true的所有元素

函数原形

template<class FwdIt, class Pred> FwdIt remove_if(FwdIt first, FwdIt last, Pred pr);

remove_copy_if

<algorithm>

将所有不匹配元素拷贝到一个指定容器

函数原形

template<class InIt, class OutIt, class Pred> OutIt remove_copy_if(InIt first, InIt last, OutIt x, Pred pr);

replace

<algorithm>

将指定范围内所有等于vold的元素都用vnew代替

函数原形

template<class FwdIt, class T> void replace(FwdIt first, FwdIt last,const T& vold, const T& vnew);

replace_copy

<algorithm>

与replace类似,不过将结果写入另一个容器

函数原形

template<class InIt, class OutIt, class T> OutIt replace_copy(InIt first, InIt last, OutIt x,const T& vold, const T& vnew);

replace_if

<algorithm>

将指定范围内所有操作结果为true的元素用新值代替

函数原形

template<class FwdIt, class Pred, class T> void replace_if(FwdIt first, FwdIt last,Pred pr, const T& val);

replace_copy_if

<algorithm>

与replace_if,不过将结果写入另一个容器

函数原形

template<class InIt, class OutIt, class Pred, class T> OutIt replace_copy_if(InIt first, InIt last, OutIt x, Pred pr, const T& val);

swap

<algorithm>

交换存储在两个对象中的值

函数原形

template<class T> void swap(T& x, T& y);

swap_range

<algorithm>

将指定范围内的元素与另一个序列元素值进行交换

函数原形

template<class FwdIt1, class FwdIt2> FwdIt2 swap_ranges(FwdIt1 first, FwdIt1 last, FwdIt2 x);

unique

<algorithm>

清除序列中重复元素,和remove类似,它也不能真正删除元素。重载版本使用自定义比较操作

函数原形

template<class FwdIt> FwdIt unique(FwdIt first, FwdIt last);

template<class FwdIt, class Pred> FwdIt unique(FwdIt first, FwdIt last, Pred pr);

unique_copy

<algorithm>

与unique类似,不过把结果输出到另一个容器

函数原形

template<class InIt, class OutIt> OutIt unique_copy(InIt first, InIt last, OutIt x);

template<class InIt, class OutIt, class Pred> OutIt unique_copy(InIt first, InIt last, OutIt x, Pred pr);

 

10.3.1.9生成和变异算法(6个)

函数名

头文件

函数功能

fill

<algorithm>

将输入值赋给标志范围内的所有元素

函数原形

template<class FwdIt, class T> void fill(FwdIt first, FwdIt last, const T& x);

fill_n

<algorithm>

将输入值赋给first到first+n范围内的所有元素

函数原形

template<class OutIt, class Size, class T> void fill_n(OutIt first, Size n, const T& x);

for_each

<algorithm>

用指定函数依次对指定范围内所有元素进行迭代访问,返回所指定的函数类型。该函数不得修改序列中的元素

函数原形

template<class InIt, class Fun> Fun for_each(InIt first, InIt last, Fun f);

generate

<algorithm>

连续调用输入的函数来填充指定的范围

函数原形

template<class FwdIt, class Gen> void generate(FwdIt first, FwdIt last, Gen g);

generate_n

<algorithm>

与generate函数类似,填充从指定iterator开始的n个元素

函数原形

template<class OutIt, class Pred, class Gen> void generate_n(OutIt first, Dist n, Gen g);

transform

<algorithm>

将输入的操作作用与指定范围内的每个元素,并产生一个新的序列。重载版本将操作作用在一对元素上,另外一个元素来自输入的另外一个序列。结果输出到指定容器

函数原形

template<class InIt, class OutIt, class Unop> OutIt transform(InIt first, InIt last, OutIt x, Unop uop);

template<class InIt1, class InIt2, class OutIt, class Binop> OutIt transform(InIt1 first1, InIt1 last1, InIt2 first2,OutIt x, Binop bop);


10.3.1.10算数算法(4个)

函数名

头文件

函数功能

accumulate

<numeric>

iterator对标识的序列段元素之和,加到一个由val指定的初始值上。重载版本不再做加法,而是传进来的二元操作符被应用到元素上

函数原形

template<class InIt, class T> T accumulate(InIt first, InIt last, T val);

template<class InIt, class T, class Pred> T accumulate(InIt first, InIt last, T val, Pred pr);

partial_sum

<numeric>

创建一个新序列,其中每个元素值代表指定范围内该位置前所有元素之和。重载版本使用自定义操作代替加法

函数原形

template<class InIt, class OutIt> OutIt partial_sum(InIt first, InIt last,OutIt result);

template<class InIt, class OutIt, class Pred> OutIt partial_sum(InIt first, InIt last,OutIt result, Pred pr);

product

<numeric>

对两个序列做内积(对应元素相乘,再求和)并将内积加到一个输入的初始值上。重载版本使用用户定义的操作

函数原形

template<class InIt1, class InIt2, class T> T product(InIt1 first1, InIt1 last1,Init2 first2, T val);

template<class InIt1, class InIt2, class T,class Pred1, class Pred2> T product(InIt1 first1, InIt1 last1,Init2 first2, T val, Pred1 pr1, Pred2 pr2);

adjacent_difference

<numeric>

创建一个新序列,新序列中每个新值代表当前元素与上一个元素的差。重载版本用指定二元操作计算相邻元素的差

函数原形

template<class InIt, class OutIt> OutIt adjacent_difference(InIt first, InIt last,OutIt result);

template<class InIt, class OutIt, class Pred> OutIt adjacent_difference(InIt first, InIt last,OutIt result, Pred pr);

 

10.3.1.11常用算法汇总

²  常用的查找算法:

adjacent_find()( adjacent 是邻近的意思),binary_search(),count(),

count_if(),equal_range(),find(),find_if()

²  常用的排序算法:

merge(),sort(),random_shuffle()(shuffle是洗牌的意思),reverse()。

²  常用的拷贝和替换算法:

copy(), replace(),

replace_if(),swap()

²  常用的算术和生成算法:

accumulate()( accumulate 是求和的意思),fill(),。

²  常用的集合算法:

set_union(),set_intersection(),

set_difference()。

²  常用的遍历算法:

for_each(), transform()( transform 是变换的意思)

10.3.2STL算法中函数对象和谓词

10.3.2.1函数对象和谓词定义

函数对象:

重载函数调用操作符的类,其对象常称为函数对象(functionobject),即它们是行为类似函数的对象。一个类对象,表现出一个函数的特征,就是通过“对象名+(参数列表)”的方式使用一个类对象,如果没有上下文,完全可以把它看作一个函数对待。

这是通过重载类的operator()来实现的。

“在标准库中,函数对象被广泛地使用以获得弹性”,标准库中的很多算法都可以使用函数对象或者函数来作为自定的回调行为

谓词:

一元函数对象:函数参数1个;

二元函数对象:函数参数2个;

一元谓词函数参数1个,函数返回值是bool类型,可以作为一个判断式

                            谓词可以使一个仿函数,也可以是一个回调函数。

二元谓词函数参数2个,函数返回值是bool类型

 

一元谓词函数举例如下

1,判断给出的string对象的长度是否小于6

bool GT6(const string &s)

{

return s.size()>= 6;

}

2,判断给出的int是否在3到8之间

bool Compare( int i )

{

return ( i >=3 && i <= 8 );

}

二元谓词举例如下

1,比较两个string对象,返回一个bool值,指出第一个string是否比第二个短

bool isShorter(const string &s1, conststring &s2)

{

return s1.size()< s2.size();

}

10.3.2.2一元函数对象案例

//1普通类 重载 函数调用操作符

template <typename T>

void FuncShowElemt(T &t)  //普通函数 不能像 仿函数那样记录状态

{

         cout<< t << " ";

};

 

void showChar(char &t)

{

         cout<< t << " ";

}

 

//函数模板 重载 函数调用操作符

template <typename T>

class ShowElemt

{

public:

         ShowElemt()

         {

                   n= 0;

         }

         voidoperator()(T &t)

         {

                   n++;

                   cout<< t << " ";

         }

         voidprintCount()

         {

                   cout<< n << endl;

         }

public:

         intn;

};

 

//1 函数对象 基本使用

void main11()

{

         inta = 100;

         FuncShowElemt<int>(a);//普通的函数调用

 

         ShowElemt<int>showElemt; //函数对象

         showElemt(a);//函数对象调用

}

10.3.2.3一元谓词案例

//1元谓词 例子

template <typename T>

class Isdiv

{

public:

         Isdiv(constT &divisor) //

         {

                   this->divisor= divisor;

         }

         booloperator()(T &t)

         {

                   return(t%divisor == 0);

         }

protected:

private:

         Tdivisor;

};

 

void main13()

{

         vector<int>v2;

         for(int i=10; i<33; i++)

         {

                   v2.push_back(i);

         }

         vector<int>::iteratorit;

         inta = 4;

         Isdiv<int>mydiv(a);

         //_InIt find_if(_InIt _First, _InIt _Last, _Pr _Pred)   //返回的是迭代器

         it= find_if(v2.begin(), v2.end(), Isdiv<int>(4));

         if(it != v2.end())

         {

                   cout<< "第一个被4整除的数是:" << *it << endl;

         }

}

 

10.3.2.4二元函数对象案例

template <typename T>

struct SumAdd

{

         Toperator()(T &t1, T &t2)

         {

                   returnt1 + t2;

         }

};

 

template <typename T>

void printE(T &t)

{

         for(vector<int>::iterator it = t.begin(); it!=t.end(); it++ )

         {

                   cout<< *it << " ";

         }

}

 

void printVector(vector<int> &v)

{

         for(vector<int>::iterator it = v.begin(); it!=v.end(); it++ )

         {

                   cout<< *it << " ";

         }

}

 

void main14()

{

         vector<int>v1, v2 ;

         vector<int>v3;

         v1.push_back(1);

         v1.push_back(2);

         v1.push_back(3);

 

         v2.push_back(4);

         v2.push_back(5);

         v2.push_back(6);

 

         v3.resize(10);

 

         //transform(v1.begin(),v1.end(), v2.begin(),v3.begin(), SumAdd<int>());

         /*

         template<class_InIt1,

         class_InIt2,

         class_OutIt,

         class_Fn2> inline

                   _OutIttransform(_InIt1 _First1, _InIt1 _Last1,

                   _InIt2_First2, _OutIt _Dest, _Fn2 _Func)

         */

         vector<int>::iteratorit = transform(v1.begin(), v1.end(), v2.begin(),v3.begin(),SumAdd<int>());

         cout<< *it << endl;

         printE(v3);       

}

 

10.3.2.5二元谓词案例

void current(int &v)

{

         cout<< v << " ";

}

 

bool MyCompare(const int &a, const int&b)

{

         returna < b;

}

void main15()

{

         vector<int>v(10);

 

         for(int i=0; i<10; i++)

         {

                   v[i]= rand() % 100;

         }

 

         for_each(v.begin(),v.end(), current);

         printf("\n");

         sort(v.begin(),v.end(), MyCompare );

 

         printf("\n");

         for(int i=0; i<10; i++)

         {

                   printf("%d", v[i]);

         }

         printf("\n");

}

10.3.2.6预定义函数对象和函数适配器

1)预定义函数对象基本概念:标准模板库STL提前定义了很多预定义函数对象,#include <functional> 必须包含。

//1使用预定义函数对象:

//类模板plus<> 的实现了: 不同类型的数据进行加法运算

void main41()

{

         plus<int>intAdd;

         intx = 10;

         inty = 20;

         intz = intAdd(x, y); //等价于 x + y

         cout<< z << endl;

 

         plus<string>stringAdd;

         stringmyc = stringAdd("aaa", "bbb");

         cout<< myc << endl;

 

         vector<string>v1;

         v1.push_back("bbb");

         v1.push_back("aaa");

         v1.push_back("ccc");

         v1.push_back("zzzz");

 

         //缺省情况下,sort()用底层元素类型的小于操作符以升序排列容器的元素。

         //为了降序,可以传递预定义的类模板greater,它调用底层元素类型的大于操作符:

         cout<< "sort()函数排序" << endl;;

         sort(v1.begin(),v1.end(), greater<string>() ); //从大到小

         for(vector<string>::iterator it=v1.begin(); it!=v1.end(); it++ )

         {

                   cout<< *it << endl;

         }

}

2)算术函数对象

预定义的函数对象支持加、减、乘、除、求余和取反。调用的操作符是与type相关联的实例

加法:plus<Types>

plus<string> stringAdd;

sres = stringAdd(sva1,sva2);

减法:minus<Types>

乘法:multiplies<Types>

除法divides<Tpye>

求余:modulus<Tpye>

取反:negate<Type>

negate<int> intNegate;

ires = intNegate(ires);

Ires= UnaryFunc(negate<int>(),Ival1);

 

3)关系函数对象

等于equal_to<Tpye>

equal_to<string> stringEqual;

sres = stringEqual(sval1,sval2);

不等于not_equal_to<Type>

大于 greater<Type>

大于等于greater_equal<Type>

小于 less<Type>

小于等于less_equal<Type>

 

void main42()

{

         vector<string>v1;

         v1.push_back("bbb");

         v1.push_back("aaa");

         v1.push_back("ccc");

         v1.push_back("zzzz");

         v1.push_back("ccc");

         strings1 = "ccc";

         //intnum = count_if(v1.begin(),v1.end(), equal_to<string>(),s1);

         intnum = count_if(v1.begin(),v1.end(),bind2nd(equal_to<string>(), s1));

         cout<< num << endl;

}

 

4)逻辑函数对象

逻辑与 logical_and<Type>

logical_and<int> indAnd;

ires = intAnd(ival1,ival2);

dres=BinaryFunc( logical_and<double>(),dval1,dval2);

逻辑或logical_or<Type>

逻辑非logical_not<Type>

logical_not<int> IntNot;

Ires = IntNot(ival1);

Dres=UnaryFunc( logical_not<double>,dval1);

10.3.2.7函数适配器

1)函数适配器的理论知识

 

2)常用函数函数适配器

标准库提供一组函数适配器,用来特殊化或者扩展一元和二元函数对象。常用适配器是:

1绑定器(binder): binder通过把二元函数对象的一个实参绑定到一个特殊的值上,将其转换成一元函数对象。C++标准库提供两种预定义的binder适配器:bind1st和bind2nd,前者把值绑定到二元函数对象的第一个实参上,后者绑定在第二个实参上。

2取反器(negator) :negator是一个将函数对象的值翻转的函数适配器。标准库提供两个预定义的ngeator适配器:not1翻转一元预定义函数对象的真值,而not2翻转二元谓词函数的真值。

常用函数适配器列表如下:

bind1st(op, value)

bind2nd(op, value)

not1(op)

not2(op)

mem_fun_ref(op)

mem_fun(op)

ptr_fun(op)

3)常用函数适配器案例

//

class IsGreat

{

public:

         IsGreat(inti)

         {

                   m_num= i;

         }

         booloperator()(int &num)

         {

                   if(num > m_num)

                   {

                            returntrue;

                   }

                   returnfalse;

         }

protected:

private:

         intm_num;

};

 

void main43()

{

         vector<int>  v1;

         for(int i=0; i<5; i++)

         {

                   v1.push_back(i+1);

         }

 

         for(vector<int>::iterator it = v1.begin(); it!=v1.end(); it ++)

         {

                   cout<< *it << " " ;

         }

 

         intnum1 = count(v1.begin(), v1.end(), 3);

         cout<< "num1:" << num1 << endl;

 

         //通过谓词求大于2的个数

         intnum2 = count_if(v1.begin(), v1.end(), IsGreat(2));

         cout<< "num2:" << num2 << endl;

 

         //通过预定义函数对象求大于2的个数   greater<int>() 有2个参数

         //                                                                                                             param> 2

         intnum3 = count_if(v1.begin(), v1.end(), bind2nd(greater<int>(), 2 ) );

         cout<< "num3:" << num3 << endl;

 

         //取模 能被2整除的数 求奇数

         intnum4 = count_if(v1.begin(), v1.end(), bind2nd(modulus <int>(), 2 ) );

         cout<< "奇数num4:" << num4 << endl;

 

         intnum5 = count_if(v1.begin(), v1.end(), not1( bind2nd(modulus <int>(), 2 )) );

         cout<< "偶数num5:" << num5 << endl;

         return;

}

 

10.3.2.8 STL的容器算法迭代器的设计理念

1)  STL的容器通过类模板技术,实现数据类型和容器模型的分离

2)  STL的迭代器技术实现了遍历容器的统一方法;也为STL的算法提供了统一性奠定了基础

3)  STL的算法,通过函数对象实现了自定义数据类型的算法运算;所以说:STL的算法也提供了统一性。

核心思想:其实函数对象本质就是回调函数,回调函数的思想:就是任务的编写者和任务的调用者有效解耦合。函数指针做函数参数。

4)  具体例子:transform算法的输入,通过迭代器first和last指向的元算作为输入;通过result作为输出;通过函数对象来做自定义数据类型的运算。、

 

10.3.3常用的遍历算法 

for_each()

²  for_each:  用指定函数依次对指定范围内所有元素进行迭代访问。该函数不得修改序列中的元素。

²  函数定义。For_each(begin,end, func);

template<class _InIt,

    class _Fn1> inline

    _Fn1for_each(_InIt _First, _InIt _Last, _Fn1 _Func)

    {   // perform functionfor each element

    _DEBUG_RANGE(_First,_Last);

    _DEBUG_POINTER(_Func);

    return (_For_each(_Unchecked(_First),_Unchecked(_Last), _Func));

    }

²  注意for_each的第三个参数 函数对象做函数参数,函数对象做返回值

 

class CMyShow

{

public:

         CMyShow()

         {

                   num= 0;

         }

         voidoperator()(const int &iItem)

         {

                   num++;

                   cout<< iItem;

         }

 

         voidprintCount()

         {

                   cout<< "num:" << num << endl;

         }

 

private:

         intnum;

};

 

void show(const int &iItem)

{

         cout<< iItem;

}

main()

{

         intiArray[] = {0,1,2,3,4};

         vector<int>vecInt(iArray,iArray+sizeof(iArray)/sizeof(iArray[0]));

   for_each(vecInt.begin(), vecInt.end(), show);

 

//结果打印出0 1 2 3 4

CMyShow show1 = for_each(vecInt.begin(), vecInt.end(), CMyShow());

cout << endl;

show1.printCount(); //显示对象被调用的次数

}

 

transform()

²  transform:   与for_each类似,遍历所有元素,但可对容器的元素进行修改

²  transform()算法有两种形式:

²  transform(b1, e1, b2, op)  

²  transform(b1, e1, b2, b3, op)

template<class_InIt,

class _OutIt,

     class _Fn1> inline

     _OutIt transform(_InIt _First, _InIt _Last,_OutIt _Dest, _Fn1 _Func)

²  transform()的作用

例如:可以一个容器的元素,通过op,变换到另一个容器中(同一个容器中)       

                   也可以把两个容器的元素,通过op,变换到另一个容器中

 

²  注意:       1.如果目标与源相同,transform()就和for_each()一样。

2.如果想以某值替换符合规则的元素,应使用replace()算法

 

int increase (int i) 

         return i+1;  

 

main()

                   {

                            vector<int>vecIntA;

                            vecIntA.push_back(1);

                            vecIntA.push_back(3);

                            vecIntA.push_back(5);

                            vecIntA.push_back(7);

                            vecIntA.push_back(9);

                            transform(vecIntA.begin(),vecIntA.end(),vecIntA.begin(),increase);              //vecIntA: {2,4,6,8,10}

                            transform(vecIntA.begin(),vecIntA.end(),vecIntA.begin(),negate<int>() );

                   }

 

for_each()和transform()算法比较

1)STL 算法 – 修改性算法

for_each()

v  copy()

v  copy_backward()

transform()

v  merge()

v  swap_ranges()

v  fill()

v  fill_n()

v  generate()

v  generate_n()

v  replace

v  replace_if()

v  replace_copy()

v  replace_copy_if()

2)

v  for_each()           速度快              不灵活

v  transform()         速度慢              非常灵活

 

//一般情况下:for_each所使用的函数对象,参数是引用,没有返回值

void mysquare(int &num)

{

         num= num * num;

}

 

//transform所使用的函数对象,参数一般不使用引用,而是还有返回值

int mysquare2(int num) //结果的传出,必须是通过返回值

{

         returnnum = num * num;

}

 

void main_foreach_pk_tranform()

{

         vector<int>  v1;

         v1.push_back(1);

         v1.push_back(3);

         v1.push_back(5);

 

         vector<int>v2= v1;

         for_each(v1.begin(),v1.end(), mysquare);

         printAA(v1);

         cout<< endl;

 

         transform(v2.begin(),v2.end(), v2.begin(), mysquare2);

         printAA(v2);

         cout<< endl;

}

 

10.3.4常用的查找算法

adjacent_find()

在iterator对标识元素范围内,查找一对相邻重复元素,找到则返回指向这对元素的第一个元素的迭代器。否则返回past-the-end。

vector<int>vecInt;

         vecInt.push_back(1);

         vecInt.push_back(2);

         vecInt.push_back(2);

         vecInt.push_back(4);

         vecInt.push_back(5);

vecInt.push_back(5);

 

         vector<int>::iteratorit = adjacent_find(vecInt.begin(), vecInt.end());            //*it == 2

binary_search

在有序序列中查找value,找到则返回true。注意:在无序序列中,不可使用。

 

                   set<int>setInt;

                   setInt.insert(3);

                   setInt.insert(1);

                   setInt.insert(7);

                   setInt.insert(5);

                   setInt.insert(9);

 

                   boolbFind = binary_search(setInt.begin(),setInt.end(),5);

count() 

利用等于操作符,把标志范围内的元素与输入值比较,返回相等的个数。

vector<int> vecInt;

                   vecInt.push_back(1);

                   vecInt.push_back(2);

                   vecInt.push_back(2);

                   vecInt.push_back(4);

                   vecInt.push_back(2);

                   vecInt.push_back(5);

                   intiCount = count(vecInt.begin(),vecInt.end(),2);  //iCount==3

        

count_if()

假设vector<int> vecIntA,vecIntA包含1,3,5,7,9元素

 

//先定义比较函数

bool GreaterThree(int iNum)

{

                   if(iNum>=3)

                   {

                            returntrue;

                   }

                   else

                   {

                            returnfalse;

                   }

}

 

int iCount = count_if(vecIntA.begin(),vecIntA.end(), GreaterThree);

//此时iCount == 4

find()

²  find:  利用底层元素的等于操作符,对指定范围内的元素与输入值进行比较。当匹配时,结束搜索,返回该元素的迭代器。

²  equal_range:    返回一对iterator,第一个表示lower_bound,第二个表示upper_bound。

 

vector<int> vecInt;

                   vecInt.push_back(1);

                   vecInt.push_back(3);

                   vecInt.push_back(5);

                   vecInt.push_back(7);

                   vecInt.push_back(9);

 

vector<int>::iterator it = find(vecInt.begin(), vecInt.end(),5);                 //*it == 5

find_if()

find_if:  使用输入的函数代替等于操作符执行find。返回被找到的元素的迭代器。

假设vector<int> vecIntA,vecIntA包含1,3,5,3,9元素

vector<int>::it =find_if(vecInt.begin(),vecInt.end(),GreaterThree);

此时 *it==3, *(it+1)==5, *(it+2)==3, *(it+3)==9

 

10.3.5常用的排序算法

merge()

²  以下是排序和通用算法:提供元素排序策略

²  merge:    合并两个有序序列,存放到另一个序列。

例如:vecIntA,vecIntB,vecIntC是用vector<int>声明的容器,vecIntA已包含1,3,5,7,9元素,vecIntB已包含2,4,6,8元素

vecIntC.resize(9);  //扩大容量

merge(vecIntA.begin(),vecIntA.end(),vecIntB.begin(),vecIntB.end(),vecIntC.begin());

此时vecIntC就存放了按顺序的1,2,3,4,5,6,7,8,9九个元素

sort()

²  sort:  以默认升序的方式重新排列指定范围内的元素。若要改排序规则,可以输入比较函数。

//学生类

Class CStudent:

{

public:

       CStudent(int iID, string strName)

                   {

m_iID=iID; 

m_strName=strName;

}

public:           

         intm_iID;

         stringm_strName;

}

 

//学号比较函数

bool Compare(const CStudent &stuA,constCStudent &stuB)

{

                  return (stuA.m_iID<strB.m_iID);

}

void main()

{

      vector<CStudent> vecStu;

      vecStu.push_back(CStudent(2,"老二"));

vecStu.push_back(CStudent(1,"老大"));

vecStu.push_back(CStudent(3,"老三"));

vecStu.push_back(CStudent(4,"老四"));

 

    sort(vecStu.begin(),vecStu.end(),Compare);

 

//  此时,vecStu容器包含了按顺序的"老大对象","老二对象","老三对象","老四对象"

}

random_shuffle()

²  random_shuffle:     对指定范围内的元素随机调整次序。

                   srand(time(0));                            //设置随机种子

 

                   vector<int>vecInt;

                   vecInt.push_back(1);

                   vecInt.push_back(3);

                   vecInt.push_back(5);

                   vecInt.push_back(7);

                   vecInt.push_back(9);

 

                   stringstr("itcastitcast ");

        

                   random_shuffle(vecInt.begin(),vecInt.end());   //随机排序,结果比如:9,7,1,5,3

                   random_shuffle(str.begin(),str.end());                //随机排序,结果比如:" itstcasticat"

reverse()

                   vector<int>vecInt;

                   vecInt.push_back(1);

                   vecInt.push_back(3);

                   vecInt.push_back(5);

                   vecInt.push_back(7);

                   vecInt.push_back(9);

 

                   reverse(vecInt.begin(),vecInt.end());               //{9,7,5,3,1}

10.3.6常用的拷贝和替换算法

copy()

vector<int> vecIntA;

                   vecIntA.push_back(1);

                   vecIntA.push_back(3);

                   vecIntA.push_back(5);

                   vecIntA.push_back(7);

                   vecIntA.push_back(9);

 

                   vector<int>vecIntB;

                   vecIntB.resize(5);                       //扩大空间

 

                   copy(vecIntA.begin(),vecIntA.end(), vecIntB.begin());   //vecIntB:{1,3,5,7,9}

replace()

²  replace(beg,end,oldValue,newValue):    将指定范围内的所有等于oldValue的元素替换成newValue。

                   vector<int>vecIntA;

                   vecIntA.push_back(1);

                   vecIntA.push_back(3);

                   vecIntA.push_back(5);

                   vecIntA.push_back(3);

                   vecIntA.push_back(9);

 

                   replace(vecIntA.begin(),vecIntA.end(), 3, 8);                   //{1,8,5,8,9}

replace_if()

²  replace_if : 将指定范围内所有操作结果为true的元素用新值替换。

用法举例:

replace_if(vecIntA.begin(),vecIntA.end(),GreaterThree,newVal)

其中vecIntA是用vector<int>声明的容器

GreaterThree 函数的原型是 boolGreaterThree(int iNum)

 

//把大于等于3的元素替换成8

                   vector<int>vecIntA;

                   vecIntA.push_back(1);

                   vecIntA.push_back(3);

                   vecIntA.push_back(5);

                   vecIntA.push_back(3);

                   vecIntA.push_back(9);

 

                   replace_if(vecIntA.begin(),vecIntA.end(), GreaterThree, 8);                   //GreaterThree的定义在上面。

swap()

²  swap:   交换两个容器的元素

                   vector<int>vecIntA;

                   vecIntA.push_back(1);

                   vecIntA.push_back(3);

                   vecIntA.push_back(5);

                  

                   vector<int>vecIntB;

                   vecIntB.push_back(2);

                   vecIntB.push_back(4);

 

                   swap(vecIntA,vecIntB);  //交换

10.3.7常用的算术和生成算法

accumulate()

²  accumulate:  对指定范围内的元素求和,然后结果再加上一个由val指定的初始值。

² #include<numeric>

                   vector<int>vecIntA;

                   vecIntA.push_back(1);

                   vecIntA.push_back(3);

                   vecIntA.push_back(5);

                   vecIntA.push_back(7);

                   vecIntA.push_back(9);

                   intiSum = accumulate(vecIntA.begin(), vecIntA.end(), 100);          //iSum==125

fill()

²  fill:   将输入值赋给标志范围内的所有元素。

                   vector<int>vecIntA;

                   vecIntA.push_back(1);

                   vecIntA.push_back(3);

                   vecIntA.push_back(5);

                   vecIntA.push_back(7);

                   vecIntA.push_back(9);

                   fill(vecIntA.begin(),vecIntA.end(), 8);               //8, 8,8, 8, 8

10.3.8常用的集合算法

set_union(),set_intersection(),set_difference()

²  set_union:  构造一个有序序列,包含两个有序序列的并集。

²  set_intersection:  构造一个有序序列,包含两个有序序列的交集。

²  set_difference:  构造一个有序序列,该序列保留第一个有序序列中存在而第二个有序序列中不存在的元素。

vector<int> vecIntA;

                   vecIntA.push_back(1);

                   vecIntA.push_back(3);

                   vecIntA.push_back(5);

                   vecIntA.push_back(7);

                   vecIntA.push_back(9);

 

                   vector<int>vecIntB;

                   vecIntB.push_back(1);

                   vecIntB.push_back(3);

                   vecIntB.push_back(5);

                   vecIntB.push_back(6);

                   vecIntB.push_back(8);

 

                   vector<int>vecIntC;

                   vecIntC.resize(10);

 

                   //并集

                   set_union(vecIntA.begin(),vecIntA.end(), vecIntB.begin(), vecIntB.end(), vecIntC.begin());                  //vecIntC :{1,3,5,6,7,8,9,0,0,0}

 

                   //交集

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

                   set_intersection(vecIntA.begin(),vecIntA.end(), vecIntB.begin(), vecIntB.end(), vecIntC.begin());               //vecIntC: {1,3,5,0,0,0,0,0,0,0}

 

                   //差集

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

                   set_difference(vecIntA.begin(),vecIntA.end(), vecIntB.begin(), vecIntB.end(), vecIntC.begin());                            //vecIntC:{7,9,0,0,0,0,0,0,0,0}

10.4 STL综合案例

10.4.1案例学校演讲比赛

10.4.1.1学校演讲比赛介绍

1)某市举行一场演讲比赛( speech_contest),共有24个人参加。比赛共三轮,前两轮为淘汰赛,第三轮为决赛。

2)比赛方式:分组比赛,每组6个人;选手每次要随机分组,进行比赛;

第一轮分为4个小组,每组6个人。比如100-105为一组,106-111为第二组,依次类推,

每人分别按照抽签(draw)顺序演讲。当小组演讲完后,淘汰组内排名最后的三个选手,然后继续下一个小组的比赛。

        第二轮分为2个小组,每组6人。比赛完毕,淘汰组内排名最后的三个选手,然后继续下一个小组的比赛。

         第三轮只剩下6个人,本轮为决赛,选出前三名。

        

4)比赛评分:10个评委打分,去除最低、最高分,求平均分

每个选手演讲完由10个评委分别打分。该选手的最终得分是去掉一个最高分和一个最低分,求得剩下的8个成绩的平均分。

选手的名次按得分降序排列,若得分一样,按参赛号升序排名。

 

用STL编程,求解这个问题

1)  请打印出所有选手的名字与参赛号,并以参赛号的升序排列。

2)  打印每一轮比赛后,小组比赛成绩和小组晋级名单

3)  打印决赛前三名,选手名称、成绩。

10.4.1.2需求分析

                   //产生选手 ( ABCDEFGHIJKLMNOPQRSTUVWXYZ) 姓名、得分;选手编号

                   //第1轮  选手抽签选手比赛 查看比赛结果

                   //第2轮  选手抽签选手比赛 查看比赛结果

                   //第3轮  选手抽签选手比赛 查看比赛结果

10.4.1.3实现思路

                   需要把选手信息、选手得分信息、选手比赛抽签信息、选手的晋级信息保存在容器中,需要涉及到各个容器的选型。(相当于信息的数据库E-R图设计)

                   选手可以设计一个类Speaker(姓名和得分)

                   所有选手编号和选手信息,可以放在容器内:map<int, Speaker>

                   所有选手的编号信息,可以放在容器:vecter<int> v1中

                   第1轮晋级名单,可以放在容器vecter<int>v2中

                   第2轮晋级名单,可以放在容器vecter<int>v3中

                   第3轮前三名名单,可以放在容器vecter<int>v4中

        

                   每个小组的比赛得分信息,按照从小到大的顺序放在

multimap<成绩, 编号, greater<int>>  multmapGroup          

也就是:multimap<int,int, greater<int> > multmapGroup;        

                   每个选手的得分,可以放在容器deque<int> dscore; 方便去除最低最高分

10.4.1.4实现细节

1)  搭建框架

 

2)  完善业务函数

random_shuffle

 

3)  测试

 

void main()

{

         //定义数据结构 所有选手放到容器中

         map<int,Speaker>  mapSpeaker;

         vector<int>                         v1; //第1轮演讲比赛 名单

         vector<int>                         v2; //第2轮演讲比赛 名单

         vector<int>                         v3; //第3轮演讲比赛 名单

         vector<int>                         v4; //最后 演讲比赛 名单

 

         //产生选手

         GenSpeaker(mapSpeaker,v1);

 

         //第1轮  选手抽签选手比赛 查看比赛结果(晋级名单 得分情况)

         cout<< "\n\n\n任意键,开始第一轮比赛" << endl;

         cin.get();

         speech_contest_draw(v1);

         speech_contest(0,  v1, mapSpeaker, v2);

         speech_contest_print(0,v2, mapSpeaker);

 

         //第2轮 选手抽签 选手比赛 查看比赛结果

         cout<< "\n\n\n任意键,开始第二轮比赛" << endl;

         cin.get();

         speech_contest_draw(v2);

         speech_contest(1,  v2, mapSpeaker, v3);

         speech_contest_print(1,v3, mapSpeaker);

 

         //第3轮 选手抽签 选手比赛 查看比赛结果

         cout<< "\n\n\n任意键,开始第三轮比赛" << endl;

         cin.get();

         speech_contest_draw(v3);

         speech_contest(2,  v3, mapSpeaker, v4);

         speech_contest_print(2,v4, mapSpeaker);

 

         system("pause");

}

 

//产生选手

int GenSpeaker(map<int, Speaker>&mapSpeaker, vector<int> &v1)

 

//选手抽签

int speech_contest_draw(vector<int>    &v)

 

//选手比赛

int speech_contest(int index,  vector<int> &v1, map<int,Speaker> &mapSpeaker, vector<int> &v2)

 

//打印选手比赛晋级名单

int speech_contest_print(int index,vector<int> v, map<int, Speaker> & mapSpeaker)

10.4.2案例:足球比赛

作业: 市区中学,足球比赛








  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值