首页 > 代码库 > .NET基础笔记(C#)
.NET基础笔记(C#)
闲着没事就把以前学习时的笔记拿出来整理了一下,个人感觉有点用,就想拿出来跟园友共享一下。有些基础性的内容比如基本概念、语法什么的就不发了。
内容:1、构造方法(函数) 2、继承 3、访问修饰符 4、静态和非静态 5、隐藏基类方法 6、重写基类方法 7、抽象方法 8、接口 9、多态和接口 10、值类型与引用类型 11、ref和out 12、类型转换 13、异常处理 14、string字符串处理 15、string常用方法 16、StringBulider 17、File类读取文件 18、文本高亮 19、泛型集合List<T>和Dictionary<TKey,TValue> 20、装箱和拆箱 21、读写数据(Ststem.IO) 22、文件流(FileStream) 23、StreamReader和StringWriter 24、File和Directory 25、序列化 26、Directory类的静态方法 27、正则表达式 28、Regex类 29、字符串提取 30、贪婪模式 31、字符串替换 32、byte数组和字符串的转换 33、委托 34、多播委托 35、事件 36、委托和事件的区别 37、var类型 38、扩展方法 39、XML
1、构造方法(函数):在对象创建的时候初始化字段
public 类名([参数])
{
//执行内容,如this.name=name;(有参数)
}
构造方法没有返回值,void也不写
创建对象时如果没写构造方法系统默认有一个无参的构造方法,一旦用户添加了构造方法,原来默认的无参的构造方法就没有了
默认初始值为:int:0; bool:false; string:null; char:‘\0’;
构造方法的作用:初始化字段(实例化类的时候传入初始值)
调用构造方法:
1)除了new对象的时候,不能直接使用构造方法名调用构造方法
2)this调用构造方法(难点)(一个构造方法调用另一个构造方法)
为了解决代码的重用才使用this调用构造方法
当一个类中出现多个构造方法重载的时候,同时构造方法都需要为字段赋初值
使用this代表当前类的构造方法来使用,根据this后面的参数决定使用哪一个重载
public 构造方法名():this([参数])
{
//代码
}
如:
public Person(string name, int age, string sex)
{
this.name = name;
this.age = age;
this.sex = sex;
}
public Person(string name)
: this(name, 0, null)
{
}
2、继承:
多个类都有共同的字段、属性或方法时,可以使用继承
需要写一个学生类、一个老师类、一个校长类
事先写一个Person类,在写其他类的时候继承这个类,Person叫父类,继承他的叫子类
特征:子类既有父类的非私有字段、属性和方法,又有子类独有的字段、属性和方法
一个子类只能有一个父类,一个父类可以有若干个子类
[访问修饰符] class 子类名:父类名
子类调用父类构造方法的问题(难点)
->构造方法的执行顺序:先父类再子类
->关于构造方法的重载:如果不声明调用哪个构造方法默认执行无参的构造方法
避免错误的方法:
->为父类提供无参构造方法
->在子类中指定调用父类有参的构造方法
注意:父类除构造函数(方法)之外的非私有成员都可以被子类所继承,构造方法不可以被继承,但可以被调用,调用方法: [访问修饰符] 构造方法名([参数]):base(父类构造方法的参数)
public Teacher():base("狗蛋”,20,"女")
{ } //直接赋值,实例化对象时不需再赋值
public Teacher(string name, int age, string sex)
: base(name, age, sex)
{
this.className = "C#";//老师类独有的字段
} //继承传递参数,实例化对象时需要赋值
base关键字用于显示声明要调用父类的哪个构造方法,是调用,不是声明不是继承
注意:为避免继承父类时因为构造方法出错,应该为每个声明的类都手动添加一个无参的构造方法
3、访问修饰符
public 最公开的,所有地方都可以访问
private 最隐蔽的,只有本类可以访问
protected 可以在当前类和子类中被访问
internal 只能在本项目中被访问
C#中规定类的访问修饰符只能是public 和internal
类中成员的访问修饰符除以上四种以外还有个(protected internal)即可以在当前类、子类和本项目中被访问
4、静态和非静态
静态使用static标记
静态成员
->如何定义静态成员
在成员前加static关键字
->如何使用静态成员
静态成员不能由实例对象调用,只能用类名调用
使用静态成员,不需要实例化对象
静态成员会在整个应用程序退出时才释放资源,所以这个变量可以在整个程序中共享数据,可用于窗体间传递参数
注意:静态变量过多会占用系统内存,因此声明静态变量时要多加考虑
Main方法是静态的,所以它只能调用静态的方法或参数,若想调用非静态方法,必须new一个类的实例,用实例名调用。
静态类
1)什么情况下要将一个类标记为静态类?
一般情况是,当这个类是一个工具类,里面都是方法,为了让用户调用的时候方便,不需要实例化对象,这时可以将该类标记为static类,此时该类中只能包含静态成员,不能包含实例成员,比如Convert、Console、Read、ReadInt等
2)什么情况下需要在一个普通类中编写一个静态成员,而这个类不能标记为static?
当这个类需要被实例化的时候,如果这个类中有一个成员是所有对象要共享的数据,这时可以将该类中的这个成员标记为静态的,但是这个类还是一个非静态类
3)静态类不能被实例化,不能被继承
4)由于静态成员会在整个程序退出时才释放资源,所以尽量避免写静态字段或静态属性,最好只写静态方法
5)静态类中可以有静态成员和实例成员,因为其不能被实例化,所以静态类中的实例成员无法被调用,也就没有任何意义了
6)静态类不能被继承,也不能继承自其它类,只能继承自Object类
静态构造函数
1)静态构造方法(或字段)在第一次访问这个类的时候执行,并且只执行一次
->静态构造方法与静态类的生命周期(了解)
静态成员属于该类的所有对象。实例成员只属于当前实例
静态类的生命周期:第一次访问类的时候创建,程序结束时才释放
5、隐藏基(父)类方法(了解,用的不多)
当子类和父类有相同名字的方法时,实例化子类调用该方法为子类内的方法,把子类转换成父类后再调用该方法就是父类内的方法了
为了代码规范化,如要有意隐藏父类的方法,要在子类的同名方法前加new关键字,用于 提醒这里是要隐藏父类方法
//隐藏基类方法
class MyBase
{
public void Func()
{Console.WriteLine("我是父类的方法);}
}
class MySub : MyBase
{
public new void Func() //在方法前用new关键字提醒这里是有意隐藏父类方法
{Console.WriteLine("我是子类的方法);}
}
class Program
{
static void Main(string[] args)
{
MySub ms = new MySub();
ms.Func();
// ((MyBase)ms).Func();
MyBase my = ms;
my.Func();
Console.ReadKey();
}
}
6、重写基(父)类方法(多态的体现)
在父类方法前加virtual(虚方法),表示这个方法可以被重写
在子类方法前加override,表示重写基类方法,子类如果不写override就是隐藏
当子类和父类有相同名字的方法时,实例化父类调用的是父类的方法,将子类赋值给父类后,父类调用的就是子类的方法了
一旦父类的方法在子类中被重写,除了实例化父类调用以外,子类或者子类的子类调用时都是调用的最新的那个子类重写方法
此时base和this就有区别了:
如果子类和父类中有名字相同的方法,那么base.Func()就代表父类的方法,this.Func()代表当前类的方法
如果不希望子类再重写,需要在子类方法前加sealed
//重写基类方法
class USB
{
public virtual void Usb() //virtual关键字表示父类方法可以被重写′
{Console.WriteLine("我是父类的方法);}
}
class Phone:USB
{
public override void Usb()//override关键字表示这个方法重写父类的方法
{Console.WriteLine("手机");}
}
class Mp3:USB
{
public override void Usb()
{Console.WriteLine("MP3");}
}
class Program:USB
{
static void Main(string[] args)
{
Console.WriteLine("输入数字");
USB usb = new USB();
switch (Console.ReadLine() )
{
case "1":
usb = new Phone();
break;
case "2":
usb = new Mp3();
break;
default:
break;
}
usb.Usb();
Console.ReadKey();
}
}
隐藏看类型,重写只管新
7、抽象方法
方法前加abstract,圆括号后加分号
[public] abstract void 方法名(参数);
抽象方法所在的类也必须是抽象的,不能被实例化,类前加abstract
abstract class 类名
抽象成员必须出现在抽象类中,但抽象类中可以有抽象成员和实例成员
抽象类的抽象方法默认可以且必须被重写,所以需要重写时不需要再写virtual,直接在子类方法中写override关键字即可重用
抽象类的成员:方法、属性、索引、事件
注意:抽象类中的所有抽象方法在子类中必须要被重写,如果子类中不重写,那这个子类也必须声明成抽象类,但此时子类也就不能被实例化了
8、接口
[访问修饰符] interface 接口名(接口一般以I开头,I+行为+able)
{
//接口成员,一定是抽象成员
}
成员的定义:与抽象成员的写法一样(没有关键字),没有执行体(方法体);没有访问修符;
实现:子类“继承”自接口,接口可以看做是子类的“干爹”,补全抽象的方法
调用:和类的使用方法一样,要使用接口中的方法,则将对象赋值给接口变量
多态:和抽象类的实现一样,接口的多继承使得多态的实现变得灵活
子类继承父类,子类实现(继承)接口:class 子类名:父类名,接口名 或class 类名:接口名
个人理解:接口就是把类中的一种特定的功能性方法封装成接口(如开车、飞等),声明类的时候可以继承这个接口,就拥有了这个接口所特有的功能,就可以在类中实现这个功能,那实现后的这个功能就成为了这个类的一个功能性方法。用接口可以实现多继承,一个类可以继承多个接口,也就拥有了多个功能性方法,声明类的时候可以选择性的继承,比如定义老师的时候可以继承说话、吃饭、教课等接口,定义司机的时候又可以继承说话、吃饭、开车等接口
//定义一个接口,表示Driving功能
interface IDrivable
{
void Driving();
}
class Teacher:Person,IDrivable//继承父类和接口
{
public Teacher(string name, int age, string sex):base(name,age,sex)
{//继承父类的构造方法
}
public void Driving()//实现接口的Driving功能
{
Console.WriteLine("我会开车");
}
public override void ShowMe()//重写父类方法
{
Console.WriteLine("我是老师");
}
}
static void Main(string[] args)
{
Teacher tea = new Teacher("张三", 18, "男");
tea.ShowMe();//调用重写后的方法
tea.Driving();//调用实现后的接口方法
Console.ReadKey();
}
9、多态和接口
接口是可以多继承的,多继承同时会引起方法的重名
为避免重名,显示实现接口:
返回值 接口名.方法名(参数)
{
//方法体
}
显示实现接口的这个方法只能由接口变量进行调用
现阶段记住接口使用的语法,然后将接口直接当做抽象类使用(初学阶段)
10、值类型和引用类型
变量可以看做是一个数据
值类型,是一个存储数据的容器,这个数据就是这个类型表示的数据
引用类型,也是一个容器,但是这个容器存储对象的一个引用(地址),真正的数据在另一块内存中,就相当于是一个指向数据的快捷方式
值类型存储在栈里面,引用类型存储在栈和堆里面(堆里存储的是真实数据,栈里面存储的是真实数据在堆里面的内存地址)
值类型和引用类型会涉及到传参和赋值时的不同,要注意区分
委托(delegate)也是引用类型
值类型赋值赋的是真实的值,引用类型赋值赋的是地址
引用类型是一个变量,两个快捷方式,修改一个快捷方式会影响另一个快捷方式;值类型是一个变量和一个复制后的变量,修改一个变量不会影响另一个变量
所有的值类型都继承自System.ValueType类
11、引用传递:ref和out
使用ref或out就可以实现将引用传递过来
定义方法时,在参数类型前加ref或out
调用方法时,在参数前加ref或out
注:定义变量时不需要加ref或out
当一个变量传递给一个方法时,总是复制一份自己的数据,赋值给方法,那么方法中执行的变量就与方法外的那么变量没有关系了,方法内修改变量值,也不会影响方法外的那个变量。有时会要求多个返回操作(方法中的变量有多个在方法外需要使用),此时可以使用ref标记参数,此时在方法中使用的这个变量与方法外的变量就是同一个变量了,相当于可以使用参数实现返回值
out与ref的作用与使用方法相同
out 必须在方法中赋值
ref必须在方法外赋值
12、类型转换
隐式类型转换:兼容类型,小转大
显式类型转换:即强制类型转换 (转换后的类型)要转换的变量
Convert类型转换:系统封装的方法,Convert.ToInt32()、Convert.ToDouble()等
其他转换方法:int.Parse()、int.TryParse()(第一个参数表示待转换的字符串,第二个out(result)参数表示数字转换成功后的变量,如果转换失败,返回false,为out result赋值为0)
用int.TryParse()代替try-catch,释放内存,提高性能
不止int类型有TryParse,所有基本类型都有TryParse,用法都一样
13、异常处理(try-catch、try-catch-finally、try-finally)
Exception是一个专门用来封装异常的一个类型
两个属性:message(异常说明文本)和stackTrace(异常抛出的顺序)
抛出异常:throw Exception的一个对象(new一个对象)
从出现异常的try向上依次抛出(方法之间依次调用),直至处理,找到最近的catch捕获异常(用于try-finally语句,catch块在方法以外,执行顺序是try-finally再向上寻找catch执行异常捕获)
Finally用于释放资源,finally{},程序执行完try-catch后会再执行finally语句块
Finally总会执行,即使try-catch中有return
try
{ }
catch (Exception ex)
{ throw; }
finally
{ }
Try-finally用处不多,用于释放资源
使用异常会降低系统性能,尽量少用
14、string字符串处理
构造函数:string(char[] chs)、string(char ch,int count)
string str = new string(’a‘,’b’,’c’);//abc
string str = new string(‘a’,3);//aaa
3.144.ToString(“0.0”);用于控制格式,会四舍五入
字符串可以当做数组进行处理(使用下标读取,拥有数组的各种属性)
string str = “不可见的你”; 此时str[1]就是“可”了
通过索引得到的数据是char类型,字符串不可改变,使用索引无法修改字符串的数据
要想修改可将字符串变成字符型数组,str.ToCharArray()
string是引用类型
string str = string.Empty;声明一个空字符串
判断字符串是否为空:
str.Length == 0(推荐)
str ==“”
str == string.Empty
String.IsNullOrEmpty(要判断的字符串)(推荐)
15、string常用方法
1)字符串比较:bool isTrue = string.Equals(string a,string b)
String.Equals(str1,str2);
Str1.Equals(str2,StringComparison.OrdinalIgnoreCase); //第二个参数意思是不区分大小写
string.Compare(str1,str2);
Equals方法:
String重载的方法判断两个,一个是==,一个是EqualsHelper
==判断两字符串是否相同
EqualsHelper判断两个字符串中每一个字符是否相同
Object的重载是来自于object类,是继承下来的
在object中使用的是==和Equals方法
Equals方法是一个虚方法,由string重写了,调用的是object.ReferenceEquals方法和EqualsHelper方法
注意:==默认表示判断两个对象的地址是否相同,与object.ReferenceEquals方法一样
String提供的str1.Equals(str2)判断地址是否相同,字符串是否object提供的virtual Eaulse(object)
Equals和==的区别:
Equals会判断两个值的内存地址;==只判断值是否相等。
Compare方法:
int result = String.Compare(str1,str2);
str1>str2 -> 1 str1 == str2 -> 0 str1<str2 -> -1
string.Compare方法比较两个字符串,这两个字符串字母顺序表示大小,按照字典排序规则进行比较(对于char类型:A>a,B>a,b>A同一个字母,大写>小写,不同字母按照字母顺序后面的大于前面的;对于string类型不是按照埃克森码比较,A>a,b>A,B>a)
2)大小写转换:ToLower()和ToUpper()
3)str1 = str1.Trim([params char[] chs]);去除字符串两边的空格,或chs中出现的字符(重载)
str1 = str1.TrimEnd(params char[] chs);去除字符串的末尾chs中出现的字符
str1 = str1.TrimStart(params char[] chs);去除字符串的开头chs中出现的字符
4)合并与分割
合并字符串:string s = string.Join(str1,str2,str3,...)
分割字符串:string[] strArray = str1.Split(‘a’);
string[] strArray= str1.Split(new char[]{‘a’,’b’},StringSplitOptions.RemoveEmptyEntries);去掉a、b和所有空格
5)字符串查找
Contain包含,返回bool类型的值,str1.Contain(“爱”);
IndexOf检索位置,返回字符串中某个字符的位置(int)找不到则返回-1
str.IndexOf(要找的字符或字符串)
str.IndexOf(要找的字符或字符串,开始寻找的位置)
str.LastIndexOf()是从后往前寻找,用法同str.IndexOf()
注意:IndexOf和LastIndexOf中第二个参数(开始寻找的位置)只是为了说明要查找的是字符串中的哪一个字符(如果字符有重复),即按查找顺序在开始位置后的第一个字符,返回的索引依然是该字符在整个字符串中的索引,而不是从查找位置开始的索引。
6)截取子字符串
string s =str1.Substring(开始的位置,子字符串的长度)
String s = str1.Substring(开始的位置)
7)判断字符串的开头和结尾
bool a = str1.StratWith(字符串);
bool a = str1.EndWith(字符串);
8)字符串的插入、移除和替换
插入:string str2 = str1.Insert(位置(int),要插入的人字符串(string));
移除:string str2 = str1.Remove(位置(int),长度(int));
string str2 = str1.Remove(位置(int));移除该位置后的所有字符
替换:string str2 = str1.Replace(旧字符串,新字符串);
9)格式化字符串
String.Format(格式化的字符串,填坑的参数)
string str1 = String.Format(“{0},{1}”,str2,str3);
10)判断字符串是否为空
String.IsNullOrEmpty(str);
10)字符串的方法(总结)
增:添加(+=)、插入(Insert)
删:Remove
改:Replace、 ToUpper、ToLower、Trim、TrimStart、TrimEnd、Split、Join、new String、SubString、Format
查:Contains、IndexOf、LastIndexOf、StartsWith、EndsWith、ToCharArray、Length、Empty
比:Equals、Compare
16、StringBuilder
大量字符串拼接的时候性能非常差,很难完成大型数据的拼接
(Stopwatch对象可以记录程序运行的时间,Start开始计时,Stop结束)
使用StringBuilder:StringBuilder sb = new StringBuilder();
sb.Append(要添加的字符串);//向sb中添加数据
sb.AppendLine(要添加的字符串);//向sb中添加数据后换行,等价于sb.Append(要添加的字符串+”\r\n”);
sb.AppendFormat(要添加的字符串);格式化处理,等价于 sb.Append(string.Format(要添加的字符串));
string str = sb.ToString;输出或赋值时要转换成string
在处理大型数据的时候,StringBuilder要比普通的string处理快很多,因为处理大型数据要用StringBuilder
17、读取文件
string[] lines = File.ReadAllLines(@"H:\传智播客实训资料代码\第9天\a.csv", Encoding.Default);//按行读取
string[] lines = File.ReadAllText(@"H:\传智播客实训资料代码\第9天\a.csv", Encoding.Default);//读取所有文本
string[] lines = File.ReadLines(@"H:\传智播客实训资料代码\第9天\a.csv", Encoding.Default);//读取一行文本
string[] lines = File.ReadAllBytes(@"H:\传智播客实训资料代码\第9天\a.csv", Encoding.Default);//读取字节
18、文本高亮
->让文本框获得焦点--txtContent.Focus();
->找到要高亮的位置--pos
->选中(高亮)的字符串长度--length
->调用TextBox的选中方法txtContent.Select(pos,length)
19、泛型集合 List<T>和Dictionary<TKey,TValue>
List集合:动态的自定义数组
List<类型> 集合名 = new List<类型>();
List用法与ArrayList一样,不同点在于定义时和使用时的类型固定,因此可以完全替代ArrayList
类型[] = list.ToArray()//将List转换成该类型的数组
Dictionary集合:动态的自定义键值对
Dictionary<类型,类型> 集合名 = new Dictionary<类型,类型>();
Dictionary用法与Hashtable一样,不同点在于定义时和使用时的类型固定,因此可以完全替代Hashtable
20、装箱与拆箱
直接将值类型赋值给引用类型就是装箱
将存储值类型的引用类型强制转化为对应的值类型就称为拆箱
Object o = 1;//装箱 int n = (int)o;//拆箱
装箱对值类型保持无关性,装箱对引用类型保持相关性
装箱和拆箱会对性能有一定的影响
21、读写数据(I/O操作、System.IO类)
System.IO.Path类,专门处理字符串路径,是一个静态类
string path = @“D:\window\system\file.txt”;
获取文件名:string fileName = Path.GetFileName(path);
获取后缀名:string fileName = Path.GetExtension(path);
修改后缀名:path = Path.ChangeExtension(待修改的路径,“mp3”);
获取文件夹名:string fileName = Path.GetDirectoryName(path);
合并路径:path= Path.Combine(路径1,路径2,路径3...);
临时文件夹:path = Path.GetTempPath();
绝对路径:从盘符开始的最精确的路径
相对路径:相对于当前文件同一个目录作为参照的路径
相对路径和绝对路径:区分就看有没有根目录盘符的标记
22、文件流(FileStream)
1)引入命名空间
2)创建FileStream对象FIleStream fs = new FileStream(文件名,FileModel,FileAccess)
FileStream fs = new FileStream(@"D:\a.txt", FileMode.Create, FileAccess.Write);
FileModel:对文件的处理方式(打开、创建、追加)
FileAccess:对文件的操作:读、写
3)使用方法操作
写一个字节:fs.WriteByte()
写一个字节片段(将字节数组中的数据写入到fs指定的文件):
fs.Write(存放字节的数组,开始写字节的数组下标,要写的字节数)
读一个字节:fs.ReadByte()
读一个字节片段(将fs指定的文件中的数据读取到字节数组):
fs.Read(存放字节的数组,开始存放字节的数组下标,要读的字节数)
4)释放资源
fs.Close()和fs.Dispose()方法(两个都要调)
23、StreamReader和StreamWriter
StreamReader:专门用来读取文本文件的流
1)引入命名空间:IO
2)new一个FileStream指向文件
3)new一个StreamReader,构造函数参数是FileStream的对象
4)调用方法
StreamReader有一个属性EndOfStream:判断当前的流位置是否在文件流的末端,一般用来判断文件是否读完
StreamWriter:专门用来写入文本文件的流
1)引入命名空间:IO
2)new一个FileStream指向文件
3)new一个StreamWriter,构造函数参数是FileStream的对象
4)调用方法
24、File和Directory对文件和目录操作做了一个封装
增(Create):创建文件或文件夹
删(Delete):删除文件或文件夹
改:改名字 File.Move(“1.txt”,“2.txt”)
查:...
判断是否存在(Exists)
移动文件:File.Move(“1.txt”,“2\1.txt”)
复制文件:Copy
Directory用于处理文件夹(目录)
删除目录的时候如果目录不是空的则会报错,此时需要在Delete内添加一个true
Directory.Delete(“1”,true);
25、序列化:
序列化:把一个对象的内容存储到磁盘上(文件流)、网络上(网络流)、内存里(内存流),下次需要用到对象数据的时候可以直接拿来用
反序列化:把已经序列化存储到磁盘、网络、内存里的对象数据,提取到项目中的对象中
序列化步骤:
1)创建一个文件流
2)为需要序列化的对象添加标记([Serializable]表示此对象可以被序列化)
3)创建BinaryFormatter(导入命名空间)
4)调用Seralize方法(bf.Seralize(文件流对象,需要被序列化的对象))
反序列化步骤:
1)创建一个文件流
2)为需要序列化的对象添加标记([Serializable]表示此对象可以被序列化)
3)创建BinaryFormatter(导入命名空间)
4)调用DeSeralize方法(object ob = bf.DeSeralize(文件流对象))
反序列化定义接收类型的时候必须根据原序列化的程序集确定类型
建议:在使用序列化的时候尽量避免使用自动属性,因为自动属性在每次编译的时候自动生成的字段名可能不一样,所以在反序列化的时候可能会造成问题
注意:如果序列化和反序列化不在同一项目下,那反序列化的时候需要引用原序列化的程序集
26、关于文件夹下的文件与子文件夹
Directory类的静态方法
获取子文件名:string[] s = Directory.GetFiles(@“D:\dir“);
获取特定后缀名的子文件名:string[] s = Directory.GetFiles(@“D:\dir“,”*.txt“);
获取所有文件夹名:string[] s = Directory.GetDirectories(@“D:\dir“);
27、正则表达式
元字符:
1).(点)表示除\n以外任意的单个字符
2)[ ]表示括号内的任意一个字符(如a[xyz]b表示axb或ayb或azb)
3)^在[ ]括号内表示取反(如^[a-z]表示除小写字母以外的所有字符)
4)^匹配一串字符的开始(^abc表示以abc为开头的任意字符串)
5)$匹配一串字符的结尾(abc$表示以abc为结尾的任意字符串)
5)|表示“或”,优先级最低(如a|bc表示a或bc,(a|b)c表示ac或bc)
6)()可以改变优先级、提取组
7)*表示限定前面的字符出现任意次数(abc*表示ab、abc、abcccc......(abc)*表示abc、abcabc)
8)+ 至少出现一次,也可出现多次(xa+y表示xay、xaay...)
9)?表示出现0次或1次
10){n}限定前面的表达式出现n次
11){n,m}至少出现n次,最多出现m次
12){n,}至少出现n次
13)\d表示0-9,\D表示\d的反面
14)\s表示空白符,\S表示\s的反面
15)\w表示数字、字母、下划线、汉字,\W表示\w的反面
注:正则表达式的转义符也是\,如果要表示*(星),可以写成\*,要表示一个\,可以写成\\
28、Regex类
Regex.IsMatch(待判断的字符串,正则表达式)判断一个字符串是否匹配某个正则表达式
!如果正则表达式没有^和$,那默认不设定开头和结束,此时只要待判断的字符串中含有该正则表达式就返回true
^z|food$:表示以z开头的任意字符串或者以food结尾的任意字符串,都返回true
因为|的优先级最低,所以上边的表达式其实是(^z)|(food$)
Regex.Match()从某个字符串中提取匹配某个正则表达式的某个子字符串,但只能提取一个
Regex.Matches()同上,可以提取所有的匹配字符串
Regex.Replace()字符串替换,把所有匹配正则表达式的字符串替换为对应的字符
29、字符串提取
字符串提取的时候一般不加^和$
Match m = Regex.Match(原字符串,要提取的正则表达式)
m.Value属性表示提取到的字符串
m.Group[n]:提取组,按正则表达式中()括号对的个数,m.Group[0]表示整个字符串,m.Group[1]表示第一个括号内的内容,所以提取组的时候要从下标1的地方开始
MatchCollection mc = Regex.Matches(原字符串,要提取的正则表达式)
MatchCollection是一个集合,需要foreach遍历输出(遍历时的类型是Match)
30、贪婪模式
定义:当正则表达式提取的时候,如果一个字符或多个字符都能匹配,这时会按照使用最多字符的方式来匹配。
正则表达式默认采用贪婪模式,尽多的匹配
在限定符后面使用问号用来终止贪婪模式,即只会提取匹配的第一个
string s = “aaaa bbbbbbbb”;string s1 = “[a-zA-Z]+”; 匹配结果:aaaa
string s = “aaaa bbbbbbbb”;string s1 = “[a-zA-Z]+?”; 匹配结果:a
31、字符串替换
String s =Regex.Replace(原字符串,待替换的正则表达式,替换后的字符串);
如:string s = Regex.Replace(s, "-+", "-");//把字符串s中的所有-替换成一个-
提取组替换:
string s = "aaa13812345678bbb";
s = Regex.Replace(s, @"(\d{3})(\d{4})(\d{4})", "$1****$3");//把手机号中间4位替换成4个*
32、byte数组和字符串的转换
byte[] bt= System.Text.Encoding.UTF8.GetBytes(str);
string str = System.Text.Encoding.UTF8.GetString(bt);
33、委托(delegate)
委托是一种数据类型(和类一样),可以将方法赋值给委托对象,可以理解为方法类型
定义(在命名空间下,或添加一个cs文件,和类的定义一样):
1)使用delegate关键字
2)这个委托将来要存储的方法如果没有返回值,那么委托也要定义成void,参数也要与方法保持一致
3)委托是一个数据类型,用的时候需要传递一个变量
4)创建委托对象的时候可以不用new,系统编译时会自动给我们new
定义委托类型:public delegate void ChangeTxtDelegate(string str);
创建委托对象:public ChangeTxtDelegate changeTxt; //Form1中
给委托赋值:f1.changeTxt = ChangeText; //Form2中
调用委托:changeTxt(txt); //Form1中,此种调用方法为简写,实际上是changeTxt.invoke(txt);
创建委托对象时也可直接对其赋值:ChangeTxtDelegate changeTxt = new ChangeTxtDelegate(ChangeText) ;
用法(窗口间传值):委托要声明在需要被传值的对象内,比如Form1要提取Form2中的数据(即Form2往Form1传值),就应该把委托声明在Form1中,在Form2中写一个方法获取要传的数据,并且赋值给Form1的委托,最后在Form1中调用委托就ok了。(委托声明位置不绝对,还需要考虑当前活动窗口的问题,要声明再活动窗口中,有时也可声明在被提取值的窗口内)
作用:可以在某个代码内部,嵌入一段外部代码(外部写一个方法赋值给委托或在定义方法时把委托类型作为参数,调用时把方法名作为参数传递给委托对象,那么委托就执行该方法,不同的外部项目可以写不同的方法赋值给委托,可以实现一个委托选择性执行多种方法的灵活性),相当于是注入
什么情况下使用委托?
当一个类型中需要嵌入一段代码,但是这段代码具有不确定性,是根据使用这个类型的用户来确定代码的,这时就可以在该类型中使用一个委托,保证在某种情况下会调用该委托,用户将对应的方法传递给该委托,就会调用这个方法
可以把静态方法或者私有方法赋值给委托了变量,赋值后只要能使用到该委托变量的地方就能使用该方法,打破了访问修饰符的限制
一般在调用委托前或者触发事件前,都要判断委托变量或事件是否为null,如果为null则不调用
34、多播委托
MyDelegate md = new MyDelegate(M1); md+=M2;(增加某个方法)md-=M4;(去掉某个方法)
使用委托时,如果不是+=而是直接用=赋值,则会将前面增加的所有方法都覆盖掉
多播委托可以存储多个方法,委托中方法调用的顺序与增加方法时的顺序是一致的,但是,不要依赖于这个顺序(微软没有承诺这样的顺序,万一以后改了就GG了)
多播委托中,如果要是有返回值,只会得到最后一个方法返回的结果。可以通过调用GetInvocationList()方法返回当前委托中的所有方法,返回值类型时一个Delegate数组(委托数组),再用foreach遍历调用所有方法
所有定义的委托都继承自抽象类MulticastDelegate,而MulticastDelegate又继承自Delegate抽象类
多播委托内部是将绑定在当前委托对象上的每个方法,都转换成一个委托对象,并且存储在了一个叫_invocationList的object数组中。当调用委托的时候,其实就是循环遍历_invocationList数组,并且调用其中的每个委托
多播委托中,如果其中的某个方法执行时发生了异常,则后面的方法不再执行。
多播委托中只能存储同一类型的委托,即返回值、参数都相同
35、事件
事件是在特定条件下执行的动作,这个动作是由用户确定的。如Button的Click事件,其动作是由程序员写的,因此可以用委托来实现这个功能,写不同的方法赋值给委托变量。
但用委托模拟方法有两个缺点:
1)定义的委托可以在类外部的任何地方被触发,即非特定条件下也可以调用。因为委托变量是public修饰,改为private就不能在外部赋值了。
2)由于委托可以用=(等号)赋值,所以就有可能把前面已经注册的事件处理程序都覆盖掉。
而用事件就可以解决以上问题:
1)用事件必须先定义一个委托,否则无法使用事件
2)实例化委托变量的时候,在委托类型名前面、访问修饰付的后面加event关键字,就定义了一个事件
3)使用方法与委托一样,只是避免了以上两个问题
4)由于事件只能有+=或-=赋值,所以就避免了用=(等号)赋值时覆盖前面事件处理程序的问题
5)由于事件不能在定义事件的类以外被触发,所以也就避免了冒充事件触发的问题。
36、委托和事件的区别(面试常考)
委托和事件没有可比性,委托是数据类型,事件是委托类型的变量
委托可以用+=、-=、=赋值,可以在类的外部被调用
事件只能用+=、-=赋值,不能在类的外部被调用
事件的内部是用委托实现的
事件的作用和委托变量(不是委托,是委托变量)一样,只是在功能上受到一定的限制
37、var类型
var只能用作局部变量,可以赋任何类型的值。
var a = 1;和int a = 1;两者完全一样
var不能作为类的成员的类型,不能用作参数,不能用作返回值,只能用作局部变量
38、扩展方法(不修改微软系统方法源代码,实现系统方法的增加)
1)添加一个静态类
2)在静态类中增加一个静态方法,方法参数是:(this+要扩展的类名+变量名,该方法的参数)
如要在string类中添加一个验证是否是Email格式的扩展方法:
public static bool IsEmail(this string str)
{
return Regex.IsMatch(str, @"^\w+@\w+\.\w+$");
}
string a = "1321321@qq.com";
Console.WriteLine(a.IsEmail());
扩展方法只是看起来像类中的方法,其实不是,所以在扩展方法中也访问不到类中原来的私有成员
因为会扰乱系统方法,所以不建议使用,尽量不用
39、XML
XML是用一种格式化的方式来存储数据,可以用记事本打开
1)XML有且只有一个根元素
2)有开始标签就必须得有结束标签,且区分大小写
3)元素的属性值要用引号
读取XML文件,通过XDocument(需要添加命名空间)
读取整个XML文件: XDocument xd = XDocument.Load(XML文件路径)
循环遍历每个节点:
1)获取根节点 XElement xeRoot = xd.Root;(xeRoot.ToString()表示XML文档的内容)
2)递归遍历XML中的每个节点
遍历xeRoot下面的所有子节点:
public static void GetEle(XElement xe)
{
foreach (XElement ele in xe.Elements())
{
Console.WriteLine(ele.Name);
GetEle(ele);
}
}
GetEle(xeRoot);
例:读取XML数据
<CFX>
<MSG>
<交易码val="1000" />
<流水号val="100000000000000001" />
<金额val="1234567890.12" />
<付款机构val="1234" />
<付款单位账号val="12345678901234567890" />
<收款机构val="1234" />
<收款单位账号val="12345678901234567890" />
</MSG>
<MSG>
<交易码val="1000" />
<流水号val="100000000000000002" />
<金额val="1234567890.12" />
<付款机构val="1234" />
<付款单位账号val="12345678901234567890" />
<收款机构val="1234" />
<收款单位账号val="12345678901234567890" />
</MSG>
</CFX>
VS中读取XML的数据:
XDocument xd = XDocument.Load("ytbank.xml"); //加载XML文件
XElement xeRoot = xd.Root; //获取根节点
foreach (XElement item in xeRoot.Elements()) //遍历根节点,获取第一级子节点(MSG)
{
foreach (XElement itema in item.Elements()) //遍历第一级子节点,获取第二级子节点
{
Console.WriteLine("{0}:{1}", item.Element(itema.Name).Name, item.Element(itema.Name).Attribute("val").Value); //获取节点名和属性值
}
Console.WriteLine("==================================");
}
XML写入:
XDocument xdoc = new XDocument(); //创建一个XML对象
XElement xeleRoot = new XElement("Websites"); //创建一个根节点
xdoc.Add(xeleRoot); //将根节点添加到XML文件中
xeleRoot.SetElementValue("baidu", "http://www.baidu.com"); //添加一个节点以及内容
XElement xeleGoogle = new XElement("website"); //创建一个节点
xeleGoogle.SetAttributeValue("url", "http://www.google.com"); //添加属性和属性值
xeleGoogle.SetElementValue("name", "谷歌"); //为节点添加子节点
xeleGoogle.SetElementValue("age", "14");
xeleGoogle.SetElementValue("boss", "谢盖尔");
xeleRoot.Add(xeleGoogle); //将节点添加进根节点
xdoc.Save("website.xml"); //保存一个XML文档路径