C++之typename和typedef关键字

时间:2019-10-20
本文章向大家介绍C++之typename和typedef关键字,主要包括C++之typename和typedef关键字使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

1. typename的作用

template <typename Distance>
class KDTreeIndex : public NNIndex<Distance> {
public:
    typedef typename Distance::ElementType ElementType;
    typedef typename Distance::ResultType DistanceType;
    typedef NNIndex<Distance> BaseClass;
    typedef bool needs_kdtree_distance;
    KDTreeIndex();
    ~KDTreeIndex();

private:
    DistanceType* mean_;
    DistanceType* var_;
};

对此处定义的模板类,调用时传入模版参数L2<T>L2本身定义为模板结构体,相当于模板类

template<class T>
struct L2 {
    typedef T ElementType;
    typedef typename Accumulator<T>::Type ResultType;

    template <typename U, typename V>
    inline ResultType accum_dist(const U& a, const V& b, int) const {
        return (a-b)*(a-b);
    }
};

其中调用的Accumulator定义如下

template<typename T>
struct Accumulator {
    typedef T Type;
};

因此反向看回去, Accumulator<T>::Type 就是类型T, L2<T>::ElementType 也是类型T, L2<T>::ResultType 也是类型T,绕了一大圈结构 ElementType 和 ResultType 表示的还是最开始传入的L2<T>里面的T,只是从字面上更清晰地表示了出来。

typedef typename Distance::ElementType ElementType;

如果不加 typename 关键字,编译器就不知道 Distance::ElementType 表示什么

因为共有三种可能:

  1. 静态成员变量
  2. 静态成员函数
  3. 类内嵌套类型

加上 typename 就是告诉编译器这表示一个类型,从而消除了歧义。

2. 关键字 typedef 用法

// From C99 Standard
The typedef specifier is called a ‘‘storage-class specifier’’ for syntactic convenience only.

In a parameter declaration, a single typedef name in parentheses is taken to be an abstract declarator that specifies a function with a single parameter,
not as redundant parentheses around the identifier for a declarator.

In a declaration whose storage-class specifier is typedef, each declarator defines an identifier to be a typedef name that denotes the type specified for
the identifier in the way described as above.
Any array size expressions associated with variable length array declarators are evaluated each time the declaration of the typedef name is reached in the
order of execution. A typedef declaration does not introduce a new type, only a synonym for the type so specified.

2.1 为各种数据类型定义新名字

typedef type_name type_alias;

有点像  #define  宏定义,但宏定义是在预处理阶段进行直接替换,而 typedef  作用于编译期,可以用于超越预处理器处理能力的类型替换。简化代码,增强可读性,同时提高跨平台适应性。

正如C语言参考手册所言,任何  declarator  中的 identifier  定义为  typedef-name  , 其表示的类型是  declarator  为正常变量声明的那个标识符的类型。举几个例子

// Example 1
int *p; // p是一个变量,其类型为pointer to int
typedef int *p; // 在int *p前面加typedef后,p变为一个typedef-name,这个typedef-name所表示的类型就是int *p声明式中p的类型(int*)。也即是说typedef去除了p普通变量的身份,使其变成了p的类型的一个typedef-name

// Example 2
double MYDOUBLE; // 正常变量声明,声明一个变量MYDOUBLE,类型为double
typedef double MYDOUBLE; // MYDOUBLE是类型double的一个typedef-name
MYDOUBLE d; // d是一个double类型的变量

// Example 3
double *Dp; // 声明变量Dp,类型为double*,即pointer to double
typedef double *Dp; // Dp是类型double*的一个typedef-name
Dp dptr; // dptr是一个pointer to double的变量

对于复杂数据类型也是一样,比如结构体或者类

// Example 4
struct _Foo_t Foo_t; // 变量Foo_t的类型为struct _Foo_t
typedef struct _Foo_t Foo_t; // Foo_t是"struct _Foo_t"的一个typedef-name
Foo_t ft; // ft is a struct type variable

