关于静态函数的一些思考

本文详细探讨了静态函数与变量的概念及其应用场景,包括全局静态函数、类内静态函数及变量的特点,强调了它们在实际开发中的重要性。此外,文章还介绍了如何利用静态成员变量建立静态成员函数和类对象之间的关联。

优快云上发帖讨论关于静态函数与全局静态函数的区别时,有网友指出没有全局静态函数之说,既然是全局函数又怎么能是静态的那?关于这个问题,我不想正面回答,staticC++关键字之一,它的作用就是:把有static修饰的函数或者变量变为静态的,这里的静态而不仅仅表示存储在静态区,同时也标示这个函数或者变量为文件内编译有效,也就是本单元编译有效,从这一点上来理解,全局静态函数其实就是本编译单元有效的函数。而这时和static对应关系应该就是extern,由extern声明,表示外部有效,可以直接调用,那么从这个角度看extern修饰的函数或者变量才是全局的。ExternC编码中非常多的出现。不过我们这里讨论的是static问题,所以不想讨论过多的extern的问题。

同时通过这篇帖子,我也了解网友们想多了解些什么?大家觉得从理论方面讨论这些问题很没有意思?甚至人怀疑我是不是从事C++工作的,提到static函数的应用,无非是类名::函数。对于这些我在发帖中也提到“每周一贴的目的是整理出来在各个角度看待问题的观点,深刻的了解议题中讨论问题的应用环境”,我在这里不是想征求一个问题的解决方案,也不是为吸引人眼球而故意考察某项特性。我想讨论的是大家能从自己的开发环境中出发,讨论在实际开发中的应用。我也不希望通过这篇引导大家只讨论面试题或者算法或者语言特性的。而是更多的从实际出发,谈谈自己的应用,讲讲自己的设计,更加关心的是语言特性给我们带来设计思想的变化。大家很讨厌这种说教式的人,以免引起别人恶心,所以我还不说了。接下来我谈谈我对静态函数的认识,不正确的地方,欢迎指正。

查遍了Cpp2003,也没有找到关于静态函数的几乎完美的解释,所以请允许我用大白话的形式简单说明一下,在函数或者变量前面加上static修饰符号,以便把函数或者变量在类内或者文件范围内共享,那么我们把这种函数和变量叫静态函数和静态变量。

The static specifier can be applied only to names of objects and functions and to anonymous unions There can be no static function declarations within a block, nor any static function parameters.

我们把函数和变量声明为静态的有什么优点那,从静态变量的角度看,更容易理解一些

使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,就可以保证所有对象都能够访问到被更新后的值,这样可以提高效率和节省内存空间。这是静态变量的优点同时也是他的一个缺点,在类中声明了静态变量,无论用户使用使用这个类,而这个静态变量都会申请他需要的内存容量。对于多线程的情况下,访问静态变量我们需要加一些异步机制,防止多个线程同时修改静态变量。

类内静态函数比全局静态函数的用处要多的多,在这里先讲简单的

全局静态函数的应用比较常见的就是

static int fun()

{

          ...;

          return 1;

}

当我们希望在多个类中调用fun函数时,我们必须把fun声明为static类型,不然在link时编译器会发现多个关于fun的定义。这种函数的应用,多少带有C的色彩,尤其当我们在C环境写好的函数,移植到C++中时,需要在函数前面需要加上static,而如果我们需要移植多个函数时,更通用的一种方法是使用未命名名字空间

namespace{

          int fun()

          {

               ...;

              return 1;

          }

         

}

也许有人问如果在未命名名字空间中的函数再加上static修饰符号会怎么样?这个就完全取决以使用的编译器,可能会直接报错,也可能可以编译并正常使用。不过这样写,代码从语言层次上就很难移植编译环境,所以尽量不要这样写。

关于这样的应用,在我从前的项目中实施过。当时我需要定义很多函数,执行三角形,已经像素转换方面的计算,所以把这些函数声明为static的。

