1、面向对象基本概念
例如把大象塞进冰箱里
面向过程:执行这件事的人不同时,需要为每个不同的人量身定做解决事情的方法,如每个人把大象塞进冰箱的方式不一样
面向对象:找个对象帮你做事,如把冰箱作为对象。也就是写出一个通用的代码,屏蔽差异。
描述一个对象,是通过描述这个对象的属性和方法进行的
对象必须是看得见摸得着的
我们把这些具有相同属性和相同方法的对象进行进一步的封装,抽象出来类这个概念
类就是个模子,确定了对象应该具有的属性和方法。
对象是根据类创建出来的,也就是实例化
2、类
1. 语法:
[public] class 类名
{
字段;_字段名 存储数据 必须是private类型
属性;public
方法;描述对象的行为
构造函数;
}
2. 类的实例化
写好一个类之后,需要创建这个类的对象
使用关键字 new 创建这个类的对象的过程称为 类的实例化
3. this
Person p = new Person();
public class Person
{
private string _name;
public string Name
{
get{ return _name; }
set{ _name = value; }
}
public void SayHello()
{
Console.Writeline("我叫{0}",this.Name);
}
}
this: 表示当前这个类的对象 如Person类的对象是p
类不占内存,而对象是占内存的
3、属性
1. 作用
保护字段、对字段的赋值set和取值get进行限定
2. 本质
两个方法set get
3. 例子
public class Person
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
}
4. 注意事项
ctrl+R+E 快速给字段设置属性
只能通过属性给字段进行赋值,字段是私有的
通过属性这个中介来对字段进行保护,防止字段被随意修改
4、对象初始化
当我们创建好一个类的对象后,需要给这个对象的每个属性去赋值
在该类中访问字段使用 this.属性名
5、静态和非静态的区别(也就是加不加static)
加了static 是静态的,不加是非静态
区别:
1. 在非静态 类 中,既可以有实例成员,也可以有静态成员
2. 在调用实例成员时,需要使用对象名.实例成员();
调用静态成员,类名.静态成员()
总结:
静态成员必须使用类名去调用,而实例成员使用对象名调用
静态 函数 中,只能访问静态成员,不允许访问实例成员
实例 函数 中,既可以使用静态成员,也可以使用实例成员
静态 类 中只允许有静态成员,不允许出现实例成员
使用:
1. 如果想要一个类当做工具类(经常使用的),可以考虑将这个类写成静态的
2. 静态类在整个项目中资源共享
6、构造函数
1. 语法
public 类名()
{
}
2. 作用
初始化对象,也就是给对象的每个属性依次赋值
3. 初始化对象的方法
(1) 对象名.属性名 = 值 如p.Name = "张三";
(2) 构造函数初始化,如Person p = new Person("张三");
4. 构造函数是一个特殊的方法:
(1) 构造函数没有返回值,并且不能写void
(2) 构造函数的名称必须与类名一样
5. 执行时间
创建对象的时候会首先执行构造函数,通过构造函数传递给每个属性,对字段进行赋值
也就是 类名 对象名 = new 构造函数(); 如 Person p = new Person();
将鼠标光标放在后面那个Person()上,发现是Person.Person(),也就是构造函数
6. 构造函数可以重载,也就是可以有多个 名相同 参数数量或类型不同的构造函数
7. 类中会有一个默认的无参数的构造函数,当你写了一个新的构造函数,不管是有参数的,还是无参数的,那个默认的无参数的构造函数都被干掉了。
7、函数重载
函数名一样 参数的数目或类型不一样
8、new 关键字
Student stu = new Student();
new 帮助我们做了3件事:
(1) 在内存中开辟一块空间
(2) 在开辟的空间中创建对象
(3) 调用对象的构造函数 进行初始化对象
9、this 关键字
(1) 代表当前类的对象
如下面的例子,此时this代表Perosn实例化对象p
Peron p = new Person();
public class Person
{
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
public void SayHello()
{
Console.WriteLine("我叫{0}", this.Name);
}
}
(2) 在类中显式的调用本类的构造函数
语法: 构造函数(参数):this(参数名与前面的参数名一致)
public class Person
{
private string _name;
private int _age;
private char _gender;
public string Name
{
get { return _name; }
set { _name = value; }
}
public int Age
{
get { return _age; }
set { _age = value; }
}
public int Gender
{
get { return _gender; }
set { _gender = value; }
}
public Person(string name, int age, char gender)
{
this.Name = name;
this.Age = age;
this.Gender = gender;
}
public Person(string name, int age):this(name, age, '男')
{
// this.Name = name;
// this.Age = age;
}
}
10、析构函数
public class Person
{
// 析构函数
~Person()
{
}
}
帮助我们马上释放资源
11、命名空间
可以认为类是属于命名空间的
如果在当前项目中没有这个类的命名空间,需要我们手动导入这个类所在的命名空间
用鼠标点
alt + shift + f10
手动写
在一个项目中引用另一个项目中的类
添加引用
引用命名空间
12、值类型和引用类型
1. 区别
(1) 值类型和引用类型在内存上存储的地方不一样
(2) 在传递值类型和传递引用类型的时候,传递的方式不一样
2. 值类型称之为值传递,引用类型称之为引用传递
我们学过的值类型和引用类型:
值类型:int、double、bool、char、decimal、struct、enum
引用类型:string、自定义类
3. 值类型的值是存储在内存的栈中,引用类型的值是存储在堆中
13、继承
1. 语法
public class 子类名():父类名
{
}
2. 由来
我们可能会在一些类中,写一些重复的成员,我们可以将这些重复的成员,
单独封装到一个类中,作为这些类的父类。
Student、Teacher、Driver 子类 派生类
Person 父类 基类
3. 具体使用
(1) 子类继承了父类,那么子类从父类那里继承过来了什么?
首先,子类继承了父类的属性和方法,没有继承父类的私有字段
(2) 子类有没有继承父类的构造函数?
子类没有继承父类的构造函数,但是子类会默认的调用父类的无参数的构造函数,
创建父类对象,让子类可以使用父类中的成员。
所以,在父类中重新写了一个有参数的构造函数之后,那个无参数的就被干掉了,
子类就调用不到,所以子类会报错。
解决办法:
(1) 在父类中,重新写一个无参数的构造函数(一般不用)
(2) 子类的构造函数调用父类的有参数的构造函数
语法:
public 类名(参数):base(参数名)
{
this.特有的参数 = 特有的参数
}
4. 实例
// 父类
public class Person
{
private string _name;
private int _age;
public string Name
{
get { return _name; }
set { _name = value; }
}
public int Age
{
get { return _age; }
set { _age = value; }
}
// 构造函数
public Person(string name, int age)
{
this.Name = name;
this.Age = age;
}
}
// 子类
public class Student:Person
{
private int _id;
public int Id
{
get { return _id; }
set { _id = value; }
}
// 构造函数
public Student(string name, int age, int id)
:base(name, age)
{
// this.Name = name;
// this.Age = age;
this.Id = id;
}
}
14、继承的特性
继承的单根性:一个子类只能有一个父类
继承的传递性:父类的迟早是子类的
15、object类是所有类的基类
16、关键字new
1. 创建对象,如 Person p = new Person();
2. 隐藏子类从父类那里继承过来的同名成员,隐藏的后果就是子类调用不到父类的成员
public class Person
{
public void SayHello()
{
Console.WriteLine("这里是父类");
}
}
public class Student:Person
{
public new void SayHello()
{
Console.WriteLine("这里是子类");
}
}
Student stu = new Student();
stu.SayHello();
使用前, 输出父类的结果
使用后,输出子类的结果
17、里氏转换
1. 子类可以赋值给父类
如果有一个地方需要父类作为参数,我们可以给一个子类代替
// Student stu = new Student();
// Person p = stu;
Person p = new Student();
public class Person
{
public void PersonSayHello()
{
Console.WriteLine("我是父类");
}
}
public class Student:Person
{
public void StudentSayHello()
{
Console.WriteLine("我是学生");
}
}
2. 如果父类中装的是子类对象,那么可以将这个父类强转成子类对象
Student stu_2 = (Student)p;
3. 子类对象可以调用父类中的成员,但是父类对象永远只能调用自己的成员
4. 判断里氏转换是否成功
(1) is 表示类型转换,如果能够转换成功,则返回一个true,否则返回false
if(p is Student)
{
Student stu_2 = (Student)p;
stu_2.StudentSayHello();
}
else
{
Console.WriteLine("转换失败");
}
(2) as 表示类型转换,如果能够转换则返回对应的对象,否则返回一个null
Student stu_2 = p as Studnet;
if(stu_2 != null) stu_2.StudentSayHello();
18、多态
1、基本概念
多态就是 让一个对象能够表现出多种的状态(类型)
实现多态的三种方法:
1、虚方法
2、抽象类
3、接口
2、虚方法
1. 将父类的方法标记为虚方法,使用关键字 virtual,子类方法使用关键字 override
结果是 父类的方法可以被子类重新写一遍
2. 实际效果
程序运行 调用方法的时候是调用父类的方法,但是父类的方法被子类重写,不再调用父类的方法。
而是调用被子类重写的方法,具体调用哪个子类重写的方法 取决于父类对象中装的是哪个子类的对象。
如 Person p = new Chinese(); 那么就调用Chinese中的方法
如果此时父类对象中装的是自己的对象,那就调用父类自己的方法。
3. 实例
Person p = new Person();
Chinese ch = new Chinese();
English en = new English();
Person[] pers = {p, ch, en};
for (int i = 0;i < pers.Length; i++)
{
// if(pers[i] is Chinese) ((Chinese)pers[i]).SayHello();
// else ((English)pers[i]).SayHello();
pers[i].SayHello();
}
Console.ReadKey();
public class Person
{
public virtual void SayHello()
{
Console.WriteLine("我是人类");
}
}
public class Chinese:Person
{
public override void SayHello()
{
Console.WriteLine("我是中国人");
}
}
public class English:Person
{
public override void SayHello()
{
Console.WriteLine("我是英国人");
}
}
3、抽象类
1. 具体使用场景
如果父类中的方法没有默认实现,父类也不需要被实例化,则可以将该类定义为抽象类。(当父类中的方法不知道如何去实现的时候,可以考虑将父类写成抽象类,将方法写成抽象方法)
如果父类中的方法有默认的实现,并且父类需要被实例化,这时可以考虑将父类定义成一个普通类,用虚方法来实现多态。(如果知道父类中的方法能写什么内容时,可以考虑使用虚方法实现)
2. 抽象方法存在的唯一意义 就是让子类重写抽象方法来实现多态;
3. 抽象类的特点
(1) 抽象成员必须标记为 abstract,并且不能有任何实现,也就是抽象函数(方法)没有方法体
(2) 抽象成员必须在抽象类中
(3) 抽象类不允许创建对象(实例化)
(4) 子类继承抽象类之后,必须把父类中的所有抽象成员都重写(除非子类也是一个抽象类,则可以不重写)
(5) 抽象成员的访问修饰符不能是private
(6) 在抽象类中可以包含实例成员,并且抽象类的实例成员可以不被子类实现
(7) 抽象类是有构造函数的,虽然不能被实例化
(8) 如果父类的抽象方法中有参数,那么继承这个抽象父类的子类,在重写父类的方法时,必须传入对应的参数
如果抽象父类的抽象方法中有返回值,那么子类在重写这个抽象方法的时候 也必须要传入返回值
4. 实例
// Animal a = new Animal(); 抽象类不允许创建对象
Animal dog = new Dog();
dog.Bark();
Console.ReadKey();
public abstract class Animal
{
public abstract void Bark();
}
public class Dog:Animal
{
public override void Bark()
{
Console.WriteLine("汪汪");
}
}
public class Cat:Animal
{
public override void Bark()
{
Console.WriteLine("喵喵");
}
}
4、简单工厂设计模式
class Program
{
static void Main(string args[])
{
Console.WriteLine("请输入您想要的笔记本品牌: ");
string brand = Console.ReadLine();
NoteBook nb = GetNoteBook(brand);
// 执行哪个类的SayHello函数 取决于父类对象装的是哪个子类
nb.SayHello();
Console.ReadKey();
}
public static NoteBook GetNoteBook(string brand)
{
NoteBook nb = null;
switch(brand)
{
// 子类对象装进父类对象中
case "Lenovo": nb = new Lenovo();
break;
case "Acer": nb = new Acer();
break;
}
return nb;
}
}
public abstract class NoteBook
{
public abstract void SayHello();
}
public class Lenovo:NoteBook
{
public ovveride void SayHello()
{
Console.WriteLine("联想笔记本");
}
}
public class Acer:NoteBook
{
public override void SayHello()
{
Console.WriteLine("鸿基笔记本");
}
}
5、值传递和引用传递
1. 值类型在复制的时候,传递的是这个值的本身
2. 引用类型在复制的时候,传递的是这个对象的地址
3. 字符串不可变性
// 每次赋值新的字符串都等于开辟了一个新的空间,所以结果一个是张三 一个是李四
string s1 = "张三";
string s2 = s1;
s2 = "李思";
Console.WriteLine(s1);
Console.WriteLine(s2);
Console.ReadKey();
4. ref
class Program
{
static void Main(string args[])
{
// ref 把一个变量以参数的形式带到方法中进行改变
// 再将改变后的值带出来
int num = 10;
Test(ref num);
Console.WriteLine(num);
Console.ReadKey();
}
public static void Test(ref int n)
{
n += 10;
}
}
6、特殊的类
1. 部分类 多人开发写一个类
// 这两个部分共同组成了Person类
public partial class Person
{
}
public partial class Person
{
}
2. 密封类 sealed
// 不能被其他类继承,但是可以继承别的类
public sealed class Person
{
}
19、接口
1、接口简介
1. 语法简介
接口就是一个规范(能力)
[public] interface I...able
{
成员;
}
2. 实例
继承具有单根性,一个子类只允许有一个父类
Student类想要继承多个类,这时可以考虑接口
一个类继承一个接口,必须实现这个接口中的所有成员
public class Person
{
public void SayHello()
{
Console.WriteLine("我是人类");
}
}
public class Student : Person, IKouLanable
{
public void KouLan()
{
Console.WriteLine("我是学生,我也可以扣篮");
}
}
public interface IKouLanable
{
// 接口中的成员不允许添加访问修饰符,默认public
// 接口中的成员可以有返回值
// string Test();
// 接口中的方法 没有方法体
// 没有字段,因为它不是存储数据的
void KouLan();
string Name
{
get;
set;
}
}
3. 自动属性和普通属性
自动属性编译时,自动生成字段,不允许写方法体(不能对字段进行限定,但可以通过构造函数限定)
public class Person
{
private string _name;
// 普通属性
public string Name
{
get { return _name; }
set { _name = value; }
}
// 自动属性
public int Age
{
get;
set;
}
}
2、接口的特点
1. 接口中的成员不允许添加访问修饰符,默认就是public
2. 不允许写具有方法体的成员,只是定义了一组未实现的成员
3. 只要一个类继承一个接口,这个类必须实现这个接口中所有的成员(实现接口的子类必须实现该接口的全部成员)
4. 为了多态,接口不能被实例化,也就是,接口不能new(不能创建对象)
接口、抽象类、静态类不能被实例化
5. 接口中只能有方法,属性(自动属性),索引器,事件 不能有字段和构造函数
6. 接口与接口之间可以继承,并且可以多继承。类有单根性,一个子类只能有一个父类
7. 接口不能去继承一个类,类可以继承接口(接口只能继承接口,类既可以继承类,也可继承接口)
8. 一个类可以同时继承一个类并实现多个接口,一个子类同时继承父类A并实现接口IA,语法上父类A必须写在接口IA前面
class 类名:A, IA(){}
类是单继承
9. 显式实现接口的目的,解决方法的重名问题
// 接口的方法
IFlyable fly = new Bird();
fly.Fly();
// 自己的方法
Bird bird = new Bird();
bird.Fly();
Console.ReadKey();
public class Bird:IFlyable
{
// Bird自己的方法
public void Fly()
{
Console.WriteLine("鸟会飞");
}
// 接口中要实现的方法
void IFlyable.Fly()
{
Console.WriteLine("我是接口");
}
}
public interface IFlyable
{
void Fly();
}
10.什么时候显式的去实现接口:当继承的接口中的方法和参数一模一样的时候,用显式的实现接口
当一个抽象类实现接口时,需要子类去实现接口