面向对象编程——类与对象(C#)

属性提供灵活的机制来读取、编写或计算私有字段的值,而方法则是以一部分代码构成代码块的形式存在,用来实现一部分特定的功能。

方法

方法是一种用于实现可以由对象(或类)执行的计算机或者操作的成员,是一个已命名的语句集。每个方法都有一个名称和一个主体。

方法的专用明

方法在类或结构中声明,声明时需要指定修饰符、返回类型、方法名称以及参数。方法的参数放在括号中,并用逗号隔开。空括号表示 方法不需要参数。

定义方法的语法如下:

<访问修饰符> <返回类型> <方法名称>(参数列表)

{

        //方法主体

}

访问修饰符:主要用于决定变量或方法对于另一个类的可见性。

返回类型:一个方法可以返回一个值。返回类型是方法返回的值 的数据类型。如果方法不返回任何值 ,则返回类型为void。

方法名称:必须是一个有意义的标识符,并且区分大小写。该标识符应该描述出方法的用途,但是不能与类中声明的其他标识符相同。

参数列表:是指在方法名后面括号内的一系列参数,这些参数是用来传递和接收方法的数据。

方法主体:包含调用方法时的实际执行语句。

用户可以为大多数方法提供一些数据来处理,并让其返回一些有用的信息。同时方法还是一种基本的、功能强大的编译机制。

C#中调用方法

调用代码中的方法,可以简化大量重复性的操作。

编写程序,分别计算7、6、5、4的阶乘。

using System;
namespace Project1
{
    class Program
    {
        static void Main(string[] args)
        {
            //求7的阶乘
            int factorial = 1;
            for (int i = 7; i > 0; i--) factorial *= i;
            Console.WriteLine("7! ={0}\n", factorial);
            //求6的阶乘
            factorial = 1;
            for (int i = 6; i > 0; i--) factorial *= i;
            Console.WriteLine("6! ={0}\n", factorial);
            //求5的阶乘
            factorial = 1;
            for (int i = 5; i > 0; i--) factorial *= i;
            Console.WriteLine("5! ={0}\n", factorial);
            //求4的阶乘
            factorial = 1;
            for (int i = 4; i > 0; i--) factorial *= i;
            Console.WriteLine("4! ={0}\n", factorial);
        }
    }
}

运行结果如下:

编写程序,通过对方法的调用,对上例进行优化。 

using System;
namespace Project2
{
    class Program
    {
        static void Factorial(int a)
        {
            int factorial = 1;
            for (int i = a; i > 0; i--)
            {
                factorial *= i;
            }
            Console.WriteLine("{0}的阶乘={1}\n", a, factorial);
        }
        static void Main(string[] args)
        {
            Factorial(7);
            Factorial(6);
            Factorial(5);
            Factorial(4);
        }
    }
}

运行结果如下:

在C#语言中,除了Main方法以外,其他所有的方法都允许调用其它方法或者被其他方法调用。同时方法还可以对自身进行调用,所以对调用自身的方法又称之为递归调用。

编写程序,使用递归调用,求出8的阶乘。 

using System;
namespace Project3
{
    class Program
    {
        static int Method(int n)   //用户自定义求阶乘的方法
        {
            int num = 0;
            if (n == 0)
            {
                return 1;
            }
            else
            {
                num = n * Method(n - 1);  //在Method方法中调用自身
            }
            return num;
        }
        static void Main(string[] args)
        {
            Console.WriteLine("递归调用8!={0}", Method(8));
        }
    }
}

运行结果如下:

传递参数

参数用于向方法传递值 或引用。参数有两种形式:用户自定义方法括号里面的变量名,称为形式参数,简称形参;在Main方法中调用另一个方法时,方法名后面括号中的参数称为实际参数,简称实参。方法的参数从方法被调用时指定的实参获取它们的实际值。

参数可以通过以下四种类型进行传递。

1、按值参数传递

按值参数进行传递时,方法中的变量修改不会影响参数列表。因为在传值过程中会为每个参数创建一个新的存储位置,并且实际参数的值会复制给形参,因此实参和形参使用的是两个不同内存中的值 。所以,当形参的值发生改变时,不会影响实参的值,从而保证了实参数据的安全。

编写程序,将变量y按值传递给方法Method。

using System;
namespace Project4
{
    class Program
    {
        public void Method(int x)   //形参x是通过值传递的
        {
            x *= x; //	对x的更改不会影响x的原始值
            Console.WriteLine("Method方法内的值: {0}", x);
        }
        static void Main()
        {
            Program n = new Program();
            int y = 9;
            Console.WriteLine("调用Method方法之前的值: {0}", y);
            n.Method(y);  // 实参y是通过值传递变量
            Console.WriteLine("调用Method方法后的值: {0}", y);
        }
    }
}

2、按引用参数传递

该方式就是对变量内存位置的引用。与值参数不同的是,它不会为这些参数创建一个新的存储位置。引用参数与提供给方法的实际参数具有相同的内存位置。在C#中,使用ref关键字声明引用参数。

编写程序,将变量y按引用传递给方法Method。

using System;
namespace Project5
{
    class Program
    {
        public void Method(ref int x)//形参x通过引用传递
        {
            x *= x;// 对x的改变会影响x的原始值。
            Console.WriteLine("Method方法内部的值: {0}", x);
        }
        static void Main()
        {
            Program n = new Program();
            int y = 7;
            Console.WriteLine("Main方法内部的值: {0}", y);
            n.Method(ref y);  // 实参y是通过引用传递变量
            Console.WriteLine("通过引用传递变量: {0}", y);
        }
    }
}

3、按输出参数传递

用户自定义的方法一般可以通过return语句返回一个值。如果希望方法返回多个值,可以按输出参数的方式进行传递参数。对于输出参数来说,调用方提供的实参的初始值并不重要,除此之外,输出参数与引用参数类似。输出参数是用out修饰符声明的。

编写程序,求出数组中的最大值进行返回,并返回最大值的一个索引。

using System;
namespace Project6
{
    class Program
    {
        //需要返回两个参数,一个是数组元素的最大值,另一个是最大值的索引
        static int MaxNum(int[] arr, out int num)
        {
            //声明变量maxNum为最大值,并且假定数组的第一个元素为最大值
            int maxNum = arr[0];
            num = 0;      //因此这个索引值也为0
            for (int i = 0; i < arr.Length; i++)
            {
                if (maxNum < arr[i])
                {
                    maxNum = arr[i];
                    num = i;
                }
            }
            return maxNum;
        }
        static void Main(string[] args)
        {
            int[] myArr = { 12, 4, 567, 3, 9, 6, 8 };
            int max;
            Console.WriteLine("数组myArr最大的元素值是{0},其索引值是{1}", MaxNum(myArr, out max), max);
        }
    }
}

4、按参数数组传递

当声明一个方法时,用户不能确定要传递的参数个数时,可使用params关键字来定义。在C#中使用参数数组可以简化代码,因为在调用代码时就可以不必传递数组,而是传递同类型的几个参数。

编写程序,用于计算整形元素的和。

using System;
namespace Project7
{
    class Program
    {
        static int Add(params int[] arr)
        {
            int sum = 0;
            foreach (int outarr in arr)
            {
                sum += outarr;
            }
            return sum;
        }
        static void Main(string[] args)
        {
            int[] myArr = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            Console.WriteLine("myArr数组中所有元素的和为:" + Add(myArr));
        }
    }
}

方法的分类

方法分为静态方法和实例方法。其中,使用static修饰符声明的方法称为静态方法,而不使用static修饰符声明的方法称为实例方法。

1、静态方法

静态方法不对特定实例进行操作,并且只能访问类中的静态成员,而且不能使用实例成员。访问静态方法时可以直接访问,也可以使用类名而不需要创建对象,也不能使用对象名来引用。

2、实例方法

实例方法对特定实例进行操作,并且能够访问静态成员和实例成员。在调用实例方法的实例上,可以通过this显式地访问该实例。而在静态方法中引用this是错误的。

编写程序,演示静态方法与实例方法的使用。

using System;
namespace Project8
{
    class Program
    {
        int exampleVer;         //实例成员
        static int staticVer;  //静态成员
        void exampleMethod()   //实例方法
        {
            //在实例方法中,可以访问静态成员,也能访问实例成员
            exampleVer = 1;     //等价于this.exampleVer = 1;
            staticVer = 1;
            Console.WriteLine("实例成员:{0}", exampleVer);
            Console.WriteLine("静态成员:{0}", staticVer);
        }
        static void staticMethod()//静态方法
        {
            //在静态方法中,可以访问静态成员,但不能访问实例成员
            staticVer = 2;
            Console.WriteLine("静态成员:{0}", staticVer);
        }
        static void Main(string[] args)
        {
            //访问静态方法
            //调用静态方有两种,一种可以直接调用,另一种使用类名
            Program.staticMethod();
            //访问实例方法
            Program p = new Program();
            p.exampleMethod();
        }
    }
}

运行结果:

外部方法

当方法声明包含extern修饰符时,称该方法为外部方法。外部方法是在外部实现的,编程语言通常是使用C#以外的语言。外部方法不可以是泛型。

extern修饰符通常与“Dll Import”属性一起使用,从而使外部方法可以由DLL(动态链接阵)实现。执行环境可以支持其它用来提供外部方法实现的机制。当外部方法包含“Dll Import”属性时,该方法声明必须同时包含一个static修饰符。

编程程序,利用extern修饰符和“Dll Import”属性,调用“User32.dll”库文件,实现自定义信息提示框的功能。

using System;
//使用DllImport属性之前应该引入命名空间
using System.Runtime.InteropServices;
namespace Project9
{
    class Program
    {
        [DllImport("User32.dll")]
        public static extern int MessageBox(int h, string m, string c, int type);
        static int Main(string[] args)
        {
            Console.Write("请输入您的姓名:");
            string name = Console.ReadLine();
            return MessageBox(0, "您好:" + name + "\n\n" + "欢迎学习C#", "信息提示", 0);
        }
    }
}

代码如下:

Main方法

Main方法是程序的入口点。程序从这里开始,也是从这里结束。C#的Main方法必须是一个类的静态成员。Main方法可以是void类型也可以是否void类型,并可以接受字符串数组形式的命令行参数。

1、Main方法的执行过程

当程序在执行编写的源代码时,会先找Main方法,然后开始执行Main方法中“{”开始后的第一句代码,并依次执行。如果遇到Main方法中有调用其他的方法时,便会根据方法名称找到定义方法的代码,然后执行这个方法内的代码,执行完这个方法后,再返回到Main方法继续执行,直到遇到Main方法的结束符“}”,执行程序结束。

2、Main方法的四种表现形式

(1)静态的无返回值的Main方法。

(2)静态的有返回值的Main方法。

(3)静态的无返回值,并且有参数的Main方法。

(4)静态的有返回值,并且有参数的Main方法。

上述有返回值int时,可以用于表示应用程序的终止方式,通常用做一种错误提示。一般情况下,返回“0”表示程序“正常”终止(即应用程序执行完毕,并安全地终止)。

Main的可选参数args是从应用程序的外部接受信息的方法,这些信息在运行应用程序时以命令行参数的形式指定。

注意:

(1)当创建一个项目的时候,编译器会默认创建一个第三种形式的Main方法,并且默认使用这个Main方法。

(2)在一个程序中,Main方法只能有一个,并且该方法的位置不必固定,C#编译器找到Main方法,并将其作为这个程序的入口。

(3)在编写程序时,尽量不要修改编译器自动生成的Main方法。

类与对象

类就是个模子,确定了对象应用具有的属性和方法。对象是根据类创建出来的。类不占内存,对象占内存。

类的定义

类是一种数据结构,可以包含数据成员(常量和字段)、函数成员(方法、属性、事件、索引器、运算符、实例构造函数、静态构造函数和析构函数)以及嵌套类型。

C#中一切类型都为类,除了引用的命名空间外,所有的语句都必须位于类(或者结构)内,不存在任何处于类(或结构)之外的语句。因此,类是C#语言的核心和基本构成模块。默认代码中包含一个Program类。

当用户定义一个类时,相当于定义了一个数据类型的蓝图。这实际上并没有定义任何的数据,但是它定义了类的名称,也就是说,类的对象由什么组成以及在这个对象上可执行什么操作。对象是类的实例,构成类的方法和变量称为类的成员。

类定义的语法结构如下:

<访问修饰符> class <类名> { 类成员 (字段、属性、方法、事件)}

class关键字前面是访问修饰符。

所有类型和类型成员都具有可访问性级别,该级别可以控制是否可以从自己的程序集或其他程序集中的其他代码中使用它们。

访问修饰符
访问修饰符说明
public公有访问。不受任何限制
private私有访问。只限于本类成员访问,子类、实例都不能访问
protected保护访问。只限于本类和子类访问,实例不能访问
internal内部访问。只限于本项目内访问,其他不能访问
protected internal内部保护访问。只限于本项目或是子类访问,其他不能访问

注意:

(1)类的命名空间默认修饰符为:public。

(2)类默认的访问修饰符为:public。

(3)类的成员默认的访问修饰符为:private。

(4)类成员的作用域永远超不出包含它的类。

(5)可以声明一个静态类static class ClassName,但是它的成员必须全部显示定义为静态。

类的实例化

类的实例化就是创建一个真实的对象。可通过使用new关键字,后跟对象要基于的类的名称。

实例化对象的语法格式如下:

<类名> <对象名> = new <类名>([构造函数的参数])

(1)把定义好的类当作类型来使用,称为引用类型。如果声明引用类型的变量,此变量就会一直包含值 null。

(2)使用new运算符能显示创建出类的实例。或者为引用类型的变量分配已在其它位置创建的对象。

类的成员

类具有表示其数据和行为的成员。类的成员包括在类中声明的所有成员,以及在该类的继承层次结构中的所有类中声明的所有成员(构造函数和析构函数除外)。基类中的私有成员被继承,但不能从派生类访问。

类的成员
成员描述
字段字段是在类范围声明的变量。字段可以是内置数值类型或其他类的实例。
常量常量是在编译时设置其值并且不能更改其值的字段或属性
属性属性是类中可以像类中的字段一样访问的方法。属性可以为类字段提供保护,以避免字段在对象不知道的情况下被更改
方法方法定义类可以执行的操作。方法可接受提供输入数据的参数,并可通过参数返回输出数据。方法还可以不使用参数而直接返回值
事件事件向其他对象提供有关发生的事情(如单击按钮或成功完成某个方法)的通知。事件是使用委托定义和触发的
运算符重载运算符被视为类成员。在重载运算符时,在类中将该运算符定义为公共静态方法。预定义的运算符(+、*、<等)不被视为成员
索引器使用索引器可以用类似于数组的方式为对象建立索引
构造函数构造函数是首次创建对象时调用的方法。它们通常用于初始化对象的数据
终结器C#中很少使用终结器。终结器是当对象即将从内存中移除时由运行时执行引擎调用的方法。它们通常用来确保任何必须释放的资源都得到适当的处理
嵌套类型嵌套类型是在其他类型中声明的类型。嵌套类型通常用于描述仅由包含它们的类型使用的对象

字段的使用

字段是在类或结构中直接声明的任意类型的变量。通常字段是用来存放数据,它可以存放多个值,而变量只能存放一个值。字段的命名规范采用的是camelCase,为了与变量区分,最好在前面加一个下画线“_”。

1、声明字段

声明字段时,可以使用赋值运算符为字段指定一个初始值。

2、访问字段 

若要访问对象中的字段,需要在对象名称后添加一个句点,后跟字段的名称。

注意:this关键字表示当前已被实例化的对象,同时,也可使程序更具完整性。

编写程序,声明一个职员类,用于存放员工的个人信息。

using System;
namespace Project10
{
    enum Gender//将性别最好声明为一个枚举
    {
        男,
        女
    }
    class Clerk
    {
        public string _name;  //姓名
        public Gender _gender;   //性别
        public int _age;       //年龄
        public string _department;  //部门
        public int _workYears;  //工作年限
        public void Write()    //定义非静态方法
        {
            Console.WriteLine("我叫{0},我是{1}生,我{2}岁了,我在{3}任职,我工作了{4}年", _name, _gender, _age, _department, _workYears);
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //将类实例化
            Clerk zs = new Clerk();
            zs._name = "张三";
            zs._gender = Gender.男;
            zs._age = 25;
            zs._department = "人力部";
            zs._workYears = 5;
            zs.Write();  //调用非静态方法 
            Clerk ls = new Clerk();
            ls._name = "李四";
            ls._gender = Gender.女;
            ls._age = 35;
            ls._department = "财务部";
            ls._workYears = 3;
            ls.Write();  //调用非静态方法
            Console.WriteLine(zs._name);   //字段
            Console.WriteLine(ls._name);
        }
    }
}

运行结果:

我叫张三,我是男生,我25岁了,我在人力部任职,我工作了5年
我叫李四,我是女生,我35岁了,我在财务部任职,我工作了3年
张三
李四

属性

使程序员可以创造新的声明性信息的种类,称为属性。属性是对现实世界中实体特征的抽象,是为访问自定义类型的注释信息提供通用的访问方式。

属性是字段的自然扩展。属性和字段都是类的成员,都具有相关的类型,并且用于访问字段和属性的语法也相同。然后,与字段不同的是属性不会被归类为变量。因此,不能将属性作为ref或out参数传递。但是,属性有访问器,这些访问器指定在它们的值被读取或写入时需执行的语句。

对于类的实现者来说,属性是两个代码块,表示get访问器和set访问器。读取属性时,执行get访问器的代码块;向属性赋予新值时,执行set访问器的代码块。通常将不带set访问器的属性视为只读;将不带get访问器的属性视为只写;将具有以上两个访问器的属性视为读写。

1、属性的定义

自定义的属性在类模块中首先要指定属性的访问修饰符,后面是属性的数据类型,接下来是属性的名称,然后是声明get访问器和(或)set访问器的代码块。

属性声明的语法格式如下:

访问修饰符 数据类型 属性名

{

        get                //读访问器,通过它外部用户可以读取属性的值

        {

                return 字段;

        }

        /*写访问器,通过它外部用户可以为属性赋值,用户输入的值就存放在value关键字中,并可以进行输入值验证*/

        set

        {

                字段=value;

        }

}

2、属性的使用

程序中调用属性的语法格式如下:

对象名.属性名

注意:

(1)如果要在其他类中调用定义好的属性,必须将该属性的访问修饰符设置为public,字段设置为private。

(2)如果属性为只读属性,则不能在调用时为其赋值,否则将产生异常。

3、get访问器

get访问器与Main方法类似,它必须 返回属性类型的值。执行get访问器等效于读取字段的值。

引用属性时,除了作为赋值目标外,还调用get访问器读取属性值。

注意:

(1)get访问器必须以return或throw语句结尾。

(2)使用get访问器更改对象的状态是一种糟糕的编程风格。

(3)get访问器可以用于返回字段值或计算并返回字段值。

4、set访问器

set访问器类似于返回类型为void的方法。它使用名为value的隐式参数,该参数的类型为属性的类型。

向属性赋值时,通过使用提供新值的自变量调用set访问器。

编写程序,定义两个属性,对员工的信息进行修改。

using System;
namespace Project11
{
    public class Clerk    //创建一个员工的类
    {
        private string _name = "张三"; //定义string类型的字段,用来记录员工姓名呢
        private int _age;     //定义int类型的字段_age,用来记录员工年龄
        public int Age         //用户定义年龄属性,该属性用于对输入的年龄进行限定
        {
            get
            {
                return _age;
            }
            set     //可以用于对赋值进行限定 value
            {
                //如果输入的年龄不在18到60的之间,就给value赋值为0
                if (value < 18 || value > 60)
                {
                    value = 0;
                }
                _age = value;
            }
        }
        private char _gender;     //定义char类型的字段_gender,用来记录员工性别
        public char Gender     //用户定义性别属性,该属性用于选取员工的性别进行限定
        {
            get        //可以用于对取值进行限定
            {
                //员工性别只有‘男’和‘女’,不管输入什么,性别只选取‘男’
                if (_gender != '男' || _gender != '女')
                {
                    _gender = '男';
                }
                return _gender;
            }
            set
            {
                _gender = value;
            }
        }
        public void Write()   //非静态方法
        {
            Console.WriteLine("新入职员工:\n姓名:{0}\t年龄:{1}\t性别:{2}", _name, Age, Gender);
        }
    }
    class Program
    {
        static void Main()
        {
            Clerk n = new Clerk();   //创建对象
            n.Age = 66;   //通过属性名来为字段进行赋值
            n.Gender = '女';
            n.Write();
            Console.WriteLine("修改员工信息");
            n.Age = 45;
            n.Write();
        }
    }
}

运行结果:

新入职员工:
姓名:张三      年龄:0 性别:男
修改员工信息
新入职员工:
姓名:张三      年龄:45        性别:男

分部类与方法

将同一个类编写在多个文件中,类的各个文件名不同,类名相同,类名前加partial关键字,这种类型叫做分部类。在分部类中可以建立分部方法,也需要在方法名前加关键字partial。分部方法只能将方法分成两部分,即声明部分和实现部分。分部方法必须在分部类或分部结构中声明,并且必须私有访问。

分部方法有着严格的限制:

(1)方法必须返回void,只能默认为private。

(2)分部方法不能为virtual和extern方法。

(3)分部方法可以有ref参数,但不能有out参数。

因为任何针对没有被实现的分部方法的调用,都会简单地被忽略。所以说这些限制是非常有必要的。

编写程序,在分部类中实现分部方法。

using System;
namespace Project12
{
    public partial class Program
    {
        partial void Write();  //分部方法的声明
        partial void Write()   //分部方法的实现
        {
            Console.WriteLine("这是一个分部方法");
        }
    }
    public partial class Program
    {
        static void Main(string[] args)
        {
            //调用分部方法
            Program p = new Program();
            p.Write();
        }
    }
}

构造函数和析构函数

构造函数和析构函数有一个共性,那就是在编写代码时,如果没有提供它们,则编译器会自动添加。

构造函数

类的构造函数是类的一种特殊的成员函数,当创建类的新对象时就会执行构造函数。构造函数的作用是帮助用户初始化(给对象的每一个属性依次赋值),并且构造函数的名称与类的名称完全相同,它没有任何返回类型。

编写程序,输入类中默认的构造函数。

using System;
namespace Project13
{
    class Line    //创建一个关于线条的类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(10.0);   //为线条长度赋值
            Console.WriteLine("线条的长度: {0}", line.getLength());
        }
    }
}

