首页 > 编程语言 >C++模板从入门到精通:初阶篇

C++模板从入门到精通:初阶篇

时间:2023-06-01 11:31:34浏览次数:46  
标签:初阶 函数 int C++ T1 实例 类型 模板

一、泛型编程

1.1什么是泛型编程?

平常写的函数与泛型编程的模板实例对比:

实现一个交换的两个数的函数

对于整型

void swap(int& a,int& b)
{
  int temp=a;
  a=b;
  b=temp;
}

对于浮点型

void swap(double& a,double& b)
{
  double temp=a;
  a=b;
  b=temp;
}

对于字符型

void swap(char& a,char& b)
{
  char temp=a;
  a=b;
  b=temp;
}

通过对上面的观察可以发现: 整个函数有很多步骤和思路是重复的。那么有人说使用函数重载可以实现。确实对于这个函数可以用函数重载。但是也有几个不好的点。

  1. 重载的函数仅仅是类型不同,代码复用率比较低,只要有新类型出现时,就需要用户自己增加对应的函数
  2. 代码的可维护性比较低,一个出错可能所有的重载均出错


所以引进了模板的概念和泛型编程的方式,目的是让编译器能自动的识别类型利用模板来生成代码并进行运行。可以说这里也极大的减少了重复造轮子。

1.2泛型编程的实用价值

  1. 代码重用:泛型编程可以使得代码更加通用,从而减少了代码的重复。我们可以把那些本质上相同但是类型不同的代码抽象成一个模板,并在不同的地方使用不同的类型来实例化该模板,从而达到代码重用的效果。
  2. 提高代码可读性:泛型编程可以使代码更加通用和简洁,不需要为每种类型都单独编写一份代码。这样,开发人员就可以更专注于算法的实现,而不必过多关注类型的变化,提高了代码的可读性。
  3. 提高代码的灵活性:泛型编程可以根据实际情况选择不同的类型,从而使代码更加灵活。例如,在STL的容器中,只需要指定其中存储的元素的类型,就可以使用其提供的各种方法对该容器进行操作。
  4. 提高代码的性能:在某些情况下,泛型编程可以比具体类型的函数更加高效。例如,对于容器类型来说,使用泛型的迭代器可以将容器中的元素依次遍历,不需要考虑具体的元素类型,从而在性能上有所提升。

二、模板

2.1模板的分类:

C++模板从入门到精通:初阶篇_泛型编程


模板在编程的时候可以分为:函数模板和类模板。


2.2函数模板

2.2.1函数模板的概念

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

类型版本。而不需要为每种类型都定义一个重载函数。

2.2.2函数模板格式

template<typename T1, typename T2,......,typename Tn> //这里typename也可以换成class

返回值类型 函数名(参数列表){}

实例:

#include<iostream>
using namespace std;
template<typename T1>
void  swap1(T1& a, T1& b)
{
	T1 temp = a;
	a = b;
	b = temp;
}
int main()
{
	int a = 10;
	int b = 20;
	swap1(a, b);  // ---->T1 这个模板类型会被自动识别为int
	cout << a <<" "<< b << endl;
	double c = 10.0;
	double d = 20.0;
	swap1(c, d);   // ---->T1 这个模板类型会被自动识别为double
	printf("%lf %lf", c, d);

	return 0;
}

C++模板从入门到精通:初阶篇_函数模板_02

上面代码实现的是两个参数类型都是一致的交换。当传的参数类型不同。

swap1(T1& a,T1& b)
{
  T1 temp=a;
  a=b;
  b=temp;
}
int main()
{
  int a=10;
  double b=20.00;
  swap1(a,b);  //一定会报错
  
  return 0;
}

上面这段代码在调用swap1的时候是会出现歧义,编译器不知道该认为T1是int还是double。

解决方案:

1.模板创建的时候template<typename T1,typename T2>,并且swap函数两个参数分别设成T1& aT2& b

2.在调用函数之前,自己手动强制类型转化。

方案一实例:

