重新系统学习c++语言,并将学习过程中的知识在这里抄录、总结、沉淀。同时希望对刷到的朋友有所帮助,一起加油哦!

 生命就像一朵花,要拼尽全力绽放!死磕自个儿,身心愉悦!

 系列文章列表:

1 c++编程-基础

2 c++编程-核心

写在前面,本篇章主要针对泛型编程学习。

1 模版

1.1 模版的概念

模版就是建立通用模具,减少重复工作量。

生活中也有很多使用模版的例子。例如:

ppt模版:  年度总结ppt模版

报表模版:  月度数据报表模版

模版的特点:

  1. 模版不可以直接使用,它只是一个框架。使用时需要自己填充业务特性的东西。
  2. 模版的通用并不是万能的,只适用于某些特定类型的场景。

1.2 函数模版

c++另一种编程思想称为 泛型编程,主要利用的技术就是模版。

c++提供两种模版机制: 函数模版    和   类模版

1.2.1 函数模版语法

函数模版的作用:

给定一个通用函数,其函数返回值类型和形参类型都可以不具体给定,用一个虚拟的类型来代替。

语法:

template<typename T>

函数声明或定义

解释:

  • template:声明要创建模版
  • typename:表明其后的符号是一种数据类型。告诉编译器不要报错。typename可用class代替
  • T:通用数据类型。可以写成其他,通常为大写字母,例如 X或Y等。

调用使用模版函数的方式:

  1. 自动类型推导
  2. 显式指定数据类型

示例:


#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 函数模版注意事项

注意事项:

  1. 自动类型推导,必须推导出一致的数据类型T,才可以使用
  2. 模板必须要确定出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 普通函数与函数模版的调用规则

调用规则:

  1. 如果普通函数和函数模版都有实现,优先调用普通函数;
  2. 可以通过空模版参数列表来强制调用函数模版;
  3. 函数模版也可以发生重载;
  4. 如果模版函数可以产生更好的匹配,优先调用函数模版;

示例:

#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 类模版与函数模版的区别

  1. 类模版没有自动堆导使用方式
  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 类模版中成员函数创建时机

普通类和类模版中成员函数的创建时机不同:

  1. 普通类中的成员函数在 一开始 就创建;
  2. 类模版中的成员函数在 调用时  才创建;

示例:


#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 类模版对象做函数参数

类模版对象做函数参数共有三种形式:

  1. 指定传入类型--直接显式的使用数据类型做函数参数   (最常用的形式)
  2. 参数模版化--将类对象中的参数模版化来做函数参数
  3. 整个类模版化--将类对象模版化来做函数参数

示例:


#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 类模版与继承

