C++模板非类型形参的详细讲解

前言

关于模板的非类型形参,网上有很多内容,C++primer只有大概一页的阐述,但是都不够清晰详细。下面我尽可能从自己的角度去给大家描述一下非类型形参的相关细节。如果想进一步理解非类型形参以及模板内容可以阅读C++template这本书,在4.1节,8.3.3节,13.2节都有相关解释。

模板除了定义类型参数,我们还可以在模板定义非类型参数。

什么是非类型形参?顾名思义,就是表示一个固定类型的常量而不是一个类型。

先举一个简单的例子(模板类与模板函数都可以用非类型形参)

//例子1:
template<class T,int MAXSIZE> class List{
      private:
         T elems[MAXSIZE];
      public:
         Print(){ cout<<"The maxsize of list is"<<MAXSIZE; }
}
List<int,5> list;
list.Print();//打印"The maxsize of list is 5"

这个固定类型是有局限的,只有整形,指针和引用才能作为非类型形参,而且绑定到该形参的实参必须是常量表达式,即编译期就能确认结果。

这里要强调一点,我们对于非类型形参的限定要分两个方面看

1.对模板形参的限定,即template<>里面的参数

2.对模板实参的限定,即实例化时<>里面的参数

下面逐个解释一下非类型形参的局限

1.浮点数不可以作为非类型形参,包括float,double。具体原因可能是历史因素,也许未来C++会支持浮点数。

2.类不可以作为非类型形参。

3.字符串不可以作为非类型形参

4.整形,可转化为整形的类型都可以作为形参,比如int,char,long,unsigned,bool,short(enum声明的内部数据可以作为实参传递给int,但是一般不能当形参)

5.指向对象或函数的指针与引用(左值引用)可以作为形参

下面解释一下非类型实参的局限

1.实参必须是编译时常量表达式,不能使用非const的局部变量,局部对象地址及动态对象

2.非Const的全局指针,全局对象,全局变量(下面可能有个特例)都不是常量表达式。

3.由于形参的已经做了限定,字符串,浮点型即使是常量表达式也不可以作为非类型实参

备注:常量表达式基本上是字面值以及const修饰的变量

//例子2:
template<class T,int MAXSIZE> class List{
private:
	T elems[MAXSIZE];
public:
	void Print(){ cout<<"The maxsize of list is "<<MAXSIZE; }
};

const int num1 = 9; ;//全局变量
static int num2= 9; ;//全局变量
const int num3 = 9; ;//局部变量

List<int,num1> list; //正确
List<int,num2> list; //错误
List<int,num3> list; //正确

//再看一个关于指针和字符串比较特别的例子
//例子3:
template<char const* name>
class pointerT{

};
 char a[] = "saaa";;//全局变量
 char a2[] = "saaa";;//局部变量,写在main函数里面
 char *b = "saaa";//全局变量
 char *const c = "saaa";//全局变量,顶层指针,指针常量

pointerT<"testVarChar">  p1;//错误

pointerT<a>  p2;//正确
pointerT<a2>  p22;//错误,局部变量不能用作非类型参数
pointerT<b>  p3;//错误,error C2975:“pointerT”的模板参数无效,应为编译时常量表达式
pointerT<c>  p4;//错误,error C2970: “c”: 涉及带有内部链接的对象的表达式不能用作非类型参数

//关于指针常量和常量指针可以参考博客

Const用法总结(快速区分指针常量与常量指针)

这里大家可能会有几个疑问

①.到底为什么字符串不能作为实参?

答:我们看到上面p1的模板实参是"testVarChar",然而当我们在另一个编译单元(.cpp文件)同样声明这么一个模板实例时,这两个"testVarChar"的地址可能是不同的,编译器传递给模板时就会传递传递不同的地址,从而导致这两个模板实例是两个不同且不兼容的类型。这就是支持字符串的问题所在。(这里可能更深的涉及模板的实现原理)

②.变量b和c作为模板实参为什么错误不同?

答:首先解释b实参,b在这里看做是一个指针,是一个全局指针,但是他不是一个常量表达式,所以b不对。我们再看看c,c相比于b对了一个const修饰符,表示这个指针是一个常量。然而const是一个比较特别的关键字,他具有内部链接属性(关于内连接参考博客 理解C++的链接:C++内链接与外链接的意义),也就是说仅在定义这个变量的文件内可见,不会造成不同编译单元的混编时的链接错误。

