今天我们来看看一些在程序中迷惑的写法,下面的程序想要表达什么意思呢?

#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 a[5] = {1, 2, 3, 4, 5};
    
    Test<int> t(5);
    
    cout << endl;
    
    func(a, 5);

    return 0;
}

        我们在类模板中用 class 来声明,但是我们在 main 函数中是用 int 类型来使用的,下来我们看看这样的写法在 C++ 编译器中支持吗?

令人迷惑的写法(五十六)_try

        我们看到编译器编译通过并且运行成功。那么我们不是用的 typename 来声明的泛指类型的嘛,这怎么用 class 了呢。这是因为历史上的一些原因,早期的 C++ 直接复用 class 关键字来定义模板,但是泛型编程针对的不只是类类型,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()
{
    T::TS * a;  // 这便出现了二义性
                // 1. 通过泛指类型 T 内部的数据类型 TS 定义指针变量 a (推荐的解读方式)
                // 2. 使用泛指类型 T 内部的静态成员变量 TS 与全局变量 a 进行乘法操作
}

int main()
{
    test_class<Test_1>();
    test_class<Test_2>();

    return 0;
}

        我们通过在函数模板指定泛指类型为 Test_1,便是第 2 种解读方式,泛指类型为 Test_2 时,便是第 1 种解读方式。那么在这块是不是就出现了二义性呢?我们看看编译结果令人迷惑的写法(五十六)_ 类模板_02

        我们看到编译器直接报错了,如果是一种类型的话,便让我们在前前面加上 typename,那么我们在模板中加上 typename,看看

令人迷惑的写法(五十六)_class_03

        编译器又报错了,它说 Test_1 中没有这种类型,肯定没有啊,只有一个成员变量而已。我们注释掉 Test_1 看看

令人迷惑的写法(五十六)_class_04

        编译通过了。那么 typename 诞生的原因是什么呢?用来自定义类类型内部的嵌套类型,不同类中的同一个标识符可能导致二义性,编译器无法直接辨识标识符究竟是什么。typename 的作用有两个:1、在模板定义中声明泛指类型;2、明确告诉编译器其后的标识符为类型

        那么下面的程序想要表达什么意思呢?

令人迷惑的写法(五十六)_ 类模板_05

        try...catch 用于分隔正常功能代码与异常处理代码,try...catch 可以直接将函数实分隔为两部分。函数声明和定义时可以直接指定可能抛出的异常类型,异常声明成为函数的一部分可以提高代码可读性。那么函数异常声明时有几个注意事项:1、函数异常声明是一种与编译器之间的契约;2、函数声明异常后就只能抛出声明的异常,抛出其他异常将导致程序运行终止,可以直接通过异常声明定义无异常函数。

        下来我们还是以代码为例来进行说明

#include <iostream>
#include <string>

using namespace std;

int func(int i, int j) throw(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()
{
    test(5);
    
    test(10);
    
    return 0;
}

        我们在 func 函数中异常只声明了 int,而抛出了字符型异常。我们来看看会导致程序运行终止嘛?

令人迷惑的写法(五十六)_typename_06

        确实导致了程序的运行终止了,注意我们的程序中还有 catch(...) 都没能捕捉到 char  类型的异常。那么我们在声明的异常中加上 char 再来看看

令人迷惑的写法(五十六)_typename_07

        程序正常的运行结束了。通过今天对一些迷惑写法的学习,总结如下:1、class 可以用来在模板中定义泛指类型(不推荐);2、typename 是可以消除模板中的二义性;3、try...catch 可以将函数体分成 2 部分;4、异常声明能够提供程序的可读性。


        欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083