当前位置: 首页 > news >正文

C++:模板——详解函数模板与类模板

1. 模板的概念

C++的模板(Templates)是泛型编程的基础,它允许编写与类型无关的代码,从而提高代码的复用性和灵活性。通过模板,你可以编写一种通用的函数或类,而不需要为每种特定的数据类型单独定义多个函数或类。这种机制主要分为函数模板类模板

模板就是建立通用的模具,大大提高复用性

2. 函数模板

函数模板可以用于编写可以接受不同类型参数的函数。你定义一次模板,编译器会根据你调用时的参数类型自动生成相应的函数版本。其基本语法如下:

template <typename T>
T add(T a, T b) {return a + b;
}

解释

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

这里的template <typename T>声明了一个模板,T是模板参数,表示一种泛型类型。函数add会根据实际调用时的参数类型推导出T,比如当你传递两个整数时,Tint类型;当你传递两个浮点数时,Tfloat类型:

int x = add(5, 3);       // T是int
double y = add(5.2, 3.8); // T是double

2.1 两种方式使用函数模板

  1. 自动类型推导myswap(a,b);
  2. 显示指定类型myswap<int>(a,b);

注意事项

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

2.2 普通函数与函数模板区别

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

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

  • 如果函数模板和普通函数都可以实现(同名且参数个数相同),优先调用普通函数

  • 可以通过空模板参数列表来强制调用函数模板

    void myPrint(int a,int b){cout<<"调用普通函数"<<endl;
    }template<typename T>
    void myPrint(T a,T b){cout<<"调用模板函数"<<endl;
    }int main(){myPrint(a,b);  //优先调用普通函数myPrint<>(a,b);  //通过空模板参数列表强制调用函数模板
    }
    
  • 函数模板也可以发生重载

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

2.4 模板的局限性

模板并不是万能的,有些特定数据类型,需要用具体化方式做特殊实现

3. 类模板

3.1 基本概念

类模板与函数模板类似,但用于类的定义。使用类模板,可以创建一个通用的类,允许在实例化时为类指定不同的数据类型。其基本语法如下:

template <typename T>
class Box {
private:T value;
public:Box(T v) : value(v) {}T getValue() { return value; }
};

在上面的示例中,Box是一个类模板,T是一个泛型类型。你可以在创建Box对象时为T指定不同的类型:

Box<int> intBox(123);      // T是int
Box<double> doubleBox(45.6); // T是double

3.2 类模板与函数模板区别

  1. 类模板没有自动类型推导的使用方式
  2. 类模板在模板参数列表中可以有默认参数
template<class NameType,class AgeType = int> //模板参数列表有默认参数
class Person {
public:Person(NameType name, AgeType age) {this->name = name;this->age = age;}void showPerson() {cout << "姓名:" << name << ",年龄:" << age << endl;}NameType name;AgeType age;
};
int main()
{//Person p("孙悟空",1000);  //错误,无法用自动类型推导//Person<string, int> p("孙悟空",1000);  //正确,只能用显示指定类型Person<string> p("猪八戒",999);  //由于模板参数列表有默认参数,所以只需要指出string即可,函数模板不能这么用p.showPerson();return 0;
}

3.3 类模板中成员函数创建时机

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

3.4 类模板对象做函数参数

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

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

  1. 指定传入的类型 – 直接显示对象的数据类型(使用广泛

    void printPerson1(Person<string,int>&p){  //指定传入的类型p.showPerson();
    }
    int main()
    {Person<string,int> p("孙悟空",18);printPerson1(p);
    }
    
  2. 参数模板化 - 将对象中的参数变为模板进行传递

    template<class T1,class T2>
    void printPerson2(Person<T1,T2>&p){  //参数模板化p.showPerson();cout << "T1 的类型为:" << typeid(T1).name() << endl;  //得到T1的类型
    }
    int main()
    {Person<string,int> p("白骨精",18);printPerson2(p);
    }
    
  3. 整个类模板化 - 将这个对象类型模板化进行传递

    template<class T>
    void printPerson3( T &p ){    //整个类模板化p.showPerson();
    }
    int main()
    {Person<string,int> p("牛魔王",18);printPerson3(p);
    }
    

3.5 类模板与继承

注意

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型

    template<class T>
    class Base {T m;
    };
    //class Son : public Base { };//报错:必须要知道父类中的T类型才能继承给子类
    class Son : public Base<int> {};
    int main() {Son s1;
    }
    
  • 如果不指定,编译器无法给子类分配内存

  • 如果想灵活指定出父类中T的类型,子类也需变为类模板

    template<class T>
    class Base {T m;
    };template<class T1,class T2>
    class Son2 : public Base<T1> {T2 obj;
    };int main()
    {Son2<int,char> S2;
    }
    

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)   //注意Person后必须写一个模板参数列表
{this->name = name;this->age = age;
}template<class T1, class T2>
void Person<T1, T2>::showPerson()  //   //注意Person后必须写一个模板参数列表
{cout << "name:" << this->name << " age:" << this->age << endl;
}int main() {Person<string,int> p("沙和尚",18);p.showPerson();
}

