C++编程:高阶编程

目录

1 模板

1.1 模板的概念

1.2 函数模板

1.2.1 函数模板的创建与使用

1.2.2 函数模板案例

1.2.3 普通函数与函数模板的区别

1.2.4 普通函数与函数模板的调用规则

1.2.5 模板的局限性

1.3  类模板

1.3.1 类模板基本语法

1.3.2 类模板成员函数创建时机

1.3.3 类模板对象做函数参数

1.3.4 类模板与继承

1.3.5 类模板成员函数类外实现

1.3.6 类模板分文件编写

1.3.7 类模板与友元

1.3.8 类模板案例

1.4 类模板与函数模板的区别

2 STL

3 容器

3.1 string容器

3.2 vector容器

3.3 deque容器

3.4 stack容器

3.5 queue容器

3.6 list容器

3.7 set容器

3.8 map容器

4 谓词

5 内建函数对象

6 常见算法

6.1 遍历算法

6.2 查找算法

6.3 排序算法

6.4 拷贝和替换算法

6.5 算术生成算法

6.6 集合算法

7综合案例


        

 本阶段主要针对泛型编程和STL技术作详细讲解。

1 模板

1.1 模板的概念

C++提供一种泛型编程,主要利用模板的技术,模板的目的是提高代码复用性,将类型参数化。

C++提供两种模板机制:

  • 函数模板
  • 类模板

模板的特点:

  • 模板不可用直接使用,它只是一个框架;
  • 模板的通用表示万能的,也是由使用范围的。

1.2 函数模板

函数模板的作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体制定,而是用一个虚拟的类型来代表。

语法:

template<typename/class T>

函数

template:声明创建模板

typename/class:表明其后面的符号是一种数据类型

T:通用的数据类型,该名称可以替换,通常为大写字母

1.2.1 函数模板的创建与使用

语法:

//声明一个模板,告诉编译器后面紧跟的代码中的T不要报错,T是一个通用类型

template<typename T>

//模板使用的两种方式:1.自动类型推导;2.显示指定类型
//1.自动类型推导
mySwap(a,b);

//2.显示指定类型
mySwap<int>(a,b);

码如下:

#include <iostream>
using namespace std;

//函数模板

//交换两个整形的函数
void swapInt(int a,int b)
{
    int temp=a;
    a=b;
    b=temp;
}

//交换两个浮点数的函数
void swapDouble(double a,double b)
{
    double temp=a;
    a=b;
    b=temp;
}

//函数模板
template<typename T>//声明一个模板,告诉编译器后面紧跟的代码中的T不要报错,T是一个通用类型
void mySwap(T &a, T &b)
{
    T temp=a;
    a=b;
    b=temp;
}

void test01()
{
    int a=4;
    int b=10;

    //swapInt(a,b);

    //利用模板交换,两种方式:1.自动类型推导;2.显示指定类型
    //1.自动类型推导
    ///mySwap(a,b);

    //2.显示指定类型
    mySwap<int>(a,b);
    cout<<"a="<<a<<endl;
    cout<<"b="<<b<<endl;

    double c=4.66;
    double d=3.22;

    swapDouble(c,d);
    //mySwap(a,c);//错误示例1:自动类型推导,必须推导出一致的数据类型T才能使用
    cout<<"c="<<c<<endl;
    cout<<"d="<<d<<endl;
}

template<class T>
void func()
{
    cout<<"this is func()"<<endl;
}

void test02()
{
    //func();//错误示例2:模板必须确定T的数据类型才能使用
}

int main()
{
    test01();
    test02();
    return 0;
}

注意事项:

  • 自动类型推导,必须推导出一致的数据类型T才能使用
  • 模板必须确定T的数据类型才能使用

错误示例1:

错误示例2:

1.2.2 函数模板案例

案例描述:

  • 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
  • 排序规则为从大到小,排序算法为选择排序
  • 分别利用char数组和int数组进行测试

代码如下:

#include <iostream>
using namespace std;

//函数模板案例:对不同数据类型的数组进行从大到小的选择排序
template<class T>
void mySort(T buff[],int len)
{
    for(int i=0;i<len;i++)
    {
        int max=i;
        for(int j=i+1;j<len;j++)
        {
            if(buff[j]>buff[max])
            {
                max=j;
            }
        }
        if(max!=i)
        {
            T temp=buff[i];
            buff[i]=buff[max];
            buff[max]=temp;
        }
    }
}

template<class T>
void printArry(T buff,int len)
{
    for(int i=0;i<len;i++)
    {
        cout<<buff[i]<<endl;
    }
}

//测试char数组
void test01()
{
    char arr[]="abcdef";
    int len=sizeof(arr)/sizeof(char);
    mySort(arr,len);
    printArry(arr,len);
    
}

//测试int数组
void test02()
{
    int arr[]={2,4,6,8,3,1};
    int len=sizeof(arr)/sizeof(int);
    mySort(arr,len);
    printArry(arr,len);
}

int main()
{
    test01();
    test02();
    return 0;
}

输出如下:

写错的地方:

