万能择优器

项目经理带你-零基础学习C/C++

【从入门到精通】

项目十五 C++核心编程-万能择优器

第1节 项目需求

程序员Jack 的团队新接手了一个底层的项目,项目经理要求Jack 实现一个通用的容器,能够支持插入多种不同的普通类型(包含 int char float double 等)和自定义结构体和自定义类的对象,并能根据每种不同类型的比较规则从容器中取得最大或最小的那个值或对象。

QQ图片20191225102429

示例代码:

// demo 15-1.c

#include <vector>

#include <iostream>

using namespace std;

class demo{

public:

    demo(int _k=0){k=_k;}

    ~demo(){}

    int value(){return k;}

private:

    int k;

};

int main(void){

    vector<int> v1;

    int i1 = 1;

    int i2 = 2;

    v1.push_back(i1);

    v1.push_back(i2);

    demo d1(10);

    vector<demo> v2;

    v2.push_back(d1);

    for(unsigned int i=0; i<v1.size(); i++){

        printf("vector v1 中的元素%d : %d\n",i ,v1[i]);

    }

    cout<<v2[0].value()<<endl;

    system("pause");

    return 0;

}

第2节 项目精讲

前言

C++提供了模板(template)编程的概念。所谓模板,实际上是建立一个通用函数或类,其类内部的类型和函数的形参类型不具体指定,用一个虚拟的类型来代表。这种通用的方式称为模板。模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

1. C++函数模板的使用

项目需求: 实现多个函数用来返回两个数的最大值,要求能支持char类型、int类型、double类型变量

// demo 15-2.c

#include <iostream>

using namespace std;

int Max(int a, int b)

{

    return a>b ? a:b;

}

char Max(char a, char b)

{

    return a>b ? a:b;

}

float Max(float a, float b)

{

    return a>b ? a:b;

}

void main()

{

    //char a = 'c';

    

    int  x = 1;

    int y = 2;

    cout<<"max(1, 2) = "<<Max(x, y)<<endl; 

    float a = 2.0;

    float b = 3.0;

  

    cout<<"max(2.0, 3.0) = "<<Max(a, b)<<endl;

    system("pause");

    return ;

}

实际上,以上程序,只需要一个“函数”就可以搞定!

// demo 15-3.c

#include <iostream>

using namespace std;

/*

int Max(int a, int b)

{

    return a>b ? a:b;

}

char Max(char a, char b)

{

    return a>b ? a:b;

}

float Max(float a, float b)

{

    return a>b ? a:b;

}

*/

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

//T - 参数化数据类型

template <typename T>

T Max(T a, T b){

    return a>b ? a:b;

}

/*如果T 使用int 类型调用,相当于调用下面这个函数

int Max(int a, int b)

{

    return a>b ? a:b;

}

*/

void main()

{

    //char a = 'c';

    

    int  x = 1;

    int y = 2;

    cout<<"max(1, 2) = "<<Max(x, y)<<endl; //实现参数类型的自动推导

    cout<<"max(1, 2) = "<<Max<int>(x,y)<<endl;//显示类型调用

    float a = 2.0;

    float b = 3.0;

  

    cout<<"max(2.0, 3.0) = "<<Max(a, b)<<endl;

    system("pause");

    return ;

}

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

函数模板定义形式

  由以下三部分组成:  模板说明 + 函数定义 + 函数模板调用

  template < 类型形式参数表 >

  类型  函数名 (形式参数表)

{

    //语句序列

}

1. 模板说明  

template    < 类型形式参数表 >  

类型形式参数的形式:

                typename T1 ,  typename T2 , …… , typename Tn 

或   class T1 ,  class T2 , …… , class Tn 

(注:typename 和 class 的效果完全等同)

2. 函数定义

 类型  函数名  (形式参数表)

{

}

注意:模板说明的类属参数必须在函数定义中出现一次

      函数参数表中可以使用类属类型参数,也可以使用一般类型参数

3. 函数模板调用

max<int>(a, b);  //显式类型调用

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

4.模板函数

模板函数

5.函数模板和函数重载

// demo 15-4.c

#include <iostream>

using namespace std;

template <typename T>

void Swap(T &a, T &b){

    T t;

    t = a;

    a = b;

    b = t;

    cout<<"Swap 模板函数被调用了"<<endl;

}

/*

void Swap(char &a, int &b){

    int  t;

    t = a;

    a = b;

    b = t;

    cout<<"Swap 普通函数被调用了"<<endl;

}

*/

void main(void){

    char cNum = 'c';

    int iNum = 65;

    //第一种情况,模板函数和普通函数并存,参数类型和普通重载函数更匹配

    //调用普通函数

    //Swap(cNum, iNum);

    //第二种情况  不存在普通函数,函数模板会隐式数据类型转换嘛?

    //结论:不提供隐式的数据类型转换,必须是严格的匹配

    //Swap(cNum, iNum);

    system("pause");

    return ;

}

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

两者允许并存

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

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

// demo 15-5.c

#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);

}

//第二版

int Max1(int a, int b)

{

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

    return a>b ? a:b;

}

template<typename T1, typename T2>

T1 Max1(T1 a, T2 b)

{

    cout<<"调用 T Max1(T1 a, T2 b)"<<endl;

    return a>b ? a:b;

}

void main(void){

    int a = 1;

    int b = 2;

    //当函数模板和普通函数都符合调用时,优先选择普通函数

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

    //如果显式的使用函数模板,则使用<> 类型列表

    //Max<>(a, b);

    char c = 'a';

    //如果函数模板会产生更好的匹配,使用函数模板

    //Max1(c, a);

    //Max(1.0, 2.0);

    Max(3.0, 4.0, 5.0);

    system("pause");

    return ;

}

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

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

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

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

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

6.函数模板调用机制

// demo 15-6.c

#include <iostream>

using namespace std;

template <typename T>

T Max(T a, T b){

    return a>b ? a:b;

}

int main()

{

    int  x = 1;

    int y = 2;

    Max(x, y);

    float a = 2.0;

    float b = 3.0;

    Max(a, b);

    return 0;

}

反汇编观察

// demo.c

#include <iostream>

using namespace std;

int Max(int a, int b){

        return a>b ? a:b;

}

int main()

{

        int  x = 1;

        int  y = 2;

        Max(x, y);

        return 0;

}

g++ -S demo.cpp -o demo.S

// demo.S

        .file   "demo.cpp"

        .local  _ZStL8__ioinit

        .comm   _ZStL8__ioinit,1,1

        .text

        .globl  _Z3Maxii

        .type   _Z3Maxii, @function

_Z3Maxii:

.LFB1021:

        .cfi_startproc

        pushq   %rbp

        .cfi_def_cfa_offset 16

        .cfi_offset 6, -16

        movq    %rsp, %rbp

        .cfi_def_cfa_register 6

        movl    %edi, -4(%rbp)

        movl    %esi, -8(%rbp)

        movl    -4(%rbp), %eax

        cmpl    -8(%rbp), %eax

        jle     .L2

        movl    -4(%rbp), %eax

        jmp     .L4

.L2:

        movl    -8(%rbp), %eax

.L4:

        popq    %rbp

        .cfi_def_cfa 7, 8

        ret

        .cfi_endproc

.LFE1021:

        .size   _Z3Maxii, .-_Z3Maxii

        .globl  main

        .type   main, @function

main:

.LFB1022:

        .cfi_startproc

        pushq   %rbp

        .cfi_def_cfa_offset 16

        .cfi_offset 6, -16

        movq    %rsp, %rbp

        .cfi_def_cfa_register 6

        subq    $16, %rsp

        movl    $1, -8(%rbp)

        movl    $2, -4(%rbp)

        movl    -4(%rbp), %edx

        movl    -8(%rbp), %eax

        movl    %edx, %esi

        movl    %eax, %edi

        call    _Z3Maxii

        movl    $0, %eax

        leave

        .cfi_def_cfa 7, 8

        ret

        .cfi_endproc

.LFE1022:

        .size   main, .-main

        .type   _Z41__static_initialization_and_destruction_0ii, @function

_Z41__static_initialization_and_destruction_0ii:

.LFB1023:

        .cfi_startproc

        pushq   %rbp

        .cfi_def_cfa_offset 16

        .cfi_offset 6, -16

        movq    %rsp, %rbp

        .cfi_def_cfa_register 6

        subq    $16, %rsp

        movl    %edi, -4(%rbp)

        movl    %esi, -8(%rbp)

        cmpl    $1, -4(%rbp)

        jne     .L9

        cmpl    $65535, -8(%rbp)

        jne     .L9

        movl    $_ZStL8__ioinit, %edi

        call    _ZNSt8ios_base4InitC1Ev

        movl    $__dso_handle, %edx

        movl    $_ZStL8__ioinit, %esi

        movl    $_ZNSt8ios_base4InitD1Ev, %edi

        call    __cxa_atexit

.L9:

        nop

        leave

        .cfi_def_cfa 7, 8

        ret

        .cfi_endproc

.LFE1023:

        .size   _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii

        .type   _GLOBAL__sub_I__Z3Maxii, @function

_GLOBAL__sub_I__Z3Maxii:

.LFB1024:

        .cfi_startproc

        pushq   %rbp

        .cfi_def_cfa_offset 16

        .cfi_offset 6, -16

        movq    %rsp, %rbp

        .cfi_def_cfa_register 6

        movl    $65535, %esi

        movl    $1, %edi

        call    _Z41__static_initialization_and_destruction_0ii

        popq    %rbp

        .cfi_def_cfa 7, 8

        ret

        .cfi_endproc

.LFE1024:

        .size   _GLOBAL__sub_I__Z3Maxii, .-_GLOBAL__sub_I__Z3Maxii

        .section        .init_array,"aw"

        .align 8

        .quad   _GLOBAL__sub_I__Z3Maxii

        .hidden __dso_handle

        .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609"

        .section        .note.GNU-stack,"",@progbits

// demo_06.cpp

#include <iostream>

using namespace std;

template <typename T>

T Max(T a, T b){

    return a>b ? a:b;

}

int main()

{

    int  x = 1;

    int y = 2;

    Max(x, y);

    float a = 2.0;

    float b = 3.0;

    Max(a, b);

    return 0;

}

g++ -S demo_06.cpp -o demo.S

// demo_06.S

        .file   "demo_06.cpp"

        .local  _ZStL8__ioinit

        .comm   _ZStL8__ioinit,1,1

        .text

        .globl  main

        .type   main, @function

main:

.LFB1022:

        .cfi_startproc

        pushq   %rbp

        .cfi_def_cfa_offset 16

        .cfi_offset 6, -16

        movq    %rsp, %rbp

        .cfi_def_cfa_register 6

        subq    $32, %rsp

        movl    $1, -16(%rbp)

        movl    $2, -12(%rbp)

        movl    -12(%rbp), %edx

        movl    -16(%rbp), %eax

        movl    %edx, %esi

        movl    %eax, %edi

        call    _Z3MaxIiET_S0_S0_

        movss   .LC0(%rip), %xmm0

        movss   %xmm0, -8(%rbp)

        movss   .LC1(%rip), %xmm0

        movss   %xmm0, -4(%rbp)

        movss   -4(%rbp), %xmm0

        movl    -8(%rbp), %eax

        movaps  %xmm0, %xmm1

        movl    %eax, -20(%rbp)

        movss   -20(%rbp), %xmm0

        call    _Z3MaxIfET_S0_S0_

        movl    $0, %eax

        leave

        .cfi_def_cfa 7, 8

        ret

        .cfi_endproc

.LFE1022:

        .size   main, .-main

        .section        .text._Z3MaxIiET_S0_S0_,"axG",@progbits,_Z3MaxIiET_S0_S0_,comdat

        .weak   _Z3MaxIiET_S0_S0_

        .type   _Z3MaxIiET_S0_S0_, @function

_Z3MaxIiET_S0_S0_:

.LFB1023:

        .cfi_startproc

        pushq   %rbp

        .cfi_def_cfa_offset 16

        .cfi_offset 6, -16

        movq    %rsp, %rbp

        .cfi_def_cfa_register 6

        movl    %edi, -4(%rbp)

        movl    %esi, -8(%rbp)

        movl    -4(%rbp), %eax

        cmpl    -8(%rbp), %eax

        jle     .L4

        movl    -4(%rbp), %eax

        jmp     .L6

.L4:

        movl    -8(%rbp), %eax

.L6:

        popq    %rbp

        .cfi_def_cfa 7, 8

        ret

        .cfi_endproc

.LFE1023:

        .size   _Z3MaxIiET_S0_S0_, .-_Z3MaxIiET_S0_S0_

        .section        .text._Z3MaxIfET_S0_S0_,"axG",@progbits,_Z3MaxIfET_S0_S0_,comdat

        .weak   _Z3MaxIfET_S0_S0_

        .type   _Z3MaxIfET_S0_S0_, @function

_Z3MaxIfET_S0_S0_:

.LFB1024:

        .cfi_startproc

        pushq   %rbp

        .cfi_def_cfa_offset 16

        .cfi_offset 6, -16

        movq    %rsp, %rbp

        .cfi_def_cfa_register 6

        movss   %xmm0, -4(%rbp)

        movss   %xmm1, -8(%rbp)

        movss   -4(%rbp), %xmm0

        ucomiss -8(%rbp), %xmm0

        jbe     .L13

        movss   -4(%rbp), %xmm0

        jmp     .L11

.L13:

        movss   -8(%rbp), %xmm0

.L11:

        popq    %rbp

        .cfi_def_cfa 7, 8

        ret

        .cfi_endproc

.LFE1024:

        .size   _Z3MaxIfET_S0_S0_, .-_Z3MaxIfET_S0_S0_

        .text

        .type   _Z41__static_initialization_and_destruction_0ii, @function

_Z41__static_initialization_and_destruction_0ii:

.LFB1025:

        .cfi_startproc

        pushq   %rbp

        .cfi_def_cfa_offset 16

        .cfi_offset 6, -16

        movq    %rsp, %rbp

        .cfi_def_cfa_register 6

        subq    $16, %rsp

        movl    %edi, -4(%rbp)

        movl    %esi, -8(%rbp)

        cmpl    $1, -4(%rbp)

        jne     .L16

        cmpl    $65535, -8(%rbp)

        jne     .L16

        movl    $_ZStL8__ioinit, %edi

        call    _ZNSt8ios_base4InitC1Ev

        movl    $__dso_handle, %edx

        movl    $_ZStL8__ioinit, %esi

        movl    $_ZNSt8ios_base4InitD1Ev, %edi

        call    __cxa_atexit

.L16:

        nop

        leave

        .cfi_def_cfa 7, 8

        ret

        .cfi_endproc

.LFE1025:

        .size   _Z41__static_initialization_and_destruction_0ii, .-_Z41__static_initialization_and_destruction_0ii

        .type   _GLOBAL__sub_I_main, @function

_GLOBAL__sub_I_main:

.LFB1026:

        .cfi_startproc

        pushq   %rbp

        .cfi_def_cfa_offset 16

        .cfi_offset 6, -16

        movq    %rsp, %rbp

        .cfi_def_cfa_register 6

        movl    $65535, %esi

        movl    $1, %edi

        call    _Z41__static_initialization_and_destruction_0ii

        popq    %rbp

        .cfi_def_cfa 7, 8

        ret

        .cfi_endproc

.LFE1026:

        .size   _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main

        .section        .init_array,"aw"

        .align 8

        .quad   _GLOBAL__sub_I_main

        .section        .rodata

        .align 4

.LC0:

        .long   1073741824

        .align 4

.LC1:

        .long   1077936128

        .hidden __dso_handle

        .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.5) 5.4.0 20160609"

        .section        .note.GNU-stack,"",@progbits

结论:

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

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

2. 类模板的使用

1.为什么需要类模板

类模板与函数模板的定义和使用类似,有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,我们可以通过如下面语句声明了一个类模板:

// demo 15-7.c

template <typename T>

class A

{

public:

       A(T t)

       {

              this->t = t;

       }

       T &getT()

       {

              return t;

       }

public:

       T t;

};

2.类模板定义

   类模板由模板说明和类说明构成

   模板说明同函数模板,如下:

         template    <类型形式参数表>

         类声明

例如: 

   template  <typename Type>

class ClassName

{

//ClassName 的成员函数

   private :

   Type DataMember;

}

3.单个类模板的使用

// demo 15-8.c

#include <iostream>

using namespace std;

template <typename T>

class A

{

public:

    //函数的参数列表使用虚拟类型

    A(T t=0)

    {

        this->t = t;

    }

    //成员函数返回值使用虚拟类型

    T &getT()

    {

        return t;

    }

private:

    //成员变量使用虚拟类型

    T t;

};

void printA(A<int> &a){

    cout<<a.getT()<<endl;

}

int main(void){

    //1.模板类定义类对象,必须显示指定类型

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

    A<int>  a(666);

    cout<<a.getT()<<endl;

    //模板类做为函数参数

    printA(a);

    system("pause");

    return 0;

}

4.继承中类模板的使用

// demo 15-9.c

#include <iostream>

using namespace std;

//继承中父子类和模板类的结合情况

//1.父类一般类,子类是模板类, 和普通继承的玩法类似

//2.子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数

//3.父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中

/*class B

{

public:

    B(int b)

    {

        this->b = b;

    }

private:

    int b;

};

*/

template <typename T>

class A

{

public:

    //函数的参数列表使用虚拟类型

    A(T t)

    {

        this->t = t;

    }

    //成员函数返回值使用虚拟类型

    T &getT()

    {

        return t;

    }

private:

    //成员变量使用虚拟类型

    T t;

};

template <typename Tb>

class B: public A<int>

{

    public:

    B(Tb b):A<Tb>(b)

    {

        this->b = b;

    }

private:

    Tb b;

};

void printA(A<int> &a){

    cout<<a.getT()<<endl;

}

int main(void){

    //1.模板类定义类对象,必须显示指定类型

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

    A<int>  a(666);

    cout<<a.getT()<<endl;

    B<int> b(888);

    cout<<"b(888): "<<b.getT()<<endl;

    //模板类做为函数参数

    printA(a);

    system("pause");

    return 0;

}

结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么

1.父类一般类,子类是模板类, 和普通继承的玩法类似

2.子类是一般类,父类是模板类,继承时必须在子类里实例化父类的类型参数

3.父类和子类都时模板类时,子类的虚拟的类型可以传递到父类中

5.类模板函数的三种表达描述方式

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

    ---上面已讲解---

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