3.7 类模板分文件编写

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

解决方法:

  • 方法一:直接包含.cpp源文件
  • 方法二:将函数声明和实现写到同一个文件中,并更改后缀名为.hpphpp是约定的名称,并不是强制

3.8 类模板与友元

全局函数类内实现:直接在类内声明友元即可

全局函数类外实现:需要提前让编译器知道全局函数的存在

4. class和typename

在C++模板的声明中,classtypename都是用于定义类型参数的关键字,它们的功能几乎完全相同。也就是说,在模板的上下文中,使用classtypename来表示模板参数时,它们是可以互换的。

template <class T>
T add(T a, T b) {return a + b;
}template <typename T>
T multiply(T a, T b) {return a * b;
}

上面两个模板函数,一个使用class声明类型参数,另一个使用typename,但它们的效果完全相同。T在这两种情况下都表示一个类型参数,编译器会根据你传递的参数类型来推导T的具体类型。

区别

尽管classtypename在大多数情况下是可以互换的,但它们之间有一些细微的区别和约定:

  • 历史原因:在最早的C++标准中,class是最早引入模板时用来定义类型参数的关键字。后来,C++标准委员会为了更加语义化和明确,才引入了typename。因此,class作为模板类型参数的关键字更多是历史遗留,而typename则从语义上更符合“表示类型”的含义。

  • 在嵌套依赖类型中,必须使用typename:在某些复杂的模板表达式中,特别是涉及到嵌套类型时,typename是必须的。例如,处理模板参数中包含的嵌套类型时,你必须用typename来明确告知编译器该嵌套依赖的标识符是一个类型,而不是一个变量或其他符号。

    例如

    template <typename T>
    void func() {typename T::value_type val;  // 必须用typename,表明T::value_type是一个类型
    }
    

    在这里,typename是必需的,因为T::value_type可能是一个类型名称。编译器需要通过typename来确认这个符号确实是一个类型,而不是某个成员变量。

总结

虽然classtypename可以在模板参数中互换,但很多C++开发者倾向于使用typename,因为它更能直观地表达模板参数是一个“类型”的含义。class这个关键字容易让人联想到类,而不是类型(尽管类也是一种类型)。


http://www.mrgr.cn/news/26799.html

相关文章:

  • AI绘画:科技赋能艺术的崭新时代
  • 【Google Chrome Windows 64 version及 WebDriver 版本】
  • MySQL5.7-虚拟列
  • 管理者须知!员工上班玩游戏怎么办?如何有效管理员工上班玩游戏行为?
  • Djourney新手入门基础,AI摄影+AI设计+AI绘画-AIGC作图
  • TCPIP网络编程(尹圣雨)UDP 轮流收发消息(windows)
  • 春日教育技术:SpringBoot在线视频教学
  • NVM 安装 + 配置淘宝镜像
  • 【大模型专栏—进阶篇】语言模型创新大总结——“后起之秀”
  • Python 内置的一些数据结构
  • 轧钢测径仪对热轧产线实现温度系数自动修正!
  • 从头开始学MyBatis—02基于xml和注解分别实现的增删改查
  • Springboot项目打war包运行及错误解决
  • 文献速递 | E3泛素连接酶PELI2介导STING信号激活的阈值设定
  • 每日处理250亿个事件,Canva如何应对数据洪流
  • VSCode 离线安装中文语言包
  • NFT Insider #147:Sandbox 人物化身九月奖励上线;Catizen 付费用户突破百万
  • 云服务器拉取docker镜像
  • Qt 实现自定义截图工具
  • 基于STM32的汽车仪表显示系统:集成CAN、UART与I2C总线设计流程