运行结果:

编写程序,在类中创建一个带参数的构造函数。

using System;
namespace Project14
{
    public class Clerk    //创建一个员工的类
    {
        public enum Gender
        { 男, 女 }
        private string _name;    //声明字段_name
        public string Name        //定义字段_name的属性为Name
        {
            get
            { return _name; }
            set
            { _name = value; }
        }
        private int _age;          //声明字段_age
        public int Age             //定义字段_age的属性为Age
        {
            get
            { return _age; }
            set
            { _age = value; }
        }
        private Gender _gender;   //声明字段_gender
        public Gender Gen          //定义字段_gender的属性为Gen
        {
            get
            { return _gender; }
            set
            { _gender = value; }
        }
        public void Write()   //定义非静态方法
        {
            Console.WriteLine("入职新员工:\n姓名:{0}\t年龄:{1}\t性别:{2}", Name, Age, Gen);
        }
        public Clerk(string name, int age, Gender gender)  //创建构造函数
        {
            this.Name = name;
            this.Age = age;
            this.Gen = gender;
        }
    }
    class Program
    {
        static void Main()
        {
            Clerk zs = new Clerk("张三", 24, Clerk.Gender.男);
            zs.Write();
        }
    }
}

运行结果:

析构函数

析构函数的作用是用于实现销毁类的实例的方法成员,并且不能继承或重载。析构函数不能有参数,不能有任何修饰符而且不能被调用。析构函数和构造函数的标识符不同,特点是在析构函数前面需要加上前缀“~”以示区别。

