目录
模板可分为
类模板,函数模板
类模板默认参数,必须遵照从右往左规则进行指定,而函数模板并不是必然的
二次编译
第一次编译:
在声明的地方对模板本身进行编译,主要是对语法进行检查。
第二次编译:
在调用的地方对参数替换后再次对代码进行编译,也称之为延时编译。
函数模板
概念
1.函数模板通过具体类型产生不同的函数(模板函数)
2.编译器并不是把函数模板处理成任何类型的函数,而是一个 函数或类 的生成器。
3.< >就是模板的类型参数列表,class 用来修饰泛型,当然也可以使用C++11的新的关键字typename;
4.函数模板不可以直接调。
5.函数模板与实际函数相比,实际函数调用效率更高,因为函数模板需要二次编译。
语法
template < class T1, class T2, class T3 = int, .... >
泛型返回值 函数模板名(泛形参列表)
{
//函数模板体。
}
显/隐调用
隐式调用
编译器通过实参推演,把具体的类型参数带入函数模板中,此时的这种函数调用称为隐式调用。
显示调用
在调用函数处函数名后加< > 在括号中进行类型指定,那么编译器将把这个指定类型带入到函数模板中生成一个可调用的函数实例,这种形式叫做显式调用。
代码演示:
#include <iostream>
using namespace std;
template <class T>
T add(T a, T b)
{
return a + b;
}
int main()
{
cout << add(10,20) << endl; //隐式调用
cout << add<string>("zhangsan",",lisi") << endl; //显式调用
return 0;
}
函数模板特化
在有基础模板之后,由于某种原因,如果实际代入的参数类型与函数的算法不匹配时,就需要进行特化。
调用优先级
隐式调用:实例 > 特化 > 基础
显式调用:特化 > 基础 (显式调用不能调用实例函数)
代码演示:
#include <iostream>
using namespace std;
//普通函数实例
const char * compair(const char* t1, const char * t2)
{
cout <<" compair的函数实例" << endl;
return string(t1) > string(t2) ? t1 : t2 ;
}
//函数基础模板
template <class T >
T compair(T t1, T t2)
{
cout << "compair的函数基础模板" << endl;
return t1 < t2 ? t1 : t2 ;
}
//函数模板特化
//char型字符串无法通用基础函数模板,需要使用特化函数模板
template <>
const char * compair(const char * t1, const char * t2)
{
cout << "compair 的函数特化模板 " << endl;
return string (t1 ) < string (t2) ? t1 : t2 ;
}
int main()
{
//隐式调用
//cout << compair("zhangsan" , "lisi") << endl; //实例 > 特化 > 基础
//显式调用
//cout << compair<const char *> ("zhangsan", "lisi") << endl; //特化 > 基础 (显式调用不能调用实例函数)
return 0;
}
运行结果:
读者可依次注释函数实例、特化、模板来查看效果。
实参推演
函数模板,也具有函数的特性: 函数重载特性。
通过模板的实参推演,泛型T即可以是单一类型,也可以是一个很大的组合类型 。
代码演示:
#include <iostream>
#include <typeinfo>
using namespace std;
int add(int a, int b) //int (*)(int , int )
{
return a + b;
}
//单个参数模板
template <class T>
void my_function(T t)
{
cout << typeid (T).name() << endl;
}
//复合参数模板(函数重载)
template <class Ret, class Arg1, class Arg2>
void my_function(Ret (*t)(Arg1, Arg2))
{
cout << typeid (Ret).name() << endl;
cout << typeid (Arg1).name() << endl;
cout << typeid (Arg2).name() << endl;
cout << typeid (t).name() << endl;
}
int main()
{
my_function(10); //i, i表示int
my_function(add); //i i i PFiiiE
return 0;
}
运行结果:
可变参
在计算机程序设计,一个可变参数函数是指函数拥有不定个数,即它接受一个可变数目的参数。
参数个数可变,参数类型不定。
... 是可变参的固定写法
printf函数的实现
代码演示:
#include <iostream>
using namespace std;
void print()
{
//此空参函数,为的是最后参数打印完后,
//会调用此空参函数,然会结束语句
}
template < class FirsArg, class ... Args>
void print(FirsArg first, Args ... args)
{
cout << first << " ";
print(args...);
}
int main()
{
print(1, "hello" , 3.14); //1 hello 3.14
return 0;
}
运行结果:>>1 hello 3.14
类模板
语法
template <class T1, class T2, class T3, ...>
class 类名
{
//类模板体:
//访问权限 + 属性 + 方法
};
概念
1.类模板它就是一个类的生成器,不可直接定义对象,需要手动指定模板中的类型。
2.类模板是不存在如同函数模板那样的自动推导类型的,需要手动指定类模板中的具体类型,编译器会根据指定的类型,带入到类模板中,经过二次编译后,生成一个可被使用的实体类。
3.应用场景:在C++标准库中的各样数据结构都使用模板。
简单练习
代码演示:
#include <iostream>
using namespace std;
template <class T1, class T2>
class Person
{
private:
T1 name;
T2 age;
public:
Person(T1 name, T2 age)
{
this->name = name;
this->age = age;
}
void showInfo()
{
cout << "姓名" << this->name << "年龄:" << this->age << endl;
}
};
int main()
{
Person<string,int> p("zhangsan",18);
p.showInfo();
Person<string,int>* p1 = new Person<string,int>("lisi",20);
p1->showInfo();
return 0;
}
运行结果:
特化
如果模板类中的算法与类型不匹配时,就应该使用特化。特化让类模板可以适应更多的场景。
特化的概念是要由基础模板做为基础的, 没有基础模板就没有特化。
优先级
全特化 > 偏特化 > 基础模板
全特化
tempalate<>
class 类名<指定的类型>
{
//这就是全特化的语法形式
};
偏特化
template <class T>
class <T* 或者是 包含T的组合类型>
{
//偏特化的语法形式。
};
特化代码练习:
基础模板、全特化代码:
#include <iostream>
using namespace std;
template <class T>
class A
{
public:
A()
{
cout << "A的基础模板" << endl;
}
};
//全特化:
template <>
class A<int>
{
public:
A()
{
cout << "A的全特化" << endl;
}
};
偏特化、偏特化传入函数代码:
//偏特化:
template <class T>
class A<T*>
{
public:
A()
{
cout << "A的偏特化的" << endl;
}
};
int add(int a, int b)
{
return a + b;
}
//add函数的函数类型 int (int, int)
//add函数指针的类型 int (*)(int, int)
//偏特化的升级:
template <class Ret, class Arg1, class Arg2>
class A<Ret (Arg1,Arg2)>
{
public:
A()
{
cout << "A的函数类型的偏特化" << endl;
}
};
template <class Ret, class Arg1, class Arg2>
class A<Ret (*)(Arg1, Arg2)>
{
public:
A()
{
cout << "A的函数指针类型的偏特化" << endl;
}
};
主函数演示:
int main()
{
A<int> a; //A的全特化
A<int*> a1; //A的偏特化的
A<int (int, int)> a2; //A的函数类型的偏特化
A<int (*)(int, int)> a3; //A的函数指针类型的偏特化
return 0;
}
运行结果:
类模板继承
继承及初始化
代码演示:
父类模板代码:
#include <iostream>
using namespace std;
//父类模板
template <class T1, class T2>
class Person
{
private:
T1 name;
T2 age;
public:
Person(T1 name, T2 age)
{
this->name = name;
this->age = age;
}
virtual void showInfo()
{
cout << " 姓名:" << this->name << " 年龄:" << this->age << endl;
}
T1 getNmae()
{
return this->name;
}
T2 getAge()
{
return this->age;
}
};
子类代码:
//使用类模板回顾类的继承
template <class T1, class T2, class T3>
class Stu : public Person <T1, T2>
{
private:
//常成员属性的初始方式: 类中的初始话列表
const T3 _id;
//静态成员属性的初始化方式: 类外全局作用域完成静态属性的初始化
static int count;
public:
Stu(T1 name, T2 age, T3 id) : _id(id), Person<T1, T2>(name, age)
{
count++;
}
//基类虚函数在子类中的重写
void showInfo() override
{
cout << "学号" << this->_id << " 姓名:" << this->getNmae() << " 年龄:" << this->getAge() << endl;
}
//不依赖与某个对象的静态成员函数
static int get_count()
{
return count;
}
};
初始化及主函数:
//类模板中的静态成员函数的初始化: 需指定模板类型
template <class T1, class T2, class T3>
int Stu <T1, T2, T3> :: count = 0;
int main()
{
Stu <string, int, int> p1("zhangsan", 12, 1001);
p1.showInfo();
cout << "----------------------------" << endl;
Stu <string, int, int> * p2 = new Stu<string, int, int> ("lisi", 39, 1002);
p2->showInfo();
cout << "----------------------------" << endl;
Person <string, int> *p3 = new Stu<string, int, int>("wangwu", 31, 1003);
p3->showInfo();
cout << "----------------------------" << endl;
//查看 已确定参数类型 的类模板 中的静态变量,需指定相应的参数类型
cout << Stu<string, int, int>::get_count() << endl; //3
//查看 未确定参数类型 的类模板 中的静态变量,需指定相应的参数类型
cout << Stu<int, int, int>::get_count() << endl; //0
return 0;
}
运行结果:
内嵌类
概念
1.当我们在设计一个类的时候,只想让某个元素类隐藏在类中,而不对外公开时,此时就可以使用内嵌类。主要为了外围类而服务。
比如迭代器与容器
2.为了保证了类的安全性,及类体的封装性,一般使用内嵌类都使用sttic关键字来修饰。
3.内嵌类当不在类内定义对象时,并不占用外围类的空间。
4.内嵌类可以访问定义在外围类中的静态实例变量,但不能访问非静态变量。
5.外围类不可以访问内嵌类的成员。
6.可以在外部通过作用域限定符调用内嵌类属性方法。
代码示例:
外围类和内嵌类定义:
#include <iostream>
using namespace std;
template <class T>
class A
{
public:
int a;
static int b;
class B
{
public:
int c = 100;
int d = 200;
static B* ptr;
void get_static()
{
cout << A::b << endl;
}
};
};
在全局对类中属性初始化:
//全局变量初始化
template <class T>
typename A<T>::B* A<T>::B::ptr = nullptr; //typename :修饰后面表示为一个泛型
template <class T>
int A<T>::b = 20;
主函数验证:
int main()
{
A<int> test1;
cout << sizeof (test1) << endl; //4, 内嵌类不占外类空间
cout << "------------------------" << endl;
A<int>::B test2;
test2.get_static(); //20, 内嵌类只能访问外类静态成员
cout << test2.c << endl; //100
cout << sizeof (test2) << endl; //8
return 0;
}
运行结果:
模板的分文件编程
分文件编程时,如果使用模板,那么一定要把函数模板或类模板的声明与定义写在同一个文件下。一般使用 . hpp 作为文件后缀,也被称为模板文件。
"模板定义很特殊。由template<…> 处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。"
普通函数或类:
对C++编译器而言,当调用函数的时候,编译器只需要看到函数的声明。当定义类类型的对象时,编译器只需要知道类的定义,而不需要知道类的实现代码。因此,因该将类的定义和函数声明放在头文件中,而普通函数和类成员函数的定义放在源文件中。
模板函数或类:
但在处理模板函数和类模板时,问题发生了变化。要进行实例化模板函数和类模板,要求编译器在实例化模板时必须在上下文中可以查看到其定义实体;而反过来,在看到实例化模板之前,编译器对模板的定义体是不处理的——原因很简单,编译器怎么会预先知道 typename 实参是什么呢?因此模板的实例化与定义体必须放到同一翻译单元中。
简单来说,当实例化一个模板(即传类型并创建)时,编译器必须看到模板确切的定义,而不仅仅是他的声明。因此最好的办法,就是将模板的声明和定义都放置在同一个 . hpp 文件中。
代码演示:
my_stack.hpp代码:
#ifndef MY_STACK_HPP
#define MY_STACK_HPP
//#include <stdexcept>
#include <iostream>
using namespace std;
template <class T>
class My_Stack
{
private:
//栈的属性
T* ptr;
int size;
int capacity;
public:
//栈的构造
My_Stack(int c = 10)
{
this->capacity = c;
this->ptr = new T[capacity];
this->size = 0;
}
//栈空
bool empty()
{
return this->size == 0;
}
//栈满
bool full()
{
return this->size == capacity;
}
//压栈
void push(const T& val)
{
if(this->full())
{
return ;
}
this->ptr[size] = val; //*(ptr + size) = val;
size++;
}
//弹栈
void pop()
{
if(this->empty())
{
return;
}
size--;
}
//获取栈顶元素。
T& top()
{
if(this->empty())
{
throw logic_error("为空了");
}
return this->ptr[size - 1];
}
};
#endif // MY_STACK_HPP
main.c代码:
#include <iostream>
#include "my_stack.hpp"
using namespace std;
int main()
{
My_Stack<int> s;
s.push(10);
s.push(20);
s.push(30);
while (!s.empty()) {
cout << s.top() << endl;
s.pop();
}
//cout << s.top() << endl; //将会打印错误信息,且程序不往下运行
cout << "Hello World!" << endl;
return 0;
}
运行结果: