当前位置: 首页 > news >正文

类模板的简单实例


author: hjjdebug
date: 2025年 05月 16日 星期五 15:06:00 CST
description: 类模板的简单实例


文章目录

  • 1.实例代码:
  • 2. 模板类写法
    • 2.1 模板类的构造函数.
    • 2.2 模板类中的语句
  • 3. 模板类的实例化过程.
    • 3.1 实例化的进一步试验.
  • 4. 怎样调试constexpr 修饰的函数?

类模板的概念可能很复杂,假定你已经理解了一些基本概念.
这里用简单实例来巩固一些基本概念.

1.实例代码:

$ cat main.cpp
#include <stdio.h>
//定义一个类模板,带类型参数和非类型参数
//将来可以生成很多类
template <typename Func, int N>
struct ConstArray 
{constexpr ConstArray(Func f) : data{} {for (int i = 0; i < N; ++i)data[i] = f(i);}int data[N];
};
//想调试,你可以去掉constexpr, 编译出debug版本调试
constexpr int myFunc(int i) { return i * 2; }/* 定义一个类模板实例, 实例化类模板, 型实结合的过程* 模板参数1,类型 ,传递实参deltype(&myFunc)* 模板参数2,整数 ,传递整数3** 定义类的对象名称arr,传递对象的构造参数&myFunc*/
//constexpr ConstArray<decltype(&myFunc),5> arr(&myFunc);  // 放开该语句,编译时就能生成数组 {0, 2, 4, 6,8}:int main()
{int(*pTest1)(int) = myFunc;int(*pTest2)(int) = &myFunc;printf("pTest1:%p,pTest2:%p\n",pTest1,pTest2);//两种赋值都能工作,随便选一种吧,都各有各的道理,其实是一样的,选第二种吧.ConstArray<decltype(myFunc),5> arr(myFunc);  //该对象执行时会被直接付给初值,数值是编译期调用构造计算好的.ConstArray<decltype(&myFunc),5> arr2(&myFunc); return 0;
}

运行:
./tt
pTest1:0x401136,pTest2:0x401136

2. 模板类写法

template <typename Func, int N>
struct ConstArray

template 是关键字, 表示模板, 模板就是模子, 就是由它可造出一堆东西来.
typename 是关键字, 表示模板类型, 也可以用class 关键字替换.
Func 是模板类型名称,是一个标识符, 你可以改成你喜欢的名字. 它代表了可变的类型.
int 是关键字, 是确定的类型为int
N 是标识符,可改为你喜欢的名字. 是非类型参数, 将来由固定的数值替换.
struct 是关键字, 代表定义的是类.
ConstArray 是标识符, 代表类模板的名称
与普通类定义比较,多了模板参数声明部分,这正式模板类的精华所在, 类型是不固定的,由参数确定类型.
模板类只注重算法,不注重类型,进一步把算法分离.
模板参数还可以传递固定类型的参数,例如这里的N,它是一个整形数, 叫非类型模板参数. 通常用来处理编译期常量

如果按定义读下来, 似乎模板类更自然, 但从意义而言,类模板更贴切,
类模板,模板类含义差不多,也就混用了.

2.1 模板类的构造函数.

constexpr ConstArray(Func f) : data{}

constexpr 是关键字, 表示函数可以在编译器调用.
ConstArray 与类同名,表示是构成函数
Func 是模板类传来的类型
f 是类型的实例,是对象, 其名称是标识符,可以随便起名.
data 是该类定义的 int 型数据成员. 是一个数组名称,数组大小为模板参数N

2.2 模板类中的语句

分析一条函数语句:
for (int i = 0; i < N; ++i) data[i] = f(i);
for循环意义比较明确,仅有f(i) 看着别扭, f 是我们传来的类型的实例, f(i)是什么意思?
如果f是一个函数指针,那f(i) 就是函数调用. 所以在实例化时要传递函数地址.

3. 模板类的实例化过程.

constexpr ConstArray<decltype(&myFunc),5> arr(&myFunc);

