自学内容网 自学内容网

C++ 模板

目录

 教学视频:05 模板-普通函数与函数模板区别_哔哩哔哩_bilibili

什么是模板?

模板的类型

函数模板语法

模板注意事项

写一个万能数组排序模板案例

隐式类型转换

普通函数调用可以发生隐式类型转换

模板使用自动推导不可以隐式类型转换

模板使用显示指定类型可以隐式类型转换

普通函数和函数模板的调用规则

模板的局限性

类模板

 类模板和函数模板的区别

类模板中的成员函数的创建时机​编辑

类模板对象做函数参数

类模板与继承 

类模板中成员函数的类外实现

分文件编写模板

类模板和友元 


 教学视频:05 模板-普通函数与函数模板区别_哔哩哔哩_bilibili

什么是模板?

模板是另一个编程思想(泛型编程)的一种技术。模板就是建立通用的模具,大大提高复用性 。

生活中的模板:

一寸照片模板:

PPT模板:

  • 模板的使用前提:
    • 模板不能直接使用,需要塞入我们自己的内容。
    • 模板不是万能的(例如不能用PPT模板去套一寸照片)

模板的类型

c++提供两种模板机制:函数模板和类模板。

函数模板语法

写一个案例:

#include<iostream>
#include<algorithm>

void Swap(int a, int b)
{
int temp = a;
b = a;
a = temp;
std::cout <<"a: "<< a << " " <<"b: "<< b << std::endl;
}
void Swap(double a, double b)
{
double temp = a;
b = a;
a = temp;
std::cout << "a: " << a << " " << "b: " << b << std::endl;
}
int main()
{
int a = 10, b = 20;
Swap(a,b);
double c = 10.1, d= 20.1;
Swap(c, d);
return 0;
}

我们发现除了参数类型名不同,其他的功能,函数名(函数名也可以不同)都相同,因为我们可以对函数参数类型模板化,以便复用:

template<typename T>
void Swap(T a, T b)
{
T temp = a;
b = a;
a = temp;
std::cout <<"a: "<< a << " " <<"b: "<< b << std::endl;
}

模板的使用有两种方式:

  • 自动推导
int a = 10, b = 20;
Swap(a,b);
double c = 10.1, d= 20.1;
Swap(c, d);

显示指定

int a = 10, b = 20;
Swap<int>(a,b);
double c = 10.1, d= 20.1;
Swap<double>(c, d);

模板注意事项

  • 必须要推导出一致的数据类型:

还是上面的Swap()模板函数,我们传(int ,char)型实参就报错了:

这是因为编译器编译器发现int类型参数不能和char类型参数交换。

  •  模板必须确定出T的数据类型,才能使用

如下图,由于T未被使用,所以推不出T的数据类型,就会报错:

 如果我们想调func(),就必须给T指定一个类型:

写一个万能数组排序模板案例

#include<iostream>
#include<algorithm>
#include<cstring>

template<typename T>
void mySwap(T* arr,int i,int j)
{
T temp= arr[i];
arr[i] = arr[j];
arr[j] = temp;
}


template<typename T>
void mySort(T arr,int len)
{
for (int i = 0; i < len; i++)
{
int max = i;
for (int j=max+1;j<len;j++)
{
if (arr[max] > arr[j])
{
mySwap(arr,max,j);
}
}
}
}
template<typename T>
void Print(T arr,int len)
{
for (int i = 0; i < len; i++)
{
std::cout << arr[i] << " ";
}
std::cout <<std:: endl;
}

void test1()
{
char charArr[] = "fqlkn";
int len = sizeof(charArr) / sizeof(char);
mySort(charArr,len);
Print(charArr,len);
}

void test2()
{
int intArr[] = { 9,6,4,2,1 };
int len = sizeof(intArr) / sizeof(int);
mySort(intArr,len);
Print(intArr,len);
}
int main()
{
test1();
test2();
return 0;
}

隐式类型转换

C++类型转换:隐式转换和显式转换_c++隐式转换-CSDN博客

  • 普通函数调用可以发生隐式类型转换

这是普通函数调用情况:

#include<iostream>

void add(int a, int b)
{
std::cout << a + b << std::endl;
}
int main()
{
int a = 10, b = 20;
add(a,b);
return 0;
}

下面实现一个整形参数和一个字符型参数相加就产生了隐式类型转换:


void add(int a, int b)
{
std::cout << a + b << std::endl;
}
int main()
{
int a = 10;
char b = 'c';
add(a,b);
return 0;
}

  • 模板使用自动推导不可以隐式类型转换

  • 模板使用显示指定类型可以隐式类型转换

普通函数和函数模板的调用规则

  • 1.

#include<iostream>

void add(int a, int b)
{
std::cout << "普通函数" << std::endl;
}
template<typename T>
void add(T a, T b)
{
std::cout << "模板函数" << std::endl;
}
int main()
{
int a = 10, b = 10;
add(a,b);
return 0;
}

2.

int main()
{
int a = 10, b = 10;
add<>(a,b);
return 0;
}


 

  • 3

template<typename T>
void add(T a, T b)
{
std::cout << "模板函数" << std::endl;
}

template<typename T>
void add(T a, T b, T c)
{
std::cout << "重载模板函数" << std::endl;
}
int main()
{
int a = 10, b = 10, c = 10;
add<>(a,b,c);
return 0;
}

 

  • 4.

void add(int a, int b)
{
std::cout << "普通函数" << std::endl;
}
template<typename T>
void add(T a, T b)
{
std::cout << "模板函数" << std::endl;
}

int main()
{
char a= 'x',b='z';
add(a,b);
return 0;
}

这里如果调用int add(int,int)的话会产生隐式类型转换转换为void add(char,char),如果调模板的话会自动推导为void add(char,char),编译器认为推导比隐式类型转换更轻便,所以就调用模板了。

模板的局限性

像下面的代码虽然表面看起来没有问题,但是一旦我们传递的参数a,b是数组,那么就出问题了,数组不能直接比较:

template<class T>
bool cmp(T& a,T& b)
{
if (a == b)return true;
return false;
}

再或者如果类型是自定义类型的话,是没有办法直接比较的。

这里我们直接比较Perason a,Person b,是会报错的:

#include <iostream>
#include <string>

class Person {
public:
    Person(std::string name, int age) : _name(name), _age(age) {}

private:
    std::string _name;
    int _age;

    // 为了让模板特化的 cmp 函数能够访问私有成员,我们可以将其声明为友元
    // 但通常,更好的做法是使用公共的获取器(getter)函数
    template<typename T>
    friend bool cmp(const T& a, const T& b);
};

// 定义一个模板函数
template<typename T>
bool cmp(const T& a, const T& b) {
    if (a == b)return true;
    return false; // 占位符,实际比较逻辑应该在特化中实现
}



int main() {
    Person a("张三", 10);
    Person b("张三", 10);
    if (cmp(a, b)) std::cout << "a==b" << std::endl;
    else std::cout << "a!=b" << std::endl;
    return 0;
}

我们对cmp()函数进行Person类模板特化:

只需要在cmp()函数前加上template<>即可,然后在cmp()内重写比较规则,就可以通过模板对Person类的对象间进行比较了: 

#include <iostream>
#include <string>

class Person {
public:
    Person(std::string name, int age) : _name(name), _age(age) {}

private:
    std::string _name;
    int _age;

    // 为了让模板特化的 cmp 函数能够访问私有成员,我们可以将其声明为友元
    // 但通常,更好的做法是使用公共的获取器(getter)函数
    template<typename T>
    friend bool cmp(const T& a, const T& b);
};

// 定义一个模板函数
template<typename T>
bool cmp(const T& a, const T& b) {
    // 这里应该有一个通用的比较方式,但因为我们只关心 Person 类型,所以这里留空
    // 在实际使用中,您可能需要为其他类型提供适当的比较逻辑
    return false; // 占位符,实际比较逻辑应该在特化中实现
}

// 为 Person 类特化模板函数
template<> bool cmp(const Person& a, const Person& b) {
    return a._name == b._name && a._age == b._age;
}

int main() {
    Person a("张三", 10);
    Person b("张三", 10);
    if (cmp(a, b)) std::cout << "a==b" << std::endl;
    else std::cout << "a!=b" << std::endl;
    return 0;
}

类模板

类模板是为了不想一开始就确定类内成员变量的类型,需要在传参时再确定类型值。

类模板和函数模板很相似,只需要声明template后紧跟着写一个类即可。

如下所示,我在传实参的时候才确定name类型为string,age类型为int:

#include <iostream>
#include <string>

template<class One, class Two>
class Person {
public:
    // 构造函数中的参数需要指定类型
    Person(One name, Two age)
        : _name(name), _age(age) {}

