C++模版复习

Sometime i feel tried

But all my scars wil be my tatoo

C++除了面向对象编程的思想 , 还有一种思想叫做泛型编程,主要利用的技术就是模版

1. 模版

1.1 模版的概念

模版的特点

  • 模版不可以直接使用,它只是一个框架
  • 模版的通用并不是万能的
  • C++提供两种模版机制:函数模版 和 类模版

1.2 函数模版

1.2.1 函数模版的作用 :

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

语法:

//函数声明或定义  
template<typename T>
函数

解释:

template --- 声明创建模版
typename --- 其后面的符号是一种数据类型 , 可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母

//函数模版声明
template<typename T>
void mySwap( T &a,  T &b )
{
    T temp = a;
    a = b;
    b = temp;
}
//函数模版的使用
1.自动类型推导
  mySwap(a,b); 

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

1.2.2 函数模版注意事项

注意事项:

  • 自动类型推导 , 必须推导出一致的数据类型T,才可以使用
  • 模版必须要确定出T的数据类型,才可以使用
template <class T>
void func(){ };

//erorr   func();  因为上面函数使用了函数模版,但是没有指明T的类型(没有使用T)

1.2.3 函数模版案例

详见Clion 用了选择排序法

1.2.4 普通函数与函数模版的区别

普通函数与函数模版区别 :

myAddSum1()---普通函数     myAddSum2()----函数模版
  • 普通函数调用时可以发生自动类型转换(隐式类型转换)

    int a = 10; int b = 20; char c = 'c'; //a-97  c-99
    cout<< myAddSum1(a,c)<<endl; //发生隐式类型转换
    
  • 函数模版调用时,如果利用自动类型推导,不会发生隐式类型转换

    cout<<myAddSum2(a,c)<<endl; //❌会报错error 因为不会发生隐式类型转换
    
  • 如果利用显示指定类型的方式,可以发生隐式类型转换

    cout<<myAddSum2<int>(a,c)<<endl;//✅不会报错
    

    总结 : 建议使用显示指定类型的方式,调用函数模版,因为可以自己确定通用类型 T

1.2.5 普通函数与函数模版的调用规则

调用规则:

myPrint()---普通函数。 myPrint()---函数模版
  • 如果函数模版和普通函数都可以实现,优先调用普通函数✅

    myPrint(a,b);----调用普通函数
    
  • 可以通过空模版参数列表来强调函数模版

    myPrint<>(a,b);----调用函数模版
    
  • 函数模版可以发生重载

  • 如果函数模版可以产生更好的匹配,优先调用函数模版

    总结:实际开发中,最好不要同时提供,否则容易出现二义性

1.2.6模版的局限性

局限性:

  • 模版的通用性并不是万能的
//当想要实现比较的一个函数

//利用具体化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;
    }
}

总结:

  • 利用具体化的模版,可以解决自定义类型的通用化
  • 学习模版并不是为了写模版,而是在STL中能够运用系统提供的模版

1.3类模版

1.3.1类模版语法

类模版作用:

  • 建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表

语法:

template<typename T> ······就不介绍了,同上
类

示例:

template<class N,class A>
class Person
{
  public:
  N Name;
  A Age;
  Person(N name,A age)
  {
     this.Name=name;
     this.Age=age;
  }
}
//实现调用
Person<string,int>person("Kolin",999);

1.3.2类模版与函数模版区别

🌟类模版与函数模版区别主要有两点:

  • 类模版没有自动类型推导的使用方式
//详细🔎见上
Person p("Kolin",1000);----error,无法自动类型推导

Person<string,int>p("Kolin",1000);----正确,只能用显示指定类型
  • 类模版在模版参数列表中可以有默认参数
template<class N,class A = int>‼️
class Person
{
  ·····
}

Person<string>p("Kolin" ,999);‼️

总结:

  • 🌟类模版使用只能用显示指定类型方式
  • 🌟类模版中的模版参数列表可以有默认参数

1.3.3 类模版中成员函数创建时机

类模版中成员函数和普通类中成员函数创建时机是有区别的:

  • 普通类中的成员函数一开始就可以创建
  • 类模版中的成员函数在调用时才创建

1.3.4类模版对象做函数参数

学习目标:

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

