C#面向对象编程
1、C# 泛型集合
List<T> 集合名=new List<T>(); //泛型类型参数"T",比如:string、int、dobule,泛型集合List<T>中只能有一个参数类型,"<T>"中T可以对集合中的元素进行类型约束
异同点 | List<T> | ArrayList |
不同点 | 增加元素时严格类型检查 | 可以增加任何类型 |
不需要装箱拆箱操作 | 需要装箱、拆箱操作 | |
相同点 | 通过索引访问集合的元素 | |
添加对象方法相同 | ||
通过索引删除元素 |
Dictionary<K,V> dt=new Dictionary<K,V>(); //Dictionary<K,V>声明中,其中"K"为占位符,具体定义用存储键"Key"的数据类型代替,"V",用元素的值"Value"的数据类型代替,这样在声明改集合时,声明了存储元素的键和值的数据类型,保证了数据类型的安全
类型 | HashTable | Dictionary<K,V> |
不同点 | 可添加装箱、读取时拆箱 | 对添加的元素具有类型约束 |
添加时装箱、读取时拆箱 | 不需要装箱、拆箱操作 | |
相同点 | 通过键值访问集合的元素 | |
添加对象方法相同 | ||
通过键值删除集合的元素 |
集合的概念:
数组是一组具有相同名称和类型的变量集合,使用数组可以存储大量数据,可以通过索引来检查数组中的元素,但是不能数组初始化后不能再改吧其大小,在程序中不能实现动态添加和删除数组元素,使数组的使用具有很多局限性,为了解决这个问题,.NET提供了各种集合对象如泛型集合:泛型集合Lsit<T>,Dictionary<K.V>。
System.Array类:
System.Array 类是所有数组的基类,位于System命名空间中,提供对数组中的值进行排序、反转数组、检索特定值等方法。
Array 类是一个抽象的基类,不能被实例化。
Array myarry=Array.CreateInstance(typeof(int),5);
这段代码创建了数组名为myarray,数据类型为int,长度为5的数组。typeof()用来获取int型的对象,Array.CreateInstance()方法中有两个参数,一个是数据类型,另一个是数组长度。
例1:创建一个字符串数组,初始化数据,然后输出数据;把数组反转后输出数据;对数组排序后输出数据。
//创建数组 string[] doges = new string[4]; //给数组元素赋值 doges[0] = "哈士奇"; doges[1] = "贵宾犬"; doges[2] = "金毛犬"; doges[3] = "巴哥犬"; Console.WriteLine("数组的元素个数为:{0}",doges.Length); Console.WriteLine("数组的维数为:{0}",doges.Rank); Console.WriteLine("------------数组元素------------"); //foreach遍历数组,获取元素数据 foreach(string item in doges) { Console.WriteLine(item); } //反转数据元素 Array.Reverse(doges); Console.WriteLine("------------反转后的数组元素------------"); //用for循环遍历数组,获取元素数据 for(int i=0;i<doges.Length;i++) { Console.WriteLine("第{0}个元素为{1}",i+1,doges[i]); } //对数组元素进行排序 Array.Sort(doges); Console.WriteLine("------------排序后的数组元素------------"); for(int i=0;i<doges.Length;i++) { Console.WriteLine("第{0}个元素为{1}", i + 1, doges[i]); }
运行图:
例2: 使用Array类的静态方法CreateInstance创建数组。
//创建数组 Array doges = Array.CreateInstance(typeof(string), 4); //给数组元素赋值 doges.SetValue("哈士奇", 0); doges.SetValue("贵宾犬", 1); doges.SetValue("金毛犬", 2); doges.SetValue("巴哥犬", 3); Console.WriteLine("数组的元素个数为:{0}", doges.Length); Console.WriteLine("数组的维数为:{0}\n", doges.Rank); Console.WriteLine("------------数组元素------------"); //foreach遍历数组,获取元素数据 foreach (string str in doges) { Console.WriteLine(str); } //反转数据元素 Array.Reverse(doges); Console.WriteLine("------------反转后的数组元素------------"); //用for循环遍历数组,获取元素数据 for (int i = 0; i < doges.Length; i++) { Console.WriteLine("第{0}个元素为{1}", i + 1, doges.GetValue(i)); } //对数组元素进行排序 Array.Sort(doges); Console.WriteLine("------------排序后的数组元素------------"); for (int i = 0; i < doges.Length; i++) { Console.WriteLine("第{0}个元素为{1}", i + 1, doges.GetValue(i)); }
运行图:
非泛型集合的不足之处:
性能和类型安全。
泛型的优点:
性能高:
使用泛型不需要进行类型转换,可以避免装箱和拆箱操作,从而提高性能。
类型安全:
泛型集合对其存对象进行了类型约束,不是定义时声明的类型,是无法存储到泛型集合中的,保证了数据的类型安全。
代码重用:
使用泛型类型可以最大限度的重用代码,保护类型的安全以及提高性能。
例3:使用List<T>泛型集合实现字符串数据动态的添加、修改、删除、检索操作。
//创建List<string>泛型集合对象,保存字符串数据 List<string> list = new List<string>(); //向集合中添加数据 list.Add("张雨琦"); list.Add("赵丽颖"); list.Add("杨幂"); list.Add("周杰伦"); list.Add("李易峰"); Console.WriteLine("集合的元素个数为{0}\n",list.Count); Console.WriteLine("------集合元素------"); //用foreach遍历集合中的元素 foreach(string item in list) { Console.WriteLine(item); } //修改集合元素 list[2] = "大杨幂"; Console.WriteLine("\n---修改之后的集合元素---"); //用for遍历集合中的元素 for(int i=0;i<list.Count;i++) { Console.WriteLine(list[i]); } //删除集合元素 list.Remove("李易峰");//根据对象来删除元素 list.RemoveAt(3);//根据索引删除元素 Console.WriteLine("\n---删除之后的集合元素---"); //用for遍历集合中的元素 for(int i=0;i<list.Count;i++) { Console.WriteLine(list[i]); } Console.ReadLine();
运行图:
2、C#类的继承
继承的概念与特征:
面向对象语言有三大特征:封装、继承、多态,继承是面向对象编程的一个重要特性,任何类都可以从另一个类中继承,也就是说,这个类拥有它继承的类的所有成员,在OOP 思想中,被继承的类称为父类(也称为基类)。注意,C#中的对象仅能直接派生于一个基类,当然基类也可以有自己的基类,类的继承一个重要作用是子类可重用父类的代码。这样就可以在一个地方集中维护一份代码,避免了很多的重复的代码。
简单的案例:
注释:新建一个控制台项目,在里面添加了3个类,Daddy是父类,son1、son2是子类。
public class Daddy_父类_ { public string Name { get; set; } public string Sex { get; set; } public int Age { get; set; } public void hello() { Console.WriteLine("早上好!"); } }
注释:这是父类中的代码,有姓名、性别、年龄属性,和一个打招呼的方法。
class son1_子类_ { public void hello1() { Console.WriteLine("陈爸比"); } }
class son2_子类_ { public void hello2() { Console.WriteLine("爹地"); } }
注释:这是两个子类中的代码,他们都各自拥有属于自己独具个性的打招呼方法,那他们想继承类中的代码该怎么做呢?
继承的语法格式:
class 子类的名字 : 父类的名字 { }
继承Daddy父类后的代码:
class son1_子类_:Daddy_父类_ { public void hello1() { Console.WriteLine("陈爸比"); } }
class son2_子类_:Daddy_父类_ { public void hello2() { Console.WriteLine("陈爸比"); } }
注释:这是继承父类之后的代码,那继承之后的区别在哪呢?又发生了什么改变?
son1_子类_ so1 = new son1_子类_(); so1.Name = "陈爸比1号"; so1.Sex = "男"; so1.Age = 19; so1.hello(); Console.WriteLine(); so1.hello1();
注释:继承之后,我们可以利用son1子类来调用父类中的属性跟打招呼的方法,有图有真相请看下面的图。
注释:我们用son1子类调用父类中的属性跟打招呼的方法,成功的创建了一个子类so1对象,并输出了父类中的打招呼方法的文本,且把自己独特的打招呼方法的文本输出了。
运行图:
sealed关键字:
C#提供了关键字sealed来防止发生继承,就是由它修饰的类或方法将不能被继承或重写,如果我们将类标记为sealed,编译器将不会允许我们从这个类派生。
sealed class Daddy_父类_ { public string Name { get; set; } public string Sex { get; set; } public int Age { get; set; } public void hello() { Console.WriteLine("早上好!"); }
注释:我们用sealed修饰了Daddy父类,那后面会发生什么呢?
注释:系统这个时候就会报错了。
Protected关键字:
公共项可以在任何地方直接访问,而私有项除了定义它的类之外,不能从其他对象进行访问,其他任何类都无法访问;作为主流的面向对象语言,提供了另外一个定义成员可访问性的关键字:protected.
base关键字调用父类成员:
C#中base关键字在继承中起到了非常重要的作用,base关键字代表父类,使用base关键字可以调用父类的构造方法、属性和方法。使用base关键字调用父类构造方法的语法如下:
子类构造方法:base(参考列表)
使用base关键字调用父类方法的语句如下:
base:父类方法();
案例:
public class 父类 { public 父类(string name,string sex)//构造方法 { this.Name = name; this.Sex = sex; } public string Name { get; set; } public string Sex { get; set; } public void hello()//父类方法 { Console.WriteLine("姓名:{0},性别:{1}",this.Name,this.Sex); } }
public class 子类:父类 { public int Age { get; set; } public 子类(string name,string sex,int age):base(name,sex)//通过base调用父类构造方法 { this.Age = age; } public void hh() { base.hello(); Console.WriteLine("年龄:{0}",this.Age); } }
static void Main(string[] args) { 子类 cbb = new 子类("陈爸比","男",19); cbb.hh(); Console.ReadLine(); }
运行图:
注释:
上面的代码中演示了如何使用base关键字调用父类构造方法,子类构造方法public 子类(string name,string sex,int age)使用base关键字调用父类构造方法,base关键字括号里面是父类构造方法的参数列表,这里只写名称,不写参数类型。代码中还演示了如何调用父类中的方法,base.hello()调用父类方法hello()。
this关键字调用本类成员:
C# this 关键字引用父类的当前实例。使用this关键字可以使代码编写更简单,不容易出错。在类的方法里输入 this 关键字,在后面输入“.”符号后,系统就会把本类所调用的非静态方法和变量都显示出来选择,提高了编码效率。
窗体继承:
继承窗体就是根据现有窗体的结构创建一个与其一样的新窗体,这种从现有窗体继承的过程称为可视化继承,创建继承窗体有两种方式一种是编程方式,一种是使用继承选择器创建继承窗体。
案例:
注释:先建两个窗体(命名一定要规范,这是案例所以写的中文,以便理解)
父窗体:
子窗体:
注释:子窗体里面内容是空的,如果子窗体的内容跟父窗体的内容差不多,那我们可不可以把父窗体中的内容拷贝到子窗体中呢?对,的确可以将父窗体的内容复制到子窗体中,那除了复制还有没有其他方法呢?对,窗体继承!
步骤:
注释:打开子窗体界面,鼠标右键查看代码
注释:子窗体中的代码
注释:把From改成父,继承父窗体中的内容,然后我们点一下启动。
注释:这个时候子窗体中已经有内容了,但如果我们想把登录按钮修改一下呢?
注释:子界面中的内容多了一把锁,然后属性栏中的功能也是灰色的,这个时候就表示无法编辑子窗体中的内容。
注释:点一下登录按钮,我们发现了锁不见了,然后属性栏功能也恢复正常了,这是为什么呢?
注释:我们发现了,账号的TextBox的Modifiers的属性是:private,登录按钮的Modifiers的属性是:Protected。
结论:如果我们想继承一个窗体,直接在该窗体中编辑代码,继承目标窗体,如果父窗体中的某个内容在子窗体中需要修改,那么需要修改内容的Modifiers的属性应该改成:Protected
访问修饰符设置访问权限:
访问修饰符 | 类内部 | 子类 | 其他类 |
Public | 可以 | 可以 | 可以 |
private | 可以 | 不可以 | 不可以 |
protected | 可以 | 可以 | 不可以 |
Internal | 可以 | 可以 | 可以 |
3、C#多态
注释:我们先建一个控制台应用程序,然后添加一个父类,两个子类,然后子类继承父类的方法,假如我们在子类中想修改一下继承过来的父类方法,那我们应该如何去修改父类中继承过来的方法呢?
注释:如果你想修改从父类中继承过来的方法,那么首先我们应该把父类中的方法写成虚方法,这样父类中的方法就允许我们去修改了,但是创建虚方法也是有语法格式的,在写父类方法的时候应该在访问修饰符后面添加 virtual 关键字。
注释:重写从父类中继承过来的方法时,应该在访问修饰符中添加重写的关键字:override ,这样我们就修改成功了。
注释:创建一个基类数组,添加基类子类对象,子类1对象,然后把我们输出会得到修改的结果。
注释:我们发现父类这个基类,我们根本不需要使用它创建的对象,它存在的意义就是供子类来继承,所以我们可以用抽象类来优化它,我们把父类改成抽象类,hello()方法改成抽象方法,代码如下:
注释:抽象类父类内添加一个hello()抽象方法,没有方法体,也不能实例化,其他子类代码不变,子类也是用override关键字来重写父类中抽象方法,Main主函数中基类就不能创建对象了,代码稍微修改如下:
运行图:
注释:由此可见,抽象类不能被实例化,我们选择使用虚方法实现多态还是抽象类抽象方法实现多态,取决于我们是否需要使用基类实例化的对象。
4、C#接口
接口的概念:
接口是一种用来定义程序的协议,它描述可属于任何类或结构的一组相关行为。接口可有方法、属性、事件和索引器或这四种成员的任何组合类型,但不能包含字段。
接口的特征:
接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员(说明:如类A继承接口B,那么A中必须实现B中定义的属性,方法等)。
·不能直接实例化接口
·接口可以包含事件、索引器、方法和属性
·接口不包含方法的实现
·类和接口可以从多个接口继承
·接口自身可以继承多个接口
定义接口的语法格式:
public interface I......//interface是接口关键字,接口名称必须是I开头 { void Fly();//接口不包含方法的实现 }
注释:在声明接口时除了Interface关键字和接口名称是必须的,其他都是可选项。另可使用new、public、protected、intenal和private等修饰符实现接口,但接口成员必须是公共的。
案例:
using System; using System.Collections.Generic; using System.Text; namespace _ { interface IPeople { /// <summary> /// 姓名 /// </summary> string Name{get;set;} /// <summary> /// 性别 /// </summary> string Sex{get;set;} } interface ITeacher:IPeople //继承公共接口 { /// <summary> /// 教学方法 /// </summary> void teach(); } interface IStudent:IPeople //继承公共接口 { /// <summary> /// 学习方法 /// </summary> void study(); } class Program:IPeople,ITeacher,IStudent//多接口继承 { string name = ""; string sex = ""; /// <summary> /// 姓名 /// </summary> public string Name { get { return name; } set { name = value; } } /// <summary> /// 性别 /// </summary> public string Sex { get { return sex; } set { sex = value; } } /// <summary> /// 教学方法 /// </summary> public void teach() { Console.WriteLine(Name + " " + Sex + " 教师"); } /// <summary> /// 学习方法 /// </summary> public void study() { Console.WriteLine(Name + " " + Sex + " 学生"); } static void Main(string[] args) { Program program = new Program(); //实例化类对象 ITeacher iteacher = program; //使用派生类对象实例化接口ITeacher iteacher.Name = "TM"; iteacher.Sex = "男"; iteacher.teach(); IStudent istudent = program; //使用派生类对象实例化接口IStudent istudent.Name = "C#"; istudent.Sex = "男"; istudent.study(); } } }
注释:上述的多重继承中说明了,在派生类中必须实现所继承的接口中的所有方法。OK,单一继承和多重继承都有了了解之后,是不是没有其他的需要了解的呢?试想一下,如果在一个类A继承自接口B和C,并且在B和C中包含具有相同签名的成员,那么在类中实现该成员将导致两个接口都使用该成员作为他们的实现,然而,如果两个接口成员实现不同的功能,那么将会导致一个接口的成员实现不正确或两个接口的成员实现都不正确,这个时候我们应该如何处理呢?我们可以显示的实现接口成员,即创建一个仅通过接口调用并且特定于该接口的类成员。
案例:
using System; using System.Collections.Generic; using System.Text; namespace _ { interface ImyInterface1 { /// <summary> /// 求和方法 /// </summary> /// <returns>加法运算的和</returns> int Add(); } interface ImyInterface2 { /// <summary> /// 求和方法 /// </summary> /// <returns>加法运算的和</returns> int Add(); } class myClass : ImyInterface1, ImyInterface2 //继承接口 { /// <summary> /// 求和方法 /// </summary> /// <returns>加法运算的和</returns> int ImyInterface1.Add() //显式接口成员实现 { int x = 3; int y = 5; return x + y; } /// <summary> /// 求和方法 /// </summary> /// <returns>加法运算的和</returns> int ImyInterface2.Add() //显式接口成员实现 { int x = 3; int y = 5; int z = 7; return x + y + z; } } class Program { static void Main(string[] args) { myClass myclass = new myClass(); //实例化接口继承类的对象 ImyInterface1 imyinterface1 = myclass; //使用接口继承类的对象实例化接口 Console.WriteLine(imyinterface1.Add()); //使用接口对象调用接口中方法 ImyInterface2 imyinterface2 = myclass; //使用接口继承类的对象实例化接口 Console.WriteLine(imyinterface2.Add()); //使用接口对象调用接口中方法 } } }
注释:上面的实例中在Myclass类中,通过两个显示接口成员的方法分别实现了两个接口中的Add方法,在实例化不同的接口后,调用相应的方法实现输出结果。
5、C#异常处理
三种异常处理语句:
- try......catch//捕获异常
- try......finally//清除异常
- try......catch......finally//处理所有异常
- throw //抛出异常
异常处理语法格式:
try { //放置可能出现异常的代码块 } catch(Exception ex)//放置可能出现的异常详细类型 参数名称 { //提示异常信息 //Console.WriteLine("出错了"); //Console.WriteLine(ex.Message); 简单的报错信息(供用户看) //Console.WriteLine(); 换行 //Console.WriteLine(ex.ToString{});详细的错误信息(供程序员看) } finally { //放置无论出错与否都会被执行的代码块 }
try注意事项:
- try 只有一个
- Finally 只有一个,或0个
- Catch 可以有0个(必须有一个Finally),可以有多个
注:try只有一个,Catch可以有多个,也可以不要Catch,Finally只有一个,也可以不要Finally(Catch&&Finally至少得有一个和try组合)
异常处理语句组合:
try......Catch || try......Finally || try......Catch......Finally || try......Catch......Catch
引发异常格式:
throw new 异常类(异常信息)
预定义异常类:
异常类 | 说明 |
Exception | 所有异常对象的基类 |
SystemException | 运行时产生的所有错误的基类 |
lndexOutOfRangeException | 当一个数组的下标超出范围时运行时引发 |
NullReferenceException | 当一个空对象被引用时运行时引发 |
ArgumentException | 所有参数异常的基类 |
lnvalidCastException | 类型的显式转换在运行时失败时,就会引发此异常 |
ArrayTypeMismatchException | 当存储一个数组时,如果由于被存储的元素的实际类型与数组的实际类型不兼容而导致存储失败,就会引发此异常 |
ArithmeticException | 算术运算期间异常发生的基类 |
DivideByZeroException | 试图除以零引发 |
OverflowException | 溢出时引发 |
FormatException | 参数格式无效时引发 |
自定义异常类格式:
-
声明异常格式
class 自定义的异常类名:Exception{}
-
引发异常格式
throw (自定义的异常类名);