首页 > 编程语言 >c++(4)

c++(4)

时间:2023-11-23 11:22:17浏览次数:34  
标签:函数 c++ template 泛型 异常 throw 模板

C++ (4)

1. C++模板

1.1 模板的概论

C++提供了函数模板(function template),函数模板是将函数的参数类型不具体化,在函数使用时,再给定具体的参数的数据类型。

C++给定两个模板机制:函数模板和类模板

【注】都属于参数类型的模板,又称之为参数模板

1.2 函数模板

函数模板可以自动推到参数的类型,当然也可以显示指定类型。

语法:

template<class或typename T>
函数的返回值 函数名(T &n1,T &n2)
{
    //函数体
}

【推荐】在定义函数模板时,使用typename,表示T是某一种类型的名称

1.3 函数模板和普通函数的区别

  • 函数模板不允许自动类型转化

  • 普通函数能够自动进行类型转化

1.4 函数模板和普通函数在一起调用规则

  • c++编译器有限考虑普通函数
  • 可以通过空模板实参列表的语法限定编译器只能通过模板匹配
  • 函数模板可以像普通函数一样被重载
  • 如果函数模板可以产生一个更好的匹配,选择模板

1.5 模板实现机制

函数模板机制结论:编译器并不是把函数模板处理成能够处理的任何类型的函数。

函数模板通过具体类型产生不同的函数。编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。

模板的局限性:函数模板是存在一些局限性的,如果函数模板声明的泛型,在实际使用中,具体化为基础数据类型则完全可以使用,但是具体化是一个类对象时或地址时,则需要特殊的处理:

  • 具体化优先于常规模板
  • 具体化函数模板为友元函数,可以访问类对象的私有成员
  • 类中定义运算符重载

1.6 类模板

类模板是类成员变量的数据类型泛化。

类模板的声明作用于整个类,即类的内部的任何位置都可以使用

如:定义一个长方形

template <typename T>
class Rect
{
private:
    T width, height;
public:
    Rect(T w, T h) : width(w), height(h) {}
    void draw()
    {
        cout << "绘制方形 width=" << width << ",height=" << height << endl;
    }
    T length()
    {
        return (width + height) * 2;
    }
};

1.6.1 类模板作为函数参数

类模板作为函数参数时,必须指定泛型的具体类型。

当然,类模板的泛型也可以是函数模板的泛型。

// 类模板作为函数的参数, 具体化类模板
void drawShape(Rect<int> &r)
{
    r.draw();
    cout << "r周长: " << r.length() << endl;
}
// 类模板作为函数的参数, 具体化类模板
// 将类模板的泛型再次泛型,通过函数模板
template <typename T>
void drawShape(Rect<T> &r)
{
    r.draw();
    cout << "r周长: " << r.length() << endl;
}

1.6.2 类模板派生普通类

类模板派生普通类的时候,必须指定具体的泛型的数据类型,以确保子类对象创建时的具体的父类

template<typename T>
class Shape{
protected:
    T mLen;
public:
    virtual T length(){
        return mLen;
    }
};

class Rect: public Shape<int>{
private:
    int w,h;
public:
    Rect(int w, int h): w(w),h(h){
        mLen = 2*(w+h);
    }
};

class Triangle: public Shape<float>{
private:
    float a, b,c;
public:
    Triangle(float a, float b, float c){
        this->a = a;
        this->b = b;
        this->c = c;
        mLen = a+b+c;
    }
};

1.6.3 类模板派生类模板

类模板派生子类时,子类也可以是类模板,将父类中的泛型指定为子类的类模板泛型

【注】派生的子类模板的类内部不能访问父类的泛型成员,并且子类模板创建对象时,必须指定泛型的具体的类型

template <typename T>
class Shape
{
public:
    virtual T length() = 0;
};

template <typename T>
class Rect : public Shape<T>
{
private:
    T w, h;
public:
    Rect(T w, T h) : w(w), h(h)
    {
    }
    virtual T length()
    {
        return 2 * (w + h);
    }
};
void test()
{
    Rect<int> r1(10, 20);
    cout << "r1的周长: " << r1.length() << endl;
    return 0;
}

