Unity面试和总结分析(五)


今天参加了一场面试,其中有几个关于c#中string的题目我觉得很有意思,这里拿出来分享一下。

题目0,热个身

题目内容:
在C#中,对一个string str赋值,str = nullstr = ""有什么区别?

这个问题很简单,我们都知道在C#中,string是一个特殊的自带类,它有着类似于struct的性质,但实际上是一个引用类型,这也是为什么可以赋值为null的原因。
那这两个有什么区别呢?
当然是一个是null一个是空字符串啦(逃

说白了,null这个值就是个空引用,如果用它来调各种各样的string中的成员方法肯定会抛出空引用的错误,而空字符串它也是一个空字符串,它只是长度为0,还是可以用它来调用方法的。
当然,最好的办法是测试:
在这里插入图片描述
其实这个问题在微软官方文档中也有解释:String文档,并且还给出了建议的判空方式,也就是使用IsNullOrEmptyIsNullOrWhiteSpace
在这里插入图片描述

题目1,关于C#中String的编码格式

题目内容:
在c#中有这样一段代码,请问最后输出的内容是什么?

     string str = "abcdefg某某某";
     byte[] strArr = System.Text.Encoding.Default.GetBytes(str);
     Console.WriteLine(str.Length);
     Console.WriteLine(strArr.Length);

这个题目考察的内容很明确,就是考察对编码的一点基本认识,只要了解C#中string的编码就能得出答案。
不过因为很久没有注意这个问题,我只隐约记得似乎以前初学的时候见过这样的说法:

c#中一个汉字使用两个字节表示

于是我在做题的时候,写上了10和13,分别是string中字符的个数和编码的字节个数。
回来以后我始终不太确定答案,于是建了一个控制台项目进行了测试,结果令我感到奇怪——

显示的结果竟然是10和16!

看到16的时候我第一反应是,这个长度正好是单个字符 * 7 + 汉字字符 * 3 ,也就是1*7+3*3=16,这个长度大概率是UTF-8,于是我加了一行代码:

     string str = "abcdefg某某某";
     byte[] strArr = System.Text.Encoding.Default.GetBytes(str);
     Console.WriteLine(str.Length);
     Console.WriteLine(strArr.Length);
     Console.WriteLine(System.Text.Encoding.Default.BodyName); //新增的一行代码

结果果然不出我所料,输出了UTF-8!再确认了一下某字在UTF-8中的编码:
在这里插入图片描述
嗯,没错了。

难道真的是我写错了?于是我又上网看了一些其他人的测试,果然也有人说是一个汉字两个字符。
脑筋一转,为了确认一下是不是编码问题,我去查了一下GB2312中“某”字的编码,确认确实是两个字节:
在这里插入图片描述
于是我怀疑是自己的PC设置有问题,检查了一下系统语言,也并没有勾选unicode编码,说明系统还是用的GB2312,那为什么我的程序里会用这个呢?
在这里插入图片描述
本着原因可能一下找不到的心态,我想先试试直接使用GB2312来检查一下长度,于是我在微软的官方文档中找到了GB2312的代码页:936
在这里插入图片描述
但是在运行的时候直接报错了:
在这里插入图片描述
简单翻译一下,这里提示说不包含代码页为936的编码数据,这下我更奇怪了!

思考了一下又想到另一个可能——我是不是有可能用的是.NET Core的控制台,而不是.NET Framework!因为.NET Core是为多平台准备的,很可能默认使用的就是UTF-8!这两个平台的具体区别我就不展开讲了,有兴趣的可以自己去查查。
接下来我检查了一下我VS的设置,果然默认是.NET Core的,翻了翻,终于在很后面找到了.NET Framework的控制台程序:
在这里插入图片描述
在这里插入图片描述

我怀着强烈的激动建了一个.NET Framework的项目,然后把最开始一样的代码放到了这里:
在这里插入图片描述
果然!结果是13!

不过这一样来又出现了另外一个问题,.NET Core和.NET Framework使用了不一样的默认编码,那么mono作为一个多平台的环境,是不是也不一样呢?
——老规矩,一样的代码在unity里再跑一次:
在这里插入图片描述

结果来了,mono和.NET Core不一样,还是支持GB2312的,但是它默认使用的Encoding却是UTF-8。

题目1的正确答案

所以上面这个面试题的正确答案是什么呢?

前提:没有手动修改过Default的Encoding。
可能性1 :在.NET Framework平台的项目中,是GB2312编码,它使用两个字节表示一个汉字,答案是 10 13;
可能性2 :在.NET Core和Unity平台的项目中,是UTF-8编码,对于“某”字,它使用三个字节来表示,那么答案是 10 16

题目2,关于C#中new String的问题

题目内容:
在C#中有这样一行代码:

String str = new String("");

它会在内存中生成几个String Object对象?

这个问题我着实反应了好一会儿,因为使用c++这一段时间里一直对构造函数很敏感,并且c++的堆栈处理方式有一些区别,看到new关键字的时候也会特别注意。
话扯得有点远,让我们还是来看代码。
在这里插入图片描述
籍由C#的优秀关键字提醒,我们可以看到在通过这种方式创建一个string的时候,实际上调用的是String(ReadOnlySpan<char> value)这个构造函数,那么这个泛型ReadOnlySpan<T>又是个什么呢?

这时候就要请出官方文档了:
在这里插入图片描述
文档说得很清楚,简单来说,使用这个泛型构造的对象只能存在于上,而不能出现在托管堆上(堆和栈的区别这里就不展开了,这算是非常重要的基础知识)。
当然,更重要的性质在这个ref结构关键字上:
在这里插入图片描述
一定要仔细看,这个ref结构很关键!
在这几条内容下,将这种类型的使用限制得非常死,这样的类只要使用了,那么就必然会跟随当前的栈被清掉,甚至也无法被制作成闭包!这基本可以算是一个彻底的临时变量。

那么回到我们的题目上来,在使用new String("")这种方式来构造一个string的时候,string还是会被放在堆里,但是它会在栈上生成一个ReadOnlySpan<char>类型的临时变量,然后再根据这个临时变量来在堆中创建一个string。

在此之外还有另外几个来自书籍 CLR via C# 的知识点:

  1. 在CLR的机制中,对string有一个“留用”机制,也就是说,如果发现某个字符串曾经被创建过,那么在之后再次遇到这个字符串的时候会再使用它,而不创建一个新的StringObject。
  2. 在C#的早期版本中,是不支持非unsafe模式下new String这种形式创建string的,但是之后放开了,如果不可以使用new的形式,而由系统提供,那么可以很大程度上利用在堆中的StringObject。
  3. 对于字面值字符串,也就是我们直接写入的"aaa"这种形式的字符串,C#在编译时只会写入一次,而在后面直接调用这个字符串。
  4. 对于上面所说的“留用”机制,也是可以关闭的,也就是说即使使用直接赋值的形式,也会直接创建不同的StringObject,但默认都是开启的。

上面说了这么多,到底使用new的形式跟我们直接使用string str = ""有什么区别呢?区别有下面点:

  1. 使用new方式会创建一个新的string,不会管这个string是否在内存中已经存在,也就是说必然会创建一个新的,而使用直接赋值的形式会重用堆中已经存在的StringObject;
  2. 使用new方式创建的对象,很可能不会被重用——举个例子,假设我要创建一个字符串aaa,即使之前没有使用过aaa,在下一次使用字面值字符串的形式对一个string赋值的时候,得到的这个StringObject与之前的那个StringObject也不是同一个。
  3. 使用new方式创建时,还会额外创建一个ReadOnlySpan<char>类型的临时变量。

最后,还是来做一下测试:

            string str_1 = "a";
            String str_2 = new String(str_1);
            String str_3 = str_1;
            String str_4 = "a";
            String str_5 = new String("a");


            Console.WriteLine("str_1 - str_2 \t" + ReferenceEquals(str_1,str_2));
            Console.WriteLine("str_1 - str_3 \t" + ReferenceEquals(str_1,str_3));
            Console.WriteLine("str_1 - str_4 \t" + ReferenceEquals(str_1,str_4));
            Console.WriteLine("str_1 - str_5 \t" + ReferenceEquals(str_1,str_5));
            Console.WriteLine("str_2 - str_5 \t" + ReferenceEquals(str_2,str_5));

内容很简单:
str_1是一个字面值字符串赋值的字符串
str_2是使用str_1使用new形式创建的一个字符串
str_3是直接使用str_1来赋值的字符串
str_4是完全跟str_1一样使用字面值字符串赋值的字符串
str_5是一个使用字面值字符串来new的字符串

然后设计了五组对比:
第一组对比是str_1与str_2,这一组对比是为了证明在托管堆中,使用new形式会创建一个新的StringObject,我们的预期结果是false;
第二组对比是str_1与str_3,这一组是为了证明使用字符串赋值时,在托管堆中实际上使用的是同一个SringObject,预期结果是true;
第三组对比是str_1与str_4,这一组是为了证明在托管堆中,存在“留用”的机制预期结果是true;
第四组对比是str_1与str_5,这一组对比是为了证明在托管堆中,即使是使用字符串字面值来new,也会创建一个新的StringObject,我们的预期结果是false;
第五组对比是str_2与str_5,这一组是为了不同次数的new形式之间也会生成不同的StringObject,我们的预期结果是false;

开始我们的测试:
在这里插入图片描述
与我们的预期一致。

题目2的正确答案

使用new来创建,会在内存中生成一个新的StringObject对象,但是同时,会在栈上生成一个ReadOnlySpan<char>类型的临时变量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值