1.选择排序比较两个数的大小是用第j个和最大的数比较if(buff[j]>buff[max]),而不是和第i个比较if(buff[j]>buff[i])。

2.计算数组长度时忘记除以数据类型的长度sizeof(arr)/sizeof(int)

考究的地方:

向函数传递数组时似乎可以只写数组名void printArry(T buff,int len),而不一定要写中括号void mySort(T buff[ ],int len)。

1.2.3 普通函数与函数模板的区别

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)
  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
  • 函数模板调用时,如果利用显示指定类型,可以发生隐式类型转换

建议:使用显式指定类型调用函数模板 

代码如下:

#include <iostream>
using namespace std;

//普通函数调用时可以发生自动类型转换(隐式类型转换)
//函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
//如果利用显示指定类型的方式,可以发生隐式类型转换

//普通函数
int myadd(int a,int b)
{
    return a+b;
}

template<class T>
T myadd2(T a,T b)
{
    return a+b;
}

void test01()
{
    int a=3;
    int b=4;
    char c='c';
    cout<<myadd(a,b)<<endl;

    //普通函数调用时可以发生自动类型转换(隐式类型转换)
    cout<<myadd(a,c)<<endl;//将字符C隐式地转换为了整形

    cout<<myadd2(a,b)<<endl;

    //函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
    //cout<<myadd2(a,c)<<endl;

    //如果利用显示指定类型的方式,可以发生隐式类型转换
    cout<<myadd2<int>(a,c)<<endl;
}


int main()
{
    test01();
    return 0;
}

输出如下:

错误示例:调用函数模板时,使用自动类型推导不会发生隐式类型转换

1.2.4 普通函数与函数模板的调用规则

  • 如果函数模板和普通函数都可以实现,优先调用普通函数
  • 可以通过空模板参数列表强制调用函数模板
  • 函数模板可以发生重载
  • 如果函数模板可以产生更好的匹配,优先调用函数模板

代码如下:

#include <iostream>
using namespace std;

//如果函数模板和普通函数都可以实现,优先调用普通函数
//可以通过空模板参数列表来强制调用函数模板
//函数模板可以发生重载
//如果函数模板可以产生更好的匹配,优先调用函数模板

//普通函数
void myprint(int a,int b)
{
    cout<<"普通函数"<<endl;
}

template<class T>
void myprint(T a,T b)//函数重载
{
    cout<<"函数模板"<<endl;
}

template<class T>
void myprint(T a,T b,T c)//函数模板重载
{
    cout<<"函数模板重载"<<endl;
}

void test01()
{
    int a=3;
    int b=4;

    //如果函数模板和普通函数都可以实现,优先调用普通函数
    myprint(a,b);

    //可以通过空模板参数列表来强制调用函数模板
    myprint<>(a,b);

    //函数模板可以发生重载
    myprint(a,b,18);

    //如果函数模板可以产生 更好的匹配 ,优先调用函数模板
    char c1='a';
    char c2='b';
    myprint(c1,c2);

}

int main()
{
    test01();
    return 0;
}

输出如下:

错误示例:普通函数没有实现,只有定义,调用时仍然不会调用函数模板且报错。

1.2.5 模板的局限性

模板的通用性表示万能的。

template<class T>

void f(T  a, T b)

{
        a=b;

}

例1:在上述代码中提供的赋值操作,如果传入的a和b是一个数组(数组之间无法赋值),就无法实现。

template<class T>

void f(T  a, T b)

{

        if(a>b){...}

}

例2:在上述代码中,如果T的数据类型传入的是像person这样自定义的数据类型(自定义的数据类型不知道如何比较),也无法正常运行。

解法1:运算符重载

解法2:对自定义的数据类型提供一个具体的版本的函数模板

因此,C++提供模板的重载,为特定的类型提供具体化的模板,来解决上述问题。

代码如下:

#include <iostream>
using namespace std;
#include <string>

//自定义数据类型
class Person
{
public:
    Person(string name,int age)
    {
        this->m_Age=age;
        this->m_Name=name;
    }

    string m_Name;
    int m_Age;
};

//对比两个数是否相等的函数
template<class T>
bool mycompare(T &a,T &b)
{
    if(a==b)
    {
        return true;
    }
    else
    {
        return false;
    }
}

void test01()
{
    int a=10;
    int b=20;
    bool ret=mycompare(a,b);

    if(ret)
    {
        cout<<"a=b"<<endl;
    }
    else
    {
        cout<<"a!=b"<<endl;
    }
}

//为person写一个具体的版本
template<> bool mycompare(Person &p1,Person &p2)
{
    if(p1.m_Name==p2.m_Name && p1.m_Age==p2.m_Age)

    {
        return true;
    }
    else
    {
        return false;
    }
}

void test02()
{
    Person p1("tom",10);
    Person p2("tom",10);

    bool ret=mycompare(p1,p2);

    if(ret)
    {
        cout<<"p1=p2"<<endl;
    }
    else
    {
        cout<<"p1!=p2"<<endl;
    }
}

int main()
{
    test01();
    test02();
    return 0;
}

