面向对象(C# )
文章目录
- 面向对象(C# )
- ref 和 out
- 传值调用和引用调用
- ref 和 out 的使用
- ref 和 out 的区别
- 结构体
- 垃圾回收GC
- 封装
- 成员属性
- 索引器
- 静态成员
- 静态类
- 静态构造函数
- 拓展方法
- 运算符重载
- 内部类和分布类
- 继承
- 里氏替换
- 继承中的构造函数
- 装箱拆箱
- 密封类
- 多态
- `virtual`、`override`、`base`
- 抽象类
- 抽象方法(纯虚函数)
- 接口
- 密封方法
ref 和 out
传值调用和引用调用
void ChangeValue(int value)
{
a = 3;
}
// 主函数
int a = 11;
ChangeValue(a);
Console.WriteLine(a);
输出:11
传值调用中形参为一个新的临时变量,赋值由实参拷贝而来,只是赋予了与实参一样的值所以在函数体内部修改并不会影响实参
void Changevalue(int[] array)
{
array[0] = 22;
}
// 主函数
int[] a = {1, 2, 3};
Changevalue(a);
Console.WriteLine(a[0]);
输出:22
引用调用时,形参拷贝的是实参的地址,二者指向同一个堆空间,所以形参改变会对实参造成影响
void Changevalue(int[] array)
{
array = new int[] {10, 20, 30};
}
// 主函数
int[] a = {1, 2, 3};
Changevalue(a);
Console.WriteLine(a[0]);
输出:1
单纯的记忆传值调用和引用调用可以改变和不能改变仍然是不太正确的,需要从栈和堆数据存储的角度去分析才行
ref 和 out 的使用
void ChangeValue1(ref int value)
{
a = 3;
}
void Changevalue2(ref int[] array)
{
array = new int[] {10, 20, 30};
}
// 主函数
int a = 11;
ChangeValue1(ref a);
Console.WriteLine(a);
int[] a = {1, 2, 3};
Changevalue2(ref a);
Console.WriteLine(a[0]);
输出:3、10
out类似
ref 和 out 的区别
- ref 传入的变量必须初始化,但是在函数体内部可以不去赋值
- out传入的变量可以不需要初始化,但是在函数体内部必须赋值
结构体
-
结构体内部不能直接进行初始化
-
结构体的存储方式:
储存结构体中不同类型的数据结构时,会以4个字节为一个单元进行存储,当四个字节容纳不下下一个类型的数据时,编译器会将这个单元中没有存放数据的剩余内存空下,转而存放在下一个单元中
-
结构体内部不能定义自身
定义自身这个结构体会导致循环
-
结构体不能定义无参构造函数
垃圾回收GC
-
垃圾回收通过遍历堆上被动态分配的对象,识别哪些对象是垃圾选择释放,垃圾是没有被任何变量或对象引用的内容
-
GC只负责堆上的垃圾回收,栈上的内存是系统自动管理的,会自动分配和释放
-
垃圾回收机制是一种算法(分代算法)
当第 n(0、1、2)代内存满的时候就触发垃圾回收释放内存,垃圾回收开始时默认堆中所有都是垃圾,从根出发检查引用对象,对可达对象进行标记,未标记的结束不可达对象为垃圾。然后释放未标记对象,搬迁可达对象到下一代
-
大对象(83kb以上)总是被认为是第二代内存,目的是减少搬迁的性能损耗
封装
成员属性
-
作用:保护成员变量,为成员属性的获取和赋值添加逻辑处理,解决3p的局限性:get、set方法
-
访问权限:
默认与声明属性权限相同
自定义权限需要低于属性权限,get \ set 不能同时低于属性权限
-
格式:
public string Name { get // 默认继承public { return name; // 获取 } set { name = value;// value关键字用来表示外部传入的值 } }
-
自动属性
public float Heigt // 相当于一个成员属性封装了getset的方法 { get; // 自动获得 set; // 自动赋值 }
索引器
-
格式:
class Person { private Person[] freinds; // 范围权限 返回值 this[参数类型 参数名, 参数类型 参数名, …] public Person this[int index] { get { return freinds[index]; } set { freinds[index] = value; } } }
-
索引器可以重载
静态成员
-
程序开始运行时就会为静态成员分配内存空间,静态成员直到程序结束才会被释放。所有静态成员拥有自己唯一的一块内存区域,在任何地方使用这块内存都会改变
-
静态成员方法:
静态函数不能使用非静态成员,因为非静态成员需要对象实例化才有内存分配,所以静态函数只能使用静态成员
-
常量和静态变量的区别
相同:都可以直接通过类名加.调用
不同:const常量不能被修改,必须赋初值
静态类
- 作用:常用静态成员写在静态类中方便调用,静态类不能被实例化更能体现工具类的唯一性
静态构造函数
-
特点:
静态类和普通类都可以使用,不能使用访问修饰符,不能有参数
只会自动调用一次,当第一次使用这个类时
拓展方法
-
作用:为现有的非静态类添加新的方法,不需要在类中重新添加方法(我理解就是直接写了一个函数在原本的类中,只是写的方式不同)
-
特点:
一定写在静态类中,一定是静态函数
-
格式:
static class test // 写在静态类中 { // 访问修饰符 static 返回值 函数名(this 拓展的类型 参数名, 参数类型 参数名, …) public static void display(this int value) // 为int拓展一个方法 { Console.WriteLine("int拓展方法"); } }
运算符重载
-
作用:让自定义类和结构体对象可以进行运算
-
注意:
一定是静态的公共的方法
不能使用ref 和 out
参数至少有一个是自身类型
条件运算符需要成对出现,例如:
== 和 !=
、> 和 <
参数个数根据运算符规则定
-
格式:
class Point { int x, y; public Point(int x, int y) { this.x = x; this.y = y; } // public static 返回类型 opeartor 重载运算符(参数列表) public static Point operator+(Point p1, Point p2) { return new Point(p1.x + p2.x, p1.y + p2.y); } }
内部类和分布类
-
内部类
-
在一个类中再申明一个类,表示类之间的层级关系,注意访问修饰符
-
格式:
class Person { public int age; public class Arm // 公共的才能在外部访问 { public int size; } }
// 主函数 Person.Arm arm = new Person.Arm();
-
-
分布类
-
作用:把一个类分成几部分申明,增加程序拓展性
-
关键字:
partial
-
格式:
partial class Student { public bool sex; public string name; } partial class Student // 分别申明 { public int number; public void Speak(string s) { Console.WriteLine(s); } }
-
继承
里氏替换
-
作用:父类对象装载子类对象
-
is
和as
GameObject p = new Player(); // Player是GameObject的子类 if (p is Player) // 判断对象是否是执行类对象,返回bool { Console.WriteLine("是Player类"); } Player p2 = p as Player; // 转换成功返回对象,失败返回null
继承中的构造函数
- 创建子类对象时会先调用父类的构造函数,后调用子类的构造函数
- 子类默认使用父类的无参构造函数,所以需要注意父类的构造函数是否有被有参构造顶掉。或者使用base关键字指定调用父类有参构造
装箱拆箱
-
装箱:把值类型用引用类型存储,栈内存迁移到堆内存中
-
拆箱:把引用类型用值类型存储,堆内存迁移到栈内存中
-
例子:
object obj = 1; // 装箱 int i = (int)obj; // 拆箱
密封类
-
作用:让类无法再被继承
-
例子:
sealed class Father // 无法再被继承 { }
多态
virtual
、override
、base
- 作用:让继承同一父类的子类,在执行相同方法时有不同的表现
- virtual(虚函数)、override(重写)、base(父类)
抽象类
- 抽象是把多个事物相同的内容抽取出来,但是自己本身是没有实际实体的,(水果没有实体,苹果才是实体)
- 不能被实例化,抽象类必须重写其中的抽象方法
- 仍然可以用父类容器装载子类对象
抽象方法(纯虚函数)
-
只能在抽象类中申明
-
没有方法体
-
不能是私有的,因为子类需要重写
-
继承后必须重写(override)抽象方法
接口
-
一个类可以继承多个接口,接口用于描述有共同方法或属性的类,成员方法没有方法体,只是定义共同需要的方法至少需要什么,不能包含成员变量,可以有方法、属性、索引器、事件
-
默认修饰符是public,因为需要被继承后的类重写所以不能设置私有
-
接口也不能被实例化,但是可以做父类容器装载子类对象
-
代码
interface IPereson // 命名规则前缀I { }
密封方法
- 作用:让虚方法或抽象方法之后不能再被重写
- 关键字:sealed