3 c++编程-提高篇-模版
重新系统学习c++语言,并将学习过程中的知识在这里抄录、总结、沉淀。同时希望对刷到的朋友有所帮助,一起加油哦!
生命就像一朵花,要拼尽全力绽放!死磕自个儿,身心愉悦!
系列文章列表:
写在前面,本篇章主要针对泛型编程学习。
1 模版
1.1 模版的概念
模版就是建立通用模具,减少重复工作量。
生活中也有很多使用模版的例子。例如:
ppt模版: 年度总结ppt模版
报表模版: 月度数据报表模版
模版的特点:
- 模版不可以直接使用,它只是一个框架。使用时需要自己填充业务特性的东西。
- 模版的通用并不是万能的,只适用于某些特定类型的场景。
1.2 函数模版
c++另一种编程思想称为 泛型编程,主要利用的技术就是模版。
c++提供两种模版机制: 函数模版 和 类模版
1.2.1 函数模版语法
函数模版的作用:
给定一个通用函数,其函数返回值类型和形参类型都可以不具体给定,用一个虚拟的类型来代替。
语法:
template<typename T>
函数声明或定义
解释:
- template:声明要创建模版
- typename:表明其后的符号是一种数据类型。告诉编译器不要报错。typename可用class代替
- T:通用数据类型。可以写成其他,通常为大写字母,例如 X或Y等。
调用使用模版函数的方式:
- 自动类型推导
- 显式指定数据类型
示例:
#include <iostream>
#include <string>
using namespace std;
// 交换两个整型数据
void swapInt(int& a, int& b) {
int tmp = a;
a = b;
b = tmp;
}
// 交换两个浮点型数据
void swapDouble(double& a, double& b) {
double tmp = a;
a = b;
b = tmp;
}
// 使用模版函数
template<typename T>
void mySwap(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
void test() {
int a = 1;
int b = 2;
swapInt(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
double c = 1.1;
double d = 2.2;
swapDouble(c, d);
cout << "c = " << c << endl;
cout << "d = " << d << endl;
}
void test2() {
cout << "------------使用模版函数--------------" << endl;
int a = 1;
int b = 2;
// 两种调用模版函数的方式:
// 1 自动类型推导
//mySwap(a, b);
// 2 显式指定数据类型
mySwap<int>(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
int main() {
test();
test2();
system("pause");
return 0;
}

1.2.2 函数模版注意事项
注意事项:
- 自动类型推导,必须推导出一致的数据类型T,才可以使用
- 模板必须要确定出T的数据类型,才可以使用
示例:
#include <iostream>
#include <string>
using namespace std;
// 使用模版函数
template<typename T>
void mySwap(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
template<typename T>
void func() {
cout << "func调用" << endl;
}
void test() {
int a = 1;
int b = 2;
double c = 3.3;
mySwap(a, b);// 正确
// mySwap(a, c);// 错误,必须推导出一致的数据类型T
}
void test2() {
// func(); // 错误,无法确定出T的数据类型
func<int>(); // 正确
func<string>(); // 正确
}
int main() {
test();
test2();
system("pause");
return 0;
}

1.2.3 函数模版案例
案例:
- 利用模版的技术封装一个排序函数,可以对不同数据类型进行排序;
- 排序规则从大到小,排序算法为选择排序;
- 用char数组和int数组进行测试;
示例:
#include <iostream>
#include <string>
using namespace std;
// 交换函数
template<class T>
void mySwap(T& a, T& b) {
T tmp = a;
a = b;
b = tmp;
}
// 选择排序算法
template<class T>
void mySort(T arr[], int len) {
for (int i = 0; i < len; i++)
{
// 选定i下标的元素值为当前轮排序的最大值下标
int max = i;
for (int k = i + 1; k < len; k++) {
// 若认定的最大值 比 遍历出的值 小,说明k下标的元素才是最大值
if (arr[max] < arr[k])
{
max = k; // 更新最大值下标
}
}
if (max != i) {
mySwap(arr[max],arr[i]); // 交换max和i元素值
}
}
}
// 打印数组
template<class T>
void printArr(T arr[], int len) {
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
// 测试char数组
void test() {
char arr[] = { 'd','e','a','b','c' };
int len = sizeof(arr) / sizeof(char);
mySort(arr, len);
printArr(arr, len);
}
// 测试int数组
void testInt() {
int arr[] = { 4,2,3,1,6,5,7 };
int len = sizeof(arr) / sizeof(int);
mySort(arr, len);
printArr(arr, len);
}
int main() {
test();
testInt();
system("pause");
return 0;
}

1.2.4 普通函数与函数模版的区别
区别:
- 普通函数 调用可以发生隐式类型转换
- 函数模版 用自动类型推导,不可以发生隐式类型转换
- 函数模版 用显式指定类型,可以发生隐式类型转换
示例:
# include <iostream>
# include <string>
using namespace std;
// 普通函数
int add1(int a, int b) {
return a + b;
}
// 模版函数
template<class T>
T add2(T a, T b) {
return a + b;
}
void test1() {
int a = 10;
int b = 20;
char c = 'c'; // a-97 c-99
cout << add1(a, b) << endl;
cout << add1(a, c) << endl;
}
void test2() {
int a = 10;
int b = 20;
char c = 'c';
cout << add2(a, b) << endl;
// 报错, 用自动类型推导,不可以发生隐式类型转换
// cout << add2(a, c) << endl;
// 用显式指定类型,可以发生隐式类型转换
cout << add2<int>(a, c) << endl;
}
int main() {
test1();
test2();
system("pause");
return 0;
}

1.2.5 普通函数与函数模版的调用规则
调用规则:
- 如果普通函数和函数模版都有实现,优先调用普通函数;
- 可以通过空模版参数列表来强制调用函数模版;
- 函数模版也可以发生重载;
- 如果模版函数可以产生更好的匹配,优先调用函数模版;
示例:
#include <iostream>
#include<string>
using namespace std;
void myPrint(int a) {
cout << "调用普通函数" << endl;
}
template<class T>
void myPrint(T a) {
cout << "调用模版函数" << endl;
}
// 函数模版也可以发生重载;
template<class T>
void myPrint(T a, T b) {
cout << "调用重载的模版函数" << endl;
}
void test() {
int a = 10;
// 如果普通函数和函数模版都有实现,优先调用普通函数;
myPrint(a);
// 可以通过空模版参数列表来强制调用函数模版;
myPrint<>(a);
// 如果模版函数可以产生更好的匹配,优先调用函数模版;
// 因为普通函数需要先做隐式类型转换,模版函数不需要,所以编译器认为模版函数更匹配
char c = 'c';
myPrint(c);
}
int main() {
test();
system("pause");
return 0;
}

1.2.6 模版的局限性
模版并不是万能的,有些特定的数据类型,需要用具体化模版方式来实现。
例如:
template<class T>
void f(T a,T b)
{
a=b;
}

在上述a=b;的赋值代码中,如果传入的a和b是数组,就无法实现,编译会报错。
示例:
#include <iostream>
#include <string>
using namespace std;
class Person {
public:
Person(string name, int age) {
m_name = name;
m_age = age;
}
bool operator==(Person &p) {
if (this->m_name == p.m_name) {
return true;
}
else
{
return false;
}
}
string m_name;
int m_age;
};
template<class T>
bool myCompare(T &a, T &b) {
if (a == b) {
return true;
}
else {
return false;
}
}
// 利用具体化Person的版本实现,具体化会优先调用
// template<> 模版定义成空模版列表
// 模版函数参数列表T需要换成具体化的数据类型Person
template<> bool myCompare(Person& p1, Person& p2) {
if (p1.m_name == p2.m_name && p1.m_age == p2.m_age) {
return true;
}
else {
return false;
}
}
void test01() {
int a = 10;
int b = 20;
bool ret = myCompare(a, b);
if (ret) {
cout << "a == b" << endl;;
}
else
{
cout << "a != b" << endl;
}
}
void test02() {
Person p1 = Person("echo", 18);
Person p2 = Person("echo", 18);
bool ret = myCompare(p1, p2);
if (ret) {
cout << "p1 == p2" << endl;;
}
else
{
cout << "p1 != p2" << endl;
}
}
int main() {
test01();
test02();
system("pause");
return 0;
}

1.3 类模版
1.3.1 类模版语法
作用:建立一个通用类,类中的成员数据类型可以不固定,用虚拟类型来指定。
语法:
template<typename T>
class 类名{
……
}

示例:
#include <iostream>
#include <string>
using namespace std;
//也可以这样定义 template<typename NameType, typename AgeType>
template<class NameType,class AgeType>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_name = name;
this->m_age = age;
}
void PrintPerson() {
cout << "name:" << m_name << ",age:" << m_age << endl;
}
NameType m_name;
AgeType m_age;
};
void test() {
Person<string,int> p1("echo", 18);
p1.PrintPerson();
Person<string, string> p2("bala", "不知道");
p2.PrintPerson();
}
int main() {
test();
system("pause");
return 0;
}

1.3.2 类模版与函数模版的区别
- 类模版没有自动堆导使用方式
- 类模版中的模版参数列表可以有默认数据类型
示例:
#include <iostream>
#include <string>
using namespace std;
template<typename NameType,typename AgeType = int>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_name = name;
this->m_age = age;
}
void PrintPerson() {
cout << "name:" << m_name << ",age:" << m_age << endl;
}
NameType m_name;
AgeType m_age;
};
void test() {
// 1 无法用自动类型推导,只能用显式指定数据类型
// Person p1("echo", 18); // 无法编译通过,错误
Person<string, int> p1("echo", 18);
p1.PrintPerson();
// 2 类模版的模版参数列表可以指定默认数据类型
Person<string> p2("tom", 30);
p2.PrintPerson();
}
int main() {
test();
system("pause");
return 0;
}

1.3.3 类模版中成员函数创建时机
普通类和类模版中成员函数的创建时机不同:
- 普通类中的成员函数在 一开始 就创建;
- 类模版中的成员函数在 调用时 才创建;
示例:
#include <iostream>
#include <string>
using namespace std;
class Person1 {
public:
void showPerson1() {
cout << "show Person1" << endl;
}
};
class Person2 {
public:
void showPerson2() {
cout << "show Person2" << endl;
}
};
template<typename T>
class MyPerson {
public:
void fun1() {
obj.showPerson1();
}
void fun2() {
obj.showPerson2();
}
T obj;
};
void test1() {
MyPerson<Person1> p;
p.fun1();
//p.fun2(); // 错误 因为:调用时确认obj是Person1数据类型,fun2成员未创建
}
void test2() {
MyPerson<Person2> p;
//p.fun1(); //错误 因为:调用时确认obj是Person2数据类型,fun1成员未创建
p.fun2();
}
int main() {
test1();
test2();
system("pause");
return 0;
}

1.3.4 类模版对象做函数参数
类模版对象做函数参数共有三种形式:
- 指定传入类型--直接显式的使用数据类型做函数参数 (最常用的形式)
- 参数模版化--将类对象中的参数模版化来做函数参数
- 整个类模版化--将类对象模版化来做函数参数
示例:
#include <iostream>
#include <string>
using namespace std;
template<class T1,class T2>
class Person {
public:
Person(T1 name, T2 age) {
this->m_name = name;
this->m_age = age;
}
void showPerson() {
cout << "name:" << m_name << ",age:" << m_age << endl;
}
T1 m_name;
T2 m_age;
};
void showPerson1(Person<string, int> &p) {
p.showPerson();
}
//1 指定传入类型--直接显式的使用数据类型做函数参数
void test1() {
Person<string, int> p("echo1", 18);
showPerson1(p);
}
template<class T1,class T2>
void showPerson2(Person<T1, T2>& p) {
p.showPerson();
}
//2 参数模版化--将类对象中的参数模版化来做函数参数
//3 整个类模版化--将类对象模版化来做函数参数
void test2() {
Person<string, int> p("echo2", 19);
showPerson2(p);
}
template<class T>
void showPerson3(T& p) {
p.showPerson();
}
//3 整个类模版化--将类对象模版化来做函数参数
void test3() {
Person<string, int> p("echo3", 20);
showPerson3(p);
}
int main() {
test1();
test2();
test3();
system("pause");
return 0;
}

1.3.5 类模版与继承
要继承类模版,需要注意:
- 当子类继承的父类是一个类模版时,子类在声明时,需要指定父类中T的数据类型。——如果不指定父类T的数据类型,编译器无法给子类分配内存,会编译不通过。
- 如果想灵活继承父类中T的数据类型,子类也需要定义成类模版。
示例:
#include <iostream>
#include <string>
using namespace std;
template<class T>
class Base {
T b;
};
// 错误写法
// 子类继承父类是类模版时,子类在声明时,需要指定父类中T的类型
//class Son :public Base {
//};
// 正确写法如下,指定T为int
class Son :public Base<int> {
};
void test1() {
Son s;
}
// 如果想灵活指定父类中T的数据类型,子类也需变为模版
// T 是base中数据类型,Y是Son2中数据类型
template<class T,class Y>
class Son2 :public Base<T> {
Y y;
};
void test2() {
Son2<int, string> s;
}
int main() {
test1();
test2();
system("pause");
return 0;
}

1.3.6 类模版成员函数类外实现
示例:
#include <iostream>
#include <string>
using namespace std;
// 类模版中的成员函数在类外实现
template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age);
void PrintPerson();
T1 m_name;
T2 m_age;
};
// 类模版中的成员函数在类外实现
template<class T1,class T2>
Person<T1,T2>::Person(T1 name, T2 age)
{
m_name = name;
m_age = age;
}
template<class T1, class T2>
void Person<T1, T2>::PrintPerson()
{
cout << "name:" << m_name << ",age:" << m_age << endl;
}
void test() {
Person<string, int> p1("echo", 18);
p1.PrintPerson();
}
int main() {
test();
system("pause");
return 0;
}

