c#入门经典(1)(197)

唉!!,es6正看着呢;去海外组了。。。。要补c#知识了

.NET程序:

使用.NET兼容语言(如c#)编写程序 -> 把代码编译为CIL,存储在程序集中(即VS干的事)

-> 执行时,先使用JIT编译器将代码编译为本机代码 -> 在托管的CLR环境下运行本机代码。

基本类型

变量类型:

字节数增多:sbyte(1) short(2) int(4) long(8)
不同的是:其它类型的unsigned前面加u就好,唯独byte是0到255

转义字符(\”表示”)和字面量(100L 100F)都和c++差不多
多了个新功能

1
2
string strTest = "wangqiu";
string strHEHE = $"{strTest} hehe" + $" {3+5}de";//这样是可以的哟,用法如同js的模板字符

差点忘了说,c#中变量类型其实全是System的类,也就意味着基本变量类型本身就有很多成员函数可以使用

typeof和GetType

c++的是typeid,记得mfc好像有个,忘了。

都返回System.Type类型,一个是类函数一个是语言函数(参数是类名)。

switch语句

switch语句有点不同,多了个goto case <com>:,用来实现在这个case语句中跳转到其它case语句中

强制转换

用法基本同;c#多了层保险,可以用checked((type))出现的崩溃来保证,高字节向低字节转换;也可以默认设置强制转换加check;在书67页。

枚举

用法和c++基本同;多了个指定类型,enum Type : long,这样指定枚举变量的类型。

数组

1
2
3
4
5
6
int[] myArray = {4, 5};//很智能,可以自动创建数组类,这样就能使用很多成员函数了。
int[] myArray = new int[2] {6, 7};//new int[number],不能new int;
//数组是引用类型,开辟的元素也在堆上
int[] myArray = new int[] {4, 3, 2, 1};
var inlist = new List<int>() { 1, 2 };

数组的遍历可以用for也可以用foreach(string f in fN);只是foreach里的f是只读属性,不能写。

  • 多元数组
1
2
3
4
//几元数组就用几个`,`表示
int[,] h = new int[3, 4];
int[,] s = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};//但是这种赋值会改变,声明的几元
//foreach会一行一行遍历全部元素。
  • 锯齿数组
    这个更该是数组中的数组,一个行不一定固定个数
1
2
3
4
5
6
int [][] d = {
new int[] {1},//这行就一个
new int[] {1, 2},//这行俩个
new int[] {1, 3, 4},
new int[] {7, 8}
};

元组

数组是合并相同类型,元祖就是合并不同类型

1
Tuple<int, double> tuple = Tuple.Create<int , double>(3, 3.3);

暂时我认为这个东西没啥用,不如用结构体。看以后吧!

匿名类型

var会自动转换到相应的类型,当然也可以访问相应的成员函数。

nameof

把变量名变成字符形式

as和is

is会做俩次检查,as会做一次检查,as效率会高点
as类似于强制转换,转换失败便返回null
as只能用于引用类型和装箱转换,所以值类型还是得用is。除此外as还有隐式转换(见类的隐式显示)

1
2
3
4
MyDrive bs = new MyDrive();
Program pro = bs as Program;
if (pro) {}//这种写法大误,被c++惯坏了。
if (pro != null)//才行。如果要上面写法成功的话就必须加个隐式转换的bool

函数

参数调用顺序是从左向右

函数定义

类中函数默认是private类型的,如果访问要public

1
2
3
4
5
6
7
8
9
10
//数组参数
void Show(int[] intA) {}
//引用参数
void Show(ref int val) {}
Show(ref intA);//它调用也比较特殊,还有就是intA必须要被赋过值
//输出参数
void Show(out int valA) {}
Show(out intA)//感觉它比引用都高级

函数可以重载,但是outref不能相同

lambda

1
2
3
4
static public int GetI() => 1 + 3;
Func<string, string , int> hh = (/*string */x, /*string */y) => {
return 1;
};