// demo 15-9.c

#include <iostream>

using namespace std;

template <typename T>

class A

{

public:

    A(T t=0);

    T &getT();

    A operator +(const A &other);

    void print();

private:

    T t;

};

/*

class A

{

public:

    A(int t=0);

    int &getT();

    A operator +(const A &other);

    void print();

private:

    int t;

};

*/

template <typename T>

A<T>::A(T t)

{

        this->t = t;

}

template <typename T>

T &A<T>::getT()

    {

        return t;

    }

template <typename T>

A<T> A<T>::operator+(const A<T> &other){

        A<T> tmp; //类的内部类型可以显示声明也可以不显示

        tmp.t =this->t + other.t;

        return tmp;

    }

template <typename T>

void A<T>::print(){

    cout<<this->t<<endl;

}

int main(void){

    

    A<int>  a(666), b(888);

    //cout<<a.getT()<<endl;

    A<int> tmp = a + b;

    tmp.print();

    system("pause");

    return 0;

}

总结:

在同一个cpp 文件中把模板类的成员函数放到类的外部,需要注意以下几点

  1. 函数前声明 template    <类型形式参数表>
  2. 类的成员函数前的类限定域说明必须要带上虚拟参数列表
  3. 返回的变量是模板类的对象时必须带上虚拟参数列表
  4. 成员函数参数中出现模板类的对象时必须带上虚拟参数列表
  5. 成员函数内部没有限定

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

// demo.h

#pragma once

template <typename T>

class A

{

public:

    A(T t=0);

    T &getT();

    A operator +(const A &other);

    void print();

private:

    T t;

};

// demo 15-10.c

#include "demo.h"

#include <iostream>

using namespace std;

template <typename T>

A<T>::A(T t)

{

        this->t = t;

}

template <typename T>

T &A<T>::getT()

    {

        return t;

    }

template <typename T>

A<T> A<T>::operator+(const A<T> &other){

        A<T> tmp; //类的内部类型可以显示声明也可以不显示

        tmp.t =this->t + other.t;

        return tmp;

    }

template <typename T>

void A<T>::print(){

    cout<<this->t<<endl;

}

int main(void){

    

    A<int>  a(666), b(888);

    //cout<<a.getT()<<endl;

    A<int> tmp = a + b;

    tmp.print();

    system("pause");

    return 0;

}

注意:当类模板的声明(.h文件)和实现(.cpp 或.hpp文件)完全分离,因为类模板的特殊实现,我们应在使用类模板时使用#include 包含 实现部分的.cpp 或.hpp文件。

5.4 特殊情况 友元函数

// demo 15-11.c

#include <iostream>

using namespace std;

template <typename T>

class A

{

public:

    A(T t=0);

    //声明一个友元函数,实现对两个A类对象进行加法操作

    template <typename T>

    friend A<T> addA(const A<T> &a, const A<T> &b);

    T &getT();

    A operator +(const A &other);

    void print();

private:

    T t;

};

template <typename T>

A<T>::A(T t)

{

        this->t = t;

}

template <typename T>

T &A<T>::getT()

    {

        return t;

    }

template <typename T>

A<T> A<T>::operator+(const A<T> &other){

        A tmp; //类的内部类型可以显示声明也可以不显示

        tmp.t =this->t + other.t;

        return tmp;

    }

template <typename T>

void A<T>::print(){

    cout<<this->t<<endl;

}

//A 类的友元函数,就是它的好朋友

template <typename T>

A<T> addA(const A<T> &a, const A<T> &b){

    A<T> tmp;

    cout<<"call addA()..."<<endl;

    tmp.t = a.t + b.t;

    return tmp;

}

int main(void){

    

    A<int>  a(666), b(888);

    //cout<<a.getT()<<endl;

    A<int> tmp = a + b;

    A<int> tmp1 = addA<int>(a, b);

    

    tmp.print();

    tmp1.print();

    system("pause");

    return 0;

}

结论:

  1. 类内部声明友元函数,必须写成一下形式

    template<typename T>

friend A<T> addA (A<T> &a, A<T> &b);

  1. 友元函数实现 必须写成

                            template<typename T>

                A<T> add(A<T> &a, A<T> &b)

{

                    //......

}

  1. 友元函数调用 必须写成

                            A<int> c4 = addA<int>(c1, c2);

                

5.5 模板类和静态成员

// demo 15-12.c

#include <iostream>

using namespace std;

template <typename T>

class A

{

public:

    A(T t=0);

    T &getT();

    A operator +(const A &other);

    void print();

public:

    static int count;

private:

    T t;

};

template <typename T> int A<T>::count = 666;

template <typename T>

A<T>::A(T t)

{

    this->t = t;

}

template <typename T>

T &A<T>::getT()

{

    return t;

}

template <typename T>

A<T> A<T>::operator+(const A<T> &other){

    A tmp; //类的内部类型可以显示声明也可以不显示

    tmp.t =this->t + other.t;

    return tmp;

}

template <typename T>

void A<T>::print(){

    cout<<this->t<<endl;

}

/*

//当我们的虚拟的类型T被 int 实例化以后,模板类如下:

class A

{

public:

A(int t=0);

int &getT();

A operator +(const A &other);

void print();

public:

static int count;

private:

int t;

};

int A::count = 666;

A::A(int t)

{

this->t = t;

}

int &A::getT()

{

return t;

}

A A::operator+(const A &other){

A tmp; //类的内部类型可以显示声明也可以不显示

tmp.t =this->t + other.t;

return tmp;

}

void A::print(){

cout<<this->t<<endl;

}

*/

/*

//当我们的虚拟的类型T被 float 实例化以后,模板类如下:

class A

{

public:

A(float t=0);

float &getT();

A operator +(const A &other);

void print();

public:

static int count;

private:

float t;

};

int A::count = 666;

A::A(float t)

{

this->t = t;

}

float &A::getT()

{

return t;

}

A A::operator+(const A &other){

A tmp; //类的内部类型可以显示声明也可以不显示

tmp.t =this->t + other.t;

return tmp;

}

void A::print(){

cout<<this->t<<endl;

}

*/

int main(void){

    A<int>  a(666), b(888);

    A<int> tmp = a + b;

    //A  a(666), b(888);

    //A tmp = a + b;

    A<float> c(777), d(999);

    a.count = 888;

    cout<<"b.count:"<<b.count<<endl;

    cout<<"c.count:"<<c.count<<endl;

    cout<<"d.count:"<<d.count<<endl;

    c.count = 1000;

    cout<<"修改后, d.count:"<<d.count<<endl;

    //tmp.print();

    system("pause");

    return 0;

}

总结:

6.类模板使用总结

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

1) 先写出一个实际的类。

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

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

    template <typename 虚拟类型参数>

如:

    template <typename numtype> 

    class A

    {…}; //类体

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

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

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

如:

    A<int> cmp;

    A<int> cmp(3,7);

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

   template <typename 虚拟类型参数>

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

关于类模板的几点补充:

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

    template <typename T1,typename T2>

    class someclass

    {…};

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

    someclass<int, char> object;

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

3) 模板类也可以有支持继承,有层次关系,一个类模板可以作为基类,派生出派生模板类。

7.类模板实战

  1. 请设计一个数组模板类( Vector ),完成对int、char、float、double 以及任意的自定义类等类型元素进行管理。           需求
  1. 实现构造函数
  2. 实现拷贝构造函数
  3. 实现cout << 操作
  4. 实现下标访问符[] 的重载操作
  5. 实现 = 号操作符重载

源码实现:

// demo 15-13  Vector.h

#include <iostream>

using namespace std;

template <typename  T>

class Vector

{

    //Vector<int> a(10); cout<<a;

    friend ostream &operator<< <T> (ostream &out, const Vector &object);

public:

    Vector(int size = 128); //构造函数

    Vector(const Vector &object); //拷贝构造函数

    //Vector<int> a(10); a

    //operator<<()

    int getLength();//获取内部储存的元素个数

    //Vector<int> a1, a2;  a1[0]

     T& operator[](int index);

     //实现=操作符重载

     //a1 = a2 = a3;

     Vector &operator=(const Vector &object);

    ~Vector(); //析构函数

private:

    T *m_base;

    int m_len;

};

// demo 15-13  Vector.cpp

#include <iostream>

using namespace std;

#include "Vector.h"

//cout<<a<<b<<c;

template<typename T>

ostream &operator<<(ostream &out, const Vector<T> &object){

    for(int i=0; i<object.m_len; i++){

        out << object.m_base[i] << " ";//Student a("18","李小花"); cout<< a<<endl;

    }

    out<<endl;

    return out;

}

template <typename T>

Vector<T>::Vector(int size){ //构造函数

    if(size > 0){

        m_len = size;

        m_base = new T[m_len];

    }

}

    

template <typename T>

Vector<T>::Vector(const Vector<T> &object){ //拷贝构造函数

    //根据传入的对象元素个数分配空间

    m_len = object.m_len;

    m_base = new T[m_len];

    //数据的拷贝

    for(int i=0; i<m_len; i++){

        m_base[i] = object.m_base[i];

    }

}

template <typename T>

int Vector<T>::getLength(){

    return m_len;

}

    //Vector<int> a1, a2;  a1[0]

template <typename T>

T& Vector<T>::operator[](int index){

    return m_base[index];// return *(m_base+index);

}

     //实现=操作符重载

     //a1 = a2 = a3;

