C++提高编—(模板,泛型,异常处理)

一 模板

1.1 模板概论

以下图为例子,提供了三个西装的证件照,谁都可以取拍照,可以是小孩,男女人,也可以是某些动物等等等。n那么我们这个模板也是这样,它可以是任何类型,基础类型,class型,等等等等。且会根据你的指定类型编程相对类型(配对)

模板的特点:

  • 模板不可以直接使用,它只是一个框架

  • 模板的通用并不是万能的

C++提高编—(模板,泛型,异常处理)_第1张图片

1.2 函数模板

1.2.1 函数模板概念及应用

语法:

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的类型具体化

函数模板目标:模板是为了实现泛型,可以减轻编程的工作量,提高函数的重用性

1.2.2 函数模板重载

重载,即在同一作用域下,函数名相同,类型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);
}

1.2.3 函数模板与普通函数区别

  • 普通函数调用时可以发生自动类型转换(隐式类型转换)

  • 函数模板调用时,如果利用自动类型推导,不会发生隐式类型转换

  • 如果利用显示指定类型的方式,可以发生隐式类型转换

  • 总结:建议使用显示指定类型的方式,调用函数模板,因为可以自己确定通用类型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;
}

1.3 类模板

1.3.1 类模板基本概念

类模板作用:

  • 建立一个通用类,类中的成员 数据类型可以不具体制定,用一个虚拟的类型来代表。

语法:

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;
}

1.3.2 类模板成员函数类外实现

1.3.3 类模板做函数参数

学习目标:

  • 类模板实例化出的对象,向函数传参的方式

一共有三种传入方式:

  1. 指定传入的类型 --- 直接显示对象的数据类型

  2. 参数模板化 --- 将对象中的参数变为模板进行传递

  3. 整个类模板化 --- 将这个对象类型 模板化进行传递

总结:

  • 通过类模板创建的对象,可以有三种方式向函数中进行传参

  • 使用比较广泛是第一种:指定传入的类型

 

#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;
}

1.3.4 类模板与继承

当类模板碰到继承时,需要注意一下几点:

  • 当子类继承的父类是一个类模板时,子类在声明的时候,要指定出父类中T的类型

  • 如果不指定,编译器无法给子类分配内存

  • 如果想灵活指定出父类中T的类型,子类也需变为类模板

1.3.4.1 普通类继承
#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.3.6 类模板头文件和源文件分离问题

学习目标:

  • 掌握类模板成员函数分文件编写产生的问题以及解决方式

问题:

  • 类模板中成员函数创建时机是在调用阶段,导致分文件编写时链接不到

解决:

  • 解决方式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;

1.3.7 友元类模板

直接举例说明,不做过多描述

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;
}
1.3.7.1 全局函数模板作为类模板的友元
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;
}
1.3.7.2 类中成员函数作为类模板的友元
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;
	}
};
1.3.7.3 普通函数作为类模板的友元
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;
}

1.4 综合练习

设计一个数组模板类(MyArray),完成对不同类型元素的管理。不仅能操作基本数据类型,还可以操作自定义数据类型

还是一个动态数组,可以扩容等操作

二 泛型(与Java对比) 

详述在之前写的文章,感兴趣的同学们,可以去看看。

zz​​​​​​​​​​​​​Java 自定义异常(案例)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();
	}
}

你可能感兴趣的:(C++,java,开发语言,c++,c语言,算法)