输出如下:

1.3  类模板

1.3.1 类模板基本语法

        类模板作用:建立一个通用类,类中的成员 数据类型可以不具体指定,而是用一个虚拟的类型代表。

语法:

template<typename T>

代码如下:

#include <iostream>
using namespace std;

//类模板
template <class NameType,class AgeType>
class Person
{
public:
    Person(NameType name,AgeType age)
    {
        this->m_Name=name;
        this->m_Age=age;
    }

    void showPerson()
    {
        cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
    }

    NameType m_Name;
    AgeType m_Age;
};

void test01()
{
    Person<string,int> p1("ddd",22);
    p1.showPerson();

}

int main()
{
    test01();
    return 0;
}

输出如下:

1.3.2 类模板成员函数创建时机

  • 普通类:成员函数一开始就可以创建
  • 类模板:成员函数在调用时创建

代码如下:

#include <iostream>
using namespace std;

class Person1
{
public:
    void showPerson1()
    {
        cout<<"Person1"<<endl;
    }
};

class Person2
{
public:
    void showPerson2()
    {
        cout<<"Person2"<<endl;
    }
};

template<class T>
class MyClass
{
public:
    T obj;

    //类模板的成员函数
    void func1()
    {
        obj.showPerson1();
    }

    void func2()
    {
        obj.showPerson2();
    }
};

void test01()
{
    MyClass<Person1> m;
    m.func1();
    //m.func2();//出错

}

int main()
{
    test01();
    return 0;
}

输出如下:

错误示例:

1.3.3 类模板对象做函数参数