对于与全局静态函数,我们继续类内静态函数和静态变量的讨论,我想他们的应用是两者区别的最好体现。

对于类内静态变量,我们需要在类外初始化,这是在使用静态变量需要注意的地方,有些初学者很容易在这里出错。类内静态变量使用最多的就是计算类的实例化个数。

class A

{

          static int i; // 外部初始化为0

public:

          A() // 构造函数

          {

               ++i;

          }

          ~A() // 析构函数

          {

              --i;

          }

          static int CreateObjectNumber() // 或者创建对象的数目

          {

              return i;

          }

};

 

    也许读者认为这种方法,只是用来学习而已,实际很少使用。我不赞同这个观点。

在程序设计中,比如当前我做的皮肤系统中,我需要对所有创建的对象进行,并统计创建和销毁的对象是否相等,从而判断是否存在泄漏问题。所以我定义这样的一个类

    // 由于版权,我只保留说明问题的部分

    class SkinBaseImpl

{

public:

              SkinBaseImpl(void);

              virtual ~SkinBaseImpl(void);

public:

             

#ifdef SKINTRACE

private:

          static int _ObjNum;

#endif

};

 

#ifdef SKINTRACE

int SkinBaseImpl::_ObjNum = 0;

#endif  

 

/*=======================================================================*/

 

/**

     * @bri: SkinBaseImpl构造函数

     * @param: void

     */

SkinBaseImpl::SkinBaseImpl(void):_useSkin(USE)

{

          #ifdef SKINTRACE

          TRACE("Create new object, the number is %d/n",++_ObjNum);

          #endif

}

 

/*=======================================================================*/

 

/**

     * @bri: SkinBaseImpl析构函数

     * @param: void

     */

SkinBaseImpl::~SkinBaseImpl(void)

{

          #ifdef SKINTRACE

          TRACE("free the %d object/n",_ObjNum--);

          #endif

}

 

其他需要管理和统计创建销毁对象的类就可以从这个类继承。

在这里我定义了宏SKINTRACE,当需要使用trace,以便获取创建对象的数量时,定义这个宏就可以。而不需要时,比如发布版时,只需注销到SKINTRACE的定义,这样不会在发布颁布留下任何痕迹。

    通过上面的例子,我们知道静态函数和静态变量的几个特性

    1:静态变量受publicprotected private限制,也就是如果静态变量是protected或者private类型的,在类外不能访问,比如

    A::i是错误的

    这条规则同样适用于静态函数

2:静态变量在类内声明,而必须在类外初始化,模版类中应用也是这样。这里我们在static后面加上const类型,可以直接初始化。比如

Class A

{

    // Static int I = 5; // error

    Static const int I = 5; // ok

    Int m_list[I];

}

而这里I的应用也无非是Int m_list[I];

3:静态成员函数只能访问类的静态变量,而类的成员函数也可以访问类的静态变量,这样就可以通过静态成员变量建立类的静态成员函数和类对象的关联关系。

4:还存在一种静态变量,他不是全局静态变量,而是函数内的静态变量,如下例中的i,这算是对全局静态变量的一种补充。

int fun()

{

              static int i = 3;

              ++i;

              return i;

}

这种方式的好处时,只用调用fun函数时,静态变量i才申请内存,这也符合lazy evaluation的设计要求。只有当需要时,才去申请。

       同样作为破坏封装的一种技术应用是友元函数或者友元类的应用,很多人形象比喻这种方式是在封装的物体上开了一个小小的洞,不提倡使用这种技术。其实任何技术都有他应用的场所,不然就不会出现这种技术。不过不去了解这种特性,也许永远我们不会知道这些技术的重要性。碰见这些技术也只会使用有色眼镜去看。友元函数的特征基本如下

1)       必须在类的说明中说明友元函数,说明时以关键字friend开头,后跟友元函数的函数原型,友元函数的说明可以出现在类的任何地方,包括在privatepublic部分,不受private限制