1.3.7 类模版分文件编写
问题:
普通类分文件编写,会直接将类分成头文件a.h文件和源文件a.cpp文件。然后在调用程序中添加#include "a.h",程序就直接调用类成员函数。但是,如果是类模版,该方式分文件编写及调用会出现编译问题。
原因:
类模版中成员函数是在调用时才创建,直接添加 .h会找不到函数。
解决办法:
方法1:直接包含.cpp源文件。
方法2:将声明和实现写在一起,不要分文件编写,并将文件名后缀写成.hpp。.hpp是大家约 定的类模版文件名写法,不是强制的。——常用方式
1.3.8 类模版与友元
全局函数类内实现:
直接在类内声明是友元即可;
全局函数类外实现:
(1) 需要提前让编译器知道类模版;
(2) 需要提前让编译器知道全局函数存在;
示例:
#include <iostream>
#include <string>
using namespace std;
// 要提前让编译器知道Person类存在
template<class T1, class T2>
class Person;
// 全局函数类外实现
template<class T1, class T2>
void showPerson2(Person < T1, T2>& p) {
cout << "类外实现--name:" << p.m_name << ",age:" << p.m_age << endl;
}
template<class T1,class T2>
class Person {
// 全局函数类内实现
friend void showPerson(Person < T1, T2>& p ){
cout << "类内实现--name:" << p.m_name << ",age:" << p.m_age << endl;
}
// 全局函数类外实现
// 注意:要加空模版参数列表
// 如果全局函数是类外实现,需要让编译器提前知道这个函数存在,不然编译不通过
friend void showPerson2<>(Person < T1, T2>& p);
public:
Person(T1 name, T2 age) {
this->m_name = name;
this->m_age = age;
}
private:
T1 m_name;
T2 m_age;
};
void test() {
Person<string, int> p("echo", 18);
showPerson(p);
}
void test2() {
Person<string, int> p("echo", 18);
showPerson2(p);
}
int main() {
test();
test2();
system("pause");
return 0;
}