/* 定义一个类模板实例, 实现型实结合的过程

  • 模板参数1,类型 ,类型是gcc 从传递的构造参数推导出来的deltype(&myFunc)
  • 模板参数2,整数 ,传递整数5 , 表示只运算5次
  • 定义类的对象名称arr,传递对象的构造参数&myFunc

我们看看gdb中打印的arr, 这就是gcc 我们编译的计算机认识的对象. 一目了然.

(gdb) p arr
$3 = {data = {0, 2, 4, 6, 8}
}
(gdb) ptype arr
type = const struct ConstArray<int (*)(int), 5> [with Func = int (*)(int)] {int data[5];public:ConstArray(Func);
}

类型的名称就有点长了,是模板类型 struct ConstArray<int (*)int,5> 类型

3.1 实例化的进一步试验.

我们把实例化改一改,如下:
constexpr ConstArray<decltype(&myFunc),5> arr(1);
编译出错:
error: invalid conversion from ‘int’ to ‘int ()(int)’ [-fpermissive]
22 | constexpr ConstArray<decltype(&myFunc),5> arr(1); // 编译时生成数组 {0, 2, 4, 6,8}:
| ^
| |
| int
咿! 它怎么知道我传1不对,要求传int(
)(int) 函数呢? 噢! 是 decltype(&myFunc)推导出来的.
重新实例化:
constexpr ConstArray<int,5> arr(1);
编译报错:
main.cpp:9:42: error: ‘f’ cannot be used as a function
9 | for (int i = 0; i < N; ++i) data[i] = f(i);
| ^~
gcc 抱怨我们传入的类型对象没法当函数来调用, 是的,我们传入的是整数1,当然不能当函数.
这样对传递的类型要求有了更清晰的认识.
可见模板类会对传递的类型进行检查, 对i语句语法进行检查, 如果gcc不理解,就会报错,
可见其使用还是很安全的. 是啊,生不成执行代码,gcc 肯定是不干的.

如果我不用编译期生成数据,而在运行期再生成数据,是怎样的过程呢?
反编译发现,竟是直接把数值付给类成员, 调用构造函数及回调函数根本就没有执行.
26 ConstArray<decltype(&myFunc),5> arr(&myFunc);
0x0000000000401151 <+27>: movl $0x0,-0x20(%rbp)
0x0000000000401158 <+34>: movl $0x2,-0x1c(%rbp)
0x000000000040115f <+41>: movl $0x4,-0x18(%rbp)
0x0000000000401166 <+48>: movl $0x6,-0x14(%rbp)
0x000000000040116d <+55>: movl $0x8,-0x10(%rbp)

可见gcc 在编译期,根据用户提供的线索,构造函数,回调函数,按解释性语句已经帮我们计算好了数据,
直接来构建了对象, gcc 很强大!

4. 怎样调试constexpr 修饰的函数?

此例我想看看构造过程,因为如果函数比较复杂,运行出错怎么办?
你只需要去掉constexpr 就可以了. debug版还可以跟入函数内部.
否则的话, 静态编译,你只能看到编译的结果而看不到具体的过程.
调试的时候,myFunc 的地址也可以看见了.
(gdb) p myFunc
$6 = {int (int)} 0x401136 <myFunc(int)>
它说myFunc 地址是0x401136
类型: 输入参数是int,输出参数是int的函数类型

(gdb) p &myFunc
函数名称本来就是地址0x401136, 函数名称再取地址是什么意思?
由调试知道, 它认为还是地址,地址没有变,但类型变了,是函数指针类型的地址,
$7 = (int (*)(int)) 0x401136 <myFunc(int)>

这与我们通常意义上的变量取地址是不一样的内涵!
变量取地址是变量所处的内存地址. 函数名称取地址其值还是函数的地址,这只能算是和gcc的约定了.

但这就要小心了,函数指针解引用,其值还是函数地址.
即从0x401136函数指针解引用还是得0x401136函数地址, 效果上好象调不调解引用都一样

(gdb) ptype myFunc
type = int (int)
(gdb) ptype &myFunc
type = int (*)(int)

int(int) 与 int(*) int 有什么区别呢?

前者为函数类型,后者为函数指针类型.如果一定要区分的话.
我们定义一个函数指针pTest,为其赋值即可调用函数.
int (*pTest1) (int) = myFunc;
int (*pTest2) (int) = &myFunc;
两种赋值方式都是可以通过编译的,且pTest1 完全等价于pTest2
c++中允许写为 pTest(5); 这种形式来调用函数

实例化类时我传myFunc,或传&myFunc 结果都是对的, 因为它们传的都是0x401136
而且其构造函数的参数类型f 都是
(gdb) p f

4 = (int (*)(int)) 0x401136 <myFunc(int)>

在这里得到了统一.

int (int) 类型的Func类型声明一个变量f, 居然是int (*) (int) 类型的, 看起来还是有一点点不爽,

所以正宗的还是传递&myFunc 吧. 这样它们的类型一致性更好.
而且了解到myFunc与&myFunc它们的值是一样的. 用着也就放心了.
按说gcc 应该加一种限制,把函数地址限定为一种会更好,
或者让函数名称代表有效地址,
或者让函数名称取地址代表有效地址,
让2者都有效,总有一种混乱的感觉. 但如果限制某一种,也有管得太宽的嫌疑.
算了,现实是允许这种有点混乱的赋值,知道了其内在实现,心理用着也就踏实了.

http://www.xdnf.cn/news/491437.html

相关文章:

  • arxiv等开源外文书数据的获取方式
  • Spring2:应用事务+连接池形成的工具类
  • FC7300 Trigger MCAL配置引导
  • Android应用内存分析与优化 - 工具篇之Booster
  • ThinkStation图形工作站进入BIOS方法
  • 读论文alexnet:ImageNet Classification with Deep Convolutional Neural Networks
  • C++循环效率比较与优化建议
  • 现代计算机图形学Games101入门笔记(十三)
  • 二叉树子树判断:从递归到迭代的全方位解析
  • uniapp-商城-60-后台 新增商品(属性的选中和页面显示,数组join 的使用)
  • rocketmq并发消费
  • 从零开始掌握FreeRTOS(4)任务的动态和静态创建
  • 实验-实现向量点积-RISC-V(计算机组成原理)
  • 使用 ESP32 驱动 ±12V 压电无源蜂鸣器(NPN 三极管 + PWM 控制驱动电路)
  • Typescript学习教程,从入门到精通,TypeScript 流程控制语法知识点及案例代码(4)
  • Docker镜像和容器有什么区别
  • NDK19无法在AppleM芯片运行解决方案
  • 深入C++的set集合:用法、特性与应用实例
  • 2025 家用投影新标杆:雷克赛恩 CyberPro1 如何重新定义客厅观影体验
  • 新京东,正在成为一种生活方式
  • Transformer网络结构
  • 【笔记】 huggingface.co:443是连接出错吗
  • Node.js 实战二:接口参数校验与类型安全方案
  • 主打「反激进」的一汽丰田,靠稳扎稳打的技术实现突围
  • 实战记录:Java 高并发插入 MySQL 唯一索引表引发死锁的排查与解决
  • Windows 本地部署MinerU详细教程
  • 厂房气楼做法
  • [Lc] 5.16 One question a day周总结
  • 项目管理进阶:全文解读企业IT系统全生命周期管理与运营平台建设方案【附全文阅读】
  • 【RTMP】RTMP协议的详细介绍