C#知识学习 遗忘的记忆 -第二天

来源:互联网 发布:江湖婚庆 3.0源码 编辑:程序博客网 时间:2024/05/04 19:38

一、函数的学习 

1.1 引用参数和值参数

引用参数 使用ref 标识

static void DoubleNum( int val )

{

val *= 2;

 return val;

}

示例:

int number1 = 2;

Console.Writeline("number1 = {0}", number1);

 DoubleNum(number1);

Console.Writeline("number1 = {0}", number1);

输出

number1 = 2

number1 = 2

 

示例:

int number1 = 2;

Console.Writeline("number1 = {0}", number1);

number1 = DoubleNum(number1);

Console.Writeline("number1 = {0}", number1);

输出

number1 = 2

number1 = 4

 

示例:

int number1 = 2;

Console.Writeline("number1 = {0}", number1);

 DoubleNum(refnumber1);

Console.Writeline("number1 = {0}", number1);

输出

number1 = 2

number1 = 4

 

注意:ref 使用的两个注意点:

1、ref参数必须是”非常量变量“

const int number 1;

ref number1 错误

2、 ref修饰的参数必须初始化

 

1.2  输出参数

out关键字

 

out参数修饰必须是未赋值

ref则必须赋初值

 

 

1.3  参数数组 

参数数组,通过关键字params定义参数数组,paras类型参数主要用于在对数组长度未知(可变)的情况下进行函数声明,调用时可以传入个数不同的实参,具备很好的灵活性。 

首先给出一个具体的例子介绍参数数组(params)的具体用法,首先定义一个带有参数数组的方法:

using System; public class MyClass { public static void UseParams(params int[] list) { for (int i = 0; i < list.Length; i++) { Console.WriteLine(list[i]); } Console.WriteLine(); } public static void UseParams2(params object[] list) { for (int i = 0; i < list.Length; i++) { Console.WriteLine(list[i]); } Console.WriteLine(); } static void Main() { UseParams(1, 2, 3); UseParams2(1, 'a', "test"); // An array of objects can also be passed, as long as // the array type matches the method being called. int[] myarray = new int[3] { 10, 11, 12 }; UseParams(myarray); } } 
程序的输出结果如下: 







test 

10 
11 
12 

下面总结一些使用参数数组的注意事项: 
1. 只能在一维数组上使用params关键字。 
2. 不能重载一个只基于params关键字的方法。params关键字不构成方法的签名的一部分。 
如: 

//编译时错误:重复访问 public static int Min(int [] paramList) ............. public static int Min(params int [] paramList) ............. 
3. 不允许ref或out params数组

//编译时错误 public static int Min(ref params int [] paramList) ............. public static int Min(out params int [] paramList) ............. 
4. params 数组必须是方法的最后一个参数(也就是只能有一个params数组参数)

public static int Min(params int [] paramList,int i) .............
5. 编译器会检查并拒绝任何可能有歧义的重载 
6. 非params方法总是优先于一个params方法。也就是说,如果愿意,仍然可以为普通情况创建一个方法的重载版本, 
如: 

public static int Min(int leftHandSide,int rightHandSide) .............
优于:

public static int Min(params int [] paramList) ............. 
第一个先被调用(声明不带params数组参数的方法,或许是一种有用的优化技术,可以避免编译器创建和填充太多的数组)

class Black { public static int Hole(params object [] paramList) ............. } 
System.Object(object)是所有类的根,使用params object数组来声明一个方法,它能接受任意数量的object参数;换言之,不仅参数的数量是任意的,参数的类型也可以是任意的。所以,此方法称为Black.Hole(黑洞) 
1. 可以不向它传递任何参数 
2. 可以在调用它时,传递null作为参数 
3. 可以向它传递一个实际的数组。也就是说,可以人工创建本由编译器来创建的数组 
4. 可以向它传递不同类型的其他任何参数,这些参数将自动封装到一个object数组

至1.3以上参考

http://blog.csdn.net/pengpegv5yaya/article/details/50771991

1.4  main函数

参考

http://blog.csdn.net/koself/article/details/7920881

http://www.cnblogs.com/android-blogs/p/6428932.html

 

 

 

1.5   函数重载 ,   类的重载和覆盖  :override和overload的区别

 

1.6  委托和事件

委托是一种可以把引用存储为函数的类型。委托的声明非常类似函数,但是不带函数体,且要使用delegate关键字。委托的声明指定一个返回类型和一个参数列表。定义委托以后,就可以声明该委托类型的变量。接着把这个变量初始化与委托有相同返回类型和参数列表的函数引用。之后,就可以使用委托调用这个函数,就像该变量是一个函数一样。有了引用函数的变量后,还可以执行不能用其他方式完成的操作。例如,可以把委托变量作为参数传递给一个函数,这样,该函数就可以使用委托调用它引用的任何函数,而且在运行之前无需知道调用的是哪个函数。

1.7  string  =null 和 string =“”区别,string 和StringBuilder区别

C#中string.Empty、""和null 之间的区别

http://blog.csdn.net/henulwj/article/details/7830615

 

在C#中,在处理字符串拼接的时候,使用StringBuilder的效率会比硬拼接字符串高很多。到底有多高,如下:

 

static void Main( string[] args )
{
 string str1 = string.Empty;
 Stopwatch sw1 = new Stopwatch();
 sw1.Start();
 for ( int i = 0; i < 10000; i++ )
 {
 str1 = str1 + i.ToString();
 }
 sw1.Stop();
 Console.WriteLine( "拼接字符串所耗费时间为:" + sw1.ElapsedMilliseconds + "毫秒" );
 StringBuilder str2 = new StringBuilder( 10000 );
 Stopwatch sw2 = new Stopwatch();
 sw2.Start();
 for ( int i = 0; i < 10000; i++ )
 {
 str2.Append( i.ToString() );
 }
 sw2.Stop();
 Console.WriteLine( "使用StringBuilder所耗费时间为:" + sw2.ElapsedMilliseconds + "毫秒" );
 Console.ReadKey();
}
 

 

上面代码执行的效果如下:

2016518151713376.png (323×59)

string类型的特别之处在于我们可以像使用值类型那样使用string类型,而实际上string是引用类型。既然是引用类型,CLR就会把string类型保存在托管堆上。当我们使用str1 = str1 + i.ToString();进行拼接,由于string类型的恒定性,不会改变str1在内存中的地址,而是在托管堆上创建了另外一个字符串对象。如此,拼接10000次,就创建了10000个string类型对象,效率难免低下。

而StringBuilder会在内存中开辟一块连续的内存,当增加字符串实际上是针对同一块内存的修改,所以效率更高。

当然,到底使用硬拼接字符串,还是使用StringBuilder,不是绝对的,要看情况。当拼接字符串很少的情况下,当然直接硬拼接字符串就行了。

深入string和stringBuilder的区别
String对象是不可改变的。每次使用System.String类中的方法之一或者是进行运算时(如赋值、拼接等),都要在内存中创建一个新的字符串对象,这就需要为该新对象分配内存空间,而StringBuilder则不会。在需要对字符串执行重复修改操作时,与创建新的 String 对象相关的系统开销可能会非常昂贵。如果要修改字符串而不创建新的对象,则可以使用 System.Text.StringBuilder 类。例如,当在一个循环中将许多字符串连接在一起时,使用 StringBuilder 类可以提升性能。

String类型对象的特点:
1.它是引用类型,在堆上分配内存
2.运算时会产生一个新的实例
3.String 对象一旦生成不可改变(Immutable)
4.定义相等运算符(== 和 !=)是为了比较 String 对象的值(而不是引用)

大家都知道字符串对象是”不可变的”,
对字符串进行操作的方法实际上返回的是新的字符串对象。
在前面的示例中,将 s1 和 s2 的内容连接起来以构成一个字符串时,包含 "orange" 和 "red" 的两个字符串均保持不变。+= 运算符会创建一个包含组合内容的新字符串。结果是 s1 现在引用一个完全不同的字符串。只包含"orange" 的字符串仍然存在,但连接 s1 后将不再被引用。
大量的字符串相加的时候就会有很多想s1一样的 不在被引用,从而造成资源的极大浪费.
大家注意这点

 

stringstringValue = this.m_StringValue;

 

internalvolatile stringm_StringValue;

 

 

写到这里,需要有人见看到了 volatile,也许不明白是什么意思,大概的说下.
volatile关键字实现了线程间数据同步,用volatile修饰后的变量不允许有不同于”主”内存区域的变量拷贝。
换句话说,一个变量经volatile修饰后在所有线程中必须是同步的;任何线程中改变了它的值,所有其他线程立即
获取到了相同的值。理所当然的,volatile修饰的变量存取时比一般变量消耗的资源要多一点,因为线程有它自己的
变量拷贝更为高效。

this.NeedsAllocation(stringValue, requiredLength)

只有在需要的时候才去重新分配.
就分配空间和线程的使用上来讲,StringBuilder肯定比String要高,但是前提是使用频率比较高的情况下.

 

http://www.jb51.net/article/84526.htm

 

string 和StringBuilder区别

http://www.cnblogs.com/tandy/p/4840754.html

http://www.cnblogs.com/reggieqiao/p/5902149.html

 

1.8 值引用和变量的引用

.NET中的六个重要概念:栈、堆、值类型、引用类型、装箱和拆箱


内容导读

  • 概述
  • 当你声明一个变量背后发生了什么?
  • 堆和栈
  • 值类型和引用类型
  • 哪些是值类型,哪些是引用类型?
  • 装箱和拆箱
  • 装箱和拆箱的性能问题

 一、概述

  本文会阐述六个重要的概念:堆、栈、值类型、引用类型、装箱和拆箱。本文首先会通过阐述当你定义一个变量之后系统内部发生的改变开始讲解,然后将关注点转移到存储双雄:堆和栈。之后,我们会探讨一下值类型和引用类型,并对有关于这两种类型的重要基础内容做一个讲解。

  本文会通过一个简单的代码来展示在装箱和拆箱过程中所带来的性能上的影响,请各位仔细阅读。

1

 二、当你声明一个变量背后发生了什么?

  当你在一个.NET应用程序中定义一个变量时,在RAM中会为其分配一些内存块。这块内存有三样东西:变量的名称、变量的数据类型以及变量的值。

  上面简单阐述了内存中发生的事情,但是你的变量究竟会被分配到哪种类型的内存取决于数据类型。在.NET中有两种可分配的内存:栈和堆。在接下来的几个部分中,我们会试着详细地来理解这两种类型的存储。

2

 三、存储双雄:堆和栈

  为了理解栈和堆,让我们通过以下的代码来了解背后到底发生了什么。

1
2
3
4
5
6
7
8
9
10
11
publicvoid Method1()
{
    // Line 1
    inti=4;
 
    // Line 2
    inty=2;
 
    //Line 3
    class1 cls1 = newclass1();
}

  代码只有三行,现在我们可以一行一行地来了解到底内部是怎么来执行的。

  • Line 1:当这一行被执行后,编译器会在栈上分配一小块内存。栈会在负责跟踪你的应用程序中是否有运行内存需要
  • Line 2:现在将会执行第二步。正如栈的名字一样,它会将此处的一小块内存分配叠加在刚刚第一步的内存分配的顶部。你可以认为栈就是一个一个叠加起来的房间或盒子。在栈中,数据的分配和解除都会通过LIFO (Last In First Out)即先进后出的逻辑规则进行。换句话说,也就是最先进入栈中的数据项有可能最后才会出栈。
  • Line 3:在第三行中,我们创建了一个对象。当这一行被执行后,.NET会在栈中创建一个指针,而实际的对象将会存储到一个叫做“堆”的内存区域中。“堆”不会监测运行内存,它只是能够被随时访问到的一堆对象而已。不同于栈,堆用于动态内存的分配。
  • 这里需要注意的另一个重要的点是对象的引用指针是分配在栈上的。 例如:声明语句 Class1 cls1; 其实并没有为Class1的实例分配内存,它只是在栈上为变量cls1创建了一个引用指针(并且将其默认职位null)。只有当其遇到new关键字时,它才会在堆上为对象分配内存。
  • 离开这个Method1方法时(the fun):现在执行控制语句开始离开方法体,这时所有在栈上为变量所分配的内存空间都会被清除。换句话说,在上面的示例中所有与int类型相关的变量将会按照“LIFO”后进先出的方式从栈中一个一个地出栈。
  • 需要注意的是:这时它并不会释放堆中的内存块,堆中的内存块将会由垃圾回收器稍候进行清理。

3

  现在我们许多的开发者朋友一定很好奇为什么会有两种不同类型的存储?我们为什么不能将所有的内存块分配只到一种类型的存储上?

  如果你观察足够仔细,基元数据类型并不复杂,他们仅仅保存像 ‘int i = 0’这样的值。对象数据类型就复杂了,他们引用其他对象或其他基元数据类型。换句话说,他们保存其他多个值的引用并且这些值必须一一地存储在内存中。对象类型需要的是动态内存而基元类型需要静态内存。如果需求是动态内存的话,那么它将会在堆上为其分配内存,相反,则会在栈上为其分配。

4

 四、值类型和引用类型

  既然我们已经了解了栈和堆的概念了,是时候了解值类型和引用类型的概念了。值类型将数据和内存都保存在同一位置,而一个引用类型则会有一个指向实际内存区域的指针。

  通过下图,我们可以看到一个名为i的整形数据类型,它的值被赋值到另一个名为j的整形数据类型。他们的值都被存储到了栈上。

  当我们将一个int类型的值赋值到另一个int类型的值时,它实际上是创建了一个完全不同的副本。换句话说,如果你改变了其中某一个的值,另一个不会发生改变。于是,这些种类的数据类型被称为“值类型”。

5

  当我们创建一个对象并且将此对象赋值给另外一个对象时,他们彼此都指向了如下图代码段所示的内存中同一块区域。因此,当我们将obj赋值给obj1时,他们都指向了堆中的同一块区域。换句话说,如果此时我们改变了其中任何一个,另一个都会受到影响,这也说明了他们为何被称为“引用类型”。

 五、哪些是值类型,哪些是引用类型?

  在.NET中,变量是存储到栈还是堆中完全取决于其所属的数据类型。比如:‘String’或‘Object’属于引用类型,而其他.NET基元数据类型则会被分配到栈上。下图则详细地展示了在.NET预置类型中,哪些是值类型,哪些又是引用类型。

6

 六、装箱和拆箱

  现在,你已经有了不少的理论基础了。现在,是时候了解上面的知识在实际编程中的使用了。在应用中最大的一个意义就在于:理解数据从栈移动到堆的过程中所发生的性能消耗问题,反之亦然。

  考虑一下以下的代码片段,当我们将一个值类型转换为引用类型,数据将会从栈移动到堆中。相反,当我们将一个引用类型转换为值类型时,数据也会从堆移动到栈中。

  不管是在从栈移动到堆还是从堆中移动到栈上都会不可避免地对系统性能产生一些影响。

  于是,两个新名词横空出世:当数据从值类型转换为引用类型的过程被称为“装箱”,而从引用类型转换为值类型的过程则被成为“拆箱”。

7

  如果你编译一下上面这段代码并且在ILDASM(一个IL的反编译工具)中对其进行查看,你会发现在IL代码中,装箱和拆箱是什么样子的。下图则展示了示例代码被编译后所产生的IL代码。

8

 七、装箱和拆箱的性能问题

  为了弄明白到底装箱和拆箱会带来怎样的性能影响,我们分别循环运行10000次下图所示的两个函数方法。其中第一个方法中有装箱操作,另一个则没有。我们使用一个Stopwatch对象来监视时间的消耗。

  具有装箱操作的方法花费了3542毫秒来执行完成,而没有装箱操作的方法只花费了2477毫秒,整整相差了1秒多。而且,这个值也会因为循环次数的增加而增加。也就是说,我们要尽量避免装箱和拆箱操作。在一个项目中,如果你需要装箱和装箱,请仔细考虑它是否是绝对必不可少的操作,如果不是,那么尽量不用。

10

  虽然以上代码段没有展示拆箱操作,但其效果同样适用于拆箱。你可以通过写代码来实现拆箱,并且通过Stopwatch来测试其时间消耗。


 转载:http://www.admin10000.com/document/5274.html

 

1.9 == 和equas

 

 

原创粉丝点击