c++模板初阶----函数模板与类模板

简介: class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;


c++的模板大致可以分为:

函数模板
类模板
首先在我们引入模板之前,先进行介绍泛型编程

泛型编程
在c语言中如果让你写一个swap函数

那么在c语言中我们可以这样写

// 交换两个整型
void swap_int(int p1, int p2)
{
int tmp = p1; p1 = p2; p2 = tmp;
}
// 交换两个双精度浮点型
void swap_double(double p1, double p2)
{
double tmp = p1; p1 = p2; p2 = tmp;
}

在c++中我们知道由函数重载这一特点,但是在c语言中这并没有,所以在对应不同类型的值进行交换时,会对应要有对应的函数。并且要注意进行址传递,而不是值传递。

而在后来的c++引入函数重载后,swap又可以进行这样的书写代码

// 交换两个整型
void swap(int& p1, int& p2)
{
int tmp = p1;
p1 = p2;
p2 = tmp;
}
// 交换两个双精度浮点型
void swap(double& p1, double& p2)
{
double tmp = p1;
p1 = p2;
p2 = tmp;
}

C++的函数重载使得用于交换不同类型变量的函数可以拥有相同的函数名,并且传参使用引用传参,使得代码看起来不那么晦涩难懂。

但是即使这样设计,还是有不足之处

1:重载的多个函数仅仅只是类型不同,代码的复用率比较低,只要出现新的类型需要交换,就需要新增对应的重载函数。

2、代码的可维护性比较低,其中一个重载函数出现错误可能意味着所有的重载函数都出现了错误。

那么是否存在一种模板,使得我们无论要交换什么类型,他都可以让编译器根据不同的类型利用该模子来生成相应的代码呢?

就像涂鸦石膏一样,已经给出了一个标准的模具,我们使用不同的颜色进行涂鸦,就可以得到不同颜色的石膏奥特曼。

如果在C++中,也能够存在这样一个模具,通过给这个模具填充不同颜色的材料(类型),从而得到形状相同但颜色不同的石膏(生成具体类型的代码),那将会大大减少代码的冗余。巧的是前人早已将树栽好,我们只需在此乘凉。

答案是存在的,也就是后来c++祖师爷所创建的c++的模板;

泛型编程:编写与类型无关的通用代码,是代码复用的一种手段。模板是泛型编程的基础。

函数模板
函数模板的概念
函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本。

函数模板的格式
template
返回类型 函数名(参数列表)
{
  //函数体
}

就比如用swap举例:

template
void swap(T& x, T& y)
{
T tmp = x;
x = y;
y = tmp;
}

注意:typename是用来定义模板参数的关键字,也可以用class代替,但是不能用struct代替。

函数模板的原理
那么函数模板的底层原理是什么呢?就拿现代的人工智能来说,

客户服务:聊天机器人和虚拟助手可以自动处理客户查询、提供帮助和支持,减轻人工客服的压力。早期的时候,这其实就是先给机器人一个对应固定回复模板来进行聊天。

马云:世界是懒人创造的

函数模板是标准的蓝图,它本身并不是函数。是编译器产生特定具体类型函数的模具。所以其实模板就是将本来应该我们做的重复的事情交给了编译器。

在编译器编译阶段,对于函数模板的使用,编译器需要根据传入的实参类型来推演生成对应类型的函数(这一步就叫做实例化)以供调用,以进行编译。比如,当用int类型使用函数模板时,编译器通过对实参类型的推演,将T确定为int类型,然后产生一份专门处理int类型的代码,对于double类型也是如此。

函数模板的实例化
 用不同类型的参数使用模板时,称为模板的实例化。模板实例化分为隐式实例化和显示实例化。

include

using namespace std;
template
void Swap(T& x, T& y)//注意不能用swap,因为引用了using namespace std;标准库里面存在标准的库函数swap
{
T tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 10, b = 20;
cout << "a=" << a << " " << "b=" << b << endl;
Swap(a, b);
cout << "a=" << a << " " << "b=" << b << endl;
return 0;
}

我们在此基础上进行略微修改

其实修改后是编译不通过的。

因为在编译期间,编译器根据实参推演模板参数的实际类型时,根据实参a将T推演为int,根据实参b将T推演为double,但是模板参数列表中只有一个T,编译器无法确定此处应该将T确定为int还是double。
 此时,我们有两种处理方式,第一种就是我们在传参时将b强制转换为int类型,第二种就是使用下面说到的显示实例化。

二、显示实例化:在函数名后的<>中指定模板参数的实际类型