template <typename T>

Vector<T> &Vector<T>::operator=(const Vector<T> &object){

    if(m_base != NULL){

        delete[] m_base;

        m_base = NULL;

        m_len = 0;

    }

    //根据传入的对象元素个数分配空间

    m_len = object.m_len;

    m_base = new T[m_len];

    //数据的拷贝

    for(int i=0; i<m_len; i++){

        m_base[i] = object.m_base[i];

    }

    return *this; // a3 = a2 = a1; 

}

template <typename T>

Vector<T>::~Vector(){ //析构函数

    if(m_base != NULL){

        delete[] m_base;

        m_base = NULL;

        m_len = 0;

    }

}

// demo 15-13  13_类模板实战.cpp

#include <iostream>

using namespace std;

#include "Vector.cpp"

class Student{

    friend ostream &operator<<(ostream &out, const Student &object);

public:

    Student(){

        age = 0;

        name[0] = '\0';

    }

    Student(int _age, char *_name){

        age = _age;

        strcpy_s(name, 64, _name);

    }

    void print(){

        cout<<name<<", "<<age<<endl;

    }

    ~Student(){

    }

private:

    int age;

    char name[64];

};

ostream &operator<<(ostream &out, const Student &object){

    out<<"("<<object.name<<" , "<<object.age<<")";

    return out;

}

int main(){

    Student s1(18, "李小花");

    Student s2(19, "王大炮");

    Vector<Student *> studentVector(2);

    studentVector[0] = &s1;

    studentVector[1] = &s2;

    /*for(int i=0; i<studentVector.getLength(); i++){

        studentVector[i].print();

    }*/

    cout<<studentVector<<endl;

    system("pause");

    //ostream cout;

    Vector<int> myVector(10);

    //int a[10]; len: sizeof(a)/sizeof(a[0])

    for(int i=0; i<myVector.getLength(); i++){

        myVector[i] = i;

    }

    cout<<myVector<<endl;

    system("pause");

    for(int i=0; i<myVector.getLength(); i++){

        cout<<myVector[i]<<endl;

    }

    //测试拷贝构造函数

    Vector<int> myIntVector1(myVector);

    cout<<"myIntVector1 中的元素如下:"<<endl;

    for(int i=0; i<myIntVector1.getLength(); i++){

        cout<<myIntVector1[i]<<endl;

    }

    cout<<"---end---"<<endl;

    //测试赋值运算符重载

    Vector<int> myIntVector2(1);

    myIntVector2 = myIntVector1;

    cout<<"myIntVector2 中的元素如下:"<<endl;

    for(int i=0; i<myIntVector1.getLength(); i++){

        cout<<myIntVector1[i]<<endl;

    }

    cout<<"---end---"<<endl;

    Vector<float> myVector1(10);

    //int a[10]; len: sizeof(a)/sizeof(a[0])

    for(int i=0; i<myVector1.getLength(); i++){

        myVector1[i] = i*0.1f;

    }

    for(int i=0; i<myVector1.getLength(); i++){

        cout<<myVector1[i]<<endl;

    }

    system("pause");

    return 0;

}

8.作业

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

附1:优化Student类, 属性变成 char *pname, 构造函数里面 分配内存

附2:优化Student类,析构函数 释放pname指向的内存空间

附3:优化Student类,避免浅拷贝 重载= 重写拷贝构造函数

附4:思考 Student * 类成员如何操作

3. 异常处理机制

唐僧一行西天取经队伍到达贫困山区,几天要不到吃的,悟空因为要保护师父,只好让沙僧和八戒去远处城里找吃的.

第一天去,空手回来,因为没有钱.第二天去,还是空手,因为没有钱.

悟空大怒:"再找不回吃的,就别回来!"

第三天傍晚,沙僧高高兴兴地背着一大袋子米,还剩了好多钱.

悟空大喜,又问:"八戒呢?"

沙僧顿时伤心地哭道:"大师兄,原谅我吧!咱们这么多人,就二师兄能卖到25块钱一斤.......

​编辑  ​编辑

异常无处不在,程序随时可能误入歧途!C++ 提出了新的异常处理机制!

异常是一种程序控制机制,与函数机制互补

    函数是一种以栈结构展开的上下函数衔接的程序控制系统,异常是另一种控制结构,它可以在出现“意外”时中断当前函数,并以某种机制(类型匹配)回馈给隔代的调用者相关的信息.

3.1传统错误处理机制

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

// demo 15-14 

#include <stdio.h>

#include <stdlib.h>

#define BUFSIZE 1024

//实现文件的二进制拷贝

int copyfile(char *dest, char *src){

    FILE *fp1 = NULL, *fp2 = NULL;

    //rb 只读方式打开一个二进制文件,只允许读取数据

    fopen_s(&fp1, src, "rb");

    if(fp1 == NULL){

        return -1;

    }

    //wb 以只写的方式打开或新建一个二进制文件,只允许写数据。

    fopen_s(&fp2, dest, "wb");

    if(fp2 == NULL){

        return -2;

    }

    char buffer[BUFSIZE];

    int readlen, writelen;

    //如果读到数据,则大于0

    while( (readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0 ){

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

        if(readlen != writelen){

            return -3 ;

        }

    }

    fclose(fp1);

    fclose(fp2);

    return 0;

}

void main(){

    int ret = 0;

    ret = copyfile("c:/test/dest.txt", "c:/test/src.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;

        }

    }

    system("pause");

}

C++ 异常处理机制

// demo 15-15 

#include <stdio.h>

#include <stdlib.h>

#include <string>

using namespace std;

#define BUFSIZE 1024

//实现文件的二进制拷贝

