数据结构开发(1):学习前的准备(上)

0.目录

1.泛型编程简介

2.智能指针示例

3.异常类构建

4.小结

参考前文传送门:
C++解析(26):函数模板与类模板
C++解析(20):智能指针与类型转换函数
C++解析(28):异常处理

1.泛型编程简介

数据结构课程的特点:

  • 专注于数据元素之间的关系
  • 专注于特定结构之上的算法

数据结构课程并不关注数据元素的具体类型!

如何为数据结构的学习选择合适的语言?
支持泛型编程的语言最适合数据结构课程的学习?

泛型编程的概念——不考虑具体数据类型的编程方式

C++中的函数模板:

  • 一种特殊的函数可用不同类型进行调用
  • 看起来和普通函数很相似,区别是类型可被参数化

函数模板的语法规则:

  • template关键字用于声明开始进行泛型编程
  • typename关键字用于声明泛型类型

函数模板的使用:

  • 自动类型推导调用
  • 具体类型显示调用

C++中的类模板:

  • 以相同的方式处理不同的类型
  • 在类声明前使用template进行标识
  • <typename T>用于说明类中使用的泛指类型 T

类模板的应用:

  • 只能显示指定具体类型,无法自动推导
  • 使用具体类型<Type>定义对象

(使用QtCreator创建Template项目)
示例——使用函数模板和类模板:

#include <iostream>

using namespace std;

template <typename T>
void Swap(T& a, T& b)
{
    T t = a;
    a = b;
    b = t;
}

template <typename T>
class Op
{
public:
    T process(T v)
    {
        return v * v;
    }
};

int main()
{
    int a = 2;
    int b = 1;

    Swap(a, b);

    cout << "a = " << a << ", " << "b = " << b << endl;

    double c = 0.01;
    double d = 0.02;

    Swap<double>(d, c);

    cout << "c = " << c << ", " << "d = " << d << endl;

    Op<int> opInt;
    Op<double> opDouble;

    cout << "5 * 5 = " << opInt.process(5) << endl;
    cout << "0.3 * 0.3 = " << opDouble.process(0.3) << endl;

    return 0;
}

运行结果为:

a = 1, b = 2
c = 0.02, d = 0.01
5 * 5 = 25
0.3 * 0.3 = 0.09

2.智能指针示例

内存泄漏(臭名昭著的Bug):

  • 动态申请堆空间,用完后不归还
  • C++语言中没有垃圾回收机制
  • 指针无法控制所指堆空间的生命周期

当代C++软件平台中的智能指针

  • 指针生命周期结束时主动释放堆空间
  • 一片堆空间最多只能由一个指针标识
  • 杜绝指针运算和指针比较

智能指针的设计方案:

  • 通过类模板描述指针的行为
    1. 能够定义不同类型的指针对象
  • 重载指针特征操作符-> *
    1. 利用对象模拟原生指针的行为

(使用QtCreator创建StLib项目)
示例——实现智能指针类模板SmartPointer.h:

#ifndef SMARTPOINTER_H
#define SMARTPOINTER_H

namespace StLib {

template <typename T>
class SmartPointer
{
protected:
    T* m_pointer;

public:
    SmartPointer(T* p = NULL)
    {
        m_pointer = p;
    }

    SmartPointer(const SmartPointer<T>& obj)
    {
        m_pointer = obj.m_pointer;

        const_cast<SmartPointer<T>&>(obj).m_pointer = NULL;
    }

    SmartPointer<T>& operator= (const SmartPointer<T>& obj)
    {
        if( this != &obj )
        {
            delete m_pointer;

            m_pointer = obj.m_pointer;

            const_cast<SmartPointer<T>&>(obj).m_pointer = NULL;
        }

        return *this;
    }

    T* operator-> ()
    {
        return m_pointer;
    }

    T& operator* ()
    {
        return *m_pointer;
    }

    bool isNull()
    {
        return (m_pointer == NULL);
    }

    T* get()
    {
        return m_pointer;
    }

    ~SmartPointer()
    {
        delete m_pointer;
    }
};

}

#endif // SMARTPOINTER_H
#include <iostream>
#include "SmartPointer.h"

using namespace std;
using namespace StLib;

class Test
{
public:
    Test()
    {
        cout << "Test()" << endl;
    }

    ~Test()
    {
        cout << "~Test()" << endl;
    }
};

int main()
{
    SmartPointer<Test> sp = new Test();
    SmartPointer<Test> nsp;

    nsp = sp;

    cout << sp.isNull() << endl;
    cout << nsp.isNull() << endl;

    return 0;
}

