news 2026/8/8 10:19:49

同样是 static,模板类和普通类区别在哪?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
同样是 static,模板类和普通类区别在哪?

同样是 static,模板类和普通类区别在哪?

  • 一、模板类和普通类区别
    • 1、 引言
    • 2、static 在普通类中的含义
      • 2.1、 静态数据成员
      • 2.2、 静态成员函数
    • 3、 static 在模板类中的含义
      • 3.1、 静态数据成员(模板类)
      • 3.2、 静态成员函数(模板类)
    • 4、 核心区别对比
    • 5、一个关键示例:展示“独立性”
    • 6、 总结与使用建议
      • 核心总结
      • 使用建议
  • 二、代码示例
    • 1、示例代码
    • 2、运行结果

一、模板类和普通类区别

1、 引言

在 C++ 编程中,static关键字是一个多功能修饰符,它既可以用于普通类(非模板类),也可以用于模板类。虽然都叫static,但它们在语义、生命周期、初始化时机以及使用场景上存在显著差异。理解这些区别对于编写正确、高效的 C++ 代码至关重要。本文将深入探讨static在普通类和模板类中的不同行为,并通过代码示例阐明其核心区别。

2、static 在普通类中的含义

在普通类(非模板类)中,static用于声明类成员,包括静态数据成员和静态成员函数。

2.1、 静态数据成员

  • 定义:属于类本身,而不是类的任何一个对象。所有该类的对象共享同一份静态数据成员。
  • 生命周期:从程序开始运行到结束(整个程序的生命周期)。
  • 初始化:必须在类外(全局作用域)单独定义并初始化一次。
  • 访问:可以通过类名直接访问(ClassName::staticMember),也可以通过对象访问。
