用C#也开发了也好几个项目,再一章章写基本语法没啥效果,一些很基础的或已经熟用的就不写了,主要写一些常用但概念模糊、未熟记及重要的知识点。第一篇就写写数值传参,或许你会发现,跟C++很神似。
值传参时,实参可能会是一个值类型或者引用类型的变量。何谓值类型和引用类型?值类型就是真实数据存栈中,引用类型就是真实数据存堆中,地址存堆中,是不是很有变量和指针的味道。如果是值类型,栈中形参复制一个真实数据,占内存;相反,如果是引用类型,栈中则复制出一个引用地址,指向同一个真实数据。
引用传参是,形参相当于是实参的别名,不重新开辟内存,形参变实参也跟着变。切勿忘记引用函数时候,写上修饰符ref或者out,两者同为引用传参,有啥异同呢?
差异:
1.参数初始化。ref不能用未初始化的实参传递,out则传递之前没有要求;
int x;
object.max(ref x)//会报错,一定要初始化
2.方法内形参使用。out在方法结束前必须显示给out形参赋值,ref没要求。ref可以把参数的数值传递进函数,但是out是要把实/形参数清空,就是说你无法把一个数值从out传递进去的,out进去后,参数的数值为空,所以你必须初始化一次。这个就是两个的区别,或者说就像有的网友说的,ref是有进有出,out是只出不进。
class Program
{
static public int s=13;
static void Main(string[] args)
{
min(out s);
}
static public void min(out int y)
{
int c = y;//y是(s)已经被清空为null,无法赋值,报错,必须先赋值后使用
y = 12;
}
}
相同:
1.当同时存在ref和out修饰符的同名函数时,无法编译,报错;
//无法重载,报错
public int max(out int z)
{
z = 16;
return z;
}
public void max(ref int x)
{
x = 15;
}
2.重载,当有且仅有ref或out,可以重载函数。
出于篇幅考虑,我们再讲讲const和readonly的区别吧,否则太苍白无力了。之前在写C#代码时用的比较随意,只知道说这两个关键字一个是不变常量并且无法修改,一个是只读不能进行写操作,感觉都是只读,今天要深入挖掘一下。首先这两个修饰符牵扯到C#的静态常量和动态常量,不妨先看下概念:
静态常量(编译时常量)是指编译器在编译时候会对常量进行解析,并将常量的值替换成初始化的那个值,const修饰即为静态常量,是不是很C++,宏定义中的预编译。
动态常量(运行时常量)的值则是在运行的那一刻才获得的,编译器编译期间将其标示为只读常量,而不用常量的值代替,这样动态常量不必在声明的时候就初始化,而可以延迟到构造函数中初始化,用readonly修饰。
readonly为运行时常量,程序运行时进行赋值,赋值完成后便无法更改,因此也有人称其为只读变量。
异:
1.const不能跟static同用,static全局存在,const编译时替代;readonly可以跟static一起用;
2.const定义的时候就要初始化,reandonly可以定义时初始化与否都可,可在构造函数中赋值初始化,其它地方不能赋值;
3.const可以修饰在编译的时候就需要有确定的值,只能用于数值类型(但不行结构体)和字符串,或者引用类型只能为null,因为一般对象引用都是在运行时分配内存决定的。而readonly在程序运行时才会去求值,它可以是任意类型,当然可以是object,数组,struct;
4.const可直接用于static方法,readonly如果用于静态方法必须用static修饰,跟普通变量用于static静态方法一样;
5.readonly还可以赋动态运行时值,比如DateTime.MinValue。
同:
1.都不能修饰属性;
2.一旦初始化后都不能改变。
是否很好奇,为什么string是引用类型,为什么能用const也能修饰?有些解释说string是个特殊的引用类型,其实我比较赞同这个解释:编译期常量仅能用于基本类型(内建的整数和浮点类型)、枚举或字符串。在编译后得到的IL代码中,只有这些常量可以直接被替换成为它们的字面值。
这里我就举两个有趣的小例子,上述的用法我就不具体写了,平时敲码的时候注意一下就行。
namespace constANDreadonly
{
class Program
{
static readonly int A = B * 10;
static readonly int B = 10;
static void Main(string[] args)
{
Console.WriteLine("A is {0},B is {1} ", A, B);
}
}
}
我想很多人会认为是A is 100,B is 10!其实,正确的输出结果是A is 0,B is 10。static readonly是动态常量,变量的值在编译期间不予以解析,所以开始都是默认值,像A与B都是int类型,故都是0。而在程序执行到A=B*10;变成A=0*10=0,程序接着执行到B=10这句时候,才会真正的B的初值10赋给B。
namespace constANDreadonly
{
class Program
{
const int A = B * 10;
const int B = 10;
public static void Main(string[] args)
{
Console.WriteLine("A is {0},B is {1} ", A, B);
}
}
}
答案还会像上面一样吗?当然不,这次输出结果是A is 100,B is 10!上面说了,const是静态常量,所以在编译的时候就将A与B的值确定下来了(即B变量时10,而A=B*10=10*10=100),那么Main函数中的输出当然是A is 100,B is 10。
总结
静态常量和动态常量的本质区别,造成不用的用法规则,我们用表格总结一些这些区别:
静态常量 | 动态常量 | |
内存消耗 | 无 | 因需保存常量,需要消耗 |
初始化 | 很少的简单类型,不能New,必须在定义时初始化 | 任意类型,可以在构造函数中初始化 |
何时发挥作用 | 在编译的时候进行替换 | 相当于类的数据成员 |