类模板实例化出的对象,向函数传参的方式:

  • 指定传入类型:直接显示对象的数据类型(最常用
  • 参数模板化:将对象中的参数变为模板进行传递
  • 整个类模板化:将对象模型,模板化 进行传递

代码如下:

#include <iostream>
using namespace std;

//类模板做函数参数

//类模板
template <class T1,class T2>
class Person
{
public:
    Person(T1 name,T2 age)
    {
        this->m_Name=name;
        this->m_Age=age;
    }

    void showPerson()
    {
        cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
    }

    T1 m_Name;
    T2 m_Age;
};

//指定传入类型:直接显示对象的数据类型
void printPerson1(Person<string,int> &p)
{
    p.showPerson();
}

void test01()
{
    Person<string,int> p("ddd",23);
    printPerson1(p);
}

//参数模板化:将对象中的参数变为模板进行传递
template <class T1,class T2>
void printPerson2(Person<T1,T2> &p)
{
    p.showPerson();
    cout<<"T1 type:"<<typeid(T1).name()<<endl;
    cout<<"T2 type:"<<typeid(T2).name()<<endl;
}

void test02()
{
    Person<string,int> p("qqq",24);
    printPerson2(p);
}

// 整个类模板化:将对象模型,模板化 进行传递
template <class T>
void printPerson3(T &p)
{
    p.showPerson();
    cout<<"T type:"<<typeid(T).name()<<endl;
}

void test03()
{
    Person<string,int> p("fff",25);
    printPerson3(p);
}


int main()
{
    test01();
    test02();
    test03();
    return 0;
}

输出如下:T出数据类型名打印和学习视频中有些出入,应该识别出class Person,string和int。

1.3.4 类模板与继承

当类模板遇到继承时,需要注意以下几点:

  • 当子类继承的父类是一个类模板时,子类在声明时,要指定出父类中T的类型。
  • 如果不指定类型,编译器无法为子类分配内存。
  • 如果想灵活指出父类中的T的类型,子类也需要变为类模板。

代码如下:

#include <iostream>
using namespace std;

//类模板与继承

template<class T>
class Base
{
    T m;
};

//class Son:public Base//错误示例:没有指定T的数据类型
class Son:public Base<int>
{

};

void test01()
{
    Son s1;
}

template<class T1,class T2>
class Son2:public Base<T2>
{
public:
    Son2()
    {
        cout<<"T1的数据类型为:"<<typeid(T1).name()<<endl;
        cout<<"T2的数据类型为:"<<typeid(T2).name()<<endl;
    }
    T1 obj;
};

void test02()
{

    Son2<int,char> s2;
}

int main()
{
    test01();
    test02();
    return 0;
}

输出如下:

错误示例:子类继承类模板父类时没有指定数据类型,所以也无法计算Son类占的内存空间大小

1.3.5 类模板成员函数类外实现

类模板中成员函数类外实现时,需要加上模板参数列表。

代码如下:

#include<iostream>
using namespace std;

//类模板成员函数类外实现

template<class T1,class T2>
class Person
{
public:
    Person(T1 name,T2 age);
    // {
    //     this->m_Name=name;
    //     this->m_Age=age;
    // }

    void showPerson();
    // {
    //     cout<<"name: "<<this->m_Name<<" age: "<<this->m_Age<<endl;
    // }

    T1 m_Name;
    T2 m_Age;
};

//类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name,T2 age)
{
    this->m_Name=name;
    this->m_Age=age;
}

template<class T1,class T2>
void Person<T1,T2>::showPerson()
{
    cout<<"name: "<<this->m_Name<<" age: "<<this->m_Age<<endl;
}

void test()
{
    Person<string,int> p("ddd",22);
    p.showPerson();
}

int main()
{
    test();
    return 0;
}

输出如下:

1.3.6 类模板分文件编写

        类模板中成员函数在调用阶段创建,导致分文件编写时链接不到。

解决方案1:直接包含.cpp源文件

解决方案2:将声明和实现写到同一个文件中,并更改后缀为.hpp,hpp是约定的名称,并不是强制。

解决方案1代码如下:

1-3-6-person.h

#pragma once
#include <iostream>
using namespace std;

template<class T1,class T2>
class Person
{
public:
    Person(T1 name ,T2 age);

    void showPerson();

    T1 m_Name;
    T2 m_Age;

};

1-3-6-person.cpp

#include "1-3-6-person.h"

template<class T1,class T2>
Person<T1,T2>::Person(T1 name ,T2 age)
{
    this->m_Name=name;
    this->m_Age=age;
}

template<class T1,class T2>
void Person<T1,T2>::showPerson()
{
    cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
}

1-3-6.cpp

#include <iostream>
using namespace std;
#include "1-3-6-person.cpp"//解决方案1:成员函数调用时才创建,.h中没有成员函数实现的代码


//类模板分文件编写


void test01()
{
    Person<string,int> p("fff",23);
    p.showPerson();
}

int main()
{
    test01();
    return 0;
}

输出如下:

解决方案2代码如下:

1-3-6-person.hpp

#pragma once
#include <iostream>
using namespace std;

template<class T1,class T2>
class Person
{
public:
    Person(T1 name ,T2 age);

    void showPerson();

    T1 m_Name;
    T2 m_Age;

};

template<class T1,class T2>
Person<T1,T2>::Person(T1 name ,T2 age)
{
    this->m_Name=name;
    this->m_Age=age;
}

template<class T1,class T2>
void Person<T1,T2>::showPerson()
{
    cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
}

1-3-6.cpp

#include <iostream>
using namespace std;

#include "1-3-6-person.hpp"//解决方案2:将.h和.cpp中的内容写到一起,将后缀名改为.hpp文件


//类模板分文件编写


void test01()
{
    Person<string,int> p("fff",23);
    p.showPerson();
}

int main()
{
    test01();
    return 0;
}

输出如下:

1.3.7 类模板与友元

        掌握类模板配合友元函数的类内和类外实现。

  • 全局函数类内实现:直接在类内声明友元即可
  • 全局函数类外实现:需要提前让编译器知道全局函数的存在

代码如下:

#include <iostream>
using namespace std;

//通过全局函数打印Person信息

//全局函数 类外实现:提前让编译器知道Person类的存在
template<class T1,class T2>
class Person;

//全局函数 类外实现
template<class T1,class T2>
void showPerson2(Person<T1,T2> p)
{
    cout<<"类外 name:"<<p.m_Name<<" age:"<<p.m_Age<<endl;
}

template<class T1,class T2>
class Person
{
    //全局函数 类内实现
    friend void showPerson(Person<T1,T2> p)
    {
        cout<<"name:"<<p.m_Name<<" age:"<<p.m_Age<<endl;
    }

    //全局函数 类外实现
    //加空模板参数列表<>(声明是普通函数的声明,实现是函数模板的实现,两套系统不一样,因此声明的时候 增加参数列表变成函数模板的函数声明)
    friend void showPerson2<>(Person<T1,T2> p);

public:
    Person(T1 name ,T2 age)
    {
        this->m_Name=name;
        this->m_Age=age;
    }

private:
    T1 m_Name;
    T2 m_Age;
};

//全局函数 类内实现
void test01()
{
    Person<string,int> p("ddd",23);
    showPerson(p);
}

void test02()
{
    Person<string,int> p("fff",25);
    showPerson2(p);
}

int main()
{
    test01();
    test02();
    return 0;
}

输出如下:

1.3.8 类模板案例

案例描述:实现一个通用的数组类,要求如下:

  • 可以对内置数据类型以及自定义数据类型的数据进行存储·将数组中的数据存储到堆区
  • 构造函数中可以传入数组的容量
  • 提供对应的拷贝构造函数以及operator=防止浅拷贝问题
  • 提供尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素个数和数组的容量

代码如下:

1-3-8-MyArray.hpp

#pragma once
#include <iostream>
using namespace std;


template<class T>
class MyArray
{
public:
    //有参构造
    MyArray(int capacity)
    {
        this->m_Capacity=capacity;
        this->m_Size=0;
        this->pAddress=new T[this->m_Capacity];
        cout<<"有参构造"<<endl;
    }

    //拷贝构造
    MyArray(const MyArray& arr)
    {
        this->m_Capacity=arr.m_Capacity;
        this->m_Size=arr.m_Size;
        //深拷贝
        this->pAddress=new T[arr.m_Capacity];
        //将arr中的数据拷贝
        for(int i=0;i<this->m_Size;i++)
        {
            this->pAddress[i]=arr.pAddress[i];
        }
        cout<<"拷贝构造"<<endl;
    }

    //=运算符重载 防止浅拷贝的问题(堆区重复释放)
    MyArray& operator=(const MyArray& arr)
    {
        //先判断原堆区是否有数据,若有先释放
        if (this->pAddress!=NULL)
        {
            delete [] this->pAddress;
            this->pAddress=NULL;
            this->m_Capacity=0;
            this->m_Size=0;
        }

        this->m_Capacity=arr.m_Capacity;
        this->m_Size=arr.m_Size;
        //深拷贝
        this->pAddress=new T[arr.m_Capacity];
        //将arr中的数据拷贝
        for(int i=0;i<this->m_Size;i++)
        {
            this->pAddress[i]=arr.pAddress[i];
        }
        cout<<"operator="<<endl;
        return * this;
    }

    //尾插法
    void PushBack(const T& val)
    {
        //判断容量
        if(this->m_Capacity==this->m_Size)
        {
            return;
        }
        this->pAddress[this->m_Size]=val;
        this->m_Size++;
    }

    //尾删法
    void PopBack()
    {
        if(this->m_Size==0)
        {
            return;
        }
        this->m_Size--;
    }

    //通过下标访问:重载[]
    T& operator[](int index)
    {
        return this->pAddress[index];
    }

    int getCap()
    {
        return this->m_Capacity;
    }
    int getSize()
    {
        return this->m_Size;
    }


    //析构函数
    ~MyArray()
    {
        if (this->pAddress!=NULL)
        {
            delete [] this->pAddress;
            this->pAddress=NULL;
        }    
        cout<<"析构函数"<<endl;    
    }
private:
    T * pAddress;
    int m_Capacity;

    int m_Size;

};

1-3-8.cpp

#include <iostream>
using namespace std;
#include "1-3-8-MyArray.hpp"

void printArry(MyArray<int>& arr)
{
    for(int i=0;i<arr.getSize();i++)
    {
        cout<<arr[i]<<endl;
    }
}

void test()
{
    //测试构造和析构
    MyArray<int> ma1(12);

    //测试拷贝构造
    MyArray<int> ma2(ma1);

    MyArray<int> ma3(100);
    //测试operate=
    ma3=ma1;
}

void test01()
{
    MyArray<int> arr1(4);
    for(int i=0;i<4;i++)
    {
        //插入数据
        arr1.PushBack(i);
    }

    cout<<"数组arr1为:"<<endl;
    printArry(arr1);

    cout<<"数组arr1的容量为:"<<arr1.getCap()<<endl;
    cout<<"数组arr1的大小为:"<<arr1.getSize()<<endl;

    MyArray<int> arr2(arr1);
    cout<<"数组arr2为:"<<endl;
    printArry(arr2);
    //删除数据
    arr2.PopBack();
    cout<<"数组arr2尾删后为:"<<endl;
    printArry(arr2);
}

//自定义数据类型
class Person
{
public:
    Person(){}
    Person(string name ,int age)
    {
        this->m_Name=name;
        this->m_Age=age;
    }

    string m_Name;
    int m_Age;

};

void printPerson(MyArray<Person> & p)
{
    for(int i=0;i<p.getSize();i++)
    {
        cout<<"name:"<<p[i].m_Name<<" age:"<<p[i].m_Age<<endl;
    }
}

void test02()
{
    MyArray<Person> buf1(4);

    Person p1("aaa",11);
    Person p2("bbb",31);
    Person p3("ccc",22);
    Person p4("ddd",12);
    buf1.PushBack(p1);
    buf1.PushBack(p2);
    buf1.PushBack(p3);
    buf1.PushBack(p4);

    printPerson(buf1);

    cout<<"数组buf1的容量为:"<<buf1.getCap()<<endl;
    cout<<"数组buf1的大小为:"<<buf1.getSize()<<endl;

    MyArray<Person> buf2(buf1);
    cout<<"数组buf2为:"<<endl;
    printPerson(buf2);
    //删除数据
    buf2.PopBack();
    cout<<"数组buf2尾删后为:"<<endl;
    printPerson(buf2);

}



int main()
{
    test();
    test01();
    test02();
    return 0;
}

输出如下:

1.4 类模板与函数模板的区别

  • 类模板没有自动类型推导
  • 类模板在模板参数列表中可以有默认参数

PS:据说版本问题,目前类模板也有自动类型推导,函数模板也有默认参数了。 

代码如下:

#include <iostream>
using namespace std;
#include <string>

//类模板与函数模板的区别
template <class NameType,class AgeType=int>//设置参数列表中的AgeType默认数据类型为int
class Person
{
public:
    Person(NameType name,AgeType age)
    {
        this->m_Name=name;
        this->m_Age=age;
    }

    void showPerson()
    {
        cout<<"name:"<<this->m_Name<<" age:"<<this->m_Age<<endl;
    }

    NameType m_Name;
    AgeType m_Age;
};

void test01()
{
    //Person p1("ddd",22);//类模板没有自动类型推导
    Person<string,int> p1("ddd",22);
    p1.showPerson();

    Person<string> p2("fff",22);//类模板在模板参数列表中可以有默认参数
    p2.showPerson();
}

int main()
{
    test01();
    return 0;
}

输出如下:

错误示例:类模板没有自动类型推导

2 STL初识

2.1 STL的基本概念

        大多数情况下,数据结构和算法都没有一套标准,导致被迫从事大量重复工作,而C++的面向对象(封装、继承、多态)和泛型编程思想的目的就是提升复用性。因此,为建立数据结构和算法的一套标准,诞生了STL。

        STL(standard template library)称为标准模板库,从广义上分为容器(container)算法(algorithm)迭代器(iterator)。容器和算法之间通过迭代器进行连接。STL中几乎所有的代码使用的都是模板类或模板函数。

STL六大组件:

  • 容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据。
  • 算法:各种常用的算法,如sort、find、copy、for_each等。
  • 迭代器:扮演容器与算法之间的胶合剂。
  • 仿函数:行为类似函数,可以作为算法的某种策略。
  • 适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。
  • 空间配置器:负责空间的配置与管理。

 容器:

STL容器将运用最广泛的一些数据结构实现出来

常用的数据结构:数组、链表、树、栈、队列、集合、映射表等

容器可分为序列式容器和关联式容器。

  • 序列式容器:强调值的排序,容器中每个元素都有固定的位置。
  • 关联式容器:二叉树结构,各元素之间没有严格物理上的顺序关系。

算法:

算法通过有限的步骤解决逻辑或数学上的问题。

算法分为质变算法和非质变算法:

质变算法:运算过程中会更改区间内元素的内容。如拷贝、替换、删除等。

非质变算法:运算过程中不会更改区间内元素的内容。如查找、计数、遍历、寻找极值等。 

 迭代器:

提供一种方法,使其能依序寻访某个容器所含的各元素(算法要通过迭代器才能访问容器中的元素),而无需暴露该容器的内部表示方式。每个容器都有自己专属的迭代器,迭代器的使用类似于指针。

迭代器种类:

常用的容器中狄俄代其种类为双向迭代器和随机访问迭代器。

2.2 vector存放内置数据类型

STL中最常用的容器为vector,可以理解为数组,下面学习如何向容器中插入数据并遍历容器。

容器:vector

算法:for_each

迭代器:vector<int>::iterator

代码如下:

#include <iostream>
using namespace std;
#include <vector>
#include <algorithm>

void myPrint(int val)
{
    cout<<val<<endl;
}

//vector容器存放内置数据类型并操作数据
void test01()
{
    //创建一个vector容器/数组
    vector<int> v;

    //向容器中插入数据
    v.push_back(1);
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);

    //通过迭代器访问容器中的数据
    vector<int>::iterator itBegin=v.begin();//起始迭代器  指向容器中第一个元素
    vector<int>::iterator itEnd=v.end();

    //第一种遍历方式
    while (itBegin!=itEnd)
    {
        cout<<*itBegin<<endl;
        itBegin++;
    }

    //第二种遍历方式
    for(vector<int>::iterator it=v.begin();it!=v.end();it++)
    {
        cout<<*it<<endl;
    }

    //第3种遍历方式
    for_each(v.begin(),v.end(),myPrint);
    
}

