1 面向对象程序设计基础
如果一个软件系统是使用这4个概念来设计和实现的,就认为这个软件系统是面向对象的
1.1 对象
1.2 类
类是一组具有相同数据结构和相同操作的对象的集合
1.3 继承
继承是以现有类型定义为基础创建新类型的技术。
1.4 消息通信
对象与对象之间,通过消息进行通信。
2 类的生命周期
2.1 构造函数
对象的初始化通常由类的构造函数来完成。
a、构造函数的名称与类名相同
b、构造函数不声明返回类型
c、构造函数通常是公有的,如果声明为保护的或私有的,则该构造函数不能用于类的实例化
d、构造函数的代码中通常中只进行对象初始化工作,而不应执行其它操作
e、构造函数在创建对象时被自动调用,不能像其它方法那样显式的调用
public class ConstructSample { public static void Main() { Person p = new Person(); Console.WriteLine(p.m_name); } } public class Person { public string m_name; protected int m_age; protected bool m_gender; //构造函数 public Person() { m_name = "Unknown"; m_age = 0; m_gender = false; } }
带参数的构造函数(构造函数的重载)
public class Person { public string m_name; protected int m_age; protected bool m_gender; //构造函数 public Person() { m_name = "Unknown"; m_age = 0; m_gender = false; } //构造函数重载 public Person(string Name) { m_name = Name; m_age = 0; m_gender = true; } }
2.2 静态构造函数
如果使用了关键字static来定义构造函数,那么该构造函数就属于类而不是类的实例所有。
在程序第一次用到某个类时,类的静态构造函数自动被调用,而且是仅此一次。
静态构造函数通常用于对类的静态字段进行初始化
静态构造函数不使用任何访问限制修饰符。
public class StaticConstructSample { public static void Main() { Person p1 = new Person("Mike"); Person p2 = new Person("John"); Person p3 = new Person("Mary"); } } public class Person { public string m_name; public static int m_object=0; public static int m_classes = -1; //构造函数 public Person(string Name) { m_name = Name; Console.WriteLine(m_name); Console.WriteLine("Object before:{0}", m_object); m_object++; Console.WriteLine("Object after:{0}", m_object); } //静态构造函数 public Person() { Console.WriteLine("Classes before:{0}", m_classes); m_classes++; Console.WriteLine("Classes after:{0}", m_classes); } }
程序输出结果
Mike Object before:0 Object after:1 John Object before:1 Object after:2 Mary Object before:2 Object after:3 请按任意键继续. . .
2.3 析构函数
对象使用完毕后,释放对象时就会自动调用类的析构函数
--析构函数的名称与类名相同,但在名称前面加了一个符号"~"
--析构函数不接受任何参数,也不返回任何值
--析构函数不能使用任何访问限制修饰符
--析构函数中的代码通常只进行销毁对象的工作,而不应执行其它的操作
--析构函数不能被继承,也不能被显示的调用
--如果类中没有显式的定义一个析构函数,编译时也会生成一个默认的析构函数,其执行代码为空。
--不存在静态的析构函数
3 属性
--为了实现良好的数据封装和数据隐藏,C#为类提供了属性(Property)成员。
--属性是对字段的扩展,它通过属性访问函数来控制对字段的访问。
--属性访问函数包括get访问函数和set访问函数,分别用于对字段的读取和修改。
例如Person类中可以使用Name属性来封装对私有字段name的访问
public class Person { //字段 private string name; //属性 public string Name { get { return name; } set { name=value; } } } public static void Main() { person p1=new Person(); p1.Name="Mike";//set Console.WriteLine(p1.Name);//get }
--定义属性时可以只声明一个访问函数。如果只有get,则表明属性的值不能为修改;如果只有set,则表明属性的值只能写入。
--和方法一样,属性可以声明为静态的,也可以使用各种访问限制修饰符
--属性可以作为特殊的方法来使用,而不必和字段一一对应
public class Person { private DateTime birthday; public int Age { get { return DataTime.Now.Year-birthday.year; } } }
4 索引函数
--索引函数对属性做了进一步的扩展,它能够以数组的方式来控制对多个变量的读写访问。
--和属性一样,索引函数可以被看作是get访问函数和set访问函数的组合,不同之处在于:
--索引函数以this关键字加数组形式的下标进行定义,并通过数组形式的下标进行访问;
--索引函数的get和set带用参数(一般为整型或字符串类型)
--索引函数不能是静态的
--和属性类似,索引函数的get和set访问函数中可以增加控制代码
下面的代码示例了一个Per public class PersonTable
{ private Person[] m_list; public int Length { get { return m_list.Length; } } //索引函数 public Person this[int index] { get {
if(index>=0 && index <Length) return m_list[index];
else
return null;
} set {
if(index>=0 && index <Length)
m_list[index] = value;
else
throw new IndexOutOfRangeException(); } } }
这样对类的使用和数组的使用就非常类似了
PersonTable pt = new PersonTable(3); pt[1]=new Person("Mike"); ... ...
索引函数的访问对象不一定要是连续的数据,也可以是多个离散的字段。例如
public class IndexerSample { static void Main() { Person p1 = new Person("李四"); p1["BusinessPhone"] = "010888888"; p1["BusinessFax"] = "0108888888"; p1["MobilePhone"]="13988888888"; p1.Output(); } } public class Person { private string m_name; private string m_busiPhone; private string m_busiFax; private string m_homePhone; private string m_mobilePhone; public string Name { get { return m_name; } set { m_name = value; } } //索引函数 public string this[string Stype] { get { string type=Stype.ToUpper(); switch (type) { case "BUSINESSPHONE": return m_busiPhone; case "BUSINESSFAX": return m_busiFax; case "HOMEPHONE": return m_homePhone; case "MOBILEPHONE": return m_mobilePhone; default: return null; } } set { string type=Stype.ToUpper(); switch (type) { case "BUSINESSPHONE": m_busiPhone = value; break; case "BUSINESSFAX": m_busiFax = value; break; case "HOMEPHONE": m_homePhone = value; break; case "MOBILEPHONE": m_mobilePhone = value; break; default: throw new ArgumentOutOfRangeException(); } } } //构造函数 public Person() { } public Person(string sName) { m_name = sName; } //方法 public void Output() { Console.WriteLine(m_name); Console.WriteLine("商务电话:{0}", m_busiPhone); Console.WriteLine("商务传真:{0}", m_busiFax); Console.WriteLine("家庭电话:{0}", m_homePhone); Console.WriteLine("移动电话:{0}", m_mobilePhone); } }
5 事件
通过事件(Event),对象可以对发生的情况做出反映。
在C#的事件处理模型中,某个事件发生后,对象通过该事件的代表(delegate)调用适当的事件处理代码,此时代表充当了产生事件的对象与处理事件的方法之间的“中间人”。
System程序集中定义了一个EventArgs的类,用来封装事件中所包含的数据:此外还定义了一个名为EventHandler的delegate对象,用来作为所有事件的代表,其原型为:
public delegate void EventHandler(object sender,EventArgs e)
其中的参数sender表示发生事件的对象,而e表示事件中包含的数据。
C#中的事件也是一种特殊的方法,它包含一对内部函数add和remove,add函数将代表附加到事件上,而remove函数将移除已附加到事件上的代表。
public class EventSample { static void Main() { Person p1 = new Person("李四"); p1.Name = "李明"; Console.WriteLine("当前姓名为:{0}", p1.Name); } } public class Person { //字段 private string m_name; private string m_busiPhone; private string m_busiFax; private string m_homePhone; private string m_mobilePhone; //属性 public string Name { get { return m_name; } set { if(m_name!=value) { OnNameChange(this, new EventArgs()); m_name = value; } } } //代表 internal EventHandler eh; //索引函数 public string this[string Stype] { get { string type=Stype.ToUpper(); switch (type) { case "BUSINESSPHONE": return m_busiPhone; case "BUSINESSFAX": return m_busiFax; case "HOMEPHONE": return m_homePhone; case "MOBILEPHONE": return m_mobilePhone; default: return null; } } set { string type=Stype.ToUpper(); switch (type) { case "BUSINESSPHONE": m_busiPhone = value; break; case "BUSINESSFAX": m_busiFax = value; break; case "HOMEPHONE": m_homePhone = value; break; case "MOBILEPHONE": m_mobilePhone = value; break; default: throw new ArgumentOutOfRangeException(); } } } //构造函数 public Person() { } public Person(string sName) { m_name = sName; } //方法 public void Output() { Console.WriteLine(m_name); Console.WriteLine("商务电话:{0}", m_busiPhone); Console.WriteLine("商务传真:{0}", m_busiFax); Console.WriteLine("家庭电话:{0}", m_homePhone); Console.WriteLine("移动电话:{0}", m_mobilePhone); } //事件 public event EventHandler NameChange { add { eh += (EventHandler)Delegate.Combine(eh, value); } remove { eh -= (EventHandler)Delegate.Combine(eh, value); } } //事件处理方法 protected void OnNameChange(object sender, EventArgs e) { Console.WriteLine("人员姓名:{0},已经被修改", m_name); } }
这样,每次修改Person的Name属性,将触发NameChange事件,程序输出为:
人员姓名:李四,已经被修改
当前姓名为:李明
请按任意键继续. . .
C#还提供了事件定义的简写方式,可以不用写出事件的add和remove函数,也不用写出代表的定义。上例中的事件和代表的定义就可以简化为一行代码:
public event EventHandler NameChange;
输出效果相同。
更多的时候,开发人员需要自定义EventArgs的派生类,并在其中自定义事件数据。对Person类进行如下修改:
public class Person { //字段 private string m_name; private string m_busiPhone; private string m_busiFax; private string m_homePhone; private string m_mobilePhone; //属性 public string Name { get { return m_name; } set { if(m_name!=value) { PersonEventArgs e = new PersonEventArgs(); e.m_oldName = m_name; e.m_newName = value; OnNameChange(this, e); } } } //索引函数 public string this[string Stype] { get { string type=Stype.ToUpper(); switch (type) { case "BUSINESSPHONE": return m_busiPhone; case "BUSINESSFAX": return m_busiFax; case "HOMEPHONE": return m_homePhone; case "MOBILEPHONE": return m_mobilePhone; default: return null; } } set { string type=Stype.ToUpper(); switch (type) { case "BUSINESSPHONE": m_busiPhone = value; break; case "BUSINESSFAX": m_busiFax = value; break; case "HOMEPHONE": m_homePhone = value; break; case "MOBILEPHONE": m_mobilePhone = value; break; default: throw new ArgumentOutOfRangeException(); } } } //构造函数 public Person() { NameChange += new EventHandler(OnNameChange); } public Person(string sName) { m_name = sName; NameChange+=new EventHandler(OnNameChange); } //方法 public void Output() { Console.WriteLine(m_name); Console.WriteLine("商务电话:{0}", m_busiPhone); Console.WriteLine("商务传真:{0}", m_busiFax); Console.WriteLine("家庭电话:{0}", m_homePhone); Console.WriteLine("移动电话:{0}", m_mobilePhone); } //事件 public event EventHandler NameChange; //事件参数类 public class PersonEventArgs : EventArgs { public string m_oldName; public string m_newName; } //事件处理方法 protected void OnNameChange(object sender, EventArgs e) { Console.WriteLine("人员姓名:{0},已经被修改为{1}!", ((PersonEventArgs)e).m_oldName,((PersonEventArgs)e).m_newName); Console.WriteLine("确定修改(Y/N)?"); char key = Console.ReadKey().KeyChar; Console.WriteLine(); if (key == 'y' || key == 'Y') { m_name = ((PersonEventArgs)e).m_newName; Console.WriteLine("姓名修改成功"); } } }
程序输出结果为:
人员姓名:李四,已经被修改为李明! 确定修改(Y/N)? y 姓名修改成功 当前姓名为:李明 请按任意键继续. . .
6 操作符重载
操作符重载可用于对自定义的数据类型进行基本操作
允许被重载的操作符包括
-- 一元操作符:+ - ! ~ ++ -- (T) true false
-- 二元操作符:+ - * / % & | ^ << >> == != > < >= <=
考虑到操作符的对称性,下列操作符要求成对重载
-- 一元操作符:true和false
-- 二元操作符:==和 !=、>和<、>=和<=
class Prime { private uint m_value; public Prime(uint iValue) { m_value=iValue; } }
public static uint operator+(Prime p1,Prime P2) { return p1.m_value+p2.m_value; }
如果添加了以上方法,则p1+p2是合法的,而不用繁琐的写法p1.m_value+p2.m_value;
对于复合赋值操作符,只要左部操作符是可重载的二元操作符,并且操作符的返回类型也可以隐式的转换成当前类,那么相应的斌值操作符也被隐式重载。例如将上面的重载定义改写为:
public static Prime operator+(Prime p1,Prime p2) { return new Prime(p1.m_value+p2.m_value); }
此时p1+=p2这样的表达式也是合法的。
--被重载的操作符也是一种特殊的方法,且必须被声明为公有的和静态的。
--重载一元操作符时需提供一个参数,且参数类型应为当前类型,或者是可以隐式转换为当前类型
--重载二元操作符时需提供两个参数,且至少有一个参数类型为当前类型,或者是可以隐式转换为当前类型
--对于类型转换操作符(T),在定义重载时需要为T指定一个数据类型。类型转换的重载有一个特殊的地方,就是它不显式的定义返回类型,而认为操作符的返回类型始终为T所代表的类型。此外,需要使用关键字explicit或implicit来指明转换是显式的还是隐式的。
例如可以定义从Prime类到uint类的隐式转换
public static implicit operator uint(prime p) { return p.m_value; }
下面的代码为一个完整的示例:
public class OperatorSample { static void Main() { //输出前20个素数 Prime p1 = new Prime(2); for (int i = 0; i < 20; i++) { Console.WriteLine(p1++); } //输出500~1000之间的所有素数 for (uint j = 500; j < 1000; j++) { Prime p2 = new Prime(j); if (p2) Console.WriteLine(p2 + ","); } } } public class Prime { private uint m_value; public uint Value { get { return m_value; } } //构造函数 public Prime(uint iValue) { m_value = iValue; } //重载二元操作符+ public static uint operator +(Prime p1, Prime p2) { return p1.m_value + p2.m_value; } //重载二元操作符- public static int operator -(Prime p1, Prime p2) { return (int)(p1.m_value - p2.m_value); } //重载一元操作符++ public static Prime operator ++(Prime p) { for (uint i = p.m_value + 1; ; i++) { if (IsPrime(i)) return new Prime(i); } } //重载一元操作符-- public static Prime operator --(Prime p) { for (uint i = p.m_value - 1;i>2; i--) { if (IsPrime(i)) return new Prime(i); } return new Prime(2); } //重载类型转换操作符(uint) public static implicit operator uint(Prime p) { return p.m_value; } //重载一元操作符true public static bool operator true(Prime p) { return IsPrime(p.m_value); } //重载一元操作符false public static bool operator false(Prime p) { return IsPrime(p.m_value); } //方法,判断一个整数是否为素数 public static bool IsPrime(uint x) { for (uint i = 2; i <= x / 2; i++) { if (x % i == 0) return false; } return true; } //重写ToString() public override string ToString() { return m_value.ToString(); } }
7 this关键字
this用于代一个变量
--它仅限于在类的非静态方法成员中使用,包括类的构造函数、非静态方法、属性、索引函数及事件
--在类的构造函数中出现时,它表示正在构造的对象本身
--在类的方法成员中出现时,它表示调用该方法成员的对象
上例中的prime类的带参数的构造函数可以写成以下形式
public Prime(uint iValue) { this.m_value=iValue; }