Unity 笔试题

1. 请回答以下代码片段执行时是否会产生堆内存分配

a. SetChar(string s)

void SetChar(string s)
{
    
    
    s.Replace('b', 'd');
}

分析:

  • string.Replace 方法不会修改原字符串 s,而是返回一个新字符串,因为字符串在 C# 中是不可变的。
  • s.Replace('b', 'd') 返回的新字符串会分配在堆上。

结论:会产生堆内存分配,因为 string.Replace 方法会创建一个新的字符串对象。

b. Update(Transform t)

void Update(Transform t)
{
    
    
    t.localPosition = new Vector3(0, 0, 0);
}

分析:

  • Transform 是一个引用类型,传递的是引用。
  • Vector3 是一个值类型,存储在栈上。
  • t.localPosition = new Vector3(0, 0, 0); 只是设置了 TransformlocalPosition 属性,没有导致新的堆内存分配。

结论:不会产生堆内存分配

c. Sum(List<int> l)

int Sum(List<int> l)
{
    
    
    int total = 0;
    foreach (int i in l)
    {
    
    
        total += i;
    }
    return total;
}

分析:

  • List<int> 是引用类型,传递的是引用。
  • int total = 0;int i 是值类型,存储在栈上。
  • foreach 只是遍历列表,没有分配新的堆内存。

结论:不会产生堆内存分配

d. SetValueAt(List<object> l, int index, int value)

void SetValueAt(List<object> l, int index, int value)
{
    
    
    if (index < 0 || index >= l.Count)
    {
    
    
        return;
    }
    l[index] = value;
}

分析:

  • List<object> 是引用类型,传递的是引用。
  • 检查索引范围的操作和赋值操作本身不会导致新的堆内存分配。
  • value 是一个值类型,但被装箱成 object 存储在列表中。装箱操作会分配新的堆内存。

结论:会产生堆内存分配,因为 int 值类型被装箱成 object 存储在 List<object> 中。

总结

  • a 会产生堆内存分配(string.Replace 创建新字符串)。
  • b 不会产生堆内存分配(仅设置 localPosition 属性)。
  • c 不会产生堆内存分配(遍历列表和累加操作)。
  • d 会产生堆内存分配(值类型 int 被装箱成 object 存储)。

2、以下代码会产生什么样的输出

IEnumerator Test1()
{
    
    
    Debug.Log("A");
    yield return Test2(); 
    
    Debug.Log("B");
    yield return null;

    Debug.Log("C");
}

IEnumerator Test2()
{
    
    
    Debug.Log("D");
    yield break;

    Debug.Log("E");
}

void Start()
{
    
    
    StartCoroutine(Test1());
}
Test1() 方法分析:
  1. Debug.Log("A"); 输出 “A”。
  2. yield return Test2(); 调用 Test2() 方法,并等待它返回。
Test2() 方法分析:
  1. Debug.Log("D"); 输出 “D”。
  2. yield break; 用于提前结束枚举器,即立即返回到 Test1() 中。
  3. 因为使用了 yield break;,所以下面的代码不会执行,包括 Debug.Log("E");
回到 Test1() 方法:
  1. yield return Test2(); 等待 Test2() 方法执行完毕。根据上述分析,一旦 Test2() 方法执行到 yield break;,它立即返回,因此下面的代码会继续执行。

  2. 继续执行 Debug.Log("B"); 输出 “B”。

  3. yield return null; 等待一帧,不执行其他协程或方法。

  4. 继续执行 Debug.Log("C"); 输出 “C”。

总结输出顺序

根据以上分析,代码的输出顺序应该是:

A
D
B
C

解释输出顺序

  • 首先输出 “A”,然后调用 Test2(),输出 “D”。
  • Test2() 中的 yield break; 结束了该协程,并立即返回到 Test1()
  • 继续执行 Debug.Log("B"); 输出 “B”,然后 yield return null; 等待一帧。
  • 最后执行 Debug.Log("C"); 输出 “C”。

这就是整个代码片段的执行流程和输出顺序。

3、请用数学公式描述如何计算向量V的反射向量R(V和N都是单位向量)

在这里插入图片描述

做如下辅助线
在这里插入图片描述

1.  V · N = |V||N|cos(θ ) =  cos(θ )
2.  P = |V| * cos(θ ) * N =( V · N)* N
3.  R = R' = 2P - V = 2(V · N)N - V

4、已知Random.value能够返回一个[0,1]之间均匀分布的随机数,请你构造一个单位圆周上均匀分布的随机点P,和一个单位圆内均匀分布的随机点Q