int main()
{
    test01();
    return 0;
}

输出如下:

2.3 vector存放自定义数据类型

代码如下:

#include <iostream>
using namespace std;
#include <vector>
#include <string>

//vector容器存放自定义数据类型

class Person
{
public:
    Person(string name,int age)
    {
        this->m_Name=name;
        this->m_Age=age;
    }
    string m_Name;
    int m_Age;
};

void test01()
{
    vector<Person> v;
    Person p1("aaa",11);
    Person p2("bbb",12);
    Person p3("ccc",14);
    Person p4("ddd",15);
    Person p5("eee",16);
    Person p6("fff",17);

    //向容器中添加数据
    v.push_back(p1);
    v.push_back(p2);
    v.push_back(p3);
    v.push_back(p4);
    v.push_back(p5);
    v.push_back(p6);

    //遍历容器中的数据
    for(vector<Person>::iterator it=v.begin();it!=v.end();it++)
    {
        cout<<"name:"<<(*it).m_Name<<" age:"<<(*it).m_Age<<endl;
    }
}

//vector容器存放自定义数据类型 数组
void test02()
{
    vector<Person*> v;
    Person p1("aaa",11);
    Person p2("bbb",12);
    Person p3("ccc",14);
    Person p4("ddd",15);
    Person p5("eee",16);
    Person p6("fff",17);

    //向容器中添加数据
    v.push_back(&p1);
    v.push_back(&p2);
    v.push_back(&p3);
    v.push_back(&p4);
    v.push_back(&p5);
    v.push_back(&p6);

    //遍历容器中的数据
    for(vector<Person*>::iterator it=v.begin();it!=v.end();it++)
    {
        cout<<"name:"<<(*it)->m_Name<<" age:"<<(*it)->m_Age<<endl;
    }
}

