令人迷惑的写法1、
- 下面的程序想要表达什么意思?
#include <iostream>
#include <string>
using namespace std;
template <class T>
class Test
{
public:
Test(T i)
{
cout << "Test(T i): " << i << endl;
}
};
template <class T>
void func(T a[], int length)
{
for (int i = 0; i < length; i++)
{
cout << a[i] << endl;
}
}
int main()
{
Test<string> t("xiebs");
string sa[5] = { "C++","Qt", "Python", "Go", "Java" };
func(sa, 5);
Test<int> t1(1);
int array[3] = { 1,2,3 };
func(array, 3);
return 0;
}
说明 模板里面写成 class 并不是限定只能定义类类型。
- 历史上的原因
— 早期的C++直接复用 class 关键字来定义模板
— 但是泛型编程针对的不只是类类型
— class 关键字的复用使得代码出现二义性
#include <iostream>
#include <string>
using namespace std;
int a = 0;
class Test1
{
public:
static const int TS = 1;
};
class Test2
{
public:
struct TS
{
int value;
};
};
template <class T>
void class_test()
{
T::TS * a; //1、通过泛指类型 T 内部的数据类型 TS 定义指针变量 a
//2、通过泛指类型 T 内部的静态成员变量 TS 定义指针变量 a
}
int main()
{
class_test<Test1>();
class_test<Test2>();
return 0;
}
程序报错,为什么会这样呢?因为在第25行的函数里,这条语句其实是具有二义性的。当我们调用第31行的class_test<Test1>();
这条语句时,程序不会报错,因为它只代表一种意思,就是静态成员变量和全局变量 a 相乘。但是当我们调用第32行的class_test<Test2>();
这条语句时,编译器会不知所措了,他不知道我们到底是要调用定义指针 a 呢还是要他和 0 相乘呢。所以我们得到提示:如果你更倾向于让 TS 作为一个数据类型来使用,你就应该用typename
关键字来说明 TS 是一个数据类型而不是一个成员变量
#include <iostream>
#include <string>
using namespace std;
int a = 0;
class Test1
{
public:
static const int TS = 1;
};
class Test2
{
public:
struct TS
{
int value;
};
};
template <class T>
void class_test()
{
typename T::TS * a; //1、通过泛指类型 T 内部的数据类型 TS 定义指针变量 a
//2、通过泛指类型 T 内部的静态成员变量 TS 定义指针变量 a
}
int main()
{
//class_test<Test1>();
class_test<Test2>();
return 0;
}
这样的代码是没有问题的。
- 所以
typename
有两个作用:
1、在模板定义中声明泛指类型
2、解决这样的二义性问题,明确告诉编译器,紧跟其后的标识符是一个类型。
令人迷惑的写法2、
- 下面的程序想要表达什么意思?
- 左边的图片:
1、 try…catch 用于分隔正常功能代码和异常处理代码
2、 try…catch 可以直接将函数实现分隔为 2 部分 - 右边的图片
1、 函数声明和函数定义时可以直接指定可能抛出的异常类型
2、 异常声明成为函数的一部分可以提高代码可读性 - 函数异常声明的注意事项
— 函数异常声明是一种与编译器之间的契约
— 函数声明异常后就只能抛出声明的异常
抛出其他异常将导致程序运行终止
可以直接通过异常声明定义无异常函数
#include <iostream>
#include <string>
using namespace std;
int func(int x, int y)throw(int) //函数异常声明
{
if (y > 0 && y < 10)
{
return x + y;
}
else
{
throw 0;
}
}
void test(int i)try // try...catch 用于分隔正常功能代码和异常处理代码
{
cout << "func(i,i) = " << func(i, i) << endl;
}
catch(int)
{
cout << "exception 1" << endl;
}
catch (...)
{
cout << "exception 2" << endl;
}
int main()
{
test(5);
test(10);
return 0;
}
如果在函数异常声明哪里最后 throw
的类型和异常声明的类型不一样,会出现什么结果呢?
程序异常终止。因为我们前面说了,函数声明异常后就只能抛出声明的异常,抛出其他异常将导致程序运行终止。
修改方式:
int func(int x, int y)throw(int,char) //函数异常声明
{
if (y > 0 && y < 10)
{
return x + y;
}
else
{
throw 0;
}
}
- 小结
1、class
可以用来在模板中定义泛指类型(不推荐)
2、typename
是可以消除模板中的二义性
3、try...catch
可以将函数体分为两部分
4、异常声明能够提供程序的可读性