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);
只是设置了Transform
的localPosition
属性,没有导致新的堆内存分配。
结论:不会产生堆内存分配。
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()
方法分析:
Debug.Log("A");
输出 “A”。yield return Test2();
调用Test2()
方法,并等待它返回。
Test2()
方法分析:
Debug.Log("D");
输出 “D”。yield break;
用于提前结束枚举器,即立即返回到Test1()
中。- 因为使用了
yield break;
,所以下面的代码不会执行,包括Debug.Log("E");
。
回到 Test1()
方法:
-
yield return Test2();
等待Test2()
方法执行完毕。根据上述分析,一旦Test2()
方法执行到yield break;
,它立即返回,因此下面的代码会继续执行。 -
继续执行
Debug.Log("B");
输出 “B”。 -
yield return null;
等待一帧,不执行其他协程或方法。 -
继续执行
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
是一个值类型,当你将i1
和i2
赋值给obj1
和obj2
时,发生了装箱操作(boxing),即i1
和i2
被装箱为System.Object
类型的实例。- 在装箱过程中,
i1
和i2
被封装到不同的System.Object
实例中。
2. 对象比较
obj1
和obj2
都是引用类型object
。- 在
if (obj1 == obj2)
中,==
运算符用于比较两个对象引用是否指向同一个实例。
由于 i1
和 i2
被装箱为不同的 System.Object
实例,因此 obj1
和 obj2
引用的是不同的对象,即使它们的值相同。
更详细的解释
-
装箱(Boxing):
- 装箱是将一个值类型(如
int
)转换为引用类型(object
)的过程。在这个过程中,值类型的值被复制到堆上的一个新对象中。 object obj1 = i1;
:这里i1
的值1
被装箱,创建了一个新的对象,其值为1
。object obj2 = i2;
:这里i2
的值1
也被装箱,创建了另一个新的对象,其值为1
。
- 装箱是将一个值类型(如
-
对象引用比较:
if (obj1 == obj2)
:这实际上是在比较两个引用类型的引用,检查它们是否指向同一个对象实例。- 由于
obj1
和obj2
指向的是两个不同的对象实例,比较结果为false
。
如何实现值比较
如果需要比较两个装箱后的对象的值,可以使用 Equals
方法:
if (obj1.Equals(obj2))
{
Debug.Log("Equals");
}
obj1.Equals(obj2)
将会比较 obj1
和 obj2
所包含的值,由于 obj1
和 obj2
都包含值 1
,因此比较结果为 true
,将会输出 "Equals"
。
总结
- 对象引用比较:默认情况下,
==
运算符比较的是引用类型的引用地址,因此obj1 == obj2
为false
。 - 值比较:使用
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
依赖于集合的状态,而修改集合会破坏迭代器的状态。
解决方法
- 使用
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--; // 因为移除了元素,调整索引以避免跳过下一个元素
}
- 使用倒序遍历:
另一种方法是从后向前遍历集合,这样可以安全地移除元素而不影响未处理的元素。
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);
}
- 使用
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%),对他来说威胁最小。
循环策略
根据以上分析,假设每个人都采取最佳策略,情况可能如下:
-
第一轮:
- 小李故意脱靶。
- 小黄故意脱靶(因为小林还在)。
- 小林攻击小黄并击中(小林消灭最强对手)。
-
第二轮:
- 剩下小李和小林。
- 小李现在必须攻击小林(没有其他选择),但命中率只有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);
}
}
测试输出