int main()
{
    test01();
    test02();
    return 0;
}

输出如下:

2.4 容器嵌套容器

代码如下:

#include <iostream>
using namespace std;
#include <vector>

//vector容器嵌套容器
void test01()
{
    vector<vector<int>> v;

    //创建小容器
    vector<int> v1;
    vector<int> v2;
    vector<int> v3;
    vector<int> v4;

    //向小容器添加数据
    for(int i=0;i<4;i++)
    {
        v1.push_back(i+1);
        v2.push_back(i+2);
        v3.push_back(i+3);
        v4.push_back(i+4);
    }

    //将小容器插入到大容器
    v.push_back(v1);
    v.push_back(v2);
    v.push_back(v3);
    v.push_back(v4);

    //通过大容器遍历所有数据
    for(vector<vector<int>>::iterator it=v.begin();it!=v.end();it++)
    {
        // *it是一个vector<int>类型的容器
        for(vector<int>::iterator vit=(*it).begin();vit!=(*it).end();vit++)
        {
            cout<<*vit<<" ";
        }
        cout<<endl;
    }
}

int main()
{
    test01();
    return 0;
}

输出如下:

3 容器

3.1 string容器

string是C++风格的字符串,而string本质上是一个类。

string和char*的区别:

  • char*是一个指针。
  • string是一个类,类内部封装了char*,管理整个字符串,是一个char*型的容器。