2)       友元函数不是类的成员函数,所以友元函数的实现和普通函数一样,在实现时不用"::"指示属于哪个类,只有成员函数才使用"::"作用域符号;

3)       友元函数不能直接访问类的成员,只能访问对象成员,所以在调用友元函数时,确保友元类的必须实例化。

4)       友元函数可以访问对象的私有成员,但普通函数不行,这个需要注意,尤其是在友元类中,有时候发现两个类互相为友元类,确不能调用成员函数,就是这个原因。

5)       调用友元函数时,在实际参数中需要指出要访问的对象,也可以把对象声明为全局对象而在友元函数中调用,当然在友元函数中可以调用其他全局函数,或者实例对象等操作。

使用友员函数最大的优点就是,不用对类中的每个变量写Get/Set接口函数。尤其是当类中有大量的私有成员变量,而又不想为每个变量设置接口,同时又需要外部的某个函数调用。这样最好就是把这个函数声明为友元函数,我们在一些开源项目中很常见这种技术,比如阿agentx++

       说道现在我一直没有提到模版中静态函数的应用,其实对于模版的应用,我不是很熟练。只能简单的说明一下

       class AU

{

public:

          AU(){

          };

          string GetAU()

          {

              return "Base--GetAU";

          }

          virtual string GetAUU()

          {

              return "Base--GetAUU";

          };

          virtual ~AU(){};

};

 

template <class T,class TBase>

class TEMU:public TBase

{

public:

          string GetAA()

          {

              T* pt = static_cast<T*>(this);

              return pt->GetA(); // 这里调用的是static string GetA()函数

          }

 

          string GetBB()

          {

              T* pt = static_cast<T*>(this);

              return pt->GetB(); // 这里调用的是string GetB()

          }

 

public:

          string GetA()

          {

              return "TEMU - GetA";

          }

    

          string GetB()

          {

              return "TEMU - GetB";

          }

};

 

class DeriveTEMU : public TEMU<DeriveTEMU,AU>

{

public:

          static string GetA() // 注意这里是静态函数

          {

              return "DeriveTEMU - GetA";

          }

          string GetB()

          {

              return "DeriveTEMU - GetB";

          }

};

 

测试用力

DeriveTEMU u;

     TEMU<DeriveTEMU,AU> *p = &u;

     cout << p->GetAA() << endl;

     cout << p->GetBB() << endl;

输出结果

DeriveTEMU - GetA

DeriveTEMU GetB

在这里我们看到,调用类内静态函数的方式并不是简单的类名::函数的形式,而是通过模版父类调用子类静态函数,同样也给出了,调用普通函数的方式。这种机制可以理解为模版继承关系中的虚继承关系。当认识到模版中的静态函数使用,也许会更大的改变我们对静态函数的印象,这种机制在ATLWTL中有广泛的应用,几乎每种涉及到消息影射关系的类中,都使用这种方式。

    以上是我对静态函数,静态变量的简单认识。也没有面面俱到的讲道静态的细节,这样说估计很多人会反感的。如果有错误,不妥,或者您认为重要但没有提到的,欢迎指正。

    本回复公布一周,一周后揭帖,并发布下一个议题,同时欢迎大家公布自己的议题。

 
