1.索引器
string arrStr = "sddfdfgfh";
索引器的目的就是为了方便而已,可以在该类型的对象后面直接写[]访问该对象里面的成员
Console.WriteLine(arrStr[2]);
2.封装
方法的多个参数封装成一个参数(和三层中Model一样)
Model m = new Model();
m.Name = "小青";
m.Age = 17;
m.Sex = "女";
Do(m);
Console.ReadKey();
}
//static void Do(string name,int age,string sex)
//{
// Console.WriteLine(name+" "+age +" "+sex);
//}
static void Do(Model mod)
{
Console.WriteLine(mod.Name + " " + mod.Age + " " + mod.Sex);
}
3.继承
继承的好处:代码重用 多态
父类更抽象 ,范围更大,子类更具体
4.继承中的构造函数
构造函数的作用是帮助我们初始化当前这个类里面的成员
base()的作用是手动指定子类调用父类里面的哪个构造函数
this()的作用是调用当前类中的其他重载的构造函数
public class Son:Father
{
注意在这的构造函数中的参数在子类里面不做任何操作,直接通过base()传给父类的构造函数
public Son(string name, char bloodtype, double property)
:base(name,bloodtype,property)
{
}
5.用virtual实现多态
里氏转换原则(要父类给子类对象)
Person p = new MeiGuo();
用virtual实现多态时,父类里面的虚方法可以有方法体也可以没有,并且子类继承父类后可以不重写父类的虚方法。
6-1.静态成员和静态类
静态成员只存在一份,直接用类名访问
当多个成员具有相同的属性时,可以将这个属性写成静态的,直接用类名访问
当一个类经常要用到(工具类)时,可以写成静态的。
6-2 使用抽象方法实现多态
1.使用abstract关键字标记方法
2.抽象方法在父类中没有任何实现,所以抽象方法没有方法体
3.抽象成员必须写在抽象类中
4.抽象类不能实例化对象
5.抽象类中既可以有抽象成员也可有实例成员。
6.抽象成员不能是私有的。
7.子类继承抽象类后对于抽象类中的抽象成员子类必须要重新。(因为父类中没有默认实现;注意和虚方法的区别,虚方法中子类可以不重写父类中的方法),除非子类也是abstract
抽象类存在的意义:
1.抽象类不能实例化对象,只能被其他类继承
2.继承抽象类的子类必须要实现抽象类中所有的抽象成员(除非子类也是abstract)
3.抽象类就是为了重写—>多态(代码重用)
4.抽象类中既可以有抽象成员也可有实例成员。
7.认识接口
使用抽象类和虚方法已经可以实现多态了,为什么还要用接口实现多态?什么情况下需要用接口来实现多态?
1.当多个类型不能抽象出一个合理的父类时,但是又要对某些方法实现多态,此时可以考虑用接口实现多态。
即把公共的方法抽象到一个接口中,让不同的子类实现该接口。
2.因为接口可以实现多实现,所以解决了类的单继承问题(一个类只能有一个父类,但是接口可以多实现),
当一个类需要同时"继承"多个类的行为时,可以考虑用接口实现多继承。
//1.接口和抽象类很类似
//2.定义接口用关键字interface
//3.一般接口名以大写I开头
//4.接口中只能有方法(属性 事件 索引器最终都是方法,所以可以说接口中只能有方法)
//5.接口中的成员不能有任何访问修饰符,默认是public,如果手动添加就会报错。
//6.接口中的成员不能有任何实现,就像抽象方法一样
8.类型转换
1.如果将字节数小的转成字节数大的类型时直接赋值就可以也叫隐式转换
int n = 10;
double m = n;
2.如果将字节数大的转成字节数小的类型时,就要强制类型转换也叫显示转换
double n = 10;
int m =(int)n;
3.可以用Convert将任意类型转换成任意类型(***万能的转换方法***)
string msg = "123";
int m= Convert.ToInt32(msg);
4.把任意的字符串转换成数值类型用int.Parse()
string msg = "34546546";
int n= int.Parse(msg);
5.把任意的字符串转换成数值类型用int.Parse()如果转换失败就会报错,所有用另外一种转换方法
// string msg = "233";
// int n;
//bool b= int.TryParse(msg,out n);
//if (b)
//{
// n++;
// Console.WriteLine("转换成功");
//}
//else {
// Console.WriteLine("转换失败");
//}
Person p = new Chines();
怎么判断Chines就是Person的子类?
#region 父类与子类相互转换 as方法 推荐用as方法,因为这种只检查一次类型,效率比is高
//如果转换失败返回null,不会报错
//Chines b = p as Chines;
//if (b != null)
//{
// Console.WriteLine("成功");
//}
//else
//{
// Console.WriteLine("失败");
//}
#endregion
#region 父类与子类相互转换 is方法 这种需要两次类型检查,效率不高
//if (p is Person)
//{
// Chines c = (Chines)p;
// Console.WriteLine("转换成功");
//}
//else
//{
// Console.WriteLine("失败");
//}
//#endregion
9.异常捕获
try
{
//可能会发生异常的代码
}
catch(Exception ex)
{
//Exception 是用来捕获具体的异常ex.Massage()
//发生异常后执行的代码
throw ex; //向上抛出当前这个异常
}
finally
{
//不算是否发生异常,都会执行这里的代码
}
如果try或catch中有return语句,那么return语句是在最后执行的(即finally执行完之后才执行return)
10.参数修饰符
<一> params 可变参数修饰符
注意事项:
1.使用关键字params
2.参数的类型时一个数组
3.如果这个方法有多个参数,可变参数必须放到最后一个。如Add(string str,char c,params int[] nums)
<二>out
int m = 1000;//赋值没意义
int m;
out参数在传递之前没有必要给变量赋值,就算给赋值也没有任何意义,
在方法中使用变量之前必须重新给变量赋值才能使用。out参数无法将变量的值从方法外面传递进来(和ref不一样)。
out参数的主要作用就是当方法中有多个返回值时可以通过out获得其他的返回值
TextOut(out m);
<三>ref
ref参数和out参数很像,但是ref参数在传值的时候必须给变量赋值,out参数在传值之前不用赋值
int x = 10;
M1(ref x);
11.简单工厂:
下面就是一个简单的工厂模式:返回值是父类对象,在里面动态返回子类类型
<returns>返回父类类型对象</returns>
private static JiSuanQi GetJiSuanQiObject(int num1, string czf, int num2)
{
JiSuanQi jsq = null;
switch (czf)
{
case "+": jsq = new Add(num1, num2); break;
case "-": jsq = new Sub(num1, num2); break;
case "*": jsq = new Cheng(num1, num2); break;
case "/": jsq = new Chu(num1, num2); break;
default: Console.WriteLine("请输入正确操作符");
break;
}
return jsq;
}
12.判断两个对象是否是同一个对象
既然Equals和==都不能判断两个对象是否是同一个对象,那么到底用什么判断呢?用object.ReferenceEquals(obj1,obj2)标准的判断方法
string s1 = new string(new char[] { 'a', 'b', 'c' });
string s2 = new string(new char[] { 'a', 'b', 'c' });
Console.WriteLine(object.ReferenceEquals(s1,s2));
13.字符串的特性
<一>字符串常量的暂存池特性
针对字符串常量的暂存池特性,对于相同的字符串常量,每次使用的时候并不会重新创建一个内存来存储,
而是在第一次创建的时候将字符串作为键,将字符串的地址作为值来存储,下次再用到的时候先去键值对中查找,
如果有则直接返回上次创建字符串的地址。这个特性依赖于字符串的另外一个特性才能存在:字符串的不可变性。如:
string s1="abc";
string s2="abc";
s1和s2就是同一个对象
<二>字符串的不可变性
14.拼接字符串
stringBuilder每次修改都是修改同一块内存里面的值,不会重复创建大量对象,也不会产生垃圾内存,
所以大大提高了字符串拼接的效率,建议大量字符串拼接的时候首选StringBuilder。
注意StringBuilder仅仅是用来拼接字符串的,拼接完成后还要转成String类型(ToString)
StringBuilder sb = new StringBuilder();
//用时:00:00:00.0118262
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
string[] lines= File.ReadAllLines("text.txt",Encoding.Default);
for (int i = 0; i < lines.Length; i++)
{
sb.Append(lines[i]);
}
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed);
//将StringBuilder转成String类型
string str = sb.ToString();
//用String拼接会产生很多对象以及很对内存垃圾,用起来效率很低
string msg = string.Empty;
//监视时间
//00:00:00.0218741
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
string[] lines = File.ReadAllLines("text.txt", Encoding.Default);
for (int i = 0; i < lines.Length; i++)
{
msg = msg + lines[i];
}
stopwatch.Stop();
Console.WriteLine(stopwatch.Elapsed);
Console.WriteLine("ok");
15.垃圾回收
值类型是不需要垃圾回收的,当执行完毕出栈后就立刻被释放,垃圾回收只回收堆中的内存资源。
16.找出字符串中字母出现的次数(不区分大小写)
声明键值对集合 键是字母 值是出现的次数
Dictionary<char, int> dic = new Dictionary<char, int>();
判断是否是字母用:
if(char.IsLetter(msg[i]))
{
17.发生装箱和拆箱的前提条件是:两者必须是父子关系(继承)才有可能发生装/拆箱。
double d=90;
object o=d; //装箱了
int n=(int)o; //拆箱了。但是这里会报错误,因为装箱的时候是double类型,拆箱是int类型,类型不一致所以报错。(也就是说装箱和拆箱的类型必须一致)
Console.Write(n);
18.Path类操作文件路劲
//Path类操作文件路径,文件路径->字符串
string path = @"C:\Users\XiaoHai\Desktop\C#基础.txt";
//获得文件名带后缀(C#基础.txt)
string fileName = Path.GetFileName(path);
//获得文件名不带后缀(C#基础)
string fileName1 = Path.GetFileNameWithoutExtension(path);
//获得文件后缀名
string ext= Path.GetExtension(path);
//截取文件路径部分,不带文件名
string dire= Path.GetDirectoryName(path);
19操作文件夹的类:
//非常常用的两个:
//Directory.GetDirectories();//获得一个目录下的所有子目录
//Directory.GetFiles();//得到一个目录下的文件
Directory.Exists(@"c:\xxx\asc\1.txt"); 验证指定的目录是否存在
Directory.Delete(@"c:\2.txt");//只能删除空文件夹
Directory.Delete(@"c:\2.txt",true);//删除该目录下所有的子目录
20操作文件的静态类File:
1.判断一个文件是否存在
//bool b= File.Exists(@"C:\as\a\1.txt");
2.拷贝一个文件
//File.Copy(@"C:\as\a\1.txt",@"d:\2.txt");
// File.Move();//移动 剪切文件
3.创建一个文件
//File.Create(@"C:\as\a\1.txt");
4.删除一个文件
//文件的删除,即便文件不存在也不会报错
File.Delete(@"C:\as\a\1.txt");
5.读取一个文件,写入
string txt = File.ReadAllText(@"d:\1.txt");
string[] line = File.ReadAllLines(@"d:\4.txt");
6.把一个字符串转换成一个byte[]
string msg = "哈喽你好世界Hello World!!";
byte[] bytes = System.Text.Encoding.UTF8.GetBytes(msg);
7.把一个byte[]转换成一个字符串
//System.Text.Encoding.UTF8.GetString();
21 //对于读取大文本文件一般使用StreamRead类和StreamWrite类
//using(StreamWriter sw = new StreamWriter("1.txt",false,Encoding.UTF8))
//{
// //读写
// for (int i = 0; i < 100; i++)
// {
// sw.WriteLine(i+"======="+System.DateTime.Now.ToString());
// }
//}
//Console.WriteLine("ok");
//Console.ReadKey();
22 序列化
对象序列化就是将一个对象以另一种格式存储,更方便数据交换(也叫格式化)
序列化的目的是为了把数据以另外一种格式来表示,
方便存储与交换数据,所以序列化只序列化那些存储数据的成员,一般都是属性,不会序列化方法
//二进制序列化的要求:
//1.要序列化的类前面必须用[Serializable]标示为可序列化
//2.序列化会把属性对应的字段给序列化到文件中,所以最好类型中不要使用自动属性,而要自己写字段和属性。
//3.当序列化一个对象的时候,这个对象本身以及所有父类都必须标记为可序列化的。
//4.类型中所有属性与字段的类型必须也标记为可序列化的,但是接口和方法有关,所以接口不需要标记。
二进制序列化
//BinaryFormatter bfSerializer = new BinaryFormatter();
//using (FileStream fswrite = File.OpenWrite("list.bin"))
//{
// bfSerializer.Serialize(fswrite, list);
// // fswrite.Deserialize();//反序列化
//}
////Console.WriteLine("ok");
////Console.ReadKey();
使用JavaScript序列化(结果是以Json格式输出)
JavaScriptSerializer jsSerializer = new JavaScriptSerializer();
string str = jsSerializer.Serialize(list);
jsSerializer.Deserialize()//反序列化
将str写入到文件里面
File.WriteAllText("list.txt", str);
Console.WriteLine(str);
Console.ReadKey();
反序列化:
//第一步创建序列化器
BinaryFormatter bfSerializer = new BinaryFormatter();
using (FileStream fsread=File.OpenRead("person.bin"))
{
//反序列化的时候,需要创建原来被序列化的类型的对象,
//所以反序列化的时候需要引用被序列化的类型的对象。
object obj= bfSerializer.Deserialize(fsread);
Person p = obj as Person;
Console.WriteLine(p.Age+"======"+p.Name+"======="+p.Email);
}
Console.WriteLine("ok");
Console.ReadKey();
23正则表达式
//^ 表示的是字符串的开头
//^abc[0-9]def:表示的是必须以abc2def开头,第四个必须是一个0-9的数字,如abc0def abc9def abc4def等都对,但是像abc9d abc9de abcdef都是错的
//^abc:表示必须以abc开头,acb ab a都是错的,必须以abc开头
//$ 表示的是字符串的结尾
//^abc.*efg$:表示的必须以abc开头 以efg结尾,中间什么都可以。
//. 匹配除了\n之外的任何单个字符。
//如:a.b可以匹配a9b avb a&b a b等等
//[] 字符的筛选.
//如:a[dfg]b表示a和b之间只能出现dfg之间任何一个(切记是出现一个),不能有其他字符出现
//a[0-9]b:表示a和b之间出现0-9任何一个数字
//a[0-9a-zA-Z]b:表示a和b之间出现0-9任何一个数字或a-z任何一个字母(切记只能出现一个)
//a[^0-9]b:表示a和b之间只能出现除了0-9之外的任意单个字符
//a[^0-9a-z]b:表示a和b之间只能出现除了0-9a-z之外的任意单个字符
//| 表示或的意 思
//如z|food:只能匹配z或food两个.
//(z|f)ood:匹配zood或food
//() 改变运算优先级和提取组
//* 限定符。表示*前面的表达式出现0次或多次
//a.*b:表示.可以出现0次或多次.像ab asdb adsfdgfdsb都是正确的
//+ 限定符,表示前面的表达式至少出现一次(一次或多次)
//a.+b:表示.至少得出现一次。ab就是错的,像agb asdfghhb就是对的
//a[0-9]+b:表示a和b之间至少出现一个0-9的数字,像a0b a9897799343454b a09b都是对的,而axb ab是错的
//? 限定符,表示前面的表达式必须出现0次或一次。
//a.?b:表示.必须出现一次或0次。像ab axb a_b就是对的,而adsfb错误
//? 另外一个作用就是"终止贪婪模式".正则表达式默认就是贪婪模式
//{n} 限定符,表示前面的表达式必须出现n次
//a[0-9]{3}b:表示a和b之间必须出现3次0-9的任意一个数字(注意这里并不是同一个数字,任意一个数字只要出现3次即可),如:a123b a111b a097b都是对的。
//{n,} 限定符,表示前面的表达式至少出现n次,至多不限
//1[a-z]{2,}2:表示1和2之间至少得出现两次a-z的字符,至多不限,像1qw2 1ww2 1assgfhgkhhg2都对
//{n,m} 限定符,表示前面的表达式至少出现n次,至多m次
//1[a-z]{2,5}2:表示1和2之间至少得出现2次a-z的字符,至多出现5次,像1qw2 1ww2 1dfg2 1ggg2 1cvbg2 1ytrec2都对
//\d等价[0-9] 如a[0-9]b == a\db
//\D等价[^0-9]
//\s 表示所有那些空白符,不可见字符。如a\s*b表示的是a和b之间可以出现0次或多次空白符,像a加换行b ab a b都是对的,只要ab之间是不可见的
//\S 表示的是除了\s意外的所有字符
//\w 表示的是[0-9a-zA-Z_]
//\W 表示的是除了\w之外的所有字符
//\b 表示单词的边界(也叫断言,只判断不匹配)
//表示a和b之间可以有任意一个字符的写法:
//a[\s\S]b
//a[\d\D]b
//a[\w\W]b
//正则表达式常 用的函数有
//Regex.IsMatch();//判断是否匹配
//Regex.Match();//提取某个(一个)匹配
// Regex.Matches();//提取所有匹配
//Regex.Split();//分割
//Regex.Replace();//替换
//正则表达式是用来操作字符串的
//正则表达式是描述字符串特征的表达式
贪婪模式:正则表达式会尽可能多的找到匹配,这就是贪婪模式
//string msg = "1111.111. 11111. 11";
//结果是1
//Match match = Regex.Match(msg, ".");
//结果是1111.111. 11111. 11
//Match match = Regex.Match(msg, ".+");
//终止贪婪模式用?,?一般出现在一些限定符后面表示终止贪婪模式.
//终止贪婪模式:尽可能少的匹配,只匹配一个
//结果是1
//Match match = Regex.Match(msg, ".+?");
//Console.WriteLine(match.Value);
string msg = "大家好,我是史彬海。我是S。我是H。我是E。";
//下面这个正则匹配到是:我是史彬海。我是S。我是H。我是E。 不是我们想要的结果,为什么会这样?
//MatchCollection names= Regex.Matches(msg,@"我是(.+)。");
//当我们希望找到多个匹配的时候,结果却只找到了一个很大的匹配值,这种情况一般都市贪婪模式的问题,试着终止贪婪模式。
MatchCollection names= Regex.Matches(msg,@"我是(.+?)。");
foreach (Match item in names)
{
Console.WriteLine(item.Groups[1].Value);
}
正则表达式替换:
string msg = "我的手机号是18309291944他的手机号是12345678444.";
msg=Regex.Replace(msg,@"(\d{3})\d{4}(\d{4})","$1****$2");
Console.WriteLine(msg);
//将http://baidu.com.cn替换成<a href="http://baidu.com.cn">http://baidu.com.cn</a>
string msg = "百度的网址是:http://baidu.com.cn";
msg = Regex.Replace(msg, @"([a-zA-Z0-9]+://[0-9a-zA-Z.\?%&]+)", @"<a href=""$1"">$1</a>");
Console.WriteLine(msg);
//将单引号替换成中括号【】
//思路:先找到''里面的内容再替换
string msg = "hellow 'welcome' to be 'aut' iful 'China'";
msg= Regex.Replace(msg,@"'(.+?)'","【$1】");
Console.WriteLine(msg);
24 委托
委托是一种数据类型,像类一样(可以声明委托类型变量),使用委托时先找到方法中通用的块,通用的代码不变,
把经常要变化的代码提取封装然后再赋值给一个委托类型的变量,这样就可以根据用户给的是什么样的方法参数就执行什么样的方法结果(和多态那块差不多).
定义了一个委托类型,注意定义委托时是否有返回值和参数,关键是看委托里面存放的那个方法有没有返回值和参数,要保持一致。
委托的作用:占位。在不知道将来要执行的方法的具体代码时,可以先用一个委托变量来代替方法调用(委托的返回值,参数列表要确定),在实际调用之前需要为委托赋值,否则为null
系统自带的泛型委托:1.Action //无参数无返回值 Action<int> 参数是int类型无返回值,只要是Action都是无返回值的
2.Fun<int> //表示没有参数 返回值是int;Fun<int,string> 表示的是参数是int,返回值是string。只要Fun<>里面有多个类型,最后一个永远是返回值,前面的都是参数类型
25 拉姆达表达式(lambda),一般在使用委托的时候用,因为只有委托才把方法当做参数来用。
(参数)=>{方法体}
匿名方法:委托变量=delegate(int x){ //方法体};
26.事件
当在声明委托变量的前面加了event关键词后,则委托变量就不是委托变量了而是立刻变成事件,委托可以直接给委托变量赋值,
但是事件不能直接赋值。事件只能使用+=或-=来赋值,这样就避免了通过=将之前的注册事件都覆盖掉。
事件的作用:事件的作用与委托一样,只是功能上比委托变量有了更多的限制。(比如:1.只能通过+=或-=来绑定方法(也叫事件处理程序)2.只能在类的内部触发事件。)
27.委托和事件的区别:
委托和事件没有可比性,因为委托是数据类型,事件是对象(可以理解为对委托变量的封装),事件的内部是用委托实现的。
对于事件来讲,外部只能“注册自己+=,注销自己-=”,外界不可以注销其他的注册者,外界不可以主动触发事件,因此如果用Delegate就没法进行上面的控制,因此诞生了事件这种语法。
事件只能+= -= 不能=,更不能再外部触发事件。
28.程序集
并不是所有的exe或dll都是程序集,只有在.NET里面编译的exe或dll文件叫程序集
程序集包含:类型元数据(描述在代码中定义的每一类型和成员,二进制形式),程序集元数据(程序集清单 版本号 名称等),IL代码,资源文件。每个程序集都有自己的名称 版本信息等信息,这些信息可以通过AssemblyInfo.cs文件来自己定义
29.反射
反射无处不在,我们天天在使用。比如vs的只能提示,就是通过反射获取到类的属性,方法等,还有反编译工具也是通过反射实现的。
反射就是动态获取程序集的元数据(提供程序集的类型信息)的功能
反射就是直接通过.dll来创建对象,调用成员。
反射:通过动态获取程序集,并获取其中的类型元数据,然后访问该类型的过程。
//反射中一个非常重要的类型就是Type
//1.如何获取Type类型的对象?
//例如获取Person类型的Type对象,Type对象中就是存放了一些关于某个类的所有信息的内容。【某个类型的Type对象就是该类型“类型元数据”】
//当没有对象的时候使用下面的方法获取某个类型的Type对象
//Type type=typeof(Person);
//当有对象的时候就用GetType()获取该类型的Type对象
Person p = new Person();
Type type= p.GetType();
//如何获取某个类里面的方法??
MethodInfo[] methods=type.GetMethods();
通过Type对象的GetMethods()可以获取指定类型的所有的方法,其中包括编译器自动生成的方法以及
从父类中继承来的方法,但不包含private方法,要获得私有方法需要使用其他方式。
//如何获取某个类的所有属性?
PropertyInfo[] property= type.GetProperties();
//如何获取某个类型里面的所有字段?因为字段一般都是私有的,所以获取到的是空的。
FieldInfo[] fields= type.GetFields();
//在添加对另一个程序集的引用时,不用右键添加引用,直接动态添加一个程序集
Assembly assembly = Assembly.LoadFile(@"要添加的程序集路劲");
//获取刚刚添加的程序集里面的所有的类型(不管访问修饰符是什么)
//Type[] types= assembly.GetTypes();
//只获取那些public的类型
Type[] type = assembly.GetExportedTypes();