一 模板
1.1 模板概论
以下图为例子,提供了三个西装的证件照,谁都可以取拍照,可以是小孩,男女人,也可以是某些动物等等等。n那么我们这个模板也是这样,它可以是任何类型,基础类型,class型,等等等等。且会根据你的指定类型编程相对类型(配对)
模板的特点:
模板不可以直接使用,它只是一个框架
模板的通用并不是万能的
1.2 函数模板
1.2.1 函数模板概念及应用
语法:
template<typename T>
//函数声明或定义
解释:
template --- 声明创建模板
typename --- 表面其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母
代码示例:
此定义了两个int的变量赋值成功了,这时的T就成为了int类型的
#include <iostream>
using namespace std;template <typename T>
int test(T &a, T &b)
{return a + b;
}int main(int argc, char const *argv[])
{// 原代码传递的是临时常量,而函数模板参数是引用,需要传递变量int num1 = 1;int num2 = 2;// test(num1, num2);// 原函数模板要求两个参数类型一致,这里将 num2 转换为 int 类型以匹配参数列表cout << test(num1, num2) << endl;return 0;
}
注意事项:
函数模板 会编译两次:
第一次:是对函数模板本身编译
第二次:函数调用处将T的类型具体化
函数模板目标:模板是为了实现泛型,可以减轻编程的工作量,提高函数的重用性
1.2.2 函数模板重载
重载,即在同一作用域下,函数名相同,类型or参数什么的不同。
// 函数模板重载
template <typename T>
void printer(T a, T b)
{cout << "a=" << a << " b=" << b << endl;cout << "2个参数函数模板执行" << endl;
}
// 这样重新声明模板类型T,因为一次声明只对当前函数生效
template <typename T>
void printer(T a)
{cout << "a=" << a << endl;cout << "1个参数函数模板执行" << endl;
}
int main()
{int a = 10;int b = 20;printer(a);printer(a,b);
}
1.2.3 函数模板与普通函数区别
普通函数调用时可以发生自动类型转换(隐式类型转换)
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
如果利用显示指定类型的方式,可以发生隐式类型转换
总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T
#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';cout << myAdd01(a, c) << endl; // 正确,将char类型的'c'隐式转换为int类型 'c' 对应 ASCII码 99// myAdd02(a, c); // 报错,使用自动类型推导时,不会发生隐式类型转换myAdd02<int>(a, c); // 正确,如果用显示指定类型,可以发生隐式类型转换
}int main(int argc, char const *argv[])
{test01();system("pause");return 0;
}
1.3 类模板
1.3.1 类模板基本概念
类模板作用:
建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。
语法:
template<typename T>
//类
解释:
template --- 声明创建模板
typename --- 表面其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母
示例:
#include <iostream>
using namespace std;// 函数模板
template <class Name, class Age>
class demo
{
public:demo(Name name, Age age){this.name = name;this.age = age;}~demo();
};int main(int argc, char const *argv[])
{system("pause");return 0;
}
1.3.2 类模板成员函数类外实现
1.3.3 类模板做函数参数
学习目标:
类模板实例化出的对象,向函数传参的方式
一共有三种传入方式:
指定传入的类型 --- 直接显示对象的数据类型
参数模板化 --- 将对象中的参数变为模板进行传递
整个类模板化 --- 将这个对象类型 模板化进行传递
总结:
通过类模板创建的对象,可以有三种方式向函数中进行传参
使用比较广泛是第一种:指定传入的类型
#include <iostream>
#include <string>
using namespace std;
// 类模板
template <class NameType, class AgeType = int>
class Person
{
public:Person(NameType name, AgeType age){this->mName = name;this->mAge = age;}void showPerson(){cout << "name: " << this->mName << " age: " << this->mAge << endl;}public:NameType mName;AgeType mAge;
};// 1、指定传入的类型
void printPerson1(Person<string, int> &p)
{p.showPerson();
}
void test01()
{Person<string, int> p("孙悟空", 100);printPerson1(p);
}// 2、参数模板化
template <class T1, class T2>
void printPerson2(Person<T1, T2> &p)
{p.showPerson();std::string typeName1 = "未知类型";std::string typeName2 = "未知类型";if (typeid(T1) == typeid(std::string)){typeName1 = "std::string";}if (typeid(T2) == typeid(int)){typeName2 = "int";}cout << "T1的类型为: " << typeName1 << endl;cout << "T2的类型为: " << typeName2 << endl;
}
void test02()
{Person<string, int> p("猪八戒", 90);printPerson2(p);
}// 3、整个类模板化
template <class T>
void printPerson3(T &p)
{std::string typeName = "未知类型";if (typeid(T) == typeid(Person<std::string, int>)){typeName = "Person<std::string, int>";}cout << "T的类型为: " << typeName << endl;p.showPerson();
}
void test03()
{Person<string, int> p("唐僧", 30);printPerson3(p);
}int main()
{test01();test02();test03();system("pause");return 0;
}
1.3.4 类模板与继承
当类模板碰到继承时,需要注意一下几点:
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
如果不指定,编译器无法给子类分配内存
如果想灵活指定出父类中T的类型,子类也需变为类模板
1.3.4.1 普通类继承
#include <iostream>
#include <string>
using namespace std;
// 父类模板
template <class T>
class Base
{T m;
};// class Son:public Base //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
class Son : public Base<int> // 必须指定一个类型
{
};
void test01()
{Son c;
}// 类模板继承类模板 ,可以用T2指定父类中的T类型
template <class T1, class T2>
class Son2 : public Base<T2>
{
public:Son2(){cout << typeid(T1).name() << endl;cout << typeid(T2).name() << endl;}
};void test02()
{Son2<int, char> child1;
}int main()
{test01();test02();system("pause");return 0;
}
1.3.6 类模板头文件和源文件分离问题
学习目标:
掌握类模板成员函数分文件编写产生的问题以及解决方式
问题:
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:
解决方式1:直接包含.cpp源文件
解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
推荐写hpp文件
#pragma once
#include <iostream>
using namespace std;
#include <string>template<class T1, class T2>
class Person {
public:Person(T1 name, T2 age);void showPerson();
public: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 << " 年龄:" << this->m_Age << endl;
1.3.7 友元类模板
直接举例说明,不做过多描述
template <class T1,class T2>
class Demo1
{template <class T3, class T4> friend void test1(Demo1<T3, T4> obj);
private:T1 a;T2 b;
public:Demo1(T1 a, T2 b){this->a = a;this->b = b;}
};
// 全局函数模板
template <class T3, class T4>
void test1(Demo1<T3, T4> obj)
{cout << obj.a << " " << obj.b << endl;
}
1.3.7.1 全局函数模板作为类模板的友元
template <class T1,class T2>
class Demo1
{template <class T3, class T4> friend void test1(Demo1<T3, T4> obj);
private:T1 a;T2 b;
public:Demo1(T1 a, T2 b){this->a = a;this->b = b;}
};
// 全局函数模板
template <class T3, class T4>
void test1(Demo1<T3, T4> obj)
{cout << obj.a << " " << obj.b << endl;
}
1.3.7.2 类中成员函数作为类模板的友元
template <class T1, class T2>
class Demo1;class Demo2
{
public:template<class T5, class T6>void test2(Demo1<T5, T6> obj){cout << "类中函数模板打印:" << obj.a << " " << obj.b << endl;}
};template <class T1,class T2>
class Demo1
{template<class T5, class T6> friend void Demo2::test2(Demo1<T5, T6> obj);
private:T1 a;T2 b;
public:Demo1(T1 a, T2 b){this->a = a;this->b = b;}
};
1.3.7.3 普通函数作为类模板的友元
template <class T1,class T2>
class Demo1
{friend void test2(Demo1<int, int> obj);
private:T1 a;T2 b;
public:Demo1(T1 a, T2 b){this->a = a;this->b = b;}
};void test2(Demo1<int,int> obj)
{cout << "普通全局函数打印:" << obj.a << " " << obj.b << endl;
}
1.4 综合练习
设计一个数组模板类(MyArray),完成对不同类型元素的管理。不仅能操作基本数据类型,还可以操作自定义数据类型
还是一个动态数组,可以扩容等操作
二 泛型(与Java对比)
详述在之前写的文章,感兴趣的同学们,可以去看看。
zzJava 自定义异常(案例)throws与throw的区别_throw的例子分数不合法-CSDN博客
Java 异常处理之Throwable的常用成员方法的概述_throwable getmessage get detail-CSDN博客
Java 自定义异常(案例)throws与throw的区别_throw的例子分数不合法-CSDN博客
三 异常处理
3.1 基本语法
// 写一个计算两个数相除的算法
int division(int a, int b)
{if (b == 0){// 对于函数的返回值是可以忽略的,但是异常不可以//return -1; // -1表示参数有问题throw - 1; // 抛出异常,throw是关键字,-1是异常值,异常值可以随便写// 如果抛出异常,不会返回-1,而是该函数直接在这个地方结束,进入到catch}return a / b; // 否则就返回正常的除法结果
}
void fun1()
{int a = 10;int b = 0;// 抛出异常,就要捕获异常,否则程序将直接崩溃int res;try{// 把会抛出异常的语句放到try中res = division(a, b);}catch (int e)// 异常值类型 异常值{//并要在catch中处理捕获到异常以后要做的事情res = 0;cerr << "int类型异常" << endl;}// 当然也可以写多个catch块catch (char e){res = 0;cerr << "char类型异常" << endl;}catch (...) // 捕获其他所有类型异常{res = 0;cerr << "其他类型异常" << endl;}int res1 = res * 100;cout << "(10/0)*100=" << res1 << endl;
}
3.2 栈解旋(unwinding)
从进入 try 块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构。析构的顺序与构造的顺序相反,这一过程称为栈的解旋(unwinding).
class Demo
{
public:int a;
public:Demo(){cout << "无参构造" << endl;}Demo(int a){this->a = a;cout << "有参构造;a=" << this->a << endl;}~Demo(){cout << "析构函数;a=" << this->a << endl;}
};void fun1()
{try{Demo ob1(10);Demo ob2(20);Demo ob3(30);// 抛出异常时,当前程序就会自动进入catch处理异常,因此要将局部变量 ob3 ob2 ob1依次释放throw 1;}catch (int){cout << "int类型异常" << endl;}catch (...){cout << "其他类型异常" << endl;}}
2.3 函数不能抛出异常- 了解
// 使用 noexcept 规格来指定函数是否可以抛出异常
// 该函数不允许抛出异常
void test04()noexcept(true)
{throw 1;
}
// 该函数可以抛出异常
void test05()noexcept(false)
{throw 1;
}
void fun1()
{try{test05();}catch (int){cout << "int类型异常" << endl;}catch (char){cout << "char类型异常" << endl;}catch (const char *){cout << "const char *类型异常" << endl;}catch (float){cout << "float类型异常" << endl;}catch (...){cout << "其他类型异常" << endl;}
}
异常接口说明在不同的编译器,编译结果是不一样的。
3.4 异常的多态使用
// 定义一个基本的父类异常类
class BaseException
{
public:virtual void printExceptionMsg() {};
};
// 定义一个空指针异常,继承于BaseException
class NullPointerException :public BaseException
{
public:void printExceptionMsg(){cout << "null pointer exception!" << endl;}
};
// 定义一个数组下标越界异常,继承于BaseException
class IndexOutOfException :public BaseException
{
public:void printExceptionMsg(){cout << " Index out of range!" << endl;}
};
void fun1()
{try{throw IndexOutOfException();}// 如果要处理多种异常,没必要在这个地方写N多个catch// 让它们继承于同一个父类,然后在这个地方用父类引用接收不同子类对象就可以// 这就是之前讲过的多态catch (BaseException &e){e.printExceptionMsg();}
}