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[]; // 变量A的类型为一个含有5个元素的整型数组
typedef int A[]; // A是含有5个元素的数组类型的一个typedef-name
A a = {, , , , }; // Right
A b = {, , , , , }; // Warning: excess elements in array initializer // Example 7
// typedef int (*A)[5]; vs typedef int* A[5];
int (*A)[]; // 变量A的类型为pointer to an array with 5 int elements
typedef int (*A)[]; // A是"pointer to an array with 5 int elements"的一个typedef-name
int c[] = {, , , , };
A a = &c; // Right
printf("%d\n", (*a)[]); // output 3 int c[] = {, , , , , };
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(,);  // 我们要从指针的层次上理解函数-函数的函数名实际上就是一个指针,函数名指向该函数的代码在内存中的首地址。
std::cout << n << std::endl; return ;
}
// 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 : -; } //定义一个函数,参数为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 -;
} int main(int argc, char *argv[]) {
int 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, '*'));
printf("calc(%d, %d, %c) = %d\n", a, b, '/', calc(a, b, '/'));
return ;
}

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 = ; 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 = ;

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 关键字。

C++之typename和typedef关键字的更多相关文章

  1. typedef关键字

    1. typedef的作用 在计算机编程语言中用来为复杂的声明定义简单的别名,与宏定义有些差异.它本身是一种存储类的关键字,与auto.extern.static.register等关键字不能出现在同 ...

  2. C语言学习及应用笔记之五:C语言typedef关键字及其使用

    在C语言中有一个typedef关键字,其用来定义用户自定义类型.当然,并不是真的创造了一种数据类型,而是给已有的或者符合型的以及复杂的数据类型取一个我们自己更容易理解的别名.总之,可以使用typede ...

  3. C语言第四讲,typedef 关键字,以及作用域

    C语言第四讲,typedef 关键字,以及作用域 一丶typedef关键字 在C语言中,有typedef 关键字,这个关键字的作用就是允许你为类型定义一个新的名字,也就是 起个别的名字 例如: typ ...

  4. c++模板编程-typename与class关键字的区别

    最近一直在研究c++模板编程,虽然有些困难,但希望能够坚持下去.今天,在书上看见一个讨论模板编程typename与class两个关键字的区别,觉得挺有意义的,就把它们给总结一下. 先看一个例子: te ...

  5. C++模板之typename和class关键字的区别

    我们都知道,在STL中基本上都使用了模板类的声明,即template.在模板类的声明中,我们有两种方式: template <class T> template <typename ...

  6. c语言typedef关键字的理解

    1.typedef的定义 很多人认为typedef 是定义新的数据类型,这可能与这个关键字有关.本来嘛,type 是数据类型的意思:def(ine)是定义的意思,合起来就是定义数据类型啦. 不过很遗憾 ...

  7. 结构体 typedef关键字

    1 结构体 #include <iostream> #include <cstring> using namespace std; void printBook( struct ...

  8. C语言结构体及typedef关键字定义结构体别名和函数指针的应用

    结构体(struct)的初始化 struct autonlist { char *symbol; struct nlist nl[2]; struct autonlist *left, *right; ...

  9. 你好,C++(11)如何用string数据类型表示一串文字?根据初始值自动推断数据类型的auto关键字(C++ 11)

    3.5.2  字符串类型 使用char类型的变量我们可以表示单个字符,那么,我们又该如何表示拥有多个字符的字符串呢? 我们注意到,一个字符串是由多个字符串连起来形成的.很自然地,一种最简单直接的方法就 ...

随机推荐

  1. Caused by: java.util.zip.ZipException: zip file is empty

    1.问题描述:mybranch分支代码和master分支的代码一模一样,mybranch代码部署到服务器上没有任何问题,而master代码部署到服务器上运行不起来. 2.解决办法: (1)登陆服务器启 ...

  2. #在windows上使用ngix重定向目录访问远程服务器文件详细实例

    为了在开发环境保持于生产环境相同的访问远程服务器文件资源的目录配置,需要在开发环境(windows)在远程文件服务器使用nignx重定向文件目录,因为网上的资料大都是copy的,解释比较笼统,也没有具 ...

  3. Flink入门(三)——环境与部署

    flink是一款开源的大数据流式处理框架,他可以同时批处理和流处理,具有容错性.高吞吐.低延迟等优势,本文简述flink在windows和linux中安装步骤,和示例程序的运行,包括本地调试环境,集群 ...

  4. 从“职场小白”进阶为“行业大牛”,四个"锦囊"教你破局

    在早期软件行业,会存在一个普遍的现象,有些大学的本科,或者研究生毕业,他们去面试工作的时候会发现,面试下来代码能力可能不是太好,这种情况下公司会问你愿不愿意去做测试? 如果说早期软件测试行业还是一个风 ...

  5. git的基本使用-1

    1.git的安装 这里只介绍在 Linux 上安装. 如果你想在 Linux 上用二进制安装程序来安装 Git,可以使用发行版包含的基础软件包管理工具来安装. 如果以 Fedora 上为例,你可以使用 ...

  6. Linux基础 - Crontab定时任务

    目录 设置Cron任务 创建任务 设置运行周期 配置命令 常见问题 如何列出所有的Cron任务 如何查看Cron任务运行log 如何配置带有虚拟venv的Python脚本 如何在Cron 任务中发送邮 ...

  7. Python中的input你真会吗?

    前言本文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理.作者:一米阳光里的晴天娃娃   python中的input()方法是在控制台可 ...

  8. 写入Apache Hudi数据集

    这一节我们将介绍使用DeltaStreamer工具从外部源甚至其他Hudi数据集摄取新更改的方法, 以及通过使用Hudi数据源的upserts加快大型Spark作业的方法. 对于此类数据集,我们可以使 ...

  9. 【JS】307- 复习 Object.assign 原理及其实现

    点击上方"前端自习课"关注,学习起来~ }let b = {    name: "muyiy",    book: {        title: " ...

  10. Windows安装MSYS2_切换zsh_整合cmder

    MSYS2是什么 MSYS2 (Minimal SYStem 2) 是一个MSYS的独立改写版本,主要用于 shell 命令行开发环境.同时它也是一个在Cygwin (POSIX 兼容性层) 和 Mi ...