数据结构开发(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++软件平台中的智能指针:
- 指针生命周期结束时主动释放堆空间
- 一片堆空间最多只能由一个指针标识
- 杜绝指针运算和指针比较
智能指针的设计方案:
- 通过类模板描述指针的行为
- 能够定义不同类型的指针对象
- 重载指针特征操作符( -> 和 * )
- 利用对象模拟原生指针的行为
(使用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++中的异常处理:
- 异常的类型可以是自定义类类型
- 对于类类型异常的匹配依旧是至上而下严格匹配
- 赋值兼容性原则在异常匹配中依然适用
- 一般而言
- 匹配子类异常的catch放在上部
- 匹配父类异常的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,一个给":",一个给"\0"
*/
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
..\StLib\main.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
..\StLib\main.cpp:11
(剩下的四个子类的实现类似于ArithmeticException类。)
设计原则:
在可复用代码库设计时,尽量使用面向对象技术进行架构,尽量使用异常处理机制分离正常逻辑和异常逻辑。
4.小结
- 模板是泛型编程理论在C++中的实现
- 函数模板支持参数的自动推导和显示指定
- 类模板在使用时只能显示指定类型
- 类模板非常适用于编写数据结构相关的代码
- 指针特征操作符( -> 和 * )可以被重载
- 重载指针特征符能够使用对象代替指针
- 智能指针只能用于指向堆空间中的内存
- 智能指针的意义在于最大程度的避免内存问题
- C++中直接支持异常处理的概念
- try ... catch ... 是C++中异常处理的专用语句
- try语句处理正常代码逻辑,catch语句处理异常情况
- 同一个try语句可以跟上多个catch语句
- 异常处理必须严格匹配,不进行任何的类型转换
- 现代C++库必然包含充要的异常类族
- 所有库中的数据结构类都依赖于异常机制
- 异常机制能够分离库中代码的正常逻辑和异常逻辑