3 c++编程-提高篇-模版的更多相关文章
- ROS Learning-032 (提高篇-010 Launch)Launch 深入研究 --- (启动文件编程)ROS 的 XML语法简介
ROS 提高篇 之 Launch 深入研究 - 01 - 启动文件的编程 - ROS 的 XML语法简介 我使用的虚拟机软件:VMware Workstation 11 使用的Ubuntu系统:Ubu ...
- ROS Learning-029 (提高篇-007 A Mobile Base-05) 控制移动平台 --- (Python编程)控制虚拟机器人的移动(精确的制定目标位置)
ROS 提高篇 之 A Mobile Base-05 - 控制移动平台 - (Python编程)控制虚拟机器人的移动(精确的制定目标位置) 使用 odometry 消息类型 重写 out_and_ba ...
- ROS Learning-028 (提高篇-006 A Mobile Base-04) 控制移动平台 --- (Python编程)控制虚拟机器人的移动(不精确的制定目标位置)
ROS 提高篇 之 A Mobile Base-04 - 控制移动平台 - (Python编程)控制虚拟机器人的移动(不精确的制定目标位置) 我使用的虚拟机软件:VMware Workstation ...
- Java提高篇(二七)-----TreeMap
TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap的实现,但是为了与Java提高篇系列博文保持一致 ...
- java提高篇(八)----详解内部类
可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一个非常有用的特性但又比较难理解使用的特性(鄙人到现在都没有怎么使用过内部类,对内部类也只是略知一二). 第一次见面 内部类我们从外面 ...
- java提高篇(四)-----理解java的三大特性之多态
面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承 ...
- java提高篇(二)-----理解java的三大特性之继承
在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句 ...
- Java提高篇---TreeMap
TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap的实现,但是为了与Java提高篇系列博文保持一致 ...
- Spring boot 提高篇
Spring boot 提高篇 上篇文章介绍了Spring boot初级教程:构建微服务:Spring boot 入门篇,方便大家快速入门.了解实践Spring boot特性:本篇文章接着上篇内容继续 ...
- java提高篇(三)-----理解java的三大特性之多态
面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承 ...
随机推荐
- Linux配置系统yum源
首先是需要你把需要使用的镜像挂载到系统上面,可以通过cd /dvd添加也可以直接上传到系统上 本文档是上传到系统上进行挂载 操作系统:Red Hat 7.6 挂载镜像:Red Hat 7.6 1.挂载 ...
- Python数据科学手册-机器学习: 支持向量机
support vector machine SVM 是非常强大. 灵活的有监督学习算法, 可以用于分类和回归. 贝叶斯分类器,对每个类进行了随机分布的假设,用生成的模型估计 新数据点 的标签.是属于 ...
- Kubernetes 监控--Alertmanager
前面我们学习 Prometheus 的时候了解到 Prometheus 包含一个报警模块,就是我们的 AlertManager,Alertmanager 主要用于接收 Prometheus 发送的告警 ...
- Elasticsearch之集群角色类型
角色划分 在Elasticsearch中,有很多角色,常用的角色有如下: Master Node:主节点 Master eligible nodes:合格节点 Data Node:数据节点 Coord ...
- 浏览器的 JavaScript 控制台功能调试vue
原始显示结果: 调试其中一个变量的值: 页面上呈现出调试后的效果了
- day44-反射03
Java反射03 3.通过反射获取类的结构信息 3.1java.lang.Class类 getName:获取全类名 getSimpleName:获取简单类名 getFields:获取所有public修 ...
- this硬绑定
一.this显示绑定 this显示绑定,顾名思义,它有别于this的隐式绑定,而隐式绑定必须要求一个对象内部包含一个指向某个函数的属性(或者某个对象或者上下文包含一个函数调用位置),并通过这个属性间接 ...
- 右击存放项目的文件夹出现 open with Visual Studio Code 的打开方式
最终效果 步骤1: 找到 Visual Studio Code 的安装位置 (右击桌面Visual Studio Code 图标-->属性-->打开文件夹所在位置) 新建一个可以编辑的 c ...
- Bootstrap‘s JavaScript requires jQuery
1.遇到的第一个问题:modal.js:6 Uncaught Error: Bootstrap's JavaScript requires jQuery at modal.js:6 2.遇到的第二个问 ...
- vue中使用分页组件、将从数据库中查询出来的数据分页展示(前后端分离SpringBoot+Vue)
文章目录 1.看实现的效果 2.前端vue页面核心代码 2.1. 表格代码(表格样式可以去elementui组件库直接调用相应的) 2.2.分页组件代码 2.3 .script中的代码 3.后端核心代 ...