如果系统中没有指定析构函数,那么编译器由GC(Garbage Collection,垃圾回收机制)来决定什么时候进行释放资源。

编写程序,在类中创建一个带参数的构造函数。

using System;
namespace Project15
{
    class Line
    {
        private double length;   // 线条的长度
        public Line()  // 构造函数
        {
            Console.WriteLine("对象已创建");
        }
        ~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(10.0);// 设置线条长度
            Console.WriteLine("线条的长度: {0}", line.getLength());
        }
    }
}

运行结果:

虚方法与重写方法

若一个实例方法的声明中含有virtual修饰符,则称该方法为虚方法。若其中没有virtual修饰符,则称该方法为非虚方法。

虚方法可以在派生类中重写。当某个实例方法声明包括override修饰符时,该方法将重写所继承的具有相同名称的虚方法。虚方法声明用于引入新方法,而重写方法声明则用于使现有的继承虚方法专用化。

编写程序,在一个类中创建一个虚方法和一个非虚方法,然后分别通过另一个类继承,并比较它们的调用结果。

using System;
namespace Project16
{
    class myClass1
    {
        //创建一个虚方法,虚方法的作用是能够在派生类中重写
        public virtual void virtualMenthod()
        {
            Console.WriteLine("这是一个虚方法");
        }
        public void notVirtualMenthod()     //非虚方法
        {
            Console.WriteLine("这是一个非虚方法");
        }
    }
    //将class2继承与class1,继承的目的是用来演示虚方法的重写
    class myClass2 : myClass1
    {
        //使用关键字override,重写一下虚方法
        public override void virtualMenthod()
        {
            Console.WriteLine("这是一个新的虚方法");
        }
        public new void notVirtualMenthod()   //在派生类中定义一个非虚方法
        {
            Console.WriteLine("这是一个新的非虚方法");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            myClass1 c1 = new myClass1();  // 将对象c1进行实例化
            c1.virtualMenthod();           // 调用myClass1类中的虚方法
            c1.notVirtualMenthod();        // 调用myClass1类中的非虚方法
            myClass2 c2 = new myClass2();  // 将对象c2进行实例化
            c2.virtualMenthod();           // 调用myClass2类中重写的虚方法
            c2.notVirtualMenthod();        // 调用myClass2类中的非虚方法
            c1 = c2;                       //  将对象c2的值赋给对象c1
            c1.virtualMenthod();           // 调用的是myClass2类中重写的虚方法
            c1.notVirtualMenthod();        // 调用的是myClass1类中的非虚方法
        }
    }
}

