这里是对刘铁锰老师讲的C#入门视频的一些笔记。
在学习过程中还是有点偷懒,有些例子没有写(比如涉及到数据库),还有必要多次观看视频,对其中理解不透彻的概念和用法再次学习。此外,虽然自己记了笔记,但是发现了另一位更详细,更有价值的笔记:https://www.yuque.com/yuejiangliu/dotnet/timothy-csharp-001 ,希望藉此使自己精进。
下面是自己的笔记:
类在名称空间中是水蓝色的,calss后接自己创建的类,C#是完全面向对象的,程序本身也是一个类。Main方法也包含在类中。
名称空间(namespace)可以通过using引用。应用类时要先using其名称空间。不引用名称空间也可以通过名称空间调用(.)其中的类。
类是对现实事物的抽象,事物包括物质和运动(实体和逻辑)。
对象也叫实例,是类经过实例化后得到的内存中的实体。
类对应的概念,实体对应事物
使用new操作符创建类的实例,new后边要加括号,叫做构造器,因为要按一定参数初始化。(new 类)就可以创建实例,引用变量用来引用实例。
类三大成员:属性,方法,事件
构成C#语言基本元素:关键字,操作符,标识符,标点符号,文本,注释和空白
对类命名时,一定要是个名词或者名词复数;
对类成员命名时,属性要是个名词或复数名词,方法要是个动词或动词短语。变量名用驼峰法,方法名,类名,名字空间用大驼峰法(Pascal)。
长整形(long)在赋值时后边加一个L或l;
double赋值加一个D,float后加F;
char赋值时,字面值只能有一个字符且用''包起来
string赋值时用“”包起来
var自动获得数据变量;
如果想从类外访问方法,要对方法使用public声明
程序 = 数据 + 算法
批量注释: Ctrl+K,Ctrl+C
取消注释: Ctrl+K,Ctrl+U
做递归的时候,假设参数为x,如果从1数到x势必要多占据一个i变量,而且可能因为i变量初始值问题,导致无法解决。而如果从x数到1,节省内存空间,有递归的思想。
汉诺塔,输出移动次数。思想就是递归,把多个元素视为一个元素,没想清楚的地方是如何输出动作已经在哪输出动作。
C#是强数据类型,不同类型的变量不能直接赋值;
js是弱数据类,可以直接赋值,会自动转换。
C#面对非法赋值时,直接报错且中断程序,弱数据类型可以赋值且会继续编译。
强数据类型可以保证数据的正确性。
C#中的var和js中的var完全不一样!!!!
dynamic是C#的弱数据类型
方法调用放在栈里, 堆存储对象
栈比较小,几M;堆比较大,几G。
栈溢出,内存泄漏。
C#有垃圾收集器,不需要手动释放内存,但是会栈溢出。
c#有指针,但是不推荐使用,需要声明unsafe;
C#语言的类型系统:
五大数据类型:类,结构体,枚举,接口,委托;
常用数据类型设计为关键字
变量,对象,内存,
变量:静态变量。实例变量。数组元素,值参数,引用参数,局部变量
静态成员隶属于这个类而不是这个类的实例
类中的字段(静态变量)可以被赋予不合理的值,所以演化出属性,属性有自己的逻辑保护字段防止被赋予不合法的数值。
计算机为每个字节都编号了;
值类型没有实例
引用类型变量及其实例:引用类型没有实例化时首先被分配四个字节空间并写入全0,创建实例时再次分配空间,并为创建时分配的空间写入再分配空间(实例)的地址;
局部变量在栈上分配内存,引用对象和实例分配到堆上,成员变量在声明后会赋予默认值,即把所有bit分为0;但本地变量不会初始化,必须先显性赋值。
常量不能再次赋值,必须在创建时初始化。
装箱与拆箱:装箱是当值类型被付给obj时,先将值类型的数据复制到一块堆,然后将这块堆的地址给obj分配到的四个字节的过程。拆箱是指将obj的数据赋给值类型的过程。装箱和拆箱会损失程序性能。
栈地址由低向高分配。
P8方法的定义,调用与调试:
方法永远是类或结构体的成员,不能直接放在名字空间中。类最基本的成员:字段与方法(成员变量与成员函数)。
对于可能改变的数值,应该确保其低耦合,确保其改变一个数据就可以影响所有方法。
方法声明:方法声明和定义在c#中是一起的,
构造器:构造器是一种特殊函数,没有返回值类型(不写void),构造器狭义指实例构造器,“()”就是在调用构造器。不写构造器时使用默认构造器,写了互后使用定义的构造器。 可以在一个类中写多个构造器,会根据参数进行匹配。
引用变量在栈中分配四个字节,存放堆地址,堆中存放实例化后的属性方法(?)。
方法重载:为一个类创建方法时,方法名可以一样但是方法签名不一样。方法签名由方法名,类型形参和每一个形参(左到右)类型和种类,但是不包含返回值。
类型形参:“public int Add
(ref int a)传引用参数 (out int a)传输出参数 也可以构成重载。
实例构造器也可以构成重载。
debug:
方法的调用与栈:callee,caller P9 50MIN处,有点蒙。不用理解也可以写程序,但是总归不太舒服。
P10 操作符详解_1
操作符概览:
操作符本质; 15MIN EXAMPLE
操作符优先级:没有赋值运算符时,运算从左向右;有赋值运算(+=)时,运算从右向左。计算机没有结合律。
同级操作符运算顺序:
各类操作符示例:
委托时不需要加()。
int[] myIntArray = new int[10];
int[] myIntArray = new int[]{1,2,3,4,5};初始化器
值类型默认值是0,引用类型的默认值也是内存全刷为0,显示null。
枚举类型中元素会被与数字结合,所以会返回标号为0的元素,若无赋值则为第一个元素。若没有标号0则会出错。尽量给个0在枚举中。
null是一个操作符。用来调构造器。
var的首次赋值后,不能再改变类型。
new可以调用实例构造器和初始化器(){ , , ,}
语法糖衣
泛型是什么??
使用new时要小心,会造成两个类的紧耦合。依赖注入设计模式可以减轻耦合。
继承和派生:使用new修饰符修饰同名方法可以隐藏父类方法。
checked检查溢出,unchecked不检查溢出。
delegate可以用来声明匿名方法,已经过时了。
sizeof可以获取除”strigng“,"object"之外类型在内存中占据的大小。siziof获取自定义的结构体大小时要使用unsafe(双保险)。
P12
类型转换:
隐式类型转换:
父类实例 = 子类实例;由子类向父类做隐式类型转换。引用变量访问内存时,只能看到该变量的成员。
显示类型转换:
cast(铸造),强制类型转换。高位舍弃。有些转换差的太多了,不能使用强制(cast),需要用Convert的方法。数值往字符串可以使用ToString()方法。
Parse只能解析正确的字符串格式。
操作符的本质是方法的简记法。
类型转换操作符:要对类的实例进行强制类型转换,要在被转换类中加入“修饰符 构造器”类似的结构。
数值提升:保证精度提升低精度的数值
“is”检测变量所引用的实例是否为某实例。
条件与,条件或有短路效应,写代码时应避开短路效应
Nullable
int? x = null;两者是一样的
P13表达式,语句
表达式定义:
专门用来求值的语法实体。
表达式概览;
int? x = null;
var y = x??100;//若x为null则返回100
三目运算符返回值类型取精度高的,但是两种类型要能进行隐式转换
赋值表达式的值就是左边的值
语句定义:
最小的独立元素,语句一定是在方法体里的。
语句详解:
标签语句;声明语句;嵌入式语句;
嵌入式语句只被嵌入在其他语句中的语句:比如if语句
常量必须在创建时被初始化。
写程序应遵循”单一职责原则“。
P15
block语句(块):名称空间体,方法体,类体不算是块语句。
语句只能出现在方法体中。
标签语句:hello:Console.WriteLine("helloworld");
goto hello;
编译器永远把块语句当成一条语句看。
ctrl + }可以在{}之间移动
块语句中可以看到块外声明且在块前的变量,块外不能看到块内声明的变量
嵌入式语句不能是声明语句或标记语句,若想使用声明或标记要用块。
try语句
try语句的finally语句写:释放资源的语句,程序的log。throw可以将异常抛给调用函数的程序。异常尽量抛出
P16 语句详解
while语句,可能执行零次或多次。
continue语句:放弃本次循环立即判断循环是否继续(while)或直接继续循环(do while)。
break语句:循环结束,不再执行
continue,break只会影响最近的循环
foreach:
继承基类IEnumerable的都可以迭代
P17 字段,属性,索引器,常量
常量
字段(filed):实例字段:隶属于某个对象,表示了该对象当前的状态
15min 静态字段(static):隶属于类型,表达类型的状态
字段为对象和类型存储数据
字段声明一定要在类体中,切勿写道函数体中,函数体中的变量是局部变量。
语句只能出现在函数体中,字段出现在类体中。
在字段声明时初始化与在类体函数中初始化是一样的。
实例字段每次创建实例时都执行,静态字段初始化只执行一次,就是第一次加载这个数据类型的时候。
静态构造器只执行一次(一般声明时初始化)
class A
{
static A(){};//静态构造器
}
readonly字段只能在创建时在构造器中初始化,不可再赋值
属性(property):属性访问特征成员反应状态。
public int Age
{
get
{return this.age}
}
set
{
if(value>=0 && value<=120)
this.age = value;
else
throw new Exception("Age value has error");
}
value上下文关键字,微软定义的
属性是一种语法糖。
属性的声明:public or “public static”
属性可以只有get或set
属性的完整声明 propfull +double tab
简略声明: public int Age{get;set;}
快捷键:ctrl r+e
动态字段:利用类的属性实时计算某个属性的值
属性名字一定是名词
永远使用属性而不是字段向外暴露数据,即字段永远是private或protected
索引器:
常量(79min):
常量属于类型不是对象
P18 参数
传值参数:声明时不带任何修饰符,不影响传入的实参
值类型:存储的数值;引用类型:存储的数据地址
obj.GetHashCode(),每个对象的hashcode都是唯一且不同的
ctrl + 。可以批量改变一个变量的名字
引用参数:引用参数不创建新的存储位置,他们指向同一个地址
使用ref显示有意改变实参值
不使用ref内存中会创建副本,使用ref则不会创建副本并使用相同地址下的地址值。
输出参数:用out修饰符声明的形参,被用作输出
不必再传入方法前赋值
方法体内要有明确的赋值
不为形参创建副本,两者对应一处内存
zhi值类型,引用类型都可以作为输出参数。
params作为形参数组变量的修饰,可以直接接受多个变量作为数组,params必须是参数列表的最后一个
string.Split(':',;.;,'.')将字符串按照一定逻辑区分开。
具名参数:提高可读性,参数顺序不再受形参顺序限制
剧名调用:Pri'n'tInof(age:34,name:“Tim”);
可选参数:
这个参数可写可不写,因为此参数声明时带有默认值
扩展方法(this参数):
首先声明静态类(static class是必须的),声明public方法
形式参数有this修饰符,且必须是形参列表中的第一个
必须有一个静态类(一般类名为SomeTypeExtension,作为对SomeType的扩展)
P19 委托(看起来很难得亚子)
委托(delegate)函数指针的升级版
间接调用:通过函数指针来调用函数
Action action = new Action(方法); //方法不带()
action.Invoke(); 或 action();
Func
z = fun1 .Invoke(1,2);
委托的声明(自定义委托):委托是一种类
public delegate double(double x,double y);
委托与所封装的方法必须"类型兼容":参数类型,返回值类型相同
类型声明要与类平行
※
※
※
※
※
※
※
※
※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
委托的一般使用:模板方法;回调;※※※※※※※※※※※※※※※※※※※※
模板方法 35MIN
回调方法
缺点:
1.方法级别的紧耦合
2.可读性下降 debug难度增加
3.
4.造成内存泄漏
委托的高级使用:
多播委托:
action1+=action2;
action2+=action3;
action1.Ivoke(); 一次调用三个委托
同步,异步:
异步调用:同步:一个一个做
异步:同时做,各不相干
//隐士异步调用※※※※※※※※※
action1.BeginInvoke(null,null);
action2.BeginInvoke(null,null);
action3.BeginInvoke(null,null);
//可能会造成资源冲突,需要加锁
//显示异步调用
Thread thread1 = ;
Thread thread2 = ;
Thread thread3 = ;
thread1 .start();
thread2 .start();
thread3 .start();
//
Task task1 = new ...;
...
...
...
...
...
//
应该适当使用接口取代一些对委托的使用
P20 事件的学习
事件:Event,能够发生的事情
事件是一种类型的成员
事件是使对象或类具备通知能力的成员
响应事件:
用于对象或类间的动作协调与信息传递:
事件模型:发生->相应的五个部分
时间多用于客户端程序(手机桌面web)
大部分时间是用C#编好的事件,不用畏惧
事件的订阅者
时间参数
P21
深入理解事件的组成部分
1.事件的拥有者(event source):对象或类
事件成员(event):
事件响应者(event subscriber)
事件处理者(event handler)
事件订阅:本质是一种已委托为基础的约定
小闪电图标的成员就是事件成员
Example: 事件响应者与事件拥有者属于不同的类
派生:在原有类的基础上创建自己的类
P22 Ling详解
自定义事件的声明:
事件是基于委托的
事件声明的完整格式
※10MIN※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
※※※※※※※※看不懂※※※※※※※※※※※※※※※※※※※※※※
事件声明的简略声明格式:
事件就是委托的模板,限制了外界对委托字段的使用
给事件命名时要注意时态
事件从外界只能访问添加或移除事件处理器
P23委托,Lambda,LINQ串讲
委托是一种类,包裹一些方法
使用委托时,方法作为参数,使用其他类的方法作为委托的参数,一定要先创建实例再调用该实例的参数吗?
临时从P30补充泛型的概念:
泛型(generic):很重要,地位与接口相当
泛化数据类型
泛型使用前要先特化,使用类名对其特化(<>中放的是类型参数)。
泛型委托:
Action
Func
对于逻辑结构很简单的小函数,不想因为名字污染上下文环境,所以使用lambda表达式使其函数再匿名函数中实现。
Lambda表达式:
1.匿名方法
2.Inline方法:
语法糖:再Lambda表达式中可以不写变量类型;可以不创建实例(不用new);//长期使用的写法
example0:MIN46
LINQ:把C#代码翻译成SQL代码,可以做数据库查询
LINQ是一种查询方式,工作时大多用于数据库
example1:MIN56 链接数据库失败 Fail
关键是LINQ如何使,交付什么样的Lanbda表达式满足LINQ
P24 什么是类(class)
类是一种数据结构
是一种数据类型
代表现实世界中的”种类“
P25 类的声明与访问级别
类可以声明再名称空间里,类内,名称空间外(基本用不到)
C#中,类声明即定义
public 类访问级别修饰符,可以从名称空间外访问。先添加依赖项,然后可以选择using或按路径访问
internal定义的类(默认的)可以在项目内访问
P26:类的继承,类成员的访问控制
基类(base class)
派生类()
C#是单根的,其顶端是object,即未继承的类都继承于Object
类名:基类名 继承了类
可以用父类类型的变量引用子类类型的实例
sealed类 封闭类 不能再派生基类
C#中一个类最多允许有一个基类,
子类的访问级别不能超过父类:父类internal 子类不可以public,反过来可以
派生类对基类成员,只能扩展不能删减
类横向扩展:类成员扩充
类总想扩展:类成员重写
F12 crtl+“-”
ctor + Tab 类的实例构造器快捷键
继承链上的类再调用构造器时,从基类构造器开始,再调用子类构造器
this.调用当前对象,base.调用基类对象,且最多只可以访问上一级的对象
this.a 与base.a 指向同一处内存
派生类调用基类构造器时,若参数不匹配,可以从派生类的构造器
public Car():base(string owner){}
或
public Car(string owner):base(owner){}
父类的实例构造器不会被子类继承
pritected级别可以从子类调用
protected是跨程序及集的,可以从其他项目中的派生类中访问到
private访问级别再类体中。
继承了却不能再派生类访问。
默认访问级别为private
类设计访问级别很重要,能不给人看就别给人看
P26※※※※※※※※※※重要※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
P27重写 多态
类的继承:
virtual override
不写上边两个叫做子类对父类的隐藏,其中有两个版本的方法。在创建时会先给父类创建,再给子类创建,同时读取内存时,如果从父类创建变量,子类构造实例,会读到父类的方法,即读取内存块的时候读取到了第一个位置的函数而不是第二个位置的函数。
如果使用了virtua和override,则只会有一个方法
重写可多级继承
属性成员可以被重写
重写与隐藏发生条件:函数成员,可见(父类方法或属性是public或protected),签名一致
可以用基类类型的变量引用派生类类型的构造器
P28抽象类与开闭原则
Solid设计原则:
abstract 类名
{abstract void Name();}
没有完全实现函数成员的类叫抽象类,没有实现的函数成员不能是private,其功能由子类实现。
抽象类不能被实例化,可以作为基类或声明变量引用子类的实例。
开闭原则:稳定成员封装,不稳定成员设计为抽象成员。 如果不是为了修bug或添加功能,不修改代码
实现抽象方法时也需要override修饰类成员
Interface要求所有成员都是public,所以可以不写public,同样可以不写override
接口作为抽象类的基类,抽象类再派生类。
封装确定的,开放不确定的,推迟到合适的子类中去实现。
抽象类->
P29 接口,依赖反转,单元测试
接口中的成员方法都是public的,不需要写。
引入接口解耦和
25MIN Example miss
P30接口隔离,反射。特性。依赖注入
当接口类型的一部分一直没被用,说明要的太多了,接口太胖了;
接口隔离原则<-->单一功能原则(过犹不及)
InterfaceExample2-> 30min Fail
接口的显示实现:使用特定接口名显示定义名字时才能调用该接口派生时定义的方法,详见P30 38MIN
反射(.NET框架的功能,不是C#功能):
在不知道对象类型的情况下创建一个同样类型的对向,且可以调用其属性,方法,事件
依赖反转原则:
单元测试:MIN55
※※※※※※※※※※缺失测试※※※※※※※※※※※
封装好的反射->依赖注入:
P30 60min 脑子不够用了,理解不能
最后一个例子很重要,但是我很懵逼。
反射->更松的耦合:
P31 60MIN
Partial类:
减少类的派生