函数参数

  1. 支持默认参数,同时不会在意默认参数位置(即不像c++那样默认参数必须放后面)
  2. 支持无序传入,SName(kk:1, hh:2)这种形式传入是支持的,但是如果这样写就必须把所有参数写完。

c#的委托和事件

1
2
3
4
5
6
7
8
9
10
11
double Run(int p1, int p2) {}
delegate double Process(int p1, int p2);
Process tp1 = Run;
Process tp2 = (int p1, int p2) => {return p1 + p2;};
Process tp3 = new Process(Run);//标准建议这种写法,说是能明白干了什么
delegate int NumberChanger(int n);
NumberChanger nc1 = new NumberChanger(AddNum);//这样才对嘛,反正引用类型都是在堆里
NumberChanger nc2 = new NumberChanger(MultNum);//
NumberChanger[] has = new NumberChanger[3];//代理数组

delegate 至少0个参数,至多32个参数,可以无返回值,也可以指定返回值类型
泛型Action至少0个参数,至多16个参数,无返回值
泛型Func至少0个参数,至多16个参数,根据返回值泛型返回。必须有返回值,不可void(它最后一个参数才表示返回)
泛型Predicate有且只有一个参数,返回值固定为bool

后面的泛型较delegate的好处是,不用先声明再使用;可以直接使用。
它们都可以当参数传值。

1
2
3
4
5
6
7
8
public static void Book() { Console.WriteLine("我是提供书籍的");}
public static void Book(string BookName) {}
Action BookAction = new Action(Book);
Action BookAction1 = new Action<string>(Book);
BookAction();
public static int Test<T1, T2>(Func<T1, T2, int> func, T1 a, T2 b) { return func(a, b); }
Console.WriteLine(Test<int,int>(Fun,100,200));

##关于委托我想放个链接:http://www.cnblogs.com/r01cn/archive/2012/11/30/2795977.html
里面有多播/过滤/GetInvocationList(多播只能保证执行分别,而返回值是最后一个,而出现的)##

匿名

除了lamda外,还可以这样用匿名

1
2
3
calc.CalculationPerformedEvent += delegate(object sender, CalculationEventArgs e) {
Console.WriteLine("Anonymous Calc: {0} x {1} = {2}", e.X, e.Y, e.Result);
};

事件

事件的实现严格需要代理的配合;关键字event

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class Mom //事件发布器
{
public event Action<int> Eat;//订阅函数模板,这个需要代理函数的支持。所以也可以用delegate等来代替
public void Cook()//发布函数
{ Eat(1); }
}
public class Dad//事件订阅器
{
public void DEat(int a)//订阅函数
{ Console.WriteLine("dad eat"); }
}
public class Child//事件订阅器
{
public void CEat(int a)//订阅函数
{ Console.WriteLine("child eat"); }
}
class Program
{
static void Main(string[] args)
{
Mom mom = new Mom();
Dad dad = new Dad();
Child ch = new Child();
mom.Eat += dad.DEat;//开始订阅
mom.Eat += ch.CEat;
mom.Cook();//开始发布,这样就会执行dad的DEat和ch的CEat函数。
Console.ReadKey();
}
}
//然而当传入信息更复杂时,可以如下。事件进阶版
public class Food : EventArgs
{
private string message;
public Food()
{ message = "wq"; }
public Food(string ss)
{ message = ss; }
}
public class Mom
{
public event EventHandler<Food> Eat;
public void Cook()
{ Eat(this, new Food("hi")); }
}
public class Dad
{
public void DEat(object obj, Food fo)
{ Console.WriteLine("dad eat"); }
}
public class Child
{
public void CEat(object obj, Food fo)
{ Console.WriteLine("child eat"); }
}
class Program
{
static void Main(string[] args)
{
Mom mom = new Mom();
Dad dad = new Dad();
Child ch = new Child();
mom.Eat += dad.DEat;
mom.Eat += ch.CEat;
mom.Cook();
Console.ReadKey();
}
}
Action和Func

Action本身就是代理只不过是返回void的代理;Action 返回void,参数为俩int的代理。

Func是有返回没参数的委托

其它