运行结果:

重写方法的作用是用来修改它的实现方式,或者说在派生类中对它进行重新编写。通常,派生类继承基类的方法,在调用对象继承方法的时候,调用和执行的是基类的实现。但是,有时候也需要对派生类中的继承 方法进行实现,所以就需要对该方法进行重写。

(1)重写基类的方法要用到override关键字。

(2)要重写基类的方法,前提是基类中要被重写的方法必须声明为virtual或者是abstract类型。给基类中要被重写的方法添加virtual关键字表示可以在派生类中重写它的实现。

编写程序 ,对虚函数进行重写操作。

using System;
namespace Project17
{
    class myClass1 //新建一个类用于存放虚方法
    {
        public virtual void Write()   //虚方法
        {
            Console.WriteLine("这是一个虚方法,可以被重写");
        }
    }
    class myClass2 : myClass1//再新建一个继承类用于重写方法
    {
        public override sealed void Write()//重写方法
        {
            Console.WriteLine("这是一个重写的方法,被称为已经重写的了基方法");
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            myClass1 c1 = new myClass1();
            c1.Write();
            myClass2 c2 = new myClass2();
            c2.Write();
        }
    }
}

运行结果:

注意:重写声明和已重写了的基方法具有相同的声明可访问性。换句话说,虚方法的访问修饰符是public,而重写方法必须 也是public。