string的特点:

  • string内部封装了很多成员方法,如查找find、删除delete、替换replace、插入insert。
  • string管理char*所分配的内存,不用担心赋值越界和取值越界,由类内部进行负责。 

3.1.1 构造函数

构造函数原型:

  • string();                           //创建一个空的字符串,如stringstr;
  • string(const char* s);     //使用字符串s初始化
  • string(const string& str);  //使用一个string对象初始化另一个string对象
  • string(int n,char c);         //使用n个字符c初始化

代码如下:

#include <iostream>
using namespace std;
#include <string>

//string容器的构造函数
// string();                           //创建一个空的字符串,如stringstr;
// string(const char* s);     //使用字符串s初始化
// string(const string& str);  //使用一个string对象初始化另一个string对象
// string(int n,char c);         //使用n个字符c初始化
void test01()
{
    string s1;//默认构造

    const char* str="hello C++";
    string s2(str);
    cout<<"s2="<<s2<<endl;

    string s3(s2);
    cout<<"s3="<<s3<<endl;

    string s4(10,'d');
    cout<<"s4="<<s4<<endl;
}

int main()
{
    test01();
    return 0;
}

输出如下:

3.1.2 赋值函数

string容器的赋值操作

  • string& operator=(const char* s);        //char*类型字符串赋值给当前字符串
  • string& operator=(const string& s);      //把字符串s赋值给当前字符串
  • string& operator=(char c);               //字符赋值给当前字符串
  • 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赋值给当前字符串

代码如下:

#include <iostream>
using namespace std;
#include <string>

// string容器的赋值操作
// string& operator=(const char* s);        //char*类型字符串赋值给当前字符串
// string& operator=(const string& s);      //把字符串s赋值给当前字符串
// string& operator=(char c);               //字符赋值给当前字符串
// 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赋值给当前字符串
void test01()
{
    string str1;
    str1="hello C++";
    cout<<"str1="<<str1<<endl;

    string str2;
    str2=str1;
    cout<<"str2="<<str2<<endl;

    string str3;
    str3='f';
    cout<<"str3="<<str3<<endl;

    string str4;
    str4.assign("hello fff");
    cout<<"str4="<<str4<<endl;

    string str5;
    str5.assign("hello hhh",4);
    cout<<"str5="<<str5<<endl;

    string str6;
    str6.assign(str1);
    cout<<"str6="<<str6<<endl;

    string str7;
    str7.assign(10,'w');
    cout<<"str7="<<str7<<endl;
}


int main()
{
    test01();
    return 0;
}

输出如下:

3.1.3 字符串拼接

string& operator+=(const char* s);               //+=运算符重载

string& operator+=(const string& s);             //+=运算符重载

string& operato+r=(char c);                          //+=运算符重载

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

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

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

string& append(const string& s,int pos,int n);   //把n个字符c赋值给当前字符串

代码如下:

#include <iostream>
using namespace std;
#include <string>

//string容器的字符串末尾拼接字符串的算法
// string& operator+=(const char* s);               //+=运算符重载
// string& operator+=(const string& s);             //+=运算符重载
// string& operato+r=(char c);                      //+=运算符重载
// string& append(const char* s);                   //把字符串s拼接到当前字符串末尾
// string& append(const char* s,int n);             //把字符串s的前n个字符拼接到当前字符串末尾
// string& append(const string& s);                 //把字符串s赋值给当前字符串,同operator+=(const string& s);
// string& append(const string& s,int pos,int n);   //把n个字符c赋值给当前字符串