一共三种传入方式:

  • 指定传入的类型---直接显示对象的数据类型(最常用🌟)

    //详细🔎前提见上
    void printPerson1(Person<string,int> &p)   <---指定传入类型
    {
         p.showPerson();
    }
    
    Person<string ,int>p("Kolin",100);
    printPerson1(p);
    
  • 参数模版化 ---将对象中的参数变为模版进行传递

//详细🔎前提见上
template<class T1,class T2>
void printPerson2(Person<T1,T2> &p)   <---参数模版化 
{
     p.showPerson();
  //补充 查看模版中推导出的类型👇
  cout<<"T1的类型:"<<typeid(T1).name()<<endl;
  cout<<"T2的类型:"<<typeid(T2).name()<<endl;
}

Person<string ,int>p("Acher",90);
printPerson2(p);
  • 整个类模版化 ---将这个对象类型 模版化进行传递
//详细🔎前提见上
template<class T>
void printPerson3(T &p)   <---整个类模版化
{
     p.showPerson();
    //补充 查看模版中推导出的类型👇
  cout<<"T的类型:"<<typeid(T).name()<<endl;
}

Person<string ,int>p("Napcats",30);
printPerson3(p);

1.3.5类模版与继承

当类模版碰到继承时,需要注意一下几点:

  • 子类继承父类是一个类模版时,子类在声明的时候,要指定出父类中T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果想灵活指定出父类中T的类型,子类也需变为类模版
//父类类模版
template<class T>
class Base
{
  T m;
};

//子类类模版
class Son: public Base<int>    <---必须指定父类中T的类型
{
  
};
//如果想灵活指定父类中的T类型,子类也需变成类模版
template<class T1,class T2>
class Son2 : public Base<T2>
{
    T1 obj;
};

//实例化
Son2<int,char>S2;

总结:如果父类是类模版,子类需要指定出父类中T的数据类型,不然不给分配内存空间

1.3.6类模版成员函数类外实现

类模版:

template <class T1,class T2>
class Person
{
public:
  	Person(T1 name,T2 age);//构造函数未实现
  	 
  	void showPerson();//成员函数未实现
    
    T1 Name;//成员属性
  	T2 Age;
  
}

类模版的构造函数的类外实现:

template<class T1,class T2>🌟
Person<T1,T2>::Person(T1 name, T2 age)  //为了体现是模版,作用域要加上模版的参数列表🌟
{
     this.Name=name;
  	 this.Age=age;
}

类模版的成员函数的类外实现:

template<class T1,class T2>
void Person<T1,T2>::showPerson()  //为了体现是模版,作用域要加上模版的参数列表🌟
{
  cout<<"·····"<<endl;
}

总结:类模版的函数,要在外部实现,比起平常的(作用域::) ,还要加上类模版的参数列表(作用域<T···>::)

1.3.7类模版分离式编译

问题:

  • 类模版中成员函数创建时机是在调用阶段,导致份文件编写时链接🔗不到

解决:

  • 解决方式1:直接包含.cpp源文件
  • 解决方式2(常用🌟):将声明和实现写到同一个文件中,并更改后缀名为.hpp , hpp是约定的名称,并不是强制

只能写个人理解了:

  • 就是类模版中成员函数创建时机是在调用阶段,所以直接引入.h会引发链接错误
  • 所以用上面两个解决方法来实现
  • 第一种是直接引入.cpp在主函数上声明,这样可以解决无法连接到函数实现的问题
  • 第二种方法是把.h .cpp直接写成一个,命名后缀改为.hpp,这个就是相当于类模版分离式编译专用nickname了

1.3.8类模版与友元

哭了 辛苦写的不小心复制粘贴全没了(还有上一节)

就这样吧 呜呜呜呜呜呜呜 幸好还有版本控制呜呜呜呜😭

就说一点个人理解吧:

  • friend友元函数在类内是全局函数,不属于类,不是成员函数
  • 如果要在外部实现全局函数(配合类模版),要先声明让编译器知道,还要给类内声明加空参列表(<>)·····
  • 详细二次复习还是再看看视频,加深理解🔎

posted on 2022-06-04 16:26  AcherLoveCode  阅读(24)  评论(0编辑  收藏  举报

导航