int copyfile2(char *dest, char *src){

    FILE *fp1 = NULL, *fp2 = NULL;

    //rb 只读方式打开一个二进制文件,只允许读取数据

    fopen_s(&fp1, src, "rb");

    if(fp1 == NULL){

        throw new string("文件不存在");

    }

    //wb 以只写的方式打开或新建一个二进制文件,只允许写数据。

    fopen_s(&fp2, dest, "wb");

    if(fp2 == NULL){

        throw -2;

    }

    char buffer[BUFSIZE];

    int readlen, writelen;

    //如果读到数据,则大于0

    while( (readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0 ){

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

        if(readlen != writelen){

            throw -3 ;

        }

    }

    fclose(fp1);

    fclose(fp2);

    return 0;

}

int copyfile1(char *dest, char *src){

    return copyfile2(dest, src);

}

void main(){

    int ret = 0;

    try{

        ret = copyfile1("c:/test/dest.txt", "c:/test/src.txt");

    }catch(int error){

        printf("出现异常啦!%d\n", error);

    }catch(string *error){

        printf("捕捉到字符串异常:%s\n", error->c_str());

        delete error;

    }

    system("pause");

}

3.2 异常处理基本语法

异常发生第一现场,抛出异常

void  function( ){

//... ...

  throw 表达式;

//... ...

}

在需要关注异常的地方,捕捉异常

try{

//程序

function();

//程序

}catch(异常类型声明){

//... 异常处理代码 ...

}catch(异常类型 形参){

//... 异常处理代码 ...

}catch(...){ //其它异常类型

//

}

注意事项:

  • 通过throw操作创建一个异常对象并抛掷

  • 在需要捕捉异常的地方,将可能抛出异常的程序段嵌在try块之中

  • 按正常的程序顺序执行到达try语句,然后执行try块{}内的保护段

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

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

  •  如果没有找到匹配,则缺省功能是调用abort终止程序。

提示:处理不了的异常,我们可以在catch的最后一个分支,使用throw语法,继续向调用者throw。

源码:

// demo 15-16 

#include <stdio.h>

#include <stdlib.h>

#include <string>

using namespace std;

#define BUFSIZE 1024

//实现文件的二进制拷贝

int copyfile2(char *dest, char *src){

    FILE *fp1 = NULL, *fp2 = NULL;

    //通过throw操作创建一个异常对象并抛掷

    throw 0.01f;

    //rb 只读方式打开一个二进制文件,只允许读取数据

    fopen_s(&fp1, src, "rb");

    if(fp1 == NULL){

        throw new string("文件不存在");

    }

    //wb 以只写的方式打开或新建一个二进制文件,只允许写数据。

    fopen_s(&fp2, dest, "wb");

    if(fp2 == NULL){

        throw -2;

    }

    char buffer[BUFSIZE];

    int readlen, writelen;

    //如果读到数据,则大于0

    while( (readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0 ){

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

        if(readlen != writelen){

            throw -3 ;

        }

    }

    fclose(fp1);

    fclose(fp2);

    return 0;

}

int copyfile1(char *dest, char *src){

    try{

        copyfile2(dest, src);

    }catch(float e){

        //throw ;

        printf("copyfile1 - catch ...\n");

        //提示:处理不了的异常,我们可以在catch的最后一个分支,使用throw语法,继续向调用者throw。

        throw ;

    }

    return 0;

}

void main(){

    int ret = 0;

    //在需要捕捉异常的地方,将可能抛出异常的程序段嵌在try块之中

    //按正常的程序顺序执行到达try语句,然后执行try块{}内的保护段

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

    try{//保护段

        printf("开始执行 copyfile1...\n");

        ret = copyfile1("c:/test/dest.txt", "c:/test/src.txt");

        printf("执行 copyfile1 完毕\n");

       

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

    }catch(int error){

        printf("出现异常啦!%d\n", error);

    }catch(string *error){

        printf("捕捉到字符串异常:%s\n", error->c_str());

        delete error;

    }catch(float error){

        printf("出现异常啦!%f\n", error);

    }catch(...){

        printf("catch ...\n");

    }

    //如果没有找到匹配,则缺省功能是调用abort终止程序。

    system("pause");

}

3.3异常接口声明

可以在函数声明中列出可能抛出的所有异常类型,加强程序的可读性。

如:

int copyfile2(char *dest, char *src) throw (float, string *, int)

1.对于异常接口的声明,在函数声明中列出可能抛出的所有异常类型

2.如果没有包含异常接口声明,此函数可以抛出任何类型的异常

3.如果函数声明中有列出可能抛出的所有异常类型,那么抛出其它类型的异常讲可能导致程序终止

4.如果一个函数不想抛出任何异常,可以使用 throw () 声明

3.4异常类型和生命周期

main

copyfile1

copyfile2

函数调用

函数调用

异常处理

  1. throw基本类型

// demo 15-17

#include <stdio.h>

#include <stdlib.h>

#include <string>

using namespace std;

#define BUFSIZE 1024

//实现文件的二进制拷贝

//第一种情况,throw 普通类型,和函数返回传值是一样的

int copyfile2(char *dest, char *src){

    FILE *fp1 = NULL, *fp2 = NULL;

    //rb 只读方式打开一个二进制文件,只允许读取数据

    fopen_s(&fp1, src, "rb");

    if(fp1 == NULL){

        //int ret = -1;

        char ret = 'a';

        throw ret;

    }

    //wb 以只写的方式打开或新建一个二进制文件,只允许写数据。

    fopen_s(&fp2, dest, "wb");

    if(fp2 == NULL){

        throw -2;

    }

    char buffer[BUFSIZE];

    int readlen, writelen;

    //如果读到数据,则大于0

    while( (readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0 ){

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

        if(readlen != writelen){

            throw -3 ;

        }

    }

    fclose(fp1);

    fclose(fp2);

    return 0;

}

int copyfile1(char *dest, char *src){

    return copyfile2(dest, src);

}

void main(){

    int ret = 0;

   

    try{//保护段

        //printf("开始执行 copyfile1...\n");

        ret = copyfile1("c:/test/dest.txt", "c:/test/src.txt");

        //printf("执行 copyfile1 完毕\n");

       

    }catch(int error){

        printf("出现异常啦!%d\n", error);

    }catch(char error){

        printf("出现异常啦!%c\n", error);

    }

    system("pause");

  1. throw 字符串类型

// demo 15-18

#include <stdio.h>

#include <stdlib.h>

#include <string>

using namespace std;

#define BUFSIZE 1024

//第二种情况,throw 字符串类型,实际抛出的指针,而且,修饰指针的const 也要严格进行类型匹配

int copyfile3(char *dest, char *src){

    FILE *fp1 = NULL, *fp2 = NULL;

    //rb 只读方式打开一个二进制文件,只允许读取数据

    fopen_s(&fp1, src, "rb");

    if(fp1 == NULL){

        const char * error = "大佬,你的源文件打开有问题";

        printf("throw 前,error 的地址:%p\n", error);

        throw error;

    }

    //wb 以只写的方式打开或新建一个二进制文件,只允许写数据。

    fopen_s(&fp2, dest, "wb");

    if(fp2 == NULL){

        throw -2;

    }

    char buffer[BUFSIZE];

    int readlen, writelen;

    //如果读到数据,则大于0

    while( (readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0 ){

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

        if(readlen != writelen){

            throw -3 ;

        }

    }

    fclose(fp1);

    fclose(fp2);

    return 0;

}

int copyfile1(char *dest, char *src){

    return copyfile3(dest, src);

}

void main(){

    int ret = 0;

    try{//保护段

        //printf("开始执行 copyfile1...\n");

        ret = copyfile1("c:/test/dest.txt", "c:/test/src.txt");

        //printf("执行 copyfile1 完毕\n");

       

    }catch(int error){

        printf("出现异常啦!%d\n", error);

    }catch(char error){

        printf("出现异常啦!%c\n", error);

    }catch(string error){

        printf("出现异常啦!%s\n", error.c_str());

    }catch(const char *error){

        printf("出现异常啦(char *)!%s(地址:%p)\n", error, error);

    }catch(...){

        printf("没捉到具体的异常类型\n");

    }

    system("pause");

}

3throw 类对象类型异常

// demo 15-19

#include <stdio.h>

#include <stdlib.h>

#include <string>

using namespace std;

#define BUFSIZE 1024

class ErrorException{

public:

    ErrorException(){

        id = 0;

        printf("ErrorException  构造!\n");

    }

    ~ErrorException(){

        printf("ErrorException  ~析构!(id: %d)\n", id);

    }

    ErrorException(const ErrorException &e){

        id = 1;

        printf("ErrorException  拷贝构造函数!\n");

    }

    int  id;

};

//第三种情况,throw 类类型,最佳的方式是使用引用类型捕捉,抛出匿名对象

//当然,如果是动态分配的对象,直接抛出其指针

//注意:引用和普通的形参传值不能共存

int copyfile4(char *dest, char *src){

    FILE *fp1 = NULL, *fp2 = NULL;

    //rb 只读方式打开一个二进制文件,只允许读取数据

    fopen_s(&fp1, src, "rb");

    if(fp1 == NULL){

        //ErrorException error1;

        throw ErrorException(); //throw ErrorException();

    }

    //wb 以只写的方式打开或新建一个二进制文件,只允许写数据。

    fopen_s(&fp2, dest, "wb");

    if(fp2 == NULL){

        throw -2;

    }

    char buffer[BUFSIZE];

    int readlen, writelen;

    //如果读到数据,则大于0

    while( (readlen = fread(buffer, 1, BUFSIZE, fp1)) > 0 ){

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

        if(readlen != writelen){

            throw -3 ;

        }

    }

    fclose(fp1);

    fclose(fp2);

    return 0;

}

int copyfile1(char *dest, char *src){

    return copyfile4(dest, src);

}

void main(){

    int ret = 0;

    try{//保护段

        //printf("开始执行 copyfile1...\n");

        ret = copyfile1("c:/test/dest.txt", "c:/test/src.txt");

        //printf("执行 copyfile1 完毕\n");

       

    }catch(ErrorException error){

        printf("出现异常啦!捕捉到 ErrorException 类型 id: %d\n", error.id);

    }catch(ErrorException &error){

        //error.id = 2;

        printf("出现异常啦!捕捉到 ErrorException &类型 id: %d\n", error.id);

    }catch(ErrorException *error){

        printf("出现异常啦!捕捉到 ErrorException *类型 id: %d\n", error->id);

        delete error;

    }catch(...){

        printf("没捉到具体的异常类型\n");

    }

    system("pause");

}

3.5 继承与异常

异常也是类,我们可以创建自己的异常类,在异常中可以使用(虚函数,派生,引用传递和数据成员等)

案例:设计一个数组类容器 Vector,重载[]操作,数组初始化时,对数组的个数进行有效检查

  1. index<0 抛出异常errNegativeException 
  2. index = 0 抛出异常 errZeroException

       3)index>1000抛出异常errTooBigException

       4)index<10 抛出异常errTooSmallException

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

// demo 15-20

#include <iostream>

using namespace std;

/*

设计一个数组类容器 Vector,重载[]操作,数组初始化时,对数组的个数进行有效检查

1)index<0 抛出异常errNegativeException 

2)index = 0 抛出异常 errZeroException

3)index>1000抛出异常errTooBigException

4)index<10 抛出异常errTooSmallException

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

*/

class errSizeException{

public:

    errSizeException(int size){

        m_size = size;

    }

    virtual void printError(){

        cout<<"size: "<<m_size<<endl;

    }

protected:

    int m_size;

};

class errNegativeException : public errSizeException{

public:

    errNegativeException(int size):errSizeException(size){

    }

    virtual void printError(){

        cout<<"errNegativeException size: "<<m_size<<endl;

    }

};

class errZeroException : public errSizeException{

    public:

    errZeroException(int size):errSizeException(size){

    }

    virtual void printError(){

        cout<<"errZeroException size: "<<m_size<<endl;

    }

};

class errTooBigException : public errSizeException{

    public:

    errTooBigException(int size):errSizeException(size){

    }

    virtual void printError(){

        cout<<"errTooBigException size: "<<m_size<<endl;

    }

};

class errTooSmallException : public errSizeException{

    public:

    errTooSmallException(int size):errSizeException(size){

    }

    virtual void printError(){

        cout<<"errTooSmallException size: "<<m_size<<endl;

    }

};

class Vector{

public:

    Vector(int  size = 128); //构造函数

    int getLength();//获取内部储存的元素个数

    int& operator[](int index);

    ~Vector();

private:

    int *m_base;

    int m_len;

};

Vector::Vector(int len){

    if(len < 0){

        throw errNegativeException(len);

    }else if(len == 0){

        throw errZeroException(len);

    }else if(len > 1000){

        throw errTooBigException(len);

    }else if(len < 10){

        throw errTooSmallException(len);

    }

    m_len = len;

    m_base = new int[len];

}

Vector::~Vector(){

    if(m_base) delete[] m_base;

    m_len = 0;

}

int Vector::getLength(){

    return m_len;

}

int &Vector::operator[](int index){

    return m_base[index];

}

void main(){

    try{

        Vector v(10000);

        for(int i=0; i<v.getLength(); i++){

            v[i] = i+10;

            printf("v[i]: %d\n", v[i]);

        }

    }catch(errSizeException &err){

        err.printError();

    }

   

    /*catch(errNegativeException &err){

        cout<<"errNegativeException..."<<endl;

    }catch(errZeroException &err){

        cout<<"errZeroException..."<<endl;

    }catch(errTooBigException &err){

        cout<<"errTooBigException..."<<endl;

    }catch(errTooSmallException &err){

        cout<<"errTooSmallException..."<<endl;

    }*/

    system("pause");

    return ;

}

3.6异常处理的基本思想

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

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

3.7 标准程序库异常

// demo 15-21

#include <iostream>

#include <exception>

#include <stdexcept>

using namespace std;

class Student{

public:

    Student(int age){

        if(age > 249){

            throw out_of_range("年龄太大,你是外星人嘛?");

        }

        m_age = age;

        m_space = new int[1024*1024*100];

    }

private :

    int m_age;

    int *m_space;

};

void main(){

    try{

        for(int i=1; i<1024; i++){

            Student * xiao6lang = new Student(18);

        }

    }catch(out_of_range &e){

        cout<<"捕捉到一只异常:"<<e.what()<<endl;

    }catch(bad_alloc &e){

        cout<<"捕捉到动态内存分配的异常:"<<e.what()<<endl;

    }

    system("pause");

}

  1. STL(标准模板库)专题

STL主要分为分为三类:

  1. algorithm(算法)  -   对数据进行处理(解决问题) 步骤的有限集合
  2. container(容器)  -   用来管理一组数据元素

  1. Iterator  (迭代器) -   可遍历STL容器内全部或部分元素”的对象

容器和算法通过迭代器可以进行无缝地连接。在STL中几乎所有的代码都采用了模板类和模板函数的方式,这相比于传统的由函数和类组成的库来说提供了更好的代码重用机会。

STL 最早源于惠普实验室,早于C++存在,但是C++引入STL概念后,STL就成为C++的一部分,因为它被内建在你的编译器之内,不需要另行安装。

STL被组织为下面的13个头文 件:<algorithm>、<deque>、<functional>、<iterator>、<vector>、<list>、<map>、<memory>、<numeric>、<queue>、<set>、<stack> 和<utility>。

// demo 15-22 

#include <iostream>

using namespace std;

#include <vector>

#include <algorithm>

class student{

public:

    student(int age, const char *name){

        this->age = age;

        strncpy(this->name, name, 64);

    }

    student(const student &s){

        this->age = s.age;

        strncpy(this->name, s.name, 64);

        cout<<"拷贝构造函数被调用!"<<endl;

    }

public:

    int age;

    char name[64];

};

//容器中直接存放对象,会发生拷贝构造

void demo2(){

    vector<student> v1;

    student s1(18, "李小美");

    student s2(19, "王大帅");

    v1.push_back(s1);

    v1.push_back(s2);

    cout<<"v1 的学生的个数:"<<v1.size()<<endl;

    //方式1,下标访问

    //for(unsigned int i=0; i<v1.size(); i++){

    //  cout<<v1[i].name<<": "<<v1[i].age<<endl;

    //}

    vector<student>::iterator it = v1.begin();

    for( ; it != v1.end(); it++){

        cout<< (*it).name<<": "<<(*it).age <<endl;

    }

}

//容器中存放指针,不执行拷贝构造,效率较高

void demo3(){

    vector<student *> v1;

    student s1(18, "李小美");

    student s2(19, "王大帅");

    v1.push_back(&s1);

    v1.push_back(&s2);

    cout<<"v1 的学生的个数:"<<v1.size()<<endl;

    //方式1,下标访问

    //for(unsigned int i=0; i<v1.size(); i++){

    //  cout<<v1[i].name<<": "<<v1[i].age<<endl;

    //}

    vector<student *>::iterator it = v1.begin();

    for( ; it != v1.end(); it++){

        cout<< (**it).name<<": "<<(**it).age <<endl;

    }

}

void demo1(){

    //第一部分 容器

    vector<int> v1;

    v1.push_back(1);//调用了拷贝构造,是复制操作,值传递

    v1.push_back(2);

    v1.push_back(3);

    v1.push_back(4);

    v1.push_back(3);

    cout<<"v1 的元素个数:"<<v1.size()<<endl;

    cout<<"v1中保存的元素:"<<endl;

    //方式1,下标访问

    //for(unsigned int i=0; i<v1.size(); i++){

    //  cout<<v1[i]<<endl;

    //}

    //方式2,迭代器访问

    //第二部分 迭代器

    //1  2  3  4

    //it

    vector<int>::iterator it = v1.begin();

    for( ; it != v1.end(); it++){

        cout<< *it <<endl;

    }

    //第三部分  算法

    int ncount = count(v1.begin(), v1.end(), 90);

    cout<<"v1 中数值为 90 的元素个数:"<< ncount<< endl;

}

void main(){

    demo3();

    system("pause");

    return ;

}

4.1容器

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

试想: 一条死胡同里面停车,这样的效率会很高嘛?

QQ图片20190911153532

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

容器部分主要有由<vector>,<list>,<deque>,<set>,<map>,<stack> 和<queue>组成。

下面是常用的一些容器,可以通过下表总结一下它们和相应头文件的对应关系。

数据结构

描述

实现头文件

向量(vector)

连续存储的元素

<vector>

列表(list)

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

<list>

双向队列(deque)

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

<deque>

集合(set)

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

<set>

多重集合(multiset)

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

<set>

栈(stack)

后进先出的元素的排列

<stack>

队列(queue)

先进先出的元素的排列

<queue>

优先队列(priority_queue)

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

<queue>

映射(map)

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

<map>

多重映射(multimap)

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

<map>

4.1.1 Vector容器
Vector容器概念

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

vector可以随机存取元素,支持索引值直接存取, 用[]操作符或at()方法对元素进行操作

插入元素

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

删除元素

vector对象的构造

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

vector<T> vecT;

//默认构造函数

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

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

vector<student> v2; //一个存放student的vector容器

//带参构造函数

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

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

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

// demo 15-23

#include <iostream>

using namespace std;

#include <vector>

#include <algorithm>

void demo1(){

    //vector 对象的默认构造

   //默认构造函数 元素个数为0, 所占内存空间为0

    /*vector<int> v1;

    //vector<float> v2;

   

    cout<<"v1 的元素个数: "<<v1.size()<<endl;

    cout<<"v1 容器的大小:"<<v1.capacity()<<endl;

    //当我们使用vector 的默认构造函数时,切记,不能直接通过下标去访问

    //v1[0]=1;

    v1.push_back(1);

    cout<<"尾部插入1个元素后:"<<endl;

    cout<<"v1 的元素个数:"<<v1.size()<<endl;

    cout<<"v1 容器的大小:"<<v1.capacity()<<endl;

    v1.push_back(2);

    v1.push_back(3);

    v1.push_back(4);

    v1.push_back(5);

    cout<<"尾部插入5个元素后:"<<endl;

    cout<<"v1 的元素个数:"<<v1.size()<<endl;

    cout<<"v1 容器的大小:"<<v1.capacity()<<endl;

    */

    //vector 带参构造函数

    //vector<int> v2(10);  //构造时就分配空间,同时插入10个元素,元素大小为0

    vector<int> v2(10, 666);

    //vector<int> v3(v2);

    //vector<int> v3(v2.begin()+3, v2.end());

    int test[]={1, 2, 3, 4, 5};

    vector<int> v3(test, test+2);

    cout<<"v2 的元素个数:"<<v2.size()<<endl;

    cout<<"v2 容器的大小:"<<v2.capacity()<<endl;

    cout<<"v2调用 assign 后:"<<endl;

    cout<<"v2 的元素个数:"<<v2.size()<<endl;

    cout<<"v2 中存储的元素是: "<<endl;

    for(int i=0; i<v2.size(); i++){

        cout<<v2[i]<<endl;

    }

    cout<<"v3 中存储的元素是: "<<endl;

    for(int i=0; i<v3.size(); i++){

        cout<<v3[i]<<endl;

    }

}

void main(){

    demo1();

    system("pause");

    return ;

}

vector的赋值

vector 的赋值

v2.assign(2, 888);//第一种玩法  改变原来vector 中的元素个数和值

v2.assign(v3.begin(), v3.end());//第二种玩法,使用迭代器重新赋值

int test1[]={1, 2, 3, 4, 5};

v2.assign(test1, test1+3);//第三种玩法,使用指针赋值

v2 = v3;//第四种玩法,赋值运算

vector的大小

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

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

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

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

vector末尾的添加移除操作 

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

v2.pop_back();    //移除容器中最后一个元素

vector的数据存取

第一  使用下标操作 v2[0] = 100;

第二  使用at 方法 如: v2.at(2) = 100;

第三  接口返回的引用 v2.front() 和 v2.back() 

注意:   第一和第二种方式必须注意越界

vector的插入

vector.insert(pos,elem);   //在pos位置插入一个elem元素的拷贝,返回新数据的位置。

vector.insert(pos,n,elem);   //在pos位置插入n个elem数据,无返回值。

vector.insert(pos,beg,end);   //在pos位置插入[beg,end)区间的数据,无返回值

vector的删除

1. 把整个vector 都干掉

       v2.clear();

       cout<<"调用 v2.clear() 后"<<endl;

2.干掉单个元素

       v2[1] = 888;

       v2.erase(v2.begin()+1);

3. 干掉多个元素

       v2.erase(v2.begin(), v2.begin()+3);

4.1.2 deque容器
deque容器概念

deque是“double-ended queue”的缩写,和vector一样都是STL的容器,唯一不同的是:

deque是双端数组,而vector是单端的。

QQ图片20200131170521

Deque 特点:

  • deque在接口上和vector非常相似,在许多操作的地方可以直接替换。
  • deque可以随机存取元素(支持索引值直接存取,用[]操作符或at()方法)
  • deque头部和尾部添加或移除元素都非常快速, 但是在中部安插元素或移除元素比较费时。

使用时,包含头文件:#include <deque> 

deque对象的默认构造

deque也是采用模板类实现。

deque对象的默认构造形式:deque<T> deqT

例如:

deque <int> deqInt;            //存放int的deque容器。

deque <float> deqFloat;         //存放float的deque容器。

deque <student> deqStu;        //存放student的deque容器。

...   

注意:尖括号内还可以设置指针类型或自定义类型。

deque对象的带参数构造

方式1:deque(beg,end);    //构造函数将[beg, end)区间中的元素拷贝给本身。

方式2:deque(n,elem);   //构造函数将n个elem拷贝给本身。

方式3:deque(const deque  &deq);  //拷贝构造函数。

deque<int> deqIntA;

              deqIntA.push_back(1);

              deqIntA.push_back(2);

              deqIntA.push_back(3);

              deqIntA.push_back(4);

             

              deque<int> deqIntB(deqIntA.begin(),deqIntA.end());            //1 2 3 4

              deque<int> deqIntC(8, 666);                                             //8 8 8 8 8

              deque<int> deqIntD(deqIntA);                                         //1 2 3 4

deque头部和末尾的添加移除操作

  1. deque.push_back(element);    //容器尾部添加一个数据
  2. deque.push_front(element);    //容器头部插入一个数据
  3. deque.pop_back();            //删除容器最后一个数据
  4. deque.pop_front();               //删除容器第一个数据

deque<int> deqIntA;

       deqIntA.push_back(1);

       deqIntA.push_back(2);

       deqIntA.push_back(3);

       deqIntA.push_back(4);

       deqIntA.push_back(5);

deqIntA.push_back(6);

       deqIntA.pop_front();

       deqIntA.pop_front();

       deqIntA.push_front(7);

       deqIntA.push_front(8);

       deqIntA.pop_back();

       deqIntA.pop_back();

deqIntA 中剩余元素: 8 7 3 4

deque的数据存取

第一  使用下标操作 deqIntA[0] = 100;

第二  使用at 方法 如: deqIntA.at(2) = 100;

第三  接口返回的引用 deqIntA.front() 和 deqIntA.back() 

注意:   第一和第二种方式必须注意越界

例如:

              deque<int> deqIntA;

              deqIntA.push_back(1);

              deqIntA.push_back(2);

              deqIntA.push_back(3);

              deqIntA.push_back(4);

              deqIntA.push_back(5);

              int i1 = deqIntA.at(0);             //i1 = 1

              int i2 = deqIntA[1];                 //i2 = 2

              deqIntA.at(0) = 666;         //第一个元素改成666

              deqIntA[1] = 888;                    //第二个元素改成888

              int iFront = deqInt.front();      //666

              int iBack = deqInt.back(); //5

              deqInt.front() = 888;                //第一个元素改成  888

              deqInt.back() = 666;                //最后一个元素改成 666

deque与迭代器
  1. deque.begin();  //返回容器中第一个元素的迭代器。
  2. deque.end();   //返回容器中最后一个元素之后的迭代器。
  3. deque.rbegin();  //返回容器中倒数第一个元素的迭代器。
  4. deque.rend();   //返回容器中倒数最后一个元素之后的迭代器。
  5. deque.cbegin();  //返回容器中第一个元素的常量迭代器。
  6. deque.cend();   //返回容器中最后一个元素之后的常量迭代器。

deque<int> deqIntA;

              deqIntA.push_back(1);

              deqIntA.push_back(2);

              deqIntA.push_back(3);

              deqIntA.push_back(4);

              deqIntA.push_back(5);

//普通迭代器

for(deque<int>::iterator it = deqIntA.begin(); it!=deqIntA.end(); ++it){

       (*it)++;  //*it++  (*it)++

       cout<<*it;

       cout<<" ";

}

//常量迭代器

deque<int>::const_iterator cit = deqIntA.cbegin();

for( ; cit!=deqIntA.cend(); cit++){

       cout<<*cit;

       cout<<" ";

}

//逆转的迭代器

for(deque<int>::reverse_iterator rit=deqIntA.rbegin(); rit!=deqIntA.rend(); ++rit){

       cout<<*rit;

       cout<<" ";

}

deque的赋值
  1. deque.assign(beg,end);    //将[beg, end)区间中的数据拷贝赋值给本身。注意该区间是左闭右开的区间。
  2. deque.assign(n,elem);  //将n个elem拷贝赋值给本身。
  3. deque& operator=(const deque &deq); //重载等号操作符
  4. deque.swap(deq);  // 将deque与本身的元素互换

例如:

deque<int> deqIntA,deqIntB,deqIntC,deqIntD;

deque<int> deqIntA;

              deqIntA.push_back(1);

              deqIntA.push_back(2);

              deqIntA.push_back(3);

              deqIntA.push_back(4);

              deqIntA.push_back(5);

deqIntB.assign(deqIntA.begin(),deqIntA.end()); // 1 2 3 4 5

             

deqIntC.assign(4,888);                                        //888 888 888 888

deqIntD = deqIntA;                                             //1 2 3 4 5

deqIntC.swap(deqIntD);                                      //互换

deque的大小

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

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

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

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

              deque<int> deqIntA;

              deqIntA.push_back(1);

              deqIntA.push_back(2);

              deqIntA.push_back(3);

deqIntA.push_back(4);

deqIntA.push_back(5);

              int iSize = deqIntA.size();  //5

              deqIntA.resize(7);            //1 2 3 4 5 0 0

              deqIntA.resize(8,1);         //1 2 3 4 5 0 0 1

              deqIntA.resize(2);            //1 2

             

deque的插入

deque.insert(pos,elem);   //在pos位置插入一个elem元素的拷贝,返回新数据                                                 的位置。

deque.insert(pos,n,elem);   //在pos位置插入n个elem数据,无返回值。

deque.insert(pos,beg,end);   //在pos位置插入[beg,end)区间的数据,无返回值

例如:

// demo 15-30

#include <deque>

#include <iostream>

using namespace std;

int main(void){

    deque<int> deqIntA;

    deque<int> deqIntB;

    deqIntA.push_back(1);

    deqIntA.push_back(2);

    deqIntA.push_back(3);

    deqIntA.push_back(4);

    deqIntB.push_back(11);

    deqIntB.push_back(12);

    deqIntB.push_back(13);

    deqIntB.push_back(14);

    deqIntA.insert(deqIntA.begin(), 0); // {0,1,2,3,4}

    deqIntA.insert(deqIntA.begin()+1, 2, 88);  //{0,88,88,1,2,3,4}

    deqIntA.insert(deqIntA.begin(), deqIntB.rbegin(), deqIntB.rend());{11,12,13,14,0,88,88,1,2,3,4}

    for(deque<int>::iterator it = deqIntA.begin(); it!=deqIntA.end(); ++it){

        cout<<*it;

        cout<<" ";

    }

    system("pause");

}

deque的删除
  1. deque.clear();          //移除容器的所有数据
  2. deque.erase(beg,end); //删除[beg,end)区间的数据,返回下一个数据的位置。
  3. deque.erase(pos);    //删除pos位置的数据,返回下一个数据的位置。

例如:

// demo 15-30-2

#include <deque>

#include <iostream>

using namespace std;

int main(void){

    deque<int> deqIntA;

   

    deqIntA.push_back(1);

    deqIntA.push_back(2);

    deqIntA.push_back(3);

    deqIntA.push_back(4);

    deqIntA.push_back(5);

    //方式一 单独使用擦除的接口

    //deqIntA.erase(deqIntA.begin()+1); //干掉第二个元素 {1,3,4,5}

    //deqIntA.erase(deqIntA.begin()+1, deqIntA.begin()+3);// 干掉3 和4, 剩下{1, 5}

    //deqIntA.clear(); //干掉所有的元素

    //方式二 使用迭代器遍历删除

    for(deque<int>::iterator it = deqIntA.begin(); it!=deqIntA.end();){

        if(*it == 4){

            it = deqIntA.erase(it);

        }else {

            cout<<*it;

            cout<<" ";

            it++;

        }

    }

    system("pause");

}

4.1.3 List容器
List 容器概念

list是一个双向链表容器,可高效地进行插入删除元素。

        

20170920101712031

双向链表

List 特点:

  • list不可以随机存取元素,所以不支持at.(position)函数与[]操作符。可以对其迭代器执行++,但是不能这样操作迭代器:it+3
  • 使用时包含 #include <list> 

list对象的默认构造

list同样采用模板类实现,对象的默认构造形式:list<T> listT;  如:

  1. list<int> lstInt;            //定义一个存放int的list容器。
  2. list<float> lstFloat;        //定义一个存放float的list容器。
  3. list<string> lstString;       //定义一个存放string的list容器。

...                            

注意:尖括号内还可以设置指针类型或自定义类型。

list对象的带参数构造

方式一:list(beg,end);     //将[beg, end)区间中的元素拷贝给本身。

方式二:list(n,elem);      //构造函数将n个elem拷贝给本身。

方式三:list(const list &lst); //拷贝构造函数。

list<int> lstInt1;

       lstInt1.push_back(1);

       lstInt1.push_back(2);

       lstInt1.push_back(3);

       list<int> lstInt2(lstInt1.begin(),lstInt1.end());             //1 2 3

       list<int> lstInt3(5,8);                                               //8 8 8 8 8

       list<int> lstInt4(lstIntA);                                        //1 2 3

list头尾的添加移除操作
  1. list.push_back(elem);    //在容器尾部加入一个元素
  2. list.pop_back();          //删除容器中最后一个元素
  3. list.push_front(elem);     //在容器开头插入一个元素
  4. list.pop_front();          //从容器开头移除第一个元素

       list<int> lstInt;

       lstInt.push_back(1);

       lstInt.push_back(2);

       lstInt.push_back(3);

       lstInt.push_back(4);

       lstInt.push_back(5);

       lstInt.pop_front();

       lstInt.pop_front();

       lstInt.push_front(11);

       lstInt.push_front(12);

       lstInt.pop_back();

       lstInt.pop_back();

// lstInt    {12, 11, 3}

list的数据存取
  1. list.front();   //返回第一个元素。
  2. list.back();  //返回最后一个元素。

list<int> lstInt;

       lstInt.push_back(1);

       lstInt.push_back(2);

       lstInt.push_back(3);

       lstInt.push_back(4);

       lstInt.push_back(5);

       int iFront = lstInt.front();      //1

       int iBack = lstInt.back();        //5

       lstInt.front() = 11;                 //11

       lstInt.back() = 19;                 //19

list与迭代器
  1. list.begin();        //返回容器中第一个元素的迭代器。
  2. list.end();          //返回容器中最后一个元素之后的迭代器。
  3. list.rbegin();        //返回容器中倒数第一个元素的迭代器。
  4. list.rend();         //返回容器中倒数最后一个元素的后面的迭代器。
  1. list.cbegin();  //返回容器中第一个元素的常量迭代器。
  2. list.cend();   //返回容器中最后一个元素之后的常量迭代器。

       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的赋值
  1. list.assign(beg,end);    //将[beg, end)区间中的数据拷贝赋值给本身。
  2. list.assign(n,elem);  //将n个elem拷贝赋值给本身。
  3. list& operator=(const list &lst);     //重载等号操作符。
  4. list.swap(lst);  // 将lst与本身的元素互换。

      

llist<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

    lstIntB.assign(++lstIntA.begin(),--lstIntA.end());        //3 5 7

       lstIntC.assign(5,8);                                           //8 8 8 8 8

       lstIntD = lstIntA;                                              //1 3 5 7 9

       lstIntC.swap(lstIntD);                                       //互换

list的大小
  1. ist.size();         //返回容器中元素的个数
  2. list.empty();    //判断容器是否为空
  3. list.resize(num);   //重新指定容器的长度为num,若容器变长,则以默认值0填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
  4. list.resize(num, elem);  //重新指定容器的长度为num,若容器变长,则以elem值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。

       list<int> lstIntA;

       lstIntA.push_back(1);

       lstIntA.push_back(2);

       lstIntA.push_back(3);

       if (!lstIntA.empty())

       {

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

              lstIntA.resize(5);                  //1 2 3 0 0

              lstIntA.resize(7,1);               //1 2 3 0 0 1 1

              lstIntA.resize(5);                  //1 2 3 0 0

       }

list的插入
  1. list.insert(pos,elem);   //在pos位置插入一个elem元素的拷贝,返回新数据的位置。
  2. list.insert(pos,n,elem);   //在pos位置插入n个elem数据,无返回值。
  3. list.insert(pos,beg,end);   //在pos位置插入[beg,end)区间的数据,无返回值。

       list<int> listA;

    list<int> listB;

    listA.push_back(1);

    listA.push_back(2);

    listA.push_back(3);

    listA.push_back(4);

    listA.push_back(5);

    listB.push_back(11);

    listB.push_back(12);

    listB.push_back(13);

    listB.push_back(14);

    listA.insert(listA.begin(), -1);        //{-1, 1, 2, 3, 4, 5}

    listA.insert( ++listA.begin(), 2, -2);  //{-1, -2, -2, 1, 2, 3, 4, 5}

    listA.insert(listA.begin() , listB.begin() , listB.end());  //{11, 12, 13, 14, -1, -2, -2, 1, 2, 3, 4, 5}

    for(list<int>::iterator it = listA.begin(); it!=listA.end(); it++){

        cout<< *it<<endl;

    }

list的删除
  1. list.clear();          //移除容器的所有数据
  2. list.erase(beg,end);  //删除[beg,end)区间的数据,返回下一个数据的位置。
  3. list.erase(pos);    //删除pos位置的数据,返回下一个数据的位置。
  4. lst.remove(elem);   //删除容器中所有与elem值匹配的元素。

// demo 15-32

#include <list>

#include <vector>

#include <iostream>

using namespace std;

int main(void){

    //list 删除元素

    list<int> listA;

    listA.push_back(1);

    listA.push_back(2);

    listA.push_back(3);

    listA.push_back(4);

    listA.push_back(5);

    //erase 的用法

    list<int>::iterator itBegin=listA.begin();

    ++ itBegin;

    list<int>::iterator itEnd=listA.begin();

    ++ itEnd;

    ++ itEnd;

    ++ itEnd;

    listA.erase(itBegin,itEnd);//此时容器lstInt包含按顺序的1, 4, 5三个元素。

    listA.erase(listA.begin());//此时容器lstInt包含按顺序的4, 5三个元素。

    listA.push_back(4); // 4, 5, 4

    listA.insert(listA.end(), 5, 4);  //4, 5, 4, 4, 4, 4, 4, 4

    /*remove 删除元素*/

    //方式一  直接调用remove 方法

    //listA.remove(4);

    //方式二   遍历然后逐个删除

    for(list<int>::iterator it=listA.begin(); it!=listA.end(); ){

        if(*it == 4){

            it =listA.erase(it); //相当于执行了++

        }else {

            it++;

        }

    }

    for (list<int>::iterator it=listA.begin(); it!=listA.end(); ++it)

    {

        cout << *it;

        cout << " ";

    }

   

    system("pause");

    return 0;

}

list的反序排列
  1. list.reverse();     //反转链表,比如list包含1, 2, 3, 4, 5五个元素,运行此方

法后,list就包含5, 4, 3, 2, 1元素。

       list<int> listA;

      

       listA.push_back(1);

       listA.push_back(2);

       listA.push_back(3);

       listA.push_back(4);

       listA.push_back(5);

       listA.reverse();                    //5, 4, 3, 2, 1

4.1.4 C++11新特性 变参模板、完美转发和emplace

变参模板 - 使得 emplace 可以接受任意参数,这样就可以适用于任意对象的构建

完美转发 - 使得接收下来的参数 能够原样的传递给对象的构造函数,这带来另一个方便性

// demo 15-33

#include <iostream>

using namespace std;

#include <vector>

#include <list>

#include <deque>

#include <algorithm>

class student {

public:

    student() {

        cout << "无参构造函数被调用!" << endl;

    }

    student(int age, string name, int test) {

        this->age = age;

        //strncpy_s(this->name, name, 64);

        cout << "有参构造函数被调用!" << endl;

        cout << "姓名:" << name.c_str() << " 年龄:" << age << endl;

    }

    student(const student &s) {

        this->age = s.age;

        //strncpy_s(this->name, s.name, 64);

        cout << "拷贝构造函数被调用!" << endl;

    }

    ~student() {

        cout << "析构函数被调用" << endl;

    }

public:

    int age;

    string name;

};

int main(void) {

    //vector<int>   vectInt(10);

    deque<int>   dqInt;   

    list<int>    lstInt;

    vector<student>   vectStu(10);

    cout << "vectStu size:" << vectStu.size() << endl;

    cout << "vectStu capacity:" << vectStu.capacity() << endl;

    //插入学生

    //方法一  先定义对象,再插入

    //student  xiaoHua(18, "李校花");

    //vectStu.push_back(xiaoHua);

    //方法二  直接插入临时对象

    //vectStu.push_back(student(19, "王大锤"));

    //c++11 新特性: 变参模板和完美转发的表演啦

    vectStu.emplace_back(19, "王大锤", 11);   //push_back

    cout << "vectStu size (1):" << vectStu.size() << endl;

    cout << "vectStu capacity(1):" << vectStu.capacity() << endl;

    vectStu.emplace(vectStu.end(), 18, "lixiaohua", 12);   //相当于 insert.

    cout << "vectStu size (2):" << vectStu.size() << endl;

    cout << "vectStu capacity (2):" << vectStu.capacity() << endl;

    system("pause");

    return 0;

}

4.2 算法

4.3 迭代器

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值