#include<iostream>
using namespace std;
template<typename T1, typename T2>
void  swap1(T1& a, T2& b)
{
	T1 temp = b;
	b = a;
	a = temp;
}
int main()
{
	int a = 10;
	double b = 20.00;
	swap1(a, b); 
	cout << a << " " << b << endl;

	return 0;
}

方案二实例:

#include<iostream>
using namespace std;
template<typename T1>
void  swap1(T1& a, T1& b)
{
	T1 temp = a;
	a = b;
	b = temp;
}
int main()
{
	int a = 10;
	double b = 20.00;
	int c = (int)b;
	swap1(a, c);
	cout << a << " " << b << endl;

	return 0;
}




2.2.3函数模板的原理

函数模板的原理就是通过一个图纸或者蓝图,通过识别需求(参数),然后交给编译器的方式产生特具体类型函数的模具。其实模板就是把我们要重复做的事情交给编译器。

特别注意:函数模板本身不是函数

特点:在编译器编译阶段,编译器需要根据传入的实参类型来推演生成对应类型的函数来进行调用。

比如,当T1确定为double或者int,那么编译器会自动生成一份double或者int类型的代码。


C++模板从入门到精通:初阶篇_c++_03

2.2.4 函数模板的实例化

函数模板的实例化部分在函数模板的格式的时候出现过,函数模板的实例化,就是在调用函数的时候用不同类型的参数使用函数模板。

函数模板的实例化分类:

C++模板从入门到精通:初阶篇_模板_04


1、隐式实例化:就是让编译器去猜,他自己去判断T1是什么类型

#include<iostream>
using namespace std;
template<typename T1>
void  swap1(T1& a, T1& b)
{
	T1 temp = a;
	a = b;
	b = temp;
}
int main()
{
	int a = 10;
	int b = 20;
	swap1(a, b);  // ---->T1 这个模板类型会被自动识别为int
	cout << a << " " << b << endl;
	double c = 10.0;
	double d = 20.0;
	swap1(c, d);   // ---->T1 这个模板类型会被自动识别为double
	printf("%lf %lf", c, d);

	return 0;
}

第一次调用swap1的时候T1会被编译器自动识别为int,第二次自动被识别为double。


2、显式实例化:当两个参数的类型不一致的时候需要我们进行显式实例化

#include<iostream>
using namespace std;

template <typename T>
T add(T a, T b)
{
	return a + b;
}
int main()
{
	int a = 10;
	double b = 20.0;
	cout<<add(a, (int)b)<<endl;   //将double强转为int
	cout<<add((double)a, b)<<endl; //将int强转为double
	cout<<add<int>(a, b)<<endl; //统一将数据类型转化为int
	cout<<add<double>(a, b)<<endl; //统一将数据类型转化为double
	return 0;
}

C++模板从入门到精通:初阶篇_函数模板_05



2.2.4模板函数的匹配原则

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

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

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

模板函数不允许自动类型转换,但普通函数可以进行自动类型转换


2.3类模板

2.3.1类模板的定义格式

template<class T1,class T2>  
class 类模板名
{
  //类成员和函数
};

注意:我们这样写出来的类并不是类,只有实例化后的才是类,这个算是生成具体类的模具。

同时,我们可以在类模板里面嵌套使用函数模板。

2.3.2类模板的实例化

类模板实例化与函数模板实例化不同,类模板实例化需要在类模板名字后跟<>,然后将实例化的类型放在<>中即可,类模板名字不是真正的类,而实例化的结果才是真正的类。

2.3.3类模板和模板类的区分

类模板和模板类是同一概念的两种不同说法。它们都指的是可以用于生成不同类型实例的通用代码模板。通常情况下,我们说的是“类模板”,因为这个说法更加准确描述了这种模板的作用和特征。

类模板可以看作是一个带有模板参数的类的定义,其中模板参数可以在类中作为类型、值或其他模板参数使用。而模板类则是通过具体化类模板,即使用特定类型参数替换模板参数而得到的类。模板类本质上就是一个已经实例化的类,可以像普通类一样使用。

