C# 编程_第03章_面向对象
C# 类(Class)
当您定义一个类时,您定义了一个数据类型的蓝图。这实际上并没有定义任何的数据,但它定义了类的名称意味着什么,也就是说,类的对象由什么组成及在这个对象上可执行什么操作。对象是类的实例。构成类的方法和变量成为类的成员。
类的定义
类的定义是以关键字 class 开始,后跟类的名称。
类的主体,包含在一对花括号内。
下面是类定义的一般形式:
<access specifier> class class_name { // member variables <access specifier> <data type> variable1; // member methods <access specifier> <return type> method1(parameter_list) { // method body } }
请注意:
- 访问标识符 <access specifier> 指定了对类及其成员的访问规则。如果没有指定,则使用默认的访问标识符。类的默认访问标识符是 internal,成员的默认访问标识符是 private。
- 数据类型 <data type> 指定了变量的类型,返回类型 <return type> 指定了返回的方法返回的数据类型。
- 如果要访问类的成员,您要使用点(.)运算符。
- 点运算符链接了对象的名称和成员的名称。
下面的实例说明了目前为止所讨论的概念:
using System; namespace BoxApplication { class Box { public double length; // 长度 public double breadth; // 宽度 public double height; // 高度 } class Boxtester { static void Main(string[] args) { Box Box1 = new Box(); // 声明 Box1,类型为 Box Box Box2 = new Box(); // 声明 Box2,类型为 Box double volume = 0.0; // 体积 // Box1 详述 Box1.height = 5.0; Box1.length = 6.0; Box1.breadth = 7.0; // Box2 详述 Box2.height = 10.0; Box2.length = 12.0; Box2.breadth = 13.0; // Box1 的体积 volume = Box1.height * Box1.length * Box1.breadth; Console.WriteLine("Box1 的体积: {0}", volume); // Box2 的体积 volume = Box2.height * Box2.length * Box2.breadth; Console.WriteLine("Box2 的体积: {0}", volume); Console.ReadKey(); } } }
当上面的代码被编译和执行时,它会产生下列结果:
Box1 的体积: 210 Box2 的体积: 1560
成员函数和封装
类的成员函数是一个在类定义中有它的定义或原型的函数,就像其他变量一样。
作为类的一个成员,它能在类的任何对象上操作,且能访问该对象的类的所有成员。
成员变量是对象的属性(从设计角度),且它们保持私有来实现封装。这些变量,在类的外部时,只能使用公共成员函数来访问。
让我们使用上面的概念来设置和获取一个类中不同的类成员的值:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
namespace BoxApplication{
class Box{
private double _length;
private double _breadth;
private double _height;
public void setLength(double length){
_length = length;
}
public void setBreadth(double breadth){
_breadth = breadth;
}
public void setHeight(double height){
_height = height;
}
public double getVolume(){
return _length * _breadth * _height;
}
}
class BoxTest{
static void Main(string[] args)
{
Box b1 = new Box();
Box b2 = new Box();
b1.setLength(5);
b1.setBreadth(6);
b1.setHeight(7);
b2.setLength(10);
b2.setBreadth(12);
b2.setHeight(13);
Console.WriteLine("box_1体积:{0}",b1.getVolume());
Console.WriteLine("box_2体积:{0}",b2.getVolume());
Console.ReadKey();
}
}
}
C# 中的构造函数
类的 构造函数 是类的一个特殊的成员函数,当创建类的新对象时执行。
构造函数的名称与类的名称完全相同,它没有任何返回类型。
下面的实例说明了构造函数的概念:
using System; namespace LineApplication { class Line { private double length; // 线条的长度 public Line() { Console.WriteLine("构造方法"); } public void setLength( double len ) { length = len; } public double getLength() { return length; } static void Main(string[] args) { Line line = new Line(); // 设置线条长度 line.setLength(6.7); Console.WriteLine("线条的长度: {0}", line.getLength()); Console.ReadKey(); } } }
当上面的代码被编译和执行时,它会产生下列结果:
构造方法 线条的长度: 6.7
默认的构造函数没有任何参数。
但是如果您需要一个带有参数的构造函数可以有参数,这种构造函数叫做参数化构造函数。
这种技术可以帮助您在创建对象的同时给对象赋初始值,具体请看下面实例:
using System; namespace LineApplication { class Line { private double length; // 线条的长度 public Line(double len) // 参数化构造函数 { Console.WriteLine("对象已创建,length = {0}", len); length = len; } public void setLength( double len ) { length = len; } public double getLength() { return length; } static void Main(string[] args) { Line line = new Line(6.7); Console.WriteLine("线条的长度: {0}", line.getLength()); // 设置线条长度 line.setLength(520); Console.WriteLine("线条的长度: {0}", line.getLength()); Console.ReadKey(); } } }
当上面的代码被编译和执行时,它会产生下列结果:
对象已创建,length = 6.7 线条的长度: 6.7 线条的长度: 520
C# 中的析构函数
类的 析构函数 是类的一个特殊的成员函数,当类的对象超出范围时执行。
析构函数的名称是在类的名称前加上一个波浪形(~)作为前缀,它不返回值,也不带任何参数。
析构函数用于在结束程序(比如关闭文件、释放内存等)之前释放资源。
析构函数不能继承或重载。
下面的实例说明了析构函数的概念:
using System; namespace LineApplication { class Line { private double length; // 线条的长度 public Line() // 构造函数 { Console.WriteLine("对象已创建"); } ~Line() //析构函数 没有public关键字,没有参数,没有返回值,不能重载和继承 { Console.WriteLine("对象已删除"); } public void setLength( double len ) { length = len; } public double getLength() { return length; } static void Main(string[] args) { Line line = new Line(); // 设置线条长度 line.setLength(6.7); Console.WriteLine("线条的长度: {0}", line.getLength()); } } }
当上面的代码被编译和执行时,它会产生下列结果:
对象已创建 线条的长度: 6.7 对象已删除
C# 类的静态成员
我们可以使用 static 关键字把类成员定义为静态的。
当我们声明一个类成员为静态时,意味着无论有多少个类的对象被创建,只会有一个该静态成员的副本。
关键字 static 意味着类中只有一个该成员的实例。
静态变量用于定义常量,因为它们的值可以通过直接调用类而不需要创建类的实例来获取。
在C#中,静态变量可在成员函数或类的定义外部进行初始化。
您也可以在类的定义内部初始化静态变量。
下面的实例演示了静态变量的用法
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
namespace StaticApplication{
class StaticMember{
public static int num;
public void addCount(){
num++;
}
public int getCurrentCount(){
return num;
}
}
class BoxTest{
static void Main(string[] args)
{
StaticMember s =new StaticMember();
Console.WriteLine("当前统计:{0}",s.getCurrentCount());
s.addCount();
s.addCount();
Console.WriteLine("当前统计:{0}",s.getCurrentCount());
Console.ReadKey();
}
}
}
运行效果如下:
您也可以把一个成员函数声明为 static。这样的函数只能访问静态变量。
静态函数在对象被创建之前就已经存在。
下面的实例演示了静态函数的用法:
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
namespace StaticApplication{
class StaticMember{
public static int num;
public void addCount(){
num++;
}
public static int getCurrentCount(){
return num;
}
}
class BoxTest{
static void Main(string[] args)
{
StaticMember s =new StaticMember();
Console.WriteLine("当前统计:{0}",StaticMember.getCurrentCount());
s.addCount();
s.addCount();
s.addCount();
Console.WriteLine("当前统计:{0}",StaticMember.getCurrentCount());
Console.ReadKey();
}
}
}
运行效果:
C# 继承
继承是面向对象程序设计中最重要的概念之一。
继承允许我们根据一个类来定义另一个类,这使得创建和维护应用程序变得更容易。同时也有利于重用代码和节省开发时间。
当创建一个类时,程序员不需要完全重新编写新的数据成员和成员函数,只需要设计一个新的类,继承了已有的类的成员即可。
这个已有的类被称为的基类,这个新的类被称为派生类。
继承的思想实现了 属于(IS-A) 关系。例如,哺乳动物 属于(IS-A) 动物,狗 属于(IS-A) 哺乳动物,因此狗 属于(IS-A) 动物。
基类和派生类 (C#是单继承)
派生类只能有一个直接基类,但一个基类可以有多个直接派生类。
继承是可以传递的。定义要从其他类派生的类时,派生类会隐式获得基类的所有成员(除了其 构造函数 和 终结器 finalizer)。
派生类因而可以重用基类中的代码,而无需重新实现。
在派生类中,可以添加更多成员。通过这种方法,派生类可扩展基类的功能。
C# 中创建派生类的语法如下:
<acess-specifier> class <base_class> { ... } class <derived_class> : <base_class> { ... }
假设,有一个基类 Shape,它的派生类是 Rectangle:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
namespace InheritanceApplication{
class Shape{
// 子类可用
protected int _width;
protected int _height;
public void setWidth(int width){
_width = width;
}
public void setHeight(int height){
_height = height;
}
}
class Rectangle:Shape{
public int GetArea(){
return _height * _width;
}
}
class InheritanceTest{
static void Main(string[] args)
{
Rectangle rect = new Rectangle();
rect.setWidth(6);
rect.setHeight(7);
Console.WriteLine("面积:{0}",rect.GetArea());
Console.ReadKey();
}
}
}
基类的初始化
派生类继承了基类的成员变量和成员方法。
因此父类对象应在子类对象创建之前被创建。您可以在成员初始化列表中进行父类的初始化。
下面的程序演示了这点:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
namespace InheritanceApplication{
class Rectangle{
// 子类可用
protected double _width;
protected double _height;
public Rectangle(double width,double height){
_width = width;
_height = height;
}
public double GetArea(){
return _height * _width;
}
public void Display(){
Console.WriteLine("长:{0}",_height);
Console.WriteLine("宽:{0}",_width);
Console.WriteLine("面积:{0}",GetArea());
}
}
class Tablecloth:Rectangle{
public Tablecloth(double height,double width):base(height,width){
}
public double GetCost(){
double price = 67;
return GetArea() * price;
}
// 覆盖父类的方法
public void Display(){
// 调用父类的方法
base.Display();
Console.WriteLine("桌布共花费:{0}",GetCost());
}
}
class InheritanceTest{
static void Main(string[] args)
{
Tablecloth tc = new Tablecloth(6,7);
tc.Display();
Console.ReadKey();
}
}
}
C# 多重继承
C# 不支持多重继承。但是,您可以使用接口来实现多重继承。下面的程序演示了这点:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
namespace InheritanceApplication{
class Shape{
protected int _width;
protected int _height;
public void setWidth(int width){
_width = width;
}
public void setHeight(int height){
_height = height;
}
public int GetArea(){
return _width * _height;
}
}
//接口
public interface ClothCost{
int moneyCost(int area);
}
class Tablecloth:Shape,ClothCost{
// 实现接口中的方法
public int moneyCost(int area){
int price = 67;
return price * area;
}
}
class InheritanceTest{
static void Main(string[] args)
{
Tablecloth tc = new Tablecloth();
tc.setWidth(6);
tc.setHeight(7);
int area = tc.GetArea();
Console.WriteLine("面积:{0}",area);
Console.WriteLine("桌布花费:{0}",tc.moneyCost(area));
Console.ReadKey();
}
}
}
C# 多态性
多态性意味着有多重形式。在面向对象编程范式中,多态性往往表现为"一个接口,多个功能"。
多态性可以是静态的或动态的。
在静态多态性中,函数的响应是在编译时发生的。
在动态多态性中,函数的响应是在运行时发生的。
静态多态性 Polymorphism
在编译时,函数和对象的连接机制被称为早期绑定,也被称为静态绑定。
C# 提供了两种技术来实现静态多态性。分别为:
- 函数重载
- 运算符重载
函数重载
您可以在同一个范围内对相同的函数名有多个定义。
函数的定义必须彼此不同,可以是参数列表中的参数类型不同,也可以是参数个数不同。
不能重载只有返回类型不同的函数声明。
下面的实例演示了几个相同的函数 print(),用于打印不同的数据类型:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
// Polimorphism 多态
namespace PolymorphismApplication{
class MultiPrint{
// public必须加,因为默认的是private
public void print(int i){
Console.WriteLine("print int:{0}",i);
}
// public必须加,因为默认的是private
public void print(double d){
Console.WriteLine("print double:{0}",d);
}
// public必须加,因为默认的是private
public void print(string s){
Console.WriteLine("print string:{0}",s);
}
}
class PolymorphismTest{
static void Main(string[] args)
{
MultiPrint printM = new MultiPrint();
printM.print(67);
printM.print(520.1314);
printM.print("hello beyond");
Console.ReadKey();
}
}
}
动态多态性
C# 允许您使用关键字 abstract 创建抽象类,用于提供接口的部分类的实现。
当一个派生类继承自该抽象类时,实现即完成。
抽象类包含抽象方法,抽象方法可被派生类实现。
派生类具有更专业的功能。
请注意,下面是有关抽象类的一些规则:
- 您不能创建一个抽象类的实例。(即抽象类没有办法实例化)
- 您不能在一个抽象类外部声明一个抽象方法。
- 通过在类定义前面放置关键字 sealed,可以将类声明为密封类。
- 当一个类被声明为 sealed 时,它不能被继承。(类似于Java的Final类)
- 抽象类不能被声明为 sealed。
下面的程序演示了一个抽象类:
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
// Polimorphism 多态
namespace PolymorphismApplication{
// 使用关键字abstract 定义抽象类
abstract class Shape{
public abstract int GetArea();
}
// 子类使用override关键字 实现抽象方法
class Rectangle:Shape{
private int _length;
private int _width;
public Rectangle(int length = 0, int width = 0){
_length = length;
_width = width;
}
public override int GetArea(){
int area = _width * _length;
Console.WriteLine("矩形面积:{0}",area);
return area;
}
}
class PolymorphismTest{
static void Main(string[] args)
{
Rectangle rect = new Rectangle(6,7);
rect.GetArea();
Console.ReadKey();
}
}
}
运行效果如下:
当有一个定义在类中的函数需要在继承类中实现时,可以使用虚方法。
虚方法是使用关键字 virtual 声明的。
虚方法可以在不同的继承类中有不同的实现。
对虚方法的调用是在运行时发生的。
动态多态性是通过 抽象类 和 虚方法 实现的。
下面的程序演示了这点:
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
// Polimorphism 多态
namespace PolymorphismApplication{
class Shape{
protected int _width,_height;
//构造方法
public Shape(int width = 0,int height = 0){
_width = width;
_height = height;
}
// 虚方法
public virtual int GetArea(){
Console.WriteLine("父类面积:");
return 0;
}
}
// 子类 矩形
class Rectangle : Shape{
// 构造方法
public Rectangle(int width,int height):base(width,height){
// do nothing
}
// 子类实现虚方法
public override int GetArea(){
int tmpArea = _width * _height;
Console.WriteLine("Rectangle 面积:");
return tmpArea;
}
}
// 子类 三角形
class Triangle:Shape{
// 构造方法
public Triangle(int width,int height):base(width,height){
// do nothing
}
// 子类实现虚方法
public override int GetArea(){
int tmpArea = _width * _height / 2;
Console.WriteLine("Triangle 面积:");
return tmpArea;
}
}
// 设计一个通用的Caller
class Caller{
// 通用的根据参数传递进来的Shape基类,输出面积
public void CallerAreaWithShape(Shape shape){
int tmpArea = shape.GetArea();
// 打印面积
Console.WriteLine("面积:{0}",tmpArea);
}
}
class PolymorphismTest{
static void Main(string[] args)
{
Caller caller = new Caller();
Rectangle rect = new Rectangle(6,7);
Triangle triangle = new Triangle(13,14);
caller.CallerAreaWithShape(rect);
caller.CallerAreaWithShape(triangle);
Console.ReadKey();
}
}
}
运行结果:
C# 运算符重载
您可以重定义或重载 C# 中内置的运算符。
因此,程序员也可以使用用户自定义类型的运算符。
重载运算符是具有特殊名称的函数,是通过关键字 operator 后跟运算符的符号来定义的。
与其他函数一样,重载运算符有返回类型和参数列表。
例如,请看下面的函数:
public static Box operator+ (Box b, Box c) { Box box = new Box(); box.length = b.length + c.length; box.breadth = b.breadth + c.breadth; box.height = b.height + c.height; return box; }
上面的函数为用户自定义的类 Box 实现了加法运算符(+)。它把两个 Box 对象的属性相加,并返回相加后的 Box 对象。
运算符重载的实现
C#要求所有的运算符重载都声明为public和static,
这表示它们与它们的类或结构相关联,而不是与实例相关联,
所以运算符重载的代码体不能访问非静态类成员,也不能访问this标识符;
这是可以的,因为参数提供了运算符执行任务所需要知道的所有数据。
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
// Operation Overload 运算符重载
namespace OperationOverloadApplication{
class Box{
// 私有成员
private double _length;
private double _breadth;
private double _height;
//构造方法
public Box(double length,double breadth,double height){
_length = length;
_breadth = breadth;
_height = height;
}
// 要不要实现无参数的构造方法???
// 不能省的get方法
public double getLength(){
return _length;
}
public double getBreadth(){
return _breadth;
}
public double getHeight(){
return _height;
}
// 不能省的set方法
public void setLength(double length){
_length = length;
}
public void setBreadth(double breadth){
_breadth = breadth;
}
public void setHeight(double height){
_height = height;
}
// 获取体积
public double GetVolumn(){
return _length * _breadth * _height;
}
// 重载+号 必须是static的
public static Box operator+(Box box_1,Box box_2){
double tmpLength = box_1.getLength() + box_2.getLength();
double tmpBreadth = box_1.getBreadth() + box_2.getBreadth();
double tmpHeight = box_1.getHeight() + box_2.getHeight();
//新建一个存放结果的Box
Box resultBox = new Box(tmpLength,tmpBreadth,tmpHeight);
return resultBox;
}
}
class OperationOverloadTest{
static void Main(string[] args)
{
Box b1 = new Box(5,6,7);
Box b2 = new Box(13,14,15);
Console.WriteLine("b1 体积:{0}",b1.GetVolumn());
Console.WriteLine("b2 体积:{0}",b2.GetVolumn());
// 加法
Box b3 = b1 + b2;
Console.WriteLine("b3 体积:{0}",b3.GetVolumn());
Console.ReadKey();
}
}
}
可重载和不可重载运算符
下表描述了 C# 中运算符重载的能力:
运算符 | 描述 |
---|---|
正+,负-,非 !, 取反~, 自增++, -- | 这些一元运算符只有一个操作数,且可以被重载。 |
+, -, *, /, % | 这些二元运算符带有两个操作数,且可以被重载。 |
==, !=, <, >, <=, >= | 这些比较运算符可以被重载。 |
&&, || | 这些条件逻辑运算符不能被直接重载。 |
+=, -=, *=, /=, %= | 这些赋值运算符不能被重载。 |
=, ., ?:, ->, new, is, sizeof, typeof | 这些运算符不能被重载。 |
实例
针对上述讨论,让我们扩展上面的实例,重载更多的运算符:
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
// Operation Overload 运算符重载
namespace OperationOverloadApplication{
class Box{
// 私有成员
private double _length;
private double _breadth;
private double _height;
//构造方法
public Box(double length,double breadth,double height){
_length = length;
_breadth = breadth;
_height = height;
}
// 要不要实现无参数的构造方法???
// 不能省的get方法
public double getLength(){
return _length;
}
public double getBreadth(){
return _breadth;
}
public double getHeight(){
return _height;
}
// 不能省的set方法
public void setLength(double length){
_length = length;
}
public void setBreadth(double breadth){
_breadth = breadth;
}
public void setHeight(double height){
_height = height;
}
// 获取体积
public double GetVolumn(){
return _length * _breadth * _height;
}
// 重载+号 必须是static的
public static Box operator+(Box box_1,Box box_2){
double tmpLength = box_1.getLength() + box_2.getLength();
double tmpBreadth = box_1.getBreadth() + box_2.getBreadth();
double tmpHeight = box_1.getHeight() + box_2.getHeight();
//新建一个存放结果的Box
Box resultBox = new Box(tmpLength,tmpBreadth,tmpHeight);
return resultBox;
}
// 重载==号 必须是static的,且必须同时 重载!=
public static bool operator==(Box b1,Box b2){
bool isEqual = false;
if(b1.getLength() == b2.getLength() && b1.getBreadth() == b2.getBreadth() && b1.getHeight() == b2.getHeight()){
isEqual = true;
}
return isEqual;
}
// 重载!=号 必须是static的,且必须同时 重载==
public static bool operator!=(Box b1,Box b2){
bool isEqual = false;
if(b1.getLength() != b2.getLength() || b1.getBreadth() != b2.getBreadth() || b1.getHeight() != b2.getHeight()){
isEqual = true;
}
return isEqual;
}
}
class OperationOverloadTest{
static void Main(string[] args)
{
Box b1 = new Box(5,6,7);
Box b2 = new Box(13,14,15);
Console.WriteLine("b1 体积:{0}",b1.GetVolumn());
Console.WriteLine("b2 体积:{0}",b2.GetVolumn());
// 加法
Box b3 = b1 + b2;
Console.WriteLine("b3 体积:{0}",b3.GetVolumn());
// == 重载
Console.WriteLine("b1与b2是否相等:{0}",b1 == b2);
Box b4 = new Box(5,6,7);
Console.WriteLine("b1与b4是否相等:{0}",b1 == b4);
Console.ReadKey();
}
}
}
运行效果如下:
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
// Operation Overload 运算符重载
namespace OperationOverloadApplication{
class Girl{
// 私有成员
private string _name;
private int _age;
//构造方法
public Girl(string name,int age){
_age = age;
_name = name;
}
// 要不要实现无参数的构造方法???
// 不能省的get方法
public string getName(){
return _name;
}
public int getAge(){
return _age;
}
// 不能省的set方法
public void setName(string name){
_name = name;
}
public void setAge(int age){
_age = age;
}
// 重载<号 必须是static的
public static bool operator<(Girl leftGirl,Girl rightGirl){
bool isLittle = false;
if(leftGirl.getAge() < rightGirl.getAge()){
isLittle = true;
}
return isLittle;
}
// 重载>号 必须是static的
public static bool operator>(Girl leftGirl,Girl rightGirl){
bool isElder = false;
if(leftGirl.getAge() > rightGirl.getAge()){
isElder = true;
}
return isElder;
}
// 重载<=号 必须是static的
public static bool operator<=(Girl leftGirl,Girl rightGirl){
bool isNotElder = false;
if(leftGirl.getAge() <= rightGirl.getAge()){
isNotElder = true;
}
return isNotElder;
}
// 重载>=号 必须是static的
public static bool operator>=(Girl leftGirl,Girl rightGirl){
bool isNotLittle = false;
if(leftGirl.getAge() >= rightGirl.getAge()){
isNotLittle = true;
}
return isNotLittle;
}
// 覆盖 ToString方法,实现自定义打印
public override string ToString(){
string resultStr = String.Format("{0} no age is {1}",this.getName(),this.getAge());
return resultStr;
}
}
class OperationOverloadTest{
static void Main(string[] args)
{
Girl menma = new Girl("面码",15);
Girl tiger = new Girl("逢坂大河",16);
Console.WriteLine(menma.ToString());
Console.WriteLine(tiger.ToString());
// 重载 >
if(menma > tiger){
Console.WriteLine("{0} > {1}",menma,tiger);
}else{
Console.WriteLine("{0} <= {1}",menma,tiger);
}
// 重载 >=
if(menma >= tiger){
Console.WriteLine("{0} >= {1}",menma,tiger);
}else{
Console.WriteLine("{0} < {1}",menma,tiger);
}
Console.ReadKey();
}
}
}
运行效果如下:
C# 接口(Interface)
接口定义了所有类继承接口时应遵循的语法合同。
接口定义了语法合同 "是什么" 部分,派生类定义了语法合同 具体"怎么做" 部分。
接口定义了属性、方法和事件,这些都是接口的成员。接口只包含了成员的声明部分。
而成员的具体的定义部分是派生类的责任。接口提供了派生类应遵循的标准结构。
抽象类在某种程度上与接口类似,但是,抽象类大多只是用在当只有少数方法由基类声明,然后由派生类实现时。
声明接口
接口使用 interface 关键字声明,它与类的声明类似。接口声明默认是 public 的。
下面是一个接口声明的实例:
public interface ITransactions { // 接口成员 void showTransaction(); double getAmount(); }
实例
下面的实例演示了上面接口的实现:
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
// LINQ(Language Integrated Query)语言集成查询是一组用于c#和Visual Basic语言的扩展。它允许编写C#或者Visual Basic代码以查询数据库相同的方式操作内存数据
// using System.Collections.Generic;
// using System.Linq;
// using System.Text;
// Interface 接口
namespace InterfaceApplication{
public interface SelfIntroduce{
// 接口里的方法
void show();
}
public class Girl : SelfIntroduce{
// 实现接口里的方法
public void show(){
Console.WriteLine("God is a girl");
}
}
public class Boya : SelfIntroduce{
// 实现接口里的方法
public void show(){
Console.WriteLine("this is a Boya");
}
}
class InterfaceTest{
static void Main(string[] args)
{
Girl girl = new Girl();
girl.show();
Boya boya = new Boya();
boya.show();
Console.ReadKey();
}
}
}
运行效果如下:
C# 命名空间(Namespace)
命名空间的设计目的是为了提供一种让一组名称与其他名称分隔开的方式。
在一个命名空间中声明的类的名称与另一个命名空间中声明的相同的类的名称不冲突。
定义命名空间
命名空间的定义是以关键字 namespace 开始,后跟命名空间的名称,如下所示:
namespace namespace_name { // 代码声明 }
为了调用支持命名空间版本的函数或变量,会把命名空间的名称置于前面,如下所示:
namespace_name.item_name;
下面的程序演示了命名空间的用法:
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
namespace space_1{
class class_1{
public void show(){
Console.WriteLine("space_1,class_1");
}
}
}
namespace space_2{
class class_2{
public void show(){
Console.WriteLine("space_2,class_2");
}
}
}
namespace NamespaceApplication{
class InterfaceTest{
static void Main(string[] args)
{
space_1.class_1 cls1 = new space_1.class_1();
cls1.show();
space_2.class_2 cls2 = new space_2.class_2();
cls2.show();
Console.ReadKey();
}
}
}
using 关键字
using 关键字表明程序使用的是给定命名空间中的名称。
例如,我们在程序中使用 System 命名空间,其中定义了类 Console。我们可以只写:
Console.WriteLine ("Hello there");
我们可以写完全限定名称,如下:
System.Console.WriteLine("Hello there");
您也可以使用 using 命名空间指令,这样在使用的时候就不用在前面加上命名空间名称。
该指令告诉编译器随后的代码使用了指定命名空间中的名称。
下面的代码演示了命名空间的应用。
让我们使用 using 指定重写上面的实例:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
using space_1;
using space_2;
namespace space_1{
class class_1{
public void show(){
Console.WriteLine("space_1,class_1");
}
}
}
namespace space_2{
class class_2{
public void show(){
Console.WriteLine("space_2,class_2");
}
}
}
namespace NamespaceApplication{
class InterfaceTest{
static void Main(string[] args)
{
class_1 cls1 = new class_1();
cls1.show();
class_2 cls2 = new class_2();
cls2.show();
Console.ReadKey();
}
}
}
嵌套命名空间
命名空间可以被嵌套,即您可以在一个命名空间内定义另一个命名空间,如下所示:
namespace namespace_name1 { // 代码声明 namespace namespace_name2 { // 代码声明 } }
您可以使用点(.)运算符访问嵌套的命名空间的成员,如下所示:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
using space_1;
// 命名空间的嵌套
using space_1.space_2;
namespace space_1{
class class_1{
public void show(){
Console.WriteLine("space_1,class_1");
}
}
namespace space_2{
class class_2{
public void show(){
Console.WriteLine("space_2,class_2");
}
}
}
}
namespace NamespaceApplication{
class InterfaceTest{
static void Main(string[] args)
{
class_1 cls1 = new class_1();
cls1.show();
class_2 cls2 = new class_2();
cls2.show();
Console.ReadKey();
}
}
}
C# 预处理器指令
预处理器指令指导编译器在实际编译开始之前对信息进行预处理。
所有的预处理器指令都是以 # 开始。
且在一行中,只有空白字符可以出现在预处理器指令之前。
预处理器指令不是语句,所以它们不以分号(;)结束。
C# 编译器没有一个单独的预处理器,但是,指令被处理时就像是有一个单独的预处理器一样。
在 C# 中,预处理器指令用于在条件编译中起作用。
与 C 和 C++ 不同指令不同,C#中的预处理指令可不是用来创建宏的说。???Excuse Me???
一个预处理器指令必须是该行上的唯一指令。意思就是说一行只能有一个指令
C# 预处理器指令列表
下表列出了 C# 中可用的预处理器指令:
预处理器指令 | 描述 |
---|---|
#define | 它用于定义一系列成为符号的字符。 |
#undef | 它用于取消定义符号。 |
#if | 它用于测试符号是否为真。 |
#else | 它用于创建复合条件指令,与 #if 一起使用。 |
#elif | 它用于创建复合条件指令。 |
#endif | 指定一个条件指令的结束。 |
#line | 它可以让您修改编译器的行数以及(可选地)输出错误和警告的文件名。 |
#error | 它允许从代码的指定位置生成一个错误。 |
#warning | 它允许从代码的指定位置生成一级警告。 |
#region | 它可以让您在使用Visual Studio Code Editor 的大纲特性时,指定一个可展开或折叠的代码块。 |
#endregion | 它标识着 #region 块的结束。 |
#define 预处理器
#define 预处理器指令创建符号常量。
#define 允许您定义一个符号,这样,通过使用符号作为传递给 #if 指令的表达式,表达式将返回 true。它的语法如下:
#define symbol
下面的程序说明了这点:
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
// 预处理指令
// 定义符号,必须在最上面,否则报错
// c#_77.cs(6,2): error CS1032: Cannot define/undefine preprocessor symbols after first token in file
// C#中,只是用于定义一个符号而已
#define PI
using System;
namespace PreCompileApplication{
class PreCompileTest{
static void Main(string[] args)
{
#if(PI)
Console.WriteLine("定义了PI这个符号");
#else
Console.WriteLine("未定义PI这个符号");
#endif
Console.ReadKey();
}
}
}
运行效果如下:
条件指令
您可以使用 #if 指令来创建一个条件指令。条件指令用于测试符号是否为真。如果为真,编译器会执行 #if 和下一个指令之间的代码。
条件指令的语法:
#if symbol [operator symbol]...
其中,symbol 是要测试的符号名称。您也可以使用 true 和 false,或在符号前放置否定运算符。
运算符符号是用于评价符号的运算符。
可以运算符可以是下列运算符之一:
- == (equality)
- != (inequality)
- && (and)
- || (or)
您也可以用括号把符号和运算符进行分组。
条件指令用于在调试版本或编译指定配置时编译代码。
一个以 #if 指令开始的条件指令,必须显示地以一个 #endif 指令终止。
下面的程序演示了条件指令的用法:
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
// 预处理指令
// 定义符号,必须在最上面,否则报错
// c#_77.cs(6,2): error CS1032: Cannot define/undefine preprocessor symbols after first token in file
// C#中,只是用于定义一个符号而已
#define Menma
#define Tiger
using System;
namespace PreCompileApplication{
class PreCompileTest{
static void Main(string[] args)
{
#if(Menma && Tiger)
Console.WriteLine("喜欢面码和逢坂大河");
#elif(!Menma && Tiger)
Console.WriteLine("喜欢逢坂大河");
#elif(Menma && !Tiger)
Console.WriteLine("喜欢面码");
#else
Console.WriteLine("不喜欢二次元");
#endif
Console.ReadKey();
}
}
}
运行效果:
目录
1. #define 和 #undef
2. #if、#elif、#else 和#endif
3. #warning 和 #error
4. #region 和#endregion
5. #line
6. #pragma
C#中有许多名为“预处理器指令”的命令。这些命令从来不会转化为可执行代码中的命令,但会影响编译过程的各个方面。
例如,使用预处理器指令可以禁止编译器编译代码的某一部分。如果计划发布两个版本的代码,即基本版本和拥有更多功能的企业版本,就可以使用这些预处理器指令。在编译软件的基本版本时,使用预处理器指令可以禁止编译器编译与额外功能相关的代码。
另外,在编写提供调试信息的代码时,也可以使用预处理器指令。实际上,在销售软件时,一般不希望编译这部分代码。
预处理器指令的开头都有符号#。
C++开发人员应知道,在C 和C++中预处理器指令非常重要,但是,在C#中,并没有那么多的预处理器指令,它们的使用也不太频繁。C#提供了其他机制来实现许多C++指令的功能,如定制特性。还要注意,C#并没有一个像C++那样的独立预处理器,C#所谓的预处理器指令实际上是由编译器处理的
尽管如此,C#仍保留了一些预处理器指令名称,因为这些命令会让人觉得就是预处理器。
下面简要介绍预处理器指令的功能。
1. #define 和 #undef
#define 的用法如下所示: #define DEBUG
它告诉编译器存在给定名称的符号,在本例中是DEBUG。这有点类似于声明一个变量,但这个变量并没有真正的值,只是存在而已。
这个符号不是实际代码的一部分,而只在编译器编译代码时存在。在C#代码中它没有任何意义。
#undef 正好相反—— 它删除符号的定义: #undef DEBUG
如果符号不存在,#undef 就没有任何作用。同样,如果符号已经存在,则#define 也不起作用。必须把#define 和#undef 命令放在C#源文件的开头位置,在声明要编译的任何对象的代码之前。
#define 本身并没有什么用,但与其他预处理器指令(特别是#if)结合使用时,它的功能就非常强大了。
这里应注意:预处理器指令不用分号结束,一般一行上只有一条命令。这是因为对于预处理器指令,C#不再要求命令使用分号进行分隔。如果它遇到一条预处理器指令,就会假定下一条命令在下一行上。
2. #if、#elif、#else 和#endif
这些指令告诉编译器是否要编译某个代码块。考虑下面的方法:
复制代码代码如下:
int DoSomeWork(double x)
{
// do something
#if DEBUG
Console.WriteLine("x is " + x);
#endif
}
这段代码会像往常那样编译,但Console.WriteLine 命令包含在#if 子句内。
这行代码只有在前面的#define 命令定义了符号DEBUG 后才执行。
当编译器遇到#if 语句后,将先检查相关的符号是否存在,如果符号存在,就编译#if 子句中的代码。否则,编译器会忽略所有的代码,直到遇到匹配的#endif 指令为止。
一般是在调试时定义符号DEBUG,把与调试相关的代码放在#if 子句中。在完成了调试后,就把#define 语句注释掉,所有的调试代码会奇迹般地消失,可执行文件也会变小,最终用户不会被这些调试信息弄糊涂(显然,要做更多的测试,确保代码在没有定义DEBUG 的情况下也能工作)。
这项技术在C 和C++编程中十分常见,称为条件编译(conditional compilation)。
#elif (=else if)和#else 指令可以用在#if 块中,其含义非常直观。也可以嵌套#if 块:
#line 164 "Core.cs" // We happen to know this is line 164 in the file Core.cs, before the intermediate package mangles it.
// later on
#line default // restores default line numbering
6. #pragma
#pragma 指令可以抑制或还原指定的编译警告。与命令行选项不同,#pragma 指令可以在类或方法级别执行,对抑制警告的内容和抑制的时间进行更精细的控制。???Excuse Me???
下面的例子禁止“字段未使用”警告,然后在编译MyClass 类后还原该警告
#pragma warning disable 649
public class MyClass
{
int neverUsedField;
}
#pragma warning restore 649
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
// 预处理指令
// 定义符号,必须在最上面,否则报错
// c#_77.cs(6,2): error CS1032: Cannot define/undefine preprocessor symbols after first token in file
// C#中,只是用于定义一个符号而已
#define Menma
#define Tiger
using System;
namespace PreCompileApplication{
// 在这个类的范围内 不产生 warning警告
#pragma warning disable 649
class PreCompileTest{
// 永不使用的字段
public int _ageNeverUsedField;
static void Main(string[] args)
{
#if(Menma && Tiger)
Console.WriteLine("喜欢面码和逢坂大河");
#elif(!Menma && Tiger)
Console.WriteLine("喜欢逢坂大河");
#elif(Menma && !Tiger)
Console.WriteLine("喜欢面码");
#else
Console.WriteLine("不喜欢二次元");
#endif
Console.ReadKey();
}
}
// 在这个类的范围外 恢复产生 warning警告
#pragma warning restore 649
}
运行效果如下:
C# 异常处理
异常是在程序执行期间出现的问题。C# 中的异常是对程序运行时出现的特殊情况的一种响应,比如尝试除以零。
异常提供了一种把程序控制权从某个部分转移到另一个部分的方式。
C# 异常处理是建立在四个关键词之上的:try、catch、finally 和 throw。
- try:一个 try 块标识了一个将被激活的特定的异常的代码块。后跟一个或多个 catch 块。
- catch:程序通过异常处理程序捕获异常。catch 关键字表示异常的捕获。
- finally:finally 块用于执行给定的语句,不管异常是否被抛出都会执行。例如,如果您打开一个文件,不管是否出现异常文件都要被关闭。
- throw:当问题出现时,程序抛出一个异常。使用 throw 关键字来完成。
语法
假设一个块将出现异常,一个方法使用 try 和 catch 关键字捕获异常。try/catch 块内的代码为受保护的代码,使用 try/catch 语法如下所示:
try{ // 引起异常的语句 }catch( ExceptionName e1 ){ // 错误处理代码 }catch( ExceptionName e2 ){ // 错误处理代码 }catch( ExceptionName eN ){ // 错误处理代码 }finally{ // 要执行的语句 }
您可以列出多个 catch 语句捕获不同类型的异常,以防 try 块在不同的情况下生成多个异常。
C# 中的异常类
C# 异常是使用类来表示的。
C# 中的异常类主要是直接或间接地派生于 System.Exception 类。
System.ApplicationException 和 System.SystemException 类是派生于 System.Exception 类的异常类。
System.ApplicationException 类支持由应用程序生成的异常。所以程序员自定义的异常都应派生自该类。
System.SystemException 类是所有C#预定义的系统异常的基类。
下表列出了一些派生自 Sytem.SystemException 类的预定义的异常类:
异常类 | 描述 |
---|---|
System.IO.IOException | 处理 I/O 错误。 |
System.IndexOutOfRangeException | 处理当方法指向超出范围的数组索引时生成的错误。 |
System.ArrayTypeMismatchException | 处理当数组类型不匹配时生成的错误。 |
System.NullReferenceException | 处理当依从一个空对象时生成的错误。 |
System.DivideByZeroException | 处理当除以零时生成的错误。 |
System.InvalidCastException | 处理在类型转换期间生成的错误。 |
System.OutOfMemoryException | 处理空闲内存不足生成的错误。 |
System.StackOverflowException | 处理栈溢出生成的错误。 |
异常处理
C# 以 try 和 catch 块的形式提供了一种结构化的异常处理方案。使用这些块,把核心程序语句与错误处理语句分离开。
这些错误处理块是使用 try、catch 和 finally 关键字实现的。
下面是一个当除以零时抛出异常的实例:
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
namespace ExceptionApplication{
class ExceptionTest{
int _result;
public ExceptionTest(){
_result = 0;
}
public void division(int n1,int n2){
try{
_result = n1 / n2;
}catch(DivideByZeroException e){
_result = 0;
Console.WriteLine("异常:{0}",e);
}finally{
Console.WriteLine("{0} / {1} = {2}",n1,n2,_result);
}
}
static void Main(string[] args)
{
ExceptionTest exc = new ExceptionTest();
exc.division(520,5);
exc.division(67,0);
Console.ReadKey();
}
}
}
运行效果:
创建用户自定义异常
您也可以定义自己的异常。用户自定义的异常类是派生自 ApplicationException 类。
下面的实例演示了这点:
代码如下:
// 注意是大写的
// 注意:C#里方法名首字母一般大写
using System;
namespace BeyondExceptionApplication{
public class Girl{
int _age;
public Girl(int age){
_age = age;
}
public void kissAndLove(){
if(_age < 14){
string msg = String.Format("才{0}eld呀,call police啊,违法啦,三年起步呀",_age);
throw (new DangerousBehaviorException(msg));
}else{
Console.WriteLine("with {0} eld girl together kiss and love",_age);
}
}
}
// 自定义异常
public class DangerousBehaviorException : ApplicationException{
// 调用父类的构造方法
public DangerousBehaviorException(string e):base(e){
// do nothing
}
}
class BeyondExceptionTest{
static void Main(string[] args)
{
Girl youngGirl = new Girl(16);
youngGirl.kissAndLove();
Girl cuteGirl = new Girl(13);
cuteGirl.kissAndLove();
Console.ReadKey();
}
}
}
效果如下:
抛出对象
如果异常是直接或间接派生自 System.Exception 类,您可以抛出一个对象。您可以在 catch 块中使用 throw 语句来抛出当前的对象,如下所示:
Catch(Exception e) { ... Throw e }
未完待续,下一章节,つづく