深入.NET框架和C#编程
一.深入.NET框架
1.1NET框架的体系结构
NET框架结构:
.NET框架运行在操作系统之上,是.NET最基础的框架,主要包含公共语言运行时(CLR)和框架类库(FCL)。
(1)公共语言运行时(CLR):
它是所有.NET应用程序运行时的环境,是所有.NET应用程序都要使用的编程基础,它如同一个支持.NET应用程序运行和开发的虚拟机。
MSIL:俗称中间语言,可被平台(操作系统)识别,从而进行编译
CLR包含两个组成部分:
- CLS(公共语言规范)
- CTS(通用类型系统)
CLS:编程语言区别不仅在于类型,语法或者语言规范都有很大的区别,CLS限制了不同点引发的互操性问题。
CTS:用来解决不同语言数据类型的不同的问题;
(2)框架类库(FCL)
就是指平常要引用的那些命名空间,.NET框架提供了非常丰富实用的类库。
1.2.面向对象回顾
1.2.1类和对象
类和对象有着本质的区别,类定义了一组概念的模型(抽象的),而对象是真正的实体。
①由对象归纳为类,是归纳对象共性的过程。
②在类的基础上,将状态和行为实体化为对象的过程称为实例化。
属性:
- 只写属性:只包含set访问器
- 只读属性:只包含get访问器
- 读写属性:同时包含set访问器和get访问器
private int _id;
public int ID
{
get {return_id;}
set {_id=value;}
}
1.2.2封装
封装:使内部结构私有化
封装的魅力:
- 保证数据的安全性
- 提供清晰的对外接口
- 类内部实现可以任意修改,不影响其他类
将字段封装为属性是封装的一种方式,类的私有方法也是一种封装。
1.2.3类图
在软件开发中,软件的规模一般都很大,一行行阅读是非常困难的,在面向对象编程中,我们经常使用类图来解决这个问题。类图将类的属性和行为以图的行为展示出来,使读者不用阅读大量的代码就可以知道类的功能及类之间的关系。
私有成员: “-”,公有成员:“+”;也有私有成员在右下角有“锁”标志
类图是表示类的结构及类与类之间关系的图表。
二.深入C#数据类型
1.值类型和引用类型
1.值类型
值类型源于System.ValueType命名空间,值类型数据所在的区域称为 “栈”,只要修改它,就会在它的内存区域内保存这个值,值类型主要包括的基本数据类型 :
int, long, float, double, char, bool, bool,enum, struct
2.引用类型
引用类型源于System.Object命名空间,在C#中引用类型主要包括 :
类(Class),数组,集合,接口
2.1 结构
1,结构的语法:
访问修饰符 struct 结构名
{
//结构体
}
2,结构的定义有以下的特点:
- 结构中可以有字段,也可以有方法;
- 定义时,结构中的字段不能被赋初始值;
3,在使用结构时要注意以下几个方面:
- 可以不用new,直接定义结构的对象即可;
- 声明结构后,必须给结构的成员赋初值;
1.1装箱和拆箱
我们说数据类型按照储存方式可以分为值类型和引用类型,两者可以互相转换。
- 装箱:将值类型转换为引用类型的过程叫装箱。
- 拆箱:将引用类型转换为值类型叫拆箱。
static void Main(string [] args)
{
int i=123;
//装箱
object o=i;
//拆箱
int j=(int)o;
}
2.参数传递
- 值传递:在方法中对参数值的更改在调用后不能保留;
- 引用传递:地址不变,可以保留对参数值的更改
- 使用ref传递:可以保留对参数值的更改;
总结:
- 使用值方式(不用ref修饰)传递值类型参数时,参数在方法中的修改不会保留;
- 使用值方式(不用ref修饰)传递引用类型参数时,参数在方法中的修改会保留;
- 使用引用方式(用ref修饰)传递值类型或引用类型参数时,参数在方法中的修改都会保留;
三.使用集合组织相关数据
1.非泛型
1.1.ArrayList
ArrayList非常类似于数组,也有人称它为数组列表,ArrayList可以动态维护,一般数组的长度都是固定的,而ArrayList的容量可以根据需要自动扩充,它的索引会根据程序的扩展而重新分配调整.
ArrayList类属于System.Collections命名空间,这个命名空间包含接口和类;
ArrayList常用的方法和属性:
属性名称 | 说明 | |
---|---|---|
Count | 获取ArrayList中实际包含的元素数 | |
返回值类型 | 方法名称 | 说明 |
int | Add(Object value) | 将对象添加到ArrayList的结尾处 |
void | RemoveAt(int index) | 移除ArrayList指定的索引元素 |
void | Remove(Object value) | 从ArrayList中移除特定元素 |
void | Clear() | 移除所有元素 |
遍历ArrayList中的元素
ArrayList array = new ArrayList();
foreach(Object item in array)
{
Console.WirteLine(item);
}
1.2.Hashtable
C#提供了一种称为Hashtable的数据结构,通常称为哈希表,Hashtable的结构是通过键(Key)和值(Value)来组织的
Hashtable也属于System.Collections命名空间,它的每一个元素都是一个键/值对;
Hashtable的常用属性和方法:
属性名称 | 说明 | |
---|---|---|
Count | 获取包含Hashtable中键/值对的数目 | |
Keys | 获取包含在Hashtable中键的集合 | |
Values | 获取包含在Hashtable中值的集合 | |
返回值类型 | 方法名称 | 说明 |
void | Add(Object key,Object value) | 带有指定键和值的元素添加到Hashtable |
void | Remove(Object key) | 从Hashtable中移除带有特定键的元素 |
void | Clear() | 从Hashtable中移除所有元素 |
遍历Hashtable中的元素
Hashtable<K,V> array = new Hashtable<K,V>();
//遍历所有的key
foreach(Object item in array.Keys)
{
Console.WirteLine(item);
}
//遍历所有的Value
foreach(Object item in array.Values)
{
Console.WirteLine(item);
}
3.泛型和泛型集合
泛型引入了一个概念:类型参数。通过使用类型参数(T),减少了运行时强制转换或装箱操作的风险,比较经典的泛型集合是List和Dictionary<K,V>。
3.1. List<>
List<T> 对象名=new List<T>();
""中的T可以对集合中的元素类型进行约束,T表明集合中管理的元素类型;
List的使用方法和ArrayList类似,只是List无须类型转换;
List与ArrayList的区别:
List | ArrayList | |
---|---|---|
不同点 | 对所保存元素进行类型约束 | 可以增加任何类型 |
添加/读取值类型元素无须拆箱和装箱 | 添加/读取值类型元素需要拆箱和装箱 | |
相同点 | 通过索引访问集合中的元素 | |
添加元素方法相同 | ||
删除元素方法相同 |
3.2.Dictionary<K,V>
Dictionary<K,V> 对象名=new Dictionary<K,V>();
说明:<K,V>中K表示集合中Key的类型,V表示Value的类型
例如:
Dictionary<string,SE> eng=new Dictionary<string,SE>();
eng集合的Key类型是string型,Value是SE类型;
Dictionary<K,V>和Hashtable的对比:
Dictionary<K,V> | Hashtable | |
---|---|---|
不同点 | 对保存元素进行类型约束 | 可以增加任何类型 |
添加/读取值类型元素无须拆箱和装箱 | 添加/读取值类型元素需要拆箱和装箱 | |
相同点 | 通过Key获取Value | |
添加元素方法相同 | ||
删除元素方法相同 | ||
遍历方法相同 |
3.泛型类
使用泛型类,可以封装不是特定于具体数据类型的操作。定义泛型类的过程,与定义一个类相似,不同之处在于,尖括号里定义了类型参数;
public class 类名<T>
{
//.....
}
T指类型参数,代表具体的数据类型,可以是类类型,也可以是基本数据类型。
泛型有以下优点:
-
性能高
-
类型安全
-
实现代码的重用
-
可以选择自己想用的数据类型
四.深入类的方法
1.构造函数
构造函数的用处:
用于new对象。兼职:用于给成员变量赋值
构造函数具有以下特点:
- 方法名和类名相同
- 没有返回值类型
1.1无参构造函数
在默认的情况下,系统会给类分配一个隐式无参构造函数,并且没有方法体;
访问修饰符 类名()
{
//方法体
}
也可以自定义无参构造函数(显式);
1.2带参构造函数(显式)
一般来讲,给方法设置参数可以调整方法的行为,使方法多样化。同样,构造函数也可以接收参数,用这些参数给属性赋值。语法如下:
访问修饰符 类名(参数列表)
{
//方法体
}
在调用带参函数时需要注意的几点:
- 参数的个数要对应
- 参数的数据类型要一一对应;
2.方法重载(多态)
在面向对象的语言中,允许我们在同一个类中定义多个方法名相同,参数列表(参数个数,参数类型)不同的方法,称为方法重载;
方法重载的特点:
- 在同一个类中;
- 方法名相同;
- 方法参数类型不同或者参数个数不同;
class PM
{
public void Show()
{
Console.WriteLine(" ");
}
public void Show(string name)
{
Console.WriteLine("名字是:{0}"+name)
}
}
3,对象交互
在面向对象的世界里,一切皆为对象,对象与对象相互独立,但在一定外力的作用下,对象开始共同努力;
每个类都有自己的特性和功能,我们把它封装为属性和方法。对象之间通过属性和方法进行交互,可以认为方法的参数及方法的返回值都是对象间相互传递的信息。
六,初识继承和多态
1.继承的概述
1.1什么是继承
简单来说,继承就是子承父
生活中最简单的继承关系:
汽车(父类,基类)
卡车和公共汽车(子类,派生类)
1.2base关键字和protected修饰符
1.2.1base关键字
在子类中使用,可以调用父类的属性,还可以用base关键字调用父类的方法及父类的构造函数。
public class Employee
{
public string Name{get; set;}
}
public class PM:Employee
{
Console.WriteLine("名字是",base.Name)
}
1.2.2访问修饰符的限制:
修饰符 | 类内部 | 子类 | 其他类 |
---|---|---|---|
public | 可以 | 可以 | 可以 |
private | 可以 | 不可以 | 不可以 |
protected | 可以 | 可以 | 不可以 |
小结:子类可以调用父类的所有非private成员
1.3继承中构造函数
注意:每一个子类的构造函数一定会调用父类的构造函数
- 没有明确指明,或使用:base()则调用父类的无参构造函数
- 若使用:base(实参),则明确指明父类中的某个构造函数(根据参数个数以及类型)
2.继承的使用
2.1 继承的特性
注意:当类被sealed修饰时,该类为密封类,不允许被继承
2.1.1.继承的传递性:
正如人类有爸爸,也有爷爷
汽车有子类卡车,而卡车又可以有子类小型卡车,大型卡车
可以说汽车也是小型卡车,大型卡车的父类
此所谓继承的传递性
2.1.2继承的单根性:
假设某个人既有程序员的天赋,又有音乐家的气质,但是在C#中是不能明确规定的,一个子类不能提示继承多个类。
2.2 is a 的应用
if(emp is SE)
{
Console.WriteLine(((SE)emp).Sahi());
}
if(emp is PM)
{
Console.WriteLine(((PM)emp).Sahi());
}
在上列代码中使用 is 关键字,这个关键字用来判断对象是否属于给定的类型,如果属于则返回true,否则返回false;
2.3继承的价值
①继承模拟了现实世界的关系,OOP中强调一切皆为对象,这符合我们面向对象编程的思考方向。
②继承实现了代码的重用,合理的使用继承,会使代码更加简洁。
③继承使得程序结构清晰,子类和父类的层次结构清晰。
3.继承的优点
- 提高代码的重用性
- 形似父类,异于父类,保持个性
4.继承的缺点
- 继承编程侵入式
- 增加耦合
3.多态
3.1 虚方法
我们可以在父类和子类中定义如下的方法:
public virtual string Sayi() //在父类中使用关键字virtual
{
//省略方法体
}
public override string Sayi() //在子类使用时是有关键字override
{
//省略方法体
}
像这种virtual关键字修饰的方法,称为虚方法,虚方法有方法体,语法如下:
访问修饰符 virtual 返回值类型 方法名()//父类
{
//方法体
}
访问修饰符 override 返回值类型 方法名()//子类
{
//方法体
}
父类中定义的虚方法并非必须被子类重写,在父类中可以给出虚方法的默认实现。如果子类不重写父类的虚方法,依然执行父类的默认实现,如果子类重写了父类的虚方法,执行子类重写后的方法。
虚方法的特点
前提:两个类有继承关系
- 不同类
- 相同的方法名
- 相同的参数(个数,类型)
- 子类可以不重写父类方法
子类调用父类:
根据方法名与参数调用,而不根据访问修饰符和返回值类型
七,深入理解多态
1.里氏替换和多态应用
1.1 里氏替换概述
子类对象可以赋给父类对象,也可以说子类可以替换父类,并且出现在父类能够出现的地方,且程序的行为不会发生变化,但是反过来,父类对象是不能够替换子类对象的,这种特性被称为:“里氏替换原则”;
“里氏替换原则”:是软件设计应该遵守的重要原则之一,有了里氏替换原则,才使继承复用成为可能。
小结:引用父类的地方,都可以透明的使用子类对象
1.2 is 和 as操作符的使用
is操作符用于检查对象和指定的类型是否兼容,而as操作符用于对象之间的类型转换:
if(emp[i] is SE)
{
PM pm=emp[i] as PM;
}
先用 is 关键字判断员工类,再用 as 关键字将此员工对象转换为对应的的类。
里氏替换原则提出子类对象可以替换父类对象,在编写时以父类类型作为形式参数的方法,在实际调用时传入子类对象。
2.抽象类和抽象方法
2.1为什么使用抽象类和抽象方法
抽象方法是一个没有实现的方法,通过在定义方法时增加关键字 abstract 可以声明抽象方法,其语法如下:
访问修饰符 abstract 返回类型 方法名();
注意:抽象方法没有闭合的大括号,而是直接跟一个分号 “:”也就是说,它没有包括方法执行逻辑的方法体。
1.含有抽象方法的类必然是抽象类,同样,我们用关键字 abstract 来定义一个抽象类,语法如下:
访问修饰符 abstract class 类名{}
2.2 抽象类和抽象方法的应用
1.如何实现抽象方法
从一个抽象父类派生一个子类时,子类将继承父类的所有特征,包括它未实现的抽象方法。抽象方法必须在子类中实现,除非它的子类也是抽象类,与子类重写父类的虚方法一样,在子类中也是使用 “override”关键字来重写抽象方法。
访问修饰符 override 返回类型 方法名();
举例一个示例:
public abstract class SE
{
public abstract void Run(); //抽象方法不能有方法体
}
public void PM:SE
{
public override void Run(){} //继承SE的Run方法
}
2.3抽象方法的特点:
- 在不同的类中
- 有相同的方法名
- 相同的参数
- 没有方法体
- 抽象方法只能位于抽象类中
- 父类里面的抽象方法子类必须实现,用关键字 override(除非子类也是抽象类)
2.4抽象类特点
- 不能new对象
- 不能用sealed和static修饰
- 可以有抽象方法,也可以有普通方法