1.6.4 类模板类内实现

在类模板内部的成员函数中,可以使用泛型成员变量

template <typename T1, typename T2>
class Point
{
private:
    T1 x;
    T2 y;
public:
    Point(T1 x, T2 y) : x(x), y(y) {}
    // T1, T2是哪一个对象的泛型的具体化: 是当前类对象的泛型
    // 此函数要求:other对象的泛型同当前类对象的泛型保持一致,否则编译器认为一个其它的类
    int distancePow(Point<T1, T2> &other)
    {
        return (x - other.x) * (x - other.x) + (y - other.y) * (y - other.y);
    }
};

1.6.5 类模板类外实现

类外部实现成员函数时,指定类模板的泛型转化为函数模板

template<typename T>
class Point{
private:
    T x, y;
public:
    Point(T x, T y);
    void show();
};

template<typename T>
Point<T>::Point(T x, T y){
    this->x = x;
    this->y = y;
}

template<typename T>
void Point<T>::show(){
    cout << "x=" << x << ",y=" << y << endl;
}

1.6.6 类模板头文件与源文件分离的问题

在使用类模板的情况下,声明与实现必须放在一起

在 Linux 和 vs 编辑器下如果只包含头文件,那么会报错链接错误,需要包含 cpp 文件,但是如果类模板中有友元类,那么编译失败!

类模板需要二次编译,在出现模板的地方编译一次,在调用模板的地方再次编译。C++编译规则为独立编译。

1.6.7 模板类遇到友元函数

模板类中的友元函数的写法:1) 内部实现 2) 外部实现 3)友元函数模板

内部实现:

template <typename T>
class A
{
    // 内部实现友元函数, 接收当前类模板的引用时,可以指定当前的类模板的泛型
    // 友元函数(全局性的),类的外部直接访问,不需要类对象调用
    friend void showIn(A<T> &a)
    {
        cout << a.item << endl;
    }
private:
    T item;
public:
    A(T item)
    {
        this->item = item;
    }
};

外部实现:

外部实现的友元函数是函数模板时,必须在类定义之前声明。在类中声明友元全局函数时,必须使用<>空泛型表示此函数是函数模板。

template <typename T>
class A;

template <typename T>
void showOut(A<T> &a);

template <typename T>
class A
{
    // 外部实现的友元函数,它有自己的模板
    // <> 空泛型表示外部是 函数模板,模板的泛型同当前类的泛型
    // 要求:必须之前先声明此函数为函数模板
    friend void showOut<>(A<T> &a);
private:
    T item;
public:
    A(T item)
    {
        this->item = item;
    }
};

template <typename T>
void showOut(A<T> &a) // 全局友元函数
{
    cout << "out item is " << a.item << endl;
}

友元函数模板:

格式:

template<typename T>
friend 返回值类型 函数名(T &参数名);
template <typename T>
class A
{
    // 友元函数模板, 声明的友元函数名不需要加 <> 空泛型
    template <typename U>
    friend void show(A<U> &a);
private:
    T item;
public:
    A(T item)
    {
        this->item = item;
    }
};
template <typename U>
void show(A<U> &a)
{
    cout << "template item is " << a.item << endl;
}

2. C++类型转换

类型转换(cast)是将一种数据类型转换成另一种数据类型,一般数据类型转换由编译器自动完成,除非强制类型转换需要手动完成

【注意】一般情况下,尽量少的去使用类型转换,除非用来解决非常特殊的问题

语法:

目标类型 变量名 = 新式转换函数名<目标类型>(转换变量);

2.1 静态转换

static_cast静态转换函数模板

上行转换和下行转换:

  • 上行转换:把派生类的指针或引用转换成基类表示,安全的。【多态的体现】
  • 下行转换:把基类指针或引用转换成派生类表示,不安全的。【调用子类的扩展功能】

image-20231123100015436

1.不同的基本数据类型的指针不能相互静态转换

2.支持父类与子类的指针的静态上行或下行的转换。

