模板的使用
1、函数模板
1.1 定义
不同类型的数据使用的方法内部实现一致,得写遍相同方法,使用函数模板就只要写一次方法,传入不同的类型.
定义方式:
template <类型形式参数表>
类型 函数名(参数) {// ……}
template <typename T1, typename T2>
T1 max(T1 a, T2 b) {
return a>b ? a : b;
}
int a=10;
int b=20;
char c=‘M’;
//调用模板函数
max(a,b);
max(a,c);
1.2 函数模板与普通函数
① 当函数模板和普通函数都存在时,若与普通函数更匹配则优先调用普通函数,当没有普通函数可以匹配时会调用函数模板
[例] 代码:
template <typename T1, typename T2>
void swap(T1 a, T2 b){
T1 temp = a;
a = b;
b = temp;
}
void swap(char a, char b){
char temp = a;
a = b;
b = temp;
}
int main(){
char a = 'q';
char b = 'e';
int c = 10;
int d= 20;
swap(a,b); //调用普通函数void swap(char a, char b)
swap(a,c); //调用函数模板void swap(T1 a, T2 b)
swap(c,d); //调用函数模板void swap(T1 a, T2 b)
return 0;
}
② 当只有函数模板时,不会进行类型的隐式转换
[例] 代码:
template <typename T>
void swap(T a, T b){
T temp = a;
a = b;
b = temp;
}
int main(){
char a = 'q';
char b = 'e';
int c = 10;
int d= 20;
swap(a,c); //不会隐式类型转换,会报错
return 0;
}
函数模板与普通函数的异同:
1⃣️ 函数模板可以与普通函数共存
2⃣️ 函数模板不允许自动类型转换,普通函数可以
2、类模板
2.1 定义
与函数模板类似,当存在多个类,其内部方法实现的功能都类似,只是数据类型不同
定义方式:
template <类型形式参数表>
class 类名{
// …
};
2.2 类模板的三种描述方式
2.2.1 所有的类模板函数写在类的内部
这种方式需要在创建对象时,将类模板中的虚拟类型实例化
[例] 代码:
template <typename T>
class A{
public:
A(T data){
this->data = data;
}
T getData(){
return data;
}
private:
T data;
};
int main(){
A<int> a(10);
std::cout << a.getData() << std::endl;
return 0;
}
2.2.2 所有的类模板函数写在类的外部,在一个cpp中
将类模板中的方法定义与方法实现都放在一个cpp文件中,但进行分开,然后创建并调用类模板中的方法。这种方式在进行方法的实现时需要将表示类模板的template <typename T>
与类的模板类型也写出来
[例] 代码:
template <typename T>
class A{
public:
A(T data);
T getData();
A operator+ (const A &a);
private:
T data;
};
template <typename T>
A<T>::A(T data){
this->data = data;
}
template <typename T>
T A<T>::getData(){
return data;
}
template <typename T>
A<T> A<T>::operator+ (const A<T> &a){
return A<T>(data+a.data);
}
int main(){
A<int> cla(666), cla2(888);
cout << (cla + cla2).getData() << endl;
return 0;
}
2.2.2 所有的类模板函数写在类的外部,在不同的.h和.cpp中
这种方式只需包含类中方法的实现文件就可以进行使用
[例] A.h 代码:
#pragma once
template <typename T>
class A{
public:
A(T data);
T getData();
A operator+ (const A &a);
private:
T data;
};
[例] A.hpp 代码:
#include "A.h"
template <typename T>
A<T>::A(T data){
this->data = data;
}
template <typename T>
T A<T>::getData(){
return data;
}
template <typename T>
A<T> A<T>::operator+ (const A<T> &a){
return A<T>(data+a.data);
}
[例] main.cpp 代码:
#include <iostream>
#include "mode.hpp"
int main() {
A<int> cla(666), cla2(888);
std::cout << (cla + cla2).getA() << std::endl;
return 0;
}
2.3 类模板中的友元函数
使用友元函数时需要在声明为友元函数前标明为模板类,在友元函数的实现前也同样需标明
[例] 代码:
#include <iostream>
using namespace std;
template <typename T>
class A{
public:
A(T data);
T getData();
A operator+ (const A &a);
template <typename T>
friend ostream &operator<< (ostream &os, const A<T> &a);
private:
T data;
};
template <typename T>
A<T>::A(T data){
this->data = data;
}
template <typename T>
T A<T>::getData(){
return data;
}
template <typename T>
A<T> A<T>::operator+ (const A<T> &a){
return A<T>(data+a.data);
}
template <typename T>
ostream &operator<< (ostream &os, const A<T> &a){
os << "A中data: " << a.data;
return os;
}
int main(){
A<int> cla(666), cla2(888);
cout << cla + cla2 << endl;
return 0;
}
2.4 类模板中的静态成员
[例] 代码:
在静态成员初始化前需要标明类模板 template <typename T>
,改变静态成员的值后,其他对象调用静态成员也是改变后的值
#include <iostream>
using namespace std;
template <typename T>
class A{
public:
A(T data);
T getData();
A operator+ (const A &a);
friend ostream &operator<< (ostream &os, const A<T> &a);
public:
static int count;
private:
T data;
};
template<typename T>
int A<T>::count = 999;
template <typename T>
A<T>::A(T data){
this->data = data;
}
template <typename T>
T A<T>::getData(){
return data;
}
template <typename T>
A<T> A<T>::operator+ (const A<T> &a){
return A<T>(data+a.data);
}
template <typename T>
ostream &operator<< (ostream &os, const A<T> &a){
os << "A中data: " << a.data;
return os;
}
int main(){
A<int> cla(666), cla2(888);
cla.count = 888;
cout << "cla.count: " << cla.count << endl;
cout << "cla2.count: " << cla2.count << endl;
return 0;
}
3、异常处理机制
3.1 传统错误处理机制
不使用异常处理机制监测错误,一般通过返回值来抛出不一样的报错信息
[例] 代码(实现文件的拷贝):
#include <stdio.h>
#include <stdlib.h>
int copyFile(const char *src, const char *dest){
FILE *file1 = NULL, *file2 = NULL;
fopen_s(&file1, src, "rb"); //以只读方式打开二进制文件
if(file1 == NULL){
return -1;
}
fopen_s(&file2, dest , "wb"); //以写的方式打开二进制文件
if(file2 == NULL){
return -2;
}
char buffer[1024]; //定义一个数组用来作文件读出与写入的媒介
int readlen = 0, wirtelen = 0; //定义变量来存下读与写文件的记录数,判断读与写是否一致
//把file1读取的数据放到buffer中,直到文件数据都读完
while((readlen = fread(buffer, 1, 1024, file1)) > 0){
//把buffer中的数据写到file2中
writelen = fwrite(buffer, 1, 1024, file2);
//如果读的数据量和写的数据量不一致,说明有问题
if(readlen != writelen){
return -3;
}
}
fclose(file1);
fclose(file2);
return 0;
}
int main(){
int ret = 0;
ret = copyFile("c:/test/src.txt", "c:/test/dest.txt");
if(ret == -1){
printf("打开源文件失败!\n");
}else if(ret == -2){
printf("打开目标文件失败!\n");
}else if(ret == -3){
printf("拷贝文件失败!\n");
}else{
printf("未知问题!\n");
}
return 0;
}
3.2 C++错误处理机制
方法使用throw进行返回,在调用处用:
try{调用方法} catch(类型 变量名){打印报错信息}
若catch没有抓到匹配的异常,系统会调用abort终止程序
[例] 代码:
#include <stdio.h>
#include <stdlib.h>
#include <string>
int copyFile(const char *src, const char *dest){
FILE *file1 = NULL, *file2 = NULL;
fopen_s(&file1, src, "rb"); //以只读方式打开二进制文件
if(file1 == NULL){
throw new std::string("打开源文件失败!");
}
fopen_s(&file2, dest , "wb"); //以写的方式打开二进制文件
if(file2 == NULL){
throw -2;
}
char buffer[1024]; //定义一个数组用来作文件读出与写入的媒介
int readlen = 0, writelen = 0; //定义变量来存下读与写文件的记录数,判断读与写是否一致
//把file1读取的数据放到buffer中,直到文件数据都读完
while((readlen = fread(buffer, 1, 1024, file1)) > 0){
//把buffer中的数据写到file2中
writelen = fwrite(buffer, 1, 1024, file2);
//如果读的数据量和写的数据量不一致,说明有问题
if(readlen != writelen){
throw -1.0f;
}
}
fclose(file1);
fclose(file2);
return 0;
}
int main(){
int ret = 0;
try{
ret = copyFile("c:/test/src.txt", "c:/test/dest.txt");
}catch(int error){
printf("出现异常了,打开目标文件失败!\n");
}catch(std::string *error){
printf("出现的问题了,%s\n",error->c_str());
delete error;
}catch(float error){ //...表示可以抓到任何类型的异常
printf("出现异常了,拷贝文件失败!\n");
}catch(...){ //...表示可以抓到任何类型的异常
printf("未知异常!\n");
}
return 0;
}
3.3 异常接口声明
可以在函数后面加上抛出的异常类型,便于程序可读
int copyFile(const char *src, const char *dest) throw(int, float, string*)
如果不声明抛出的异常类型,则可以抛出任何类型的异常;
声明抛出的异常类型,再抛出其他异常类型,可能会导致程序终止;
当不想要抛出任何异常时,直接声明为 throw()
3.4 异常类型
3.4.1 throw基本类型
与函数return传值一样,函数执行结束,throw的值拷贝到catch中的变量,同时释放throw的变量
[例] 代码:
#include <stdio.h>
#include <stdlib.h>
#include <string>
int copyFile(const char *src, const char *dest){
FILE *file1 = NULL, *file2 = NULL;
fopen_s(&file1, src, "rb"); //以只读方式打开二进制文件
if(file1 == NULL){
char err = 'e';
throw err; //抛出基本类型,char类型数据
}
fopen_s(&file2, dest , "wb"); //以写的方式打开二进制文件
if(file2 == NULL){
throw -2;
}
char buffer[1024]; //定义一个数组用来作文件读出与写入的媒介
int readlen = 0, writelen = 0; //定义变量来存下读与写文件的记录数,判断读与写是否一致
//把file1读取的数据放到buffer中,直到文件数据都读完
while((readlen = fread(buffer, 1, 1024, file1)) > 0){
//把buffer中的数据写到file2中
writelen = fwrite(buffer, 1, 1024, file2);
//如果读的数据量和写的数据量不一致,说明有问题
if(readlen != writelen){
throw -1.0f;
}
}
fclose(file1);
fclose(file2);
return 0;
}
int main(){
int ret = 0;
try{
ret = copyFile("c:/test/src.txt", "c:/test/dest.txt");
}catch(int error){
printf("出现异常了,打开目标文件失败!\n");
}catch(char error){
printf("出现异常了,打开源文件失败!\n");
delete error;
}catch(float error){
printf("出现异常了,拷贝文件失败!\n");
}
return 0;
}
3.4.2 throw字符串类型
throw的内容为const char * 类型变量,在catch中抓取,类型必须一致,使用指针,没有值的拷贝,都是指向相同的地址
[例] 代码:
#include <stdio.h>
#include <stdlib.h>
#include <string>
int copyFile(const char *src, const char *dest){
FILE *file1 = NULL, *file2 = NULL;
fopen_s(&file1, src, "rb"); //以只读方式打开二进制文件
if(file1 == NULL){
const char *err = "打开源文件失败";
printf("抛出异常前,地址:%p",err);
throw err; //抛出字符串类型,const char*类型数据
}
fopen_s(&file2, dest , "wb"); //以写的方式打开二进制文件
if(file2 == NULL){
throw -2;
}
char buffer[1024]; //定义一个数组用来作文件读出与写入的媒介
int readlen = 0, writelen = 0; //定义变量来存下读与写文件的记录数,判断读与写是否一致
//把file1读取的数据放到buffer中,直到文件数据都读完
while((readlen = fread(buffer, 1, 1024, file1)) > 0){
//把buffer中的数据写到file2中
writelen = fwrite(buffer, 1, 1024, file2);
//如果读的数据量和写的数据量不一致,说明有问题
if(readlen != writelen){
throw -1.0f;
}
}
fclose(file1);
fclose(file2);
return 0;
}
int main(){
int ret = 0;
try{
ret = copyFile("c:/test/src.txt", "c:/test/dest.txt");
}catch(int error){
printf("出现异常了,打开目标文件失败!\n");
}catch(const char *error){
printf("出现异常了,%s 地址:%p\n",error,error);
}catch(float error){
printf("出现异常了,拷贝文件失败!\n");
}
return 0;
}
3.4.3 throw类对象
当抛出类对象时,catch中使用类对象来接,会调用拷贝构造函数;使用引用/指针来接,不会调用拷贝构造函数
[例] 代码:
#include <stdio.h>
#include <stdlib.h>
#include <string>
class Exception{
public:
Exception(){
id = 0;
printf("调用构造函数\n");
}
~Exception(){
printf("调用析构函数\n");
}
Exception(const Exception &error){
id = 1;
printf("调用拷贝构造函数\n");
}
int id;
};
int copyFile(const char *src, const char *dest){
FILE *file1 = NULL, *file2 = NULL;
fopen_s(&file1, src, "rb"); //以只读方式打开二进制文件
if(file1 == NULL){
throw Exception(); //抛出类对象
// throw new Exception();
}
fopen_s(&file2, dest , "wb"); //以写的方式打开二进制文件
if(file2 == NULL){
throw -2;
}
char buffer[1024]; //定义一个数组用来作文件读出与写入的媒介
int readlen = 0, writelen = 0; //定义变量来存下读与写文件的记录数,判断读与写是否一致
//把file1读取的数据放到buffer中,直到文件数据都读完
while((readlen = fread(buffer, 1, 1024, file1)) > 0){
//把buffer中的数据写到file2中
writelen = fwrite(buffer, 1, 1024, file2);
//如果读的数据量和写的数据量不一致,说明有问题
if(readlen != writelen){
throw -1.0f;
}
}
fclose(file1);
fclose(file2);
return 0;
}
int main(){
int ret = 0;
try{
ret = copyFile("c:/test/src.txt", "c:/test/dest.txt");
}catch(Exception error){
printf("出现异常了,id=%d!\n", error.id);
}catch(Exception &error){
printf("出现异常了,id=%d!\n", error.id);
}catch(Exception *error){
printf("出现异常了,id=%d!\n", error->id);
}catch(...){ //...表示可以抓到任何类型的异常
printf("未知异常!\n");
}
return 0;
}