格式化输出

  1. 带运算;
    Console.WriteLine($"{SName(1, 2)}");会运算了SName函数再输出;注意的是它和参数传入形式不能共存。

  2. 参数传入;
    Console.WriteLine("{0}a{1}", 10.02, 121);这里0表示后面第一个参数,随着参数增加,可以依次递增。
    还有,它会完整输出参数的形式。不用担心是string、int、float,它全按参数形式输出。

  3. 格式符;
    只记录几个简单的:
    D 十进制、N数字格式、X十六进制、P百分数格式;
    Console.WriteLine(String.Format("{0:N1}", 10))这里{0:N1}表示第一个参数,按数字格式输出,小数点后留一位。
    格式符后面的数字可增加,表示小数点后面的位数。但是格式符前面不能加数字,实在加要用占位符。

  4. 占位符

    1
    2
    3
    4
    string.Format("{0:0000.00}", 12394.039) 结果为:12394.04
    string.Format("{0:0000.00}", 194.039) 结果为:0194.04
    string.Format("{0:###.##}", 12394.039) 结果为:12394.04
    string.Format("{0:####.#}", 194.039) 结果为:194

零占位符:
如果格式化的值在格式字符串中出现“0”的位置有一个数字,则此数字被复制到结果字符串中。小数点前最左边的“0”的位置和小数点后最右边的“0”的位置确定总在结果字符串中出现的数字范围。
“00”说明符使得值被舍入到小数点前最近的数字,其中零位总被舍去。
数字占位符:
如果格式化的值在格式字符串中出现“#”的位置有一个数字,则此数字被复制到结果字符串中。否则,结果字符串中的此位置不存储任何值。
请注意,如果“0”不是有效数字,此说明符永不显示“0”字符,即使“0”是字符串中唯一的数字。如果“0”是所显示的数字中的有效数字,则显示“0”字符。
“##”格式字符串使得值被舍入到小数点前最近的数字,其中零总被舍去。

但是占位符又不简单的是这些:("{0:123##2.00}", 11)输出:123112.00 ("{0:123##20.00}", 11)输出:123121.00
看来实际用还得再测试下。

调试

调试信息的输出,这个调试信息是输出一行,比c++方便多了。
Debug.WriteLine()只能在debug模式下
Trace.WriteLine()任何模式下
Debug.Assert()
Trace.Assert()

也支持try catch finally finally必执行。

XML文档

这个我觉得很有必要。c++一般都是doxygen语法来,c#却这么高级直接官方支持(官方的snippets简直丧心病狂,太好用了)。
另外官方建议用Sandcastle来生成相关阅读文档。

值列举常用的,详细:http://www.cnblogs.com/mq0036/p/3544264.html
summary:永远描述类型
remarks:添加有关类型的信息,补充summary
param:参数描述
returns:返回值描述
value: 属性说明(官方示例是set、get的那些属性)
example:示例代码部分
c:文本标记为代码
code:多行指示为代码
exception:异常说明
see:seealso:用<see cref="member"/>

预编译

1
2
3
4
5
6
7
8
9
#define #undef #if #elif #else #endif //和c++相同
#if DEBUG
#warning "Don't"//c#的警告和错误的预编译和c++不同
#error "Wrong"
#endif
#region #endregion//折叠用
#pragma//此指令和c++使用完全不同

this和base

类的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
internal class My{};//此关键字修饰的类,只运行同一个程序集访问(即同一exe或dll)
public class My{};//运行其它程序集访问
public abstract class M{};//也可以加private等,此修饰符是抽象类,只能继承,不能实例化
public sealed class M{};//此修饰符不允许继承
//继承的时候要注意:编译器不允许派生类的可访性高于基类,即内部类继承公共类可以,公共类继承内部不行。
//以下不行:
internal class My{}
public class MD : My{}//不行
//继承时不能有多个基类,c#只运行一个基类;但是可以加接口的
class My : MyBass, IMyInterface, IMy{} //虽然只能一个基类,但是多个接口是可以的。注意接口必须在基类后面

属性