方法的重载

方法重载允许同一类中的多个方法具有相同名称,条件是这些方法具有唯一的签名。在编译一个重载方法的调用时,编译器使用重载决策确定要调用的特定方法。重载决策将查找与参数最佳匹配的方法,如果没有找到任何最佳匹配的方法则报告错误信息。

编写程序,利用方法的重载,求出圆、三角形、矩形的面积。

using System;
namespace Project18
{
    class Program
    {
        // 决定方法是否构成重载有三个条件
        //(1)在同一个类中
        //(2)方法名相同:
        //(3)参数列表不同。
        static void writeArea(int radius)
        {
            double area = System.Math.PI * radius * radius;
            Console.WriteLine("您求的圆面积是:{0}", area);
        }
        static void writeArea(int l, int w)
        {
            int area = l * w;
            Console.WriteLine("您求的矩形面积是:{0}", area);
        }
        static void writeArea(int a, int b, int c)
        {
            double x = (a + b + c) / 2;
            double area = System.Math.Sqrt(x * (x - a) * (x - b) * (x - c));
            Console.WriteLine("您求的三角形面积是:{0}", area);
        }
        static void Main(string[] args)
        {
            writeArea(6, 8, 10);
            writeArea(5, 7);
            writeArea(4);
        }
    }
}