V = Random.value ;
value = V*2*PI ;
P.x = cos(value);
P.y = sin(value);
Q.x = P.x*V;
Q.y = P.y*V;

5、请写出下面的代码输出的结果

int a = 0x7fffffff;
int b = 0x7fffffff;
int c = a + b;
Console.WriteLine("{0}", c);

答案参考这篇博客

6.以下代码会输出吗,为什么?

在C#中,以下代码段涉及值类型与引用类型的比较:

int i1 = 1;
int i2 = 1;
object obj1 = i1;
object obj2 = i2;

if(obj1 == obj2)
{
    
    
    Debug.Log("Equals");
}

不会输出 "Equals"

原因解释

1. 值类型与引用类型
  • int 是一个值类型,当你将 i1i2 赋值给 obj1obj2 时,发生了装箱操作(boxing),即 i1i2 被装箱为 System.Object 类型的实例。
  • 在装箱过程中,i1i2 被封装到不同的 System.Object 实例中。
2. 对象比较
  • obj1obj2 都是引用类型 object
  • if (obj1 == obj2) 中,== 运算符用于比较两个对象引用是否指向同一个实例。

由于 i1i2 被装箱为不同的 System.Object 实例,因此 obj1obj2 引用的是不同的对象,即使它们的值相同。

更详细的解释

  • 装箱(Boxing)

    • 装箱是将一个值类型(如 int)转换为引用类型(object)的过程。在这个过程中,值类型的值被复制到堆上的一个新对象中。
    • object obj1 = i1;:这里 i1 的值 1 被装箱,创建了一个新的对象,其值为 1
    • object obj2 = i2;:这里 i2 的值 1 也被装箱,创建了另一个新的对象,其值为 1
  • 对象引用比较

    • if (obj1 == obj2):这实际上是在比较两个引用类型的引用,检查它们是否指向同一个对象实例。
    • 由于 obj1obj2 指向的是两个不同的对象实例,比较结果为 false

如何实现值比较

如果需要比较两个装箱后的对象的值,可以使用 Equals 方法:

if (obj1.Equals(obj2))
{
    
    
    Debug.Log("Equals");
}

obj1.Equals(obj2) 将会比较 obj1obj2 所包含的值,由于 obj1obj2 都包含值 1,因此比较结果为 true,将会输出 "Equals"

总结

  • 对象引用比较:默认情况下,== 运算符比较的是引用类型的引用地址,因此 obj1 == obj2false
  • 值比较:使用 Equals 方法可以比较装箱对象的实际值,此时 obj1.Equals(obj2)true

七、下列代码在运行中会发生什么问题?如何避免?

List<int> ls = new List<int>(new int[] {
    
     1, 2, 3, 4, 5 });
foreach (int item in ls)
{
    
    
    Console.WriteLine(item * item);
    ls.Remove(item);  // 尝试在遍历时移除元素
}

问题描述

foreach 循环中尝试修改集合(例如 Remove 元素)会抛出 InvalidOperationException。这是因为 foreach 依赖于集合的状态,而修改集合会破坏迭代器的状态。

解决方法

  1. 使用 for 循环
    使用普通的 for 循环可以安全地修改集合。
List<int> ls = new List<int> {
    
     1, 2, 3, 4, 5 };
for (int i = 0; i < ls.Count; i++)
{
    
    
    Console.WriteLine(ls[i] * ls[i]);
    ls.RemoveAt(i);
    i--; // 因为移除了元素,调整索引以避免跳过下一个元素
}
  1. 使用倒序遍历
    另一种方法是从后向前遍历集合,这样可以安全地移除元素而不影响未处理的元素。
List<int> ls = new List<int> {
    
     1, 2, 3, 4, 5 };
for (int i = ls.Count - 1; i >= 0; i--)
{
    
    
    Console.WriteLine(ls[i] * ls[i]);
    ls.RemoveAt(i);
}
  1. 使用 RemoveAll 方法
    如果需要根据某个条件移除元素,可以使用 RemoveAll 方法。
List<int> ls = new List<int> {
    
     1, 2, 3, 4, 5 };
ls.ForEach(item => Console.WriteLine(item * item));
ls.RemoveAll(item => item % 2 == 1); // 示例:移除所有奇数元素

思考题

三个小伙子同时爱上了一个姑娘,为了决定他们谁能娶这个姑娘,他们决定用手枪进行一次决斗。小李的命中率是30%,小黄比他好些,命中率是50%,最出色的枪手是小林,他从不失误,命中率是100%。由于这个显而易见的事实,为公平起见,他们决定按这样的顺序:小李先开枪,小黄第二,小林最后。然后这样循环,直到他们只剩下一个人。那么这三个人中谁活下来的机会最大呢?他们都应该采取什么样的策略?

