5.1类的定义和声明
5.1.1构造函数
我们构造对象的时候,对象的初始化过程是自动完成的,但是在初始化对象的过程中有的时候需要做一些额外的工作,例如需要初始化对象存储的数据,构造函数就是用于初始化数据的函数。
声明基本的构造函数的语法就是声明一个和所在类同名的方法,但是该方法没有返回类型。
public class MyClass{
public MyClass(){
这个构造函数的函数体
}
}
当我们使用new关键字创建类的时候,就会调用构造方法。
我们一般会使用构造方法进行初始化数据的一些操作。
构造函数可以进行重载,跟普通函数重载是一样的规则。
当我们不写,任何构造函数的时候,编译器会提供给我们一个默认的 无参的构造函数,但是如果我们定义了一个或者多个构造函数,编译器就不会再提供默认的构造函数。
5.1.2属性的定义
属性的定义结构:
public int MyIntProp{
get{
// get code
}
set{
//set code
}
}
定义属性需要名字和类型。
属性包含两个块 get块和set块。
访问属性和访问字段一样,当取得属性的值的时候,就会调用属性中的get块,所以get块,类型需要一个返回值就是属性的类型;当我们去给属性设置值的时候,就会调用属性中的set块,我们可以在set块中通过value访问到我们设置的值。
5.1.3通过属性访问字段
我们习惯上把字段设置为私有的,这样外界不能修改字段的值,然后我们可以通过定义属性来设置和取得字段中的值。
private int age;
public int Age{//习惯上属性大写 字段小写
set{
if(value<0)return;
age = value;
}
get{
return age;
}
}
5.1.4设置属性的只读或只写
属性可以值只提供一个set块或者get块
5.1.5属性访问修饰
5.1.6匿名类型
我们创建变量(对象的时候),必须指定类型,其实我们也可以不去指定类型,这个就是匿名类型,我们可以使用var声明一个匿名类型。
使用var声明的匿名类型,当初始化的时候,这个变量的类型就被确定下来,并且以后不可以修改。
var v = new Vehicle();
var v1 = "Lemon";
//var v1 = 1;//这里不允许,v1的类型已被确定为字符串
public class Vector3//设置为public这样在别的项目中才可以访问
{
//我们定义了一个构造函数,那么编译器不会为我们提供构造函数了
public Vector3()
{
Console.WriteLine("Vector3的构造函数被调用了");
}
public Vector3(float x, float y, float z)
{
this.x = x;
this.y = y;
this.z = z;
length = Length();
}
//编程规范上习惯把所有的字段设置为private,只可以在类内部访问,不可以通过对象访问
private float x, y, z, length;
private int age;
//private string name;
//public String Name
//{
// get { return name; }//可以通过对象获取属性的值
// private set { name = value; }//只可以在类内部设置属性的值
//
public string Name { get; set; }//编译器会自动给我们提供一个字段,来存储name(注意我们在定义字 //段的时候并没有定义name)
public int Age
{
set
{
//通过set方法 在设置值之前做一些校验的工作
if (value >= 0)
{
age = value;
}
}
}
public float X// 也可以叫做get set方法
{
get { return x; }
set { x = value; }//如果在get 或者 set前面加上 private,表示这个块只能在类内部调用
}
//为字段提供set方法,来设置字段的值
public void SetX(float x)
{
//如果我们直接在方法内部访问同名的变量的时候,优先访问最近 (形参)
//我们可以通过this.表示访问的是类的字段或者方法
this.x = x;
}
public void SetY(float y)
{
this.y = y;
}
public void SetZ(float z)
{
this.z = z;
}
public float Length() {
return (float)Math.Sqrt(x * x + y * y + z * z);
}
//定义属性
public int MyIntProperty {
set {
Console.WriteLine("属性中set块被调用");
Console.WriteLine("在set块中访问value的值是:"+value);
}
//如果没有get块,就不能通过属性取值了
get {
Console.WriteLine("属性中的get块被调用 ");
return 100;
}
}
}
5.2继承
5.2.1继承的类型
实现继承:
表示一个类型派生于一个基类型,它拥有该基类型的所有成员字段和函数。 在实现继承中,派生类型采用基类型的每个函数的实现代码,除非在派生类型的定义中指定重写某个函数的实现代码。 在需要给现有的类型添加功能,或许多相关的类型共享一组重要的公共功能时,这种类型的继承非常有用。
namespace c_sharp_001
{
class Enemy
{
private int hp;
private int speed;
public int Hp
{
get { return hp; }
set { hp = value; }
}
public int Speed
{
get { return speed; }
set { speed = value; }
}
public void AI()
{
Console.WriteLine("这里是Enemy1的公有AI方法");
}
public void MoveI()
{
Console.WriteLine("这里是Enemy的公有Move方法");
}
}
}
namespace c_sharp_001
{
class Boss:Enemy
{
public void Attack()
{
//hp = 100;
Hp = 100;//父类里公有的数据和函数成员在子类里才可以访问
AI();
MoveI();
Console.WriteLine("Boss正在攻击");
}
}
}
namespace c_sharp_001
{
class Type1Enemy:Enemy
{
}
}
namespace c_sharp_001
{
class Program
{
static void Main(string[] args)
{
Boss boss = new Boss();
boss.AI();
boss.Attack();
Enemy enemy;
enemy = new Boss();//父类声明的对象可以用子类构造
Boss bossEnemy = (Boss)enemy;//enemy虽然使用了父类进行声明,但是用子类构造(实例化),本质上是一
//个子类类型,我们可以强制类型转换成子类类型
Enemy enemy1 = new Enemy();
Boss bossEnemy1 = (Boss)enemy1;//强制类型转换错误,一个对象是什么类型,主要看它是通过什么构造的
Console.ReadKey();
}
}
}
接口继承:
表示一个类型只继承了函数的签名,没有继承任何实现代码。 在需要指定该类型具有某些可用的特性时,最好使用这种类型的继承。
多重继承(C#不支持):
一些语言(C++)支持所谓的“多重继承”,即一个类派生自多个类。使用多重继承的优点是有争议的:一方面,毫无疑问,可以使用多重继承编写非常复杂但很紧凑的代码。另一方面,使用多重实现继承的代码常常很难理解和调试。如前所述,简化健壮代码的编写工作是开发 C#的重要设计目标。因此,C#不支持多重实现继承。而 C#允许类型派生自多个接口——多重接口继承。这说明,C#类可以派生自另一个类和任意多个接口。更准确地说, System.Object是一个公共的基类,所以每个C#(除了Object类之外)都有一个基类,还可以有任意多个基接口。
5.3虚方法
把一个基类函数声明为virtual,就可以在任何派生类中重写该函数:
class MyBaseClass{
public virtual string VirtualMethod(){
return "Method is called in base class";
}
}
在派生类中重写另外一个函数时,要使用override关键字显示声明
class MyDerivedClass:MyBaseClass{
public override string VirtualMethod(){
return "Method is called in derivedclass.";
}
}
我们在子类里面重写虚函数之后,不管在哪里调用都是调用重写之后的方法
namespace c_sharp_001
{
class Enemy
{
public void AI()
{
Move();
Console.WriteLine("这里是Enemy1的公有AI方法");
}
public virtual void Move()
{
Console.WriteLine("这里是Enemy的公有Move方法");
}
}
}
namespace c_sharp_001
{
class Boss:Enemy
{
public override void Move()//重写:原来的方法不存在了
{
Console.WriteLine("这里是Boss的移动方法");
}
public void Attack()
{
Move();//子类里对Move进行重写,调用的子类里的Move函数
Console.WriteLine("Boss正在攻击");
}
}
}
namespace c_sharp_001
{
class Program
{
static void Main(string[] args)
{
Boss boss = new Boss();
boss.Attack();
boss.AI();//父类中AI方法调用的Move方法已在子类中重写
//我们在子类里面重写虚函数之后,不管在哪里调用都是调用重写之后的方法
Enemy enemy = new Enemy();
enemy.AI();//子类里重写的Move方法不会影响父类构造的对象
Console.ReadKey();
}
}
}
5.4隐藏方法
如果签名(就是方法的定义,返回值、参数、方法名)相同的方法在基类和派生类中都进行了声明,但是该方法没有分别声明为virtual和override,派生类就会隐藏基类方法。(要使用new关键字进行声明)
基类
class MyBaseClass{
public int MyMethod(){
}
}
派生类(在派生类中把基类同名的方法隐藏掉了)
class MyDerivedClass :MyBaseClass{
public new void MyMethod() {
}
}
namespace c_sharp_001
{
class Enemy
{
public void Move()
{
Console.WriteLine("这里是Enemy的公有Move方法");
}
}
}
namespace c_sharp_001
{
class Boss:Enemy
{
public new void Move()//当子类里有一个和父类签名相同的方法时,就会把父类中的方法隐藏
{ //隐藏:原来的方法还存在,只是父类中的方法被隐藏了
Console.WriteLine("这里是Boss的移动方法");
}
}
}
namespace c_sharp_001
{
class Program
{
static void Main(string[] args)
{
Boss boss = new Boss();
boss.Move();//使用子类声明的对象,调用隐藏方法会调用子类的
Enemy boss1 = new Boss();
boss1.Move();//使用父类声明的对象,调用隐藏方法会调用父类的
Console.ReadKey();
}
}
}
5.5.this和base关键字
this可以访问当前类中定义的字段,属性和方法,有没有this都可以访问,有this可以让IDE-VS编译器给出提示。另外当方法的参数跟字段重名的时候,使用this可以表明访问的是类中的字段。
base可以调用父类中的公有方法和字段,有没有base都可以访问,但是加上base.IED工具会给出提示,把所有可以调用的字段和方法罗列出来方便选择。
5.6抽象类
C#允许把类和函数声明为 abstract。抽象类不能实例化,抽象类可以包含普通函数和抽象函数,抽象函数就是只有函数定义没有函数体。显然,抽象函数本身也是虚拟的Virtual(只有函数定义,没有函数体实现)。
类是一个模板,那么抽象类就是一个不完整的模板,我们不能使用不完整的模板去构造对象。
abstract class Building{
public abstract decimal CalculateHeatingCost();
namespace c_sharp_001
{
abstract class Bird//抽象类
{
private float speed;
public void Eat()
{
}
public abstract void Fly();
}
}
namespace c_sharp_001
{
class Crow : Bird//继承了一个抽象类,就必须去实现抽象方法
{
public override void Fly()
{
Console.WriteLine("乌鸦在飞行");
}
}
}
namespace c_sharp_001
{
class Program
{
static void Main(string[] args)
{
Crow crow = new Crow();
crow.Fly();
Bird bird = new Crow();//可以通过抽象类去声明对象,但不能去构造对象
bird.Fly();
Console.ReadKey();
}
}
}
5.7密封类和密封方法
C#允许把类和方法声明为sealed。对于类,这表示不能继承该类;对于方法(把重写的方法声明为sealed)表示不能再次重写该方法。防止重写某些类导致代码混乱或商业原因
sealed FinalClass
{
// etc
}
5.8派生类的构造函数
在子类中调用父类的默认构造函数(无参)(会先调用父类的,然后是子类的)
public class MyDerivedClass{
public MyDerivedClass():base(){
//do something
}
}
在这里 :base()可以直接不写,因为默认会调用父类中的默认构造函数
调用有参数的构造函数
public class MyDerivedClass{
public MyDerivedClass(string name):base(name){
//do something
}
}
namespace c_sharp_001
{
class BaseClass
{
private int x;
public BaseClass()
{
Console.WriteLine("BaseClass无参构造函数");
}
public BaseClass(int x)
{
this.x = x;
Console.WriteLine("x赋值完成");
}
}
}
namespace c_sharp_001
{
class DerivedClass:BaseClass
{
private int y;
public DerivedClass() : base()//调用父类中无参的构造函数
{ //当没有在子类中显示调用父类的构造函数,会默认调用父类的无参构造函数
Console.WriteLine("DerivedClass无参构造函数");
}
public DerivedClass(int x,int y):base(x)
{
this.y = y;
Console.WriteLine("y赋值完成");
}
}
}
5.9修饰符
protected保护的,当没有继承的时候,它的作用和private是一样的,当有继承的时候,protected表示可以被子类访问的字段或者方法。
static可以修饰字段或者方法,修饰字段的时候,表示这个字段是静态的数据,叫做静态字段或者静态属性;修饰方法的时候,叫做静态方法或者静态函数。使用static修饰的成员,只能通过类名访问,当我们构造对象的时候,对象中只包含了普通的字段不包含静态字段。
5.10接口
定义一个接口在语法上跟定义一个抽象类完全相同,但不允许提供接口中任何成员的实现方式,一般情况下,接口只能包含方法,属性,索引器和事件的声明(抽象方法)。
接口不能有构造函数,也不能有字段,接口也不允许运算符重载。
接口定义中不允许声明成员的修饰符,接口成员都是公有的。
接口可以彼此继承,其方式和类的继承方式相同。
namespace c_sharp_001
{
interface IFly
{
void Fly1();//不可用修饰符修饰,默认public
void Fly2();
}
}
namespace c_sharp_001
{
interface IA
{
void MethodA();
}
}
namespace c_sharp_001
{
interface IB:IA
{
void MethodB();
}
}
namespace c_sharp_001
{
class Bird : IFly,IB//类中要实现其继承的接口中的所有方法
{
public void Fly1()
{
}
public void Fly2()
{
}
public void MethodA()//IB继承自IA的方法
{
}
public void MethodB()//IB中的方法
{
}
}
}