    void Print() const { // 添加const使得Print可以在const对象上调用
        std::cout << _name << " " << _age << std::endl;
    }

private:
    One _name;
    Two _age;
};

int main() {
    Person<std::string, int> person("张三", 18); // 使用更具描述性的变量名
    person.Print(); // 调用Print函数以看到输出
    return 0;
}

 类模板和函数模板的区别

1. 2.

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

例如:下面这样的代码是可以正常编译执行的,这个obj即调用Person1的成员函数,又调Person2的成员函数,但是此时obj没有被调用,因此不能确定obj的类型:

#include<iostream>

class Person1
{
public:
void showPerson1()
{
std::cout << "showPerson1" << std::endl;
}
};
class Person2
{
public:
void showPerson2()
{
std::cout << "showPerson2" << std::endl;
}
};
template<class T>
class testPerson
{
public:
T obj;

void fun1()
{
obj.showPerson1();
}

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

int main()
{


}

现在我们调用obj,就会报错:

 因为我们调用的时候确定obj的数据类型是Person1了,所以调Person2的成员函数fun2()就会出错。

类模板对象做函数参数

 先写一个Person模板类,再写一个Print打印函数:


template<class T1, class T2>
class Person
{
public:
Person(T1 name,T2 age)
{
_name = name;
_age = age;
}
void showPerson()
{
std::cout << this->_name << " " << _age << std::endl;
}
private:
T1 _name;
T2 _age;
};
void Print()
{
}
int main()
{

Person<std::string,int> p("张三",10);
return 0;
}

1. 直接显示参数类型

void Print(Person<std::string, int>& p)
{
p.showPerson();
}
int main()
{

Person<std::string,int> p("张三",10);
Print(p);
return 0;
}

2.参数类型模板化,让编译器去推参数类型

template<class T1, class T2>
void Print(Person<T1, T2>& p)
{
p.showPerson();
std::cout << "T1的类型: " << typeid(T1).name() << std::endl <<"T2的类型: "<< typeid(T2).name() << std::endl;
}
int main()
{

Person<std::string, int> p("张三", 10);
Print(p);
return 0;
}

 3.整体模板化,让编译器去推类的类型:

template<class T>
void Print(T& p)
{
p.showPerson();
std::cout<< "T的类型: " << typeid(T).name() << std::endl;
}
int main()
{

Person<std::string,int> p("张三",10);
Print(p);
return 0;
}

类模板与继承 

1.父类是模板类,子类继承父类的成员,但是因为父类是模板类型,无法计算出成员大小。所以子类需要指定父类类型,才能继承:

3.

假设我子类也不想一开始就确定类型,也想使用模板,那我们就可以给子类模板化声明两个模板参数,一个指定父类的类型,一个指定子类的类型。

#include<iostream>
template<class T>
class Base
{
public:
T a;
};
template<class T1, class T2>
class Son :public Base<T2>
{
public:
T1 b;
void Print()
{
std::cout << "父类类型: " << typeid(T1).name() << " " << "子类类型: " << typeid(T2).name() << std::endl;
}
};


int main()
{
Son<int, char> s;
s.Print();
return 0;
}

类模板中成员函数的类外实现

如下所示这样一个类:

#include <iostream>
#include <string>

template<class One, class Two>
class Person {
public:
    // 构造函数中的参数需要指定类型
    Person(One name, Two age)
    {
        _name = name;
        _age = age;
    }


    void Print() const { // 添加const使得Print可以在const对象上调用
        std::cout << _name << " " << _age << std::endl;
    }

private:
    One _name;
    Two _age;
};

现在我们想把这个模板类的成员函数的定义放在类外,先说构造函数Person()如何类外定义:

再说Print(),按照以往经验,我们的Print()函数如果在类外定义应该这样写:

void Person::Print()
{
    std::cout << _name << " " << _age << std::endl;
}

 但是呢,因为此时我们的Person类是一个模板类,所以不能这样写了,我们还是得仿照Person()的方法写:

template<class One,class Two>
void Person<One,Two>::Print()
{
    std::cout << _name << " " << _age << std::endl;
}

完整代码

#include <iostream>
#include <string>

template<class One, class Two>
class Person {
public:
    Person(One name, Two age);

