C++深度解析教程笔记14
本文学习自狄泰软件学院 唐佐林老师的 C++深度解析教程,图片全部来源于课程PPT,仅用于个人学习记录
第56课 - 函数模板的概念和意义
交换变量的方法
定义宏代码块
Vs
定义函数
C++中有几种交换变量的方法?
实验-宏定义和函数交换变量
#include <iostream>
#include <string>
using namespace std;
#define SWAP(t, a, b) \
do \
{ \
t c = a; \
a = b; \
b = c; \
}while(0)
void Swap(int& a, int& b)
{
int c = a;
a = b;
b = c;
}
void Swap(double& a, double& b)
{
double c = a;
a = b;
b = c;
}
void Swap(string& a, string& b)
{
string c = a;
a = b;
b = c;
}
int main()
{
int a = 0;
int b = 1;
SWAP(int ,a,b);
//Swap(a, b);
cout << "a = " << a << endl;
cout << "b = " << b << endl;
double m = 2;
double n = 3;
SWAP(double ,m,n);
//Swap(m, n);
cout << "m = " << m << endl;
cout << "n = " << n << endl;
string d = "Delphi";
string t = "Tang";
Swap(d, t);
cout << "d = " << d << endl;
cout << "t = " << t << endl;
return 0;
}
交换变量的方法
定义宏代码块
优点:代码复用,适合所有的类型
一缺点:编译器不知道宏的存在,缺少类型检查
定义函数
一优点:真正的函数调用,编译器对类型进行检查
一缺点:根据类型重复定义函数,无法代码复用
函数模板
C++中泛型编程函数模板
·一种特殊的函数可用不同类型进行调用
·看起来和普通函数很相似,区别是类型可被参数化
template<typename T>
void Swap(T& a,T&b)
{
T t=a;
a=b;
b=t;
}
函数模板的使用
一 自动类型推导调用
一具体类型显示调用
int a = 0; int b = l;
Swap(a,b); //自动推导
float c = 2;
float d = 3;
Swap<float>(c,d);//显示调用
实验-函数模板
#include<iostream>
#include<string>
using namespace std;
template<typename T>
void Swap(T& a,T& b)
{
T c=a;
a=b;
b=c;
}
template<typename T>
void Sort(T a[],int len)
{
for(int i=0;i<len;i++)
{
for(int j=i+1;j<len;j++)
{
if(a[i]>a[j])Swap(a[i],a[j]);
}
}
}
template<typename T>
void PrintIn(T a[],int len)
{
for(int i=0;i<len;i++)
{
cout<<a[i]<<", ";
}
cout << endl;
}
int main()
{
int a=1;
int b=2;
Swap(a,b);
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
int c[5]={3,5,1,2,7};
Sort(c,5);
PrintIn(c,5);
string s[5] = {"Java", "C++", "Pascal", "Ruby", "Basic"};
PrintIn(s, 5);
Sort(s, 5);
PrintIn(s, 5);
return 0;
}
小结
丞数模板是泛型编程在C+ + 中的应用方式之一
函数模板能够根据实参对参数类型进行推导
函数模板支持显示的指定参数类型
函数模板是C++ 中重要的代码复用方式
第57课 - 深入理解函数模板
函数模板
函数模板深入理解
一编译器从函数模板通过具体类型产生不同的函数
一编译器会对函数模板进行两次编译
,对模板代码本身进行编译
,对参数替换后的代码进行编译
注意事项:
一函数模板本身不允许隐式类型转换
·自动推导类型时,必须严格匹配
·显示类型指定时,能够进行隐式类型转换
实验-函数模板两次编译
#include <iostream>
#include <string>
using namespace std;
class Test
{
Test(const Test&);
public:
Test()
{
}
};
template < typename T >
void Swap(T& a, T& b)
{
T c = a;
a = b;
b = c;
}
typedef void(FuncI)(int&, int&);
typedef void(FuncD)(double&, double&);
typedef void(FuncT)(Test&, Test&);
int main()
{
FuncI* pi = Swap; // 编译器自动推导 T 为 int
FuncD* pd = Swap; // 编译器自动推导 T 为 double
//FuncT* pt = Swap; // 编译器自动推导 T 为 Test error: 'Test::Test(const Test&)' is private within this context
cout << "pi = " << reinterpret_cast<void*>(pi) << endl;//pi = 0x402d80
cout << "pd = " << reinterpret_cast<void*>(pd) << endl;//pd = 0x402d30
// cout << "pt = " << reinterpret_cast<void*>(pt) << endl;
return 0;
}
多参数函数模板
■对于多参数函数模板
无法自动推导返回值类型
一可以从左向右部分指定类型参数
// Tl = int. T2 = double. T3 = double
int rl = Add<int>(0.5, 0.8):
// Tl = int, T2 = float, T3 = double
int r2 =Add<int,float>(o.5,Q.8);
// Tl = int, T2 = float T3 = float
int r3 = Add<int,float, float>(0.5, 0.8);
工程中将返回值参数作为第一个类型参数!
实验-多参数函数模板
#include <iostream>
#include <string>
using namespace std;
template
< typename T1, typename T2, typename T3 >
T1 Add(T2 a, T3 b)
{
return static_cast<T1>(a + b);
}
int main()
{
// T1 = int, T2 = double, T3 = double
int r1 = Add<int>(0.5, 0.8);
// T1 = double, T2 = float, T3 = double
double r2 = Add<double, float>(0.5, 0.8);
// T1 = float, T2 = float, T3 = float
float r3 = Add<float, float, float>(0.5, 0.8);
cout << "r1 = " << r1 << endl; // r1 = 1
cout << "r2 = " << r2 << endl; // r2 = 1.3
cout << "r3 = " << r3 << endl; // r3 = 1.3
return 0;
}
重载函数模板
实验-函数模板重载
#include <iostream>
#include <string>
using namespace std;
template < typename T >// 函数模板 2个参数
T Max(T a, T b)
{
cout << "T Max(T a, T b)" << endl;
return a > b ? a : b;
}
int Max(int a, int b)// 普通函数
{
cout << "int Max(int a, int b)" << endl;
return a > b ? a : b;
}
template < typename T >// 函数模板 3个参数
T Max(T a, T b, T c)
{
cout << "T Max(T a, T b, T c)" << endl;
return Max(Max(a, b), c);
}
int main()
{
int a = 1;
int b = 2;
cout << Max(a, b) << endl; // 普通函数 Max(int, int)
cout << Max<>(a, b) << endl; // 函数模板 Max<int>(int, int)
cout << Max(3.0, 4.0) << endl; // 函数模板 Max<double>(double, double)
cout << Max(5.0, 6.0, 7.0) << endl; // 函数模板 Max<double>(double, double, double)
cout << Max('a', 100) << endl; // 普通函数 Max(int, int)
return 0;
}
小结
函数模板通过真体类型产生不同的函数
函数模板可以定义任意多个不同的类型参数
函数模板中的返回值类型必须显示指定
函数模板可以像普通函数一样被重载
第58课 - 类模板的概念和意义
类模板
C++中的类模板
一以相同的方式处理不同的类型
一在类声明前使用 template 进行标识
一 < typename T >用于说明类中使用的泛指类型 T
template < typename T>
class Operator{
public:
T op(T a,T b);
};
一些类主要用于存储和组织数据元素
类中数据组织的方式和数据元素的具体类型无关
如:数组类,链表类,Stack类,Queue 类,等
C++中将模板的思想应用于类,使得类的实现不关注数据元素的具体类型,而只关注类所需要实现的功能
在C++ 中是否能够将泛型的思想应用干类?
类模板
声明的泛指类型T可以出现在类模板的任意地方
编译器对类模板的处理方式和函数模板相同,
一从类模板通过具体类型产生不同的类
一在声明的地方对类模板代码本身进行编译
一在使用的地方对参数替换后的代码进行编译
类模板的应用
一只能显示指定具体类型,无法自动推导
一 使用具体类型 定义对象
Operator<int>opl;
Operator<string> op2;
int i = opl.op(l,2);
string s = op2.op("D.T.", "Software"):
实验-类模板
#include <iostream>
#include <string>
using namespace std;
template < typename T >//类模板
class Operator
{
public:
T add(T a, T b)
{
return a + b;
}
T minus(T a, T b)
{
return a - b;
}
T multiply(T a, T b)
{
return a * b;
}
T divide(T a, T b)
{
return a / b;
}
};
string operator-(string& l, string& r)
{
return "Minus";
}
int main()
{
Operator<int> op1;
cout << op1.add(1, 2) << endl;
Operator<string> op2;
cout << op2.add("D.T.", "Software") << endl;
cout << op2.minus("D.T", "Software") << endl;
return 0;
}
类模板的工程应用
一类模板必须在头文件中定义
一类模板不能分开实现在不同的文件中
一类模板外部定义的成员函数需要加上模板<>声明
实验-类模板的工程应用
//Operator.h
#ifndef _OPERATOR_H_
#define _OPERATOR_H_
template < typename T >
class Operator
{
public:
T add(T a, T b);
T minus(T a, T b);
T multiply(T a, T b);
T divide(T a, T b);
};
template < typename T >
T Operator<T>::add(T a, T b)
{
return a + b;
}
template < typename T >
T Operator<T>::minus(T a, T b)
{
return a - b;
}
template < typename T >
T Operator<T>::multiply(T a, T b)
{
return a * b;
}
template < typename T >
T Operator<T>::divide(T a, T b)
{
return a / b;
}
#endif
//
#include <iostream>
#include <string>
#include "Operator.h"
using namespace std;
int main()
{
Operator<int> op1;
cout << op1.add(1, 2) << endl;
cout << op1.multiply(4, 5) << endl;
cout << op1.minus(5, 6) << endl;
cout << op1.divide(10, 5) << endl;
return 0;
}
小结
泛型编程的思想可以应用于类
类模板以相同的方式处理不同类型的数据
类模板非常适用于编写数据结构相关的代码
类模板在使用时只能显示指定类型
第59课 - 类模板深度剖析
实验-类的部分特化与完全特化
#include <iostream>
#include <string>
using namespace std;
template //类模板 加法 模板1
< typename T1, typename T2 >
class Test
{
public:
void add(T1 a, T2 b)
{
cout << "void add(T1 a, T2 b)" << endl;
cout << a + b << endl;
}
};
/**/
template
< typename T1, typename T2 >
class Test < T1*, T2* > // 关于指针的特化实现 模板4
{
public:
void add(T1* a, T2* b)
{
cout << "void add(T1* a, T2* b)" << endl;
cout << *a + *b << endl;
}
};
template
< typename T >
class Test < T, T > // 当 Test 类模板的两个类型参数完全相同时,使用这个实现 模板2
{
public:
void add(T a, T b)
{
cout << "void add(T a, T b)" << endl;
cout << a + b << endl;
}
void print()
{
cout << "class Test < T, T >" << endl;
}
};
template
< >
class Test < void*, void* > // 当 T1 == void* 并且 T2 == void* 时 模板3
{
public:
void add(void* a, void* b)
{
cout << "void add(void* a, void* b)" << endl;
cout << "Error to add void* param..." << endl;
}
};
int main()
{
Test<int, float> t1;
Test<long, long> t2;
Test<void*, void*> t3;//模板3
t1.add(1, 2.5);//模板1
//void add(T1 a, T2 b)
//3.5
t2.add(5, 5);//模板2
//void add(T a, T b)
//10
t2.print();
//class Test < T, T >
t3.add(NULL, NULL);
//void add(void* a, void* b)
//Error to add void* param...
Test<int*, double*> t4;//模板4
int a = 1;
double b = 0.1;
t4.add(&a, &b);
//void add(T1* a, T2* b)
//1.1
return 0;
}
类模板特化与重定义有区别吗?
函数模板可以特化吗?
多参数类模板
■类模板特化注意事项
一特化只是模板的分开实现
·本质上是同一个类模板
一特化类模板的使用方式是统一的
必须显示指定每一个类型参数
特化的深度分析,
函数模板只支持类型参数完全特化
template
< typename T >//函数模板定义
boolEgual(Ta,Tb)
{
return a==b;
}
template
< > //函数模板完全特化
bool Egual<void*>(void* a,void*b)
{
return a==b;
}
特化的深度分析
■重定义和特化的不同
一重定义
:一个类模板和一个新类(或者两个类模板)
·使用的时候需要考虑如何选择的问题
-特化
·以统一的方式使用类模板和特化类
·编译器自动优先选择特化类
实验-模板特化与函数重载
#include <iostream>
#include <string>
using namespace std;
template
< typename T1, typename T2 >
class Test
{
public:
void add(T1 a, T2 b)
{
cout << "void add(T1 a, T2 b)" << endl;
cout << a + b << endl;
}
};
/*
template
< >
class Test < void*, void* > // 当 T1 == void* 并且 T2 == void* 时
{
public:
void add(void* a, void* b)
{
cout << "void add(void* a, void* b)" << endl;
cout << "Error to add void* param..." << endl;
}
};
*/
class Test_Void
{
public:
void add(void* a, void* b)
{
cout << "void add(void* a, void* b)" << endl;
cout << "Error to add void* param..." << endl;
}
};
template
< typename T >
bool Equal(T a, T b)//模板1
{
cout << "bool Equal(T a, T b)" << endl;
return a == b;
}
template
< >
bool Equal<double>(double a, double b)//特化
{
const double delta = 0.00000000000001;
double r = a - b;
cout << "bool Equal<double>(double a, double b)" << endl;
return (-delta < r) && (r < delta);
}
bool Equal(double a, double b)//全局函数
{
const double delta = 0.00000000000001;
double r = a - b;
cout << "bool Equal(double a, double b)" << endl;
return (-delta < r) && (r < delta);
}
int main()
{
cout << Equal( 1, 1 ) << endl;//int参数 使用模板
cout << Equal( 0.001, 0.001 ) << endl;//全局函数
cout << Equal<>( 0.001, 0.001 ) << endl;//指定使用模板
return 0;
}
/*
bool Equal(T a, T b)
1
bool Equal(double a, double b)
1
bool Equal<double>(double a, double b)
1*/
工程中的建议
当需要重载函数模板时,优先考虑使用模板特化:
当模板特化无法满足需求,再使用函数重载!
小结
类模板可以定义任意多入不同的类型参数
类模板可以被部分特化和完全特化
特化的本质是模板的分开实现
函数模板只支持完全特化
工程中使用模板特化代替类(函数)重定义
第60课 - 数组类模板
预备知识
■数值型模板参数的限制
一变量不能作为模板参数
一浮点数不能作为模板参数
一类对象不能作为模板参数
本质:
模板参数是在编译阶段被处理的单元,因此在编译阶段必须准确无误的唯一确定
有趣的面试题
用你觉得最高效的方法求1+2+3+…+N的值
实验-最高效求前N个自然数之和
#include <iostream>
#include <string>
using namespace std;
template
< typename T, int N >
void func()
{
T a[N] = {0};
for(int i=0; i<N; i++)
{
a[i] = i;
}
for(int i=0; i<N; i++)
{
cout << a[i] << endl;
}
}
template
< int N >
class Sum
{
public:
static const int VALUE = Sum<N-1>::VALUE + N;
};
template
< >
class Sum < 1 >
{
public:
static const int VALUE = 1;
};
int main()
{
cout << "1 + 2 + 3 + ... + 10 = " << Sum<10>::VALUE << endl;
cout << "1 + 2 + 3 + ... + 100 = " << Sum<100>::VALUE << endl;
return 0;
}
实验-Array数组模板的实现
#ifndef _ARRAY_H_
#define _ARRAY_H_
template
<typename T,int N>
class Array
{
T m_array[N];
public:
int length();
bool set(int index,T value);
bool get(int index,T& value);
T& operator [](int index);//???dongle
T operator [](int index)const ;//添加const重载版本,防止对象为const对象
virtual ~Array();//virtual ~array();
};
template
<typename T,int N>
int Array<T,N>::length()
{
return N;
}
template
<typename T,int N>
bool Array<T,N>::set(int index,T value)
{
bool ret=(0<=index)&&(index<N);
if(ret)
{
m_array[index]=value;//T[index]=value;
}
return ret;
}
template
<typename T,int N>
bool Array<T,N>::get(int index,T& value)
{
bool ret=(0<=index)&&(index<N);
if(ret)
{
value=m_array[index];
}
return ret;
}
template
<typename T,int N>
T& Array<T,N>::operator [](int index)
{
return m_array[index];
}
template
<typename T,int N>
T Array<T,N>::operator [](int index)const
{
return m_array[index];
}
template
<typename T,int N>
Array<T,N>::~Array()//???virtual Array<T,N>::~array()
{
}
#endif
//main.cpp
#include <iostream>
#include "Array.h"
using namespace std;
int main()
{
Array<double,5> ad;
int i;
for(i=0;i<ad.length();i++)
{
ad[i]=0.1*i;
}
for(i=0;i<ad.length();i++)
{
cout << "a[" <<i<<"]="<<ad[i]<< endl;
}
double temp;
for(i=0;i<ad.length();i++)
{
ad.get(i,temp);
cout << "temp"<<"="<<temp<< endl;
}
return 0;
}
IntArray改写为模板
#ifndef _HEAPARRAY_H_
#define _HEAPARRAY_H_
template
<typename T>//<typename T,int N>
class HeapArray
{
private:
int m_length;
T* m_pointer;
HeapArray(int len);
HeapArray(const HeapArray<T>& obj);
bool construct();
public:
static HeapArray<T>* NewInstance(int length);
int length();
bool get(int index, T& value);
bool set(int index ,T value);
T& operator [] (int index);
T operator [] (int index) const;
HeapArray<T>& operator =(const HeapArray& obj);//
HeapArray<T>& self();//HeapArray& self();
~HeapArray();
};
template
<typename T>
HeapArray<T>::HeapArray(int len)
{
m_length = len;
}
template
<typename T>
bool HeapArray<T>::construct()
{
bool ret = true;
m_pointer = new T[m_length];
if( m_pointer )
{
for(int i=0; i<m_length; i++)
{
m_pointer[i] = 0;
}
}
else
{
ret = false;
}
return ret;
}
template
<typename T>
HeapArray<T>* HeapArray<T>::NewInstance(int length)
{
HeapArray<T>* ret = new HeapArray<T>(length);//??
if( !(ret && ret->construct()) )
{
delete ret;
ret = 0;
}
return ret;
}
template
<typename T>
int HeapArray<T>::length()
{
return m_length;
}
template
<typename T>
bool HeapArray<T>::get(int index, T& value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
value = m_pointer[index];
}
return ret;
}
template
<typename T>
bool HeapArray<T>::set(int index, T value)
{
bool ret = (0 <= index) && (index < length());
if( ret )
{
m_pointer[index] = value;
}
return ret;
}
template
<typename T>
T& HeapArray<T>::operator [] (int index)
{
return *(m_pointer+index);
}
template
<typename T>
T HeapArray<T>::operator [] (int index) const
{
return *(m_pointer+index);
}
template
<typename T>
HeapArray<T>& HeapArray<T>::operator =(const HeapArray& obj)//gai
{
if(this!=&obj)
{
T * pointer =new T[obj.m_length];//int * pointer =new IntArray(obj.m_length);
if( pointer )
{
for(int i=0; i<obj.m_length; i++)
{
pointer[i] = obj.m_pointer[i];
}
m_length=obj.m_length;
delete[] m_pointer;//delete m_pointer delete obj.m_pointer
m_pointer=pointer;
}
//obj.m_pointer=0;error: assignment of member 'IntArray::m_pointer' in read-only object
}
return *this;
}
template
<typename T>
HeapArray<T>& HeapArray<T>::self()
{
return *this;
}
template
<typename T>
HeapArray<T>::~HeapArray()
{
delete[]m_pointer;
}
#endif
//maincpp
HeapArray<int>* pai=HeapArray<int>::NewInstance(10);//new HeapArray<int>::NewInstance(10);
if(pai!=NULL)
{
HeapArray<int>&ai=pai->self();//char
for(i=0;i<ai.length();i++)
{
ai[i]=i+1;//i+'a';
}
//int tem;
//get(int index, T& value);
for(i=0;i<ai.length();i++)
{
cout<<ai[i]<<endl;
}
}
delete pai;//delete[]????
小结
模板参数可以是数值型参数
数值型模板参数必须仕编译期间唯一确定
数组类模板是基于数值型模板参数实现的
数组类模板是简易的线性表数据结构
第61课 - 智能指针类模板
智能指针
■智能指针的意义
-现代C++开发库中最重要的类模板之一
C++中自动内存管理的主要手段
一能够在很大程度上避开内存相关的问题
智能指针
■sTL中的智能指针auto_ptr
一生命周期结束时,销毁指向的内存空间
一不能指向堆数组,只能指向堆对象(变量)
一片堆空间只属于一智能指针对象
一多个智能指针对象不能指向同一片堆空间
实验-auto_ptr的使用
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Test
{
string m_name;
public:
Test(const char* name)
{
cout << "Hello, " << name << "." << endl;
m_name = name;
}
void print()
{
cout << "I'm " << m_name << "." << endl;
}
~Test()
{
cout << "Goodbye, " << m_name << "." << endl;
}
};
int main()
{
auto_ptr<Test> pt(new Test("D.T.Software"));
cout << "pt = " << pt.get() << endl;
pt->print();
cout << endl;
auto_ptr<Test> pt1(pt);
cout << "pt = " << pt.get() << endl;
cout << "pt1 = " << pt1.get() << endl;
pt1->print();
return 0;
}
/*
Hello, D.T.Software.
pt = 0x25c2180
I'm D.T.Software.
pt = 0
pt1 = 0x25c2180
I'm D.T.Software.
Goodbye, D.T.Software.
*/
智能指针
Qt中的智能指针
-QPointer
·当其指向的对象被销毁时,它会被自动置空
·析构时不会自动销毁所指向的对象
-QSharedPointer
·引用计数型智能指针
·可以被自由地拷贝和赋值
·当引用计数为0时才删除指向的对象
智能指针
■STL中的其它智能指针
- shared_ptr
·带有引用计数机制,支持多个指针对象指向同一片内存
-weak_ptr
·配合shared_ptr而引入的一种智能指针
-unique_ptr
·一个指针对象指向一片内存空间,不能拷贝构造和赋值
实验-QT例子??
在这里插入代码片
智能指针
■Qt 中的其它智能指针
- QWeakPointer
-
- QScopedPointer
- QScopedArrayPointel
-
- QSharedDataPointer
- QExplicitlySharedDataPointer
实验-smartpointer.h???
在这里插入代码片
小结
智能指针C++中自动内存管理的主要手段
智能指针任各种平台上都有不同的表现式
智能指针能够尽可能的避升内存相关的可题
STL和Qt 中都提供了对智能指针的支持
第62课 - 单例类模板
问题
如何定义一人类,使得这人类最多只能创建一个对象?
单例模式
■需求的提出
在架构设计时,某些类在整个系统生命期中最多只能有一个对象存在(SingleInstance)
实验-单例的类
#include <iostream>
#include <string>
using namespace std;
class SObject
{
static SObject* c_instance;//1静态指针,标记是否为空
SObject(const SObject&);//3
SObject& operator= (const SObject&);//4
SObject()//0构造函数为private
{
}
public:
static SObject* GetInstance();//7
void print()//5
{
cout << "this = " << this << endl;
}
};
SObject* SObject::c_instance = NULL;//2初始化静态成员变量
SObject* SObject::GetInstance//7
{
if( c_instance == NULL )
{
c_instance = new SObject();
}
return c_instance;
}
int main()
{
// SObject* s ;6,试试 报错
SObject* s = SObject::GetInstance();
SObject* s1 = SObject::GetInstance();
SObject* s2 = SObject::GetInstance();
s->print();
s1->print();
s2->print();
return 0;
}
/*
this = 0x702180
this = 0x702180
this = 0x702180
*/
存在的问题
一需要使用单例模式时:
·必须定义静态成员变量c_instance
·必须定义静态成员函数GetInstance()
单例模式
■解决方案
将单例模式相关的代码抽取出来,开发单例类模板。当需要单例类时,直接使用单例类模板。
实验-单例类模板
Singleton.h
#ifndef _SINGLETON_H_
#define _SINGLETON_H_
template
<typename T>
class Singleton
{
static T* c_instance;
public:
static T* GetInstance();
};
template
<typename T>
T* Singleton<T>::c_instance = NULL;
template
<typename T>
T* Singleton<T>::GetInstance()//static T* Singleton<T>::GetInstance()
{
if( c_instance == NULL )
{
c_instance = new T();
}
return c_instance;
}
#endif
main.cpp
#include <iostream>
#include <string>
#include "Singleton.h"
using namespace std;
class SObject
{
friend Singleton<SObject>;//?????友元friend class Singleton<SObject>;有没有class没区别
//static SObject* c_instance;//1静态指针,标记是否为空
SObject(const SObject&);//3
SObject& operator= (const SObject&);//4
SObject()//0构造函数为private
{
}
public:
//static SObject* GetInstance();//7
void print()//5
{
cout << "this = " << this << endl;
}
};
//SObject* SObject::c_instance = NULL;//2初始化静态成员变量
/*SObject* SObject::GetInstance//7
{
if( c_instance == NULL )
{
c_instance = new SObject();
}
return c_instance;
}*/
int main()
{
// SObject* s ;6,试试 报错
//SObject* s = SObject::GetInstance();
SObject* s = Singleton<SObject>::GetInstance();
SObject* s1 = Singleton<SObject>::GetInstance();
SObject* s2 = Singleton<SObject>::GetInstance();
s->print();
s1->print();
s2->print();/**/
return 0;
}
/*
this = 0x702180
this = 0x702180
this = 0x702180
*/
小结
单例模式是开发中最常用的设计模式之一
单例模式的应用使得一个类最多只有一个对象
可以将单例模式相关的代码抽象成类模板
需要使用单例模式的类直接使用单例类模板
第63课 - C 语言异常处理
异常处理的方式
■C语言经典处理方式:if…else.
void func(...)
{
if(判断是否产生异常)
{
正常情况代码逻辑;
}
else
{
异常情况代码逻辑;
}
}
异常处理
■异常(Exception)和Bug的对比:
一异常
·运行时产生除0的情况
·需要打开的外部文件不存在
·数组访问时越界
- Bug
使用野指针
堆数组使用结束后未释放
选择排序无法处理长度为0的数组
异常处理
■异常的概念
一程序在运行过程中可能产生异常
异常(Exception)与Bug的区别
·异常是程序运行时可预料的执行分支
·Bug是程序中的错误,是不被预期的运行方式
实验-3个参数的除法函数
#include <iostream>
#include <string>
using namespace std;
double divide(double a, double b, int* valid)
{
const double delta = 0.000000000000001;
double ret = 0;
if( !((-delta < b) && (b < delta)) )
{
ret = a / b;
*valid = 1;
}
else
{
*valid = 0;
}
return ret;
}
int main(int argc, char *argv[])
{
int valid = 0;
double r = divide(1, 1, &valid);
if( valid )
{
cout << "r = " << r << endl;
}
else
{
cout << "Divided by zero..." << endl;
}
return 0;
}
异常处理的方式
■通过setjmpO和longjmpO进行优化
.int setjmp(jmp_buf env)
将当前上下文保存在jmp_buf结构体中
-void longjmp(jmp_buf env,int val)
从jmp_buf结构体中恢复setjmp()保存的上下文最终从setjump函数调用点返回,返回值为Val
异常处理的方式·
缺陷
一divide函数有3个参数,难以理解其用法
divide函数调用后必须判断valid代表的结果
·当valid为true时,运算结果正常
·当valid为false时,运算过程出现异常
实验-longjump&setjump
#include <iostream>
#include <string>
#include <csetjmp>
using namespace std;
static jmp_buf env;
double divide(double a, double b)
{
const double delta = 0.000000000000001;
double ret = 0;
if( !((-delta < b) && (b < delta)) )
{
ret = a / b;
}
else
{
longjmp(env, 1);
}
return ret;
}
int main(int argc, char *argv[])
{
if( setjmp(env) == 0 )
{
double r = divide(1, 0);
cout << "r = " << r << endl;
}
else
{
cout << "Divided by zero..." << endl;
}
return 0;
}
异常处理的方式
C语言中的经典异常处理方式会使得程序中逻辑中混入大量的处理异常的代码。
正常逻辑代码和异常处理代码混合在一起,导致代码迅速膨胀,难以维护。。
异常处理的方式缺陷
setjmp()和longjmp()的引入
必然涉及到使用全局变量
·暴力跳转导致代码可读性降低
·本质还是if…else…异常处理方式
实验-C++异常的处理
#include <iostream>
#include <string>
using namespace std;
#define SUCCESS 0
#define INVALID_POINTER -1
#define INVALID_LENGTH -2
#define INVALID_PARAMETER -3
int MemSet(void* dest, unsigned int length, unsigned char v)
{
if( dest == NULL )
{
return INVALID_POINTER;
}
if( length < 4 )
{
return INVALID_LENGTH;
}
if( (v < 0) || (v > 9) )
{
return INVALID_PARAMETER;
}
unsigned char* p = (unsigned char*)dest;
for(int i=0; i<length; i++)
{
p[i] = v;
}
return SUCCESS;
}
int main(int argc, char *argv[])
{
int ai[5];
int ret = MemSet(ai, sizeof(ai), 0);
if( ret == SUCCESS )
{
}
else if( ret == INVALID_POINTER )
{
}
else if( ret == INVALID_LENGTH )
{
}
else if( ret == INVALID_PARAMETER )
{
}
return ret;
}
小结
程序中不口避免的会发生异常
异常是在开发阶价段就可以预见的运行时问题
C语言中通过经典的if…else…方式处理异常
C++中存在更好的异常处理方式
第64课 - C++ 中的异常处理(上)
C++ 异常处理
■C++异常处理分析
throw抛出的异常必须被catch处理
·当前函数能够处理异常,程序继续往下执行
·当前函数无法处理异常,则函数停止执行,并返回
实验-throw&getcatch
#include <iostream>
#include <string>
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;
}
return ret;
}
int main(int argc, char *argv[])
{
try
{
double r = divide(1, 0);
cout << "r = " << r << endl;
}
catch(...)
{
cout << "Divided by zero..." << endl;
}
return 0;
}
C++ 异常处理
■同一个try语句可以跟上多个catch语句
-catch语句可以定义具体处理的异常类型
一不同类型的异常由不同的catch语句负责处理
_try语句中可以抛出任何类型的异常
catch(…)用于处理所有类型的异常
一任何异常都只能被捕获(catch)一次
实验-catch的类型
#include <iostream>
#include <string>
using namespace std;
void Demo1()
{
try
{
throw 'c';
}
catch(char c)
{
cout << "catch(char c)" << endl;
}
catch(short c)
{
cout << "catch(short c)" << endl;
}
catch(double c)
{
cout << "catch(double c)" << endl;
}
catch(...)
{
cout << "catch(...)" << endl;
}
}
void Demo2()
{
throw string("D.T.Software");
}
int main(int argc, char *argv[])
{
Demo1();
try
{
Demo2();
}
catch(char* s)
{
cout << "catch(char *s)" << endl;
}
catch(const char* cs)
{
cout << "catch(const char *cs)" << endl;
}
catch(string ss)
{
cout << "catch(string ss)" << endl;
}
return 0;
}
小结
C++中直接支持异常处理的概念
try…catch…是C++中异常处理的专用语句
try语句处理正常代码逻辑,catch语句处理异常情况
同一个try语句可以跟上多个catch语句
异常处理必须严格匹配,不进行任何的类型转换
第65课 - C++ 中的异常处理(下)
问题
为什么要在catch中重新抛出异常?
实验-catch异常处理
#include <iostream>
#include <string>
using namespace std;
void D0()
{
try
{
//throw 0;//case1
throw 'a';//case2
}
catch(int i)
{
cout << "Inner: catch(int i)" << endl;
throw i;
}
catch(...)
{
cout << "Inner: catch(...)" << endl;
throw;
}
}
void Demo()
{
try
{
try
{
//throw 0;//case3
throw 'c';//case4
}
catch(int i)
{
cout << "Inner: catch(int i)" << endl;
throw i;
}
catch(...)
{
cout << "Inner: catch(...)" << endl;
throw;
}
}
catch(...)
{
cout << "Outer: catch(...)" << endl;
}
}
/*
假设: 当前的函数式第三方库中的函数,因此,我们无法修改源代码
函数名: void func(int i)
抛出异常的类型: int
-1 ==》 参数异常
-2 ==》 运行异常
-3 ==》 超时异常
*/
void func(int i)
{
if( i < 0 )
{
throw -1;
}
if( i > 100 )
{
throw -2;
}
if( i == 11 )
{
throw -3;
}
cout << "Run func..." << endl;
}
void MyFunc(int i)//封装异常的代号
{
try
{
func(i);
}
catch(int i)
{
switch(i)
{
case -1:
throw "Invalid Parameter";
break;
case -2:
throw "Runtime Exception";
break;
case -3:
throw "Timeout Exception";
break;
}
}
}
int main(int argc, char *argv[])
{
// D0();
/*case1:output
Inner: catch(int i)
terminate called after throwing an instance of 'int'
case2:output
Inner: catch(...)
terminate called after throwing an instance of 'char'
*/
// Demo();
/*case3:output
Inner: catch(int i)
Outer: catch(...)
case4:output
Inner: catch(...)
Outer: catch(...)
*/
// throw后必要 有catch,否则报错
/**/
try
{
MyFunc(11);//3种情况3种输出
}
catch(const char* cs)
{
cout << "Exception Info: " << cs << endl;
}
return 0;
}
C++ 中的异常处理
异常的类型可以是自定义类类型
对于类类型异常的匹配依旧是至上而下严格匹配
赋值兼容性原则在异常配中依然适用
一般而言
匹配子类异常的catch放在上部
匹配父类异常的catch放在下部
C++ 中的异常处理
■在工程中会定义一系列的异常类
每个类代表工程中可能出现的一种异常类型
代码复用时可能需要重解释不司的异常类
■在定义catch语句块时推荐使用引用作为参数
实验-类实现异常匹配子类父类
#include <iostream>
#include <string>
using namespace std;
class Base
{
};
class Exception : public Base
{
int m_id;
string m_desc;
public:
Exception(int id, string desc)
{
m_id = id;
m_desc = desc;
}
int id() const
{
return m_id;
}
string description() const
{
return m_desc;
}
};
/*
假设: 当前的函数式第三方库中的函数,因此,我们无法修改源代码
函数名: void func(int i)
抛出异常的类型: int
-1 ==》 参数异常
-2 ==》 运行异常
-3 ==》 超时异常
*/
void func(int i)
{
if( i < 0 )
{
throw -1;
}
if( i > 100 )
{
throw -2;
}
if( i == 11 )
{
throw -3;
}
cout << "Run func..." << endl;
}
void MyFunc(int i)
{
try
{
func(i);
}
catch(int i)
{
switch(i)
{
case -1:
throw Exception(-1, "Invalid Parameter");
break;
case -2:
throw Exception(-2, "Runtime Exception");
break;
case -3:
throw Exception(-3, "Timeout Exception");
break;
}
}
}
int main(int argc, char *argv[])
{
try
{
MyFunc(11);
}
catch(const Exception& e)//子类 例子:const Exception& e=Exception(-1, "Invalid Parameter");
{
cout << "Exception Info: " << endl;
cout << " ID: " << e.id() << endl;
cout << " Description: " << e.description() << endl;
}
catch(const Base& e)//父类
{
cout << "catch(const Base& e)" << endl;
}
return 0;
}
C++ 中的异常处理
■C++标准库中提供了实用异常类族
■标准库中的异常都是从exception类派生的
exception类有两个主要的分支
logic_error
,常用于程序中的可避免逻辑错误
runtime_error
·常用于程序中无法避免的恶性错误
实验-Array增加异常判断
#include<stdexcept>
using namespace std;
template
<typename T,int N>
T& Array<T,N>::operator [](int index)//增加异常判断
{
if((0<=index)&&(index<N))
{
return m_array[index];
}
else
{
throw out_of_range("T& Array<T,N>::operator [](int index)");
}
}
template
<typename T,int N>
T Array<T,N>::operator [](int index)const
{
if((0<=index)&&(index<N))
{
return m_array[index];
}
else
{
throw out_of_range("T& Array<T,N>::operator [](int index) const");
}
}
//main函数内部
Array<double,5> ad;
int i;
//case1;
// for(i=0;i<=ad.length();i++)
// {
// ad[i]=0.1*i;
// }
// terminate called after throwing an instance of 'std::out_of_range'
// what(): T& Array<T,N>::operator [](int index)
//case2:
try{
for(i=0;i<=ad.length();i++)
{
ad[i]=0.1*i;
}
}
catch(...)
{
cout<<"Exception"<<endl;
}
实验-HeapArray优化???
1.length() 改为const函数
2.self()增加const版本
3.[]重载函数的异常判断、
4.添加头文件stdexcpt,命名空间
在这里插入代码片
小结
catch语句块中可以抛出异常
异常的类型可以是自定义类类型
赋值兼容性原则在异常匹配中依然适用
标准库中的异常都是从exception类派生的