结构体

在C#中,结构体是值类型数据结构。它使得一个单一变量可以存储各种数据类型的相关数据。struct关键字用于创建结构体。

结构体的定义和使用

结构体就是将不同类型的数据组合成一个有机的整体,以供用户方便地使用。这些组合在一个整体中的数据是互相联系的。

编写程序,通过结构输出一 名员工的信息。

using System;
namespace Project19
{
    public struct Clerk
    {
        public string name;
        public int age;
        public string department;
        public char gender;
    }
    class Program
    {
        static void Main(string[] args)
        {
            Clerk zs = new Clerk();
            zs.name = "张三";
            zs.age = 25;
            zs.department = "人力部";
            zs.gender = '女';
            Console.WriteLine("我叫{0},今年{1}岁了,在{2}上班,我是{3}生", zs.name, zs.age, zs.department, zs.gender);
        }
    }
}

C#的结构具有以下特点:

(1)结构可带有方法、字段、索引、属性、运算符方法和事件。

(2)结构可定义构造函数,但不能定义析构函数。

(3)与类不同,结构不能继承其他的结构或类。

(4)结构不能作为其他结构或类的基础结构。

(5)结构可实现一个或多个接口。

(6)结构成员不能指定为abstract、virtual或protected。

(7)当用户使用new操作符创建一个结构对象时,会调用适当的构造函数来创建结构。与类不同,结构可以不使用new操作符即可被实例化。

