首页 > 编程语言 >C++模板中的非类型参数

C++模板中的非类型参数

时间:2024-03-16 13:46:32浏览次数:26  
标签:int C++ 参数 len 数组 类型 Array 模板

C++ 模板

模板是一种泛型技术,目的是将数据的类型参数化,以增强 C++ 语言(强类型语言)的灵活性。C++ 对模板的支持非常自由,模板中除了可以包含类型参数,还可以包含非类型参数,例如:

template<typename T, int N> class Demo{ };
template<class T, int N> void func(T (&arr)[N]);

T 是一个类型参数,它通过classtypename关键字指定。N 是一个非类型参数,用来传递数据的值,而不是类型,它和普通函数的形参一样,都需要指明具体的类型。类型参数和非类型参数都可以用在函数体或者类体中。

当调用一个函数模板或者通过一个类模板创建对象时,非类型参数会被用户提供的、或者编译器推断出的值所取代。

在函数模板中使用非类型参数

C++函数模板的重载,我们通过 Swap() 函数来交换两个数组的值,其原型为:

template<typename T> void Swap(T a[], T b[], int len);

形参 len 用来指明要交换的数组的长度,调用 Swap() 函数之前必须先通过sizeof求得数组长度再传递给它。

为什么在函数内部不能求得数组长度,一定要通过形参把数组长度传递进去呢?这是因为数组在作为函数参数时会自动转换为数组指针,而sizeof只能通过数组名求得数组长度,不能通过数组指针求得数组长度。

多出来的形参 len 给编码带来了不便,我们可以借助模板中的非类型参数将它消除,请看下面的代码:

template<typename T, unsigned N> void Swap(T (&a)[N], T (&b)[N]){
    T temp;
    for(int i=0; i<N; i++){
        temp = a[i];
        a[i] = b[i];
        b[i] = temp;
    }
}

T (&a)[N]表明 a 是一个引用,它引用的数据的类型是T [N],也即一个数组;T (&b)[N]也是类似的道理。分析一个引用和分析一个指针的方法类似,编译器总是从它的名字开始读取,然后按照优先级顺序依次解析。

调用 Swap() 函数时,需要将数组名字传递给它:

int a[5] = { 1, 2, 3, 4, 5 };
int b[5] = { 10, 20, 30, 40, 50 };
Swap(a, b);

编译器会使用数组类型int来代替类型参数T,使用数组长度5来代替非类型参数N。注意,两个数组的长度一定要相等,否则会报错。

下面是一个完整的示例:

#include <iostream>
using namespace std;

template<class T> void Swap(T &a, T &b);  //模板①:交换基本类型的值
template<typename T, unsigned N> void Swap(T (&a)[N], T (&b)[N]);  //模板②:交换两个数组
template<typename T, unsigned N> void printArray(T (&arr)[N]);  //打印数组元素

template<class T> void Swap(T &a, T &b){
    T temp = a;
    a = b;
    b = temp;
}

template<typename T, unsigned N> void Swap(T (&a)[N], T (&b)[N]){
    T temp;
    for(int i=0; i<N; i++){
        temp = a[i];
        a[i] = b[i];
        b[i] = temp;
    }
}

template<typename T, unsigned N> void printArray(T (&arr)[N]){
    for(int i=0; i<N; i++){
        if(i == N-1)    cout<<arr[i]<<endl;
        else    cout<<arr[i]<<", ";
    }
}

int main(){
    //交换基本类型的值
    int m = 10, n = 99;
    Swap(m, n);  //匹配模板①
    cout<<m<<", "<<n<<endl;
    //交换两个数组
    int a[5] = { 1, 2, 3, 4, 5 };
    int b[5] = { 10, 20, 30, 40, 50 };
    Swap(a, b);  //匹配模板②
    printArray(a);
    printArray(b);
    return 0;
}

运行结果:

99, 10
10, 20, 30, 40, 50
1, 2, 3, 4, 5

printArray() 也使用了非类型参数,这样只传递数组名字就能够打印数组元素了。

在类模板中使用非类型参数

C/C++ 规定,数组一旦定义后,它的长度就不能改变了;换句话说,数组容量不能动态地增大或者减小。这样的数组称为静态数组(Static array)。静态数组有时候会给编码代码不便,我们可以通过自定义的 Array 类来实现动态数组(Dynamic array)。所谓动态数组,是指数组容量能够在使用的过程中随时增大或减小。