因此,类模板和模板类之间的区别在于:类模板是一个通用的模板定义,包含了可以被替换的模板参数;而模板类是通过使用具体类型替换模板参数生成的特定的类实例。


标签:初阶,函数,int,C++,T1,实例,类型,模板
From: https://blog.51cto.com/u_15831056/6393084

相关文章

  • C++中分别使用左值形参和右值形参的拷贝构造和移动构造
    #include<iostream>classData{public:Data(){std::cout<<"EMPTY."<<std::endl;}Data(constData&d){std::cout<<"lvaluecopy."<<std::endl;}Data(constData&&d){std::cout......
  • 模板方法模式
    一、定义定义一个操作中的算法的框架,而将一些步骤延迟到了子类中。使得子类可以不改变一个算法的结构即可重定义该算法的某些步骤。二、实例来一个经典的脑筋急转弯。把一个大象装进冰箱要几个步骤?然后把一个长颈鹿装进冰箱要几个步骤?三、UML类图  四、钩子函数钩子......
  • vue+docxtemplater,填充word模板
    安装依赖yarnadddocxtemplateryarnaddpizzipyarnaddjszip-utilsyarnaddfile-saver//模板解析插件(支持list循环直接使用$index,使用if判断语法)yarnaddangular-expressionsyarnaddlodash页面测试代码<template> <div> <button@click="bt......
  • [C++学习] 整型常量
    C++中二进制以0b开头,如0b00001011(值为11);八进制以0开头,如0013(值为11);十六进制以0x开头,如0x001a(值为26);注意:C++中cout是默认将数据以十进制输出,如果要将数据以八进制,十六进制输出应在前面加oct或hex;如:cout<<oct<<0123<<endl; //输出123cout<<hex<<0x1e<<en......
  • mongocxx c++ 14标准,进行多表联合查询
     #include<mongocxx/client.hpp>#include<mongocxx/instance.hpp>#include<mongocxx/uri.hpp>#include<bsoncxx/builder/stream/document.hpp>#include<bsoncxx/json.hpp>#include<bsoncxx/types.hpp>usingbsoncxx::builder::s......
  • C++指针需要知道的小细节
    一、定义和赋值一个普通对象intival=1024;如上述代码所示,将ival定义为一个int对象,并给予初始值1024。二、指针对象的声明int*pi;1、如上述代码所示,当定义/声明某个特定类型的指针时,要在类型名称后加一个*号。2、在这里,pi是int类型对象的指针。三、指针的初始值int*......
  • C++ 在 cout 中使用关系表达式
    用std::cout输出关系运算表达式时,关系表达式要加括号,否则编译会报错。例如:#include<iostream>intmain(intargc,char**argv){std::cout<<1<2<<std::endl;return0;}在linux中编译后报错内容如下:test.cpp:Infunction'intmain(int,char**)':te......
  • C++基础知识系列-4
    C++基础1C++的struct和class的区别区别1:默认继承的权限。不明确指定的情况下,来自class的继承按照private继承处理,来自struct继承按照public处理区别2:成员的默认访问权限。class成员默认是private权限,struct默认是public2C和C++区别struct上的区别:C中无Protection行为,不能定......
  • c++算法:二分
    算法中,有一种比线性查找算力费得更少的一种算法思想,叫“分治”,今天讲的是分治里的二分查找:借助(low+high)/2公式,找到搜索区域内的中间元素。图1中,搜索区域内中间元素的位置是 ⌊(1+10)/2⌋=5,因此中间元素是27,此元素显然不是要找的目标元素。然后就是缩小范围。 下面就是......
  • C++ 初始化赋值
    把值写在小括号中,等于号可以省略(C++标准)inta=(15);intb(20);把值写在花括号中,等于号也可以省略(C++11标准),统一初始化列表注意:在Linux平台下,编译需要加-std=c++11参数inta={15};inta{15};......