3.不支持不相关的两类的引用或指针转换

2.2 动态转换

dynamic_cast动态转换函数模板

主要用于类层次间的上行转换和下行转换;

在类层次间进行上行转换时,动态和静态转换的效果是一样的

在进行下行转换时,动态转换具有类型检查的功能,比静态更安全

不支持基本的数据类型之间的转换

2.3 常量转换

const_cast常量转换函数模板

可以将指针或引用转化为const指针或引用变量,也可以将const指针或引用变量转化为指针或引用变量

【注】不能直接对非指针和非引用的变量使用const_cast去直接移除它的const

2.4 重新解释转换

reinterpret_cast重新解释转换函数模板

主要用于将一种数据类型转为另一种;它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针。

这是最不安全的一种转换机制。

3. C++异常

3.1 异常基本概念

C语言中处理异常的方式:不会中断程序的执行

1)返回常量值,如0成功,1失败
2)宏 errno (类似于全局整数类型的变量),记录程序出现异常的标识
   perror()进行打印错误信息
3)NULL 指针的NULL表示,表示异常的情况
  FILE *f = fopen("不存在的文件","r");

C++处理异常的方式:抛出异常、捕获异常

3.2 C++异常语法

3.2.1 throw抛出异常

语法:throw 异常数据

异常数据包含基本数据类型、类、struct等

【注】如果抛出的异常没有处理时,则会中断程序的执行

3.2.2 处理异常的语法

语法格式:

try{
    执行可能存在异常的语句块;
}catch(异常数据的类型1 变量){ //捕获相应数据类型的异常信息
    //处理异常的语句;
}catch(异常数据的类型2 变量){
    //处理异常的语句;
}
...
catch(...){ //必须放在最后
    //以上异常类型都没有匹配成功时,执行的语句        
}

【小结】如果异常被捕获之后,程序则不会中断。捕获异常信息的时候,一定考虑异常信息的数据类型。

3.2.3 throw的限制与严格类型异常匹配

在使用throw抛出异常信息时,受到函数声明处的throw()声明的可抛出异常类型的限制

1.函数内可以抛出任何异常

#include <iostream>
using namespace std;
class A
{
public:
    int n;
    A(int n) : n(n) {}
};

void show(int x)
{
    if (x == 1)
        throw 0;
    else if (x == 2)
        throw 'a';
    else if (x == 3)
        throw "abc";
    else if (x == 4)
        throw 1.25;
    else if (x == 5)
        throw A(100);
    cout << "x=" << x << endl;
}
int main()
{
    try
    {
        show(5);
    }
    catch (int error)
    { // 与throw int 匹配
        cout << "error is " << error << endl;
    }
    catch (const char &error)
    {
        cout << "error is " << error << endl;
    }
    catch (A &error)
    {
        cout << "error is " << error.n << endl;
    }
}

image-20231123103546030

2.限制函数抛出的异常类型

#include <iostream>
using namespace std;

class A
{
public:
    int n;
    A(int n) : n(n) {}
};

void show(int x) throw(int,char)
{
    if (x == 1)
        throw 0;
    else if (x == 2)
        throw 'a';
    else if (x == 3)
        throw "abc";
    else if (x == 4)
        throw 1.25;
    else if (x == 5)
        throw A(100);
    cout << "x=" << x << endl;
}

int main()
{
    try
    {
        show(5);
    }
    catch (int error)
    { // 与throw int 匹配
        cout << "error is " << error << endl;
    }
    catch (const char &error)
    {
        cout << "error is " << error << endl;
    }
    catch (A &error)
    {
        cout << "error is " << error.n << endl;
    }
}

image-20231123103659590

3.限制函数抛出异常

在函数的声明位置使用throw()

#include <iostream>
using namespace std;
void show(int x) throw()
{
    if (x == 1)
        throw 0;
    cout << "x=" << x << endl;
}
int main()
{
    try
    {
        show(1);
    }
    catch (int error)
    { // 与throw int 匹配
        cout << "error is " << error << endl;
    }
}

image-20231123103944233

3.2.4 栈解旋(unwinding)

