又是新的一周,小白又来学习C#了,今天要学的内容是C#的面向对象的特性-类与继承,我们直接看题,然后说明其中的知识点:
编写一个C#程序,定义:
- 一个表示人的类Person,包括字段“姓名”“性别”,方法“获得性别”,属性“年龄”,限定“年龄”的取值范围为0~120;
- 一个表示学生的类Student,继承自Person,包括字段“学号”“班号”;方法“获得学号”“获得班号”。
这里我先给出我在课堂上写的代码(没有写主函数,可自行编写测试),然后逐点说明一下其中的注意点:
using System;
namespace ConsoleApp4
{
class Person
{
private string name;
private string sex;
private int age;
public Person(string name,string sex,int age)
{
this.name = name;
this.sex = sex;
this.age = age;
}
public string getsex()
{
return sex;
}
public int Age
{
get
{
return age;
}
set
{
if (Age < 0 && Age > 120)
Console.WriteLine("Age error.");
}
}
}
class Student : Person
{
private int number;
private int cnumber;
public Student(string name,string sex,int age,int number,int cnumber):base(name,sex,age)
{
this.number = number;
this.cnumber = cnumber;
}
public int get_number()
{
return number;
}
public int get_cnumber()
{
return cnumber;
}
}
}
下面开始说明:
首先,第3行的ConsoleApp4这个名字空间其实就是创建的工程名,这里在创建的时候自己设置的,无需多言
其次就是这句:
class Person
这个是这个类的名称,而这里这个名称是和我创建的.cs文件名(C#的代码文件)是一样的,看一下旁边的解决方案的截图,
好的接下来就是C#的核心知识点,第一部分:类,字段和方法
首先,C#是面向对象的语言,面向对象语言的一个重要特点就是具有“封装性”,而类就是实现封装性的重要手段,C#中通过将事物表达成类(class),每个类又通过字段(filed)和方法(method)来表达事物的状态和行为。
//定义类
class 类名{
...
}
类中包含两种最基本的元素:字段和方法,其中字段(filed)是表示事物的状态,又称字段变量,成员变量和域(没错,对应C++中的成员变量),而方法(method)就是表示类的动态行为,即类所具有的功能和操作。
我们可以看出来,字段和方法的定义方法与C++中的有些许差异,我们看下面这段更简洁的代码,我们同样用Person类来说明:
class Person
{
public string name;
public int age;
public void SayHello()
{
Console.WriteLine("Hello!My name is " + name);
}
public string GetInfo()
{
return "Name:" + name + ",Age:" + age;
}
}
-
定义字段和方法的形式:
//定义字段
修饰符 返回值类型 字段名;
//定义方法
修饰符 返回值类型 方法名(形参列表){}
-
构造和析构方法
有过其他语言基础的应该很容易理解,下面是构造方法(constructor):主要作用是完成对象的初始化工作,但是注意以下两点:
- 构造方法的方法名与类名相同
- 构造方法没有返回类型,也不能写void
形如:修饰符 构造方法名(即类名) (形参列表){}
public Person(string n,int a){
name = n;age = a;
}
这里我们用自定义的形参来初始化字段,如果我们没有定义任何构造方法的话,系统就会自动产生一个,形如
public Person(){}
有了构造方法,那就一定有析构方法(Destructor),这一点和C++也是相对应的,只需要在构造函数前加一个"~"我们继续用Person类来举例:
~Person(){}
但其实由于C#会自动进行对象的释放,所以我们一般不用显式的定义析构方法。
-
对象的创建和使用
首先C#中的构造方法不能显式地直接调用,而是用new来调用,形如:
Person p = new Person("LiMing",20);
此时我们就创建了一个Person的对象p,姓名为LiMing,年龄为20,当要使用这个对象进行函数调用和取对象字段值时,形式为:
//对象名.字段名
//对象名.方法名();
Console.WriteLine(p.name); //输出对象的名称字段值
p.SayHello(); //调用SayHello()方法
-
方法的重载(overloading)
所谓方法重载,就是相同的函数名,但是返回值类型,形参个数,形参值类型等不完全一样的函数,举一个最简单的例子:
//同一个函数名F(),但不是同一个函数
void F();
void F(int x);
void F(int x,int y);
int F(string s);
int F(int y);
在C#中还有下面这种用法:
public void SayHello() { //签名SayHello();
Console.WriteLine("Hello! My name is " + name);
}
public void SayHello(Person another) { //签名SayHello(Person)
Console.WriteLine("Hello," + another.name + "! My name is " + name);
}
其中,方法的签名由方法名称、参数类型、参数修饰符构成,目前这个用的并不多,所以只见到认识即可。
-
使用this
这个this指的是当前对象本身,常用于以下情况:
- 访问这个对象的字段及方法(vs会智能提示)
- 区分字段与局部变量
public Person(int age,string name){
this.age = age;
this.name = name;
}
3.用于构造方法调用另一个构造方法,注意位置
public Person():this(0,""){
//构造方法的其它语句;
}
这里用的最多的就是第二种,我们在构造函数对字段进行初始化的时候可以避免混淆,另外两种并未作过多说明。
-
属性
注意,这里是C#的特点之一了,属性(property)用于表达事物的状态,前面说字段也是表示事物状态的,怎么回事呢?其实,属性使用另一种方式事物的状态,它可以获取和设置事物的状态,属性的存取方式可以是读,也可以是写,读属性和写属性分别用get和set进行表示。
一般形式及示例:
/*修饰符 类型名 属性名{
get{
}
set{
}
}
*/
public string Name{
get{
return_name;
}
set{
_name = value;
}
}
/*在属性的“获取方法”(get方法)中,用return来返回一个事物的属性值
在属性“设置方法”(set方法)中可以使用一个特殊的value变量,该变量包含用户指定的值,通常在set方法中,将用户指定的值记录到一个字段变量中,这里set、get和value被称为上下文关键字
*/
其中在C#3以上版本中可以简写为:
public string Name{set;get;}
注意:按照惯例,属性首字母大写,而字段首字母小写。
访问属性时,实际上是调用相应的set和get方法,使用采用以下形式:
//对象.属性
Person p = new Person();
p.Name = "LiMing";
Console.WriteLine(p.Name);
- 属性与字段的比较
属性实际上是方法,可以只读或只写(只有get和set),可以进行有效性检查(if...),可以是计算得到的数据,如下:
public string Info{
get{return "Name:" + Name + ", Age: " + Age;
}
另外,还可以定义抽象属性。
-
索引器(Indexer)
索引器也是一种函数式的成员,可以使得对象能用下标来得到一个值。定义索引器为类创建了“虚拟数组”,该类的实例可以使用[](数组访问运算符)进行访问。
形式:
/*修饰符 类型名 this[参数列表]{
set{
}
get{
}
}
*/
使用索引的形式:
/*
对象名[参数]
*/
这里给出属性和索引的简单比较:
属性 | 索引器 |
---|---|
通过名称标识 | 通过参数列表进行标识 |
通过简单名称来访问 | 通过[]运算符来访问 |
可以用static修饰 | 不能用static修饰 |
属性get访问器没有参数 | 索引的get访问器具有与索引相同的参数列表 |
属性的set访问器包含隐式value参数 | 除value参数外,索引的set访问器还具有与索引相同的参数列表 |
-
继承(inheritance)
这次我们给出简单的继承说明和实例:C#中都是采用单继承,即一个子类只能有一个父类,但是一个父类可以有多个子类,所有的类都是通过直接或间接地继承object(即System.Object)得到的。
先给一个简单的形式:
class SubClass:BaseClass{
//...
}
说明:
- 子类自动的从父亲那里继承所有的字段、方法、属性、索引器等作为自己的成员。
- 除了继承父类的成员外,子类还可以添加新的成员,隐藏或修改父类的成员。
这里我们着重说一下这个隐藏,其实就是重新定义一个从父类哪里继承来的字段变量完全相同的变量,实际上用得少,但需要注意下:
class A{
public int a;
}
class B:A{
new public int a;
}
- 与父类同名的方法
一是定义同名、但是参数列表(签名)与父类不同的方法,这称为对父类方法的重载
二是定义同名且参数列表也与父类相同的方法,这称为新增加一种方法,用new表示(实际较少用)
三是定义同名且参数列表也与父类相同的方法,而且父类的方法用了abstract或virtual进行了修饰,子类的同名方法用了override进行了修饰,这称为虚方法的重写或覆盖(实际使用多)
- 使用base
使用base访问父类的字段和方法
override void sayHello(){
base.sayHello();
Console.WriteLine("My school is " + school);
}
使用父类的构造方法:
Student(string name,int age,string school):Person(name,age){
this.school = school;
...
}
下面在介绍几个C#关于继承的特性:
- 父类与子类的转换
Personp1 = new Person();
Person p2 = new Student();
Student s1 = new Student();
Student s2 = new Student();
p1 = s1; //可以,因为Person类型的变量可以引用Student对象
s2 = p1; //不行,因为会产生编译错误
s2 = (Student) p1; // 编译时可以通过,运行时则会出现类型不能转换的异常
s2 = (Student) p2; //正确,因为p2引用的正好是Student对象实例
- as运算符
如果不能转换,则值为null
Student s3 = p1 as Student; //结果s3为null
Student s4 = p2 as Student; //s4被赋值
与强制类型转换的差别
//as只能针对引用型变量
//如果不能转换,as运算不会引起异常,只是值为null
- is运算符
if(p is Person)
//判断一个对象是不是某个类的实例
- Typed()运算符
获得其运行时的类型:
Type t = typeof(变量);
Type t = typeof(类名);
- 属性、索引的继承
/*
属性、索引也是可以继承的
• 子类也可以增加属性和索引器
• 子类还可以定义与父类同名或同参数列表的属性或索引器(通
过new或overide)
*/
行吧,这一节就先写到这吧,学习语言最重要的就是要写要练,一起加油吧!!