C#类的成员初始化顺序

C#作为一种纯面向对象的话言,为它编写的整个代码里面到处都离不开对象。一个对象的完整的生命周期是从开始分配空间到初始化,到使用,最后是销毁,使用的资源被回收。要想真正写出面高质量的代码,我们就得对这期间每一个阶段是怎么样一个状态,framework都做了些什么,我们又能够做些什么都要有些了解才行。ITPUB个人空间 I mc_+F
  一般来说大部分程序员对于一个创建好了的对象怎么使用都是比较清楚的,所以本文也就不想就这一部分做太多的说明,重点就集中开对象的创建和销毁这两个阶段,这也是程序员最容易范错误的阶断。本文首先来讲一讲对象成员的初始化,至于对象的释放和销毁,我想放到另外一篇文章里去讲。虽然本文是以C#2005 为例的,但推而广之,对于其它的基于CLS规范的语言应该也是一样的。
;?gLt)K t[8?0ITPUB个人空间NJt#tf`| l
首先我们来看看引用类型的成员初始化过程
:E5Z;_e+R!F0ITPUB个人空间 _,fF,kUxCCJ_5D
  我们来看一个例子吧 ITPUB个人空间1O(l)QmzE

[R];@U.Z,qIm8w#b0class ProgramITPUB个人空间 r H;Exs_ ] |e l
{ITPUB个人空间 G:Ym&RR2j8@T V
    
static void Main(string[] args)
l^6L:A2wq)M0    {ITPUB个人空间)[bJNG x6p
         DriveB d 
= new DriveB();
*{+y1@ N(o9zW0     }
.Wl:mw[V?E;G0}
rGY"/s^i0ITPUB个人空间#E%F'U~-["M-o
class BaseAITPUB个人空间+[6j Mh q]A/V~
{ITPUB个人空间L7Me"T]3_:B
    
static DisplayClass a = new DisplayClass("基类静态成员初始化");ITPUB个人空间r)C!s,CiB5XiRu s

w_[IuR4g0     DisplayClass BaseA_c 
= new DisplayClass("基类实例变量BaseA_c初始化");ITPUB个人空间pMSZ:p

#UqZ-Dm.e+K1`2~0    
public BaseA()ITPUB个人空间?UAUC C+J mti
    {ITPUB个人空间&V|0S+n]sO*h
         Console.WriteLine(
"基类构造方法被调用");
$Z'o G'bTf3U?0     }ITPUB个人空间+_,@o0a*uU9[Nl/E
}ITPUB个人空间C:m6X nPd

BC Md8m r)^ D0
class DriveB : BaseA
'WFwxc,w@%^^-K0{ITPUB个人空间 quj2U;B Cq$FV
    
static DisplayClass DriveB_b = new DisplayClass("继承类静态成员DriveB_b初始化");
E h6A eP[/z m0ITPUB个人空间'U qP0|]1eR e:l
    
//static BaseA DriveB_a = new BaseA();ITPUB个人空间?,L ADoi
ITPUB个人空间vMF{#B;vU'T)x5M
     DisplayClass DriveB_c 
= new DisplayClass("继承类实例变量DriveB_c初始化");ITPUB个人空间jQW&D+_8t${Hy;k~
ITPUB个人空间"M&] V*v!A6f~
   
public DriveB()ITPUB个人空间"Xd}0A2` Hm
    {
y:X./J)@y JQ&@0         Console.WriteLine(
"继承类构造方法被调用");ITPUB个人空间u4vXx![)I!J
     }
/M+Y8P5L%ev8_!rE0}
m4? M2H4_0
class DisplayClass
7q g+I+{s0{
XPJ(J$We0    
public DisplayClass(string diplayString)
x'a!b1]u4H0    {ITPUB个人空间6b:]6yi]Pps
         Console.WriteLine(diplayString);ITPUB个人空间O(^{c1Xd
         Console.WriteLine();
[!}Kp*a0     }ITPUB个人空间+OO4L eK~
}


3o1d B9Ut0程序动行的结果是:ITPUB个人空间x P)t$BumdI
继承类静态成员DriveB_b初始化ITPUB个人空间N2jn"Nrv{E
继承类实例变量DriveB_c初始化
-WWqi*/"X%S0基类静态成员初始化
V WLV }y.tyw0基类实例变量BaseA_c初始化
q3|0a.yo2|!t%S"I0基类构造方法被调用ITPUB个人空间$T/f9K0PB*a/F"h
继承类构造方法被调用
sAH;g*/{@0ITPUB个人空间xG[QL3YKQs)A
得出初始化顺序结论: ITPUB个人空间rr0r(x7^*D/?

P#B!}!SCA%I.c3~01)继承类静态成员变量初始化 
s'U6nE/c02)继承类实例变量初始化 
!C$w1Zz*D6W(h.N(Y03)基类静态静态成员变量初始化 
vnX3{G%IE1]'x04)基类实例变量初始化 
/S^I V"N P05)基类构造方法调用 ITPUB个人空间0vW{V b&~/
6)继承类构造方法调用。 
/fs*h6}:cf_2X0ITPUB个人空间cJ7E3/!w
  好像结果和JAVA的有点不一样啊, 有点混乱的感觉,搞不懂M$为什么要让初始化按这样的顺序执行,像JAVA那样严格的从基类到派生类多好呀.上例的运行结果说明, 构造函数这么这个和我们通常思路执行的顺序还是有一定的差别.对于实例成员初始化,基本上就是以下步骤执行:
J6aK5qV_01 类的对象初始化大体顺序上实例成员赋值到构造函数ITPUB个人空间ap5] LK EM
2 成员赋值初始化按照由子类到父类的顺序
$?+TR l%Zp,s?"UPS03 构造函数的初始化按照由父类到子类的顺序
K7{1F5q:]h2y-X0从这里我们有一点需要注意的是,因为成员赋值初始化是从子类到父类的,所以在子类的成员赋值初始化的过程中,不要引用父类定义的成员,因为这个时候父类成员还没有开始初始化.需要说明一点的是C#在创建对象的第一步分配内存完成后会动把所有实例成员变量初始化成变量的默认值,例如整型就是0,引用类型就是null.然后才开始进行成员变量初始化的过程.C#并没有提供类似于C++构造函数中成员特殊的初始化方式:
,jfo}]D uU*S0public constructor(int a)i_a(a){}
8Kc`#_k }J0估计是因为分配内存和初始化的严格分离,以及反射创建对象的需要,而且也不像C++那样追求的是extreme效率的原因吧;而且就像是以前看到有人说过,再好的语法级别的优化都不能改变写得烂的代码带来的效率低下.
~G8I:{Q c{0ITPUB个人空间LI1Og-o"F
  我们知道,C#里面的静态成员初始化不同于C++的静态成员初始化.C#里的静态成员只会在必要的时候,确切的说是在第一次访问该类的时候才会进行静态成员的初始化.这样做也是有一定道理的,一是减少了内存的开销,再就是加快了程序集启动的时间,很难想像多一个比较费时的静态初始化在程序启动的时候就一一进行,那样的等待会是比较痛苦的.而且大部分时间我们都只是使用一个程序集里面很少的一部分类,如果把程序集里面所有的类不管三七二十一都预先进行初始化的话,对内存和时间的浪废还是比较大的.ITPUB个人空间.`k}Z;k

qk U nc2}d4Q0  了解了静态成员初始化的时机,就引出了另外一个问题,如果两个类相互间引用,比如A类的静态初始化里引用到了B类,B类的静态
;P C:JS*e'c%V p0初始化里又引用到了A类,这个时候又会出现什么样的结果呢,还是用例子还说明吧,请看下面这段代码:

using System;ITPUB个人空间-xP1|te
 
class A
0K0{Kz"h8j0{
3G"sc/ qC{]*z'co0      
public static int X;
ve3SN.[7/F0      
static A(){ITPUB个人空间)`/QC(wX!BP B
         X
=B.Y+1;
oW)M-w7fFc0      }
4yQ Z2/if9t0}
8xG_;T wN#T0
class BITPUB个人空间6wmAP-k'ScB9Q
{ITPUB个人空间/gLzeA/b
      
public static int Y=A.X+1;ITPUB个人空间a&`1J KP#_,L
      
static B(){}ITPUB个人空间*g%e t/t {6UZ;D
      
static void Main(){
3q3f P v B7F0              Console.WriteLine(
"X={0},Y={1}",A.X,B.Y);
)`(UO0/ONB0      }ITPUB个人空间0p z"r-R_ R*_
}

ITPUB个人空间3/gS `y8/y
产生的输出结果是什么?ITPUB个人空间fl5|"g1B qu~Y9a {~
ITPUB个人空间mmiQm'J
一般来说静态声明赋值语句先于静态构造函数执行,没有赋值的类成员声明会被初始化成该类型的默认值,也就是说ITPUB个人空间(~(|$Q7_a}
public static int X;
d+T9G7/;}M7Tc }0public static int Y=A.X+1;
#/5^)Kiq$U,]wwC0比各自所在的静态构造函数先执行,前一句X没有赋值,默认就是0,后一句的Y在没有赋值之前也是0,赋值后就是A.X+1的值。
}#HZ4D)U3J$vgM0类的静态初始化包括成员变量的声明赋值,静态构造函数的执行。ITPUB个人空间B"Tz,/;^?j t!Hv-p
静态初始化只有在类第一次被访问的时候才执行,而且是优先于第一次访问该类的代码执行ITPUB个人空间)H(m/BcDF2`y

;u7y)Ov9eg#[A/]0因为Main函数在class B中,所以程序先执行的是上面的第二条语句,声明一个Y,再给Y赋值
WT#_L /G!p#~0在赋值的时候又用到了A类中的X静态,当第一次访问A.X的时候,会先调用A类的静态构造函数,这里执行赋值X=B.Y+1,而重新去访问B类的成员,因为前面说的静态初始化只有第一次被访问的时候会执行,所以再次访问B类的时候不会重复进行静态初始化的。这时会因为前一次初始化还未完成,特别是B.Y还没有赋值完成,所以根据上面说的,B.Y现在处理只是声明完成的状态,所以现在B.Y的值就是0,相应的得到的X的值就是1了,在A类的静态构造函数执行完成的时候,程序会再回到B中Y的赋值语句上来,这时候得到的A.X的值就是1,而Y赋值完成后,此时值就变成了2了ITPUB个人空间h@#h ax9tHd5n
因此最终输出的结果就是X=1,Y=2ITPUB个人空间@/h'G,vf:OGR

)d6B/]%NQ*| n)?0 对于引用类型成员的初始化说了这么多还是总结一下吧.C#中初始化变量(包括实例成员变量和静态成员变量)可以采用成员声明的地方赋值的方式,也可以采用构造函数的方式.我个人在使用实例对象的时候比较推荐采用构造函数的方式,因为构造函数赋值的方式执行的顺序是从父类到子类,这种顺序避免了子类成员变量的初始化过程引用了未赋值的父类成员变量.而且在构造函数中初始化变量可以采用更多的语句块,更多的判断逻辑来初始化,甚至可以加上结构化异常处理try{}catch{}来处理异常信息,远比单单一个赋值语句来得灵活.不过对于简单的内置基本类型(如int,Enum,string等)就无所谓在哪里进行初始化了.
Z,Vih+x5~&z0ITPUB个人空间2?!SsR)g%k9Zr
  以上是引用类型的初始化过程,值类型(这里主要是指的结构类型)的静态初始化和引用类型的完全一致.C#的结构类型是有构造函数的(记得C++里面结构也貌似可以声明构造函数),而实例成员的初始化因为结构没有派生的功能,所以在这方面反而比较简单.但是因为值类型始终是不能为空的,一旦声明就必须要分配相应的内存空间,有了内存空间当然是要首先进行初始化的了,这都是为了保证值类型的有效性吧.这个过程是由Framework来完成的,我们自己是没有办法写代码来控制.因此Framework自己在初始化调用构造函数的时候当然就需要对自己要调用的构造函数的参数作个统一的约定,最简单的就是无参构造函数了.所以在C#的每个结构里都默认隐含了一个无参的构造函数,程序员自己可以重载构造函数,但是不能声明自己的无参构造函数(这个是被Framework占用了的).ITPUB个人空间"j l? a2OMiB)t

wE8/I:j%xg5[d&/$?Z/?/0  有很多刚从C++转到C#的程序员在使用引用类型作为函数的临时变量的时候还能认识到在使用之前需要new一下创建实例再使用,但是在使用结构作为函数的临时变量的时候就喜欢声明后直接拿来使用,问起他们的时候总是说结构是值类型,值类型是存在栈上的,声明后就直接可以使用了.先不论这句话是不是正确的(关于C#中值类型和引用类型到底存在什么地方有时间以后一定写一篇文章专门讨论一下).首先按C#编程规范值类型同样是需要进行成员变量的封装的,很多值类型在声明后就不能够改变,而只声明一个结构体不赋值的话相当于是调用的默认的构造函数,而通常这个默认的构造函数对于我们来说是没有什么意义的.所以得到的值也是没有太大的用处,除非你是想用作out参数所实参,真正用到的时候还得另外赋值.所以当你这样使用结构体的时候,C#编译器会警告你,这个变量只是声明了没有赋值(其实是相当于有一个值,但是没有意义).其实变量使用之前赋值这也是一个很好的习惯,C++里面虽然直接声明了就可以用,但是一般也会在使用之前先ZeroMemory一下,这其实也是相当于初始化了结构体吧,唯一的区别是不需要重新分配空间.

 

内容概要:本文详细比较了GPU、TPU专用AI芯片在大模型推理优化方面的性能、成本及适用场景。GPU以其强大的并行计算能力和高带宽显存,适用于多种类型的神经网络模型和计算任务,尤其适合快速原型开发和边缘计算设备。TPU专为机器学习设计,擅长处理大规模矩阵运算密集型任务,如Transformer模型的推理,具有高吞吐量和低延迟特性,适用于自然语言处理和大规模数据中心的推理任务。专用AI芯片通过高度定制化架构,针对特定神经网络模型进行优化,如卷积神经网络(CNN),在处理特定任务时表现出色,同时具备低功耗和高能效比的优势,适用于边缘计算设备。文章还介绍了各自的优化工具和框架,如CUDA、TensorRT、TPU编译器等,并从硬件成本、运营成本和开发成本三个角度进行了成本对比。 适合人群:从事人工智能、深度学习领域的研究人员和技术人员,尤其是对大模型推理优化感兴趣的读者。 使用场景及目标:①帮助读者理解GPU、TPU和专用AI芯片在大模型推理中的优缺点;②为选择适合的硬件平台提供参考依据,以实现最优的推理性能和成本效益;③介绍各种优化工具和框架,帮助开发者高效部署和优化模型。 其他说明:本文不仅涵盖了硬件架构特性,还深入探讨了优化技术和应用场景,旨在为读者提供全面的技术参考。在选择硬件平台时,需综合考虑具体任务需求、预算限制及开发资源等因素。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值