异常被抛出后,从进入try块起,到异常被抛掷前,这期间在栈上构造的所有对象,都会被自动析构,析构的顺序与构造的顺序相反,这一过程称为栈的解旋

#include <iostream>
using namespace std;
class A
{
public:
    A()
    {
        cout << "A()" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
};
int main(int argc, char const *argv[])
{
    try
    {
        A a1;
        throw 0; // 抛出异常时,则会回收 a1栈中的空间(解旋)
    }
    catch (...)
    {
        cout << "异常被处理" << endl;
    }
    return 0;
}

image-20231123104228759

3.2.5 异常接口声明

在声明函数时,可以声明throw()可抛出的异常接口(基本数据类型、类、结构体)。即为函数内限制throw抛出异常信息的类型

void func();则此函数可以抛任何类型的异常 
void func() throw(A,B,C);这个函数func能够且只能抛出类型A,B,C及其子类型的异常 
void func() throw();不抛任何类型异常的函数 

3.2.6 异常变量生命周期

catch()捕获异常信息的对象的生成周期,如果声明时变量或对象时,会在捕获到时,则会在栈中临时会创建变量或对象空间

3.2.7 异常的多态

throw抛出的异常信息时属于子类对象,catch捕获异常的类型是父类的引用或指针

3.3 C++标准异常库

C++提供了异常类的基类:exception

3.3.1 标准异常库的说明

c++提供的异常类的常用类:使用时引入<exception>头文件

image-20231123105019067

标准异常类的成员:

1)每个类都有提供了构造函数,拷贝构造函数和赋值操作符重载
2)logic_error 类及其子类、runtime_error类及其子类,它们的构造函数是接受一个string类型的形式参数,用于异常信息的描述
3)所有的异常类都有一个what()方法,返回const char* 类型(C风格字符串)的值,描述异常信息

标准异常类的具体描述:

exception   所有标准异常类的父类
bad_alloc   当operator new and operator new[]请求分配内存失败时
bad_exception 这是个特殊的异常,如果函数的异常抛出列表里声明了bad_exception异常,当函数内部抛出了异常抛出列表中没有的异常,这时调用的 unexpected 函数中若抛出异常,不论什么类型,都会
被替换为 bad_exception 类型。
bad_typeid 使用typeid操作符,操作一个NULL指针,而该指针是带有虚函数的类,这时抛出bad_typeid异常
bad_cast 使用dynamic_cast转换引用失败的时候
ios_base::failure io操作过程出现错误
logic_error 逻辑错误,可以在运行前检测的错误
runtime_error 运行时错误,仅在运行时才可以检测的错误

logic_error的子类:

length_error 试图生成一个超出该类型最大长度的对象时,例如vector的resize操作
domain_error 参数的值域错误,主要用在数学函数中,例如使用一个负值调用只能操作非负数的函数
out_of_range 超出有效范围
invalid_argument 参数不适合,在标准库中,当利用string对象构造bitset时,而string中的字符不是'0'或'1'的时候,抛出该异常

runtime_error的子类:

range_error 计算结果超出了有意义的值域范围
overflow_error 算术计算上溢
underflow_error 算术计算下溢

3.3.2 自定义标准异常类

1.自己的异常类要继承标准异常类

因为C++中可以抛出任何类型的异常,所以我们的异常类可以不继承自标准异常,但是这样可能会导致程序混乱,尤其是当我们多人协同开发时。

2.当继承标准异常类时,应该重载父类的what函数和虚析构函数。

3.因为栈展开的过程中,要赋值异常类型,那么要根据你在类中添加的成员考虑是否提供自己的复制构造函数

如:自定义OutOfRangeError

class OutOfRangeError : public exception
{
public:
    virtual const char *what() const throw()
    {
        return "访问位置越界";
    }
};

标签:函数,c++,template,泛型,异常,throw,模板
From: https://www.cnblogs.com/dijun666/p/17851149.html

