[Day 007.02 of C#] 面向对象初级 —— Rojection
Day 007.02
part 98 - 107.
面向对象初级
类
因为对象是属于类的,所以想有对象,得先有类。
- 语法
public class 类名{
字段; // 用于存储数据,可以存多个值
属性; // 看后文
函数; // 行为,用于描述对象的行为
构造函数; //看后文
}
- 如何在Visual Studio里给当前的项目添加一个类?
- (类都是以.cs结尾的)
- 选中当前你要添加类的一个项目
- 右键>添加>类(Shift+Alt+C)
或者 右键>添加>添加项==>类
在Person.cs中
public static Person{
public string _name; //字段
public int _age; //字段
public char _gender; //字段
public void things(){
Console.WriteLine("我叫{0},我今年{1}岁了,我是{2}生。我会躺平w",this._name, this._age, this._gender);
//this 表示当前这个类的对象。不加也行,但在某些情况下就会出问题
//this 和下面的例子一起看的话,this就是bingE
}
}
- 写好了一个类(自定义类)后,需要创建这个类的对象。
- 我们把创建这个类的对象的过程称之为类的实例化
- 通过使用关键词new.
- 在类中声明字段不能加static (要非静态的)
- 由于代码只有写在Main函数里才会被执行,所以我们若要创建类的对象的话,也应该在Main函数里创建。
- 函数、属性、字段在自己的类下是不能被使用的,得通过当前类的对象才能被使用
- 所以写好一个类后,就应该创建这个类的对象
- 语法
//写在Main函数里
public void Main(string[] args){
//创建类
类名.对象名 = new 类名();
//给类的字段赋值
对象名.字段名 = ;
//调用类中的方法
对象名.方法名(/*参数*/);
}
internal class Program{
static void Main(string[] args){
//创建Person类的对象
Person.bingE = new Person();
//给类中的字段赋值
bingE._name = "小冰";
bingE._age = "14";
bingE._gender = "男";
//调用类中的方法
bingE.things();
Console.ReadKey();
}
}
// 输出: 我叫小冰,我今年14岁了,我是男生。我会躺平w
类是不占内存的,而对象是占内存的,其实就是类当中的字段占内存了
- 因为字段有初值,也会开辟一定的空间
结构和类的区别
- 结构是一个面向过程的东西,不具备面向对象的任何特征, 即封装、继承和多态
- 类是面向对象的东西
- 即它们的用法和实际含义一点都不一样!!
属性
- 属性的作用就是来保护字段,对字段的赋值和取值范围进行限定。
比如说,我在类里面给_age赋值-14可以不?给_gender赋值"秋''可以不?
可以,但是不符合我们的要求,不能让用户输入这些无关的,会导致错误。所以就拿属性来进行限制,和枚举类似。
如何在类中写一个属性?
- 属性应该写在类里面
- 正常情况下,应该给每一个字段都配备一个属性。
- 并且一个字段一个属性
- 这个属性,用来对这个字段的取值和赋值进行保护
- 字段对于类而言就是存数据的,应该被好好保护,不能被外界轻易地访问到。所有跟外界打交道的事情都应该让属性去做才对,所以不再public,变成了private,因此在外面也就访问不到了 (字段前不加访问修饰符,则默认private)。所以,字段在类中必须是私有的
- 所以现在只能通过属性来对字段进行赋值
public static Person{
string _name; //字段
public string Name{
// 当你输出属性的值的时候,会执行get方法
get { return _name; }
// 当你给属性赋值的时候,首先会执行set方法。
// value存的就是我们在Main函数里面赋的值
set { _name = value; }
} //这个就是属性,是保护字段_name
int _age; //字段
public int Age{
get { return _age; }
set {
if(value < 0 || value > 100) value = 0;
// 在当value把值赋给字段_age前,先对value的值进行正确的判断。如果value不符合限定,则使value=0
_age = value; }
}
char _gender; //字段
public char Gender{
get {
if(_gender != '男' && _gender != '女') _gender = '男';
return _gender;
}
// 如果输入的既不是"男",又不是女"女",则默认为"男"
set { _gender = value; }
}
public void things(){
Console.WriteLine("我叫{0},我今年{1}岁了,我是{2}生。我会躺平w",this.Name, this.Age, this.Gender);
//this 表示当前这个类的对象。不加也行,但在某些情况下就会出问题
//this 和下面的例子一起看的话,this就是bingE
}
}
internal class Program{
static void Main(string[] args){
//创建Person类的对象
Person.bingE = new Person();
//通过属性给类中的字段赋值,而不应该直接给字段赋值
bingE.Name = "小冰";
bingE.Age = "14";
bingE.Gender = "男";
//调用类中的方法
bingE.things();
Console.ReadKey();
}
}
// 输出: 我叫小冰,我今年14岁了,我是男生。我会躺平w
属性的本质
- 属性的本质就是两个方法,一个叫get(),一个叫set().
- 众所周知,get是取得,set是设置。也就是说,get方法用来控制取值,set方法用来控制赋值,便能对一个字段的取值和赋值进行保护
- 通过Reflector反编译工具可以发现,上面代码框中的属性Name的面貌是这样的
public string Name {get; set;}
把get和set转成 IL中间代码之后,发现
- set 的内部
public void set_Name(string value)
{
this._name = value;
// this是当前类的对象,_name是当前类的字段
// 外面赋值的时候,会以value的形式传给并赋值到字段
// 因此,当给value赋值的时候,会执行set这个函数
}
- get 的内部
public string get_Name(){
return this._name;
// 直接返回字段的值
}
所以在给对象赋值的时候,不该再给字段赋值,而应通过属性赋值。
因为属性中有两个方法能够对字段的取值和赋值进行限定,只起到过渡、判断的作用,最终还是赋值给了字段,整个过程中,属性并没有占用内存,没有任何值。
- 并不是所有的属性都既有get又有set。
- 如果一个属性既有get又有set,我们称之为可读可写属性
- 只有get是只读属性,只有set是只写属性。
- 在对字段进行限制时,我们可以写在get,也可以写在set
- 只不过,set判断的是value的值,get判断的值。因为在执行get的时候,字段可能已经被set赋值了。
☆静态与非静态☆
- 静态: 有加 static 用于修饰, 被称之为静态成员
- 非静态: 没有加 static 用于修饰,被称之为非静态成员
- 在非静态类中,既可以有实例成员(非静态成员),也可以有静态成员。
- 在调用实例成员(非静态成员)的时候,需要使用对象名.实例成员名; (无论类静态与否)
- 在调用静态成员的时候,应该使用类名.静态成员名; (无论类静态与否)
- 类中可以出现静态的字段,也可以出现非静态字段
- 如果要使用静态成员,不管是字段、属性还是方法,都得需要用类名去调用。
- 如果要使用实例成员(非静态成员),以上三项都得需要对象去调用
- 静态函数中只能访问静态成员,不允许访问实例成员
- 实例方法中既可以使用静态成员,也可以使用实例成员
- 静态类当中只允许存在静态成员,不允许存在实例成员
- 静态类不允许被实例化,因为调用静态成员的时候需要用类名去调用,没有对象的事情,所以不需要被实例化,不需要创建一个对象,这是语法上的规定。
静态类与实例类的选择
- 如果想要把你的类当做一个"工具类[^工具类]"去使用,可以考虑将类写成静态类。
为什么?
- 拿静态类Console为例,如果它是一个实例类,我们在使用的时候就必须先创建它的一个对象,很麻烦!而当它是一个静态类的话,只需要类名.方法名即刻。
- 工具类: 用来干活的,经常去使用的,例如Console类
- 静态类在整个项目中资源共享。而静态类本身是占内存的(因为静态类中也有字段,也会有初值;而实例类没有声明对象,就不会占用内存),就都存在静态存储区域中。在整个项目当中,谁都可以访问静态存储区域。
- 内存被人为分成5块,但在内存里有3块地方我们经常使用:
- 堆、栈、静态存储区域
- 在应用程序当中静态类应该越少越好。因为会占用内存资源,而且只有在程序全部之后,静态类才会释放资源
- 释放资源: .Net平台引入了一个cool things叫 GC (Garbage Collection) 垃圾回收器 ,这个东西帮我们去释放资源 (在其他于语言都需要手动写语句去释放)。但是有一些东西它是释放不了的。
构造函数
我们管给对象的属性依次赋值的过程叫作对象的初始化
如果一个属性里有很多字段,那么在对象的初始化的过程中就会有很多冗余的代码,也会花费我们很多时间。所以就有了构造函数这个概念。
- 构造函数用来创建对象,并且可以在构造函数中对对象进行初始化,即给对象的属性依次地赋值。
- 构造函数是一个特殊的方法
- 构造函数没有返回值,void也不能写。
- 构造函数的名称必须跟类名一样
- 在Person.cs中
//可以写字段、属性、函数与构造函数
public static Person{
//构造函数的访问修饰符必须是public,解释见后文
public Person(string name, int age, char gender) {// 不能有返回值,并且名称必须和类名一样
//在创建完对象之后,会先执行构造函数,然后再对对象进行初始化。
this.Name = name;
//将形参name赋值给Person类中所对应的对象的属性Name
this.Age = age;
//将形参age赋值给Person类中所对应的对象的属性Age
this.Gender = gender.
//将形参gender赋值给Person类中所对应的对象的属性Game
Console.WriteLine("我叫{0},我今年{1}岁了,我是{2}生。我会躺平w",this.Name, this.Age, this.Gender);
}
string _name; //字段
public string Name{
// 当你输出属性的值的时候,会执行get方法
get { return _name; }
// 当你给属性赋值的时候,首先会执行set方法。
// value存的就是我们在Main函数里面赋的值
set { _name = value; }
} //这个就是属性,是保护字段_name
int _age; //字段
public int Age{
get { return _age; }
set {
if(value < 0 || value > 100) value = 0;
// 在当value把值赋给字段_age前,先对value的值进行正确的判断。如果value不符合限定,则使value=0
_age = value; }
}
char _gender; //字段
public char Gender{
get {
if(_gender != '男' && _gender != '女') _gender = '男';
return _gender;
}
// 如果输入的既不是"男",又不是女"女",则默认为"男"
set { _gender = value; }
}
public void things(){
Console.WriteLine("我叫{0},我今年{1}岁了,我是{2}生。我会躺平w",this.Name, this.Age, this.Gender);
//this 表示当前这个类的对象。不加也行,但在某些情况下就会出问题
//this 和下面的例子一起看的话,this就是bingE
}
}
- 在Program.cs中
internal class Program{
static void Main(string[] args){
//创建Person类的对象
Person.bing = new Person("小冰", 14, '男');
/* 输入实参:
小冰(实参) -> name(形参) -> Name(属性)
14(实参) -> age(形参) -> Age(属性)
男(实参) -> gender(形参) -> Gender(属性)
*/
Person.redPanda = new Person("小熊猫", 20, '男')
/* 输入实参:
小熊猫(实参) -> name(形参) -> Name(属性)
20(实参) -> age(形参) -> Age(属性)
男(实参) -> gender(形参) -> Gender(属性)
*/
//调用类中的方法
bingE.things();
Console.ReadKey();
}
}
// 输出: 我叫小冰,我今年14岁了,我是男生。我会躺平w
- 由上可知,构造函数这个东西极大程度上地帮助我们去创建多个运用到相同属性的对象,非常方便。
细节
- 构造函数也可以重载,哪个不要,参数就不写哪个
- 每每写好类之后,都会有一个默认的构造函数,是一个无参数的构造函数。如果自己写上了一个新的构造函数之后,默认的构造函数就被赶走了,被替代了(TAT)
关键字new
- new帮我们做了3件事
- 在内存中开辟一块空间
- 在开辟的空间中创建对象
- 调用对象的构造函数进行初始化对象
- 由第三条可知,构造函数的访问修饰符必须是public,如果是private,那么new就不能访问到对象的构造函数,就不能初始化对象,对象就创建不出来,就boom了
关键字this
- 代表了当前类的对象
- 在类当中显示调用本类的构造构造函数\
- 语法
:this
//可以写字段、属性、函数与构造函数
public static Person{
//构造函数的访问修饰符必须是public,解释见后文
public Person(string name, int age, char gender) {// 不能有返回值,并且名称必须和类名一样
//在创建完对象之后,会先执行构造函数,然后再对对象进行初始化。
this.Name = name;
//将形参name赋值给Person类中所对应的对象的属性Name
this.Age = age;
//将形参age赋值给Person类中所对应的对象的属性Age
this.Gender = gender.
//将形参gender赋值给Person类中所对应的对象的属性Game
Console.WriteLine("我叫{0},我今年{1}岁了,我是{2}生。我会躺平w",this.Name, this.Age, this.Gender);
}
public Person(string name, int age):this(name,age,0) {
//在通过:this调用全参构造函数的时候,小括号里填需要的参数。如果不需要,就随便输入一个值,反正不会被读取。在调用完全参构造函数,完成给属性赋值后,又调回当前的构造函数,对当前的构造函数里的语句进行执行。
Console.WriteLine("我叫{0},我今年{1}岁了,我是{2}生。我会躺平w",this.Name, this.Age);
}
string _name; //字段
public string Name{
// 当你输出属性的值的时候,会执行get方法
get { return _name; }
// 当你给属性赋值的时候,首先会执行set方法。
// value存的就是我们在Main函数里面赋的值
set { _name = value; }
} //这个就是属性,是保护字段_name
int _age; //字段
public int Age{
get { return _age; }
set {
if(value < 0 || value > 100) value = 0;
// 在当value把值赋给字段_age前,先对value的值进行正确的判断。如果value不符合限定,则使value=0
_age = value; }
}
char _gender; //字段
public char Gender{
get {
if(_gender != '男' && _gender != '女') _gender = '男';
return _gender;
}
// 如果输入的既不是"男",又不是女"女",则默认为"男"
set { _gender = value; }
}
public void things(){
Console.WriteLine("我叫{0},我今年{1}岁了,我是{2}生。我会躺平w",this.Name, this.Age, this.Gender);
//this 表示当前这个类的对象。不加也行,但在某些情况下就会出问题
//this 和下面的例子一起看的话,this就是bingE
}
}
- 操作过程:
- 在通过:this调用全参构造函数的时候,小括号里填需要的参数。如果不需要,就随便输入一个值,反正不会被读取。在调用完全参构造函数,完成给属性赋值后,又调回当前的构造函数,对当前的构造函数里的语句进行执行。
析构函数
- 语法
~类名(){
语句;
}
- 析构函数只在程序结束的时候才会执行
- 析构函数用于帮助我们释放资源。因为Garbage Collection垃圾回收器在有些时候,当程序结束后不会立即清理垃圾,所以需要析构函数来马上清理垃圾。
访问修饰符
- public: 公开的、公共的,哪都可以访问到
- privat: 私有的,只能再当前类的内部进行访问,出了这个类就访问不到了
sentences
There's a thin line between live and death.