大量例子对属性的使用都是这样的,感觉这个属性有点多余。
属性支持访问控制:getset支持使用:public、private、protected来控制访问。
属性支持重写:abstractvirtualoverride这些可以联合使用

1
2
3
4
5
6
7
8
9
protected int _age;
public virtual int Age {
get {
return _age;
}
set {
_age = 1;
}
}

自动属性public double Height { get; set; }

构造函数和析构

基本和c++相同,允许重载、访问控制。
不同:

  • 执行顺序(MyDericed继承基类);MyDericed myO = new MyDericed();1、执行System.Object.Object()构造函数;2、执行MyBaseClass.MyBaseClass构造函数;3、执行MyDericed构造函数
  • 委托不同;
    1
    2
    3
    4
    5
    public class MyD: MyBass
    {
    public MyD() : this(5, 6){}//委托给自身的另一个构造函数
    public MyD(int i, int j) : base(i) {}//调用基类构造函数
    }

对象初始化器

HeClass hh = new HeClass(2) { _me = 3 };
HeClass有个成员_me;
这个功能简直装逼用,没啥实用。效率和调用构造函数一样;
而且成员还必须是public,不然没法访问。。。。

##然后就是静态构造函数##
编写静态构造函数的一个原因是,类有一些静态字段或属性,需要在第一次使用类之前,从外部源中初始化这些静态字段和属性
由于并不确定它执行的时间,所以注意下此函数中对静态变量的赋值。

静态类

给类加个static只是告诉编译器,这个类的所有成员必须是static类型的,不然就报错

类和结构体区别

c#中类和结构体有个大大的区别, 类是引用类型,结构体是值类型。
也就是说:
直接的=操作对类来说是引用赋值(是浅拷贝过程)
直接的=操作对结构体来说是复制信息(是深拷贝)

1
MyStru st = new MyStru{ d=1, a=2 };//值类型还是在栈空间

嵌套类

被嵌套在里面的类可以访问外部类的private成员,这和c++不同

对于嵌套类则不能把类型定义为protectcd、private和protected internal,
因为这些修饰符对于包含在名称空间中的类型没有意义。因此这些修饰符只能应用于成员。
但是,如果是嵌套类的话,这些修饰符就有了些意义。

其它定义字段

1
2
public readonly int MyInt = 3;//表示这个字段只能在执行构造函数的过程中赋值,或由初始化赋值语句赋值。
public static int MyInt;//书上建议用const代替static,这样既避免出错也可以达到相同效果

c#的extern导入的是外部程序集,经常和DllImport联合使用。

继承使用

c#的virtual、override、更是不同

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MyClass {
public virtual void ShowD() {
Console.WriteLine("My");
}
}
class YouClass : MyClass {
public override void ShowD() {
Console.WriteLine("You");
}
}
class HeClass : YouClass {
public override void ShowD() {
Console.WriteLine("He");
}
}
//可以看出,如果想要重写,必须用virtual、override的配合
//如果是 override sealed void... 配合的话,重写就此结束;派生类不能再重写。

partial部分关键字的使用

partial关键字允许把类、结构或接口放在多个文件中,编译再变成一个类。
包括继承对象也会整合在一起(但是访问权限关键字不能整合)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
//Big1.cs
partial class TheBig : IMyInter1
{
...
}
//Big2.cs
partial class TheBig : IMyInter2
{
...
}
//编译后就是
public class TheBig :IMyInter1, IMyInter2
{}
//上面介绍了部分类,下面介绍部分方法
public partial class MyClass
{
partial void MyMethod();
}
public partial class MyClass
{
partial void MyMethod()
{
//...
}
}
//部分方法的出现是为了解决要使用其它部分类的方法时,避免访问不到。
//但是它有以下毛病:
//0、只能存在于partial的类中
//1、总是私有类型,且不能有返回值
//2、使用的任何参数不能是out参数。但ref参数是可以的
//3、不能使用virtual、abstract、overrride、new、sealed、extern这些修饰符

浅拷贝和深拷贝