相关文章

  • C/C++ 开发SCM服务管理组件
    SCM(ServiceControlManager)服务管理器是Windows操作系统中的一个关键组件,负责管理系统服务的启动、停止和配置。服务是一种在后台运行的应用程序,可以在系统启动时自动启动,也可以由用户或其他应用程序手动启动。本篇文章中,我们将通过使用Windows的服务管理器(SCM)提供的API接口,......
  • wxwidgets实战手册-c++(2)
    目录oninitoninit继承自wxApp的子类,可将它视为main和winmain,应用程序的入口//wxWidgets"HelloWorld"Program//Forcompilersthatsupportprecompilation,includes"wx/wx.h".#include<wx/wxprec.h>#ifndefWX_PRECOMP#include<wx/wx.h>......
  • 哈夫曼编码和解码(c++实现)
    给一篇英文文章(text),统计各字符出现(仅需包括英文大小写字母)次数。1) 输出每个字符出现的次数,并进行Huffman树构造,将每个字符的编码存入到文件code1.txt。2) 输出字符串”DataStructure”的编码。3)将英文文章前4段的Huffman编码保存到文件code2.txt。4)实现解码功能,对文章的前2......
  • C/C++ 实现Windows注册表操作
    Windows注册表(Registry)是Windows操作系统中用于存储系统配置信息、用户设置和应用程序数据的一个集中式数据库。它是一个层次结构的数据库,由键(Key)和值(Value)组成,这些键和值被用于存储各种系统和应用程序的配置信息。以下是注册表的一些基本概念:键(Key):注册表中的数据结构,类似于文......
  • 159.102 C++问题求解
    一家生产纽扣的工厂给了你一份合同。工厂识别损坏的按钮,使其不会提供给商店。这家工厂有一台可以拍摄纽扣的照片。这台相机只能用黑白(没有颜色),分辨率不是很高很好,但这不是问题。你的工作是编写一个C++程序,识别照片中任何损坏的按钮。你需要生成一个图像,在每个按钮周围显示一个框。......
  • ISOM 3029 - Computer Programming Using C++
    以下是C++程序的问题。作业的硬拷贝和软拷贝都应按时提交。全部的程序(.cpp文件)将被压缩并上传到“提交作业1”按钮。压缩文件应与您的学生一起命名编号,例如“ba12345_Ass1.zip”。问题1:彩票计划(50%)编写一个C++程序,在开始时输出一条问候信息,然后生成6个肯定信息对于彩票游......
  • 【笔记】C++系列02:连续的作用域解析运算符::的场景有哪些?
    在C++中,可以使用连续的作用域解析运算符::来访问嵌套的命名空间、类和类成员。这种用法通常在以下场景下出现:命名空间嵌套:当命名空间中存在嵌套的命名空间时,可以使用连续的作用域解析运算符来访问内层命名空间中的成员。例如:namespaceA{namespaceB{namespac......
  • C++ MiniZip实现目录压缩与解压
    Zlib是一个开源的数据压缩库,提供了一种通用的数据压缩和解压缩算法。它最初由Jean-LoupGailly和MarkAdler开发,旨在成为一个高效、轻量级的压缩库,其被广泛应用于许多领域,包括网络通信、文件压缩、数据库系统等。其压缩算法是基于DEFLATE算法,这是一种无损数据压缩算法,通常能够提供......
  • L1-8 阅览室 (20 分)(C/C++)
    天梯图书阅览室请你编写一个简单的图书借阅统计程序。当读者借书时,管理员输入书号并按下S键,程序开始计时;当读者还书时,管理员输入书号并按下E键,程序结束计时。书号为不超过1000的正整数。当管理员将0作为书号输入时,表示一天工作结束,你的程序应输出当天的读者借书次数和平均阅读时间......
  • C/C++ 使用API实现数据压缩与解压缩
    在Windows编程中,经常会遇到需要对数据进行压缩和解压缩的情况,数据压缩是一种常见的优化手段,能够减小数据的存储空间并提高传输效率。Windows提供了这些API函数,本文将深入探讨使用WindowsAPI进行数据压缩与解压缩的过程,主要使用ntdll.dll库中的相关函数。关键函数介绍RtlGetComp......