Const用法总结(快速区分指针常量与常量指针) char * itoa(int, char *, int); 第二个参数明明是char*,为什么却又不能是“char*”?


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

 

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

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

 

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

  1. //例子1:
  2. template< class T,int MAXSIZE> class List{
  3. private:
  4. T elems[MAXSIZE];
  5. public:
  6. Print(){ cout<< "The maxsize of list is"<<MAXSIZE; }
  7. }
  8. List< int, 5> list;
  9. 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修饰的变量


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

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

  11. List<int,num1> list; //正确
  12. List< int,num2> list; //错误
  13. List<int,num3> list; //正确

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

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


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

  24. p ointerT<a> p2;//正确
  25. pointerT<a2> p22; //错误,局部变量不能用作非类型参数
  26. pointerT<b> p3; //错误,error C2975:“pointerT”的模板参数无效,应为编译时常量表达式
  27. 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*”?

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


注意:虽然字符串不能直接作为参数传递进去非类型模版,但是可以使用以下的方式进行。const char* name

// t.h
#include <cstdio>
 
template < typename  T,  const  char * name>
struct  Registered
{
     void  show()
     {
         puts (name);
     }
};
 
#define REG_CLASS_DECL(CLASS) \
extern  char  class_ ## CLASS ## _name[];\
struct  CLASS :  public  Registered< CLASS, class_ ## CLASS ## _name >{}
 
#define REG_CLASS_IMPL(CLASS) \
char  class_ ## CLASS ## _name[] = # CLASS
 
REG_CLASS_DECL(test_class);
 
// t1.c
#include <cstdio>
#include "t.h"
int  main()
{
     test_class orz;
     orz.show();
     return  0;
}
 
// t2.c
#include "t.h"
REG_CLASS_IMPL(test_class);



三、非类型模板参数(Nontype Template Parameters)

对于函数模板和类模板,模板参数不一定必须是类型,也可是是常规的数值。当以类型(type)作为模板参数的时候,代码中未决定的是类型;当以一般的数字(non-type)作为模板参数的时候,代码中待定的内容便是某些数值。使用者这种模板必须要显示指定数值,模板才能实例化。

1、非类型类模板参数(Nontype Class Template Parameters)

之前章节中的列子中Stack类中使用vector或deque来存储元素。我们也可以使用一个固定大小的数值来存储元素。这么做的好处是在定义一个Stack对象的时候就分配了固定大小的内存空间,之后的元素操作就不再需要内存分配管理了。坏处是这个数值固定大小的设置比较困难,如果设置太小很数组容易满;如果设置太大又浪费内存空间。
一个可行方法是让使用者自己定义数组的最大空间。
如下:

// basics/stack4.hpp 

#include <stdexcept> 

template <typename T, int MAXSIZE> 
class Stack { 
  private: 
    T elems[MAXSIZE];        // elements 
    int numElems;            // current number of elements 
  public: 
    Stack();                 // constructor 
    void push(T const&);     // push element 
    void pop();              // pop element 
    T top() const;           // return top element 
    bool empty() const {     // return whether the stack is empty 
        return numElems == 0; 
    } 
    bool full() const {      // return whether the stack is full 
        return numElems == MAXSIZE; 
    } 
}; 

// constructor 
template <typename T, int MAXSIZE> 
Stack<T,MAXSIZE>::Stack () 
  : numElems(0)              // start with no elements 
{ 
    // nothing else to do 
}

template <typename T, int MAXSIZE> 
void Stack<T,MAXSIZE>::push (T const& elem) 
{ 
    if (numElems == MAXSIZE) { 
        throw std::out_of_range("Stack<>::push(): stack is full"); 
    } 
    elems[numElems] = elem;  // append element 
    ++numElems;              // increment number of elements 
} 

template<typename T, int MAXSIZE> 
void Stack<T,MAXSIZE>::pop () 
{ 
    if (numElems <= 0) { 
        throw std::out_of_range("Stack<>::pop(): empty stack"); 
    } 
    --numElems;              // decrement number of elements 
} 

template <typename T, int MAXSIZE> 
T Stack<T,MAXSIZE>::top () const 
{ 
    if (numElems <= 0) { 
        throw std::out_of_range("Stack<>::top(): empty stack"); 
    } 
    return elems[numElems-1];  // return last element 
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57

第二个新的模板参数MAXSIZE是int类型,它来指定stack对象所能容纳的最大元素个数。

使用这个Statck类模板的时候,需要指定元素类型和所能容纳的最大元素个数。如下:

Stack<int,20>         int20Stack;    // stack of up to 20 ints 
Stack<int,40>         int40Stack;    // stack of up to 40 ints 
Stack<std::string,40> stringStack;   // stack of up to 40 strings 
// manipulate stack of up to 20 ints 
int20Stack.push(7); 
int20Stack.pop(); 

// manipulate stack of up to 40 strings 
stringStack.push("hello"); 
std::cout << stringStack.top() << std::endl; 
stringStack.pop(); 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

每一个模板的实例都有自己的类型。上述的例子中,“int20Stack”和“int40Stack”是两个不同的类型,因此这两个类型之间是不能相互隐式或显示类型转换,也不能相互替换,也不能相互赋值。
可以对模板参数设置默认值,如下:

template <typename T = int, int MAXSIZE = 100> 
class Stack { 
  … 
};
  • 1
  • 2
  • 3
  • 4

2、非类型函数模板参数(Nontype Function Template Parameters)

可以为函数模板定义非类型参数,如下:

template <typename T, int VAL> 
T addValue (T const& x) 
{ 
    return x + VAL; 
} 
  • 1
  • 2
  • 3
  • 4
  • 5

3、非类型模板参数的局限(Restrictions for Nontype Template Parameters)

非类型模板有它的局限。通常它们只能是常数整数(constant integral values )包括枚举,或者是指向外部链接的指针。

float或者类类型的对象是不被允许的,如下:

template <double VAT>        // ERROR: floating-point values are not 
double process (double v)    //        allowed as template parameters 
{ 
    return v * VAT; 
} 

template <std::string name>  // ERROR: class-type objects are not 
class MyClass {              //        allowed as template parameters 
  … 
}; 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

字符串常量不能作为模板参数,如下:

template <char const* name> 
class MyClass { 
  … 
}; 

MyClass<"hello"> x;   // ERROR: string literal "hello" not allowed 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

全局指针也不能作为非类型的模板参数,如下:

template <char const* name> 
class MyClass { 
  … 
}; 

char const* s = "hello"; 

MyClass<s> x;         // ERROR: s is pointer to object with internal linkage 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

但是如下代码是可以的:

template <char const* name> 
class MyClass { 
  … 
}; 

extern char const s[] = "hello"; 

MyClass<s> x;        // OK 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

因为全局的char类型的数组已经被初始化为了”hello”,这是一个外部链接的对象。

4、总结

1、模板参数不仅仅可以是类型(type),还可以是值(value)
2、不能把float,class-type类型的对象,内部链接(internal linkage )对象,作为非类型模板参数。


猜你喜欢

转载自blog.csdn.net/windgs_yf/article/details/80915405