// Example 5
struct { ... // } Foo_t; // 变量Foo_t的类型为struct { ... // }
typedef struct { ... // } Foo_t; // Foo_t是struct { ... // }的一个typedef-name, 这里struct {...//}是一个无"标志名称(tag name)"的结构体声明

或者一些数组名或指针的别名

// Example 6
int A[5]; // 变量A的类型为一个含有5个元素的整型数组
typedef int A[5]; // A是含有5个元素的数组类型的一个typedef-name
A a = {3, 4, 5, 7, 8}; // Right
A b = {3, 4, 5, 7, 8, 9}; // Warning: excess elements in array initializer

// Example 7
// typedef int (*A)[5]; vs typedef int* A[5];
int (*A)[5]; // 变量A的类型为pointer to an array with 5 int elements
typedef int (*A)[5]; // A是"pointer to an array with 5 int elements"的一个typedef-name
int c[5] = {3, 4, 5, 7, 8};
A a = &c; // Right
printf("%d\n", (*a)[0]); // output 3

int c[6] = {3, 4, 5, 7, 8, 9};
A a = &c; // Warning: initialization from incompatible pointer type

2.2 用来定义函数指针

分析方法与定义基本类型别名类似,都是去掉 typedef 后观察期类型

// Example 1
int* Func(int); // 变量Func的类型为一个函数标识符,该函数返回值类型为int*,参数类型为int
typedef int* Func(int); // Func是函数类型(函数返回值类型为int*,参数类型为int)的一个typedef-name
Func *fptr; // fptr是一个pointer to function with one int parameter, returning a pointer to int
Func f; // 这样的声明没有多大意义
// Example 2
int (*PFunc)(int); // 变量PFunc的类型为一个函数指针,指向的返回值类型为int,参数类型为int的函数原型

typedef int (*PFunc)(int); // PFunc是函数指针类型(该指针类型指向返回值类型为int,参数类型为int的函数)的一个typedef-name
PFunc fptr; // fptr是一个pointer to function with one int parameter, returning int


// Example 3 #include <iostream> int add(int a, int b) { return (a+b); } typedef int (* func)(int , int ); int main(int argc, char *argv[]) { func f = add;   // 定义一个指针变量f,它是一个指向某种函数的指针,这种函数参数是两个int类型,返回值也是int类型。将其指向add函数入口地址。 int n = f(1,2);  // 我们要从指针的层次上理解函数-函数的函数名实际上就是一个指针,函数名指向该函数的代码在内存中的首地址。 std::cout << n << std::endl; return 0; }
// Example 4
#include <stdio.h>

typedef int (*FP_CALC)(int, int); 

// 注意这里不是函数声明而是函数定义,它是一个地址,你可以输出add看看
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return b? a/b : -1; }

//定义一个函数,参数为op,返回一个指针。该指针类型为拥有两个int参数、返回类型为int 的函数指针。它的作用是根据操作符返回相应函数的地址
FP_CALC calc_func(char op) {
    switch (op) {
    case '+': return add;//返回函数的地址
    case '-': return sub;
    case '*': return mul;
    case '/': return div;
    default:  return NULL;
    }
    return NULL;
}

//s_calc_func为函数,它的参数是 op, 返回值为一个拥有 两个int参数、返回类型为int 的函数指针
int (*s_calc_func(char op)) (int, int) { return calc_func(op); } 

//最终用户直接调用的函数,该函数接收两个int整数,和一个算术运算符,返回两数的运算结果
int calc(int a, int b, char op) {
    FP_CALC fp = calc_func(op); //根据预算符得到各种运算的函数的地址
    int (*s_fp)(int, int) = s_calc_func(op);//用于测试
    // ASSERT(fp == s_fp);   // 可以断言这俩是相等的
    if (fp)
        return fp(a, b);//根据上一步得到的函数的地址调用相应函数,并返回结果
    else
        return -1;
}

int main(int argc, char *argv[]) {   
    int a = 100, b = 20;
    
    printf("calc(%d, %d, %c) = %d\n", a, b, '+', calc(a, b, '+'));
    printf("calc(%d, %d, %c) = %d\n", a, b, '-', calc(a, b, '-'));
    printf("calc(%d, %d, %c) = %d\n", a, b, '*', calc(a, b, '*'));
    printf("calc(%d, %d, %c) = %d\n", a, b, '/', calc(a, b, '/'));
    return 0;
}

3. 关键字 typename 用法

3.1. 在模板定义中,表明其后的模板参数为类型参数

template<typename T>
class Mytest {
public:
    template<typename U, typename V>
    T foo(const U &u, const V &v) {
        // function body
    }

private
    T t;
};

这里  typename  就相当于关键字  class  ,二者可以相互替换,最初定义模板的方式就是  template<class T> ...   这样可以减少关键字的引入。

3.2 在模板中用于表明内嵌依赖类型名(Nested Dependent Type Name)

template<class _InputIter, class _Tp>
typename iterator_traits<_InputIter>::difference_type
count(_InputIter __first, _InputIter __last, const _Tp& __value) {
    __STL_REQUIRES(_InputIter, _InputIterator);
    __STL_REQUIRES(typename iterator_traits<_InputIter>::value_type, _EqualityComparable);
    __STL_REQUIRES(_Tp, _EqualityComparable);
    typename iterator_traits<_InputIter>::difference_type __n = 0;

    for ( ; __first != __last; ++__first) {
        if (*__first == __value) {
            ++__n;
        }
    }

    return __n;
}

这里有三处用到了 typename 关键字

typename iterator_traits<_InputIter>::difference_type
typename iterator_traits<_InputIter>::value_type
typename iterator_traits<_InputIter>::difference_type __n = 0;

 difference_type, value_type  就是依赖于  _InputIter  (模板类型参数)的类型名。

 iterator_traits  结构体定义如下:

// iterator_traits结构体定义
template <class _Iterator>
struct iterator_traits {
    typedef typename _Iterator::iterator_category iterator_category;
    typedef typename _Iterator::value_type value_type;
    typedef typename _Iterator::difference_type difference_type;
    typedef typename _Iterator::pointer pointer;
    typedef typename _Iterator::reference reference;
};
  • 内嵌是指定义在类名的定义中的。比如这里的  difference_type, value_type  。
  • 依赖是指依赖于一个模板参数。比如difference_type (typename iterator_traits<_inputiter>::difference_type) 依赖于模板参数 _InputIter
  • 类型名是指最终要指出的是个类型名,而不是变量。比如 iterator_traits<_inputiter>::difference_type 完全有可能是类 iterator_traits<_inputiter> 类里的一个 static 对象。并且C++默认就是解释为一个变量。所以为了避免歧义,使用 typename 告诉编译器。

但这并不说所有的  T::type_or_variable  , 或者  tmpl:type_or_variable  都需要使用  typename  ,比如下面的情况

// The first situation
template<class T>
class Derived: public Base<T>::XXX
{
    // ...
}

// Another situation
Derived(int x) : Base<T>::xxx(x)
{
    // ...
}

第一种是类模板定义中的基类列表,里面肯定是类型名,第二种是类模板定义中的初始化列表,里面肯定是成员变量,这对于编译器而言没有任何歧义,因此不需要 typename 关键字。

原文地址:https://www.cnblogs.com/phillee/p/11707820.html