目录
一、类的对象的创建及使用
C#是一门面向对象的程序设计语言,对象是由类抽象出来的,所有的问题都是通过对象来处理的,对象可以操作类的属性和方法解决相应的问题。
1、对象的创建
对象可以认为是在一类事物中抽象出某一个特例,通过这个特例来处理这类事物出现的问题。在C#语言中通过new操作符来创建对象。语法格式:
Test test=new();
Test test=new("id");
test对象被创建出来时,test对象就是一个对象的引用,这个引用在内存中为对象分配了存储空间;另外,可以在构造函数中初始化成员变量,当创建对象时,自动调用构造函数,也就是说,在C#语言中初始化与创建是被捆绑在一起的。
每个对象都是相互独立的,在内存中占据独立的内存地址,并且每个对象都具有自己的生命周期,当一个对象的生命周期结束时,对象变成了垃圾,由.NET自带的垃圾回收机制处理。
在C#语言中对象和实例事实上可以通用。
2、访问对象的属性和行为
当用户使用new操作符创建一个对象后,可以使用“对象.类成员”来获取对象的属性和行为。对象的属性和行为在类中是通过类成员变量和成员方法的形式来表示的,所以当对象获取类成员时,也就相应地获取了对象的属性和行为。
//对象是如何调用类成员
//两个对象的产生是相互独立的,改变了t2的i 值,不会影响到t1的i值
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Test9_4
{
class Program
{
int i = 47; //定义成员变量
public void Call() //定义成员方法
{
Console.WriteLine("调用call()方法");
for (int j = 0; j < 3; j++)
{
Console.Write(j + " ");
if (j == 2)
{
Console.WriteLine("\n");
}
}
}
public Program() //定义构造函数
{
}
static void Main(string[] args)
{
if (args is null) //解除IDE0060
{
throw new ArgumentNullException(nameof(args));
}
Program t1 = new(); //创建一个对象t1:int i=47->构造函数->new()->t1.i=47
Program t2 = new() //创建一个对象t2:int i=47->构造函数->new()->t2.i=60
{
i = 60 //将类成员变量赋值为60
};
///<summary>
///使用第一个对象调用类成员变量
///使用第一个对象调用类成员方法
/// </summary>
Console.WriteLine("第一个实例对象调用变量i的结果:" + t1.i);
t1.Call();
///<summary>
///使用第二个对象调用类成员变量
///使用第二个对象调用类成员方法
/// </summary>
Console.WriteLine("第二个实例对象调用变量i的结果:" + t2.i);
t2.Call();
Console.ReadLine();
}
}
}
/*运行结果:
第一个实例对象调用变量i的结果:47
调用call()方法
0 1 2
第二个实例对象调用变量i的结果:60
调用call()方法
0 1 2 */
3、对象的引用
在C#语言中尽管一切都可以看作对象,但真正的操作标识符实质上是一个引用。
//语法格式:类名 对象引用名;
Book book;
//引用不一定与对象关联,如果引用与对象关联,其语法格式:
Book book = new();
☑ Book:类名。
☑ book:对象。
☑ new:创建对象操作符。
引用只是存放一个对象的内存地址,并非存放一个对象,严格地说引用和对象是不同的,但是可以将这种区别忽略,如可以简单地说book是Book类的一个对象,而事实上应该是book包含Book对象的一个引用。
4、对象的销毁
每个对象都有生命周期,当对象的生命周期结束时,分配给该对象的内存地址将会被回收。在其他语言中需要手动回收废弃的对象,但是C#拥有一套完整的垃圾回收机制,用户不必担心废弃的对象占用内存,垃圾回收器将回收无用的但占用内存的资源。
何种对象会被.NET垃圾回收器视为垃圾,主要包括以下两种情况:
☑ 对象引用超过其作用范围,则这个对象将被视为垃圾。
☑ 将对象赋值为null。
5、this关键字
在C#语言中规定使用this关键字来代表本类对象的引用,this关键字被隐式地用于引用对象的成员变量和方法。
private void setName(String name) //定义一个方法
{
this.name=name; //将参数值赋予类中的成员变量
}
在C#语言中规定使用this关键字来代表本类对象的引用,this关键字被隐式地用于引用对象的成员变量和方法,如在上述代码中,this.name指的就是Book类中的name成员变量,而this.name=name语句中的第二个name则指的是形参name。实质上setName()方法实现的功能就是将形参name的值赋予成员变量name。
事实上,this引用的就是本类的一个对象,在局部变量或方法参数覆盖了成员变量时,如上面代码的情况,就要添加this关键字明确引用的是类成员还是局部变量或方法参数。
this除了可以调用成员变量或成员方法之外,还可以作为方法的返回值。
public Book getBook()
{
return this; //返回Book类引用
}
在getBook()方法中,方法的返回值为Book类,所以方法体中使用return this这种形式将Book类的对象进行返回。
6、类与对象的关系
类是一种抽象的数据类型,但是其抽象的程度可能不同,而对象就是一个类的实例。
类是具有相同或相似结构、操作和约束规则的对象组成的集合,而对象是某一类的具体化实例,每一个类都是具有某些共同特征的对象的抽象。
二、类的封装
C#中可使用类来达到数据封装的效果,这样就可以使数据与方法封装成单一元素,以便于通过方法存取数据。除此之外,还可以控制数据的存取方式。
在面向对象编程中,大多数都是以类作为数据封装的基本单位。类将数据和操作数据的方法结合成一个单位。设计类时,不希望直接存取类中的数据,而是希望通过方法来存取数据,这样就可以达到封装数据的目的,方便以后的维护升级,也可以在操作数据时多一层判断。
此外,封装还可以解决数据存取的权限问题,可以使用封装将数据隐藏起来,形成一个封闭的空间,然后可以设置哪些数据只能在这个空间中使用,哪些数据可以在空间外部使用。一个类中包含敏感数据,有些人可以访问,有些人不能访问,如果不对这些数据的访问加以限制,后果将会非常严重。所以在编写程序时,要对类的成员使用不同的访问修饰符,从而定义它们的访问级别。
封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口这一特定的访问权限来使用类的成员。
三、类的继承
继承是面向对象编程最重要的特性之一。任何类都可以从另外一个类继承,这就是说,这个类拥有它继承的类的所有成员。在面向对象编程中,被继承的类称为父类或基类。C#中提供了类的继承机制,但只支持单继承,而不支持多重继承,即在C#中一次只允许继承一个类,不能同时继承多个类。
利用类的继承机制,用户可以通过增加、修改或替换类中的方法对这个类进行扩充,以适应不同的应用要求。利用继承,程序开发人员可以在已有类的基础上构造新类,这一性质使类支持分类的概念。使用继承后,每个对象就可以只定义自己的特殊性质,每一层的对象只需定义本身的性质,其他性质可以从上一层继承下来。
继承的基本思想是基于某个父类的扩展,制定出一个新的子类,子类可以继承父类原有的属性和方法,也可以增加原来父类所不具备的属性和方法,或者直接重写父类中的某些方法。
在C#中使用“:”来标识两个类的继承关系。继承一个类时,类成员的可访问性是一个重要的问题。子类(派生类)不能访问基类的私有成员,但是可以访问其公共成员。这就是说,只要使用public声明类成员,就可以让一个类成员被基类和子类(派生类)同时访问,同时也可以被外部的代码访问。
为了解决基类成员访问问题,C#还提供了另一种可访问性:protected,只有子类(派生类)才能访问protected成员,基类和外部代码都不能访问protected成员。
当对一个类应用sealed修饰符时,此修饰符会阻止其他类从该类继承。
继承并不只是扩展父类的功能,还可以重写父类的成员方法。重写(还可以称为覆盖)就是在子类中将父类的成员方法的名称保留,重写成员方法的实现内容,更改成员方法的存储权限,或是修改成员方法的返回值类型。
在继承中还有一种特殊的重写方式,子类与父类的成员方法返回值、方法名称、参数类型及个数完全相同,唯一不同的是方法实现内容,这种特殊重写方式被称为重构。
当重写父类方法时,修改方法的修饰权限只能从小的范围到大的范围改变,例如,父类中的doit()方法的修饰权限为protected,继承后子类中的方法doit()的修饰权限只能修改为public,不能修改为private。
//类的封装和继承
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Test9_6_1
{
/// <summary>
/// 自定义类,封装加数和被加数属性
/// </summary>
class MyClass1
{
private int x = 0; //定义int型变量,作为加数
private int y = 0; //定义int型变量,作为被加数
/// <summary>
/// 加数
/// </summary>
public int X
{
get
{
return x;
}
set
{
x = value;
}
}
/// <summary>
/// 被加数
/// </summary>
public int Y
{
get
{
return y;
}
set
{
y = value;
}
}
/// <summary>
/// 求和
/// 通过外部接口这一特定的访问权限来使用类的成员。
/// </summary>
/// <returns>加法运算和</returns>
public int Add1()
{
return (X + Y);
}
}
class MyClass2: MyClass1
{
private int z = 0;
/// <summary>
/// 被加数
/// </summary>
public int Z
{
get
{
return z;
}
set
{
z = value;
}
}
/// <summary>
/// 求和
/// 通过外部接口这一特定的访问权限来使用类的成员。
/// </summary>
/// <returns>加法运算和</returns>
public int Add2()
{
return (X + Y + Z);
}
}
class Program
{
static void Main(string[] args)
{
if (args is null) //解除IDE0060
{
throw new ArgumentNullException(nameof(args));
}
MyClass1 myclass1 = new() //实例化MyClass1的对象
{
X = 3, //为MyClass1类中的属性赋值
Y = 5 //为MyClass1类中的属性赋值
};
MyClass2 myclass2 = new() //实例化MyClass2的对象
{
X = 3, //继承MyClass1类中的属性并赋值
Y = 5, //继承MyClass1类中的属性并赋值
Z = 7 //为MyClass2类中的属性赋值
};
Console.WriteLine(myclass1.Add1());//MyClass1成员调用MyClass1方法
Console.WriteLine(myclass2.Add1());//MyClass2成员调用MyClass1方法
Console.WriteLine(myclass2.Add2());//MyClass2成员调用MyClass2方法
Console.ReadLine();
}
}
}
/*运行结果:
8
8
15
*/
四、类的多态
多态使子类(派生类)的实例可以直接赋予基类的变量(这里不需要进行强制类型转换),然后直接就可以通过这个变量调用子类(派生类)的方法。
利用多态可以使程序具有良好的扩展性,并可以对所有类对象进行通用的处理。
使用多态节省了开发和维护时间,因为程序员无须在所有的子类中定义执行相同功能的方法,避免了大量重复 代码的开发,同时只要实例化一个继承父类的子类对象即可调用相应的方法,这里只要维护父类中的这个方法即可。
在派生于同一个类的不同对象上执行任务时,多态是一种极为有效的技巧,使用的代码最少。可以把一组对象放到一个数组中然后调用它们的方法,在这种情况下多态的作用就体现出来了,这些对象不必是相同类型的对象。当然如果它们都继承自某个类,可以把这些子类(派生类)都放到一个数组中。如果这些对象都有同名方法,就可以调用每个对象的同名方法。
//类的多态
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Demo9_7
{
class People //定义基类
{
public virtual void Say(string name) //定义一个虚方法,用来表示人的说话行为
{
Console.WriteLine(name); //输出人的名字
}
}
class Chinese : People //定义派生类,继承于People类
{
public override void Say(string name) //重写基类中的虚方法
{
base.Say(name + "说汉语!");
}
}
class American : People //定义派生类,继承于People类
{
public override void Say(string name) //重写基类中的虚方法
{
base.Say(name + "说英语!");
}
}
class Program
{
static void Main(string[] args)
{
if (args is null) //解除IDE0060
{
throw new ArgumentNullException(nameof(args));
}
Console.Write("请输入姓名:");
string strName = Console.ReadLine() ?? string.Empty; //记录用户输入的名字
People[] people = new People[2]; //声明People类型数组
people[0] = new Chinese(); //使用第一个派生类的对象初始化数组的第一个元素
people[1] = new American(); //使用第二个派生类的对象初始化数组的第二个元素
for (int i = 0; i < people.Length; i++) //遍历赋值后的数组
{
people[i].Say(strName); //根据数组元素调用相应派生类中的重写方法
}
Console.ReadLine();
}
}
}
/*运行结果:
请输入姓名:wen chm
wen chm说汉语!
wen chm说英语! */