68-拾遗:令人迷惑的写法

注:博客中内容主要来自《狄泰软件学院》,博客仅当私人笔记使用。

测试环境: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)异常声明能够提供程序的可读性

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值