初步分析

  • 小李(命中率30%):

    • 小李在第一个轮次,命中率最差,他的最佳策略是故意脱靶。因为如果他击中了目标(小黄或小林),剩下的对手会立即把注意力转向他。
  • 小黄(命中率50%):

    • 小黄在第二个轮次,他的命中率比小李高,但不如小林。他的最佳策略也可能是故意脱靶,特别是在小林还在的时候。这是因为击中对手会导致小林立即攻击他。
  • 小林(命中率100%):

    • 小林在第三个轮次,他的命中率是100%。他是最强的射手,他的最佳策略是击中一个对手,并且优先攻击最强的对手(小黄),以减少下一轮的威胁。

深入策略分析

小李的策略
  • 小李在第一个轮次如果击中一个对手(假设是小黄),会导致小林下一轮直接攻击他(小李)。因此,小李应该选择故意脱靶,以增加自己存活到下一轮的机会。
小黄的策略
  • 小黄在第二个轮次,如果小李已经脱靶,他面对小林时也应该考虑故意脱靶,特别是在三人都还在的情况下。因为如果他击中小李,小林下一轮会直接攻击他。
小林的策略
  • 小林的策略是明确的,他应该击中最有威胁的对手。如果小黄还在,小林应该击中小黄。这样他会面对小李(命中率30%),对他来说威胁最小。

循环策略

根据以上分析,假设每个人都采取最佳策略,情况可能如下:

  1. 第一轮

    • 小李故意脱靶。
    • 小黄故意脱靶(因为小林还在)。
    • 小林攻击小黄并击中(小林消灭最强对手)。
  2. 第二轮

    • 剩下小李和小林。
    • 小李现在必须攻击小林(没有其他选择),但命中率只有30%。
    • 小林如果小李没有击中,则会在下一轮击中并消灭小李。

结论

  • 小林的活下来的机会最大。因为他的命中率是100%,他能有效地消灭对手,尤其是在小黄被消灭后,小李的威胁最小。
  • 策略
    • 小李:第一轮故意脱靶。
    • 小黄:第一轮故意脱靶(只要小林还在)。
    • 小林:优先击中最强对手(小黄),然后在剩下的轮次中消灭小李。

因此,小林将成为这场决斗中最终存活的可能性最大的那个人。

测试代码如下

using UnityEngine;
using Random = System.Random;

public class DuelSimulation : MonoBehaviour
{
    
    
    static Random random = new Random();
    
    private void Start()
    {
    
    
        Main();
    }

    static bool Shoot(double accuracy)
    {
    
    
        return random.NextDouble() < accuracy;
    }

    static void Main()
    {
    
    
        const int simulations = 10000;
        int liWins = 0, huangWins = 0, linWins = 0;

        for (int i = 0; i < simulations; i++)
        {
    
    
            bool liAlive = true;
            bool huangAlive = true;
            bool linAlive = true;

            while ((liAlive && huangAlive) || (huangAlive && linAlive) || (liAlive && linAlive))
            {
    
    
                // Li's turn
                if (liAlive)
                {
    
    
                    if (huangAlive)
                    {
    
    
                        huangAlive = !Shoot(0.30);
                    }
                    else if (linAlive)
                    {
    
    
                        linAlive = !Shoot(0.30);
                    }
                }

                // Huang's turn
                if (huangAlive)
                {
    
    
                    if (linAlive)
                    {
    
    
                        linAlive = !Shoot(0.50);
                    }
                    else if (liAlive)
                    {
    
    
                        // Huang shoots at Li
                        liAlive = !Shoot(0.50);
                    }
                }

                // Lin's turn
                if (linAlive)
                {
    
    
                    if (huangAlive)
                    {
    
    
                        // Lin shoots at Huang
                        huangAlive = false;
                    }
                    else if (liAlive)
                    {
    
    
                        // Lin shoots at Li
                        liAlive = false;
                    }
                }
            }

            if (liAlive) liWins++;
            if (huangAlive) huangWins++;
            if (linAlive) linWins++;
        }

        Debug.Log("After " + simulations + " simulations:");
        Debug.Log("Li's probability of winning: " + (double)liWins / simulations);
        Debug.Log("Huang's probability of winning: " + (double)huangWins / simulations);
        Debug.Log("Lin's probability of winning: " + (double)linWins / simulations);
    }
}

测试输出
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_40924071/article/details/139580111