这个特性对于模板来说可是有问题的,就像问题①所描述的,由于每个编译单元可能都有一个c变量,导致在编译时,实例化多个c,而且c的地址还不同,这就造成二个模板的实例是两个不同且不兼容的类型。

③为什么a变量作为实参可以?

答:我看过一些书籍,上面举得例子都是用const修饰不行的情况下在加extern来形成extern constchara[]="saaa";这样形式的语句,extern和const联合使用确实可以压制const的内部属性。

这个a这里可以看做一个数组类型,进一步理解数组与指针的关系

附:char * itoa(int, char *, int); 第二个参数明明是char*,为什么却又不能是“char*”?

Itoa这个函数大家应该多多少少接触过,它的功能使把一个整型按照你给的进制转换成你想要的字符串,也就是这个函数让我觉得有必要再去研究一下字符串数组和字符串指针的区别。

首先看itoa这个函数原型,char * itoa(int originNum, char * targetStr, int standard);

第一个参数你的整型数据,第二个是一个字符串,第三个是一个int型表示N进制。

现在我们测试一下,

char *str=“hello”;

int num=123;

_itoa_s(num, str,10); //vs C++下使用会提示编译错误

itoa(num,str,10);//codeblocks下运行会崩溃,正常环境下都会崩溃的

char str2[]=“hello”;

itoa(num, str2,10);//运行正常

这样我们发现明明函数原型的参数就是char*,为什么我们写的str却不行呢?

char *和char[]到底有什么区别?

这里,我们先从其本质说起。说到底一个是数组一个是指针,两者其实除了都能保存字符串外区别确实大了。最重要的一点区别就是内存分配(关于C语言变量在内存的存储位置,大家可以参考…….),对于基本类型的单个变量与数组我们都会为其在栈上申请空间来存放数据,而指针只是指向一块内存的索引,所以char*声明的只是指向常量区”hello”的指针。

这时候我们再看一下itoa的功能,它是要把num转换成字符串存在str里面,然而这时候我们的str根本没有一块可以用的内存,当然会崩溃。

反观数组str2,在声明的时候就已经在栈上分配内存了,这时候当然可以保存数据了。

因此,必须要为其分配内存

char *t;

t = (char*)malloc(9*sizeof(char));

接着上面的例子,我们需要理解

char *str=“hello”;

char str2[]=“hello”;

这是两种不同的操作,str是声明一个指针指向常量区的”hello”。而str2是声明一个str2数组用来存放一个”hello”字符串的拷贝。总之,如果str动态申请内存的话,那么在堆里str指向的位置就会有一个”hello”,栈里面有str2指向的”hello”,常量区还有一个”hello”。虽然都是赋值,差距却非常大。

下面一个例子进一步证明了这一点(vs2012):

const char*s1= "sa1";

const char*s2= "sa1";

if(s1== "sa1")

{

         cout<<"ok";//打印ok

}

char *oname= (char*)malloc(6*sizeof(char));         

strcpy_s(oname,6,"hello");

cout<<oname;//打印hello

if(oname== "hello")

{

         cout<<oname;//不执行

}

char str2[]= "hello";

cout<<str2;//打印hello

if(str2== "hello")

{

         cout<<str2;//不执行

}

我们知道字符串比较不能用 == 直接比较,需要用strcmp,因为上面的s1,s2,oname,str2都是指针,比较其实只是比较指针的大小。我们看到,只有上面的 s1 == "sa1"结果是true。因为s1,s2指向的都是常量区的“sa1”字符串。oname,str2分别指向堆和栈区。

那么我们看到他不仅避免了①中的实例化地址不同的问题(因为是全局唯一的),而且还避免了const带来的内部链接问题,所以这一项可能是经过编译器优化过的结果。

总结

