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<<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值