浅拷贝由默认隐式转换和MemberwiseClone来完成不用说。
来看深拷贝代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
class HeClass : ICloneable
{
HeClass(int a)
{ _me = a; }
int _me;
public object Clone()
{
HeClass c = new HeClass(_me);
return c;
}
}
//发现必须继承ICloneable,并重写其函数
//还要注意new的构造函数传值问题。

重载符函数

1
2
3
4
5
6
7
8
9
10
11
12
class YouClass : MyClass {
public static YouClass operator +(YouClass a) {
YouClass yy = new YouClass();
return yy;
}
}
ClassA a;
classB b;
ClassC c;
c = a + b;
像这样的参数就必须是(ClassA, classB),更重要的和顺序有关。

如上操作符函数有这些要求:
必须是public
必须是static
返回值必须是原型

一元运算符:+、-、!、~、++、–、true、false
二元:+、-、*、/、%、&、|、^、<<、>>
比较:==、!=、<、>、<=、>=

隐式转换和显式转换

对于引用类型来说,如下代码有误:

1
2
3
4
5
6
7
8
9
10
11
12
13
MyDrive bs = new MyDrive();
Program pro = (Program)bs;//1
Program pro = bs;//2
//如要成功,就需要支持隐式转换(2)和显式转换(1)
public static implicit operator Program(MyDrive s)
{ return null; }//隐式
public static explicit operator Program(MyDrive s)
{ return null; }//显式
向MyDrive类添加如上函数便可,
但是注意;相同函数隐式和显式只能存在一个,不能共存。

继承

函数隐藏

c#会像c++那样出现函数隐藏,但是它面对函数隐藏的时候会报警告的,如果要消除警告:

1
2
3
4
class MyDriver:
{
public new int ShowD(){}//加个new,这样做并不会改变执行时的代码调用;只是去掉编译的警告而已。
}

Object类

所有类都派生于System.Object,即使定义时没指定基类也会默认继承。

abstract和sealed

  • abstract对类;修饰并不能阻止其函数的定义,也无法使函数默认变成可重写类型(函数依旧需要自己声明重写类型);
    它对类来说就只能是个说明是接口类的作用,实际作用并没有。
  • abstract对函数;此函数为虚函数,默认重写类型
  • sealed对类;这样表示此类不能被继承
  • sealed对函数;只能对可重写类型的函数才能使用(virtual abstract),表示此函数子类不能重写,常和override联合使用

拆箱和装箱

首先这俩个操作是隐式的
所有值类型均隐式派生自System.ValueType:
结构体、数值、整形、字符(char)、浮点、bool、枚举、派生于System.Nullable
int i = new int()等价于int i = 0

所有引用类型均隐式派生自System.object(包括它本身):
数组:(派生于System.Array)数组的元素,不管是引用类型还是值类型,都存储在托管堆上;
类:class(派生于System.Object);
接口:interface(接口不是一个“东西”,所以不存在派生于何处的问题。);
委托:delegate(派生于System.Delegate);
object:(System.Object的别名);
字符串:string(System.String的别名)。

今天测试了下,发现string却可以像值类型那样运作,搜索了下:
string最为显著的一个特点就是它具有恒定不变性:我们一旦创建了一个string,在managed heap 上为他分配了一块连续的内存空间,我们将不能以任何方式对这个string进行修改使之变长、变短、改变格式。
所有对这个string进行各项操作(比如调用ToUpper获得大写格式的string)而返回的string,实际上另一个重新创建的string,其本身并不会产生任何变化。
每次改变值时都会建立一个新的string对象,变量会指向这个新的对象,而原来的还是指向原来的对象,所以不会改变。这也是string效率低下的原因。

1
2
3
4
5
6
7
8
object obj = 1;//装箱
int value = (int)obj;//拆箱
//如上,c#中对值类型是放入栈中,对引用类型是放入堆中
//上面1是值,obj是引用。所以存在过程的转换
//这多余的转换会导致运行效率低下,所以请尽量避免。
//比如使用List<obj>来代替ArrayList;避免拆箱和装箱
// //