以下图为例子,提供了三个西装的证件照,谁都可以取拍照,可以是小孩,男女人,也可以是某些动物等等等。n那么我们这个模板也是这样,它可以是任何类型,基础类型,class型,等等等等。且会根据你的指定类型编程相对类型(配对)
模板的特点:
模板不可以直接使用,它只是一个框架
模板的通用并不是万能的
语法:
template
//函数声明或定义
解释:
template --- 声明创建模板
typename --- 表面其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母
代码示例:
此定义了两个int的变量赋值成功了,这时的T就成为了int类型的
#include
using namespace std;
template
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的类型具体化
函数模板目标:模板是为了实现泛型,可以减轻编程的工作量,提高函数的重用性
重载,即在同一作用域下,函数名相同,类型or参数什么的不同。
// 函数模板重载
template
void printer(T a, T b)
{
cout << "a=" << a << " b=" << b << endl;
cout << "2个参数函数模板执行" << endl;
}
// 这样重新声明模板类型T,因为一次声明只对当前函数生效
template
void printer(T a)
{
cout << "a=" << a << endl;
cout << "1个参数函数模板执行" << endl;
}
int main()
{
int a = 10;
int b = 20;
printer(a);
printer(a,b);
}
普通函数调用时可以发生自动类型转换(隐式类型转换)
函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换
如果利用显示指定类型的方式,可以发生隐式类型转换
总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型T
#include
using namespace std;
// 普通函数
int myAdd01(int a, int b)
{
return a + b;
}
// 函数模板
template
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(a, c); // 正确,如果用显示指定类型,可以发生隐式类型转换
}
int main(int argc, char const *argv[])
{
test01();
system("pause");
return 0;
}
类模板作用:
建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。
语法:
template
//类
解释:
template --- 声明创建模板
typename --- 表面其后面的符号是一种数据类型,可以用class代替
T --- 通用的数据类型,名称可以替换,通常为大写字母
示例:
#include
using namespace std;
// 函数模板
template
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;
}
学习目标:
类模板实例化出的对象,向函数传参的方式
一共有三种传入方式:
指定传入的类型 --- 直接显示对象的数据类型
参数模板化 --- 将对象中的参数变为模板进行传递
整个类模板化 --- 将这个对象类型 模板化进行传递
总结:
通过类模板创建的对象,可以有三种方式向函数中进行传参
使用比较广泛是第一种:指定传入的类型
#include
#include
using namespace std;
// 类模板
template
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 &p)
{
p.showPerson();
}
void test01()
{
Person p("孙悟空", 100);
printPerson1(p);
}
// 2、参数模板化
template
void printPerson2(Person &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 p("猪八戒", 90);
printPerson2(p);
}
// 3、整个类模板化
template
void printPerson3(T &p)
{
std::string typeName = "未知类型";
if (typeid(T) == typeid(Person))
{
typeName = "Person";
}
cout << "T的类型为: " << typeName << endl;
p.showPerson();
}
void test03()
{
Person p("唐僧", 30);
printPerson3(p);
}
int main()
{
test01();
test02();
test03();
system("pause");
return 0;
}
当类模板碰到继承时,需要注意一下几点:
当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型
如果不指定,编译器无法给子类分配内存
如果想灵活指定出父类中T的类型,子类也需变为类模板
#include
#include
using namespace std;
// 父类模板
template
class Base
{
T m;
};
// class Son:public Base //错误,c++编译需要给子类分配内存,必须知道父类中T的类型才可以向下继承
class Son : public Base // 必须指定一个类型
{
};
void test01()
{
Son c;
}
// 类模板继承类模板 ,可以用T2指定父类中的T类型
template
class Son2 : public Base
{
public:
Son2()
{
cout << typeid(T1).name() << endl;
cout << typeid(T2).name() << endl;
}
};
void test02()
{
Son2 child1;
}
int main()
{
test01();
test02();
system("pause");
return 0;
}
学习目标:
掌握类模板成员函数分文件编写产生的问题以及解决方式
问题:
类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到
解决:
解决方式1:直接包含.cpp源文件
解决方式2:将声明和实现写到同一个文件中,并更改后缀名为.hpp,hpp是约定的名称,并不是强制
推荐写hpp文件
#pragma once
#include
using namespace std;
#include
template
class Person {
public:
Person(T1 name, T2 age);
void showPerson();
public:
T1 m_Name;
T2 m_Age;
};
//构造函数 类外实现
template
Person::Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
//成员函数 类外实现
template
void Person::showPerson() {
cout << "姓名: " << this->m_Name << " 年龄:" << this->m_Age << endl;
直接举例说明,不做过多描述
template
class Demo1
{
template friend void test1(Demo1 obj);
private:
T1 a;
T2 b;
public:
Demo1(T1 a, T2 b)
{
this->a = a;
this->b = b;
}
};
// 全局函数模板
template
void test1(Demo1 obj)
{
cout << obj.a << " " << obj.b << endl;
}
template
class Demo1
{
template friend void test1(Demo1 obj);
private:
T1 a;
T2 b;
public:
Demo1(T1 a, T2 b)
{
this->a = a;
this->b = b;
}
};
// 全局函数模板
template
void test1(Demo1 obj)
{
cout << obj.a << " " << obj.b << endl;
}
template
class Demo1;
class Demo2
{
public:
template
void test2(Demo1 obj)
{
cout << "类中函数模板打印:" << obj.a << " " << obj.b << endl;
}
};
template
class Demo1
{
template friend void Demo2::test2(Demo1 obj);
private:
T1 a;
T2 b;
public:
Demo1(T1 a, T2 b)
{
this->a = a;
this->b = b;
}
};
template
class Demo1
{
friend void test2(Demo1 obj);
private:
T1 a;
T2 b;
public:
Demo1(T1 a, T2 b)
{
this->a = a;
this->b = b;
}
};
void test2(Demo1 obj)
{
cout << "普通全局函数打印:" << obj.a << " " << obj.b << endl;
}
设计一个数组模板类(MyArray),完成对不同类型元素的管理。不仅能操作基本数据类型,还可以操作自定义数据类型
还是一个动态数组,可以扩容等操作
详述在之前写的文章,感兴趣的同学们,可以去看看。
zzJava 自定义异常(案例)throws与throw的区别_throw的例子分数不合法-CSDN博客
Java 异常处理之Throwable的常用成员方法的概述_throwable getmessage get detail-CSDN博客
Java 自定义异常(案例)throws与throw的区别_throw的例子分数不合法-CSDN博客
// 写一个计算两个数相除的算法
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;
}
从进入 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;
}
}
// 使用 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;
}
}
异常接口说明在不同的编译器,编译结果是不一样的。
// 定义一个基本的父类异常类
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();
}
}