C++提高编程(一)
2021/5/15 14:55:25
本文主要是介绍C++提高编程(一),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
1. 模板
1.1 模板的概念
模板就是建立通用的模具,大大地提高复用性
特点:
- 模板不可以直接使用,它只是一个框架
- 模板的通用并不是万能的
1.2 函数模板
- c++另一种编程思想称为泛型编程,主要利用的技术就是模板
- c++提供两种模板机制:函数模板和类模板
1.2.1 函数模板语法
函数模板作用:建立一个通用函数,其函数返回值类型和形参类型可以不具体定制,用一个虚拟的类型来代表
语法:template <typename T>
函数声明或定义
解释:template
— 声明创建模板 typename
— 表明其后面的符号是一种数据类型,可以用class代替 T
— 通用的数据类型,名称可以替换,通常为大写字母
//实现两个整型数交换 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; } //函数模板 template<typename T> //声明一个模板 void MySwap(T &a , T &b) { T temp = a; a = b; b = temp; } int main() { int a= 10 , b = 20; //swapInt(a,b); double c = 10.1 , d = 12.3; //swapDouble(c,d); //利用函数模板交换 //两种方式使用函数模板 //1、自动类型推导 MySwap(a , b); //2、显示指定类型 MySwap<int>(a , b); //指定T 是int类型 cout << "a = " << a << endl; cout << "b = " << b << endl; system("pause"); return 0; }
1.2.2 函数模板注意事项
注意事项:
- 自动类型推导,必须推导出一致的数据类型T,才可以使用
- 模板必须要确定出T的数据类型,才可以使用
//函数模板 //声明一个模板 template<class T> //typename可以替换为class void MySwap(T &a , T &b) { T temp = a; a = b; b = temp; } //模板必须要确定出T的数据类型,才可以使用 template<class T> //typename可以替换为class void func() { cout << "func 调用" << endl; } int main() { int a = 10 , b = 20 ; char c = 'c'; //自动类型推导,必须推导出一致的数据类型T,才可以使用 MySwap(a,b); //正确 MySwap(a , c); //错误 func(); //报错 func<int>(); //正确 system("pause"); return 0; }
1.2.3 函数模板案例
描述:
- 利用函数模板封装一个排序的函数,可以对不同数据类型数组进行排序
- 排序规则从大到小,排序算法为选择排序
- 分别利用char数组和int数组进行测试
#include <iostream> using namespace std; //交换函数模板 template<typename 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++) { if(arr[max] < arr[j]) { max = j; } } if(max != i) { //交换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 charArr[] = "badcfe"; int num = sizeof(charArr) / sizeof(char); mySort(charArr , num); printArray(charArr , num); } void test02() { int intArray[] = { 1,6,3,2,8,9,10,11,99,44,55 }; int num = sizeof(intArray) / sizeof(int); mySort(intArray, num); printArray(intArray, num); } int main() { test01(); test02(); system("pause"); return 0; }
1.2.4 普通函数与函数模板的区别
- 普通函数调用时可以发生自动类型转换(隐式类型转换)
- 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
- 如果利用显示指定类型的方式,可以发生隐式类型转换
//1、普通函数调用可以发生隐式类型转换 //2、函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换 //3、如果利用显示指定类型的方式,可以发生隐式类型转换 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'; //a - 97 c - 99 cout << myAdd01(a,b) <<endl; cout << myAdd01(a,c) <<endl; //发生了隐式类型转换 //自动类型推导 cout << myAdd02(a , b) << endl; cout << myAdd02(a , c) << endl; //报错 //显示指定类型 cout << myAdd02<int>(a, c) << endl; //发生隐式类型转换 } int main() { test01(); system("pause"); return 0; }
1.2.5 普通函数与函数模板的调用规则
- 如果函数模板和普通函数都可以实现,优先调用普通函数
- 可以通过空模板参数列表来强制调用函数模板
- 函数模板也可以发生重载
- 如果函数模板可以更好的匹配,优先调用函数模板
void myPrint(int a, int b) { cout << "调用的普通函数" << endl; } template<class T> void myPrint(T a, T b) { cout << "调用的函数模板" << endl; } template<class T> void myPrint(T a, T b, T c) { cout << "调用的函数模板的重载" << endl; } void test01() { int a = 10 , b = 20; myPrint(a, b); //调用的普通函数 //通过空模板参数列表来强制调用函数模板 myPrint<>(a, b); // 调用的函数模板 myPrint(a, b, 100); //调用的函数模板的重载 //如果函数模板可以更好的匹配,优先调用函数模板 char c1 = 'a'; char c2 = 'b'; myPrint(c1, c2); //两个都可以,但函数模板可以更改好的匹配 } int main() { test01(); system("pause"); return 0; }
总结:既然提供了函数模板,最后不要提供普通函数,否则容易出现二义性
1.2.6 模板的局限性
- 模板的通用性并不是万能的
template<class T> void f(T a, T b) { a = b; }
在上述代码提供的赋值操作,如果传入的a,b是一个数组,就无法实现了
template<class T> void f(T a, T b) { if(a = b){......} }
在上述代码中,如果T的数据类型传入的是像person这样的自定义数据类型,也无法正常运行
- 因此c++为了解决这种问题,提供模板的重载,可以为这些特定的类型提供具体化模板
解决办法: - 运算符重载
- 利用具体化Person的版本实现代码,具体化会优先调用
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) //前面加template<>告诉编译器是,模板具体化 { if(p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) { return true; } else return false; } void test01() { int a = 10, b = 20; bool res = myCompare(a, b); if(res) { cout << "a == b" << endl; } else cout << "a != b" << endl; } /* void test02() { Person p1("Tom", 10); Person p2("Tom", 10); bool res = myCompare(p1, p2); //运行报错 if(res) { cout << "p1 == p2" << endl; } else cout << "p1 != p2" << endl; } */ int main() { test01(); system("pause"); return 0; }
总结:
- 利用具体化的模板,可以解决自定义类型的通用化
- 学习模板并不是为了写模板,而是在STL能够运用系统提供的模板
1.3 类模板
1.3.1 类模板语法
作用:建立一个通用类,类中的成员数据类型可以不具体制定,用一个虚拟的类型来代表
语法:template<typename T> 类
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("猪八戒", 200); p1.showPerson(); } int main() { test01(); system("pause"); return 0; }
1.3.2 类模板与函数模板区别
区别:
- 类模板没有自动类型推导的使用方式
- 类模板在模板参数列表中可以有默认参数
//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、类模板没有自动类型推导使用方式 //2、类模板在模板参数列表中可以有默认参数 /* void test01() { //Person p("孙悟空", 200); //报错,无法用自动类型推导 Person<string, int> p("孙悟空", 200); //只能用显示指定类型 p.showPerson(); } */ void test02() { Person<string> p("猪八戒", 100); p.showPerson(); } int main() { test01(); system("pause"); return 0; }
1.3.3 类模板中成员函数创建时机
- 普通类中的成员函数一开始就可以创建
- 类模板中的成员函数在调用时才创建
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() { obj.showPerosn1(); //生成代码不会报错,因为没有调用所以不会去创建 } void func2() { obj.showPerson2(); } }; void test01() { MyClass<Person1>m; m.func1(); MyClass<Person2>n; n.func2(); } int main() { test01(); system("pause"); return 0; }
1.3.4 类模板对象做函数参数
- 指定传入的类型— 直接显示对象的数据类型
- 参数模板化 — 将对象中的参数变为模板进行传递
- 整个类模板化 — 将这个对象类型 模板化进行传递
template<class NameType , class AgeType> class Person { public: Person(NameType name , AgeType age) { this->m_Name = name; this->m_Age = age; } void showPerson() { cout << "姓名:" << this->m_Name << "\t年龄:" << this->m_Age << endl; } NameType m_Name; AgeType m_Age; }; //1、指定传入参数(最常用的) void printPerson1(Person<string,int>&p) { p.showPerson(); } void test01() { Person<string,int> p("孙悟空",200); printPerson1(p); } //2、参数模板化 template<class T1, class T2> void printPerson2(Person<T1, T2>&p) { p.showPerson(); cout << "T1的类型为: " << typeid(T1).name()<< endl; cout << "T2的类型为: " << typeid(T2).name()<< endl; } void test02() { Person<string,int> p("猪八戒",200); printPerson2(p); } //3、整个类模板化 template<class T> void printPerson3(T &p) { p.showPerson(); cout << "T的类型为: " << typeid(T).name()<< endl; } void test03() { Person<string,int> p("唐僧",30); printPerson3(p); } int main() { test01(); test02(); test03(); system("pause"); return 0; }
1.3.5 类模板与继承
当类模板碰到继承时,需要注意以下几点:
- 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
- 如果不指定,编译器无法给子类分配内存
- 如果想灵活指出父类中T的类型,子类也需变为类模板
#include<iostream> #include <string> using namespace std; template<class T> class Base { public: T m; }; /* class Son :public Base //错误,必须要知道父类中的T类型,才能继承给子类 { }; */ class Son :public Base<int> { }; //如果想灵活指出父类中T的类型,子类也需变为类模板 template<class T1, class T2> class Son2 :public Base<T2> { public: Son2() { cout << "T1的类型为:" << typeid(T1).name() << endl; cout << "T2的类型为:" << typeid(T2).name() << endl; } T1 obj; }; void test01() { Son s1; } void test02() { Son2<int, char>S2; } int main() { //test01(); test02(); system("pause"); return 0; }
总结:如果父类是类模板,子类需要指定出父类中T的数据类型
1.3.6 类模板成员函数类外实现
template<class T1 , class T2> class Person { public: Person(T1 name, T2 age); //{ // this->m_Age = age; // this->m_Name = name; //} void showPerson(); //{ // cout << "姓名:" << this->m_Name << endl; // cout << "年龄:" << this->m_Age << endl; //} 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 << "姓名:" << this->m_Name << endl; cout << "年龄:" << this->m_Age << endl; } void test01() { Person<string, int> P("张三", 29); P.showPerson(); } int main() { test01(); system("pause"); return 0; }
注意:类模板中成员函数类外实现时,需要加上模板的参数列表
1.3.7 类模板分文件编写
- 问题:类模板中成员函数创建时机是在调用阶段,导致份文件编写时链接不到
- 解决方法1:直接包含 .cpp源文件
- 解决方法2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
//person.hpp #pragma once #include <iostream> #include <string> using namespace std; 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 << "姓名:" << this->m_Name << endl; cout << "年龄:" << this->m_Age << endl; } //main.cpp //第一种解决办法,直接包含 源文件 //#include "person.cpp" //第二种办法,将声明和实现写到同一个文件中,并更改后缀名为.hpp #include "person.hpp" void test01() { Person<string, int> P("张三", 29); P.showPerson(); } int main() { test01(); system("pause"); return 0; }
1.3.8 模板类与友元
- 全局函数类内实现 - 直接在类内声明友元即可
- 全局函数类外实现 - 需要提前让编译器知道全局函数的存在
#include <iostream> #include <string> using namespace std; //通过全局函数,打印Person信息 //提前让编译器知道Person类的存在 template<class T1, class T2> class Person; //全局函数类外实现 template<class T1, class T2> void printPerson2(Person<T1, T2>p) { cout << "姓名:" << p.m_Name << "年龄:" << p.m_Age << endl; } template<class T1 , class T2> class Person { //全局函数类内实现 friend void printPerson(Person<T1 , T2> p) { cout << "姓名:" << p.m_Name << "年龄:" << 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("张三", 29); printPerson(P); } void test02() { Person<string, int> P("李四", 29); printPerson2(P); } int main() { test01(); test02(); system("pause"); return 0; }
1.3.9 类模板案例
案例描述:
#include <iostream> #include <string> #include "MyArray.hpp" using namespace std; void test01() { MyArray<int> arr1(5); for (int i = 0;i<5;i++) { arr1.Push_Back(i); } cout << "arr1的打印输出为:" << endl; arr1.printArray(); cout << arr1[2] << endl; cout << arr1.getCapacity() << endl; cout << arr1.getSize() << endl; arr1.Pop_Back(); arr1.printArray(); cout << arr1.getCapacity() << endl; cout << arr1.getSize() << endl; } 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 << "姓名:" << arr[i].m_Name << "\t年龄:" << arr[i].m_Age << endl; } } void test02() { MyArray<Person> arr(10); Person p1("张三",20); Person p2("李四", 30); Person p3("王五", 25); Person p4("赵六", 26); Person p5("钱七", 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.getCapacity() << endl; cout << arr.getSize() << endl; arr.Pop_Back(); printPersonArray(arr); cout << arr.getCapacity() << endl; cout << arr.getSize() << endl; } int main(int argc, char *argv[]) { //test01(); test02(); system("pause"); return 0; }
#pragma once #include <iostream> using namespace std; template<class T> class MyArray { public: MyArray(int capacity) { //cout << "myarray的有参构造调用" << endl; this->m_Capacity = capacity; this->m_Size = 0; this->pAddress = new T[this->m_Capacity]; } //拷贝构造 MyArray(const MyArray& arr) { //cout << "myarray的拷贝构造调用" << endl; this->m_Capacity = arr.m_Capacity; this->m_Size = arr.m_Size; //深拷贝 this->pAddress = new T[arr.m_Capacity]; //将arr中数据拷贝过来 for (int i=0;i<this->m_Size;i++) { this->pAddress[i] = arr.pAddress[i]; } } //operator=防止浅拷贝的问题 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) { cout << "数组已满"<< endl; return; } this->pAddress[this->m_Size] = val; this->m_Size++; } //尾删法 void Pop_Back() { //让用户访问不到最后一个元素 if (this->m_Size == 0) { cout << "数组为空" << endl; return; } this->m_Size--; } //通过下标访问 arr[10] = 100; T& operator[](int index) { return this->pAddress[index]; } //返回数组容量 int getCapacity() { return this->m_Capacity; } //返回数组大小 int getSize() { return this->m_Size; } //打印输出 void printArray() { for (int i = 0;i< this->m_Size;i++) { cout << "第"<<i<< "个元素为"<<this->pAddress[i]<<endl; } } ~MyArray() { if (this->pAddress != NULL) { //cout << "myarray的析构函数调用" << endl; delete[] this->pAddress; this->pAddress = NULL; } } private: T* pAddress; //只想堆区开辟的真实数值 int m_Capacity; //数组容量 int m_Size; //数组大小 };
这篇关于C++提高编程(一)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-27Nacos多环境配置学习入门
- 2024-12-27Nacos快速入门学习入门
- 2024-12-27Nacos快速入门学习入门
- 2024-12-27Nacos配置中心学习入门指南
- 2024-12-27Nacos配置中心学习入门
- 2024-12-27Nacos做项目隔离学习入门
- 2024-12-27Nacos做项目隔离学习入门
- 2024-12-27Nacos初识学习入门:轻松掌握服务发现与配置管理
- 2024-12-27Nacos初识学习入门:轻松掌握Nacos基础操作
- 2024-12-27Nacos多环境配置学习入门