classOrdinaryClass{public:staticintcount;// 声明静态数据成员OrdinaryClass(){count++;}};// 必须在类外定义并初始化intOrdinaryClass::count=0;// 定义和初始化intmain(){OrdinaryClass obj1,obj2;std::cout<<OrdinaryClass::count<<std::endl;// 输出 2return0;}

2.2、 静态成员函数

  • 定义:属于类本身,不依赖于任何对象实例。
  • 特点:没有this指针,因此只能访问类的静态成员(静态数据成员和其他静态成员函数),不能访问非静态成员。
  • 调用:可以通过类名直接调用。
classOrdinaryClass{public:staticintcount;staticvoidprintCount(){std::cout<<"Count: "<<count<<std::endl;// 错误:不能访问非静态成员// std::cout << nonStaticMember << std::endl;}private:intnonStaticMember=10;};intOrdinaryClass::count=5;intmain(){OrdinaryClass::printCount();// 输出 Count: 5return0;}

3、 static 在模板类中的含义

在模板类中,static的声明方式与普通类类似,但其每个模板实例化都会拥有自己独立的静态成员副本。这是最核心的区别。

3.1、 静态数据成员(模板类)

  • 定义:每个不同的模板参数组合(即每个不同的模板实例化类型)都会生成一个独立的静态成员实例。
  • 生命周期:同样为整个程序生命周期,但每个实例化类型独立管理自己的静态成员。
  • 初始化:同样需要在类外定义,但定义本身也是模板化的。
template<typenameT>classTemplateClass{public:staticT value;// 声明静态数据成员};// 类外定义(也是模板)template<typenameT>T TemplateClass<T>::value=T();// 默认初始化intmain(){TemplateClass<int>::value=42;TemplateClass<double>::value=3.14;std::cout<<TemplateClass<int>::value<<std::endl;// 输出 42std::cout<<TemplateClass<double>::value<<std::endl;// 输出 3.14// 这是两个完全独立的内存位置return0;}

3.2、 静态成员函数(模板类)

  • 定义:每个模板实例化拥有自己独立的静态成员函数。
  • 特点:同样没有this指针,只能访问对应实例化类型的静态成员。
  • 调用:通过ClassName<TemplateArgs>::staticFunction()调用。
template<typenameT>classTemplateClass{public:staticT data;staticvoidsetData(constT&val){data=val;}staticTgetData(){returndata;}};template<typenameT>T TemplateClass<T>::data=T();intmain(){TemplateClass<int>::setData(100);TemplateClass<std::string>::setData("Hello");std::cout<<TemplateClass<int>::getData()<<std::endl;// 输出 100std::cout<<TemplateClass<std::string>::getData()<<std::endl;// 输出 Helloreturn0;}

4、 核心区别对比

特性普通类中的 static模板类中的 static
实例化数量整个类只有一个静态成员实例。每个不同的模板参数类型组合都会生成一个独立的静态成员实例。
内存与生命周期单一实例,程序生命周期。多个实例(每个实例化类型一个),各自拥有独立的存储和生命周期。
定义与初始化在类外进行一次全局定义:int ClassName::member = value;在类外进行模板化定义template<typename T> T ClassName<T>::member = value;
访问方式ClassName::memberobject.memberClassName<TemplateArgs>::memberobject<TemplateArgs>.member
主要用途实现类级别的共享数据或工具函数(如计数器、全局配置、单例模式)。每种类型提供独立的类级别共享数据(如类型特定的缓存、工厂映射、类型标识)。

5、一个关键示例:展示“独立性”

下面的例子清晰地展示了模板类static成员的独立性:

#include<iostream>#include<string>template<typenameT>classTypeLogger{public:staticintcreationCount;TypeLogger(){creationCount++;}staticvoidreport(){std::cout<<"TypeLogger<"<<typeid(T).name()<<"> instances: "<<creationCount<<std::endl;}};// 为每个实例化类型单独定义静态成员template<typenameT>intTypeLogger<T>::creationCount=0;intmain(){TypeLogger<int>a1,a2,a3;TypeLogger<double>b1,b2;TypeLogger<std::string>c1;TypeLogger<int>::report();// 输出: TypeLogger<int> instances: 3TypeLogger<double>::report();// 输出: TypeLogger<double> instances: 2TypeLogger<std::string>::report();// 输出: TypeLogger<std::string> instances: 1// 普通类作为对比classOrdinaryLogger{public:staticintcount;OrdinaryLogger(){count++;}};intOrdinaryLogger::count=0;OrdinaryLogger o1,o2,o3;std::cout<<"OrdinaryLogger instances: "<<OrdinaryLogger::count<<std::endl;// 输出 3// 无论创建什么对象,count 都累加,因为它们共享同一个静态成员。return0;}

输出结果分析

  • TypeLogger<int>TypeLogger<double>TypeLogger<std::string>creationCount互不影响,各自独立计数。
  • OrdinaryLogger的所有对象共享同一个count

6、 总结与使用建议

核心总结

  1. 普通类的static:实现跨对象的共享。所有同类对象共享同一份数据或函数。
  2. 模板类的static:实现跨对象但限定于同类型的共享。每个不同的模板实例化类型(如MyClass<int>MyClass<double>)拥有自己独立的静态成员,互不干扰。

使用建议

  • 当需要为整个类(所有对象)维护一份全局状态时,使用普通类的static。例如,对象计数器、全局配置管理器、单例模式实现。
  • 当需要为每个特定的类型维护独立于其他类型的类级别状态时,使用模板类的static。例如,实现一个类型特定的对象工厂注册表、为不同类型维护独立的缓存或元信息。

二、代码示例

1、示例代码

#include<iostream>#include<typeinfo>// ======================================================// 1. 普通类static 缺陷演示(全局共享计数器)// ======================================================classNormalBase{public:NormalBase(){++m_cnt;}staticsize_tGetCount(){returnm_cnt;}protected:staticsize_t m_cnt;};size_t NormalBase::m_cnt=0;classButtonNormal:publicNormalBase{};classWindowNormal:publicNormalBase{};// ======================================================// 2. 模板类static 隔离特性演示// ======================================================template<typenameT>classTemplateStaticDemo{public:TemplateStaticDemo(){++m_count;}staticsize_tGetCount(){returnm_count;}staticvoidPrintTypeName(){std::cout<<"模板实例类型:"<<typeid(T).name()<<std::endl;}private:staticsize_t m_count;};template<typenameT>size_t TemplateStaticDemo<T>::m_count=0;// ======================================================// 3. 修复版 CRTP 独立对象计数器【重点】// ======================================================template<typenameDerived>classCRTPObjectCounter{public:// 构造计数CRTPObjectCounter(){++s_objNum;}// 拷贝构造计数CRTPObjectCounter(constCRTPObjectCounter&){++s_objNum;}// 静态接口获取数量staticsize_tGetInstanceNum(){returns_objNum;}protected:// 保护析构:只能派生类析构,防止外部裸删除基类指针~CRTPObjectCounter(){--s_objNum;}private:// 显式声明静态成员变量,staticsize_t s_objNum;};// 模板静态变量类外初始化,固定写法template<typenameDerived>size_t CRTPObjectCounter<Derived>::s_objNum=0;// 业务派生类classButton:publicCRTPObjectCounter<Button>{};classWindow:publicCRTPObjectCounter<Window>{};classLabel:publicCRTPObjectCounter<Label>{};// ======================================================// 主函数测试// ======================================================intmain(){std::cout<<"========== 1. 普通基类static:子类共用同一个计数器 ==========\n";ButtonNormal b1,b2;WindowNormal w1;std::cout<<"总计数:"<<NormalBase::GetCount()<<"\n\n";std::cout<<"========== 2. 模板类static:不同类型互相隔离 ==========\n";TemplateStaticDemo<int>t1,t2;TemplateStaticDemo<double>t3;TemplateStaticDemo<int>::PrintTypeName();std::cout<<"int 实例数:"<<TemplateStaticDemo<int>::GetCount()<<"\n";TemplateStaticDemo<double>::PrintTypeName();std::cout<<"double 实例数:"<<TemplateStaticDemo<double>::GetCount()<<"\n\n";std::cout<<"========== 3. CRTP 每类独立计数器(核心) ==========\n";Button btn1,btn2,btn3;Window win1;Label lab1,lab2;std::cout<<"Button 数量:"<<Button::GetInstanceNum()<<"\n";std::cout<<"Window 数量:"<<Window::GetInstanceNum()<<"\n";std::cout<<"Label 数量:"<<Label::GetInstanceNum()<<"\n\n";// 临时对象析构测试{Button tmp;std::cout<<"创建临时Button后:"<<Button::GetInstanceNum()<<"\n";}std::cout<<"销毁临时Button后:"<<Button::GetInstanceNum()<<"\n";return0;}

2、运行结果

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/8 10:19:31

Lua+Go游戏开发实战:构建高并发水果机服务端与客户端架构

1. 项目概述&#xff1a;一个跨语言协作的游戏架构 最近和朋友一起折腾&#xff0c;用Python的cocos2d-lua框架做客户端&#xff0c;Go语言写后端服务&#xff0c;搞了一个网络版的水果机游戏。这个组合听起来有点“混搭”&#xff0c;但实际跑下来&#xff0c;效果出奇地好。项…

作者头像 李华
网站建设 2026/8/8 10:17:13

AEE mokacam运动相机按键失灵修复指南:从诊断到焊接更换微动开关

你手里有一台 AEE mokacam 运动相机&#xff0c;它曾经是你记录户外骑行、徒步甚至日常 Vlog 的得力伙伴。但不知道从什么时候开始&#xff0c;它的某个按键——也许是电源键&#xff0c;也许是模式切换键&#xff0c;也许是录制键——变得不那么灵敏了。按下去手感生涩&#x…

作者头像 李华
网站建设 2026/8/8 10:13:33

基于提示的跨物种动物姿态追踪:原理、实现与应用

最近在动物行为研究和生物医学领域&#xff0c;有一个痛点越来越突出&#xff1a;如何高效、准确地追踪不同物种的动物姿态&#xff1f;传统的解决方案往往“专精”于单一物种&#xff0c;比如训练一个模型只能识别小鼠&#xff0c;换到斑马鱼或果蝇上就完全失效。这不仅意味着…

作者头像 李华
网站建设 2026/8/8 10:07:49

从“安和昴”现象到工程实践:构建具备长期记忆与强人设的AI角色

最近在AI圈里&#xff0c;一个名为“安和昴”的AI角色火了。但如果你以为这只是一个普通的虚拟偶像或者聊天机器人&#xff0c;那就错过了它背后真正值得开发者关注的东西。很多技术讨论停留在“对话很流畅”、“人设很可爱”的层面&#xff0c;但深入其技术实现和社区生态后&a…

作者头像 李华
网站建设 2026/8/8 10:06:50

云计算运维学习day13--LNMP架构(5)--MYSQL_3

目录 一.mysql路由器 1.1简单介绍 1.2实现mysql路由器 1.2.1准备工作 1.2.2下载mysql路由器软件&#xff0c;并解压安装 1.2.3修改文件配置 1.2.4恢复多组复制 1.2.5创建远程登录用户 1.2.6使用路由器远程访问数据库 1.2.7查看当前路由器连接的数据库 二.MySQL Inno…

作者头像 李华