C#
- 什么是面向对象,和面向过程区别。
点击查看代码
面向对象就是把一个问题拆分成不同的对象,然后给这些对象赋予他们的属性和方法,让这些对象去执行他们的方法,最终解决问题。
面向过程就是把事情拆分成一个个数据和函数,然后按照一定顺序执行完这些方法,事情也就解决。
简单的说:C# 与C++ 比较的话,最重要的特性 就是C# 是一种完全面向对象的语言,而C++ 不 是,另外C# 是基于IL 中间语言和.Net,可维护性和强壮性很大改进。
2.五大基本原则
点击查看代码
单一职责原则:类的功能要单一,不能太复杂。就像一个人一样不能分配太多的任务,虽然很忙碌,但是效率不高。
开放封闭原则:一个模块在扩展性应该是开放的,在更改性应该是封闭的。例如一个模块,已有服务端模块,想要增加客户端模块,那么在设计之初就应该把两端分开,而不是在服务端代码上面增加客户端代码。
里式替换原则:子类可以替换父类,并且可以出现在父类的任何地方。例如公司的所有员工都可以抽奖,而不是让几个员工可以抽奖。
依赖倒置原则:具体依赖抽象,上层依赖下层。B比A低,B定义一个抽象接口,A来实现这个接口。
接口分离原则:模块之间要通过抽象接口隔离开,而不是通过具体的类强耦合起来。
3.面向对象的特征?如何设计和实现面向对象?
点击查看代码
封装:将数据和行为相结合,用行为来约束代码,从而增加数据安全性。
继承:用来拓展类,子类可用继承父类的部分行为和属性,从而便于管理和提高代码复用。
多态:一个对象,不同情形有不同的表现形势。
把不同的功能封装成不同的类,通过继承和多态来实现
- 静态构造函数
点击查看代码
在首次被使用之前调用,且只调用一次。没有参数也没有访问修饰符。
- 不安全代码
点击查看代码
当代码被unsafe标记时,c#允许在函数中使用指针变量
6.值类型和引用类型
点击查看代码
* 值类型储存在内存栈中,超过作用域自动清理,在作用域结束后会被清理,而引用类型储存在内存堆中,由GC自动释放
* 值类型转换为引用类型(装箱)要经历分配内存和拷贝数据
* 引用类型转换为值类型(拆箱)要首先找到属于值类型的地址,再把引用类型的值拷贝到内存栈中的值实例中。
* 值类型存储速度快,引用类型慢。
* 值类型表示实际的数据,引用类型表示储存在内存堆中的数据的指针的引用
* 值类型的基类是ValuaType,引用类型的基类是Object;
* 值类型在内存管理方面有更好的效率,且不支持多态,适合用作储存数据的载体,引用类型适合多态,适合用作定义应用程序的行为。
* 值类型:byte,short,int...
* 引用类型:arry,class,interface,delegate,Object,string
- 接口和抽象类
点击查看代码
接口
* 接口不能实例化
* 类可以实现一个或多个接口
抽象类
* 对同类事务具体的抽象;例如我会玩游戏,如果你继承了我,你也得会玩
* 不能实例化,可以作为基类
* 设计目的是为了代码复用
* 一个类可以有多个接口,但是只能继承一个父类
- 字段,属性,索引器
点击查看代码
private string _name;//_name为字段
public string Name //Name为属性,它含有代码块
public int this[int index]//this索引器,获取数组内容
{
get { return array[index]; }
set { array[index] = value; }
}
9.什么是lambda表达式
点击查看代码
一种委托实例的匿名方法(就是一个方法没有名字),可以使代码更加简洁。
//不使用Lambda表达式时的一个点击事件
public class button : MonoBehaviour
{
public Button btn;
void Start ()
{
btn.onClick.AddListener(btnClick);
}
void btnClick()
{
Debug.Log("按钮被点击了!");
}
}
-------------------------------------------------------------------
-------------------------------------------------------------------
//使用Lambda表达式时的一个点击事件
public class button : MonoBehaviour
{
public Button btn;
void Start ()
{
//无参匿名函数
btn.onClick.AddListener(()=>
{
Debug.Log("按钮被点击了");
}
}
}
- Func和Action
点击查看代码
* Func是泛型委托类型,指定返回类型的方法。
Func<int,int,int> add=(x,y)=>x+y;//前两个为传的参数,最后一个为返回值
int result =add(3,5);// 结果为 8
* Action 也是一个委托类型,但是没有返回值。
Action<string> logMessage=message=>Console.WriteLine(message);
LogMessage("Hello,World");
- foreach和for区别
点击查看代码
foreach从头到尾,对集合中的对象进行遍历。在遍历的时候,对象是只读的,不能对元素更改。
for通过下标,对循环中的代码反复执行,功能强大,可以通过index索引去取得元素。
* foreach效率低,内存开销大。但是在处理不确定循环次数或者循环次数需要计算的时候,使用foreach更加方便
- is和as
点击查看代码
is返回bool值
object obj = "Hello";
if (obj is string)
{
Console.WriteLine("obj is a string");
}
as是强转
object obj = "Hello";
string str = obj as string;
if (str != null)
{
Console.WriteLine("Successfully cast to string: " + str);
}
- Async和Await
点击查看代码
Async用于声明异步方法
async Task<string> DownloadDataAsync()
{
HttpClient client = new HttpClient();
string result = await client.GetStringAsync("https://example.com");
return result;
}
Await用于等异步操作完成,获取结果
string data = await DownloadDataAsync();
Console.WriteLine("Downloaded data: " + data);
- string stringBuilder和stringBuffer区别
点击查看代码
都是处理字符串的类
string;字符串常量
1. 是不可变的,一但创建就不能被修改
2. 对string创建实际上就是创建新的string
3. 因为每次修改都是创建,所以产生额外的内存开销
stringBuilder:字符串变量
1. 可变
2. 性能比string 好,特别是在需要多次修改字符串时
3. 用Append来追加字符串,而不是创建新的字符串
4. 单线程会更加高效
stringBuffer:
1. 可变,线程安全
点击查看代码
1. 使用 string
由于 string 是不可变的,每次对字符串进行修改都会生成一个新的字符串对象。以下是一个连接字符串的例子:
string str = "Hello";
str += ", World"; // 实际上这里创建了一个新的字符串对象
Console.WriteLine(str); // 输出 "Hello, World"
2. 使用 StringBuilder
StringBuilder 提供了一种可变的方式来构建字符串,适用于需要多次修改字符串内容的场景,比如循环中不断追加字符串:
StringBuilder sb = new StringBuilder();
sb.Append("Hello");
sb.Append(", World");
Console.WriteLine(sb.ToString()); // 输出 "Hello, World"
3. 使用 stringBuffer
stringBuffer 与 StringBuilder 类似,但它是线程安全的。这意味着在多线程环境中,stringBuffer 可以确保字符串操作的安全性。以下是使用 stringBuffer 的示例:
StringBuffer sb = new StringBuffer();
sb.Append("Hello");
sb.Append(", World");
Console.WriteLine(sb.ToString()); // 输出 "Hello, World"
15.什么是GC垃圾管理器?产生的原因?如何避免?
点击查看代码
避免内存溢出而产生的回收机制
(例如忘记释放内存或者释放了仍在使用的内存,造成内存泄漏和错误。)
* 减少new 产生的对象次数
* 使用公用对象(静态)
* string 换成stringBuilder
16.属性,特性
点击查看代码
特性是可以放在脚本中的类、属性或函数上方来指示特殊行为的标记,如下
[Serializable]//我是特性,代表这个类可以被序列化
public class HumanBase
{
public string Name { get; set; }
public int Age { get; set; }
public int Gender { get; set; }
}
- 泛型是什么,有什么好处?
点击查看代码
是一种编程机制,允许在编写代码时使用参数化类型
* 代码复用:可以让代码变得更加通用,可以处理多种不同类型的数据
* 类型安全: 泛型在编译的时候会进行编译检查,可以提前发现类型错误,从而避免在运行时出现类型不匹配
* 性能优化:泛型在编译时会生成特定类型的代码,避免了装箱拆箱,提高了执行效率。
* 抽象数据结构:泛型可以定义抽象的数据结构,可以适用不同类型的数据,提高数据结构的通用性和灵活性
* 更好的API设计:可以提供更加抽象和通用的接口,满足不同场景的需求
- Ref和Out关键字什么区别?
点击查看代码
两者都用于参数传递
Ref:
将参数引用传递给方法,这意味着对参数的任何更改都会影响原始变量
在调用方法前,必须初始化Ref参数
public class Program
{
public static void Main()
{
int number = 10; // 初始化变量
RefExample(number);
Console.WriteLine(number); // 输出修改后的值,15
}
public static void RefExample(ref int number)
{
number += 5; // 修改引用传递的变量
}
}
Out:
也用于引用传递参数,但通常从方法返回多个值
不需要初始化,但在退出之前必须为Out参数赋值
public class Program
{
public static void Main()
{
// 不需要初始化
int number;
OutExample(out number);
Console.WriteLine(number); // 输出方法中赋的值,25
}
public static void OutExample(out int number)
{
number = 25; // 方法内部赋值
}
}
- 引用和指针的区别
点击查看代码
引用:
* 间接访问对象,更安全和易于使用的方式
* 大多数情况,使用引用来操作对象,而不是直接操作内存地址
* 引用不直接暴露内存地址,而是提供一种更高级别的抽象,使对象创建销毁操作更加简单和安全
* 在编译的时候就已经确定对象的类型,因此是类型安全的
public class MyClass
{
public int Value { get; set; }
}
public class Program
{
public static void Main()
{
MyClass obj = new MyClass { Value = 10 };
MyClass refObj = obj;
// 修改引用对象的属性
refObj.Value = 20;
// obj的Value也被改变了,因为refObj是obj的引用
Console.WriteLine(obj.Value); // 输出 20
}
}
指针:
* 是一种直接访问内存地址的方式,在c#中使用指针通常需要使用不安全代码快,并且谨慎处理避免内存错误
* 对内存中特定位置直接访问能力,允许我们绕过编译时类型检查和安全检查
* 在特殊情况下提供更加高效的内存操作,但是增加了代码的复杂性和风险
* unsafe<details>
<summary>点击查看代码</summary>
</details>
{
public class Program
{
public static void Main()
{
int number = 10;
int* pNumber = &number;
// 直接通过指针修改值
(*pNumber) += 5;
Console.WriteLine(number); // 输出 15
}
}
}
综上所述,引用更加安全和高级别的访问方式,指针比较底层和危险
点击查看代码
Stack栈,Heap堆
heap是堆,stack是栈(先进后出)。
栈自动系统管理,堆的空间是手动申请和释放的, 堆常用new关键字来分配。
栈空间有限,堆的空间是很大的自由区。
栈快堆慢。
栈适合存储局部变量和函数调用等临时数据,堆更适合存储动态分配和数据结构
// 创建一个栈实例
Stack
// 入栈操作:将元素压入栈中
stack.Push(1);
// 在堆上动态分配内存
int[] heapArray = new int[5]; // 默认初始化为0
// 使用new关键字分配对象
Car myCar = new Car();
点击查看代码
解耦是将系统中的各个模块之间的耦合度降到最低,依赖关系减弱。
* 接口抽象:定义接口来描述模块之间的通信和交互方式,而不是直接依赖具体的实现类
* 依赖注入:通过依赖注入的方式将之前的依赖关系交给外部管理。
* 事件驱动:一个模块产生事件,其他模块监听并响应事件
* 消息队列:通过消息队列来实现模块之间的异步通信。
* 中介者模式:引入中介者来管理模块之间的通信,将复杂的交互逻辑集中到中介者中,减少直接依赖
内聚成员之间相互联系的紧密程度:
* 功能内聚:各个成员完成同一个功能,。
* 顺序内聚:一个成员的输出作为下一个成员的输入。
* 通信内聚:成员之间通过传递数据进行通信,彼此交流频繁
* 过程内聚:各个成员都在完成某个过程或算法。
* 时间内聚:在某个时间段内一起执行
高内聚低耦合。提高可理解性,可维护性,可拓展性,同时降低代码的复杂度
- throw和catch
点击查看代码
public void Divide(int numerator, int denominator)
{
if (denominator == 0)
{
throw new ArgumentException("分母不能为零。");
}
Console.WriteLine($"结果是:{numerator / denominator}");
}
public static void Main()
{
try
{
Divide(10, 0);
}
catch (ArgumentException ex)
{
Console.WriteLine($"捕获到异常:{ex.Message}");
}
}
try
{
// 尝试执行的代码
int result = 10 / 0;
}
catch (DivideByZeroException ex)
{
// 当发生特定类型的异常时执行的代码
Console.WriteLine($"捕获到除以零的异常:{ex.Message}");
}
catch (Exception ex)
{
// 当发生任何其他类型的异常时执行的代码
Console.WriteLine($"捕获到异常:{ex.Message}");
}
finally
{
// 无论是否发生异常都会执行的代码
Console.WriteLine("这是 finally 块。");
}
- 重载和重写
点击查看代码
public class Calculator
{
// 加法方法重载
public int Add(int a, int b)
{
return a + b;
}
// 加法方法重载
public double Add(double a, double b)
{
return a + b;
}
// 加法方法重载
public string Add(string a, string b)
{
return a + b;
}
}
public class Program
{
public static void Main()
{
Calculator calc = new Calculator();
Console.WriteLine(calc.Add(1, 2)); // 输出 3
Console.WriteLine(calc.Add(1.5, 2.3)); // 输出 3.8
Console.WriteLine(calc.Add("Hello, ", "World!")); // 输出 "Hello, World!"
}
}
点击查看代码
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("Some generic sound");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Bark");
}
}
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine("Meow");
}
}
public class Program
{
public static void Main()
{
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.MakeSound(); // 输出 "Bark"
myCat.MakeSound(); // 输出 "Meow"
}
}
点击查看代码
* 封装、继承、多态所处位置不同,重载在同类中,重写在父子类中。
* 定义方式不同,重载方法名相同参数列表不同,重写方法名和参数列表都相同。
* 调用方式不同,重载使用相同对象以不同参数调用,重写用不同对象以相同参数调用。
* 多态时机不同,重载时编译时多态,重写是运行时多态。
- 装箱和拆箱
点击查看代码
装箱:值类型转换为引用类型
int a =1;
object b=1;//a被装箱为a
拆箱: 引用类型转换为值类型
object a=1;
int b=(int)a;//拆箱,a被拆成int类型
点击查看代码
* public:公开
* private:对类公开
* protected:对类及其派生公开
* internal:只能在该类的程序集中访问该类
- .Net和Mono关系
点击查看代码
.Net是一个语言平台,Mono为.Net提供集成开发环境
- C#函数 Func(string a, string b)用 Lambda 表达式怎么写?
点击查看代码
(a,b)=>{};
28.C#函数 Func(string a, string b)用 Lambda 表达式怎么写?
点击查看代码
public int a(int n) {
if (n == 1 || n == 2) {
return 1;
} else {
return a(n -1) + a(n-2);
}
}