做网站字号多大,wordpress 分享按钮,证书在线制作生成器,江北区网络推广技巧20240214 文章目录 C泛型编程技术模版的概念 函数模版函数模版语法不使用模版的模版完成两个数交换使用模版的方式完成两个数的交换模版注意事项函数模版案列使用模版实现升序选择排序 模版函数和普通函数区别点调用规则 模版的局限性模版的通用性问题模版重载 类模板类模板语…20240214 文章目录 C泛型编程技术模版的概念 函数模版函数模版语法不使用模版的模版完成两个数交换使用模版的方式完成两个数的交换模版注意事项函数模版案列使用模版实现升序选择排序 模版函数和普通函数区别点调用规则 模版的局限性模版的通用性问题模版重载 类模板类模板语法类模板和函数模版区别类模板中成员函数创建时机类模板对象做函数参数指定传入类型参数模板化类模版化 类模版和继承类模板成员函数类外实现类模板分文件编写问题解决方法 类模板与友元 类模板案例实现一个通用数组类功能需求 功能实现功能测试 C泛型编程技术
模版的概念
模版就是建立通用的模具大大提高通用性C除了面向对象的编程思想之外还有另一种编程思想泛型编程主要利用的技术就是模版c提供两种模版模版机制函数模版和类模板
函数模版
函数模版语法
函数模版作用建立一个通用函数其函数返回值和参数类型不具体指定用一个虚拟的类型来表示语法
template typename T
函数声明或定义说明 template声明创建模版typename表名其后面的符号是一种数据类型可以用class代替T通用数据类型名称可以替换通常为大写英文字母 函数模版使用方式 自动类型推导swap(a, b)显示指定类型:swapint(a, b)
不使用模版的模版完成两个数交换
假设现在有交换整数和交换浮点数的需求后面可能还会有交换其他类型数的需求这样每多增一种类型比较就需要重载一份类型不同的交换函数代码示例
#include iostreamusing namespace std;// 交换两个整形数
void swapNum(int a, int b) {int tmp a;a b;b tmp;
}// 交换两个浮点型数
void swapNum(float a, float b) {float tmp a;a b;b tmp;
}void test() {int a 10, b 20;cout 整形交换前 a: a b: b endl;swapNum(a, b);cout 整形交换后 a: a b: b endl;float c 3.14, d 5.21;cout 整形交换前 c: c d: d endl;swapNum(c, d);cout 整形交换后 c: c d: d endl;
}int main() {test();return 0;
}使用模版的方式完成两个数的交换
根据上面不使用模版的方式交换两个数时只是进行函数重载变更交换的数据类型这种情况恰好使用泛型编程的思想参数类型不定其它都确定代码示例
#include iostreamusing namespace std;templatetypename T
void swapNum(T a, T b) {T tmp a;a b;b tmp;
}void test() {int a 10, b 20;cout 整形交换前 a: a b: b endl;// 自动类型推导swapNum(a, b);cout 整形交换后 a: a b: b endl;float c 3.14, d 5.21;cout 整形交换前 c: c d: d endl;// 显示指定类型 swapNumfloat(c, d);cout 整形交换后 c: c d: d endl;
}int main() {test();return 0;
} 结果示例 模版注意事项
使用自动推导的方式时推导出来的数据类型必须一致才可以使用
templatetypename T
void swapNum(T a, T b) {T tmp a;a b;b tmp;
}void test1() {int a 10;char b 20;// error: no matching function for call to swapNum// candidate template ignored: deduced conflicting types for parameter T (int vs. char) // swapNum(a, b);
} 模版必须要确定出数据类型T才可以使用
#include iostreamusing namespace std;templateclass T
void func() {cout func() endl;
}// 模版必须要确定出类型T才可以使用
void test1() {// error: no matching function for call to func// func();funcint();
}int main() {test1();return 0;
} 函数模版案列
使用模版实现升序选择排序
需求分析 首先要实现主要的升序排序函数在排序中需要实现交换函数在排序前后要实现不同类型的数组打印 代码示例
#include iostreamusing namespace std;// 定义模版交换
templateclass T
void swapNum(T a, T b) {T tmp a;a b;b tmp;
}// 定义打印模版
templateclass T
void printArr(const T arr) {for (int i 0; i sizeof(arr) / sizeof(arr[0]); i) {cout arr[i] ;}cout endl;
}// 定义模版选择排序
templateclass T
void selectAscSort(T *arr, int count) {for (int i 0; i count; i) {for (int j 0; j count - i - 1; j) {if (arr[j] arr[j 1]) {swapNum(arr[j], arr[j 1]);}}}
}// 测试整形数组排序
void testInt() {int arr[5] {3, 6, 1, 5, 9};cout 整型数组排序前;printArr(arr);selectAscSortint(arr, sizeof(arr) / sizeof(arr[0]));cout 整型数组排序后;printArr(arr);
}// 测试浮点型数组排序
void testDouble() {double arr[10] {3.12, 4.32, 1.65, 5.6, 7.91, 5.05, 0.19, 4.32, 8.22, 3.97};cout 浮点型数组排序前;printArr(arr);selectAscSortdouble(arr, sizeof(arr) / sizeof(arr[0]));cout 浮点型数组排序后;printArr(arr);
}int main() {testInt();testDouble();return 0;
} 结果示例 模版函数和普通函数
区别点
普通函数调用时可以发生自动类型转换隐式类型转换
#include iostreamusing namespace std;// 普通函数
int myAdd(int a, int b) {return a b;
}// 普通函数类型自动类型转换测试
void test1() {int a 10;int b 10;char c a;cout 调用普通函数不发生隐式类型转换 myAdd(a, b) endl;cout 调用普通函数发生隐式类型转换 myAdd(a, c) endl;
}int main() {test1();return 0;
}模版函数调用时如果利用自动类型推导不会发生自动类型转换如果利用显示指定类型方式时可以发生自动类型转换
#include iostreamusing namespace std;// 定义模版加法函数
templatetypename T
T myAdd(T a, T b) {return a b;
}void test() {int a 1;char b a;// 调用时使用自动类型推导不发生隐式类型转换// candidate template ignored: deduced conflicting types for parameter T (int vs. char)// cout 自动类型推导时不发生隐式类型转换 myAdd(a, b) endl;// 显示指定类型调用时发生自动类型转换cout 显示指定类型调用时发生自动类型转换 myAddint(a, b) endl;
}int main() {test();return 0;
} 调用规则
如果普通函数和模版函数都可以实现优先调用普通函数可以通过空模版参数列表来强制调用函数模版函数模版也可以发生重载如果函数模版可以产生更好的匹配优先使用函数模版代码示例
#include iostreamusing namespace std;// 普通函数
int add(int a, int b) {cout int add(int a, int b) endl;return a b;
}// 模版函数
templatetypename T
T add(T a, T b) {cout T add(T a, T b) endl;return a b;
}// 模版函数
templatetypename T
T add(T a, T b, T c) {cout T add(T a, T b, T c) endl;return a b c;
}void test() {int a 10;int b 20;int c 20;// 如果普通函数和模版函数都可以实现优先调用普通函数add(a, b);// 可以通过空模版参数列表来强制调用函数模版add(a, b);// 函数模版也可以发生重载add(a, b, c);// 如果函数模版可以产生更好的匹配优先使用函数模版char c1 a, c2 b;// char可以隐式类型转换成int,也可以直接调用add(char, char),显然模版的匹配更好add(c1, c2);
}int main() {test();return 0;
} 模版的局限性
模版的通用性问题
例如给类型直接传数组或者自定类型在类似赋值之类的操作中就不能直接使用
#include iostreamusing namespace std;templateclass T
void func(T a, T b) {if (a b) {cout a b endl;} else {{cout a ! b endl;}}return;
}// 普通类型直接比没问题
void test1() {int a 1;int b 2;func(a, b);
}void test2() {int a[3] {1, 2, 3};int b[3] {1, 2, 3};// a ! b func(a, b);
}struct A {int a;
};void test3() {A a1;A a2;// error: invalid operands to binary expression (A and A)// func(a1, a2);
}int main() {test1();test2();test3();return 0;
}模版重载
C为了解决模版通用性的局限问题可以为这些特殊的类型提供具体化的模版使用具体化模版比较自定义数据类型
#include iostreamusing namespace std;templateclass T
void func(T a, T b) {if (a b) {cout a b endl;} else {{cout a ! b endl;}}return;
}struct A {int a;
};// 实现A结构具体化的比较操作
template
void func(struct A a, struct A b) {if (a.a b.a) {cout a.a b.a endl;} else {{cout a.a ! b.a endl;}}return;
}void test() {struct A a1;struct A a2;a1.a 1;a2.a 2;// error: invalid operands to binary expression (A and A)func(a1, a2);
}int main() {test();return 0;
} 类模板
类模板语法
类模板作用建立一个通用类类中的成员数据类型可以不具体指定用一个虚拟的类型来表示语法
template class T
类类模板代码示例
#include iostreamusing namespace std;templateclass NameType, class AgeType
class Person {
public:Person(NameType name, AgeType age) {this-name name;this-age age;}NameType name;AgeType age;
};void test() {Personstring, int p(zsx, 18);cout p.name p.age endl;
}int main() {test();return 0;
}类模板和函数模版区别
类模板没有自动类型推导的使用方式类模板在模版参数中可以有默认参数代码示例
#include iostreamusing namespace std;//AgeTypeint 给定 AgeType 的默认参数为int
templateclass NameType, class AgeTypeint
class Person {
public:Person(NameType name, AgeType age) {this-name name;this-age age;}NameType name;AgeType age;
};void test() {// 不能进行自动类型推导// too few template arguments for class template Person// Person p(zsx, 18);// 缺省使用AgeType的默认参数列表类型intPersonstring p(zsx, 18);cout p.name p.age endl;
}int main() {test();return 0;
} 类模板中成员函数创建时机
类模板中的成员函数并不是在一开始创建的而是在模版调用时在生成代码示例
#include iostreamusing namespace std;class P1 {
public:void showPInfo1() {cout P1 showPInfo() endl;}
};class P2 {
public:void showPInfo2() {cout P2 showPInfo() endl;}
};templateclass T
class myClass {
public:// 类模板中的成员函数并不是在一开始创建的而是在模版调用时在生成void func1() {obj.showPInfo1();}void func2() {obj.showPInfo2();}T obj;
};void test() {myClassP1 p1;p1.func1();// showPInfo2 不是P1中的对象// error: no member named showPInfo2 in P1// p1.func2();
}int main() {test();return 0;
} 类模板对象做函数参数
类模板实例化出对象向函数传参的方式创建类模版代码示例通过类模板创建对象向函数中进行传参的方式 指定出入类型最常用的方式参数模版化类模版化
#include iostreamusing namespace std;templateclass T1, class T2
class Person {
public:Person(T1 name, T2 age) {this-name name;this-age age;}void showPerson() {cout 姓名 name 年龄 age endl;}T1 name;T2 age;
}; 指定传入类型
直接显示对象的数据类型代码示例
// 指定传入类型
void printPerson1(Personstring, int p) {p.showPerson();
}void test1() {Personstring, int p(zs, 12);printPerson1(p);
} 参数模板化
将对象中的参数变为模版进行传递代码示例
// 参数模板化
templateclass T1, class T2
void printPerson2(PersonT1, T2 p) {p.showPerson();cout 查看T1传入的数据类型 typeid(T1).name() endl;cout 查看T2传入的数据类型 typeid(T2).name() endl;
}void test2() {Personstring, int p(li, 13);printPerson2(p);
} 类模版化
将这个对象类型模板化进行传递代码示例
// 类模版化
templateclass T
void printPerson3(T p) {p.showPerson();cout 查看T传入的数据类型 typeid(T).name() endl;
}void test3() {Personstring, int p(ww, 18);printPerson3(p);
}类模版和继承 当子类继承的父类是一个类模板的时候子类在声明的时候要指出父类中T的类型 如果不指定编译器无法给子类分配内存 如果想灵活指定父类中T的类型子类也需要变为类模板 代码示例
#include iostreamusing namespace std;templateclass T
class Base {
public:T m;
};// 字了继承父类时指定父类的类型
class B1 : public Basestring {
};void test1() {B1 b;cout 父类的类型是 typeid(b.m).name() endl;
} 子类继承父类不指定父类的类型
//class B2 : public Base {
//};void test2() {// 子类继承父类不指定父类的类型不能实例化对象// error: expected class name// B2 b;
}// 子类继承父类,想灵活指定父类中T的类型子类也需要变为类模板
templateclass T1, class T2
class B3 : public BaseT2 {
public:T1 t;
};void test3() {B3int, int b;cout 父类的类型是 typeid(b.m).name() endl;cout 子类的类型是 typeid(b.t).name() endl;
}int main() {test1();test2();test3();return 0;
}类模板成员函数类外实现
#include iostreamusing namespace std;templateclass T
class AAA {
public:AAA(T a);void show();T a;
};// 类模板构造函数类外实现
templateclass T
AAAT::AAA(T a) {this-a a;
}// 类模板普通函数类外实现
templateclass T
void AAAT::show() {cout a endl;
}void test() {AAAint Aaa(1);Aaa.show();
}int main() {test();return 0;
}类模板分文件编写
问题
按照将类声明和类实现分文件的方式将类模版文件的声明和类实现分文件的方式这个时候在调用时就会发生链接错误linker command failed with exit code 1
解决方法 方式一包含.cpp文件 可以解决但不常用 底层原理类模板中的成员函数是在创建的时候生成的如果只引入头文件在调用时只会看到类及其成员函数的声明找不到实现方法而引入cpp文件则可以通过cpp文件中引入的头文件以及cpp中的实现从而解决这个问题 方式二将类模板的声明和定义都写在.hpp文件
类模板与友元 实现方式 全局函数类内实现 全局函数内外实现 代码示例
#include iostreamusing namespace std;// 需要让编译器提前知道Pp类
templateclass T1, class T2
class Pp;// 需要将全局函数的类外实现放到最前面提前让编译器知道
templateclass T1, class T2
void printPerson2(PpT1, T2 p) {cout name: p.name age: p.age endl;
}templateclass T1, class T2
class Pp {
public:// 1.全局函数类内实现friend void printPerson1(PpT1, T2 p) {cout name: p.name age: p.age endl;}// 2.全局函数类外实现// 加空模版参数列表如果全局函数需要在类外实现需要让编译器提前知道这个函数存在friend void printPerson2(PpT1, T2 p);Pp(T1 name, T2 age) {this-name name;this-age age;}private:T1 name;T2 age;
};void test() {Ppstring, int p1(zsx, 18);printPerson1(p1);printPerson2(p1);
}int main() {test();return 0;
}类模板案例
实现一个通用数组类
功能需求 可以对内置数据类型以及自定义数据类型的数据进行存储 将数组中的数据存储到堆区 构造函数中可以传入数组的容量 提供对应的拷贝构造函数以及operator防止浅拷贝问题 提供尾插法和尾删法对数组中的数据进行增加和删除 可以通过下标的方式访问数组中的元素 可以获取数组中当前元素个数和数组的容量
功能实现
#pragma once#include iostream
#include stringusing namespace std;templateclass T
class MyArray {
public:// 可以对内置数据类型以及自定义数据类型的数据进行存储// 构造函数中可以传入数组的容量MyArray(int cap) {// cout MyArray 有参构造函数 endl;this-cap cap;this-len 0;this-pArr new T[this-cap];}MyArray(const MyArray ma) {// cout MyArray 拷贝构造函数 endl;// 深拷贝this-pArr new T[ma.cap];this-len ma.len;this-cap ma.cap;// 拷贝原来数组中的元素for (int i 0; i ma.len; i) {this-pArr[i] ma.pArr[i];}return;}// 提供对应的拷贝构造函数以及operator防止浅拷贝问题// MyArray 需要返回自身的引用这样可以在使用的地方支持连等操作MyArray operator(const MyArrayT ma) {// cout operator赋值运算符重载函数 endl;// 需要先判断原来的数组中是否有元素如果已经有了需要先将原来堆区数据释放干净if (this-pArr ! NULL) {delete[] this-pArr;this-pArr NULL;this-len 0;this-cap 0;}// 深拷贝this-pArr new T[ma.cap];this-len ma.len;this-cap ma.cap;// 拷贝原来数组中的元素for (int i 0; i ma.len; i) {this-pArr[i] ma.pArr[i];}return *this;}// 提供尾 插法和尾删法对数组中的数据进行增加和删除void PushTail(T *elem) {if (this-len this-cap) {cout 数组已满请扩容 endl;return;}this-pArr[this-len] *elem;this-len;}void PopTail() {if (this-len 0) {cout 数组已空 endl;return;}this-len--;}// 可以通过下标的方式访问数组中的元素// 返回是为了可以支持链式左值操作T operator[](int index) {return this-pArr[index];}// 可以获取数组中当前元素个数和数组的容量int GetArrLen() {return this-len;}int GetArrCap() {return this-cap;}friend void printArray(MyArray ma) {cout 打印arr内容;for (int i 0; i ma.len; i) {cout ma.pArr[i] ;}cout endl;}// 析构函数~MyArray() {// cout MyArray 析构函数 endl;if (this-pArr ! NULL) {delete[] this-pArr;this-pArr NULL;}}private:// 指针指向堆区开辟的真实数组T *pArr;// 数组中元素个数int len;// 数组容量int cap;
};功能测试
#include myArray.hppvoid test() {MyArrayint ma1(10);MyArrayint ma2(ma1);MyArrayint ma3(100);ma3 ma1;for (int i 0; i 4; i) {ma3.PushTail(i);}cout ma3.GetArrLen() endl;cout ma3.GetArrCap() endl;printArray(ma3);cout ma3[2] endl;ma3.PopTail();cout ma3.GetArrLen() endl;
}// 测试自定义数据类型
class PersonArr {
public:PersonArr() {}PersonArr(string name, int age) {this-name name;this-age age;}string name;int age;
};void printPersonArr(MyArrayPersonArr arr) {for (int i 0; i arr.GetArrLen(); i) {cout arr[i].name arr[i].age endl;}cout endl;
}void test1() {MyArrayPersonArr arr(10);PersonArr p1(zsx1, 12);PersonArr p2(zsx2, 13);PersonArr p3(zsx3, 14);PersonArr p4(zsx4, 15);arr.PushTail(p1);arr.PushTail(p2);arr.PushTail(p3);arr.PushTail(p4);arr.PopTail();cout arr.GetArrLen() endl;cout arr.GetArrCap() endl;printPersonArr(arr);
}int main() {test();test1();return 0;
}