C#基础小记(一)
2021-02-19 23:18
1.值类型和引用类型
参考(http://blog.csdn.net/qiaoquan3/article/details/51202926)
值类型和引用类型的使用范围:
C#的值类型包括:结构体(数值类型、bool型、用户定义的结构体),枚举,可空类型。
C#的引用类型包括:数组,用户定义的类、接口、委托,object,字符串。
值类型和引用类型储存的方式:
在C#中值类型的变量直接存储数据,而引用类型的变量持有的是数据的引用,数据存储在数据堆中。
//值类型直接向栈中储存具体数据,引用类型储存的是数据的引用,也就是数据存储在数据堆上的地址,也可以理解为标识。
值类型和引用类型的继承类:
引用类型和值类型都继承自System.Object类。不同的是,几乎所有的引用类型都直接从System.Object继承,
而值类型则继承其子类,即 直接继承System.ValueType。System.ValueType直接派生于System.Object。
C#的所有值类型均隐式派生自System.ValueType;
值类型和引用类型的区别:
引用类型与值类型相同的是,结构体也可以实现接口;
引用类型可以派生出新的类型,而值类型不能;
引用类型可以包含null值,值类型不能(可空类型功能允许将 null 赋给值类型);
引用类型变量的赋值只复制对对象的引用,而不复制对象本身。而将一个值类型变量赋给另一个值类型变量时,将复制包含的值
2.堆和栈
参考(http://blog.csdn.net/wolenski/article/details/7951961)
stack和heap是两种内存分配的两个统称。
stack和heap的第一个区别就是申请方式不同:stack是系统自动分配空间的,而heap则是程序员根据需要自己申请的空间。
每一个线程都有一个stack,但是每一个应用程序通常都只有一个heap
stack和heap回收方式的区别:
由于stack上的空间是自动分配自动回收的,所以stack上的数据的生存周期只是在函数的运行过程中,运行后就释放掉,不可以再访问。而heap上的数据只要程序员不释放空间,就一直可以访问到,不过缺点是一旦忘记释放会造成内存泄露。
stack和heap申请后系统的响应:
stack:只要stack的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示stack溢出。
heap:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的heap。
stack和heap申请效率的比较:
stack:由系统自动分配,速度较快。但程序员是无法控制的。
heap:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。
总结:1.在创建stack的时候,stack的大小就固定了,因为stack需要连续占用一段时间。
而heap的大小是动态的,其分配和释放也是动态的。
2.如果stack的数据过多,就会爆栈,而heap如果也爆了,就说明内存也一起爆了。
3.每个函数的stack都是相对独立的,但是一个应用程序的heap是被所有stack所共享的。
特殊案例:
考虑数组:
int[] reference = new int[100];
根据定义,数组都是引用类型,所以int数组当然是引用类型(即reference.GetType().IsValueType为false)。
而int数组的元素都是int,根据定义,int是值类型(即reference[i].GetType().IsValueType为true)。那么引用类型数组中的值类型元素究竟位于栈还是堆?
如果用WinDbg去看reference[i]在内存中的具体位置,就会发现它们并不在栈上,而是在托管堆上。
实际上,对于数组:
TestType[] testTypes = new TestType[100];
如果TestType是值类型,则会一次在托管堆上为100个值类型的元素分配存储空间,并自动初始化这100个元素,将这100个元素存储到这块内存里。
如果TestType是引用类型,则会先在托管堆为testTypes分配一次空间,并且这时不会自动初始化任何元素(即testTypes[i]均为null)。等到以后有代码初始化某个元素的时候,这个引用类型元素的存储空间才会被分配在托管堆上。
3.结构体
参考(https://baike.baidu.com/item/%E7%BB%93%E6%9E%84%E4%BD%93?fr=aladdin)
结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合,叫做结构。
结构体的定义如下所示,
struct为结构体关键字,
tag为结构体的标志,
member-list为结构体成员列表,其必须列出其所有成员;
variable-list为此结构体声明的变量。
(一般情况下,tag、member-list、variable-list这3部分至少要出现2个)
规范:
struct tag {
member-list
} variable-list ;
例子:
struct simple { //struct为结构体关键字,simple为结构体标志
int a;
char b;
double c; //int a ,char b ,double c 为结构体成员
} s1;//s1为结构体声明的变量
结构体作用:
结构体和其他类型基础数据类型一样,例如int类型,char类型 只不过结构体可以做成你想要的数据类型。以方便日后的使用。
结构体在函数中的作用不是简便,其最主要的作用就是封装。封装的好处就是可以再次利用。
让使用者不必关心这个是什么,只要根据定义使用就可以了
4.构造函数
构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。
构造函数是在创建给定类型的对象时执行的类方法。构造函数具有与类相同的名称,它通常初始化新对象的数据成员。
构造函数的特点:
1.它的函数名与类名相同;
2.它可以重载;
3.不能指定返回类型,即使是void也不行;
4.虽然在一般情况下,构造函数不被显式调用,而是在创建对象时自动被调用。但是并不是不能被显式调用。有些时候是一定要显式调用的,只要是父类有带参的构造函数,在子类中就必须显式的调用父类的构造函数,因为子类的构造器在实例化时无法找到父类的构造函数(当父类有自己写的无参构造函数时,子类也不用显式调用)。
一般情况下,如果没有提供任何构造函数,编译器会在后台创建一个默认的构造函数。这是一个非常基本的构造函数,它只能把所有的成员字段初始化为标准的默认值(例如,引用类型为空引用,数字数据类型为0,bool为false)。
注意:如果提供了带参数的构造函数,编译器就不会自动提供默认的构造函数,只有在没有定义任何构造函数时,编译器才会自动提供默认的构造函数。
案例:
在下面的示例中,使用一个简单的构造函数定义了名为 Taxi 的类。然后使用 new运算符来实例化该类。在为新对象分配内存之后,new运算符立即调用 Taxi 构造函数。
publicclass Taxi
{
public bool isInitialized;
public Taxi()
{
isInitialized = true;
}
}
class TestTaxi
{
static void Main()
{
Taxi t = new Taxi();
System.Console.WriteLine(t.isInitialized);
}
}
5.静态
一、静态成员
1、通过static关键字修饰,是属于类,实例成员属于对象,在这个类第一次加载的时候,这个类下面的所有静态成员会被加载。
2、静态成员只被创建一次,所以静态成员只有一份,实例成员有多少个对象,就有多少份。
3、类加载的时候,所有的静态成员就会被创建在“静态存储区”里面,一旦创建直到程序退出,才会被回收。
4、变量需要被共享的时候,方法需要被反复调用的时候,就可以把这些成员定义为静态成员。
5、在静态方法中,不能直接调用实例成员,因为静态方法被调用的时候,对象还有可能不存在。
6、在实例方法中,可以调用静态成员,因为这个时候静态成员肯定存在。
二、静态成员和实例成员的区别
1、生命周期不一样。
2、在内存中存储的位置不一样。
三、静态类
1、被static关键字修饰的类。
2、静态类里面只能声明静态成员。
3、静态类的本质,是一个抽象的密封类,所以不能被继承,也不能被实例化。
4、如果一个类下面的所有成员,都需要被共享,那么可以把这个类定义为静态类。
四、静态构造函数
1、这个类的成员,第一次被访问之前,就会执行静态构造函数。
2、静态构造函数只被执行一次。
6.字段和属性
字段和属性是C#面向对象模式中的两个概念。
字段(field) 用来存储数值或对象的真正实体,属性(property) 对字段的封装(也不一定- -) 有get段落和set段落 ,字段和属性都属于类的成员。
属性自带读写器,也就是get(只读)和set(只写)。
案例:
Class test{
int val;
Public int value{
get{return this.val;}
set{this.val=value;}
}
}//它包含一个字段和一个有公开读写访问权的属性
7.Ref和out参数
1.ref参数
一般通过值传送变量是默认的,也可以迫使值参数通过引用传送给方法。为此,要是用ref关键字。如果把一个参数传递给方法,且这个方法的输出参数前带有ref关键字,则该方法对变量所做的任何改变都会影响原来对象的值。
案例:
Static void SomeFunction(int[] ints,ref int i)
{
Ints[0]=100;
I=100;
}
在调用该方法时,还需要添加ref关键字:
SomeFunction(ints,ref i);
最后,C#仍要求对传递给方法的参数进行初始化。在传递给方法之前,无论是按值传递,还是按引用传递,变量都必须初始化;
2.out参数
编译器使用Out关键字来初始化。在方法的输入参数前面加上out关键字时,传递给该方法的变量可以不初始化。该变量通过引用传送,所以在从被调用的方法中返回时,方法对该变量的任何改变都会保留下来。在调用该方法时,还需要使用out关键字,与在定义该方式一样:
Static void SomeFunction(out int i)
{
i=100;
}
Public static int Main()
{
Int i;
SomeFunction(out i);
Console.WriteLine(i);
Return 0;
}
3.ref 和out的区别:
(1)ref传进去的参数必须在调用前初始化,out不必,即:
int i;
SomeMethod( ref i );//语法错误
SomeMethod( out i );//通过
(2)ref传进去的参数在函数内部可以直接使用,而out不可:
public void SomeMethod(ref int i)
{
int j=i;//通过
//...
}
public void SomeMethod(out int i)
{
int j=i;//语法错误
}
(3)ref传进去的参数在函数内部可以不被修改,但out必须在离开函数体前进行赋值。ref在参数传递之前必须初始化;而out则在传递前不必初始化,且在 ... 值类型与引用类型之间的转换过程称为装箱与拆箱。
4.ref和out的重载
ref 和 out 关键字在运行时的处理方式不同,但在编译时的处理方式相同。因此,如果一个方法采用 ref 参数,而另一个方法采用 out 参数,则无法重载这两个方法。例如,从编译的角度来看,以下代码中的两个方法是完全相同的,因此将不会编译以下代码:
class CS0663_Example
{
// Compiler error CS0663: "Cannot define overloaded
// methods that differ only on ref and out".
public void SampleMethod(out int i) { }
public void SampleMethod(ref int i) { }
}
但是,如果一个方法采用 ref 或 out 参数,而另一个方法不采用这两类参数,则可以进行重载,如下所示:
class RefOutOverloadExample
{
public void SampleMethod(int i) { }
public void SampleMethod(out int i) { }
}
总结:
1. ref的使用:使用ref进行参数的传递时,该参数在创建时,必须设置其初始值,且ref侧重于修改;
2. out的使用: 采用out参数传递时,该参数在创建时,可以不设置初始值,但是在方法中必须初始化,out侧重于输出;
注释:当希望方法返回多个值时,可以用out,并且一个方法中的参数可以有一个或多个out参数;使用 out 参数,必须将参数作为 out 参数显式传递到方法中,但是out 参数的值不会被传递到 方法中,且属性不是变量,不能作为 out 参数传递。
ref是有进有出,而out是只出不进。