Files
Obsidian_Unity/CSharp学习/CSharp入门查漏补缺.md
T
2026-05-03 14:06:26 +08:00

221 lines
6.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#### 一.转义字符
#### 常用的转义字符有:
1.斜杠单引号,表示单引号
例子 string str="\\'哈哈哈\\' "
2.斜杠双引号,表示双引号
例子 string str="\\\\'哈哈哈\\\\' "
3.斜杠+n表示换行
4.双斜杠表示单斜杠
#### 不常用转义字符:
1.制表符t 相当于按下tab,空4格
2.光标退格字符 b,是光标位置向后推一个
3.空字符0 无实际效果
4.警报音 a 系统发出提示音
#### 取消转义字符
在字符串前面加上@,将所有转义字符按照字面显示
### 二:类型转换
#### 隐式类型转换
小转大,不会影响精度,可以自动转换
#### 显示类型转换
##### 1. 括号强转
##### 2.Parse法
将string转成目标类型
##### 3.Convert法
##### 4.其他类型转string ToString()方法
### 三:枚举
枚举是一组命名的整型常量
它的本质是整数类型的包装,枚举在编译后会被编译为一个**密封类(sealed class**,继承自 `System.Enum`(而 `System.Enum` 又继承自 `System.ValueType`),因此枚举是值类型。
每个枚举成员本质上是一个**常量整数**,默认从 `0` 开始递增(也可手动指定值)。例如:
```
public enum Time { yi, er, san }
```
编译后等价于
```
public sealed class Time : Enum
{
public const int yi = 0;
public const int er = 1;
public const int san = 2;
}
```
### 四:接口
常规的接口声明是这样的
```
interface ICanRun
{
void Run();
}
```
我们不需要显式的去写访问修饰符,接口默认的是internal,程序集可见
内部的成员都是public的,继承了接口的方法也是public.
继承了接口的类,他需要实现接口提供的契约方法
```
public class Dog : ICanRun
{
public void Run()
{
}
}
```
以上是我们接口中的常规写法,但是会有一些问题出现.
假如有两个接口,他们内部都有一个Run方法
```
interface ICanFastRun
{
void Run();
}
interface ICanSlowRun
{
void Run();
}
```
那么我们的Dog继承了两个接口,这时候在使用常规的方法去实现接口方法,就会出现一些问题.
我们需要新的方法去解决问题.
==显式接口实现==
我们的Dog在同时继承了两个接口的时候,实现方法应该用显示接口实现
```
public class Dog:ICanFastRun, ICanSlowRun
{
void ICanSlowRun.Run()
{
Console.WriteLine("我可以跑的很慢");
}
void ICanFastRun.Run()
{
Console.WriteLine("我可以跑的很快");
}
}
```
显示接口实现是非常清晰易懂的,让我们知道这个是那个接口的那个方法,但是他会有一些问题,
==我们不能直接在Dog变量里面调用Run方法,需要将Dog强转为接口类型==
```
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog();
dog.Run(); //会报错
}
}
```
这就是麻烦的地方,我们需要对Dog进行类型转换才可以调用对应的Run方法
```
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog();
((ICanFastRun)dog).Run(); //将dog转换为ICanFastRun类型,可移植性跑得快的Run方法
((ICanSlowRun)dog).Run(); //将dog转换为ICanSlowRun类型,可移植性跑得慢的Run方法
}
}
```
显示接口实现,从另一层面来讲,也是对方法的一种隐藏,对API的一种清洁.
### 五:多态中的类型
```
public class Animal
{
}
public class Dog : Animal
{
}
public class Cat : Animal
{
}
class Program
{
static void Main(string[] args)
{
Animal animal = new Animal();
Animal dog = new Dog();
Animal cat = new Cat();
}
}
```
在上述代码当中请问animal,dog,cat他们三个都是什么类型?
答案是两个,他们即是Animal又是Dog(Cat),他们两者兼备
这是需要区分情况的
##### ==在编译时==
他们三者都是Animal类型的
##### ==在运行时==
他们是new 后面的类型
dog是Dog类型
cat是Cat类型
### 六:多态中的new关键字
new关键字在类当中用来隐藏基类的方法
在继承链当中,他会影响运行时多态
```
public class Animal
{
public virtual void Eat()
{ Console.WriteLine("我正在吃");
}
}
public class Dog : Animal
{
public new void Eat()
{
Console.WriteLine("狗正在吃");
}
}
class Program
{
static void Main(string[] args)
{
Animal animal = new Animal();
Animal dog1 = new Dog();
Dog dog2 = new Dog();
animal.Eat();
dog1.Eat();
dog2.Eat();
}
}
```
上面代码的输出结果是
```
我正在吃
我正在吃
狗正在吃
```
关键点在于Animal dog1 = new Dog();
dog1究竟输出的是什么呢?
答案是"我正在吃"
我们要研究为什么他会这样输出
```
1. 编译阶段:编译器只认 “声明类型”,确定要调用的方法​
dog1的声明类型是Animal(=左边),编译器在编译时会做两件事:​
- 检查Animal类中是否有Eat()方法:发现Animal有public virtual void Eat()(虚拟方法),符合调用条件,编译通过;​
- 记录 “要调用的是Animal类的Eat()方法”:因为Dog类的Eat()用new修饰,编译器会将其视为 “子类新增的独立方法”,而非对Animal.Eat()的重写,所以不会将dog1与Dog.Eat()关联。​
简单说:编译时,编译器认为dog1是Animal类型,只能调用Animal的方法,根本 “看不到”Dog类用new隐藏的Eat()。​
2. 运行阶段:CLR 执行 “编译时确定的方法”​
运行时,dog1的实际类型是Dog=右边new Dog()),但 CLR 的执行逻辑受new关键字影响:​
- 若子类用override重写:CLR 会优先执行 “实际类型(Dog)的重写方法”(多态生效);​
- 若子类用new隐藏:CLR 会执行 “编译时确定的父类方法(Animal.Eat())”,因为new修饰的方法与父类方法无关联,CLR 不会去子类中找这个 “独立方法”。​
所以,运行时dog1.Eat()最终执行的是Animal类的Eat(),输出 “我正在吃”。
```
简单的说,new隐藏了方法之后,他在编译时是什么类型,就执行谁的方法.