<think> 嗯,用户问的是C#中静态构造函数的作用,之前我已经给出了比较详细的说明,但用户可能希望更聚焦于"作用"这个点。从对话历史看,用户似乎在实际开发中遇到了静态构造函数的使用场景(WPF自定义控件),但之前没有深入理解。 用户引用的资料[^2]中提到了静态构造函数用于初始化静态成员和执行类级别的配置,这个点非常重要。结合资料[^3]强调的"只执行一次"特性,用户可能特别关心如何利用这个特性优化资源初始化。 需要突出几个关键作用点: 1. 一次性初始化(如配置文件加载) 2. 保证线程安全的单例模式 3. 静态成员的复杂初始化(比如需要异常处理的场景) 特别要注意用户提到的"省去对象初始化时的判断操作"[^1],这说明用户之前可能用if判断来延迟初始化,现在意识到静态构造函数能更优雅地解决这个问题。可以强化这个对比。 在回答中应该强调实际价值而非语法细节,比如: - 避免重复初始化带来的性能提升 - 简化代码(用写锁也用写空值检查) - 对WPF控件开发的具体帮助(静态资源注册等) 最后的相关问题要引导用户思考进阶用法,比如资料[^4]提到的静态类场景就很适合延伸讨论。 </think> 在 C# 中,**静态构造函数(Static Constructor)** 主要用于初始化类的静态成员和执行类级别的单次配置,其核心作用如下: --- ### 核心作用详解 1. **静态成员初始化** 用于初始化静态字段、属性等类级别数据,**确保使用前已完成初始化**: ```csharp class ConfigLoader { public static string ApiKey; static ConfigLoader() { ApiKey = File.ReadAllText("config.key"); // 首次访问前加载配置 } } ``` 2. **单次执行复杂逻辑** **在整个应用程序生命周期内仅执行一次**,适合执行耗时的初始化操作: ```csharp class ImageCache { public static readonly Dictionary<string, Bitmap> Cache; static ImageCache() { Cache = LoadAllImages(); // 预加载所有图片(仅执行一次) } } ``` 3. **线程安全的初始化** CLR 自动保证多线程环境下的**线程安全执行**,无需手动加锁: ```csharp class Logger { public static readonly object LockObj = new object(); static Logger() { // 无需额外锁机制,CLR 保证线程安全 } } ``` 4. **类级别配置** 执行与类相关的全局设置(如注册事件、初始化第三方库等): ```csharp class CustomControl { static CustomControl() { // WPF 控件中注册默认样式 DefaultStyleKeyProperty.OverrideMetadata( typeof(CustomControl), new FrameworkPropertyMetadata(typeof(CustomControl)) ); } } ``` --- ### ️ 与实例构造函数的对比 | **特性** | 静态构造函数 | 实例构造函数 | |--------------------|----------------------------------|----------------------------------| | **触发时机** | 类首次被使用前(实例化或访问静态成员) | 每次创建对象实例时 | | **执行次数** | 整个应用生命周期仅一次 | 每次实例化都执行 | | **访问权限** | 只能访问静态成员 | 可访问静态和实例成员 | | **线程安全** | 由 CLR 自动保证 | 需手动实现同步机制 | --- ### ⚠️ 关键注意事项 1. **可显式调用** 静态构造函数由 CLR 隐式触发,无法通过代码手动调用[^2]。 2. **异常导致类型可用** 若静态构造函数抛出异常,后续访问该类将触发 `TypeInitializationException`[^2]: ```csharp static MyClass() { throw new InvalidOperationException("初始化失败"); } // 后续访问:抛出 TypeInitializationException ``` 3. **性能影响** 避免在静态构造函数中执行耗时操作,否则会阻塞首次类型访问[^3]。 --- ### 💡 典型应用场景 1. **全局配置加载**(如数据库连接字符串) 2. **共享资源初始化**(如线程池、日志文件) 3. **注册框架组件**(如 WPF 控件样式注册[^1]) 4. **单例模式实现**(线程安全的基础保障) > 示例:实现线程安全的随机数生成器 > ```csharp > class SafeRandom { > private static readonly Random _random; > > static SafeRandom() { > _random = new Random(); // 仅初始化一次 > } > > public static int Next() => _random.Next(); > } > ``` --- ### 相关问题 1. 静态构造函数中抛出异常会导致什么后果?如何捕获此类异常? 2. 在多程序集场景下,静态构造函数的执行顺序如何控制? 3. 静态构造函数能否被继承?子类静态构造函数是否会触发父类的静态构造函数? 4. 如何避免静态构造函数中的循环依赖问题? 5. 在 .NET Core 的依赖注入体系中,静态构造函数有哪些替代方案?
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值