    void Print();

private:
    One _name;
    Two _age;
};
template<class One,class Two>
Person<One, Two>::Person(One name,Two age)
{
    _name = name;
     _age = age;
}

template<class One,class Two>
void Person<One,Two>::Print()
{
    std::cout << _name << " " << _age << std::endl;
}

int main() {
    Person<std::string, int> person("张三", 18); 
    person.Print();
    return 0;
}

分文件编写模板

还是上面的Person模板类,我们将其声明定义分离。声明放Person.h文件,定义放Person.cpp文件。main函数文件负责调用:

Person.h

#pragma once
#include <iostream>
#include <string>

template<class One, class Two>
class Person {
public:
    Person(One name, Two age);

    void Print();

private:
    One _name;
    Two _age;
};

Person.cpp

#include"Person.h"
template<class One, class Two>
Person<One, Two>::Person(One name, Two age)
{
    _name = name;
    _age = age;
}

template<class One, class Two>
void Person<One, Two>::Print()
{
    std::cout << _name << " " << _age << std::endl;
}

 main.cpp

#include"Person.h"

int main() {
    Person<std::string, int> person("张三", 18); 
    person.Print();
    return 0;
}

此时编译是没有问题的: 

但是一旦我们运行,就会报错: 

这实际上报的链接错误。

原因

main.cpp包含Person.h文件,所以编译器会先看到模板类的声明。编译器会去其他文件中(这里就是Person.cpp)找该模板的定义,又因为模板类没有确定的类型(在调用的时候才会实例化),那么在其他Person.cpp文件中就无法看到模板的定义,从而无法生成模板实例,导致链接错误。

如果先包含Person.cpp文件,在cpp文件中又包含了Person.h文件,在预编译阶段会头文件展开,那么声明和定义就在一个文件中了。

解决方案1

把main.cpp文件中包含的"Person.h"改为"Person.cpp"就可以正常运行了:

#include"Person.cpp"

int main() {
    Person<std::string, int> person("张三", 18); 
    person.Print();
    return 0;
}

解决方案2

把模板类声明和定义写到一个文件里,该文件后缀改为.hpp(约定俗称,也可以用其他后缀名):

#include"Person.hpp"

int main() {
    Person<std::string, int> person("张三", 18); 
    person.Print();
    return 0;
}

类模板和友元 

全局函数类内实现

写一个Person模板类:

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

然后我们写一个打印函数,这个打印函数我们给它定位全局函数,但是我们要让它在Person类内进行实现,并且让它打印Person的两个私有成员变量,那么我们就要让它作为Peron类的友元函数。


template<class T1,class T2>
class Person
{
friend void Print(Person<T1,T2> p)
{
cout << p._name << " " <<p._age << endl;
}
public:
Person(T1 name,T2 age)
{
_name = name;
_age = age;
}
private:
T1 _name;
T2 _age;
};

int main()
{
Person<string, int>p("张三", 10);
Print(p);
return 0;
}

全局函数类外实现

我们把Print()定义放到外面:

template<class T1,class T2>
class Person
{
friend void Print(Person<T1, T2> p);

public:
Person(T1 name,T2 age)
{
_name = name;
_age = age;
}
private:
T1 _name;
T2 _age;
};
template<class T1,class T2>
void Print(Person<T1, T2> p)
{
cout << p._name << " " << p._age << endl;
}
int main()
{
Person<string, int>p("张三", 10);
Print(p);
return 0;
}

上面的代码看着没什么问题,编译也没问题,但是一运行就报链接错误了:

这是因为Print()的声明是一个普通函数的声明:

 但是它的定义确实一个模板函数的定义:

编译器认为这是两套东西,找不到定义。

因此我们需要给声明加上空模板列表:

但是这样一来又有新的错误了:

这是因为全局函数的定义是在类外定义的,那么需要提前让编译器知道这个函数的存在。(也就是需要先让编译器看到该全局函数的定义)。我们只需要把定义放到最上方即可:

因为Print()中用到了Person类,因此我们需要告诉编译器我们有一个Person类:

 

又因为Person是一个模板类,因此我们还要告诉编译器这是一个模板类:

完整代码

#include<iostream>
#include<string>
using namespace std;
template<class T1,class T2>
class Person;
template<class T1, class T2>
void Print(Person<T1, T2> p)
{
cout << p._name << " " << p._age << endl;
}
template<class T1,class T2>
class Person
{
friend void Print<>(Person<T1, T2> p);

public:
Person(T1 name,T2 age)
{
_name = name;
_age = age;
}
private:
T1 _name;
T2 _age;
};

int main()
{
Person<string, int>p("张三", 10);
Print(p);
return 0;
}


原文地址:https://blog.csdn.net/m0_65143871/article/details/143822450

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!