注:博客中内容主要来自《狄泰软件学院》,博客仅当私人笔记使用。
测试环境:Ubuntu 10.10
GCC版本:9.2.0
一、令人迷惑的写法
1)下面的程序想要表达什么意思?
template <class T> //二义性:1、特定的类类型T 2、限定为自定义类类型T
class Test //类模板
{
public:
Test(T t){}
};
template <class T>
void func(T a[], int len) //函数模板
{
}
推理:模板类型被限定为类类型。
2)历史上的原因……
- 早期的C++直接复用class关键字来定义模板(之前复用的都是数据类型)
- 但是泛型编程针对的不只是类类型
- class关键字的复用使得代码出现二义性
class和typename功能没有区别,只是意思容易迷惑人(class:类,typename:类型名称)。
3)typename诞生的直接诱因(泛型编程和面向对象编程揉在一起)
- 自定义类类型内部的嵌套类型
- 不同类中的同一个标识符可能导致二义性
- 编译器无法辨识标识符究竟是什么
编程实验
模板中的二义性
68-1.cpp
#include <iostream>
#include <string>
using namespace std;
template < class T >
class Test
{
public:
Test(T t)
{
cout << "t = " << t << endl;
}
};
template < class T >
void func(T a[], int len)
{
for(int i=0; i<len; i++)
{
cout << a[i] << endl;
}
}
int main(int argc, char *argv[])
{
Test<string> ts("D.T.Software");
string ta[] = {"D",".","T","."};
func(ta, 4);
Test<int> ti(100);
int ai[] = {1,2,3,4};
func(ai, 4);
return 0;
}
操作:
1) g++ 68-1.cpp -o 68-1.out编译正确,打印结果:
t = D.T.Software
D
.
T
.
t = 100
1
2
3
4
分析:
证明了我们猜想是错的。结论:class也可以声明泛类型,功能和typename一样。
2) 证明class二义性问题
#include <iostream>
#include <string>
using namespace std;
int a = 0;
class Test_1
{
public:
static const int TS = 1;
};
class Test_2
{
public:
struct TS
{
int value;
};
};
template
< class T >
void test_class()
{
typename T::TS*a;//1.通过泛指类型T内部的数据类型TS定义指针变量a(推荐的解读方式)
//2.使用泛指类型T内部的静态成员变量TS与全局变量a进行乘法操作
}
int main(int argc, char *argv[])
{
test_class<Test_1>();
test_class<Test_2>();
return 0;
}
操作:
1) g++ 68-1.cpp -o 68-1.out编译错误:
68-1.cpp: In instantiation of ‘void test_class() [with T = Test_2]’:
68-1.cpp:62:21: required from here
68-1.cpp:46:7: error: dependent-name ‘T:: TS’ is parsed as a non-type, but instantiation yields a type
T::TS* a;
^
68-1.cpp:46:7: note: say ‘typename T:: TS’ if a type is meant
如果更倾向说明"T:: TS"是数据类型,用typename说明
4)typename的作用:(重点)
1、在模板定义中声明泛指类型
2、明确告诉编译器其后的标识符为类型
5)下面的程序想要表达什么意思?
int func(int i) try
{
return i; //正常功能代码
}
catch(...)
{
return -1; //异常功能
}
使用try...catch将正常代码和异常代码分开。
int func(int i, int j)
throw(int) //异常声明,可能抛出的异常是int类型
{
return i + j;
}
函数声明和定义时可以直接指定可能抛出的异常类型。
6)try...catch用于分隔正常功能代码与异常处理代码
7)try...catch可以直接将函数实现分隔为2部分
8)函数声明和定义时可以直接指定可能抛出的异常类型
9)异常声明成为函数的一部分可以提高代码可读性
10)函数异常声明的注意事项
- 函数异常声明是一种预编译器之间的契约
- 函数声明异常后就只能抛出声明的异常
*抛出其它异常将导致程序运行终止(抛出与异常声明类型不同的类型,程序会停止)
*可以直接通过异常声明定义无异常函数
编程实验
新的异常写法
68-2.cpp
#include <iostream>
#include <string>
using namespace std;
int func(int i, int j) throw(int) //异常抛出声明,抛出类型int.
{
if((0 < j)&&(j < 10)) //正常情况
{
return (i + j);
}
else //异常处理
{
throw 0;
}
}
void test(int i) try//这部分是:正常代码和异常处理代码分开,看起来更直观,好维护
{
cout << "func(i, i) = " << func(i, i) << endl;
}
catch(int i) //捕获可能抛出来的异常
{
cout << "Exception: " << i << endl;
}
int main(int argc, char *argv[])
{
test(5); //正常执行
test(10); //抛异常
return 0;
}
操作:
1) g++ 68-2.cpp -o 68-2.out编译正确,打印结果:
func(i, i) = 10 //正常代码
Exception: 0 //异常执行
2) 修改代码,扔出字符型异常:
#include <iostream>
#include <string>
using namespace std;
int func(int i, int j) throw(int) //异常抛出声明,抛出类型int.
{
if((0 < j)&&(j < 10)) //正常情况
{
return (i + j);
}
else //异常处理
{
throw '0';
}
}
void test(int i) try//这部分是:正常代码和异常处理代码分开,看起来更直观,好维护
{
cout << "func(i, i) = " << func(i, i) << endl;
}
catch(int i) //捕获可能抛出来的异常
{
cout << "Exception: " << i << endl;
}
int main(int argc, char *argv[])
{
test(5); //正常执行
test(10); //抛异常
return 0;
}
g++ 68-2.cpp -o 68-2.out编译正确,运行错误:
terminate called after throwing an instance of 'char'
分析:
抛出'char'异常后没有被捕捉。
解决办法:
用catch(...)捕获异常:
#include <iostream>
#include <string>
using namespace std;
int func(int i, int j) throw(int) //异常抛出声明,抛出类型int.
{
if((0 < j)&&(j < 10)) //正常情况
{
return (i + j);
}
else //异常处理
{
throw '0';
}
}
void test(int i) try//这部分是:正常代码和异常处理代码分开,看起来更直观,好维护
{
cout << "func(i, i) = " << func(i, i) << endl;
}
catch(int i) //捕获可能抛出来的异常
{
cout << "Exception: " << i << endl;
}
catch(...)
{
cout << "Exception..." << endl;
}
int main(int argc, char *argv[])
{
test(5); //正常执行
test(10); //抛异常
return 0;
}
g++ 68-2.cpp -o 68-2.out编译正确,运行错误:
terminate called after throwing an instance of 'char'
分析:
并不是异常捕获类型问题,int func(int , int j) throw(int)定义时声明允许抛出int类型异常,但实际函数中抛出的是字符型异常('0')。程序无法正常抛出异常,运行终止。
解决办法:
函数定义时声明允许抛出字符型异常:
#include <iostream>
#include <string>
using namespace std;
int func(int i, int j) throw(int, char) //异常抛出声明,抛出类型char或int。如果没有char,运行时程序异常终止
{
if((0 < j)&&(j < 10)) //正常
{
return (i + j);
}
else //异常
{
throw '0';
}
}
void test(int i) try//这部分是:正常代码和异常处理代码分开,看起来更直观,好维护
{
cout << "func(i, i) = " << func(i, i) << endl;
}
catch(int i) //捕获可能抛出来的异常
{
cout << "Exception: " << i << endl;
}
catch(...) //如果没有这个就捕捉不到char类型异常,控制台报错
{
cout << "Exception..." << endl;
}
int main(int argc, char *argv[])
{
test(5);
test(10); //抛异常,没执行
return 0;
}
程序正常运行。
小结
1)class可以用来在模板中定义泛指类型(不推荐)
2)typename是可以消除模板中二义性
3)try...catch可以将函数体分成2部分
4)异常声明能够提供程序的可读性