动态数组的完整实现代码如下:

#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;

template<typename T, int N>
class Array{
public:
    Array();
    ~Array();
public:
    T & operator[](int i);  //重载下标运算符[]
    int length() const { return m_length; }  //获取数组长度
    bool capacity(int n);  //改变数组容量
private:
    int m_length;  //数组的当前长度
    int m_capacity;  //当前内存的容量(能容乃的元素的个数)
    T *m_p;  //指向数组内存的指针
};

template<typename T, int N>
Array<T, N>::Array(){
    m_p = new T[N];
    m_capacity = m_length = N;
}

template<typename T, int N>
Array<T, N>::~Array()  {delete[] m_p;}

template<typename T, int N>
T & Array<T, N>::operator[](int i){
    if(i<0 || i>=m_length)  cout<<"Exception: Array index out of bounds!"<<endl;
    return m_p[i];
}

template<typename T, int N>
bool Array<T, N>::capacity(int n){
    if(n > 0){
        //增大数组
        int len = m_length + n;  //增大后的数组长度
        if(len <= m_capacity){
            //现有内存足以容纳增大后的数组
            m_length = len;
            return true;
        }
        else{
            //现有内存不能容纳增大后的数组
            T *pTemp = new T[m_length + 2 * n * sizeof(T)];  //增加的内存足以容纳 2*n 个元素
            if(pTemp == NULL){
                //内存分配失败
                cout<<"Exception: Failed to allocate memory!"<<endl;
                return false;
            }
            else{
                //内存分配成功
                memcpy( pTemp, m_p, m_length*sizeof(T) );
                delete[] m_p;
                m_p = pTemp;
                m_capacity = m_length = len;
            }
        }
    }
    else{
        //收缩数组
        int len = m_length - abs(n);  //收缩后的数组长度
        if(len < 0){
            cout<<"Exception: Array length is too small!"<<endl;
            return false;
        }
        else{
            m_length = len;
            return true;
        }
    }
}

int main()
{
    Array<int, 5> arr;
    //为数组元素赋值
    for(int i=0, len=arr.length(); i<len; i++)  arr[i] = 2*i;
    //第一次打印数组
    for(int i=0, len=arr.length(); i<len; i++)  cout<<arr[i]<<" ";
    cout<<endl;
    //扩大容量并为增加的元素赋值
    arr.capacity(8);
    for(int i=5, len=arr.length(); i<len; i++)  arr[i] = 2*i;
    //第二次打印数组
    for(int i=0, len=arr.length(); i<len; i++)  cout<<arr[i]<<" ";
    cout<<endl;
    //收缩容量
    arr.capacity(-4);
    //第三次打印数组
    for(int i=0, len=arr.length(); i<len; i++)  cout<<arr[i]<<" ";
    cout<<endl;
    return 0;
}

运行结果:

0 2 4 6 8
0 2 4 6 8 10 12 14 16 18 20 22 24
0 2 4 6 8 10 12 14 16


Array 是一个类模板,它有一个类型参数T和一个非类型参数N,T 指明了数组元素的类型,N 指明了数组长度。

capacity() 成员函数是 Array 类的关键,它使得数组容量可以动态地增加或者减小。传递给它一个正数时,数组容量增大;传递给它一个负数时,数组容量减小。

之所以能通过[ ]来访问数组元素,是因为在 Array 类中以成员函数的形式重载了[ ]运算符,并且返回值是数组元素的引用。如果直接返回数组元素的值,那么将无法给数组元素赋值。

非类型参数的限制

非类型参数的类型不能随意指定,它受到了严格的限制,只能是一个整数,或者是一个指向对象或函数的指针(也可以是引用)。引用和指针在本质上是一样的。

1) 当非类型参数是一个整数时,传递给它的实参,或者由编译器推导出的实参必须是一个常量表达式,例如102 * 3018 + 23 - 4等,但不能是nn + 10n + m等(n 和 m 都是变量)。

对于上面的 Swap() 函数,下面的调用就是错误的:

int len;
cin>>len;
int a[len];
int b[len];
Swap(a, b);

对上面的 Array 类,以下创建对象的方式是错误的:

int len;
cin>>len;
Array<int, len> arr;

这两种情况,编译器推导出来的实参是 len,是一个变量,而不是常量。