运行结果为:

Test()
1
0
~Test()

智能指针的使用军规——只能用来指向堆空间中的对象或者变量

3.异常类构建

3.1 C++异常简介

C++内置了异常处理的语法元素try ... catch ...

  • try语句处理正常代码逻辑
  • catch语句处理异常情况
  • try语句中的异常由对应的catch语句处理

C++通过throw语句抛出异常信息:

throw抛出的异常必须被catch处理:

  • 当前函数能够处理异常,程序继续往下执行
  • 当前函数无法处理异常,则函数停止执行,并返回

未被处理的异常会顺着函数调用栈向上传播,直到被处理为止,否则程序将停止执行。

同一个try语句可以跟上多个catch语句:

  • catch语句可以定义具体处理的异常类型
  • 不同类型的异常由不同的catch语句负责处理
  • try语句中可以抛出任何类型的异常
  • catch(...)用于处理所有类型的异常
  • 任何异常都只能被捕获(catch)一次

异常处理的匹配规则:

(使用QtCreator创建Exception项目)
示例——异常类型匹配:

#include <iostream>

using namespace std;

double divide(double a, double b)
{
    const double delta = 0.000000000000001;
    double ret = 0;

    if( !((-delta < b) && (b < delta)) ) {
        ret = a / b;
    }
    else {
        throw 0;   // 产生除 0 异常
    }

    return ret;
}

void Demo1()
{
    try
    {
        throw 'c';
    }
    catch(int i)
    {
        cout << "catch(int i)" << endl;
    }
    catch(double d)
    {
        cout << "catch(double d)" << endl;
    }
    catch(char c)
    {
        cout << "catch(char c)" << endl;
    }
}

void Demo2()
{
    throw 0.0001; // "HelloWorld"; // const char*
}

int main()
{
    cout << "main() begin" << endl;

    try
    {
        double c = divide(1, 1);

        cout << "c = " << c << endl;
    }
    catch(...)
    {
        cout << "Divided by zero..." << endl;
    }

    Demo1();

    try
    {
        Demo2();
    }
    catch(char* c)
    {
        cout << "catch(char* c)" << endl;
    }
    catch(const char* cc)
    {
        cout << "catch(const char* cc)" << endl;
    }
    catch(...)
    {
        cout << "catch(...)" << endl;
    }

    cout << "main() end" << endl;

    return 0;
}

运行结果为:

main() begin
c = 1
catch(char c)
catch(...)
main() end

3.2 异常类构建

C++中的异常处理:

  • 异常的类型可以是自定义类类型
  • 对于类类型异常的匹配依旧是至上而下严格匹配
  • 赋值兼容性原则在异常匹配中依然适用
  • 一般而言
    1. 匹配子类异常的catch放在上部
    2. 匹配父类异常的catch放在下部

(根据赋值兼容性原则:子类的异常对象可以被父类的catch语句块抓住。)

现代C++库中必然包含充要的异常类族
异常类是数据结构类所依赖的“基础设施”

异常类功能定义:

异常类中的接口定义:

(在StLib中实现异常类族)
示例1——创建异常类族Exception类:
实现Exception.h

#ifndef EXCEPTION_H
#define EXCEPTION_H

using namespace std;

namespace StLib
{

#define THROW_EXCEPTION(e, m) (throw e(m, __FILE__, __LINE__))

class Exception
{
protected:
    char* m_message;
    char* m_location;

    void init(const char* message, const char* file, int line);
public:
    Exception(const char* message);
    Exception(const char* file, int line);
    Exception(const char* message, const char* file, int line);

    Exception(const Exception& e);
    Exception& operator= (const Exception& e);

    virtual const char* message() const;
    virtual const char* location() const;

    virtual ~Exception();
};

}

#endif // EXCEPTION_H

实现Exception.cpp

#include "Exception.h"
#include <cstring>
#include <cstdlib>

