C++自学笔记2.0(模板元编程)
更多教程笔记请查看我的上一篇文章:点击跳转
c++自学之旅2.0(模板元编程)开始!
函数模板
//函数模板
//模板的意义是使函数可重复化
#include <iostream>
using namespace std;
//两个整型交换的函数,通过引用交换
void swapInt(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
//两个浮点型的数交换,通过引用
void swapDouble(double &a, double &b)
{
double temp = a;
a = b;
b = temp;
}
//如果要交换所有的数据类型的值那么要写无数个函数
//但是用模板就可以解决这个问题,模板只有在使用的时候才会确定数据类型
//定义一个模板,T是一个通用的数据类型,可以改名字
template <typename T>
//当定义了模板之后就可以使用通用的数据类型T,T只有在函数被调用的时候才会指定数据类型
void mySwap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
void test01()
{
int a = 10, b = 20;
// swapInt(a, b);
//利用函数模板来交换两个整型的值
//模板有两种使用方式
// 1.自动类型推导,也就是直接使用,T自动推导出来传入的数据类型
// mySwap(a, b);
// 2.显示指定类型,也就是直接告诉模板传入的是什么类型
//也就是说<>括号里面的就是告诉模板的类型,下面的函数就是告诉模板要传int类型的参数进去
mySwap<int>(a, b);
cout << "a=" << a << " "
<< "b=" << b << endl;
double c = 1.1, d = 2.2;
swapDouble(c, d);
cout << "c=" << c << " "
<< "d=" << d << endl;
}
int main()
{
test01();
return 0;
}
函数模板的注意事项
//函数模板的注意事项
#include <iostream>
using namespace std;
//函数模板可以使用class替代typename,也可以继续用typename和后面的类模板做区分
template <class T>
void mySwap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
void test01()
{
int a = 10;
int b = 20;
char c = 'c';
//使用模板
// mySwap(a, b); 正确
//错误,因为模板类型不一致,无法匹配
// mySwap(a, c);
cout << "a=" << a << " "
<< "b=" << b << endl;
}
template <class T>
void func()
{
cout << "func" << endl;
}
void test02()
{
//错误,函数模板里面必须要用到T才能使用
// func();
//解决方法:直接确定T的数据类型
func<int>();
}
int main()
{
// 1.自动类型推导,必须推导出一致的数据类型,T才可以使用
test01();
// 2.模板必须要确定出T的数据类型才可以使用
test02();
return 0;
}
函数模板案例-数组排序
//函数模板案例-数组排序
#include <iostream>
using namespace std;
//交换模板实现
template <class T>
void mySwap(T &a, T &b)
{
T temp = a;
a = b;
b = temp;
}
//排序算法模板实现
template <class T>
void mySort(T arr[], int len) //第一个参数是数组,第二个参数是数组长度
{
//选择排序算法
for (int i = 0; i < len; i++)
{
//认定最大值的下标
int max = i;
for (int j = i + 1; j < len; j++)
{
//如果认定的最大值比遍历出的数值要小,说明j下标的元素才是真正的最大值
if (arr[max] < arr[j])
{
max = j;
}
}
//如果排序出来后max不再是一开始认定的值了那就交换两边的最大值
if (max != i)
{
mySwap(arr[max], arr[i]);
}
}
}
//提供输出的模板
template <class T>
void printArray(T arr[], int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
void test01()
{
//测试char数组
char charArray[] = "badcfe";
int num = sizeof(charArray) / sizeof(charArray[0]);
//排序
mySort(charArray, num);
//输出
printArray(charArray, num);
}
void test02()
{
//测试int数组
int intArray[] = {7, 5, 1, 3, 9, 2, 4, 6, 8};
int num = sizeof(intArray) / sizeof(int);
//排序
mySort(intArray, num);
//输出
printArray(intArray, num);
}
int main()
{
// test01();
test02();
return 0;
}
普通函数与模板函数的区别
//普通函数与模板函数的区别
#include <iostream>
using namespace std;
//普通函数
int myAdd01(int a, int b)
{
return a + b;
}
//函数模板
template <class T>
T myAdd02(T a, T b)
{
return a + b;
}
void test01()
{
int a = 10;
int b = 20;
char c = 'c';
//普通函数调用,隐式转换,会将c字符转换为ASCII码中的99再相加
cout << myAdd01(a, c) << endl;
//自动类型推导,使用模板调用
cout << myAdd02(a, b) << endl;
//错误,无法推导出一致的类型
// myAdd02(a, c);
//解决方法,显示指定类型调用,指定为int,会发生隐式类型转换
cout << myAdd02<int>(a, c) << endl;
}
int main()
{
// 1.普通函数调用可以发生隐式类型转换
test01();
// 2.函数模板 用自动类型推导,不可以发生隐式类型转换
// 3.函数模板 用显示指定类型,可以发生隐式类型转换
return 0;
}
普通函数与函数模板的调用规则
//普通函数与函数模板的调用规则
#include <iostream>
using namespace std;
//普通函数
void myPrint(int a, int b)
{
cout << "my function" << endl;
}
//利用模板进行函数重载
//这是可以重载的,因为函数类型不一样
template <class T>
void myPrint(T a, T b)
{
cout << "my template function" << endl;
}
//函数模板重载函数模板
template <class T>
void myPrint(T a, T b, T c)
{
cout << "my template2 functions" << endl;
}
void test01()
{
int a = 10;
int b = 20;
//当普通函数和函数模板发生重载(重名)时优先调用普通函数
// myPrint(a, b);
//通过空模板参数列表,强制调用函数模板
//所谓空模板也就是什么类型都不传,直接传参数
// myPrint<>(a, b);
//函数模板重载函数模板,通过参数不同来区分
// myPrint(a, b, 100);
//如果函数模板产生更好的匹配,优先调用函数模板
char c1 = 'a';
char c2 = 'b';
//优先调用了模板,因为普通函数会发生隐式转换,而模板可以直接匹配
myPrint(c1, c2);
}
int main()
{
//注意:1.普通函数与函数模板是可以发生函数重载的
// 2.可以使用空模板参数列表来强制调用函数模板
// 3.如果函数模板和普通函数都可以实现,则优先调用普通函数
// 4.如果函数模板可以产生更好的匹配,则优先调用函数模板
test01();
return 0;
}
模板的局限性
//模板的局限性
#include <iostream>
using namespace std;
#include <string>
//定义一个类(自定义数据类型)
class Person
{
public:
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
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 <>
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("Tom", 10);
Person p2("Tom", 10);
//错误,这就是模板的局限性,无法识别用自定义的数据类型进行==操作
//第一种方法:可以在函数模板里面对==符号进行运算符重载
//第二种方法:可以再自定义一个Person类型的函数模板,让它优先走这个函数模板
bool ret = myCompare(p1, p2);
if (ret)
{
cout << "p1==p2" << endl;
}
else
{
cout << "p1!=p2" << endl;
}
}
int main()
{
// test01();
test02();
return 0;
}
类模板
//类模板
#include <iostream>
using namespace std;
#include <string>
//建立一个通用类,类中的成员的数据类型是不确定的,只有在调用时才确定具体的类型
//第一个NameType代表名字的类型,第二个AgeType代表年龄的类型
//也就是说模板中可以定义两个不同的类型
template <class NameType, class AgeType>
class Person
{
public:
//构造函数
Person(NameType name, AgeType age)
{
this->m_Name = name;
this->m_Age = age;
}
//输出函数
void showPerson()
{
cout << "name:" << this->m_Name << " age:" << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01()
{
//通过模板创建对象,传入不同的类型给构造函数初始化属性
Person<string, int> p1("swk", 999);
//输出
p1.showPerson();
}
int main()
{
test01();
return 0;
}
类模板与函数模板的区别
//类模板与函数模板的区别
#include <iostream>
using namespace std;
#include <string>
//定义一个类模板
// template <class NameType, class AgeType>
//第二种定义类模板的方式,指定数据类型,这样在调用模板类的时候只需要传入一个类型就行了
//另外一个类型已经在定义模板时定义好了
template <class NameType, class AgeType = int>
class Person
{
public:
Person(NameType name, AgeType age)
{
this->m_Name = name;
this->m_Age = age;
}
//输出函数
void showPerson()
{
cout << "name: " << this->m_Name << " age = " << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
// 1.类模板没有自动类型推导的使用发生
void test01()
{
//错误,无法用自动类型推导的方式调用构造函数传值,只能用模板(显示指定类型)的方式
// Person p("swk", 1000);
//正确
Person<string, int> p("swk", 1000);
//输出
p.showPerson();
}
void test02()
{
//因为在定义模板的时候已经指定了一个数据类型,所以在调用的时候只需要传入另一个类型就行了
Person<string> p("zbj", 999);
//输出
p.showPerson();
//注:如果默认参数传的是 char ,而你显示指定 int ,编译器仍可以进行隐式转换
}
// 2.类模板在参数列表中可以有默认参数
int main()
{
// test01();
test02();
return 0;
}
类模板中成员函数的创建时机
//类模板中成员函数的创建时机
#include <iostream>
using namespace std;
class Person1
{
public:
//成员函数
void showPerson1()
{
cout << "Person1 show" << endl;
}
};
class Person2
{
public:
//成员函数
void showPerson2()
{
cout << "Person2 show" << endl;
}
};
//类模板
template <class T>
class MyClass
{
public:
T obj;
//类模板中的成员函数
void func1()
{
// T如果是上面两个类的类型就可以点出来他们的函数
obj.showPerson1();
}
//类模板中的成员函数,在一开始并不创建,而是再模板调用时再生成
void func2()
{
obj.showPerson2();
}
};
void test01()
{
//这算是一个类成员函数调用另一个类的成员函数
MyClass<Person1> m;
m.func1();
MyClass<Person2> m2;
m2.func2();
}
int main()
{
test01();
return 0;
}
类模板对象做函数参数
//类模板对象做函数参数
#include <iostream>
using namespace std;
#include <string>
// T1代表string类型,T2代表int类型
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: " << this->m_Name << " age: " << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
// 1.指定传入类型,也就是定义什么就传什么给函数作为形参,通过引用的方式来接收
void printPerson1(Person<string, int> &p)
{
p.showPerson();
}
void test01()
{
Person<string, int> p("swk", 100);
printPerson1(p);
}
// 2.参数模板化,传递,将模板中定义好的类型作为参数的类型
template <class T1, class T2>
void printPerson2(Person<T1, T2> &p)
{
p.showPerson();
//通过typeid().name()可以看到函数自动推导出来的数据类型
cout << "T1 :" << typeid(T1).name() << endl;
cout << "T2 :" << typeid(T2).name() << endl;
}
void test02()
{
Person<string, int> p("zbj", 90);
printPerson2(p);
}
// 3.整个类模板化,也就是直接把Person定义为T,当传入参数时就自动推导出T是Person类型
template <class T>
void printPerson3(T &p)
{
p.showPerson();
//查看T的数据类型
cout << "T: " << typeid(T).name() << endl;
}
void test03()
{
Person<string, int> p("tanshen", 30);
printPerson3(p);
}
int main()
{
// test01();
// test02();
test03();
return 0;
}
类模板与继承
//类模板与继承
#include <iostream>
using namespace std;
//通过类模板创建一个父类
template <class T>
class Base
{
public:
T m;
};
//子类继承模板父类
//错误,必须要知道父类中的T类型,才能继承给子类
// class Son : public Base
//正确,直接告诉父类中的T是int类型,才可以继承
class Son : public Base<int>
{
public:
};
void test01()
{
Son s1;
}
//如果想灵活的指定父类中的T类型,那么子类也需要变为模板类
//其实也就是在继承时把父类原本的模板类型T,抽象成下面的T2,T2就代表父类的T,T1就代表子类自己用的模板类型
//当函数调用时会把T2所接收的数据类型传给父类的T,让父类的T也等于这个类型
//看到孩子眼睛是黑色的,那就知道父亲眼睛也是黑色,同理
template <class T1, class T2>
class Son2 : public Base<T2>
{
public:
Son2()
{
cout << "T1: " << typeid(T1).name() << endl
<< "T2: " << typeid(T2).name() << endl;
}
T1 obj;
};
void test02()
{
//通过模板创建一个子类的对象,其中会把char类型传给T2,而T2又代表父类的继承模板T
//所以也会把char类型传给父类的T
Son2<int, char> S2;
}
int main()
{
// test01();
test02();
return 0;
}
类模板成员函数的类外实现
//类模板成员函数的类外实现
#include <iostream>
using namespace std;
#include <string>
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: " << this->m_Name << " age: " << this->m_Age << endl;
// }
T1 m_Name;
T2 m_Age;
};
//模板构造函数类外实现,要在函数声明时加上template和在::作用域的前面加上<>符号
template <class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
//模板成员函数的类外实现
template <class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
}
void test01()
{
//通过类外实现模板构造函数的方式来创建对象
Person<string, int> P("Tom", 20);
P.showPerson();
}
int main()
{
test01();
return 0;
}
类模板类外实现的分文件编写
day13.cpp文件内容:
//类模板类外实现的分文件编写
#include <iostream>
using namespace std;
//包含.cpp头文件的原因:
//因为类模板的成员函数在调用时才会创建,所以编译器会先看到.h的文件内容
//但是编译器在没有看到.cpp文件的内容之前是无法识别.h的文件内容的,所以直接引用.h会报错
//#include "day13_person.cpp"
//第二种解决方法:将.h和.cpp文件中的内容写到一起,将后缀改为.hpp文件
#include "day13_person.hpp"
void test01()
{
Person<string, int> p("Jerry", 18);
p.showPerson();
}
int main()
{
test01();
return 0;
}
day13_person.hpp文件内容:
//hpp = h + cpp,
//此文件的作用是把.h和.cpp分文件中的内容全部写到一起
//解决.h文件引入时模板冲突的问题
//防止头文件重复包含
#pragma once
#include <iostream>
using namespace std;
#include <string>
template <class T1, class T2>
class Person
{
public:
//模板函数的声明
Person(T1 name, T2 age);
void showPerson();
T1 m_Name;
T2 m_Age;
};
//模板构造函数的类外分文件实现
template <class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age)
{
this->m_Name = name;
this->m_Age = age;
}
//模板成员函数的类外分文件实现
template <class T1, class T2>
void Person<T1, T2>::showPerson()
{
cout << "name: " << this->m_Name << " age: " << this->m_Age << endl;
}
模板类与友元
//模板类与友元
#include <iostream>
using namespace std;
#include <string>
//通过全局函数,打印Person信息
//因为全局函数类外实现比较麻烦
//要先声明模板类让编译器知道有这个类存在
template <class T1, class T2>
class Person;
//再把全局函数的具体实现放到最上面让编译器看到
//类外实现全局函数,因为要在类外识别类内的T1和T2,所以要加template
template <class T1, class T2>
void printPerson2(Person<T1, T2> p)
{
cout << "name wai: " << p.m_Name << " age wai: " << p.m_Age << endl;
}
//最后才在类里面声明全局函数
//定义一个模板类
template <class T1, class T2>
class Person
{
//全局函数,类内实现
//这个函数是全局函数,不是成员函数,他只是被类内调用了
//因为是私有想要类外调用 所以要加友元 加了友元 编译器才不管你类内类外 你就是全局函数
friend void printPerson(Person<T1, T2> p)
{
cout << "name: " << p.m_Name << " age: " << p.m_Age << endl;
}
//全局函数,类外实现,类内声明
//加一个空模板的参数列表
//如果全局函数是类外实现,需要让编译器提前知道这个函数的存在
friend void printPerson2<>(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 test01()
{
//调用类内全局函数
Person<string, int> p("Tom", 20);
printPerson(p);
}
void test02()
{
//调用类外全局函数
Person<string, int> p("Jorry", 20);
printPerson2(p);
}
int main()
{
// test01();
test02();
return 0;
}
类模板案例-实现一个通用的数组类(分文件编写)
day15.cpp文件内容:
//类模板案例-实现一个通用的数组类
#include <iostream>
using namespace std;
#include "day15_MyArray.hpp"
//输出数组
void printIntArray(MyArray<int> &arr)
{
for (int i = 0; i < arr.getSize(); i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
//测试数组
void test01()
{
//创建一个模板对象,调用有参构造赋值为5
MyArray<int> arr1(5);
for (int i = 0; i < 5; i++)
{
//利用尾插法向数组中插入数据
arr1.Push_Back(i);
}
cout << "arr1: " << endl;
printIntArray(arr1);
//输出数组的容量
cout << "arr1 area:" << arr1.getCapacity() << endl;
//输出数组的长度
cout << "arr1 size:" << arr1.getSize() << endl;
//测试类中的拷贝构造函数
MyArray<int> arr2(arr1);
cout << "arr2: " << endl;
printIntArray(arr2);
//尾删
arr2.Pop_Back();
//输出尾删后的数组的容量
cout << "arr2 area:" << arr2.getCapacity() << endl;
//输出尾删后的数组的长度
cout << "arr2 size:" << arr2.getSize() << endl;
//测试函数重载,一开始arr3的容量是100
//后来等号赋值的时候会先将arr3中的数据清空
//然后再把arr1的数据赋给arr3
// MyArray<int> arr3(100);
// arr3 = arr1;
}
//测试自定义的数据类型
class Person
{
public:
Person(){};
Person(string name, int age)
{
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
//输出对象
void printPersonArray(MyArray<Person> &arr)
{
for (int i = 0; i < arr.getSize(); i++)
{
cout << "name: " << arr[i].m_Name << " age: " << arr[i].m_Age << endl;
}
}
//测试自定义的数据类型
void test02()
{
//传入自定义的数据类型
MyArray<Person> arr(10);
Person p1("swk", 999);
Person p2("zbj", 30);
Person p3("zs", 20);
Person p4("lisi", 25);
Person p5("wangwu", 27);
//将数据插入到数组中
arr.Push_Back(p1);
arr.Push_Back(p2);
arr.Push_Back(p3);
arr.Push_Back(p4);
arr.Push_Back(p5);
//打印数组
printPersonArray(arr);
//输出容量
cout << "arr area: " << arr.getCapacity() << endl;
//输出长度
cout << "arr size: " << arr.getSize() << endl;
}
int main()
{
// test01();
test02();
return 0;
}
day15_MyArray.hpp文件内容:
//写一个自己的通用的数组类
#pragma once
#include <iostream>
#include <string>
using namespace std;
template <class T>
class MyArray
{
public:
//有参构造 参数 容量
MyArray(int capacity)
{
// cout << "Myarray open" << endl;
this->m_Capacity = capacity;
this->m_Size = 0;
// new一个相等于数组容量的空间
this->pAddress = new T[this->m_Capacity];
}
//拷贝构造函数,防止浅拷贝的问题
MyArray(const MyArray &arr)
{
// cout << "Myarray copy" << endl;
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
// this->pAddress = arr.pAddress;
//深拷贝,重新在堆区创建一个空间
this->pAddress = new T[arr.m_Capacity];
//将arr中的数据都拷贝过来
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
}
//重载等于符号,防止浅拷贝的问题
//返回一个引用来实现链式编程
MyArray &operator=(const MyArray &arr)
{
// cout << "Myarray operator=" << endl;
//先判断原来堆区是否有数据,如果有先释放
if (this->pAddress != NULL)
{
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
//拷贝
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//深拷贝
this->pAddress = new T[arr.m_Capacity];
//把数组中的数据都拿过来
for (int i = 0; i < this->m_Size; i++)
{
this->pAddress[i] = arr.pAddress[i];
}
//将自身返回
return *this;
}
//尾插法
void Push_Back(const T &val)
{
//判断容量是否等于大小
if (this->m_Capacity == this->m_Size)
{
//容量已满,无法插入
return;
}
//将数据插入到数组当中的最后一个位置
this->pAddress[this->m_Size] = val;
//更新数组的大小
this->m_Size++;
}
//尾删法
void Pop_Back()
{
//让用户访问不到最后一个元素,即为尾删
if (this->m_Size == 0)
{
//如果数组里没有数据就不删,直接退出
return;
}
//数组的大小减一,则用户就访问不到最后一个下标了
this->m_Size--;
}
//通过下标的方式访问数组中的元素,也就是重载[]符号
//如果函数调用后还想作为左值存在则需要返回引用 arr[0] = 100;
//把数据本身返回,再进行赋值的操作
T &operator[](int index)
{
//返回数组中下标对应的数据
return this->pAddress[index];
}
//返回数组的容量
int getCapacity()
{
return this->m_Capacity;
}
//返回数组有效数据的长度
int getSize()
{
return this->m_Size;
}
//析构函数
~MyArray()
{
// cout << "Myarray end" << endl;
if (this->pAddress != NULL)
{
//这里是对象数组而不是对象指针数组
//所以不需要写for循环去释放每一个数组元素
delete[] this->pAddress;
this->pAddress = NULL;
}
}
private:
//指针指向堆区开辟的真实数组
T *pAddress;
//记录数组的容量
int m_Capacity;
//数组的元素个数(大小)
int m_Size;
};
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 一生雾梦!
评论
ValineDisqus