include

using namespace std;
template
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10;
double b = 20;
int c = Add(a, b); //指定模板参数的实际类型为int
cout << c;
return 0;
}

注意:使用显示实例化时,如果传入的参数类型与模板参数类型不匹配,编译器会尝试进行隐式类型转换,如果无法转换成功,则编译器将会报错。

就比如:

include

using namespace std;
template
void Swap(T& x,T& y)
{
T tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 10;
double b = 20;

cout << "a=" << a << " " <<  "b=" << b << endl;
Swap<int>(a, b);
cout << "a=" << a << " " << "b=" << b << endl;
return 0;

}

观察仔细的话可以发现我们这里没有用const修饰,这就是其中一个问题。

因为b要进行隐式类型转化,如果不加const的话,那么就会存在权限放大的错误。

但是加上const的话又会报错,不能修改其值。

所以来说不是所有的都是要加const,不是所有都可以及进行要进行隐式类型转化的显示实例化;

函数模板的匹配原则
一、一个非模板函数可以和一个同名的函数模板同时存在,而且该函数模板还可以被实例化为这个非模板函数

include

using namespace std;
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
return x + y;
}
//通用类型加法的函数模板
template
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = 10, b = 20;
int c = Add(a, b); //调用非模板函数,编译器不需要实例化
int d = Add(a, b); //调用编译器实例化的Add函数
return 0;
}

二、对于非模板函数和同名的函数模板,如果其他条件都相同,在调用时会优先调用非模板函数,而不会从该模板产生出一个实例。如果模板可以产生一个具有更好匹配的函数,那么选择模板

include

using namespace std;
//专门用于int类型加法的非模板函数
int Add(const int& x, const int& y)
{
return x + y;
}
//通用类型加法的函数模板
template
T1 Add(const T1& x, const T2& y)
{
return x + y;
}
int main()
{
int a = Add(10, 20); //与非模板函数完全匹配,不需要函数模板实例化
int b = Add(2.2, 2); //函数模板可以生成更加匹配的版本,编译器会根据实参生成更加匹配的Add函数
return 0;
}

三、模板函数在隐式实例化不允许自动类型转换,但普通函数可以进行自动类型转换

include

using namespace std;
template
T Add(const T& x, const T& y)
{
return x + y;
}
int main()
{
int a = Add(2, 2.2); //模板函数不允许自动类型转换,不能通过编译
return 0;
}

编译无法通过,因为模板函数不允许自动类型转换,所以不会将2自动转换为2.0,或是将2.2自动转换为2。

类模板
类模板的定义格式
template
class 类模板名
{
private:
  //类内成员声明
};

include

using namespace std;
template
class A
{
public:
A(T val)
:a(val)
{}
void print()
{
cout << a << endl;
}
private:
T a;
};
int main()
{
A a(2);
a.print();
return 0;
}

注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。

include

using namespace std;
template
class A
{
public:
A(T val)
:a(val)
{}
void print();
private:
T a;
};
template
void A::print()
{
cout << a << endl;
}
int main()
{
A a(2);
a.print();
return 0;
}

类模板的实例化
 类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后面根<>,然后将实例化的类型放在<>中即可。

注意:类模板名字不是真正的类,而实例化的结果才是真正的类。

目录
相关文章
|
1月前
|
存储 算法 安全
c++模板进阶操作——非类型模板参数、模板的特化以及模板的分离编译
在 C++ 中,仿函数(Functor)是指重载了函数调用运算符()的对象。仿函数可以像普通函数一样被调用,但它们实际上是对象,可以携带状态并具有更多功能。与普通函数相比,仿函数具有更强的灵活性和可扩展性。仿函数通常通过定义一个包含operator()的类来实现。public:// 重载函数调用运算符Add add;// 创建 Add 类的对象// 使用仿函数return 0;
74 0
|
1月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
117 0
|
3月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
115 12
|
4月前
|
编译器 C++
模板(C++)
本内容主要讲解了C++中的函数模板与类模板。函数模板是一个与类型无关的函数家族,使用时根据实参类型生成特定版本,其定义可用`typename`或`class`作为关键字。函数模板实例化分为隐式和显式,前者由编译器推导类型,后者手动指定类型。同时,非模板函数优先于同名模板函数调用,且模板函数不支持自动类型转换。类模板则通过在类名后加`&lt;&gt;`指定类型实例化,生成具体类。最后,语录鼓励大家继续努力,技术不断进步!
|
4月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
4月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
5月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
4月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
101 16
|
4月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
5月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)