void test01()
{
    string str1="pretty ";
    str1+="good ";
    cout<<"str1="<<str1<<endl;

    str1+='a';
    cout<<"str1="<<str1<<endl;

    string str2="fff ";
    str1+=str2;
    cout<<"str1="<<str1<<endl;

    string str3="I ";
    str3.append("love ");
    cout<<"str3="<<str3<<endl;

    str3.append("game abcde",8);
    cout<<"str3="<<str3<<endl;

    str3.append(str2);
    cout<<"str3="<<str3<<endl;

    str3.append("abcdefghijklmnopqrst",4,8);
    cout<<"str3="<<str3<<endl;
}

int main()
{
    test01();
    return 0;
}

输出如下:

3.1.4 字符串查找和替换

代码如下:

#include <iostream>
using namespace std;

//string字符串查找和替换

//查找
void test01()
{
    string str1="abcdefg";

    int pos =str1.find("de",0);
    cout<<"pos="<<pos<<endl;

    pos=str1.rfind("de");
    cout<<"pos="<<pos<<endl;

    //find rfind区别:find从左往右   rfind从右往左找 只是查找方向不同!!!不是下标相反
}

//替换
void test02()
{
    string str2="abcdefg";
    str2.replace(1,3,"1111");
    cout<<"str2:"<<str2<<endl;
}

int main()
{
    test01();
    test02();
    return 0;
}

输出如下:

3.1.5 字符串比较

按ASCII码进行对比:

=返回0

>返回1

<返回-1

代码如下:

#include <iostream>
using namespace std;
#include <string> 

//字符串比较
void test01()
{
    string str1="hello";
    string str2="hedlo";
    if(str1.compare(str2)==0)
    {
        cout<<"str1=str2"<<endl;
    }
    else if(str1.compare(str2)>0)
    {
        cout<<"str1>str2"<<endl;
    }
    else
    {
        cout<<"str1<str2"<<endl;
    }
}

int main()
{
    test01();
    return 0;
}

3.1.6 字符存取

string中单个字符存取方式:

代码如下:

#include <iostream>
using namespace std;
#include <string> 

//string字符存取
void test01()
{
    string str="hello";
    cout<<"str="<<str<<endl;

    //访问字符
    for(int i=0;i<str.size();i++)
    {
        cout<<str[i]<<" ";
    }
    cout<<endl;

    for(int i=0;i<str.size();i++)
    {
        cout<<str.at(i)<<" ";
    }
    cout<<endl;

    //修改字符
    str[0]='x';
    cout<<"str="<<str<<endl;

    str.at(1)='x';
    cout<<"str="<<str<<endl;
}

int main()
{
    test01();
    return 0;
}

输出如下:

3.1.7 字符串插入和删除

代码如下:

#include <iostream>
using namespace std;
#include <string> 

//string字符的插入和删除
void test01()
{
    string str="hello";
    //插入
    str.insert(1,"ddd");
    cout<<"str="<<str<<endl;

    //删除
    str.erase(1,3);
    cout<<"str="<<str<<endl;
}

int main()
{
    test01();
    return 0;
}

输出如下:

3.1.8 子串获取

代码如下:

#include <iostream>
using namespace std;
#include <string> 

//string子串获取
void test01()
{
    string str="hello";
    string substr=str.substr(1,3);
    cout<<"substr="<<substr<<endl;
}

void test02()
{
    string email="zhangsan@sina.com";

    int pos=email.find('@');
    cout<<"pos of @ = "<<pos<<endl;

    string userName=email.substr(0,pos);
    cout<<"username="<<userName<<endl;
}

int main()
{
    test01();
    test02();
    return 0;
}

输出如下:

3.2 vector容器

vector容器和数组相似,称为单端数组。数组是静态的vector容器是可以动态扩展的,扩展的方式不是通过在原空间之后增加新空间,而是另外开辟更大的内存空间,拷贝原数据,并释放原空间。

PS:vector容器的迭代器支持随机访问

3.2.1 构造函数

代码如下:

#include<iostream>
using namespace std;
#include<vector>

//vector容器构造函数

void printVector(vector <int> &v)
{
    for(vector<int>::iterator it =v.begin();it!=v.end();it++)
    {
        cout<<*it<<" ";
    }
    cout<<endl;
}

void test01()
{
    vector<int> v1;
    for(int i=0;i<10;i++)
    {
        v1.push_back(i);
    }
    printVector(v1);

    //通过区间方式进行构造
    vector<int> v2(v1.begin(),v1.end());
    printVector(v2);

    //n个elem方式构造
    vector<int> v3(10,100);//10个100
    printVector(v3);

    vector<int> v4(v3);
    printVector(v4);


}

int main()
{
    test01();
    return 0;
}

输出如下:

3.2.2 赋值操作

3.2.3 容量和大小

3.2.4 插入和删除

3.2.5 数据存取

3.2.6 互换容器

3.2.7 预留空间

3.3 deque容器

3.4 stack容器

3.5 queue容器

3.6 list容器

3.7 set容器

3.8 map容器

4 谓词

5 内建函数对象

6 常见算法

6.1 遍历算法

6.2 查找算法

6.3 排序算法

6.4 拷贝和替换算法

6.5 算术生成算法

6.6 集合算法

7综合案例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值