到此这篇关于C++模板非类型形参的文章就介绍到这了,更多相关C++模板非类型形参内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • C++中的类模板详解及示例

    C++中的函数模板 对于类的声明来说,也有同样的问题.有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如下面语句声明了一个类: 复制代码 代码如下: class Compare_int{ public:  Compare(int a,int b)  {   x=a;   y=b;  }   int max()  {   return (x>y)?x:y;  }  int min()  {   return (x<y)?x:y;  } private:  int x,y;}; 其作用是

  • C++类模板与模板类深入详解

    1.在c++的Template中很多地方都用到了typename与class这两个关键字,而且有时候二者可以替换,那么是不是这两个关键字完全一样呢? 事实上class用于定义类,在模板引入c++后,最初定义模板的方法为:template<class T>,这里class关键字表明T是一个类型,后来为了避免class在这两个地方的使用可能给人带来混淆,所以引入了typename这个关键字,它的作用同class一样表明后面的符号为一个类型,这样在定义模板的时候就可以使用下面的方式了:      t

  • 浅析C++中模板的那点事

    1.什么是模板 假设现在我们完成这样的函数,给定两个数x和y求式子x^2 + y^2 + x * y的值 .考虑到x和y可能是 int , float 或者double类型,那么我们就要完成三个函数: int fun(int x,int y);float fun(float x,float y);double fun(double x,double y); 并且每个fun函数内部所要完成的操作也是极其的相似.如下: 复制代码 代码如下: int fun(int x,int y){    int

  • C++中函数模板的用法详细解析

    定义 我们知道函数的重载可以实现一个函数名多用,将功能相同或者类似函数用同一个名来定义.这样可以简化函数的调用形式,但是程序中,仍然需要分别定义每一个函数. C++提供的函数模板可以更加简化这个过程. 所谓函数模板实际上是建立一个通用函数,其涵涵素类型额形参类型不具体指定,用一个虚拟的类型来代表,这个通用函数就称为函数模板. 凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需要在模板中定义一次即可.在调用函数时,系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能

  • C++模板类的用法实例

    本文实例讲述了C++中模板类的用法,分享给大家供大家参考.具体方法如下: //#include "StdAfx.h #ifndef __AFXTLS_H__ #define __AFXTLS_H__ #include <Windows.h> class CSimpleList { public: CSimpleList(int nNextOffset=0); void Construct(int nNextOffset); //接口 BOOL IsEmpty() const; voi

  • C++模板特例化应用实例

    模板特例化是C++程序设计中一个非常重要的应用,本文就以实例形式对其进行分析,相信对大家进一步理解C++程序设计能够带来一定的帮助.具体内容如下: 首先,模板是C++中一个很重要的特性,写一份代码能用于多种数据类型(包括用户自定义类型).例如,STL的sort()函数可以用于多种数据类型的排序,类stack可以用作多种数据类型的栈.但是,如果我们想对特定的数据类型执行不同的代码(而不是通用模板)呢?这种情况下就可以使用模板特例化(template specialization). 一.函数模板特

  • C++模板非类型形参的详细讲解

    前言 关于模板的非类型形参,网上有很多内容,C++primer只有大概一页的阐述,但是都不够清晰详细.下面我尽可能从自己的角度去给大家描述一下非类型形参的相关细节.如果想进一步理解非类型形参以及模板内容可以阅读C++template这本书,在4.1节,8.3.3节,13.2节都有相关解释. 模板除了定义类型参数,我们还可以在模板定义非类型参数. 什么是非类型形参?顾名思义,就是表示一个固定类型的常量而不是一个类型. 先举一个简单的例子(模板类与模板函数都可以用非类型形参) //例子1: temp

  • C++函数模板与重载解析超详细讲解

    目录 1.快速上手 2.重载的模板 3.模板的局限性 4.显式具体化函数 5.实例化和具体化 6.重载解析 6.1 概览 6.2 完全匹配中的三六九等 6.3 总结 7.模板的发展 1.快速上手 函数模板是通用的函数描述,也就是说,它们使用泛型来定义函数. #include<iostream> using namespace std; template <typename T> void Swap(T &a,T &b);//模板原型 struct apple{ st

  • C++二叉树的前序中序后序非递归实现方法详细讲解

    目录 二叉树的前序遍历 二叉树的中序遍历 二叉树的后序遍历 总结 二叉树的前序遍历 前序遍历的顺序是根.左.右.任何一颗树都可以认为分为左路节点,左路节点的右子树.先访问左路节点,再来访问左路节点的右子树.把访问左路节点的右子树看成一个子问题,就可以完整递归访问了. 先定义栈st存放节点.v存放值,TreeNode* cur,cur初始化为root. 当cur不为空或者栈不为空的时候(一开始栈是空的,cur不为空),循环继续:先把左路节点存放进栈中,同时把值存入v中,一直循环,直到此时的左路节点

  • C++类模板与函数模板基础详细讲解

    目录 函数模板 类模板 总结 函数模板 当我们想要定义一个可以支持泛型的函数时,就要采用函数模板的方式了.所谓泛型就是可以支持多种类型的操作,比如我们定义一个compare操作,他可以根据传递给他的参数类型动态调用对应的函数版本,实现多种类型的比较. template <typename T> int compare(const T &v1, const T &v2) { if (v1 < v2) return -1; if (v2 < v1) return 1;

  • C++超详细讲解模板的使用

    目录 一.函数模板 1.1函数模板概念 1.2 函数模板格式 1.3 函数模板的原理 1.4 函数模板的实例化 二.类模板 2.1 类模板的定义格式 2.2类模板的实例化 总结 一.函数模板 1.1函数模板概念 函数模板代表了一个函数家族,该函数模板与类型无关,在使用时被参数化,根据实参类型产生函数的特定类型版本. 1.2 函数模板格式 template<typename T1, typename T2,…,typename Tn> 返回值类型 函数名(参数列表){} template<

  • SpringBoot超详细讲解Thymeleaf模板引擎

    Jsp是最早的模板技术,用来处理视图层的,用来做数据显示的模板 B S结构: B:浏览器:用来显示数据,发送请求,没有处理能力 发送一个请求,访问a.jsp,a.jsp在服务器端变成Servlet,在将输出的数据返回给浏览器,浏览器就可以看到结果数据,jsp最终翻译过来也是个html页面 模板技术你就可以把它们当成字符串的替换,比如说:这里{data}这里有一个字符串,你把它换成固定值其他值,但是这个替换有一些附加的功能,通过模板技术处理视图层的内容 第一个例子: pom.xml:Thymele

  • Python模板的使用详细讲解

    目录 一 模板语法传值 二 过滤器 三 标签 四 自定义模板标签和过滤器 4.1 自定义过滤器 4.2 自定义标签函数 4.3 自定义inclusion_tag 五 模板的继承 六 模板的导入 一 模板语法传值 方式一: # urls.py path('template', views.template) # views.py def template(request): name = "jasper" age = "18" return render(reques

  • Golang表示枚举类型的详细讲解

    枚举,是一种重要的数据类型,由一组键值对组成,通常用来在编程语言中充当常量的标识符.在主流行编程语言如 c. java 等,都有原生支持.在 go 中,大家却找不到 enum 或者其它直接用来声明枚举类型的关键字.从熟悉其它编程语言的开发者转用 go 编程,刚开始会比较难接受这种情况.其实,如果你看到如何在 go 中表示枚举类型时,可能会感受到 go 语言设计者对简洁性.问题考虑的深度,是一般资浅工程师无法比拟的. 其实,在 go 语言设计者的眼里,enum 本质是常量,为什么要多余一个关键字呢

  • C++详解非类型模板参数Nontype与Template及Parameters的使用

    目录 非类型类模板参数 非类型函数模板参数 非类型模板参数的限制 非类型模板参数 auto 非类型类模板参数 前一章使用的例子 Stack 使用的是标准库中的容器管理元素,也可以使用固定大小的 std::array,它的优势是内存管理开销更小,数组的大小可以交给用户指定. #include <array> #include <cassert> template<typename T, std::size_t Maxsize> class Stack { private:

  • C++详细讲解互斥量与lock_guard类模板及死锁

    目录 互斥量的基本概念 互斥量的使用 lock_guard类模板 死锁 lock与lock_guard的使用 保护共享数据,操作时,用代码把共享数据锁住.操作数据.解锁 其他想操作共享数据的线程必须等待解锁.锁定住.操作.解锁 互斥量的基本概念 互斥量是个类对象,理解成一把锁,多个线程尝试使用lock()成员函数来枷锁这个锁,是有一个线程可以锁成功,成功的标志是返回 如果没有锁成功,那么流程卡在lock这里不断尝试去锁 互斥量的使用 #include <iostream> #include &

随机推荐