c++模板函数,c++模板元编程
Yyds干货库存
@toc
现在我们来打开C和C的区别,模板。众所周知,C中没有标准的数据库,但是C中却存在STL,这是因为C支持泛型编程,这是我们今天需要了解的重点。今天的模板是初级阶段。我们来简单了解一下。
什么是泛型?所谓泛型,不再是针对某一种类型,而是着眼于广泛的类型。
这句话你可能不太懂。我们用一个简单的例子来解释一下。在C语言中,我们需要写一个简单的两个数的交换。我们需要考虑这两个数的类型,写不同的函数。而且C语言不支持函数重载,不方便。然而,使用泛型是不同的。我们可以用一个通用函数来解决它。
可能你还是看不懂下面这段代码。我们只需要先了解这个东西,后面会一一给你解释。
模板类T
无效交换(T左,T右)
T temp=左;
左=右;
右=temp
}
泛型编程:编写与类型无关的泛型代码是代码重用的一种手段。模板是泛型编程的基础。
我们都知道活字印刷,一个通用的功能相当于一个模板。我们需要什么类型就会给编译器,编译器会通过我们给的类型的模板自动写出这个函数。
模板主要分为两类。
通用关键字
c提供了两个关键字来帮助我们学习模板,下面的代码是。
这句话意味着我们声明下面的函数或类是一个模板,而T1,T2.表示数据类型。我们需要在使用的时候告诉编译器,后面会讲到。
模板类型名T1,类型名T2.
在这里,我要声明,在一些旧语法中,我们也可以用class代替typename,但现在我们认为它们的功能是一样的。
#函数模板
函数模板代表一系列函数。函数模板与类型无关,使用时参数化,根据实参类型生成函数的特定类型版本。
模板的格式
功能模板就像一个模子,它没有地址。当我们传入参数时,编译器会通过模板自动推出一个函数,那么这个函数就会有一个地址,但是模具没有变。
模板类型名T1,类型名T2,键入名称Tn
返回值函数名(参数列表)
}
我们先来看看函数模板,还是以交换函数为例。后面还有很多例子。
#包括iostream
使用STD:cout;
使用STD:endl;
模板类T
无效交换(T左,T右)
T temp=左;
左=右;
右=temp
int main()
int a=1;
int b=2;
互换(a,b);
cout a= a
cout b= b endl
返回0;
}
模板实例化
让我们先解决一个不太难的问题。我们是通过提供不同的参数类型来调用同一个函数吗?这肯定不是真的。我们先来看看现象。
int main()
int a=1;
int b=2;
双c=1.0
双d=2.0
互换(a,b);
cout ========= endl
互换(c,d);
返回0;
}
这就是模板的实例化,编译器通过我们给定的数据类型(或者自动派生)自动生成相应的函数。
在这里告诉你,以后我们不需要用C写交换函数了。是用C标准库写的。
隐式实例化
模板类T
T添加(T a,T b)
返回a b;
}
这是一个模板,为我们提供一个编译器。一旦我们决定调用add函数,这里就会发生一些事情。
我们传入了参数,编译器会通过参数自动推导出数据类型,然后自动生成相应的函数。
显式实例化
现在我们有一个问题。编译器一定会通过参数推导出类型吗?看下面这个函数。
我们传入的参数类型已经确定,那么告诉我如何确定返回值的类型。
模板类T
T func(int n)
返回n;
}
这里需要以现在的形式调用这个函数。编译器不知道,但是我们可以知道。看下面的用法。
模板类T
T func(int n)
返回n;
int main()
int ret=func int(10);
cout ret endl
返回0;
}
显式实例化和隐式实例化的优先级
现在有了两个实例化,我想知道如果同时存在,哪一个起决定作用?
答案是显而易见的,显示器起着决定性的作用。
模板类T
T函数
返回n;
}
模板参数有默认值吗?
是的,也就是说,我们可以使用提供给模板的默认值,但是这个默认值是一个类型。如果看一下用法,会发现和函数参数的用法很像。
模板类T=int
T func(int n)
返回n;
int main()
int ret=func(10);
cout ret endl
返回0;
}
声明和定义可以分开吗?
这个问题需要分开来看。
既然模板可以看作一个函数,那么可以单独声明和定义吗?我们都需要添加模板关键字。
注意,我们需要知道一件事。模板不支持两个文件中的声明和定义,会有链接错误。需要到了高级阶段再说具体原因。
很多地方我们把模板声明和定义放在同一个文件里,有的把这个文件的后缀写成。默认情况下,hpp可以告诉我们这是一个带有模板的头文件,但这不是必需的。
以下是报告错误原因的简要说明。
我们可以这样理解。在重定义文件中,编译器不知道T的类型,也就是说,编译器不会把符号存储在符号表中,链接时也找不到。这里有一个令人沮丧的方法,它使用显示实例化来指定。
也就是说,在定义文件中显示使用模板,但这令人沮丧。你必须展示所有你用过的。比如使用int类型很麻烦,但是对其他人来说也是必须的。最好不要把它们分成两个文件。
模板参数的匹配规则
我们有麻烦了。我们先来分析一下以下两者是否可以同时存在。是的,模板不是一个函数。
模板类T
T加法(T左,T右)
cout T add endl
向左向右返回;
int add(int left,int right)
cout int add endl
向左向右返回;
}
既然能存在,这个地方又有一个问题。编译器先调用哪个?
int main()
int x=1;
int y=2;
add(x,y);
返回0;
}
直接看现象,可以发现先调用函数无可厚非,而不是函数模板。既然是现成的,何必自己去生成呢?
那么我们如何让编译器调用模板呢?这里还有一个方法,使用显式实例化。
我们先来了解一下类模板。我们发现它们和普通的类没什么区别,就是把对应的具体数据类型改成了泛型。
模板类T
类堆栈
公共:
stack(int cap=4);
~ Stack();
void push();
私人:
T * elme
size_t大小;
int cap
};
类别模板格式
包括类定义和成员函数定义,我们分开来说。
先说我们现在拥有的最基本的东西。
但是我们的成员函数的声明和定义并没有分开,所以我们可以按照下面的方法来做。
班级学生
公共:
学生()
_ cap=4;
_ elem=new T[_ cap];
_ size=10
~ Student();
私人:
T * elem
szie _ t _ size
szie _ t _ cap
};
但是如果成员函数的声明和定义是分开的,我们就做不到这一点。
可以说每个成员函数都会有template\ class T和Student\ T:这是编译器所需要的。
模板类T
班级学生
公共:
学生();
~ Student();
私人:
T * elem
szie _ t _ size
szie _ t _ cap
模板类T
学生T:学生()
_ cap=4;
_ elem=new T[_ cap];
_ size=10
模板类T
学生T:~学生()
如果(元素)
删除[]elem;
_ size=0;
_ cap=0;
}
类实例化
类模板实例化不同于函数模板实例化。类模板实例化需要后跟类模板名称,然后可以将实例化的类型放入。类模板名不是真正的类,但实例化的结果是真正的类。
我们用的时候,记得显示实例化就行了。我们来看看用法。
int main()
学生char学生;
返回0;
}
模板参数的再分析
我们需要理解模板参数。这里我们需要思考一个问题。为什么下面的代码会报告错误?
模板类T
T相加(左常数,右常数)
cout T add endl
向左向右返回;
int main()
添加(1,2.2);
返回0;
}
这是因为编译器无法通过给定分部数据推导出参数的类型、一个int、一个double,编译器会因为矛盾而给出错误。
我们可以通过以下方法解决这个问题。
默认模板参数
这个我们可以和默认函数一起理解,思路是一样的。
模板类别K,类别V
void函数()
cout sizeof(K)endl;
cout sizeof(V)endl;
int main()
func int,double
返回0;
}
如果我们这样做呢?
int main()
函数int
返回0;
}
这就要求默认模板参数的存在,并且我们可以通过全部或部分默认来保证程序的正确。
模板类K,类V=char
void函数()
cout sizeof(K)endl;
cout sizeof(V)endl;
int main()
函数int
返回0;
}
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。