2) 当非类型参数是一个指针(引用)时,绑定到该指针的实参必须具有静态的生存期;换句话说,实参必须存储在虚拟地址空间中的静态数据区。局部变量位于栈区,动态创建的对象位于堆区,它们都不能用作实参。

标签:int,C++,参数,len,数组,类型,Array,模板
From: https://www.cnblogs.com/uacs2024/p/18076984

相关文章

  • C++示例:学习C++标准库,std::unordered_map无序关联容器的使用
    01std::unordered_map介绍std::unordered_map是C++标准库中的一种无序关联容器模板类,它提供了一种将键映射到值的方法。它的底层基于哈希表实现,内容是无序的,可以在平均情况下在O(1)的时间复杂度内完成插入、查找和删除操作。值得注意的是,哈希表可能存在冲突,即不同的键值......
  • 酷睿Ultra 9 185h和i5-13500H选哪个好?参数性能区别对比
    i513500h采用10纳米制作工艺最高睿频4.7GHz十二核心十六线程三级缓存18MB热设计功耗(TDP)45W支持最大内存64GB内存类型DDR43200MHzDDR55200MHz集成显卡IntelIrisXeGraphics选Ultra9185h还是i5-13500H这些点很重要看过你就懂了http://www.adiannao.c......
  • 酷睿i9 14900hx参数 i914900hx核显什么水平
    i914900hx采用Intel7制程工艺,有24核心,其中8个高性能核心,16个高效能核心,共32线程,P核心最大睿频5.8GHz,全核最大睿频5.2GHz;E核心最大睿频/全核心最大睿频4.1GHz,L2缓存32MB、L3缓存36MB,TDP55W,最大可配置功耗为157W,内存支持DDR55600MHz。i914900hx怎么样这些点很重要 http:/......
  • 滴水逆向笔记系列-c++总结2-36.权限控制-37.虚函数-38.多态_绑定
    第三十六课c++3权限控制1.定义和实现分开写2.private和publicprivate权限说明私有变量在类外是无法访问的,只有在类内或者使用类内函数访问类内函数访问3.private真的不能访问吗反汇编看看t对象在初始化public和private成员时都是一视同仁的,在底层还是没区别,都是编......
  • 滴水逆向笔记系列-c++总结3-39.模板-40.引用_友元_运算符重载
    第三十八课c++6模板1.冒泡排序和折半查找voidSort(int*arr,intnLength) { inti; intk; for(i=0;i<nLength-1;i++) { for(k=0;k<nLength-1-i;k++) { if(arr[k]>arr[k+1]) { inttemp=arr[k]; a......
  • 滴水逆向笔记系列-c++总结4-41.new-delete-vector-42.链表
    第四十课c++8new-delete-vector1.内存空间复习在类外函数外的变量就是全局变量,程序一编译地址就已经确定了的临时数据,参数和局部变量就是在堆栈里而使用malloc函数动态申请的则是在堆里2.跟踪调试反汇编函数我们调用malloc函数申请内存,但是不是malloc一个函数完成整个......
  • C++性能分析工具
    gprof:这是一个GNU的性能分析工具,主要用于分析程序的函数调用关系,以及每个函数的运行时间等。Valgrind:这是一个用于内存调试、内存泄漏检测以及性能分析的开源工具集。其中,Valgrind的Callgrind工具可以收集程序运行时的函数调用信息,用于性能分析。perf:这是Linux下的一个性能分析......
  • SQLiteC/C++接口详细介绍-sqlite3类(一)
    快速跳转文章列表:SQLite—系列文章目录  上一篇:SQLiteC/C++接口简介 下一篇:SQLiteC/C++接口详细介绍(二) 引言:SQLiteC/C++数据库接口是一个流行的SQLite库使用形式,它允许开发者在C和C++代码中嵌入SQLite基本功能的解决方案。通过SQLiteC/C++数据库接口,开发者可以......
  • 【C++函数速查】lower_bound和upper_bound使用方法详细解读
    文章目录1)概述2)函数使用3)案例代码1)概述lower_......
  • 【C++算法模板】图论-拓扑排序,超详细注释带例题
    文章目录0)概述1)Kahn算法1:数据结构2:建图3:Kanh算法2)DFS染色1:数据结构2:建图3:DFS3)算法对比【例题】洛谷B3644推荐视频链接:D01拓扑排序0)概述给定一张有向无环图,排出所有顶点的一个序列A......