(8)如果不使用new操作符,只有在所有的字段都被初始化之后,字段才被赋值,对象才被使用。

类与结构体的区别

C#中结构类型和类类型在语法上非常相似,它们都是一种数据结构 ,都可以包括数据成员和方法成员。

1、类与结构体的区别

(1)语法定义上的区别。定义类使用关键字class,定义结构体使用关键字struct。

(2)在结构体中可以声明字段,但是声明字段的时候是不能给初始值的。

(3)如果在一个类中,用户没有为类写任意的构造函数,那么C#编译器在编译的时候会自动为这个类生成一个无参数的构造函数,该构造函数称为隐式构造函数。但是一旦用户为这个类写了任意的一个构造函数的时候,这个隐式的构造函数就不会自动生成。而在结构中就不是这样,在结构 中隐式的构造函数无论如何都存在。

(4)创建结构本对象可以不使用new关键字,直接声明一个变量就可以。但是这样的话,结构体对象中的字段是没有初始值的,所以在使用字段 之前 必须要为这个字段赋值。

new关键字调用了构造函数,而结构体构造函数要求必须要为所有的字段赋值。

结构体的无参数的构造函数做了什么事情,在无参数的构造函数中为所有的字段赋值,值类型的字段赋值0,给引用类型的字段赋值null。

(5)结构体与类之间最大的区别是:结构体的值类型,类是引用类型。

结构体是值类型,当其作为一个局部变量的时候,变量是存储在栈空间中的,其对象的字段是直接存储在这个变量中的。

与引用类型的类不一样,引用类型的变量中存储的是对象在堆空间中的地址,所以当用户传递一个引用类型的变量的时候,其实传递的是变量的值(对象的地址),传递完以后对变量的修改会影响到另外一个变量指向的对象的值。

2、结构和类的适用场合分析

(1)当堆栈的空间有限,且有大量的逻辑对象时,创建类要比创建结构好一些。

(2)对于点、矩形和颜色这样的轻量对象,假如要声明一个含有许多个颜色对象的数组,则CLR需要为每个对象分配内存,在这种情况下,使用结构的成本较低。

(2)在表现抽象和多级别的对象层次时,类是最好的选择,因为结构不支持继承。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值