要继承类模版,需要注意:

  1. 当子类继承的父类是一个类模版时,子类在声明时,需要指定父类中T的数据类型。——如果不指定父类T的数据类型,编译器无法给子类分配内存,会编译不通过。
  2. 如果想灵活继承父类中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++编程-提高篇-模版的更多相关文章

  1. ROS Learning-032 (提高篇-010 Launch)Launch 深入研究 --- (启动文件编程)ROS 的 XML语法简介

    ROS 提高篇 之 Launch 深入研究 - 01 - 启动文件的编程 - ROS 的 XML语法简介 我使用的虚拟机软件:VMware Workstation 11 使用的Ubuntu系统:Ubu ...

  2. ROS Learning-029 (提高篇-007 A Mobile Base-05) 控制移动平台 --- (Python编程)控制虚拟机器人的移动(精确的制定目标位置)

    ROS 提高篇 之 A Mobile Base-05 - 控制移动平台 - (Python编程)控制虚拟机器人的移动(精确的制定目标位置) 使用 odometry 消息类型 重写 out_and_ba ...

  3. ROS Learning-028 (提高篇-006 A Mobile Base-04) 控制移动平台 --- (Python编程)控制虚拟机器人的移动(不精确的制定目标位置)

    ROS 提高篇 之 A Mobile Base-04 - 控制移动平台 - (Python编程)控制虚拟机器人的移动(不精确的制定目标位置) 我使用的虚拟机软件:VMware Workstation ...

  4. Java提高篇(二七)-----TreeMap

    TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap的实现,但是为了与Java提高篇系列博文保持一致 ...

  5. java提高篇(八)----详解内部类

    可以将一个类的定义放在另一个类的定义内部,这就是内部类. 内部类是一个非常有用的特性但又比较难理解使用的特性(鄙人到现在都没有怎么使用过内部类,对内部类也只是略知一二). 第一次见面 内部类我们从外面 ...

  6. java提高篇(四)-----理解java的三大特性之多态

    面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承 ...

  7. java提高篇(二)-----理解java的三大特性之继承

    在<Think in java>中有这样一句话:复用代码是Java众多引人注目的功能之一.但要想成为极具革命性的语言,仅仅能够复制代码并对加以改变是不够的,它还必须能够做更多的事情.在这句 ...

  8. Java提高篇---TreeMap

    TreeMap的实现是红黑树算法的实现,所以要了解TreeMap就必须对红黑树有一定的了解,其实这篇博文的名字叫做:根据红黑树的算法来分析TreeMap的实现,但是为了与Java提高篇系列博文保持一致 ...

  9. Spring boot 提高篇

    Spring boot 提高篇 上篇文章介绍了Spring boot初级教程:构建微服务:Spring boot 入门篇,方便大家快速入门.了解实践Spring boot特性:本篇文章接着上篇内容继续 ...

  10. java提高篇(三)-----理解java的三大特性之多态

    面向对象编程有三大特性:封装.继承.多态. 封装隐藏了类的内部实现机制,可以在不影响使用的情况下改变类的内部结构,同时也保护了数据.对外界而已它的内部细节是隐藏的,暴露给外界的只是它的访问方法. 继承 ...

随机推荐

  1. C++ 二级指针与 const 关键字

    可用七种不同的方式将 const 关键字用于二级指针,如下所示: //方式一:所指一级指针指向的数据为常量,以下几种为等效表示 const int ** pptc; //方式一 int const * ...

  2. Python数据科学手册-机器学习介绍

    机器学习分为俩类: 有监督学习 supervised learning 和 无监督学习 unsupervised learning 有监督学习: 对数据的若干特征与若干标签之间 的关联性 进行建模的过 ...

  3. 第一个HTML

    第一个HTML <!DOCTYPE html><!--html 文件开始--><html lang="en"><!--head 文件头-- ...

  4. MongoDB分片集群中配置参数说明

    replication:   #副本集的名称 replSetName: myshardrs01 sharding: #分片角色 clusterRole: shardsvr sharding.clust ...

  5. Fluentd部署详解

    Fluentd系统配置项 https://www.cnblogs.com/sanduzxcvbnm/p/13920972.html Fluentd自身日志 https://www.cnblogs.co ...

  6. 【疫情动态条形图】用Python开发全球疫情排名动态条形图bar_chart_race

    一.开发背景 你好,我是 @马哥python说 ,这是我用Python开发的全球疫情动态条形图,演示效果: https://www.zhihu.com/zvideo/15603276220259696 ...

  7. WinDbg Preview安装以及符号表配置

    1.安装WinDbgPreview 在Microsoft Store直接搜索windbg就可以下载. 2.配置符号服务器 2.1 符号 符号是方便调试程序的文件,通常是pdb文件.一个模块(可执行程序 ...

  8. 如何用Virtualbox搭建一个虚拟机

    序言 各位好啊,我是会编程的蜗牛,作为java开发者,我们肯定会接触Linux服务器,除了使用云服务搭建Linux服务器外,我们一般也可以在自己的电脑上安装虚拟机来搭建Linux服务器用于各种功能的验 ...

  9. Linux实战笔记_CentOS7_格式化磁盘

    fdisk -l #检查是否添加成功(添加一块磁盘并重启计算机后) fdisk /dev/sdb #格式化磁盘 mount /dev/sdb1 /opt #挂载到/opt目录 df -h #查看是否挂 ...

  10. java集合框架复习----(3)Set

    文章目录 四.set集合 1.hashSet[重点] 2.TreeSet 四.set集合 无序.无下标.元素不可重复 1.hashSet[重点] == 数组+链表+红黑树== 基于hashcode计算 ...