namespace StLib
{

void Exception::init(const char* message, const char* file, int line)
{
    /* message指向的字符串有可能在栈上,有可能在堆空间,还有可能在全局数据区
     * strdup()将字符串复制一份到堆空间中
     * file:发生异常的文件名
     * line:发生异常的行号
     * m_location的长度加2,一个给":",一个给""
     */
    m_message = strdup(message);

    if( file != NULL )
    {
        char sl[16] = {0};

        itoa(line, sl, 10);

        m_location = static_cast<char*>(malloc(strlen(file) + strlen(sl) + 2));
        m_location = strcpy(m_location, file);
        m_location = strcat(m_location, ":");
        m_location = strcat(m_location, sl);
    }
    else
    {
        m_location = NULL;
    }
}

Exception::Exception(const char *message)
{
    init(message, NULL, 0);
}

Exception::Exception(const char* file, int line)
{
    init(NULL, file, line);
}

Exception::Exception(const char* message, const char* file, int line)
{
    init(message, file, line);
}

Exception::Exception(const Exception& e)
{
    m_message = strdup(e.m_message);
    m_location = strdup(e.m_location);
}

Exception &Exception::operator= (const Exception& e)
{
    if( this != &e )
    {
        free(m_message);
        free(m_location);

        m_message = strdup(e.m_message);
        m_location = strdup(e.m_location);
    }

    return *this;
}

const char* Exception::message() const
{
    return m_message;
}

const char* Exception::location() const
{
    return m_location;
}

Exception::~Exception()
{
    free(m_message);
    free(m_location);
}

}

main.cpp测试

#include <iostream>
#include "Exception.h"

using namespace std;
using namespace StLib;

int main()
{
    try
    {
        THROW_EXCEPTION(Exception, "test"); // ==>throw Exception("test", __FILE__, __LINE__);
    }
    catch(const Exception& e)
    {
        cout << "catch(const Exception& e)" << endl;
        cout << e.message() << endl;
        cout << e.location() << endl;
    }

    return 0;
}

运行结果为:

catch(const Exception& e)
test
..StLibmain.cpp:11

(在StLib中实现异常类族)
示例2——实现ArithmeticException.h:

#ifndef EXCEPTION_H
#define EXCEPTION_H

using namespace std;

namespace StLib
{

#define THROW_EXCEPTION(e, m) (throw e(m, __FILE__, __LINE__))

class Exception
{
protected:
    char* m_message;
    char* m_location;

    void init(const char* message, const char* file, int line);
public:
    Exception(const char* message);
    Exception(const char* file, int line);
    Exception(const char* message, const char* file, int line);

    Exception(const Exception& e);
    Exception& operator= (const Exception& e);

    virtual const char* message() const;
    virtual const char* location() const;

    virtual ~Exception() = 0;
};

class ArithmeticException : public Exception
{
public:
    ArithmeticException() : Exception(0) { }
    ArithmeticException(const char* message) : Exception(message) { }
    ArithmeticException(const char* file, int line) : Exception(file, line) { }
    ArithmeticException(const char* message, const char* file, int line) : Exception(message, file, line) { }

    ArithmeticException(const ArithmeticException& e) : Exception(e) { }
    ArithmeticException& operator= (const ArithmeticException& e)
    {
        Exception::operator=(e);

        return *this;
    }
};

}

#endif // EXCEPTION_H

main.cpp测试

#include <iostream>
#include "Exception.h"

using namespace std;
using namespace StLib;

int main()
{
    try
    {
        THROW_EXCEPTION(ArithmeticException, "test");
    }
    catch(const ArithmeticException& e)
    {
        cout << "catch(const ArithmeticException& e)" << endl;
        cout << e.message() << endl;
        cout << e.location() << endl;
    }
    catch(const Exception& e)
    {
        cout << "catch(const Exception& e)" << endl;
        cout << e.message() << endl;
        cout << e.location() << endl;
    }

    return 0;
}

运行结果为:

catch(const ArithmeticException& e)
test
..StLibmain.cpp:11

(剩下的四个子类的实现类似于ArithmeticException类。)

设计原则:
在可复用代码库设计时,尽量使用面向对象技术进行架构,尽量使用异常处理机制分离正常逻辑和异常逻辑。

4.小结

  • 模板是泛型编程理论在C++中的实现
  • 函数模板支持参数的自动推导显示指定
  • 类模板在使用时只能显示指定类型
  • 类模板非常适用于编写数据结构相关的代码
  • 指针特征操作符( -> * )可以被重载
  • 重载指针特征符能够使用对象代替指针
  • 智能指针只能用于指向堆空间中的内存
  • 智能指针的意义在于最大程度的避免内存问题
  • C++中直接支持异常处理的概念
  • try ... catch ... 是C++中异常处理的专用语句
  • try语句处理正常代码逻辑,catch语句处理异常情况
  • 同一个try语句可以跟上多个catch语句
  • 异常处理必须严格匹配,不进行任何的类型转换
  • 现代C++库必然包含充要的异常类族
  • 所有库中的数据结构类都依赖于异常机制
  • 异常机制能够分离库中代码的正常逻辑异常逻辑

原文地址:https://www.cnblogs.com/PyLearn/p/10105886.html