zoukankan      html  css  js  c++  java
  • C#入门(1)

    C#入门(1)

    基本概念

    三个基本概念:
    CLR: Common Language Runtime, 就像是一个虚拟机和运行时环境,负责定位管理.Net的对象,还包括低层次的内存管理,线程分配,应用管理和安全检查等等.
    CTS: Common Type System, 管理整个系统运行时的所有对象类型和类型间的转换和构建.
    CLS: Common Language Specification, 作为.Net平台下所有的语言应共同遵守的规则,

    .Net的架构: 外层的Base Class Library提供了所有语言的一些公有库和接口, 而每个语言的CLR和CTS,CLS一层包裹着一层. 这三者是语言特定的.

    各种compiler将不同语言转换为IL(Intermediate Language)和MetaData, 通常是exe或者dll这样的程序集, 而在.Net FrameWork下才能进一步执行.所以同其他语言不同的是, 这里应该是.Net FrameWork提供了IL的运行库, 因此所有这些运行库和namespace都可以使用.给出了以System开头的一堆运行库.还有以MicroSoft开头的命名空间.前者是平台无关的,后者是调用Windows系统级别的.

    多系统移植和跨平台:
    对于C#的跨平台主要仍是实现在该平台上高效的虚拟机, 现在在其他平台上多是Mono, DotGNU两个实现,两者实现了在其他平台上的C#的运行时环境等多项内容.

    对于多个依赖项和多个cs文件共同编译的文件, 有类似Makefile的C# response文件, 后缀名是.rsp 在编译时应使用@前缀:

    csc @Test.rsp
    

    具体的编译可以参考和csc.exe同目录下的default.rsp文件. 已经添加了多数的运行库, 当然如果需要忽略某些库, 可以使用/noconfig.

    程序入口点的Main函数同C++一样可以定义成,void或者是int的,同样可以定义接受参数是string[] args, 不像C++, 还需要定义参数个数和参数数组指针. 在Windows系统中,一个应用程序的返回值存在一个系统环境变量%ERRORLEVEL%中, 如果需要捕捉该值则需要使用static System.Diagnostics.Process.ExitCode这一属性.

    C#的命令行参数都是以/开头的,后面跟缩写.获得命令行参数可以用以下方法:Environment.GetCommandLineArgs();其中的第一个参数就是该程序名本身.使用了该种方法就不必在Main方法的参数中加string[]了.

    基本API

    有关系统的基本变量都在System.Environment这个类中, using System就包含了.
    而Console则包含了关于控制台的基本信息,甚至包括改变颜色等等.在WriteLine({x})中,大括号包裹的数字是占位符,当参数少于占位符时报错,多于占位符时会被忽略.但是在打印的时候是按照里面的数字的顺序打印的.

    格式化输出数字:
    为了方便输出格式化的数字字符串,有多种后缀.

    后缀 含义
    C or c 货币型 接受一切数值类型,精度为后面紧跟的整型数字,输出自动加上货币符号,有关货币符号的改变可以参见CultureInfo.在外部改,占位符中仅仅包含{x:cy},没有发现其余的可接受参数
    D or d 十进制数 仅接受整型,返回结果是整型数字,负号可选,精度为最小位数. 如1234("D") -> 1234, -1234("D6") -> -001234
    E or e 指数型 接受一切数值类型,返回结果为指数型计数,精度说明符为小数位数,而默认精度说明符为6
    F or f 定点型 接受一切数值,返回结果是整数和小数,负号可选,精度说明符为小数位数,精度大于当前的小数位数直接补零
    G or g 常规型 接受一切类型,返回结果是最紧凑的定点表示法或者是科学技术法,精度说明符为有效位数,具体取决于给定数,多余0会去除
    N or n 数字 接受一切类型,返回结果是整数和小数,组分隔符和小数分隔符,负号可选.精度说明符是小数位数,默认是小数点后两位,且四舍五入
    P or p 百分比 接受一切数值类型,返回乘以100并显示百分比符号的数字,精度说明符表示小数位数
    R or r 往返过程 仅接受Single,Double,BigInteger,精度运算符会被忽略,基本上就是返回相同的数字.
    X or x 十六进制 仅接受整型,结果为十六进制字符串,精度说明符是结果字符串中的位数.

    这些格式化输出在toString()和string的静态方法Format中都可以应用.
    所有这些数值类型都是从栈中分配空间,而不是类似new对象是从堆中分配空间.这和C++一样.
    本质上int是System.Int32的缩写.

    System.DateTime和System.TimeSpan是有关C#时间运算的两个类,前者主要是关注年月日,而后者主要关注时分秒.

    BigInteger定义在System.Numerrics中. 同时还有Complex.
    需要注意的是BigInteger是immutable的,即一旦赋值,就不可再变.但是由于多态的存在,系统默认的运算符和函数实现其实是新建了一个BigInteger对象,经过计算后再回传过来,所以对于BigInteger类的数学运算加上循环的,运算部分使用可变参数然后在结果上对BigInteger操作可以提高效率.

    在字符串前面加@就使其成为了原生字节字符串.同样地,string也是immutable的,有一定的效率风险.
    所以在运用中可以使用System.Text来代替string,里面有StringBuilder,可以在内部实现用char代替string,最后才转换为string.

    在C#中,可以所有的内置类型都是强类型的,虽然可以使用var来用初始化的方式自动推断出数据类型,但是仍然不是动态变化的,真的动态变化的数据类型需要声明为surprise-dynamic.可以中间换数据类型的.使用var声明变量最有用的地方在LINQ,在LIINQ返回的对象中不是简单的数据类型的组合,而是特定的LINQ对象,此时很难确切定义某些数据结构,因此使用var会减少麻烦.

    foreach仅仅是正向遍历集合中的所有值.
    if/else仅仅接受boolean值而不接受转换值,如-1或者0.逻辑操作符都是短路的.如果需要全部检查请使用&和|
    同样地,正确地使用break;防止贯穿.case中的判断项可以是数字和string,或者是枚举,其实也是数字?

    函数的传递参数:
    函数的传入值可以是按值传递也可以是按引用传递:并且有修饰类型.具体如下:

    修饰字段 含义
    (None) 按值传递
    out 按引用传递,同时必须由被调用的函数,即这个函数本身内部赋值,如果赋值失败,则会报编译错误,单个使用out修饰参数没有很大的意义,因为返回的时候自动加上了该修饰符,但是当可以返回多个返回值的时候比较方便,不需要给出特殊的返回参数,仅仅给出void就可以.记住这里在传递函数参数时也必须加上out
    ref 按引用传递,内部也可以再次赋值,而赋值失败也不会报错
    params 可变参数标记,一个函数只能有一个,并且都是在最后出现的.base class中的大部分都用了

    特殊地:有关out和ref的一点不同,out修饰的参数不必在传入前初始化,而ref应该这样做,前者在函数内必须初始化,而后者不必要,会有可能造成函数结束依然没有初始化而出错.

    params必须在参数列表的最后,一般地应该定义为某个内置类型的数组,这样就既可以接受一个数组作为参数,或者是一列由逗号分割开的该类型的数做参数.

    默认参数: 默认参数和python设计的一样,参数在等号后面直接加默认值,但是需要注意的是该值必须是在编译时有效的,而不是运行时有效的,如DataTime.Now,就会报错.

    Named Parameters: 用特定的名字参数对调用函数,如foo(A:"Hello"),该中方法仅仅由参数名得到参数,因此不需要遵守参数的顺序,所以在有默认参数的情况下可以简单调用后面的参数,即仅给后面的值赋值.

    数组:C#支持大括号初始化的方法.new甚至也是可选的.但是有new的时候如果数组的长度和{}内的初值个数不符合,那么还是会报错.同时也可以使用var.

    多维数组:
    C#的多维数组有两种方式,一种是真正的多维数组int[,],即在初始化的时候确定了各维的长度,然后就是固定的大小,而另一种被成为交错数组int[][],是其他语言中实现多维数组的方式,即是一维数组的多次嵌套,如二维数组就是一维数组中每一个元素都是一维数组,所以每个数组的元素都不定长.

    枚举类型:
    首先,枚举类型应该定义在类中或者是类之外,不能是在方法内定义,定义了就非法,所以枚举的本质还是在外部做一个能够引用值的池.
    可以使用enum A : TypeName, 但是仅限于byte, int, short, long四种.enum中的值不能再被赋值了.
    通过GetType()可以得到一个枚举的类型,从而得到整个枚举的信息.

    Struct就是一个轻量版的class,所有的规定基本和class一样.System.ValueType就是保证这些类型的实例从stack分配内存而不是从垃圾回收堆开始.
    所以类和结构很重要的区别就是类是从垃圾回收堆上分配的内存,而结构体是从栈上分配的内存,并且结构体都是值类型,而类都是引用类型.
    当一个结构体包含一个类类型作为引用时,并没有改变类类型的引用类型的特点,所以对该结构进行copy两个结构体将包含同一个类类型的实例,是浅引用,仅仅是指针.如果是要全部复制,或是深copy则需要实现ICloneable接口.

    如果对于一个函数,仅仅传值,那么能改变object的状态,但是不能改变object本身;
    但是对于传递引用,不仅可以改变对象的状态,还可以改变指向的对象本身.

    值类型和类类型的不同,详细见下表:

    问题 值类型 类类型
    对象是在哪里分配的 栈上 堆上
    变量的表示方法 就是本地的局部变量 指向内存中的实例所开辟的空间
    基类或者属性 隐式继承自System.ValueType 可以继承自其他基本任意的类型
    是否能被继承 值类型永远不能被继承 可以
    默认传递类型 只能传值 传值时传值,传引用的时候传递对象的引用
    是否重写了垃圾回收的函数System.Object.Finalize() 不必,因为永远不会由堆来分配内存,因此不需要实现 需要,间接实现了该函数
    能否定义构造函数 可以,但是默认的构造函数依然会被保留 当然
    该类的变量何时会失效 当离开定义域 当被垃圾回收时

    Nullable Type:
    在原来数据类型的集合中加上一个null,在和数据库交互的时候很有用.该类型仅对值类型有效,对于类类型无效.用法就是在数据类型后面加上一个?,如:

    int? nullableInt = 10;
    Nullable<int> nullable = 10;
    

    两者的含义相同.

    ??代表如果一个nullable对象的值是null就使用??后面的值.可以简化一个if判断是不是null.

    int? data = data.GetFromDataBase() ?? 100;
    

    如果没有??就是:

    int? data = data.GetFromDataBase();
    if (!data.HasValue){
    	data = 100;
    }
    

    C# oop

    C#的this参数跟C++的默认参数很像,先定义一个完整的构造函数,然后其他的更改其中的某些参数以成为不完整的构造参数.

    public Motorcycle(string name)
    : this(0, name) {}
    
    // This is the 'master' constructor that does all the real work.
    public Motorcycle(int intensity, string name)
    {
    	if (intensity > 10)
    	{
    		intensity = 10;
    	}
    	driverIntensity = intensity;
    	driverName = name;
    }
    

    并且多个构造函数时,很奇怪,都是先传给主构造函数,运行了部分后再转给符合参数的构造函数.

    在4.0以上可以使用默认参数代替多个构造函数的构造函数chaining.

    OOP的三大特征:封装,继承,多态.
    和C++不一样,所谓的抽象函数就是继承之间的接口的抽象函数,也类似钩子,仅在基类定义的时候使用,但是就是需要子类去覆盖取重写的,实现子类自己的功能.但是虚函数是在基类中有default的实现,但是可以被子类重写的.一个是必须重写,一个是可以重写.

    可见性:
    成员在域中的可见性,详见下表:

    关键词 适用对象 解释
    public 类型和类型成员 对外一切可见,包括对象中以及继承的类等等,对外部程序集都可见,在编写库函数的时候很有用
    private 类型成员或者是嵌套类 仅能在类内部或者是结构内部可见
    protected 类型成员或者是嵌套类 可以在内部及子类中可见,但是不能由外部的点运算符取到
    internal 类型或者类型成员 仅在当前的程序集内可见,比Java的包高一级的可见性,基本上是在该库中可见的意思
    protected internal 类型成员或者嵌套类 该程序集和定义类和继承该类的子类可见

    默认的可见性:类型成员是private的,而类都是internal的.最外层的类类型不能被定义为private的.

    C#和OC一样,都可以通过getter/setter或者属性的方式访问私有成员变量.
    在成员变量上用Ctrl+r,Ctrl+e得到包装好的属性.属性中有自动生成的get,set方法,可以自己修改.所以如果想得到一个只读的属性,就把属性中的set函数省去;自然地,如果要求得到一个只写的属性值,则省去get函数.所以这里的属性和oc中的一样,本身并不代表一个真实的成员变量而是对应了成员变量的get/set方法而已.在只读的内部,仍有用原始的成员变量赋值的能力.

    静态变量直接构建出来就是静态属性.

    若想自动生成不改变的get/set方法,可以用自动属性,敲prop,tab两次就可以了.特别地,自动属性必须是可读可写的,忽略其中一个都是错误.自动属性已经给其中的参数赋了初值,一般的数值型是0,对象型是null.自动属性中不能有任何其他的检查值等等的操作,只有简单的get/set.

    对象的大括号赋值:隐式调用构造函数,C++11才有的特性,给个例子:

    Point a = new Point(10,20);	//normal init
    Point b = new Point{x=10, y=20};   //invoke the default constructor
    <-->
    Point b = new Point(){x=10, y=20}
    
    Point c = new Point(10,16){x=10, y=20}    //invoke the custom constructor
    

    而且这样的方式在组合时也可以使用,嵌套的大括号赋初值.

    const和其他语言一样,一旦赋值不可更改,并且默认是static的.readonly修饰词和const类似但是不一样的是,readonly稍弱一些,可以在声明时不赋值,但是在构造函数中赋值,但是在任何其他地方都不能再修改其值.另外和const不同,readonly并不是static的,如果需要将其变为类等级的必须显式声明为static.

    partial class:由于C#不要求文件名和类名相同,因此可以将一个类分散在多个文件中,只要前面加上partial两部分保持类名不变.在编译的时候还是一样的.感觉真是一般不需要用啊.(貌似只有设计和逻辑分开的时候采用.)

    继承:
    首先C#也只支持单继承,没有多继承,只能实现多个接口;其次.使用sealed来标识一个类不能再被继承,和java中的final一样.所有的structure都是sealed,不能被继承.

    有关继承的构造函数问题:首先,构造函数是不会继承的,public和protected标记的函数和属性,变量等等会被继承,而在定义子类的构造函数时,如果是显式低将父类的属性或者函数直接赋值可以实现,但是这样实现,仍然是先调父类的构造函数,然后后调子类的构造函数,以这种方式写会增加多次的属性赋值和额外的函数调用.所以一般采用直接将参数回传给父类的构造函数的方法:

    public SalesPerson(string fullName, int age, int empID,float currPay, string ssn, int numbOfSales): base(fullName, age, empID, currPay, ssn)
    {
    	// This belongs with us!
    	SalesNumber = numberOfSales;
    }
    

    即至少有一个子类的构造函数要显式地指向调用父类的构造函数,并传递参数.内部类是理想的处理方式,即只要前面声明的类型对,后面的构造函数一层一层拨开就可以了.

    如果可以修改的父类函数,就给出一个virtual标识,只要在子类中使用override表明就可以修改该函数的实现.也可以在这个函数里调父类的函数,用base.函数名就可以了.override还可以和sealed一起使用,即该子类中的函数虽然override了父类的函数,但是却不能够再被该子类的子类override.

    和其他语言一样,如果定义了基类而只打算让其成为其他类的父类,作为公共基类而不让它本身实例化,就用abstract修饰该类.但是值得注意的是abstract修饰类并不会让类内部的所有属性和函数变为abstract的,因此如果需要子类必须实现某些方法,还必须显式地将函数声明为abstract的.
    而子类不实现父类的abstract函数,那么子类也必须声明为abstract.

    Member Shadowing:当子类和父类的属性或者是方法名重复了,这时有两种解决方法,将父类声明为virtual,子类override,但是有时候第三方库,没有源码,所以也可以在子类的该方法前加上new,来表示和父类的方法或属性的不同.额外的一种方式是做强制的继承类型转换.

    继承类型转换:隐式低子类朝父类转换永远是安全的.父类显式地朝下转型也不一定正确.

    as和is: as可以在强制转型时使用,如果不是或者不能转就会返回null.as其实保证了是可以向下转的类型,而is则是判断是否就是要转的特定类型.

    System.Object: override了Equal()就必须复写GetHashCode(),默认的Equal()实现是看内存中的位置是否相同.

  • 相关阅读:
    对double数据类型的数据保留两位小数,并且进行四舍五入
    div位置设置
    每天一算法 -- (排序算法总结)
    SQL行转列
    设计模式的六大原则
    每天一算法 -- (插入排序)
    每天一算法 -- (选择排序)
    通用扩展函数--类型转换
    wcf和webservice
    Cookie的介绍及使用
  • 原文地址:https://www.cnblogs.com/putuotingchan/p/8628709.html
Copyright © 2011-2022 走看看