文章目录
一、GC 回收机制
垃圾回收,英文简写 GC(Garbage Collector)
垃圾回收的过程是在遍历堆(Heap)上动态分配的所有对象,通过识别它们是否被引用来确定哪些对象是垃圾,哪些对象仍要被使用
所谓的垃圾就是没有被任何变量,对象引用的内容
垃圾就需要被回收释放
垃圾回收有很多种算法,比如:
引用计数(Reference Counting)
标记清除(Mark Sweep)
标记整理(Mark Compact)
复制集合(Copy Collection)
注意:
GC只负责堆(Heap)内存的垃圾回收
引用类型都是存在堆(Heap)中的,所以它的分配和释放都通过垃圾回收机制来管理
栈(Stack)上的内存是由系统自动管理的
值类型在栈(Stack)中分配内存的,他们有自己的生命周期,不用对他们进行管理,会自动分配和释放
C# 中内存回收机制的大概原理
0代内存 1代内存 2代内存
代的概念:
代是垃圾回收机制使用的一种算法(分代算法)
新分配的对象都会被配置在第0代内存中
每次分配都可能会进行垃圾回收以释放内存(0代内存满时)
在一次内存回收过程开始时,垃圾回收器会认为堆中全是垃圾,会进行以下两步
1.标记对象 从根(静态字段、方法参数)开始检查引用对象,标记后为可达对象,未标记为不可达对象,不可达对象就认为是垃圾
2.搬迁对象压缩堆 (挂起执行托管代码线程) 释放未标记的对象 搬迁可达对象 修改引用地址
大对象总被认为是第二代内存 目的是减少性能损耗,提高性能
不会对大对象进行搬迁压缩 85000字节(83kb)以上的对象为大对象
二、成员属性
基本概念
-
用于保护成员变量
-
为成员属性的获取和赋值添加逻辑处理
-
解决 3P (Private、Public、Protected)的局限性
public——内外访问
private——内部访问
protected——内部和子类访问
属性可以让成员变量在外部只能获取、不能修改,或者,只能修改、不能获取
// 访问修饰符 属性类型 属性名
// {
// get{}
// set{}
// }
internal class Person
{
private int age;
private int money;
// 属性的命名一般使用 帕斯卡命名法
public string Name { get; set; }
public int Money {
get =>
//解密处理
money - 5;
set =>
//加密处理
money = value + 5;
}
// get和set可以只有一个
// 注意:
// 只有一个时 没必要在前面加访问修饰符
// 一般情况下 只会出现 只有 get的情况 基本不会出现只有set
public bool Sex { get; }
// set
// {
// sex = value;
// }
// 作用:外部能得不能改的特征
// 如果类中有一个特征是只希望外部能得不能改的 又没什么特殊处理
// 那么可以直接使用自动属性
public float Height {
// 没有再get和set中写逻辑的需求或者想法
get;
private set;
}
// 成员属性中,get和set前可以加访问修饰符
// 注意
// 1.默认不加 会使用属性申明时的访问权限
// 2.加的访问修饰符要低于属性的访问权限
// 3.不能让get和set的访问权限都低于属性的权限
}
1、成员属性概念:一般是用来保护成员变量的
2、成员属性的使用和变量一样 外部用对象点出
3、get 中需要 return 内容 ; set 中用 value 表示传入的内容
4、get 和 set 语句块中可以加逻辑处理
5、get 和 set 可以加访问修饰符,但是要按照一定的规则进行添加
6、get 和 set 可以只有一个
7、自动属性是属性语句块中只有 get 和 set,一般用于 外部能得不能改这种情况
三、索引器
索引器:让对象可以像数组一样通过索引访问其中元素,使程序看起来更直观,更容易编写
// 访问修饰符 返回值 this[参数类型 参数名, 参数类型 参数名.....]
// {
// 内部的写法和规则和属性相同
// get{}
// set{}
// }
internal class Person
{
private int age;
private int[,] array;
private Person[] friends;
private string name;
public Person this[int index] {
get {
// 可以写逻辑的 根据需求来处理这里面的内容
// 索引器中可以写逻辑
if (friends == null ||
friends.Length - 1 < index)
return null;
return friends[index];
}
set {
// value代表传入的值
// 可以写逻辑的 根据需求来处理这里面的内容
if (friends == null)
friends = new[] { value };
else if (index > friends.Length - 1)
// 自己定了一个规则 如果索引越界 就默认把最后一个朋友顶掉
friends[friends.Length - 1] = value;
friends[index] = value;
}
}
// 索引器可以重载
// 重载的概念是——函数名相同 参数类型、数量、顺序不同
public int this[int i, int j] {
get => array[i, j];
set => array[i, j] = value;
}
public string this[string str] {
get {
switch (str)
{
case "name":
return name;
case "age":
return age.ToString();
}
return "";
}
}
}
注意:结构体里面也支持索引器
四、静态成员
概念:用 static 修饰的成员变量、成员方法、成员属性等 就称为静态成员
特点:直接用类名点出来使用(全局性)
生命周期:和程序同生共死
程序运行后就会一直存在内存中,知道程序结束后才会释放,因此静态成员具有唯一性
注意:
1、静态函数中不能直接使用非静态成员
2、非静态函数中可以直接使用静态成员
常量和静态变量
常量是特殊的静态变量
相同点:
他们都可以通过类名点出来使用
不同点:
1、const 必须初始化不能被修改 static 没有这个规则
2、const 只能修饰变量,static 可以修饰很多
3、const 不能写在访问修饰符前面,一定是写在变量申明前面 static 没有这个规则
五、静态类和静态构造函数
(一)静态类
概念:用 static 修饰的类
特点:
1、只能包含静态成员
2、不能被实例化
作用:
1、将常用的静态成员写在静态类中,方便使用
2、静态类不能被实例化,更能体现工具类的唯一性
比如 Console 就是一个静态类
(二)静态构造函数
概念:在构造函数加上 static 修饰
特点:
1、静态类和普通类都可以有
2、不能使用访问修饰符
3、不能有参数
4、只会自动调用一次
作用:在静态构造函数中初始化静态变量
// 1.静态类中的静态构造函数
internal static class StaticClass
{
public static int testInt = 100;
public static int testInt2 = 100;
static StaticClass()
{
Console.WriteLine("静态构造函数");
testInt = 200;
testInt2 = 300;
}
}
// 2.普通类中的静态构造函数
internal class Test
{
public static int testInt = 200;
static Test()
{
Console.WriteLine("静态构造");
}
public Test()
{
Console.WriteLine("普通构造");
}
}
六、拓展方法
概念:为现有非静态变量类型添加新方法
作用:
1、提升程序拓展性
2、不需要再对象中重新写方法
3、不需要继承来添加方法
4、为别人封装的类型写额外的方法
特点:
1、一定是写在静态类中
2、一定是个静态函数
3、第一个参数为拓展目标
4、第一个参数用 this 修饰
语法:
访问修饰符 static 返回值 函数名(this 拓展类名 参数名, 参数类型 参数名,参数类型 参数名....)
七、运算符重载
概念:让自定义类和结构体能够使用运算符
使用关键字 operator
特点:
1、一定是一个公共的静态方法
2、返回值写在 operator 前
3、逻辑处理自定义
作用:让自定义类和结构体对象可以进行运算
注意:
1、条件运算符需要成对实现
2、一个符号可以多个重载
3、不能使用 ref 和 out
基本语法:
public static 返回类型 operator 运算符(参数列表)
internal class Point
{
public int x;
public int y;
public static Point operator +(Point p1, Point p2)
{
var p = new Point();
p.x = p1.x + p2.x;
p.y = p1.y + p2.y;
return p;
}
public static Point operator +(Point p1, int value)
{
var p = new Point();
p.x = p1.x + value;
p.y = p1.y + value;
return p;
}
public static Point operator +(int value, Point p1)
{
var p = new Point();
p.x = p1.x + value;
p.y = p1.y + value;
return p;
}
// 注意 符号需要两个参数还是一个参数
public static Point operator -(Point p1, Point P2)
{
return null;
}
public static Point operator *(Point p1, Point P2)
{
return null;
}
public static Point operator /(Point p1, Point P2)
{
return null;
}
public static Point operator %(Point p1, Point P2)
{
return null;
}
public static Point operator ++(Point p1)
{
return null;
}
public static Point operator --(Point p1)
{
return null;
}
// 注意 符号需要两个参数还是一个参数
public static bool operator !(Point p1)
{
return false;
}
// 注意 符号需要两个参数还是一个参数
public static Point operator |(Point p1, Point p2)
{
return null;
}
public static Point operator &(Point p1, Point p2)
{
return null;
}
public static Point operator ^(Point p1, Point p2)
{
return null;
}
public static Point operator ~(Point p1)
{
return null;
}
public static Point operator <<(Point p1, int num)
{
return null;
}
public static Point operator >> (Point p1, int num)
{
return null;
}
// 1.返回值一般是 bool 值 也可以是其它的
// 2.相关符号必须配对实现
public static bool operator >(Point p1, Point p2)
{
return false;
}
public static bool operator <(Point p1, Point p2)
{
return false;
}
public static bool operator >=(Point p1, Point p2)
{
return false;
}
public static bool operator <=(Point p1, Point p2)
{
return false;
}
public static bool operator ==(Point p1, Point p2)
{
return false;
}
public static bool operator !=(Point p1, Point p2)
{
return false;
}
public static bool operator true(Point p1)
{
return false;
}
public static bool operator false(Point p1)
{
return false;
}
}
不可重载的运算符:
逻辑与(&&) 逻辑或(||)
索引符 []
强转运算符 ()
点.
三目运算符? :
赋值符号=
七、内部类和分部类
(一)内部类
概念:在一个类中再申明一个类
特点:使用时要用包裹者点出自己
作用:亲密关系的变现
注意:访问修饰符作用很大
internal class Person
{
public int age;
public Body body;
public string name;
public class Body
{
private Arm leftArm;
private Arm rightArm;
private class Arm { }
}
}
(二)分部类
概念:把一个类分成几部分申明
关键字:partial
作用:分部描述一个类,增加程序的拓展性
注意:
1、分部类可以写在多个脚本文件中
2、分部类的访问修饰符要一致
3、分部类中不能有重复成员
internal partial class Student
{
public string name;
public bool sex;
partial void Speak();
}
internal partial class Student
{
public int number;
partial void Speak()
{
// 实现逻辑
}
public void Speak(string str) { }
}
(三)分部方法
概念:将方法的申明和实现分离
特点:
1、不能加访问修饰符 默认私有
2、只能在分部类中申明
3、返回值只能是 void
4、可以有参数但不用 out 关键字
局限性大,了解即可
八、继承
继承基本语法:class 类名: 父类名
1、单根性:只能继承一个父类
2、传递性:子类可以继承父类的父类。。。的所有内容
3、访问修饰符 对于成员的影响
4、极奇不建议使用 在子类中申明和父类同名的成员
九、里氏替换原则
里氏替换原则是面向对象七大原则中最重要的原则
概念:任何父类出现的地方,子类都可以替代
重点:语法表现——父类容器装子类对象,因为子类对象包含了父类的所有内容
作用:方便进行对象存储和管理
(一)is 和 as
基本概念
is:判断一个对象是否是指定类对象
返回值:bool 是为真,不是为假
as:将一个对象转换为指定类对象
返回值:指定类型对象
成功返回指定类型对象,失败返回null
基本语法
类对象 is 类名,该语句块会有一个 bool 返回值,true 和 false
类对象 as 类名,该语句块会有一个对象返回值,对象和 null
internal class GameObject { }
internal class Player : GameObject
{
public void PlayerAtk()
{
Console.WriteLine("玩家攻击");
}
}
internal class Monster : GameObject
{
public void MonsterAtk()
{
Console.WriteLine("怪物攻击");
}
}
internal class Boss : GameObject
{
public void BossAtk()
{
Console.WriteLine("Boss攻击");
}
}
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("里氏替换原则");
// 里氏替换原则 用父类容器 装载子类对象
GameObject player = new Player();
GameObject monster = new Monster();
GameObject boss = new Boss();
GameObject[] objects = { new Player(), new Monster(), new Boss() };
if (player is Player)
// Player p = player as Player;
// p.PlayerAtk();
(player as Player).PlayerAtk();
for (var i = 0; i < objects.Length; i++)
if (objects[i] is Player)
(objects[i] as Player)?.PlayerAtk();
else if (objects[i] is Monster)
(objects[i] as Monster)?.MonsterAtk();
else if (objects[i] is Boss)
(objects[i] as Boss)?.BossAtk();
}
}
十、继承中的构造函数
internal class Test
{
public int testI;
public string testStr;
public Test() { }
public Test(int i)
{
testI = i;
}
public Test(int i, string str) : this(i)
{
testStr = str;
}
}
// 继承中的构造函数 基本概念
// 特点
// 当申明一个子类对象时
// 先执行父类的构造函数
// 再执行子类的构造函数
// 注意:
// 1.父类的无参构造 很重要
// 2.子类可以通过base关键字 代表父类 调用父类构造
// 继承中构造函数的执行顺序
// 父类的父类的构造——>。。。父类构造——>。。。——>子类构造
internal class GameObject
{
public GameObject()
{
Console.WriteLine("GameObject的构造函数");
}
}
internal class Player : GameObject
{
public Player()
{
Console.WriteLine("Player的构造函数");
}
}
internal class MainPlayer : Player
{
public MainPlayer()
{
Console.WriteLine("MainPlayer的构造函数");
}
}
// 父类的无参构造函重要
// 子类实例化时 默认自动调用的 是父类的无参构造 所以如果父类无参构造被顶掉 会报错
internal class Father
{
// public Father()
// {
// }
public Father(int i)
{
Console.WriteLine("Father构造");
}
}
internal class Son : Father
{
// 通过base调用指定父类构造
public Son(int i) : base(i)
{
Console.WriteLine("Son的一个参数的构造");
}
public Son(int i, string str) : this(i)
{
Console.WriteLine("Son的两个参数的构造");
}
}
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("继承中的构造函数");
var mp = new MainPlayer();
var s = new Son(1, "123");
}
}
特点:执行顺序 是先执行父类的构造函数 再执行子类的 从老祖宗开始 依次一代一代向下执行
父类中的无参构造函数很重要,如果被顶掉 子类中就无法默认调用无参构造了
解决方法:
1、始终保持申明一个无参构造
2、通过 base 关键字,调用指定父类的构造
注意:
区分 this 和 base 的区别
十一、万物之父和装箱拆箱
(一)万物之父
关键字:object
概念:object 是所有类型的基类,它是一个类(引用类型)
作用:
1、可以利用里氏替换原则,用 object 容器装所有对象
2、可以用来表示不确定类型,作为函数参数类型
(二)装箱拆箱
发生条件:用 object 存值类型(装箱),再把 object 转为值类型(拆箱)
装箱:把值类型用引用类型存储,栈内存会迁移到堆内存中
拆箱:把引用类型存储的值类型取出来,堆内存会迁移到栈内存中
好处:不确定类型时可以方便参数的存储和传递
坏处:存在内存迁移,增加性能消耗
// 装箱
object v = 3;
// 拆箱
var intValue = (int) v;
十二、密封类
使用 sealed 密封关键字修饰的类
作用:让类无法再被继承
意义:加强面向对象程序设计的规范性、结构性、安全性
十三、多态 vob
(一)多态的概念
多态按字面的意思就是“多种状态”
让继承同一父类的子类们 在执行相同方法时有不同的表现(状态)
主要目的:同一父类的对象 执行相同行为(方法)有不同的表现
解决的问题:让同一个对象有唯一行为的特征
(二)vob 的使用
// 运行时多态( vob、抽象函数、接口 )
// v: virtual(虚函数)
// o: override(重写)
// b: base(父类)
internal class GameObject
{
public string name;
public GameObject(string name)
{
this.name = name;
}
// 虚函数 可以被子类重写
public virtual void Atk()
{
Console.WriteLine("游戏对象进行攻击");
}
}
internal class Player : GameObject
{
public Player(string name) : base(name) { }
// 重写虚函数
public override void Atk()
{
// base 的作用
// 代表父类 可以通过base来保留父类的行为
base.Atk();
Console.WriteLine("玩家对象进行攻击");
}
}
internal class Monster : GameObject
{
public Monster(string name) : base(name) { }
public override void Atk()
{
Console.WriteLine("怪物对象进行攻击");
}
}
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("多态vob");
Father f = new Son();
f.SpeakName();
(f as Son).SpeakName();
// 多态的使用
GameObject p = new Player("xxx");
p.Atk();
(p as Player).Atk();
GameObject m = new Monster("xxx");
m.Atk();
(m as Monster).Atk();
}
}
多态:让同一类型的对象,执行相同行为时有
解决的问题: 让同一对象有唯一的行为特征
vob:
v:virtual 虚函数
o:override 重写
b:base 父类
v 和 o 一定是结合使用的,来实现多态
b 是否使用根据实际需求,保留父类行为
十四、抽象类和抽象方法
(一)抽象类
概念:被抽象关键字abstract修饰的类
特点:
1、不能被实例化的类
2、可以包含抽象方法
3、继承抽象类必须重写其抽象方法
(二)抽象方法
又叫纯虚方法,用 abstract 关键字修饰的方法
特点:
1、只能在抽象类中申明
2、没有方法体
3、不能是私有的
4、继承后必须实现,用 override 重写
internal abstract class Fruits
{
public string name;
// 抽象方法 是一定不能有函数体的
public abstract void Bad();
public virtual void Test()
{
// 可以选择是否写逻辑
}
}
internal class Apple : Fruits
{
public override void Bad() { }
// 虚方法是可以由我们子类选择性来实现的
// 抽象方法必须要实现
}
internal class SuperApple : Apple
{
// 虚方法和抽象方法 都可以被子类无限的 去重写
public override void Bad()
{
base.Bad();
}
public override void Test()
{
base.Test();
}
}
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("抽象类和抽象方法");
// 抽象不能被实例化
// Thing t = new Thing();
// 但是 可以遵循里氏替换原则 用父类容器装子类
Thing t = new Water();
}
}
十五、接口
接口是行为的抽象规范,它也是一种自定义类型
关键字 :interface
接口申明的规范
1、不包含成员变量
2、只包含方法、属性、索引器、事件
3、成员不能被实现
4、成员可以不用写访问修饰符,不能是私有的
5、接口不能继承类,但是可以继承另一个接口
接口的使用规范
1、类可以继承多个接口
2、类继承接口后,必须实现接口中所有成员
特点:
1、它和类的申明类似
2、接口是用来继承的
3、接口不能被实例化,但是可以作为容器存储对象
// 接口关键字:interface
// 语法:
// interface 接口名
// {
// }
// 一句话记忆:接口是抽象行为的“基类”
// 接口命名规范 帕斯卡前面加个 I
internal interface IFly
{
string Name { get; set; }
int this[int index] { get; set; }
void Fly();
event Action doSomthing;
}
// 接口用来继承
internal class Animal { }
// 1.类可以继承1个类,n个接口
// 2.继承了接口后 必须实现其中的内容 并且必须是public的
internal class Person : Animal, IFly
{
// 3.实现的接口函数,可以加virtual再在子类重写
public virtual void Fly() { }
public string Name { get; set; }
public int this[int index] {
get => 0;
set { }
}
public event Action doSomthing;
}
// 接口可以继承接口
// 接口继承接口时 不需要实现
// 待类继承接口后 类自己去实现所有内容
internal interface IWalk
{
void Walk();
}
internal interface IMove : IFly, IWalk { }
internal class Test : IMove
{
public int this[int index] {
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
public string Name {
get => throw new NotImplementedException();
set => throw new NotImplementedException();
}
public event Action doSomthing;
public void Fly()
{
throw new NotImplementedException();
}
public void Walk()
{
throw new NotImplementedException();
}
}
// 当一个类继承两个接口
// 但是接口中存在着同名方法时
// 注意:显示实现接口时 不能写访问修饰符
internal interface IAtk
{
void Atk();
}
internal interface ISuperAtk
{
void Atk();
}
internal class Player : IAtk, ISuperAtk
{
// 显示实现接口 就是用 接口名.行为名 去实现
void IAtk.Atk() { }
void ISuperAtk.Atk() { }
public void Atk() { }
}
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("接口");
// 4.接口也遵循里氏替换原则
IFly f = new Person();
IMove im = new Test();
IFly ifly = new Test();
IWalk iw = new Test();
IAtk ia = new Player();
ISuperAtk isa = new Player();
ia.Atk();
isa.Atk();
var p = new Player();
(p as IAtk).Atk();
(p as ISuperAtk).Atk();
p.Atk();
}
}
十六、密封方法
用密封关键字 sealed 修饰的重写函数
作用:让虚方法或者抽象方法之后不能再被重写
特点:和 override 一起出现
十七、命名空间
概念:命名空间是用来组织和重用代码的
作用:就像是一个工具包,类就像是一件一件的工具,都是申明在命名空间中的
// 基本语法
// namespace 命名空间名
// {
// 类
// 类
// }
namespace MyGame
{
internal class GameObject { }
}
namespace MyGame
{
internal class Player : GameObject { }
}
// 不同命名空间中允许有同名类
namespace MyGame2
{
// 在不同的命名空间中 是可以有同名类的
internal class GameObject { }
}
// 命名空间可以包裹命名空间
namespace MyGame
{
namespace UI
{
internal class Image { }
}
namespace Game
{
internal class Image { }
}
}
十八、万物之父中的方法
(一)静态方法
public static bool Equals(object? objA, object? objB);
// 静态方法 Equals 判断两个对象是否相等
// 最终的判断权,交给左侧对象的Equals方法,
// 不管值类型引用类型都会按照左侧对象Equals方法的规则来进行比较
Console.WriteLine(Equals(1, 1));
Test t = new Test();
Test t2 = new Test();
Console.WriteLine(Object.Equals(t, t2));
public static bool ReferenceEquals(object? objA, object? objB);
// 静态方法 ReferenceEquals
// 比较两个对象是否是相同的引用,主要是用来比较引用类型的对象。
// 值类型对象返回值始终是false。
Console.WriteLine(Object.ReferenceEquals(t, t2));
(二)成员方法
public Type GetType();
// 普通方法GetType
// 该方法在反射相关知识点中是非常重要的方法,之后我们会具体的讲解这里返回的Type类型。
// 该方法的主要作用就是获取对象运行时的类型Type,
// 通过Type结合反射相关知识点可以做很多关于对象的操作。
var t = new Test();
var type = t.GetType();
protected object MemberwiseClone ();
// 普通方法 MemberwiseClone
// 该方法用于获取对象的浅拷贝对象,口语化的意思就是会返回一个新的对象,
// 但是新对象中的引用变量会和老对象中一致。
var t2 = t.MemberwiseClone();
(三)虚方法
public virtual bool Equals(object? obj);
// 虚方法Equals
// 默认实现还是比较两者是否为同一个引用,即相当于ReferenceEquals。
// 但是微软在所有值类型的基类System.ValueType中重写了该方法,用来比较值相等。
// 我们也可以重写该方法,定义自己的比较相等的规则
public virtual int GetHashCode();
// 虚方法GetHashCode
// 该方法是获取对象的哈希码
// (一种通过算法算出的,表示对象的唯一编码,不同对象哈希码有可能一样,具体值根据哈希算法决定),
// 我们可以通过重写该函数来自己定义对象的哈希码算法,正常情况下,我们使用的极少,基本不用。
public virtual string? ToString();
// 虚方法ToString
// 该方法用于返回当前对象代表的字符串,我们可以重写它定义我们自己的对象转字符串规则,
// 该方法非常常用。当我们调用打印方法时,默认使用的就是对象的ToString方法后打印出来的内容。
Console.WriteLine(t);
十九、string
// 1.字符串指定位置获取
// 字符串本质是char数组
var str = "xxx";
Console.WriteLine(str[0]);
// 转为char数组
var chars = str.ToCharArray();
Console.WriteLine(chars[1]);
for (var i = 0; i < str.Length; i++) Console.WriteLine(str[i]);
// 2.字符串拼接
str = string.Format("{0}{1}", 1, 3333);
Console.WriteLine(str);
// 3.正向查找字符位置
str = "我是xxx!";
var index = str.IndexOf("x");
Console.WriteLine(index);
index = str.IndexOf("吊");
Console.WriteLine(index);
// 4.反向查找指定字符串位置
str = "我是xxxxxx";
index = str.LastIndexOf("xxx");
Console.WriteLine(index);
index = str.LastIndexOf("xxx");
Console.WriteLine(index);
// 5.移除指定位置后的字符
str = "我是xxxxxx";
str.Remove(4);
Console.WriteLine(str);
str = str.Remove(4);
Console.WriteLine(str);
// 执行两个参数进行移除
// 参数1 开始位置
// 参数2 字符个数
str = str.Remove(1, 1);
Console.WriteLine(str);
// 6.替换指定字符串
str = "我是xxxxxx";
str.Replace("xxx", "aaa");
Console.WriteLine(str);
str = str.Replace("xxx", "aaa");
Console.WriteLine(str);
// 7.大小写转换
str = "ksdfasdfasfasdfsasdfasdf";
str.ToUpper();
Console.WriteLine(str);
str = str.ToUpper();
Console.WriteLine(str);
str.ToLower();
Console.WriteLine(str);
str = str.ToLower();
Console.WriteLine(str);
// 8.字符串截取
str = "xxxxxx";
// 截取从指定位置开始之后的字符串
str.Substring(2);
Console.WriteLine(str);
str = str.Substring(2);
Console.WriteLine(str);
// 参数一 开始位置
// 参数二 指定个数
// 不会自动的帮助你判断是否越界 你需要自己去判断
str = str.Substring(2, 2);
Console.WriteLine(str);
// 9.字符串切割
str = "1_1|2_2|3_3|5_1|6_1|7_2|8_3";
var strs = str.Split('|');
for (var i = 0; i < strs.Length; i++) Console.WriteLine(strs[i]);
二十、StringBuilder
string 是特殊的引用,每次重新赋值或者拼接时会分配新的内存空间,如果一个字符串经常改变会非常浪费空间
C# 提供的一个用于处理字符串的公共类:StringBuilder
主要解决的问题是:修改字符串而不创建新的对象,需要频繁修改和拼接的字符串。可以使用它,可以提升性能
使用前需要引用命名空间
// 初始化 直接指明内容
var str = new StringBuilder("123123123");
Console.WriteLine(str);
// 容量
//StringBuilder存在一个容量的问题,每次往里面增加时 会自动扩容
//获得容量
Console.WriteLine(str.Capacity);
//获得字符长度
Console.WriteLine(str.Length);
// 增删查改替换
// 增
str.Append("4444");
Console.WriteLine(str);
str.AppendFormat("{0}{1}", 100, 999);
Console.WriteLine(str);
// 插入
str.Insert(0, "xxx");
Console.WriteLine(str);
// 删
str.Remove(0, 10);
Console.WriteLine(str);
// 清空
//str.Clear();
//Console.WriteLine(str);
// 查
Console.WriteLine(str[1]);
// 改
str[0] = 'A';
Console.WriteLine(str);
// 替换
str.Replace("1", "x");
Console.WriteLine(str);
// 重新赋值 StringBuilder
str.Clear();
str.Append("123123");
Console.WriteLine(str);
// 判断StringBuilder是否和某一个字符串相等
if (str.Equals("12312")) Console.WriteLine("相等");
二十一、结构体和类的区别
(一)区别概述
结构体和类最大的区别是在存储空间上的,因为结构体是值,类是引用,
因此他们的存储位置一个在栈上,一个在堆上,
通过之前知识点的学习,我相信你能够从此处看出他们在使用的区别——值和引用对象在赋值时的区别。
结构体和类在使用上很类似,结构体甚至可以用面向对象的思想来形容一类对象。
结构体具备着面向对象思想中封装的特性,但是它不具备继承和多态的特性,因此大大减少了它的使用频率。
由于结构体不具备继承的特性,所以它不能够使用 protected 保护访问修饰符。
(二)细节区别
- 结构体是值类型,类是引用类型
- 结构体存在栈中,类存在堆中
- 结构体成员不能使用 protected 访问修饰符,而类可以
- 结构体成员变量申明不能指定初始值,而类可以
- 结构体不能申明无参的构造函数,而类可以
- 结构体申明有参构造函数后,无参构造不会被顶掉
- 结构体不能申明析构函数,而类可以
- 结构体不能被继承,而类可以
- 结构体需要在构造函数中初始化所有成员变量,而类随意
- 结构体不能被静态 static 修饰(不存在静态结构体),而类可以
- 结构体不能在自己内部申明和自已一样的结构体变量,而类可以
- 特别地,结构体可以继承接口,因为接口是行为的抽象
(三)如何选择
- 想要用继承和多态时,直接淘汰结构体,比如玩家、怪物等等
- 对象是数据集合时,优先考虑结构体,比如位置、坐标等等
- 从值类型和引用类型赋值时的区别上去考虑,比如经常被赋值传递的对象,并且
- 改变赋值对象,原对象不想跟着变化时,就用结构体。比如坐标、向量、旋转等等
二十二、抽象类和接口的区别
(一)相同点
- 都可以被继承
- 都不能直接实例化
- 都可以包含方法申明
- 子类必须实现未实现的方法
- 都遵循里氏替换原则
(二)区别
- 抽象类中可以有构造函数;接口中不能
- 抽象类只能被单一继承;接口可以被继承多个
- 抽象类中可以有成员变量;接口中不能
- 抽象类中可以申明成员方法,虚方法,抽象方法,静态方法;接口中只能申明没有实现的抽象方法
- 抽象类方法可以使用访问修饰符;接口中建议不写,默认 public
(三)如何选择
表示对象的用抽象类,表示行为拓展的用接口
不同对象拥有的共同行为,我们往往可以使用接口来实现
举个例子:动物是一类对象,我们自然会选择抽象类;而飞翔是一个行为,我们自然会选择接口。